diff --git a/Core/CKAN-core.csproj b/Core/CKAN-core.csproj
index 69f7596df7..8858b5b317 100644
--- a/Core/CKAN-core.csproj
+++ b/Core/CKAN-core.csproj
@@ -53,7 +53,11 @@
..\_build\lib\nuget\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+
+ ..\_build\lib\nuget\NJsonSchema.9.10.6\lib\net45\NJsonSchema.dll
+
+
@@ -132,10 +136,12 @@
+
+
diff --git a/Core/CKAN.schema b/Core/CKAN.schema
new file mode 100644
index 0000000000..3aab11ee07
--- /dev/null
+++ b/Core/CKAN.schema
@@ -0,0 +1,423 @@
+{
+ "$schema" : "http://json-schema.org/draft-04/schema#",
+ "title" : "CKAN JSON Schema",
+ "description" : "Schema for validating CKAN meta files",
+ "type" : "object",
+ "properties" : {
+ "spec_version" : {
+ "description" : "Version of the CKAN spec this document uses",
+ "oneOf" : [
+ {
+ "type" : "integer",
+ "description" : "The integer '1' is special case",
+ "minimum" : 1,
+ "maximum" : 1
+ },
+ {
+ "type" : "string",
+ "description" : "A vx.x string",
+ "pattern" : "^v[0-9]+\\.[0-9]+$"
+ }
+ ]
+ },
+ "name" : {
+ "description" : "Human readable name of the mod",
+ "type" : "string"
+ },
+ "identifier" : {
+ "description" : "Unique identifier for this mod.",
+ "$ref" : "#/definitions/identifier"
+ },
+ "kind" : {
+ "description" : "Package type, defaults to package.",
+ "enum" : [ "package", "metapackage" ]
+ },
+ "abstract" : {
+ "description" : "Short description of the mod",
+ "type" : "string"
+ },
+ "description" : {
+ "description" : "Longer description of the mod",
+ "type" : "string"
+ },
+ "comment" : {
+ "description" : "Free-form comment. Not displayed to users.",
+ "type" : "string"
+ },
+ "author" : {
+ "description" : "Author, or list of authors.",
+ "oneOf" : [
+ { "type" : "string" },
+ {
+ "type" : "array",
+ "items" : { "type" : "string" },
+ "uniqueItems" : true
+ }
+ ]
+ },
+ "download" : {
+ "description" : "URL where mod can be downloaded by tools",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "download_size" : {
+ "description" : "The size of the download in bytes",
+ "type" : "integer"
+ },
+ "download_hash" : {
+ "description" : "A object of hashes of the downloaded file",
+ "type" : "object",
+ "properties" : {
+ "sha1" : {
+ "description" : "SHA1 hash of the file",
+ "type" : "string"
+ },
+ "sha256" : {
+ "description" : "SHA256 hash of the file",
+ "type" : "string"
+ }
+ }
+ },
+ "download_content_type" : {
+ "description" : "The content type of the download",
+ "type" : "string"
+ },
+ "license" : {
+ "description" : "Machine readable license, or array of licenses",
+ "$ref" : "#/definitions/licenses"
+ },
+ "version" : {
+ "description" : "Version of the mod. Drop the leading v",
+ "$ref" : "#/definitions/version"
+ },
+ "release_status" : {
+ "description" : "Optional release status",
+ "enum" : [ "stable", "testing", "development" ]
+ },
+ "ksp_version" : {
+ "description" : "Optional target KSP version",
+ "$ref" : "#/definitions/ksp_version"
+ },
+ "ksp_version_min" : {
+ "description" : "Optional minimum KSP version",
+ "$ref" : "#/definitions/ksp_version"
+ },
+ "ksp_version_max" : {
+ "description" : "Optional maximum KSP version",
+ "$ref" : "#/definitions/ksp_version"
+ },
+ "ksp_version_strict" : {
+ "description" : "Optional enforcement of strict version checks (defaults to false)",
+ "type" : "boolean"
+ },
+ "tags" : {
+ "description" : "A series of descriptive keywords to provide quick filtering and organization",
+ "type" : "array",
+ "items" : {
+ "type" : "string",
+ "pattern" : "^[a-z0-9-]+$"
+ }
+ },
+ "depends" : {
+ "description" : "Optional list of dependencies",
+ "$ref" : "#/definitions/relationship"
+ },
+ "recommends" : {
+ "description" : "Optional list of recommended mods",
+ "$ref" : "#/definitions/relationship"
+ },
+ "suggests" : {
+ "description" : "Optional list of recommended, but not essential mods",
+ "$ref" : "#/definitions/relationship"
+ },
+ "supports" : {
+ "description" : "Optional list of supported mods",
+ "$ref" : "#/definitions/relationship"
+ },
+ "conflicts" : {
+ "description" : "Optional list of conflicting mods",
+ "$ref" : "#/definitions/relationship"
+ },
+ "provides" : {
+ "description" : "A list of virtual packages this mod provides",
+ "type" : "array",
+ "items" : { "type" : "string" },
+ "uniqueItems" : true
+ },
+ "resources" : {
+ "description" : "Additional resources",
+ "type" : "object",
+ "properties" : {
+ "homepage" : {
+ "description" : "Mod homepage",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "bugtracker" : {
+ "description" : "Mod bugtracker",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "license" : {
+ "description" : "Mod license",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "repository" : {
+ "description" : "Mod repository",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "ci" : {
+ "description" : "Continuous Integration",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "spacedock" : {
+ "description" : "Project on SpaceDock",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "curse" : {
+ "description" : "Project on Curse",
+ "type" : "string",
+ "format" : "uri"
+ },
+ "manual" : {
+ "description" : "Mod's manual",
+ "type" : "string",
+ "format" : "uri"
+ }
+ }
+ },
+ "install" : {
+ "description" : "List of install directives",
+ "type" : "array",
+ "items" : {
+ "type" : "object",
+ "properties" : {
+ "file" : {
+ "description" : "Path to file to install",
+ "type" : "string"
+ },
+ "find" : {
+ "description" : "A directory to find for installation",
+ "type" : "string"
+ },
+ "find_regexp" : {
+ "description" : "A regexp that matches the directory to install",
+ "type" : "string"
+ },
+ "find_matches_files" : {
+ "description" : "If true, find directives match files as well as directories",
+ "type" : "boolean"
+ },
+ "install_to" : {
+ "description" : "Where file should be installed to",
+ "$ref" : "#/definitions/install_to"
+ },
+ "as": {
+ "description" : "The name to give the matching directory or file when installed",
+ "type" : "string",
+ "pattern" : "^[^\\\\/]+$"
+ },
+ "filter" : {
+ "description" : "List of files and directories that should be filtered from the install",
+ "$ref" : "#/definitions/one_or_more_strings"
+ },
+ "filter_regexp" : {
+ "description" : "List of regexps that should filter files from this install",
+ "$ref" : "#/definitions/one_or_more_strings"
+ },
+ "include_only" : {
+ "description" : "List of files and directories that should not be excluded from the install",
+ "$ref" : "#/definitions/one_or_more_strings"
+ },
+ "include_only_regexp" : {
+ "description" : "List of regexps that should include files in this install",
+ "$ref" : "#/definitions/one_or_more_strings"
+ }
+ },
+ "not" : {
+ "anyOf" : [
+ {
+ "required" : [ "filter", "include_only" ]
+ },
+ {
+ "required" : [ "filter", "include_only_regexp" ]
+ },
+ {
+ "required" : [ "filter_regexp", "include_only" ]
+ },
+ {
+ "required" : [ "filter_regexp", "include_only_regexp" ]
+ }
+ ]
+ },
+ "required" : [ "install_to" ],
+ "oneOf": [
+ {
+ "required": [ "file" ]
+ },
+ {
+ "required": [ "find" ]
+ },
+ {
+ "required": [ "find_regexp" ]
+ }
+ ]
+ }
+ }
+ },
+ "required" : [
+ "spec_version",
+ "name",
+ "abstract",
+ "identifier",
+ "license",
+ "version"
+ ],
+ "oneOf": [
+ {
+ "required": [ "download" ]
+ },
+ {
+ "properties": {
+ "kind": {
+ "enum": [ "metapackage" ]
+ }
+ },
+ "not": {
+ "required": [ "download" ]
+ },
+ "required": [ "kind" ]
+ }
+ ],
+ "definitions" : {
+ "identifier" : {
+ "description" : "Unique identifier for this mod.",
+ "type" : "string",
+ "pattern" : "^[A-Za-z0-9-]*$"
+ },
+ "version" : {
+ "description" : "A version string",
+ "type" : "string"
+ },
+ "ksp_version" : {
+ "description" : "A version of KSP",
+ "type" : "string",
+ "pattern" : "^(any|[0-9]+\\.[0-9]+(\\.[0-9]+)?)$"
+ },
+ "relationship" : {
+ "description" : "A relationship to a list of mods",
+ "type" : "array",
+ "items" : {
+ "type" : "object",
+ "properties" : {
+ "name" : {
+ "description" : "Identifier of the mod",
+ "$ref" : "#/definitions/identifier"
+ },
+ "version" : {
+ "description" : "Optional version",
+ "$ref" : "#/definitions/version"
+ },
+ "min_version" : {
+ "description" : "Optional minimum version",
+ "$ref" : "#/definitions/version"
+ },
+ "max_version" : {
+ "description" : "Optional maximum version",
+ "$ref" : "#/definitions/version"
+ }
+ },
+ "required" : [ "name" ]
+ }
+ },
+ "install_to" : {
+ "description" : "Where file should be installed to. As of v1.2, GameData may take a path",
+ "oneOf" : [
+ {
+ "description" : "Spec version 1 targets",
+ "enum" : [ "GameData", "Ships", "GameRoot", "Tutorial", "Scenarios" ]
+ },
+ {
+ "description" : "Spec v1.2 GameData path",
+ "type" : "string",
+ "pattern" : "^GameData/"
+ },
+ {
+ "description" : "Spec v1.12 Ships subfolder paths",
+ "type" : "string",
+ "pattern" : "^Ships/(SPH|VAB)$"
+ },
+ {
+ "description" : "Spec v1.16 thumbs paths",
+ "type" : "string",
+ "pattern" : "^Ships/@thumbs/(SPH|VAB)$"
+ }
+ ]
+ },
+ "license" : {
+ "description" : "A license.",
+ "enum" : [
+ "public-domain",
+ "AFL-3.0",
+ "AGPL-3.0",
+ "Apache", "Apache-1.0", "Apache-2.0",
+ "APSL-2.0",
+ "Artistic", "Artistic-1.0", "Artistic-2.0",
+ "BSD-2-clause", "BSD-3-clause", "BSD-4-clause",
+ "ISC",
+ "CC-BY", "CC-BY-1.0", "CC-BY-2.0", "CC-BY-2.5", "CC-BY-3.0", "CC-BY-4.0",
+ "CC-BY-SA", "CC-BY-SA-1.0", "CC-BY-SA-2.0", "CC-BY-SA-2.5", "CC-BY-SA-3.0", "CC-BY-SA-4.0",
+ "CC-BY-NC", "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5", "CC-BY-NC-3.0", "CC-BY-NC-4.0",
+ "CC-BY-NC-SA", "CC-BY-NC-SA-1.0", "CC-BY-NC-SA-2.0", "CC-BY-NC-SA-2.5", "CC-BY-NC-SA-3.0", "CC-BY-NC-SA-4.0",
+ "CC-BY-NC-ND", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-2.0", "CC-BY-NC-ND-2.5", "CC-BY-NC-ND-3.0", "CC-BY-NC-ND-4.0",
+ "CC-BY-ND", "CC-BY-ND-1.0", "CC-BY-ND-2.0", "CC-BY-ND-2.5", "CC-BY-ND-3.0", "CC-BY-ND-4.0",
+ "CC0",
+ "CDDL", "CPL",
+ "EFL-1.0", "EFL-2.0",
+ "Expat", "MIT",
+ "GPL-1.0", "GPL-2.0", "GPL-3.0",
+ "LGPL-2.0", "LGPL-2.1", "LGPL-3.0",
+ "GFDL-1.0", "GFDL-1.1", "GFDL-1.2", "GFDL-1.3",
+ "GFDL-NIV-1.0", "GFDL-NIV-1.1", "GFDL-NIV-1.2", "GFDL-NIV-1.3",
+ "LPPL-1.0", "LPPL-1.1", "LPPL-1.2", "LPPL-1.3c",
+ "MPL-1.0", "MPL-1.1",
+ "Ms-PL", "Ms-RL",
+ "Perl",
+ "Python-2.0",
+ "QPL-1.0",
+ "Unlicense",
+ "W3C",
+ "WTFPL",
+ "Zlib",
+ "Zope",
+ "open-source", "restricted", "unrestricted", "unknown"
+ ]
+ },
+ "licenses" : {
+ "description" : "A license, or array of licenses",
+ "anyOf" : [
+ { "$ref" : "#/definitions/license" },
+ {
+ "type" : "array",
+ "items" : { "$ref" : "#/definitions/license" },
+ "uniqueItems" : true
+ }
+ ]
+ },
+ "one_or_more_strings" : {
+ "description" : "One or more strings",
+ "oneOf" : [
+ { "type" : "string" },
+ {
+ "type" : "array",
+ "items" : { "type" : "string" },
+ "uniqueItems" : true
+ }
+ ]
+ }
+ }
+}
diff --git a/Core/Types/CkanModule.cs b/Core/Types/CkanModule.cs
index 1467286ac0..4c52b7aff8 100644
--- a/Core/Types/CkanModule.cs
+++ b/Core/Types/CkanModule.cs
@@ -2,8 +2,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using NJsonSchema;
using Autofac;
using CKAN.Versioning;
using log4net;
@@ -292,6 +295,13 @@ public CkanModule(string json, IGameComparator comparator)
{
_comparator = comparator;
+ var errors = ValidateAgainstSchema(json);
+ if (errors.Result.Any())
+ {
+ var message = "Validation against the schema failed.\n" + string.Join("\n ", errors);
+ throw new BadMetadataKraken(null, message);
+ }
+
try
{
// Use the json string to populate our object
@@ -366,6 +376,33 @@ private void DeSerialisationFixes(StreamingContext like_i_could_care)
name = name ?? string.Empty;
}
+ static CkanModule()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using (var stream = assembly.GetManifestResourceStream(SchemaPath))
+ using (var reader = new StreamReader(stream))
+ {
+ var result = reader.ReadToEnd();
+ MetadataSchema = JsonSchema4.FromSampleJson(result);
+ }
+ }
+
+ private static readonly string SchemaPath = "CKAN.CKAN.schema";
+ private static readonly JsonSchema4 MetadataSchema;
+
+ private static async Task> ValidateAgainstSchema(string json)
+ {
+ JsonSchema4 schema = await JsonSchema4.FromJsonAsync(json);
+
+ IList errorList = new List();
+ var errors = schema.Validate(MetadataSchema.ToJson());
+
+ foreach (var error in errors)
+ errorList.Add(error.ToString());
+
+ return errorList;
+ }
+
///
/// Tries to parse an identifier in the format Modname=version
/// If the module cannot be found in the registry, throws a ModuleNotFoundKraken.
diff --git a/Core/app.config b/Core/app.config
new file mode 100644
index 0000000000..dde2c3cc64
--- /dev/null
+++ b/Core/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Core/packages.config b/Core/packages.config
index aa696112f9..3edeb57499 100644
--- a/Core/packages.config
+++ b/Core/packages.config
@@ -4,5 +4,6 @@
+
\ No newline at end of file