@roster-lock/shared 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.js +7 -2
- package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.js.map +1 -1
- package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.js +1 -0
- package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.js.map +1 -1
- package/dist/match-lock-file/match-config/version-1/lock/rosters/validate/human.js +1 -1
- package/dist/match-lock-file/match-config/version-1/lock/rosters/validate/human.js.map +1 -1
- package/dist/match-lock-file/match-config/version-1/lock/rosters/validate/utils.js +25 -0
- package/dist/match-lock-file/match-config/version-1/lock/rosters/validate/utils.js.map +1 -1
- package/dist/match-lock-file/match-config/version-1/piece-info/keywords.js +1 -0
- package/dist/match-lock-file/match-config/version-1/piece-info/keywords.js.map +1 -1
- package/package.json +2 -2
- package/src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.ts +8 -2
- package/src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.ts +2 -1
- package/src/match-lock-file/match-config/version-1/lock/rosters/validate/human.ts +2 -2
- package/src/match-lock-file/match-config/version-1/lock/rosters/validate/utils.ts +33 -0
- package/src/match-lock-file/match-config/version-1/piece-info/keywords.ts +2 -0
package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.humanInfoSchema = exports.friendlyStringSchemaValidator = exports.urlSchemaValidator = void 0;
|
|
3
|
+
exports.humanInfoSchema = exports.friendlyStringSchemaValidator = exports.imageDataURISchemaValidator = exports.urlSchemaValidator = void 0;
|
|
4
4
|
const json_schema_1 = require("../../../../../util-types/json-schema");
|
|
5
5
|
const validate_1 = require("../validate");
|
|
6
6
|
exports.urlSchemaValidator = (0, json_schema_1.defineKeyword)({
|
|
@@ -8,6 +8,11 @@ exports.urlSchemaValidator = (0, json_schema_1.defineKeyword)({
|
|
|
8
8
|
type: "string",
|
|
9
9
|
validate: validate_1.validateURL
|
|
10
10
|
});
|
|
11
|
+
exports.imageDataURISchemaValidator = (0, json_schema_1.defineKeyword)({
|
|
12
|
+
keyword: "imageDataURI",
|
|
13
|
+
type: "string",
|
|
14
|
+
validate: validate_1.validateImageDataURI
|
|
15
|
+
});
|
|
11
16
|
const human_1 = require("../validate/human");
|
|
12
17
|
exports.friendlyStringSchemaValidator = (0, json_schema_1.defineKeyword)({
|
|
13
18
|
keyword: "friendlyString",
|
|
@@ -22,7 +27,7 @@ exports.humanInfoSchema = {
|
|
|
22
27
|
name: { type: "string", [exports.friendlyStringSchemaValidator.keyword]: true },
|
|
23
28
|
author: { type: "string", [exports.friendlyStringSchemaValidator.keyword]: true },
|
|
24
29
|
url: { type: "string", [exports.urlSchemaValidator.keyword]: true },
|
|
25
|
-
image: { type: "string", [exports.
|
|
30
|
+
image: { type: "string", [exports.imageDataURISchemaValidator.keyword]: true, nullable: true },
|
|
26
31
|
},
|
|
27
32
|
};
|
|
28
33
|
//# sourceMappingURL=human.js.map
|
package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"human.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.ts"],"names":[],"mappings":";;;AAGA,uEAAsE;AAGtE,
|
|
1
|
+
{"version":3,"file":"human.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/human.ts"],"names":[],"mappings":";;;AAGA,uEAAsE;AAGtE,0CAAgE;AACnD,QAAA,kBAAkB,GAAG,IAAA,2BAAa,EAAC;IAC9C,OAAO,EAAE,KAAK;IACd,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,sBAAW;CACtB,CAAC,CAAC;AAEU,QAAA,2BAA2B,GAAG,IAAA,2BAAa,EAAC;IACvD,OAAO,EAAE,cAAc;IACvB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,+BAAoB;CAC/B,CAAC,CAAC;AAEH,6CAA2D;AAC9C,QAAA,6BAA6B,GAAG,IAAA,2BAAa,EAAC;IACzD,OAAO,EAAE,gBAAgB;IACzB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,8BAAsB;CACjC,CAAC,CAAC;AAGU,QAAA,eAAe,GAAiD;IAC3E,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,QAAQ,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC;IACnC,UAAU,EAAE;QACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,QAAA,6BAA6B,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE;QACvE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,QAAA,6BAA6B,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE;QACzE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,QAAA,kBAAkB,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE;QAC3D,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,QAAA,2BAA2B,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;KACvF;CACF,CAAA"}
|
package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.js
CHANGED
|
@@ -52,6 +52,7 @@ exports.rostersSchema = {
|
|
|
52
52
|
exports.rosterKeywords = [
|
|
53
53
|
downloadableSources_1.downloadableSourceListSchemaValidator,
|
|
54
54
|
human_1.urlSchemaValidator,
|
|
55
|
+
human_1.imageDataURISchemaValidator,
|
|
55
56
|
human_1.friendlyStringSchemaValidator,
|
|
56
57
|
id_1.idSchemaValidator,
|
|
57
58
|
id_1.idUniquenessSchemaValidator,
|
package/dist/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.ts"],"names":[],"mappings":";;;AACA,uEAAsE;AAItE,6BAAgF;AAChF,uCAAiE;AACjE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/caster-json-schema/index.ts"],"names":[],"mappings":";;;AACA,uEAAsE;AAItE,6BAAgF;AAChF,uCAAiE;AACjE,mCAA0H;AAC1H,+DAG+B;AAC/B,mDAKyB;AACzB,qDAK0B;AAG1B,MAAM,gCAAgC,GAAG,IAAA,2BAAa,EAAC;IACrD,OAAO,EAAE,yBAAyB;IAClC,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,UAAU,KAAsB,EAAE,EAAE,MAAM,EAAsB,EAAE,IAAI;QAE9E,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,IAAG,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QAC/C,IAAA,gCAAqB,EAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;CACF,CAAC,CAAC;AAEU,QAAA,eAAe,GAAoC;IAC9D,IAAI,EAAE,QAAQ;IACd,CAAC,gCAAgC,CAAC,OAAO,CAAC,EAAE,IAAI;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,CAAC;IAC9F,oBAAoB,EAAE,KAAK;IAC3B,UAAU,EAAE;QACV,EAAE,EAAE,aAAQ;QACZ,OAAO,EAAE,uBAAa;QACtB,SAAS,EAAE,uBAAe;QAC1B,eAAe,EAAE,+CAAyB;QAC1C,aAAa,EAAE,mCAAmB;QAClC,cAAc,EAAE,qCAAoB;KACrC;CACF,CAAC;AAGF,0CAAoF;AACpF,MAAM,oCAAoC,GAAG,IAAA,2BAAa,EAAC;IACzD,OAAO,EAAE,6BAA6B;IACtC,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,UAAU,OAAsC,EAAE,EAAE,MAAM,EAAsB,EAAE,IAAI;QAC9F,IAAA,yCAA8B,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;CACF,CAAC,CAAC;AAEU,QAAA,aAAa,GAAkD;IAC1E,IAAI,EAAE,QAAQ;IACd,CAAC,oCAAoC,CAAC,OAAO,CAAC,EAAE,IAAI;IACpD,QAAQ,EAAE,EAAE;IACZ,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,QAAA,eAAe;KACvB;CACF,CAAC;AAGW,QAAA,cAAc,GAAG;IAE5B,2DAAqC;IAGrC,0BAAkB;IAClB,mCAA2B;IAC3B,qCAA6B;IAG7B,sBAAiB;IACjB,gCAA2B;IAG3B,+CAA+B;IAC/B,gDAAgC;IAChC,qDAAqC;IAGrC,wDAAuC;IACvC,kDAAiC;IACjC,iDAAgC;IAGhC,+BAAqB;IAIrB,oCAAoC;IACpC,gCAAgC;CACjC,CAAC"}
|
|
@@ -8,7 +8,7 @@ function validateHumanInfo(humanInfo) {
|
|
|
8
8
|
validateFriendlyString(humanInfo.author);
|
|
9
9
|
(0, utils_1.validateURL)(humanInfo.url);
|
|
10
10
|
if (humanInfo.image)
|
|
11
|
-
(0, utils_1.
|
|
11
|
+
(0, utils_1.validateImageDataURI)(humanInfo.image);
|
|
12
12
|
}
|
|
13
13
|
function validateFriendlyString(value) {
|
|
14
14
|
if (!value.trim())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"human.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/validate/human.ts"],"names":[],"mappings":";;;;AACA,
|
|
1
|
+
{"version":3,"file":"human.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/validate/human.ts"],"names":[],"mappings":";;;;AACA,mCAA4D;AAG5D,2BACE,SAAgE;IAEhE,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACvC,sBAAsB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACzC,IAAA,mBAAW,EAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAG,SAAS,CAAC,KAAK;QAAE,IAAA,4BAAoB,EAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,gCAAuC,KAAa;IAClD,IAAG,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxD,IAAG,KAAK,CAAC,MAAM,GAAG,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC7D,IAAG,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAE5D,IAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IACvE,IAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACxF,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IMAGE_DATA_URI_MAX_BYTES = void 0;
|
|
3
4
|
exports.validateSha256 = validateSha256;
|
|
4
5
|
exports.validateURL = validateURL;
|
|
6
|
+
exports.validateImageDataURI = validateImageDataURI;
|
|
5
7
|
function validateSha256(value) {
|
|
6
8
|
if (/^[0-9a-f]{64}$/i.test(value))
|
|
7
9
|
return;
|
|
@@ -20,4 +22,27 @@ function validateURL(value) {
|
|
|
20
22
|
if (!["https:"].includes(url.protocol))
|
|
21
23
|
throw new Error(`${value} is not a valid https`);
|
|
22
24
|
}
|
|
25
|
+
const IMAGE_DATA_URI_PATTERN = /^data:image\/(png|jpeg|jpg|gif|webp|svg\+xml);base64,([A-Za-z0-9+/]+=*)$/;
|
|
26
|
+
exports.IMAGE_DATA_URI_MAX_BYTES = 64 * 1024;
|
|
27
|
+
const UNSAFE_SVG_CONTENT_PATTERN = /<\s*script\b|<\s*iframe\b|<\s*embed\b|<\s*object\b|<\s*foreignobject\b|on[a-z]+\s*=|javascript:/i;
|
|
28
|
+
function validateImageDataURI(value) {
|
|
29
|
+
const match = IMAGE_DATA_URI_PATTERN.exec(value);
|
|
30
|
+
if (!match)
|
|
31
|
+
throw new Error(`${value.slice(0, 32)}... is not a valid "data:image/<type>;base64,..." URI`);
|
|
32
|
+
const [, mimeSubtype, base64Data] = match;
|
|
33
|
+
const decodedByteLength = Math.floor(base64Data.length * 3 / 4) - (base64Data.match(/=+$/)?.[0].length ?? 0);
|
|
34
|
+
if (decodedByteLength > exports.IMAGE_DATA_URI_MAX_BYTES) {
|
|
35
|
+
throw new Error(`Embedded image is ${decodedByteLength} bytes, which exceeds the ${exports.IMAGE_DATA_URI_MAX_BYTES}-byte limit`);
|
|
36
|
+
}
|
|
37
|
+
if (mimeSubtype === "svg+xml") {
|
|
38
|
+
const svgText = base64ToUtf8(base64Data);
|
|
39
|
+
if (UNSAFE_SVG_CONTENT_PATTERN.test(svgText)) {
|
|
40
|
+
throw new Error("Embedded SVG must not contain scripts, event handler attributes, or embedded documents");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function base64ToUtf8(base64) {
|
|
45
|
+
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
46
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
47
|
+
}
|
|
23
48
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/validate/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../../../../src/match-lock-file/match-config/version-1/lock/rosters/validate/utils.ts"],"names":[],"mappings":";;;;;;AACA,wBAA+B,KAAa;IAE1C,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO;IAC1C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,CAAC,CAAC;AACnE,CAAC;AAED,qBAA4B,KAAa;IACvC,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,qBAAqB,CAAC,CAAC;IACjD,CAAC;IACD,IAAG,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW;QAAE,OAAO;IACpE,IAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uBAAuB,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,sBAAsB,GAAG,0EAA0E,CAAC;AAC7F,QAAA,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAC;AAKlD,MAAM,0BAA0B,GAAG,kGAAkG,CAAC;AAEtI,8BAAqC,KAAa;IAChD,MAAM,KAAK,GAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjD,IAAG,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,uDAAuD,CAAC,CAAC;IAEzG,MAAM,CAAC,EAAE,WAAW,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC;IAC1C,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAC7G,IAAG,iBAAiB,GAAG,QAAA,wBAAwB,EAAC,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,qBAAqB,iBAAiB,6BAA6B,QAAA,wBAAwB,aAAa,CACzG,CAAC;IACJ,CAAC;IAED,IAAG,WAAW,KAAK,SAAS,EAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QACzC,IAAG,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC,EAAC,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -8,6 +8,7 @@ const json_schema_1 = require("../../../util-types/json-schema");
|
|
|
8
8
|
function ignore() { return true; }
|
|
9
9
|
exports.rosterLockPieceInfoKeywords = [
|
|
10
10
|
human_1.urlSchemaValidator,
|
|
11
|
+
human_1.imageDataURISchemaValidator,
|
|
11
12
|
human_1.friendlyStringSchemaValidator,
|
|
12
13
|
downloadableSources_1.downloadableSourceListSchemaValidator,
|
|
13
14
|
(0, json_schema_1.defineKeyword)({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keywords.js","sourceRoot":"","sources":["../../../../../src/match-lock-file/match-config/version-1/piece-info/keywords.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"keywords.js","sourceRoot":"","sources":["../../../../../src/match-lock-file/match-config/version-1/piece-info/keywords.ts"],"names":[],"mappings":";;;AACA,oEAIkD;AAClD,gGAEgE;AAChE,oFAI0D;AAE1D,iEAAgE;AAEhE,SAAS,MAAM,KAAI,OAAO,IAAI,CAAC,CAAC,CAAC;AAEpB,QAAA,2BAA2B,GAAG;IACzC,0BAAkB;IAClB,mCAA2B;IAC3B,qCAA6B;IAE7B,2DAAqC;IACrC,IAAA,2BAAa,EAAC;QACZ,OAAO,EAAE,+CAA+B,CAAC,OAAO;QAChD,IAAI,EAAE,+CAA+B,CAAC,IAAI;QAC1C,QAAQ,EAAE,MAAM;KACjB,CAAC;IACF,IAAA,2BAAa,EAAC;QACZ,OAAO,EAAE,gDAAgC,CAAC,OAAO;QACjD,IAAI,EAAE,gDAAgC,CAAC,IAAI;QAC3C,QAAQ,EAAE,MAAM;KACjB,CAAC;IACF,IAAA,2BAAa,EAAC;QACZ,OAAO,EAAE,qDAAqC,CAAC,OAAO;QACtD,IAAI,EAAE,qDAAqC,CAAC,IAAI;QAChD,QAAQ,EAAE,MAAM;KACjB,CAAC;CACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roster-lock/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared utilities, variables, etc between client and server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"roster-lock",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"magnet-uri": "^7.0.7",
|
|
40
40
|
"micromatch": "^4.0.8",
|
|
41
41
|
"runtypes": "^7.0.4",
|
|
42
|
-
"@roster-lock/types": "1.
|
|
42
|
+
"@roster-lock/types": "1.1.0",
|
|
43
43
|
"@roster-lock/utils": "1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
@@ -4,13 +4,19 @@ type RosterLockPiece = RosterLockV1Config["rosters"][string][number];
|
|
|
4
4
|
import { defineKeyword } from "../../../../../util-types/json-schema";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
import { validateURL } from "../validate";
|
|
7
|
+
import { validateURL, validateImageDataURI } from "../validate";
|
|
8
8
|
export const urlSchemaValidator = defineKeyword({
|
|
9
9
|
keyword: "URL",
|
|
10
10
|
type: "string",
|
|
11
11
|
validate: validateURL
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
export const imageDataURISchemaValidator = defineKeyword({
|
|
15
|
+
keyword: "imageDataURI",
|
|
16
|
+
type: "string",
|
|
17
|
+
validate: validateImageDataURI
|
|
18
|
+
});
|
|
19
|
+
|
|
14
20
|
import { validateFriendlyString } from "../validate/human";
|
|
15
21
|
export const friendlyStringSchemaValidator = defineKeyword({
|
|
16
22
|
keyword: "friendlyString",
|
|
@@ -27,7 +33,7 @@ export const humanInfoSchema: JSONSchemaType<RosterLockPiece["humanInfo"]> = {
|
|
|
27
33
|
name: { type: "string", [friendlyStringSchemaValidator.keyword]: true },
|
|
28
34
|
author: { type: "string", [friendlyStringSchemaValidator.keyword]: true },
|
|
29
35
|
url: { type: "string", [urlSchemaValidator.keyword]: true },
|
|
30
|
-
image: { type: "string", [
|
|
36
|
+
image: { type: "string", [imageDataURISchemaValidator.keyword]: true, nullable: true },
|
|
31
37
|
},
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -5,7 +5,7 @@ type RosterLockPiece = RosterLockV1Config["rosters"][string][number];
|
|
|
5
5
|
|
|
6
6
|
import { idSchema, idUniquenessSchemaValidator, idSchemaValidator } from "./id";
|
|
7
7
|
import { versionSchema, sha256SchemaValidator } from "./version";
|
|
8
|
-
import { humanInfoSchema, urlSchemaValidator, friendlyStringSchemaValidator } from "./human";
|
|
8
|
+
import { humanInfoSchema, urlSchemaValidator, imageDataURISchemaValidator, friendlyStringSchemaValidator } from "./human";
|
|
9
9
|
import {
|
|
10
10
|
downloadableSourcesSchema,
|
|
11
11
|
downloadableSourceListSchemaValidator,
|
|
@@ -77,6 +77,7 @@ export const rosterKeywords = [
|
|
|
77
77
|
|
|
78
78
|
// Human Related
|
|
79
79
|
urlSchemaValidator,
|
|
80
|
+
imageDataURISchemaValidator,
|
|
80
81
|
friendlyStringSchemaValidator,
|
|
81
82
|
|
|
82
83
|
// Id Related
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RosterLockV1Config } from "@roster-lock/types";
|
|
2
|
-
import { validateURL } from "./utils";
|
|
2
|
+
import { validateURL, validateImageDataURI } from "./utils";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export function validateHumanInfo(
|
|
@@ -8,7 +8,7 @@ export function validateHumanInfo(
|
|
|
8
8
|
validateFriendlyString(humanInfo.name);
|
|
9
9
|
validateFriendlyString(humanInfo.author);
|
|
10
10
|
validateURL(humanInfo.url);
|
|
11
|
-
if(humanInfo.image)
|
|
11
|
+
if(humanInfo.image) validateImageDataURI(humanInfo.image);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function validateFriendlyString(value: string){
|
|
@@ -15,3 +15,36 @@ export function validateURL(value: string){
|
|
|
15
15
|
if(url.protocol === "http:" && url.hostname === "localhost") return;
|
|
16
16
|
if(!["https:"].includes(url.protocol)) throw new Error(`${value} is not a valid https`);
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
const IMAGE_DATA_URI_PATTERN = /^data:image\/(png|jpeg|jpg|gif|webp|svg\+xml);base64,([A-Za-z0-9+/]+=*)$/;
|
|
20
|
+
export const IMAGE_DATA_URI_MAX_BYTES = 64 * 1024;
|
|
21
|
+
|
|
22
|
+
// Scripts/event handlers wouldn't execute from an <img src="data:...">, but pieces can
|
|
23
|
+
// come from third-party downloadSources - reject anything that would be dangerous if a
|
|
24
|
+
// future consumer ever rendered the SVG a different way (e.g. inline in the DOM).
|
|
25
|
+
const UNSAFE_SVG_CONTENT_PATTERN = /<\s*script\b|<\s*iframe\b|<\s*embed\b|<\s*object\b|<\s*foreignobject\b|on[a-z]+\s*=|javascript:/i;
|
|
26
|
+
|
|
27
|
+
export function validateImageDataURI(value: string){
|
|
28
|
+
const match = IMAGE_DATA_URI_PATTERN.exec(value);
|
|
29
|
+
if(!match) throw new Error(`${value.slice(0, 32)}... is not a valid "data:image/<type>;base64,..." URI`);
|
|
30
|
+
|
|
31
|
+
const [, mimeSubtype, base64Data] = match;
|
|
32
|
+
const decodedByteLength = Math.floor(base64Data.length * 3 / 4) - (base64Data.match(/=+$/)?.[0].length ?? 0);
|
|
33
|
+
if(decodedByteLength > IMAGE_DATA_URI_MAX_BYTES){
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Embedded image is ${decodedByteLength} bytes, which exceeds the ${IMAGE_DATA_URI_MAX_BYTES}-byte limit`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if(mimeSubtype === "svg+xml"){
|
|
40
|
+
const svgText = base64ToUtf8(base64Data);
|
|
41
|
+
if(UNSAFE_SVG_CONTENT_PATTERN.test(svgText)){
|
|
42
|
+
throw new Error("Embedded SVG must not contain scripts, event handler attributes, or embedded documents");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function base64ToUtf8(base64: string): string {
|
|
48
|
+
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
49
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
50
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import {
|
|
3
3
|
urlSchemaValidator,
|
|
4
|
+
imageDataURISchemaValidator,
|
|
4
5
|
friendlyStringSchemaValidator,
|
|
5
6
|
} from "../lock/rosters/caster-json-schema/human";
|
|
6
7
|
import {
|
|
@@ -18,6 +19,7 @@ function ignore(){ return true; }
|
|
|
18
19
|
|
|
19
20
|
export const rosterLockPieceInfoKeywords = [
|
|
20
21
|
urlSchemaValidator,
|
|
22
|
+
imageDataURISchemaValidator,
|
|
21
23
|
friendlyStringSchemaValidator,
|
|
22
24
|
|
|
23
25
|
downloadableSourceListSchemaValidator,
|