Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Perl validation checks into netkan.exe #2788

Merged
merged 1 commit into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cmdline/packages.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommandLineParser" version="1.9.71" targetFramework="net45" />
<package id="log4net" version="2.0.8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net45" />
</packages>
21 changes: 21 additions & 0 deletions Netkan/CKAN-netkan.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\_build\lib\nuget\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NJsonSchema" Version="10.0.19">
<HintPath>..\_build\lib\nuget\NJsonSchema.10.0.19\lib\net45\NJsonSchema.dll</HintPath>
</Reference>
<Reference Include="Namotion.Reflection" Version="1.0.5">
<HintPath>..\_build\lib\nuget\Namotion.Reflection.1.0.5\lib\net45\Namotion.Reflection.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -131,6 +137,21 @@
<Compile Include="Validators\KrefValidator.cs" />
<Compile Include="Validators\MatchingIdentifiersValidator.cs" />
<Compile Include="Validators\NetkanValidator.cs" />
<Compile Include="Validators\AlphaNumericIdentifierValidator.cs" />
<Compile Include="Validators\DownloadVersionValidator.cs" />
<Compile Include="Validators\InstallValidator.cs" />
<Compile Include="Validators\KrefDownloadMutexValidator.cs" />
<Compile Include="Validators\LicensesValidator.cs" />
<Compile Include="Validators\MatchesKnownGameVersionsValidator.cs" />
<Compile Include="Validators\ObeysCKANSchemaValidator.cs" />
<Compile Include="Validators\OverrideValidator.cs" />
<Compile Include="Validators\ReplacedByValidator.cs" />
<Compile Include="Validators\RelationshipsValidator.cs" />
<Compile Include="Validators\SpecVersionFormatValidator.cs" />
<Compile Include="Validators\VersionStrictValidator.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="../CKAN.schema" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
Expand Down
15 changes: 7 additions & 8 deletions Netkan/Model/Metadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ internal sealed class Metadata

private readonly JObject _json;

// FIXME: Alignment
public string Identifier { get { return (string)_json["identifier"]; } }
public RemoteRef Kref { get; private set; }
public RemoteRef Vref { get; private set; }
public ModuleVersion SpecVersion { get; private set; }
public ModuleVersion Version { get; private set; }
public Uri Download { get; private set; }
public DateTime? RemoteTimestamp { get; private set; }
public string Identifier { get { return (string)_json["identifier"]; } }
public RemoteRef Kref { get; private set; }
public RemoteRef Vref { get; private set; }
public ModuleVersion SpecVersion { get; private set; }
public ModuleVersion Version { get; private set; }
public Uri Download { get; private set; }
public DateTime? RemoteTimestamp { get; private set; }

