Skip to content

Commit

Permalink
Migrate Perl validation checks into netkan.exe
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Jun 19, 2019
1 parent 74ff9af commit 2153070
Show file tree
Hide file tree
Showing 20 changed files with 436 additions and 20 deletions.
18 changes: 18 additions & 0 deletions Netkan/CKAN-netkan.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<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="Newtonsoft.Json.Schema">
<HintPath>..\_build\lib\nuget\Newtonsoft.Json.Schema.3.0.11\lib\net45\Newtonsoft.Json.Schema.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
Expand Down Expand Up @@ -131,6 +134,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");
}
}
16 changes: 16 additions & 0 deletions Netkan/Validators/KrefDownloadMutexValidator.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 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");
}
}
}
}
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
35 changes: 35 additions & 0 deletions Netkan/Validators/ObeysCKANSchemaValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Schema;
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 = JSchema.Parse(reader.ReadToEnd());
}
}

public void Validate(Metadata metadata)
{
var json = metadata.Json();
IList<string> messages;
if (!json.IsValid(schema, out messages))
{
string msg = messages.Aggregate((a, b) => $"{a}\r\n{b}");
throw new Kraken($"Schema validation failed: {msg}");
}
}

private static readonly JSchema 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

0 comments on commit 2153070

Please sign in to comment.