public Metadata(JObject json)
{
Expand Down
2 changes: 1 addition & 1 deletion Netkan/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public static int Main(string[] args)
var netkan = ReadNetkan();
Log.Info("Finished reading input");

new NetkanValidator().Validate(netkan);
new NetkanValidator(Options.File).Validate(netkan);
Log.Info("Input successfully passed pre-validation");

var transformer = new NetkanTransformer(
Expand Down
21 changes: 21 additions & 0 deletions Netkan/Validators/AlphaNumericIdentifierValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text.RegularExpressions;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class AlphaNumericIdentifierValidator : IValidator
{
public void Validate(Metadata metadata)
{
if (!alphanumeric.IsMatch(metadata.Identifier))
{
throw new Kraken("CKAN identifiers must consist only of letters, numbers, and dashes, and must start with a letter or number.");
}
}

private static readonly Regex alphanumeric = new Regex(
@"^[A-Za-z0-9-]+$",
RegexOptions.Compiled
);
}
}
4 changes: 3 additions & 1 deletion Netkan/Validators/CkanValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public CkanValidator(Metadata netkan, IHttpService downloader, IModuleService mo
{
new IsCkanModuleValidator(),
new MatchingIdentifiersValidator(netkan.Identifier),
new InstallsFilesValidator(downloader, moduleService)
new InstallsFilesValidator(downloader, moduleService),
new MatchesKnownGameVersionsValidator(),
new ObeysCKANSchemaValidator()
};
}

Expand Down
16 changes: 16 additions & 0 deletions Netkan/Validators/DownloadVersionValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class DownloadVersionValidator : IValidator
{
public void Validate(Metadata metadata)
{
var json = metadata.Json();
if (json.ContainsKey("download") && !json.ContainsKey("version"))
{
throw new Kraken($"{metadata.Identifier} expects a version when a download url is provided");
}
}
}
}
56 changes: 56 additions & 0 deletions Netkan/Validators/InstallValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Newtonsoft.Json.Linq;
using CKAN.Versioning;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class InstallValidator : IValidator
{
public void Validate(Metadata metadata)
{
var json = metadata.Json();
if (json.ContainsKey("install"))
{
foreach (JObject stanza in json["install"])
{
string install_to = (string)stanza["install_to"];
if (metadata.SpecVersion < v1p2 && install_to.StartsWith("GameData/"))
{
throw new Kraken("spec_version v1.2+ required for GameData with path");
}
if (metadata.SpecVersion < v1p12 && install_to.StartsWith("Ships/"))
{
throw new Kraken("spec_version v1.12+ required to install to Ships/ with path");
}
if (metadata.SpecVersion < v1p16 && install_to.StartsWith("Ships/@thumbs"))
{
throw new Kraken("spec_version v1.16+ required to install to Ships/@thumbs with path");
}
if (metadata.SpecVersion < v1p4 && stanza.ContainsKey("find"))
{
throw new Kraken("spec_version v1.4+ required for install with 'find'");
}
if (metadata.SpecVersion < v1p10 && stanza.ContainsKey("find_regexp"))
{
throw new Kraken("spec_version v1.10+ required for install with 'find_regexp'");
}
if (metadata.SpecVersion < v1p16 && stanza.ContainsKey("find_matches_files"))
{
throw new Kraken("spec_version v1.16+ required for 'find_matches_files'");
}
if (metadata.SpecVersion < v1p18 && stanza.ContainsKey("as"))
{
throw new Kraken("spec_version v1.18+ required for 'as'");
}
}
}
}

private static readonly ModuleVersion v1p2 = new ModuleVersion("v1.2");
private static readonly ModuleVersion v1p4 = new ModuleVersion("v1.4");
private static readonly ModuleVersion v1p10 = new ModuleVersion("v1.10");
private static readonly ModuleVersion v1p12 = new ModuleVersion("v1.12");
private static readonly ModuleVersion v1p16 = new ModuleVersion("v1.16");
private static readonly ModuleVersion v1p18 = new ModuleVersion("v1.18");
}
}
20 changes: 20 additions & 0 deletions Netkan/Validators/KrefDownloadMutexValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class KrefDownloadMutexValidator : IValidator
{
public void Validate(Metadata metadata)
{
var json = metadata.Json();
if (json.ContainsKey("download") && json.ContainsKey("$kref"))
{
throw new Kraken($"{metadata.Identifier} has a $kref and a download field, this is likely incorrect");
}
if (!json.ContainsKey("download") && !json.ContainsKey("$kref"))
{
throw new Kraken($"{metadata.Identifier} has no $kref field, this is required when no download url is specified");
}
}
}
}
60 changes: 60 additions & 0 deletions Netkan/Validators/LicensesValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using CKAN.Versioning;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class LicensesValidator : IValidator
{
public void Validate(Metadata metadata)
{
var json = metadata.Json();
JArray licenses = !json.ContainsKey("license") ? null
: json["license"] is JArray
? (JArray)json["license"]
: new JArray() { json["license"] };
if (licenses != null)
{
foreach (var lic in licenses)
{
if (metadata.SpecVersion < v1p2 && (string)lic == "WTFPL")
{
throw new Kraken("spec_version v1.2+ required for license 'WTFPL'");
}
if (metadata.SpecVersion < v1p18 && (string)lic == "Unlicense")
{
throw new Kraken("spec_version v1.18+ required for license 'Unlicense'");
}
}
}
var kref = (string)json["$kref"] ?? "";
if (!metanetkan.IsMatch(kref) && !json.ContainsKey("x_netkan_license_ok"))
{
if (licenses == null || licenses.Count < 1)
{
throw new Kraken("License should match spec. Set `x_netkan_license_ok` to supress");
}
else foreach (var lic in licenses)
{
try
{
// This will throw BadMetadataKraken if the license isn't known
new CKAN.License((string)lic);
}
catch
{
throw new Kraken($"License {lic} should match spec. Set `x_netkan_license_ok` to supress");
}
}
}
}

private static readonly Regex metanetkan = new Regex(
@"^#/ckan/netkan/",
RegexOptions.Compiled
);
private static readonly ModuleVersion v1p2 = new ModuleVersion("v1.2");
private static readonly ModuleVersion v1p18 = new ModuleVersion("v1.18");
}
}
19 changes: 19 additions & 0 deletions Netkan/Validators/MatchesKnownGameVersionsValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CKAN.GameVersionProviders;
using CKAN.Versioning;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class MatchesKnownGameVersionsValidator : IValidator
{
public void Validate(Metadata metadata)
{
var mod = CkanModule.FromJson(metadata.Json().ToString());
var knownVersions = new KspBuildMap(new Win32Registry()).KnownVersions;
if (!mod.IsCompatibleKSP(new KspVersionCriteria(null, knownVersions)))
{
throw new Kraken($"{metadata.Identifier} doesn't match any valid game version");
}
}
}
}
18 changes: 15 additions & 3 deletions Netkan/Validators/NetkanValidator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.IO;
using System.Collections.Generic;
using CKAN.NetKAN.Model;

Expand All @@ -7,12 +8,23 @@ internal sealed class NetkanValidator : IValidator
{
private readonly List<IValidator> _validators;

public NetkanValidator()
public NetkanValidator(string filename)
{
_validators = new List<IValidator>
_validators = new List<IValidator>()
{
new SpecVersionFormatValidator(),
new HasIdentifierValidator(),
new KrefValidator()
new KrefValidator(),
new MatchingIdentifiersValidator(Path.GetFileNameWithoutExtension(filename)),
new AlphaNumericIdentifierValidator(),
new RelationshipsValidator(),
new LicensesValidator(),
new KrefDownloadMutexValidator(),
new DownloadVersionValidator(),
new OverrideValidator(),
new VersionStrictValidator(),
new ReplacedByValidator(),
new InstallValidator(),
};
}

Expand Down
36 changes: 36 additions & 0 deletions Netkan/Validators/ObeysCKANSchemaValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using NJsonSchema;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class ObeysCKANSchemaValidator : IValidator
{
static ObeysCKANSchemaValidator()
{
var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedSchema);
using (var reader = new StreamReader(resourceStream))
{
schema = JsonSchema.FromJsonAsync(reader.ReadToEnd()).Result;
}
}

public void Validate(Metadata metadata)
{
var errors = schema.Validate(metadata.Json());
if (errors.Any())
{
string msg = errors
.Select(err => $"{err.Path}: {err.Kind}")
.Aggregate((a, b) => $"{a}\r\n{b}");
throw new Kraken($"Schema validation failed: {msg}");
}
}

private static readonly JsonSchema schema;
private const string embeddedSchema = "CKAN.NetKAN.CKAN.schema";
}
}
32 changes: 32 additions & 0 deletions Netkan/Validators/OverrideValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Newtonsoft.Json.Linq;
using CKAN.NetKAN.Model;

namespace CKAN.NetKAN.Validators
{
internal sealed class OverrideValidator : IValidator
{
public void Validate(Metadata metadata)
{
var json = metadata.Json();
var overrides = json["x_netkan_override"];
if (overrides != null)
{
if (!(overrides is JArray))
{
throw new Kraken("Netkan overrides require an array");
}
foreach (JObject ovr in overrides)
{
if (!ovr.ContainsKey("version"))
{
throw new Kraken("Netkan overrides require a version");
}
if (!ovr.ContainsKey("delete") && !ovr.ContainsKey("override"))
{
throw new Kraken("Netkan overrides require a delete or override section");
}
}
}
}
}
}
Loading