@fedify/vocab-tools 2.4.0-pr.934.40 → 2.4.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/README.md +39 -0
- package/deno.json +1 -1
- package/dist/mod.cjs +587 -116
- package/dist/mod.d.cts +14 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +587 -116
- package/dist/schema.yaml +15 -0
- package/package.json +2 -2
- package/src/__snapshots__/class.test.ts.deno.snap +55028 -11696
- package/src/__snapshots__/class.test.ts.node.snap +55028 -11696
- package/src/__snapshots__/class.test.ts.snap +55028 -11696
- package/src/class.ts +117 -0
- package/src/codec.ts +133 -34
- package/src/constructor.ts +11 -0
- package/src/inspector.ts +4 -4
- package/src/property.ts +343 -70
- package/src/schema.ts +16 -0
- package/src/schema.yaml +15 -0
- package/src/type.ts +41 -5
package/dist/mod.cjs
CHANGED
|
@@ -8,15 +8,17 @@ let yaml = require("yaml");
|
|
|
8
8
|
let es_toolkit = require("es-toolkit");
|
|
9
9
|
//#region deno.json
|
|
10
10
|
var name = "@fedify/vocab-tools";
|
|
11
|
-
var version = "2.4.0
|
|
11
|
+
var version = "2.4.0";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/type.ts
|
|
14
14
|
const HEURISTICS_CONTEXTS = [
|
|
15
15
|
"https://www.w3.org/ns/activitystreams",
|
|
16
16
|
"https://w3id.org/security/v1",
|
|
17
17
|
"https://w3id.org/security/data-integrity/v1",
|
|
18
|
+
"https://w3id.org/security/data-integrity/v2",
|
|
18
19
|
"https://www.w3.org/ns/did/v1",
|
|
19
|
-
"https://w3id.org/security/multikey/v1"
|
|
20
|
+
"https://w3id.org/security/multikey/v1",
|
|
21
|
+
"https://w3id.org/fep/ef61"
|
|
20
22
|
];
|
|
21
23
|
const scalarTypes = {
|
|
22
24
|
"http://www.w3.org/2001/XMLSchema#boolean": {
|
|
@@ -184,7 +186,8 @@ const scalarTypes = {
|
|
|
184
186
|
dataCheck(v) {
|
|
185
187
|
return `typeof ${v} === "object" && "@language" in ${v} && "@value" in ${v}
|
|
186
188
|
&& typeof ${v}["@language"] === "string"
|
|
187
|
-
&& typeof ${v}["@value"] === "string"
|
|
189
|
+
&& typeof ${v}["@value"] === "string"
|
|
190
|
+
&& isValidLanguageTag(${v}["@language"])`;
|
|
188
191
|
},
|
|
189
192
|
decoder(v) {
|
|
190
193
|
return `new LanguageString(${v}["@value"], ${v}["@language"])`;
|
|
@@ -315,12 +318,35 @@ const scalarTypes = {
|
|
|
315
318
|
return `formatIri(${v})`;
|
|
316
319
|
},
|
|
317
320
|
dataCheck(v) {
|
|
318
|
-
return `typeof ${v} === "object" &&
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
return `typeof ${v} === "object" &&
|
|
322
|
+
((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
|
|
323
|
+
(typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
|
|
324
|
+
},
|
|
325
|
+
decoder(v) {
|
|
326
|
+
return `decodeIri(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
|
|
327
|
+
},
|
|
328
|
+
skipUnparsable: true
|
|
329
|
+
},
|
|
330
|
+
"fedify:gatewayUrl": {
|
|
331
|
+
name: "URL",
|
|
332
|
+
typeGuard(v) {
|
|
333
|
+
return `${v} instanceof URL && isGatewayUrl(${v})`;
|
|
334
|
+
},
|
|
335
|
+
encoder(v) {
|
|
336
|
+
return `{ "@id": ${v}.origin }`;
|
|
337
|
+
},
|
|
338
|
+
compactEncoder(v) {
|
|
339
|
+
return `${v}.origin`;
|
|
340
|
+
},
|
|
341
|
+
dataCheck(v) {
|
|
342
|
+
return `${v} != null && typeof ${v} === "object" &&
|
|
343
|
+
(("@id" in ${v} && typeof ${v}["@id"] === "string" &&
|
|
344
|
+
${v}["@id"] !== "" && ${v}["@id"] !== "/") ||
|
|
345
|
+
("@value" in ${v} && typeof ${v}["@value"] === "string" &&
|
|
346
|
+
${v}["@value"] !== "" && ${v}["@value"] !== "/"))`;
|
|
321
347
|
},
|
|
322
348
|
decoder(v) {
|
|
323
|
-
return `
|
|
349
|
+
return `parseGatewayUrl(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
|
|
324
350
|
}
|
|
325
351
|
},
|
|
326
352
|
"fedify:publicKey": {
|
|
@@ -533,6 +559,9 @@ function getDecoder(typeUri, types, variable, optionsVariable, baseUrlVariable)
|
|
|
533
559
|
)`;
|
|
534
560
|
throw new Error(`Unknown type: ${typeUri}`);
|
|
535
561
|
}
|
|
562
|
+
function skipsUnparsable(typeUri) {
|
|
563
|
+
return scalarTypes[typeUri]?.skipUnparsable ?? false;
|
|
564
|
+
}
|
|
536
565
|
function getDataCheck(typeUri, types, variable) {
|
|
537
566
|
if (typeUri in scalarTypes) return scalarTypes[typeUri].dataCheck(variable);
|
|
538
567
|
if (typeUri in types) {
|
|
@@ -712,6 +741,7 @@ function* generatePreprocessorBlock(property, rangeTypeName, variable, baseUrlEx
|
|
|
712
741
|
documentLoader: options.documentLoader,
|
|
713
742
|
contextLoader: options.contextLoader,
|
|
714
743
|
tracerProvider: options.tracerProvider,
|
|
744
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
715
745
|
baseUrl: ${baseUrlExpr},
|
|
716
746
|
});
|
|
717
747
|
if (_result instanceof Error) throw _result;
|
|
@@ -760,6 +790,10 @@ async function* generateEncoder(typeUri, types) {
|
|
|
760
790
|
...options,
|
|
761
791
|
contextLoader: options.contextLoader ?? getDocumentLoader(),
|
|
762
792
|
};
|
|
793
|
+
// Joins the enclosing frame's signed-value scope, or starts one when this
|
|
794
|
+
// frame is the outermost. Only the owning frame puts retained signed
|
|
795
|
+
// child documents back in place of their placeholders.
|
|
796
|
+
const signedValues = enterSignedValueScope(options);
|
|
763
797
|
`;
|
|
764
798
|
if (isCompactableType(typeUri, types)) {
|
|
765
799
|
yield `
|
|
@@ -790,9 +824,13 @@ async function* generateEncoder(typeUri, types) {
|
|
|
790
824
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
791
825
|
const item = (
|
|
792
826
|
`;
|
|
793
|
-
if (!areAllScalarTypes(property.range, types))
|
|
827
|
+
if (!areAllScalarTypes(property.range, types)) {
|
|
828
|
+
yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
|
|
829
|
+
yield "v instanceof URL ? formatIri(v) : ";
|
|
830
|
+
}
|
|
794
831
|
const encoders = getEncoders(property.range, types, "v", "options", true);
|
|
795
832
|
for (const code of encoders) yield code;
|
|
833
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
796
834
|
yield `
|
|
797
835
|
);
|
|
798
836
|
compactItems.push(item);
|
|
@@ -823,7 +861,9 @@ async function* generateEncoder(typeUri, types) {
|
|
|
823
861
|
${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
|
|
824
862
|
if (this.id != null) result["id"] = formatIri(this.id);
|
|
825
863
|
result["@context"] = ${JSON.stringify(type.defaultContext)};
|
|
826
|
-
return
|
|
864
|
+
return signedValues.owner
|
|
865
|
+
? resolveSignedValues(result, signedValues.scope)
|
|
866
|
+
: result;
|
|
827
867
|
}
|
|
828
868
|
`;
|
|
829
869
|
}
|
|
@@ -847,12 +887,21 @@ async function* generateEncoder(typeUri, types) {
|
|
|
847
887
|
yield `
|
|
848
888
|
array = [];
|
|
849
889
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
850
|
-
|
|
890
|
+
let element = (
|
|
851
891
|
`;
|
|
852
|
-
if (!areAllScalarTypes(property.range, types))
|
|
892
|
+
if (!areAllScalarTypes(property.range, types)) {
|
|
893
|
+
yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
|
|
894
|
+
yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
|
|
895
|
+
}
|
|
853
896
|
for (const code of getEncoders(property.range, types, "v", "options")) yield code;
|
|
897
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
854
898
|
yield `
|
|
855
899
|
);
|
|
900
|
+
if (Array.isArray(element)) {
|
|
901
|
+
if (element.length < 1) continue;
|
|
902
|
+
if (element.length == 1) element = element[0];
|
|
903
|
+
}
|
|
904
|
+
if (typeof element === "undefined") continue;
|
|
856
905
|
`;
|
|
857
906
|
if (isNonFunctionalProperty(property) && property.container === "graph") yield `array.push({ "@graph": element });`;
|
|
858
907
|
else yield `array.push(element);`;
|
|
@@ -880,16 +929,35 @@ async function* generateEncoder(typeUri, types) {
|
|
|
880
929
|
if (options.format === "expand") {
|
|
881
930
|
return await jsonld.expand(
|
|
882
931
|
values,
|
|
883
|
-
{
|
|
932
|
+
{
|
|
933
|
+
documentLoader: options.contextLoader,
|
|
934
|
+
keepFreeFloatingNodes: true,
|
|
935
|
+
},
|
|
884
936
|
);
|
|
885
937
|
}
|
|
886
|
-
|
|
938
|
+
let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
|
|
887
939
|
${JSON.stringify(type.defaultContext)};
|
|
888
|
-
|
|
940
|
+
let compacted = await jsonld.compact(
|
|
889
941
|
values,
|
|
890
942
|
docContext,
|
|
891
943
|
{ documentLoader: options.contextLoader },
|
|
892
944
|
);
|
|
945
|
+
`;
|
|
946
|
+
if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
|
|
947
|
+
if (options.context == null) {
|
|
948
|
+
const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
|
|
949
|
+
const additionalContexts = getExtraContexts(compacted).filter(
|
|
950
|
+
context => !currentContexts.includes(context)
|
|
951
|
+
);
|
|
952
|
+
if (additionalContexts.length > 0) {
|
|
953
|
+
docContext = [...currentContexts, ...additionalContexts];
|
|
954
|
+
compacted = await jsonld.compact(values, docContext, {
|
|
955
|
+
documentLoader: options.contextLoader,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
`;
|
|
960
|
+
yield `
|
|
893
961
|
if (docContext != null) {
|
|
894
962
|
// Embed context
|
|
895
963
|
`;
|
|
@@ -913,12 +981,14 @@ async function* generateEncoder(typeUri, types) {
|
|
|
913
981
|
}
|
|
914
982
|
yield `
|
|
915
983
|
}
|
|
916
|
-
return
|
|
984
|
+
return signedValues.owner
|
|
985
|
+
? resolveSignedValues(compacted, signedValues.scope)
|
|
986
|
+
: compacted;
|
|
917
987
|
}
|
|
918
988
|
|
|
919
989
|
protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
|
|
920
990
|
`;
|
|
921
|
-
for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
|
|
991
|
+
for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
|
|
922
992
|
if (
|
|
923
993
|
this.${await getFieldName(property.uri)} != null &&
|
|
924
994
|
this.${await getFieldName(property.uri)}.length > 0
|
|
@@ -940,6 +1010,10 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
940
1010
|
* - \`contextLoader\`: The loader for remote JSON-LD contexts.
|
|
941
1011
|
* - \`tracerProvider\`: The OpenTelemetry tracer provider to use.
|
|
942
1012
|
* If omitted, the global tracer provider is used.
|
|
1013
|
+
* - \`verifyPortableObject\`: The default FEP-ef61 portable
|
|
1014
|
+
* object verifier for the property accessors of the
|
|
1015
|
+
* returned object and the objects obtained from it. It
|
|
1016
|
+
* does not verify the given \`json\` itself.
|
|
943
1017
|
* @returns The object of this type.
|
|
944
1018
|
* @throws {TypeError} If the given \`json\` is invalid.
|
|
945
1019
|
*/
|
|
@@ -949,6 +1023,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
949
1023
|
documentLoader?: DocumentLoader,
|
|
950
1024
|
contextLoader?: DocumentLoader,
|
|
951
1025
|
tracerProvider?: TracerProvider,
|
|
1026
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
952
1027
|
baseUrl?: URL,
|
|
953
1028
|
} = {},
|
|
954
1029
|
): Promise<${type.name}> {
|
|
@@ -987,6 +1062,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
987
1062
|
documentLoader?: DocumentLoader,
|
|
988
1063
|
contextLoader?: DocumentLoader,
|
|
989
1064
|
tracerProvider?: TracerProvider,
|
|
1065
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
990
1066
|
baseUrl?: URL,
|
|
991
1067
|
} = {},
|
|
992
1068
|
): Promise<${type.name}> {
|
|
@@ -1003,7 +1079,17 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1003
1079
|
// deno-lint-ignore no-explicit-any
|
|
1004
1080
|
let values: Record<string, any[]> & { "@id"?: string };
|
|
1005
1081
|
let expanded: unknown[];
|
|
1006
|
-
if (
|
|
1082
|
+
if (
|
|
1083
|
+
"_fromSubclass" in options &&
|
|
1084
|
+
options._fromSubclass &&
|
|
1085
|
+
typeof json === "object" &&
|
|
1086
|
+
!Array.isArray(json)
|
|
1087
|
+
) {
|
|
1088
|
+
values =
|
|
1089
|
+
// deno-lint-ignore no-explicit-any
|
|
1090
|
+
json as (Record<string, any[]> & { "@id"?: string });
|
|
1091
|
+
expanded = [values];
|
|
1092
|
+
} else if (globalThis.Object.keys(json).length == 0) {
|
|
1007
1093
|
values = {};
|
|
1008
1094
|
expanded = [values];
|
|
1009
1095
|
} else {
|
|
@@ -1060,8 +1146,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1060
1146
|
);
|
|
1061
1147
|
`;
|
|
1062
1148
|
else yield `
|
|
1063
|
-
|
|
1064
|
-
|
|
1149
|
+
const superValues = structuredClone(values);
|
|
1150
|
+
delete superValues["@type"];
|
|
1151
|
+
const instance = await super.fromJsonLd(superValues, {
|
|
1065
1152
|
...options,
|
|
1066
1153
|
// @ts-ignore: an internal option
|
|
1067
1154
|
_fromSubclass: true,
|
|
@@ -1070,25 +1157,35 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1070
1157
|
throw new TypeError("Unexpected type: " + instance.constructor.name);
|
|
1071
1158
|
}
|
|
1072
1159
|
`;
|
|
1160
|
+
yield `
|
|
1161
|
+
let shouldCacheJsonLd = instance._shouldCacheJsonLd;
|
|
1162
|
+
`;
|
|
1073
1163
|
for (const property of type.properties) {
|
|
1074
1164
|
const variable = await getFieldName(property.uri, "");
|
|
1075
1165
|
yield await generateField(property, types, "const ");
|
|
1076
1166
|
const arrayVariable = `${variable}__array`;
|
|
1077
1167
|
yield `
|
|
1078
|
-
let ${arrayVariable} = values[${JSON.stringify(property.uri)}];
|
|
1168
|
+
let ${arrayVariable} = ${property.uri === "https://w3id.org/fep/ef61/gateways" ? "getPortableActorGateways(values) as typeof values[string]" : `values[${JSON.stringify(property.uri)}]`};
|
|
1079
1169
|
`;
|
|
1080
1170
|
if (property.functional && property.redundantProperties != null) for (const prop of property.redundantProperties) yield `
|
|
1081
1171
|
if (${arrayVariable} == null || ${arrayVariable}.length < 1) {
|
|
1082
1172
|
${arrayVariable} = values[${JSON.stringify(prop.uri)}];
|
|
1083
1173
|
}
|
|
1084
1174
|
`;
|
|
1175
|
+
let itemsExpression = `${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
|
|
1176
|
+
? ${arrayVariable}[0]["@list"]
|
|
1177
|
+
: ${arrayVariable}`;
|
|
1178
|
+
if (isNonFunctionalProperty(property) && property.container === "graph") itemsExpression = `(${itemsExpression}).flatMap((item: unknown) =>
|
|
1179
|
+
item != null && typeof item === "object" && "@graph" in item &&
|
|
1180
|
+
Array.isArray((item as { "@graph": unknown })["@graph"])
|
|
1181
|
+
? (item as { "@graph": unknown[] })["@graph"]
|
|
1182
|
+
: [item]
|
|
1183
|
+
)`;
|
|
1085
1184
|
yield `
|
|
1086
1185
|
for (
|
|
1087
1186
|
const v of ${arrayVariable} == null
|
|
1088
1187
|
? []
|
|
1089
|
-
: ${
|
|
1090
|
-
? ${arrayVariable}[0]["@list"]
|
|
1091
|
-
: ${arrayVariable}
|
|
1188
|
+
: ${itemsExpression}
|
|
1092
1189
|
) {
|
|
1093
1190
|
if (v == null) continue;
|
|
1094
1191
|
`;
|
|
@@ -1102,29 +1199,47 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1102
1199
|
continue;
|
|
1103
1200
|
}
|
|
1104
1201
|
`;
|
|
1105
|
-
|
|
1106
|
-
const decoded = ${getDecoder(property.range[0], types, "v", "options", propertyBaseUrl)};
|
|
1107
|
-
if (typeof decoded === "undefined") continue;
|
|
1108
|
-
${variable}.push(decoded);`;
|
|
1109
|
-
else {
|
|
1110
|
-
yield `
|
|
1202
|
+
yield `
|
|
1111
1203
|
const decoded =
|
|
1112
|
-
|
|
1204
|
+
`;
|
|
1205
|
+
const lenient = property.range.length == 1 && skipsUnparsable(property.range[0]);
|
|
1206
|
+
if (property.range.length == 1) {
|
|
1207
|
+
if (lenient) {
|
|
1208
|
+
yield getDataCheck(property.range[0], types, "v");
|
|
1209
|
+
yield " ? ";
|
|
1210
|
+
}
|
|
1211
|
+
yield getDecoder(property.range[0], types, "v", "options", propertyBaseUrl);
|
|
1212
|
+
if (lenient) yield " : undefined";
|
|
1213
|
+
} else {
|
|
1113
1214
|
const decoders = getDecoders(property.range, types, "v", "options", propertyBaseUrl);
|
|
1114
1215
|
for (const code of decoders) yield code;
|
|
1115
|
-
|
|
1216
|
+
}
|
|
1217
|
+
yield `
|
|
1116
1218
|
;
|
|
1117
|
-
|
|
1219
|
+
`;
|
|
1220
|
+
yield `
|
|
1221
|
+
if (typeof decoded === "undefined") {
|
|
1222
|
+
shouldCacheJsonLd = false;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
`;
|
|
1226
|
+
yield `
|
|
1227
|
+
if (!this._shouldCacheDecodedJsonLd(decoded)) {
|
|
1228
|
+
shouldCacheJsonLd = false;
|
|
1229
|
+
}
|
|
1118
1230
|
${variable}.push(decoded);
|
|
1119
|
-
|
|
1120
|
-
}
|
|
1231
|
+
`;
|
|
1121
1232
|
yield `
|
|
1122
1233
|
}
|
|
1123
1234
|
instance.${await getFieldName(property.uri)} = ${variable};
|
|
1124
1235
|
`;
|
|
1125
1236
|
}
|
|
1126
1237
|
yield `
|
|
1127
|
-
|
|
1238
|
+
instance._shouldCacheJsonLd = shouldCacheJsonLd;
|
|
1239
|
+
if (
|
|
1240
|
+
shouldCacheJsonLd &&
|
|
1241
|
+
(!("_fromSubclass" in options) || !options._fromSubclass)
|
|
1242
|
+
) {
|
|
1128
1243
|
try {
|
|
1129
1244
|
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
1130
1245
|
const compactArray = Array.isArray(json) && json.length === 1;
|
|
@@ -1190,6 +1305,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1190
1305
|
documentLoader?: DocumentLoader,
|
|
1191
1306
|
contextLoader?: DocumentLoader,
|
|
1192
1307
|
tracerProvider?: TracerProvider,
|
|
1308
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1193
1309
|
} = {},
|
|
1194
1310
|
) {
|
|
1195
1311
|
`;
|
|
@@ -1197,6 +1313,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1197
1313
|
this.#documentLoader = options.documentLoader;
|
|
1198
1314
|
this.#contextLoader = options.contextLoader;
|
|
1199
1315
|
this.#tracerProvider = options.tracerProvider;
|
|
1316
|
+
this.#verifyPortableObject = options.verifyPortableObject;
|
|
1200
1317
|
const baseUrl = (options as { baseUrl?: URL }).baseUrl;
|
|
1201
1318
|
this.#_baseUrl = baseUrl == null ? undefined : new URL(baseUrl.href);
|
|
1202
1319
|
if ("$warning" in options) {
|
|
@@ -1291,6 +1408,13 @@ async function* generateCloner(typeUri, types) {
|
|
|
1291
1408
|
yield `
|
|
1292
1409
|
/**
|
|
1293
1410
|
* Clones this instance, optionally updating it with the given values.
|
|
1411
|
+
*
|
|
1412
|
+
* A clone never inherits a signed JSON-LD representation retained by
|
|
1413
|
+
* \`signObject()\`: the clone may differ from the document that the proof
|
|
1414
|
+
* covers, so embedding the original secured JSON in a parent document
|
|
1415
|
+
* would be wrong. Sign the clone again if it has to be embedded as a
|
|
1416
|
+
* secured child.
|
|
1417
|
+
*
|
|
1294
1418
|
* @param values The values to update the clone with.
|
|
1295
1419
|
* @param options The options to use for cloning.
|
|
1296
1420
|
* @returns The cloned instance.
|
|
@@ -1304,6 +1428,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1304
1428
|
options: {
|
|
1305
1429
|
documentLoader?: DocumentLoader,
|
|
1306
1430
|
contextLoader?: DocumentLoader,
|
|
1431
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1307
1432
|
} = {}
|
|
1308
1433
|
): ${type.name} {
|
|
1309
1434
|
if (this._warning != null) {
|
|
@@ -1321,6 +1446,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1321
1446
|
{ id: values.id ?? this.id },
|
|
1322
1447
|
options
|
|
1323
1448
|
);
|
|
1449
|
+
copyPortableProvenance(this, clone);
|
|
1324
1450
|
`;
|
|
1325
1451
|
else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
|
|
1326
1452
|
for (const property of type.properties) {
|
|
@@ -1416,12 +1542,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1416
1542
|
[Symbol.for("Deno.customInspect")]: (
|
|
1417
1543
|
inspect: typeof Deno.inspect,
|
|
1418
1544
|
options: Deno.InspectOptions,
|
|
1419
|
-
): string => "URL " + inspect(this.id
|
|
1545
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1420
1546
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1421
1547
|
_depth: number,
|
|
1422
1548
|
options: unknown,
|
|
1423
1549
|
inspect: (value: unknown, options: unknown) => string,
|
|
1424
|
-
): string => "URL " + inspect(this.id
|
|
1550
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1425
1551
|
};
|
|
1426
1552
|
}
|
|
1427
1553
|
`;
|
|
@@ -1437,12 +1563,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1437
1563
|
[Symbol.for("Deno.customInspect")]: (
|
|
1438
1564
|
inspect: typeof Deno.inspect,
|
|
1439
1565
|
options: Deno.InspectOptions,
|
|
1440
|
-
): string => "URL " + inspect(v
|
|
1566
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1441
1567
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1442
1568
|
_depth: number,
|
|
1443
1569
|
options: unknown,
|
|
1444
1570
|
inspect: (value: unknown, options: unknown) => string,
|
|
1445
|
-
): string => "URL " + inspect(v
|
|
1571
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1446
1572
|
}
|
|
1447
1573
|
: v);
|
|
1448
1574
|
`;
|
|
@@ -1512,6 +1638,7 @@ function emitOverride(typeUri, types, property) {
|
|
|
1512
1638
|
}
|
|
1513
1639
|
async function* generateProperty(type, property, types, moduleVarNames) {
|
|
1514
1640
|
const override = emitOverride(type.uri, types, property);
|
|
1641
|
+
const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
|
|
1515
1642
|
const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
|
|
1516
1643
|
const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
|
|
1517
1644
|
this._baseUrl ??
|
|
@@ -1552,6 +1679,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1552
1679
|
suppressError?: boolean,
|
|
1553
1680
|
tracerProvider?: TracerProvider,
|
|
1554
1681
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
1682
|
+
gateways?: readonly (string | URL)[];
|
|
1683
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
1684
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1555
1685
|
} = {},
|
|
1556
1686
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1557
1687
|
const documentLoader =
|
|
@@ -1564,11 +1694,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1564
1694
|
${JSON.stringify(name)},
|
|
1565
1695
|
${JSON.stringify(version)},
|
|
1566
1696
|
);
|
|
1697
|
+
// The fetched object uses the verifier given to this call (which falls
|
|
1698
|
+
// back to the parent's own default) for its accessors by default,
|
|
1699
|
+
// unless the call keeps its verifier to itself:
|
|
1700
|
+
const childVerifier = options.inheritPortableObjectVerifier === false
|
|
1701
|
+
? this._verifyPortableObject
|
|
1702
|
+
: options.verifyPortableObject;
|
|
1567
1703
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
1704
|
+
const dereferencePortable = async (
|
|
1705
|
+
portableUrl: URL,
|
|
1706
|
+
extra: {
|
|
1707
|
+
inferredGateways?: readonly URL[];
|
|
1708
|
+
response?: RemoteDocument;
|
|
1709
|
+
contextLoader?: DocumentLoader;
|
|
1710
|
+
} = {},
|
|
1711
|
+
) => {
|
|
1712
|
+
try {
|
|
1713
|
+
const obj = await dereferencePortableIri(portableUrl, {
|
|
1714
|
+
documentLoader,
|
|
1715
|
+
contextLoader: extra.contextLoader ?? contextLoader,
|
|
1716
|
+
tracerProvider,
|
|
1717
|
+
gateways: options.gateways,
|
|
1718
|
+
inferredGateways: extra.inferredGateways,
|
|
1719
|
+
response: extra.response,
|
|
1720
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
1721
|
+
referrer: {
|
|
1722
|
+
object: this,
|
|
1723
|
+
property: ${JSON.stringify(property.uri)},
|
|
1724
|
+
},
|
|
1725
|
+
suppressError: options.suppressError,
|
|
1726
|
+
crossOrigin: options.crossOrigin,
|
|
1727
|
+
span,
|
|
1728
|
+
parse: (document, parseOptions) =>
|
|
1729
|
+
this.#${property.singularName}_fromJsonLd(document, {
|
|
1730
|
+
documentLoader,
|
|
1731
|
+
contextLoader: parseOptions.contextLoader,
|
|
1732
|
+
tracerProvider,
|
|
1733
|
+
verifyPortableObject: childVerifier,
|
|
1734
|
+
baseUrl: parseOptions.baseUrl,
|
|
1735
|
+
}),
|
|
1736
|
+
});
|
|
1737
|
+
if (obj != null) {
|
|
1738
|
+
span.setAttribute(
|
|
1739
|
+
"activitypub.object.id",
|
|
1740
|
+
(obj.id ?? portableUrl).href,
|
|
1741
|
+
);
|
|
1742
|
+
span.setAttribute(
|
|
1743
|
+
"activitypub.object.type",
|
|
1744
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
1745
|
+
obj.constructor.typeId.href
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
return obj;
|
|
1749
|
+
} catch (e) {
|
|
1750
|
+
span.setStatus({
|
|
1751
|
+
code: SpanStatusCode.ERROR,
|
|
1752
|
+
message: String(e),
|
|
1753
|
+
});
|
|
1754
|
+
throw e;
|
|
1755
|
+
} finally {
|
|
1756
|
+
span.end();
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
if (isPortableIri(url)) {
|
|
1760
|
+
// A reference in a portable actor's own document, e.g., its outbox,
|
|
1761
|
+
// has no location hints, so the actor's gateways are used instead:
|
|
1762
|
+
return await dereferencePortable(
|
|
1763
|
+
url,
|
|
1764
|
+
options.gateways == null
|
|
1765
|
+
? { inferredGateways: getReferrerGateways(this, url) }
|
|
1766
|
+
: {},
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1568
1769
|
const lookupUrl = formatIri(url);
|
|
1770
|
+
const portableMode = isPortableMode(this, options);
|
|
1771
|
+
if (portableMode) {
|
|
1772
|
+
const compatible = parseCompatibleEf61Reference(url);
|
|
1773
|
+
if (compatible === null) {
|
|
1774
|
+
span.end();
|
|
1775
|
+
return rejectMalformedCompatibleReference(lookupUrl, options);
|
|
1776
|
+
} else if (compatible != null) {
|
|
1777
|
+
return await dereferencePortable(compatible.id, {
|
|
1778
|
+
inferredGateways: [compatible.gateway],
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1569
1782
|
let fetchResult: RemoteDocument;
|
|
1570
1783
|
try {
|
|
1571
|
-
fetchResult = await documentLoader(lookupUrl
|
|
1784
|
+
fetchResult = await documentLoader(lookupUrl, {
|
|
1785
|
+
suppressError: options.suppressError,
|
|
1786
|
+
});
|
|
1572
1787
|
} catch (error) {
|
|
1573
1788
|
span.setStatus({
|
|
1574
1789
|
code: SpanStatusCode.ERROR,
|
|
@@ -1576,7 +1791,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1576
1791
|
});
|
|
1577
1792
|
span.end();
|
|
1578
1793
|
if (options.suppressError) {
|
|
1579
|
-
getLogger(["fedify", "vocab"]).
|
|
1794
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1580
1795
|
"Failed to fetch {url}: {error}",
|
|
1581
1796
|
{ error, url: lookupUrl }
|
|
1582
1797
|
);
|
|
@@ -1585,52 +1800,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1585
1800
|
throw error;
|
|
1586
1801
|
}
|
|
1587
1802
|
const { document, documentUrl } = fetchResult;
|
|
1588
|
-
|
|
1803
|
+
// In portable mode, the document may turn out to be a portable object,
|
|
1804
|
+
// which has to be verified with the same context documents:
|
|
1805
|
+
const snapshot = portableMode
|
|
1806
|
+
? createSnapshotContextLoader(contextLoader, options.suppressError)
|
|
1807
|
+
: null;
|
|
1808
|
+
const scopedContext = createScopedContextLoader(
|
|
1809
|
+
snapshot?.loader ?? contextLoader,
|
|
1810
|
+
snapshot == null && options.suppressError,
|
|
1811
|
+
);
|
|
1589
1812
|
try {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1813
|
+
let claim: PortableResponseClaim | null | undefined;
|
|
1814
|
+
try {
|
|
1815
|
+
const baseUrl = parseIri(documentUrl);
|
|
1816
|
+
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
1817
|
+
document,
|
|
1818
|
+
{
|
|
1819
|
+
documentLoader,
|
|
1820
|
+
contextLoader: scopedContext.loader,
|
|
1821
|
+
tracerProvider,
|
|
1822
|
+
verifyPortableObject: childVerifier,
|
|
1823
|
+
baseUrl,
|
|
1824
|
+
}
|
|
1825
|
+
);
|
|
1826
|
+
if (snapshot == null) {
|
|
1827
|
+
markUnverifiedPortableClaim(obj, url, documentUrl);
|
|
1828
|
+
} else {
|
|
1829
|
+
claim = getPortableResponseClaim(documentUrl, obj.id);
|
|
1830
|
+
}
|
|
1831
|
+
if (claim === undefined) {
|
|
1832
|
+
if (
|
|
1833
|
+
obj?.id != null &&
|
|
1834
|
+
!isTrustedIriOrigin(options, obj.id, baseUrl)
|
|
1835
|
+
) {
|
|
1836
|
+
if (options.crossOrigin === "throw") {
|
|
1837
|
+
throw new Error(
|
|
1838
|
+
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
1839
|
+
"than the document URL (" + baseUrl.href + "); refusing to return " +
|
|
1840
|
+
"the object. If you want to bypass this check and are aware of" +
|
|
1841
|
+
'the security implications, set the crossOrigin option to "trust".'
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1845
|
+
"The object's @id ({objectId}) has a different origin than the document " +
|
|
1846
|
+
"URL ({documentUrl}); refusing to return the object. If you want to " +
|
|
1847
|
+
"bypass this check and are aware of the security implications, " +
|
|
1848
|
+
'set the crossOrigin option to "trust".',
|
|
1849
|
+
{ ...fetchResult, objectId: obj.id.href },
|
|
1850
|
+
);
|
|
1851
|
+
span.end();
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
|
|
1855
|
+
span.setAttribute(
|
|
1856
|
+
"activitypub.object.type",
|
|
1857
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
1858
|
+
obj.constructor.typeId.href
|
|
1601
1859
|
);
|
|
1860
|
+
span.end();
|
|
1861
|
+
return obj;
|
|
1602
1862
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
"
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1863
|
+
} catch (e) {
|
|
1864
|
+
if (options.suppressError) {
|
|
1865
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1866
|
+
"Failed to parse {url}: {error}",
|
|
1867
|
+
{ error: e, url: lookupUrl }
|
|
1868
|
+
);
|
|
1869
|
+
span.end();
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
span.setStatus({
|
|
1873
|
+
code: SpanStatusCode.ERROR,
|
|
1874
|
+
message: String(e),
|
|
1875
|
+
});
|
|
1876
|
+
span.end();
|
|
1877
|
+
throw e;
|
|
1611
1878
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
);
|
|
1618
|
-
return obj;
|
|
1619
|
-
} catch (e) {
|
|
1620
|
-
if (options.suppressError) {
|
|
1621
|
-
getLogger(["fedify", "vocab"]).error(
|
|
1622
|
-
"Failed to parse {url}: {error}",
|
|
1623
|
-
{ error: e, url: lookupUrl }
|
|
1624
|
-
);
|
|
1625
|
-
return null;
|
|
1879
|
+
// The document stands for a portable object, so it is verified as
|
|
1880
|
+
// one instead of being trusted because of where it came from:
|
|
1881
|
+
if (claim === null) {
|
|
1882
|
+
span.end();
|
|
1883
|
+
return rejectMalformedCompatibleReference(documentUrl, options);
|
|
1626
1884
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1885
|
+
return await dereferencePortable(claim.id, {
|
|
1886
|
+
inferredGateways: claim.inferredGateways,
|
|
1887
|
+
response: fetchResult,
|
|
1888
|
+
contextLoader: snapshot?.loader,
|
|
1630
1889
|
});
|
|
1631
|
-
throw e;
|
|
1632
1890
|
} finally {
|
|
1633
|
-
|
|
1891
|
+
scopedContext.release();
|
|
1892
|
+
snapshot?.release();
|
|
1634
1893
|
}
|
|
1635
1894
|
});
|
|
1636
1895
|
}
|
|
@@ -1641,6 +1900,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1641
1900
|
documentLoader?: DocumentLoader,
|
|
1642
1901
|
contextLoader?: DocumentLoader,
|
|
1643
1902
|
tracerProvider?: TracerProvider,
|
|
1903
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1644
1904
|
baseUrl?: URL
|
|
1645
1905
|
}
|
|
1646
1906
|
): Promise<${getTypeNames(property.range, types)}> {
|
|
@@ -1651,6 +1911,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1651
1911
|
const tracerProvider = options.tracerProvider ??
|
|
1652
1912
|
this._tracerProvider ?? trace.getTracerProvider();
|
|
1653
1913
|
const baseUrl = options.baseUrl;
|
|
1914
|
+
const verifyPortableObject = options.verifyPortableObject;
|
|
1654
1915
|
`;
|
|
1655
1916
|
if (property.preprocessors != null && property.preprocessors.length > 0) {
|
|
1656
1917
|
yield `
|
|
@@ -1670,6 +1931,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1670
1931
|
documentLoader,
|
|
1671
1932
|
contextLoader,
|
|
1672
1933
|
tracerProvider,
|
|
1934
|
+
verifyPortableObject,
|
|
1673
1935
|
baseUrl,
|
|
1674
1936
|
});
|
|
1675
1937
|
if (_result instanceof Error) throw _result;
|
|
@@ -1688,7 +1950,13 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1688
1950
|
try {
|
|
1689
1951
|
return await ${types[range].name}.fromJsonLd(
|
|
1690
1952
|
jsonLd,
|
|
1691
|
-
{
|
|
1953
|
+
{
|
|
1954
|
+
documentLoader,
|
|
1955
|
+
contextLoader,
|
|
1956
|
+
tracerProvider,
|
|
1957
|
+
verifyPortableObject,
|
|
1958
|
+
baseUrl,
|
|
1959
|
+
},
|
|
1692
1960
|
);
|
|
1693
1961
|
} catch (e) {
|
|
1694
1962
|
if (!(e instanceof TypeError)) throw e;
|
|
@@ -1730,6 +1998,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1730
1998
|
suppressError?: boolean,
|
|
1731
1999
|
tracerProvider?: TracerProvider,
|
|
1732
2000
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
2001
|
+
gateways?: readonly (string | URL)[];
|
|
2002
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
2003
|
+
/**
|
|
2004
|
+
* Whether objects that this call newly fetches use the
|
|
2005
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
2006
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
2007
|
+
* applies to this call only, and fetched objects use this object's
|
|
2008
|
+
* own default verifier, if any, as their default instead. Pass
|
|
2009
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
2010
|
+
* everything. Objects that were already cached in this object or
|
|
2011
|
+
* embedded in it keep their defaults either way, and this option
|
|
2012
|
+
* itself is not passed on.
|
|
2013
|
+
* @since 2.4.0
|
|
2014
|
+
*/
|
|
2015
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1733
2016
|
} = {}
|
|
1734
2017
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1735
2018
|
if (this._warning != null) {
|
|
@@ -1739,25 +2022,46 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1739
2022
|
);
|
|
1740
2023
|
}
|
|
1741
2024
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
2025
|
+
if (
|
|
2026
|
+
options.verifyPortableObject == null &&
|
|
2027
|
+
this._verifyPortableObject != null
|
|
2028
|
+
) {
|
|
2029
|
+
options = {
|
|
2030
|
+
...options,
|
|
2031
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
1742
2034
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
1743
2035
|
if (!(v instanceof URL) &&
|
|
1744
|
-
v.id != null &&
|
|
1745
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1746
2036
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1747
|
-
|
|
2037
|
+
if (v.id == null) {
|
|
2038
|
+
if (isUnsecuredPortableObject(this)) {
|
|
2039
|
+
warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
} else if (
|
|
2043
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
2044
|
+
isUnsecuredPortableObject(this) ||
|
|
2045
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
2046
|
+
) {
|
|
2047
|
+
v = v.id;
|
|
2048
|
+
}
|
|
1748
2049
|
}
|
|
1749
2050
|
if (v instanceof URL) {
|
|
1750
2051
|
const fetched =
|
|
1751
2052
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1752
2053
|
if (fetched == null) return null;
|
|
2054
|
+
// Not cached, so that it is verified when the portable object
|
|
2055
|
+
// policy applies later:
|
|
2056
|
+
if (isUnverifiedPortableClaim(fetched)) return fetched;
|
|
1753
2057
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
1754
2058
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
1755
2059
|
this._cachedJsonLd = undefined;
|
|
2060
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1756
2061
|
return fetched;
|
|
1757
2062
|
}
|
|
1758
2063
|
`;
|
|
1759
|
-
if (property.compactName != null)
|
|
1760
|
-
yield `
|
|
2064
|
+
if (property.compactName != null) yield `
|
|
1761
2065
|
if (
|
|
1762
2066
|
this._cachedJsonLd != null &&
|
|
1763
2067
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1768,26 +2072,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1768
2072
|
${JSON.stringify(property.compactName)}];
|
|
1769
2073
|
const doc = Array.isArray(prop) ? prop[0] : prop;
|
|
1770
2074
|
if (doc != null && typeof doc === "object" && "@context" in doc) {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2075
|
+
try {
|
|
2076
|
+
v = await this.#${property.singularName}_fromJsonLd(doc, {
|
|
2077
|
+
...options,
|
|
2078
|
+
// Keep the parent default for embedded objects.
|
|
2079
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2080
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
2081
|
+
});
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
if (!options.suppressError) throw error;
|
|
2084
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
2085
|
+
"Failed to parse embedded value of {property}: {error}",
|
|
2086
|
+
{ property: ${JSON.stringify(property.uri)}, error },
|
|
2087
|
+
);
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
1779
2090
|
}
|
|
1780
2091
|
}
|
|
1781
2092
|
`;
|
|
1782
|
-
}
|
|
1783
2093
|
yield `
|
|
1784
2094
|
if (v?.id != null &&
|
|
1785
|
-
this.id != null && !isTrustedIriOrigin(options, v.id,
|
|
2095
|
+
((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
2096
|
+
isUnsecuredPortableObject(this) ||
|
|
2097
|
+
mustDereferencePortableObject(
|
|
2098
|
+
this, ${trustOwner}, v.id, options,
|
|
2099
|
+
)) &&
|
|
1786
2100
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1787
2101
|
if (options.crossOrigin === "throw") {
|
|
1788
2102
|
throw new Error(
|
|
1789
2103
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1790
|
-
"origin than the property owner's @id (" + this.id
|
|
2104
|
+
"origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
|
|
1791
2105
|
"refusing to return the object. If you want to bypass this " +
|
|
1792
2106
|
"check and are aware of the security implications, set the " +
|
|
1793
2107
|
'crossOrigin option to "trust".'
|
|
@@ -1799,10 +2113,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1799
2113
|
"return the object. If you want to bypass this check and are " +
|
|
1800
2114
|
"aware of the security implications, set the crossOrigin option " +
|
|
1801
2115
|
'to "trust".',
|
|
1802
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2116
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1803
2117
|
);
|
|
1804
2118
|
return null;
|
|
1805
2119
|
}
|
|
2120
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1806
2121
|
return v;
|
|
1807
2122
|
}
|
|
1808
2123
|
`;
|
|
@@ -1835,6 +2150,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1835
2150
|
suppressError?: boolean,
|
|
1836
2151
|
tracerProvider?: TracerProvider,
|
|
1837
2152
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
2153
|
+
gateways?: readonly (string | URL)[];
|
|
2154
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
2155
|
+
/**
|
|
2156
|
+
* Whether objects that this call newly fetches use the
|
|
2157
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
2158
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
2159
|
+
* applies to this call only, and fetched objects use this object's
|
|
2160
|
+
* own default verifier, if any, as their default instead. Pass
|
|
2161
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
2162
|
+
* everything. Objects that were already cached in this object or
|
|
2163
|
+
* embedded in it keep their defaults either way, and this option
|
|
2164
|
+
* itself is not passed on.
|
|
2165
|
+
* @since 2.4.0
|
|
2166
|
+
*/
|
|
2167
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1838
2168
|
} = {}
|
|
1839
2169
|
): AsyncIterable<${getTypeNames(property.range, types)}> {
|
|
1840
2170
|
if (this._warning != null) {
|
|
@@ -1843,28 +2173,52 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1843
2173
|
this._warning.values
|
|
1844
2174
|
);
|
|
1845
2175
|
}
|
|
2176
|
+
if (
|
|
2177
|
+
options.verifyPortableObject == null &&
|
|
2178
|
+
this._verifyPortableObject != null
|
|
2179
|
+
) {
|
|
2180
|
+
options = {
|
|
2181
|
+
...options,
|
|
2182
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
1846
2185
|
const vs = this.${await getFieldName(property.uri)};
|
|
1847
2186
|
for (let i = 0; i < vs.length; i++) {
|
|
1848
2187
|
let v = vs[i];
|
|
1849
2188
|
if (!(v instanceof URL) &&
|
|
1850
|
-
v.id != null &&
|
|
1851
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1852
2189
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1853
|
-
|
|
2190
|
+
if (v.id == null) {
|
|
2191
|
+
if (isUnsecuredPortableObject(this)) {
|
|
2192
|
+
warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
} else if (
|
|
2196
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
2197
|
+
isUnsecuredPortableObject(this) ||
|
|
2198
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
2199
|
+
) {
|
|
2200
|
+
v = v.id;
|
|
2201
|
+
}
|
|
1854
2202
|
}
|
|
1855
2203
|
if (v instanceof URL) {
|
|
1856
2204
|
const fetched =
|
|
1857
2205
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1858
2206
|
if (fetched == null) continue;
|
|
2207
|
+
// Not cached, so that it is verified when the portable object
|
|
2208
|
+
// policy applies later:
|
|
2209
|
+
if (isUnverifiedPortableClaim(fetched)) {
|
|
2210
|
+
yield fetched;
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
1859
2213
|
vs[i] = fetched;
|
|
1860
2214
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
1861
2215
|
this._cachedJsonLd = undefined;
|
|
2216
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1862
2217
|
yield fetched;
|
|
1863
2218
|
continue;
|
|
1864
2219
|
}
|
|
1865
2220
|
`;
|
|
1866
|
-
if (property.compactName != null)
|
|
1867
|
-
yield `
|
|
2221
|
+
if (property.compactName != null) yield `
|
|
1868
2222
|
if (
|
|
1869
2223
|
this._cachedJsonLd != null &&
|
|
1870
2224
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1875,26 +2229,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1875
2229
|
${JSON.stringify(property.compactName)}];
|
|
1876
2230
|
const obj = Array.isArray(prop) ? prop[i] : prop;
|
|
1877
2231
|
if (obj != null && typeof obj === "object" && "@context" in obj) {
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2232
|
+
try {
|
|
2233
|
+
v = await this.#${property.singularName}_fromJsonLd(obj, {
|
|
2234
|
+
...options,
|
|
2235
|
+
// Keep the parent default for embedded objects.
|
|
2236
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2237
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
2238
|
+
});
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
if (!options.suppressError) throw error;
|
|
2241
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
2242
|
+
"Failed to parse embedded value of {property} at index {index}: {error}",
|
|
2243
|
+
{ property: ${JSON.stringify(property.uri)}, index: i, error },
|
|
2244
|
+
);
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
1886
2247
|
}
|
|
1887
2248
|
}
|
|
1888
2249
|
`;
|
|
1889
|
-
}
|
|
1890
2250
|
yield `
|
|
1891
2251
|
if (v?.id != null &&
|
|
1892
|
-
this.id != null && !isTrustedIriOrigin(options, v.id,
|
|
2252
|
+
((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
2253
|
+
isUnsecuredPortableObject(this) ||
|
|
2254
|
+
mustDereferencePortableObject(
|
|
2255
|
+
this, ${trustOwner}, v.id, options,
|
|
2256
|
+
)) &&
|
|
1893
2257
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1894
2258
|
if (options.crossOrigin === "throw") {
|
|
1895
2259
|
throw new Error(
|
|
1896
2260
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1897
|
-
"origin than the property owner's @id (" + this.id
|
|
2261
|
+
"origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
|
|
1898
2262
|
"refusing to return the object. If you want to bypass this " +
|
|
1899
2263
|
"check and are aware of the security implications, set the " +
|
|
1900
2264
|
'crossOrigin option to "trust".'
|
|
@@ -1906,10 +2270,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1906
2270
|
"return the object. If you want to bypass this check and are " +
|
|
1907
2271
|
"aware of the security implications, set the crossOrigin " +
|
|
1908
2272
|
'option to "trust".',
|
|
1909
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2273
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1910
2274
|
);
|
|
1911
2275
|
continue;
|
|
1912
2276
|
}
|
|
2277
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1913
2278
|
yield v;
|
|
1914
2279
|
}
|
|
1915
2280
|
}
|
|
@@ -1927,10 +2292,35 @@ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
|
|
|
1927
2292
|
const FEDIFY_URL = "fedify:url";
|
|
1928
2293
|
const INTERNAL_RUNTIME_IMPORTS = [
|
|
1929
2294
|
"compactJsonLdCache",
|
|
2295
|
+
"createScopedContextLoader",
|
|
1930
2296
|
"getJsonLdContext",
|
|
2297
|
+
"getPortableActorGateways",
|
|
1931
2298
|
"isTrustedIriOrigin",
|
|
1932
2299
|
"normalizeJsonLdIris"
|
|
1933
2300
|
].join(",\n ");
|
|
2301
|
+
const PORTABLE_DEREFERENCE_IMPORTS = [
|
|
2302
|
+
"copyPortableProvenance",
|
|
2303
|
+
"createSnapshotContextLoader",
|
|
2304
|
+
"dereferencePortableIri",
|
|
2305
|
+
"getPortableResponseClaim",
|
|
2306
|
+
"getReferrerGateways",
|
|
2307
|
+
"isPortableIri",
|
|
2308
|
+
"isPortableMode",
|
|
2309
|
+
"isUnsecuredPortableObject",
|
|
2310
|
+
"isUnverifiedPortableClaim",
|
|
2311
|
+
"markUnverifiedPortableClaim",
|
|
2312
|
+
"mustDereferencePortableObject",
|
|
2313
|
+
"parseCompatibleEf61Reference",
|
|
2314
|
+
"type PortableResponseClaim",
|
|
2315
|
+
"recordPortableReferrer",
|
|
2316
|
+
"rejectMalformedCompatibleReference",
|
|
2317
|
+
"warnUnverifiableEmbeddedObject"
|
|
2318
|
+
].join(",\n ");
|
|
2319
|
+
const SIGNED_REPRESENTATION_IMPORTS = [
|
|
2320
|
+
"enterSignedValueScope",
|
|
2321
|
+
"resolveSignedValues",
|
|
2322
|
+
"retainedSignedValueRef"
|
|
2323
|
+
].join(",\n ");
|
|
1934
2324
|
const RUNTIME_IMPORTS = [
|
|
1935
2325
|
"canParseDecimal",
|
|
1936
2326
|
"decodeMultibase",
|
|
@@ -1944,10 +2334,13 @@ const RUNTIME_IMPORTS = [
|
|
|
1944
2334
|
"importMultibaseKey",
|
|
1945
2335
|
"importPem",
|
|
1946
2336
|
"isDecimal",
|
|
2337
|
+
"isGatewayUrl",
|
|
1947
2338
|
"LanguageString",
|
|
1948
2339
|
"parseDecimal",
|
|
2340
|
+
"parseGatewayUrl",
|
|
1949
2341
|
"parseIri",
|
|
1950
2342
|
"parseJsonLdId",
|
|
2343
|
+
"type PortableObjectVerifier",
|
|
1951
2344
|
"type RemoteDocument"
|
|
1952
2345
|
].join(",\n ");
|
|
1953
2346
|
/**
|
|
@@ -1983,6 +2376,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
1983
2376
|
readonly #documentLoader?: DocumentLoader;
|
|
1984
2377
|
readonly #contextLoader?: DocumentLoader;
|
|
1985
2378
|
readonly #tracerProvider?: TracerProvider;
|
|
2379
|
+
readonly #verifyPortableObject?: PortableObjectVerifier;
|
|
1986
2380
|
readonly #warning?: {
|
|
1987
2381
|
category: string[];
|
|
1988
2382
|
message: string;
|
|
@@ -1990,6 +2384,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
1990
2384
|
};
|
|
1991
2385
|
#cachedJsonLd?: unknown;
|
|
1992
2386
|
readonly #_baseUrl?: URL;
|
|
2387
|
+
#shouldCacheJsonLd = true;
|
|
1993
2388
|
readonly id: URL | null;
|
|
1994
2389
|
|
|
1995
2390
|
protected get _documentLoader(): DocumentLoader | undefined {
|
|
@@ -2004,6 +2399,10 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2004
2399
|
return this.#tracerProvider;
|
|
2005
2400
|
}
|
|
2006
2401
|
|
|
2402
|
+
protected get _verifyPortableObject(): PortableObjectVerifier | undefined {
|
|
2403
|
+
return this.#verifyPortableObject;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2007
2406
|
protected get _warning(): {
|
|
2008
2407
|
category: string[];
|
|
2009
2408
|
message: string;
|
|
@@ -2023,6 +2422,20 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2023
2422
|
protected get _baseUrl(): URL | undefined {
|
|
2024
2423
|
return this.#_baseUrl;
|
|
2025
2424
|
}
|
|
2425
|
+
|
|
2426
|
+
protected get _shouldCacheJsonLd(): boolean {
|
|
2427
|
+
return this.#shouldCacheJsonLd;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
protected set _shouldCacheJsonLd(value: boolean) {
|
|
2431
|
+
this.#shouldCacheJsonLd = value;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
protected static _shouldCacheDecodedJsonLd(value: unknown): boolean {
|
|
2435
|
+
if (value == null || typeof value !== "object") return true;
|
|
2436
|
+
if (!("_shouldCacheJsonLd" in value)) return true;
|
|
2437
|
+
return (value as { _shouldCacheJsonLd: boolean })._shouldCacheJsonLd;
|
|
2438
|
+
}
|
|
2026
2439
|
`;
|
|
2027
2440
|
yield `
|
|
2028
2441
|
/**
|
|
@@ -2109,11 +2522,69 @@ async function* generateClasses(types) {
|
|
|
2109
2522
|
from "@opentelemetry/api";\n`;
|
|
2110
2523
|
yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
|
|
2111
2524
|
yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
|
|
2525
|
+
yield `import {\n ${PORTABLE_DEREFERENCE_IMPORTS}\n} from "@fedify/vocab-runtime/internal/portable-dereference";\n`;
|
|
2526
|
+
yield `import {\n ${SIGNED_REPRESENTATION_IMPORTS}\n} from "@fedify/vocab-runtime/internal/signed-representation";\n`;
|
|
2112
2527
|
yield `import {
|
|
2113
2528
|
isTemporalDuration,
|
|
2114
2529
|
isTemporalInstant,
|
|
2115
2530
|
} from "@fedify/vocab-runtime/temporal";\n`;
|
|
2116
|
-
|
|
2531
|
+
yield `
|
|
2532
|
+
function canDecodeIri(iri: string): boolean {
|
|
2533
|
+
try { parseIri(iri); return true; } catch { return iri.startsWith("at://"); }
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
function decodeIri(iri: string): URL {
|
|
2537
|
+
return !URL.canParse(iri) && iri.startsWith("at://")
|
|
2538
|
+
? new URL("at://" + encodeURIComponent(iri.substring(5)))
|
|
2539
|
+
: parseIri(iri);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function inspectIri(iri: URL): string {
|
|
2543
|
+
// Inspecting an object must not throw, even for a URL that cannot be
|
|
2544
|
+
// formatted as an IRI, e.g., a malformed portable ID:
|
|
2545
|
+
try { return formatIri(iri); } catch { return iri.href; }
|
|
2546
|
+
}
|
|
2547
|
+
`;
|
|
2548
|
+
yield `
|
|
2549
|
+
function isValidLanguageTag(language: string): boolean {
|
|
2550
|
+
try {
|
|
2551
|
+
new Intl.Locale(language);
|
|
2552
|
+
return true;
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
if (error instanceof RangeError) return false;
|
|
2555
|
+
throw error;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
`;
|
|
2559
|
+
const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
|
|
2560
|
+
if (Object.keys(extraContexts).length > 0) yield `
|
|
2561
|
+
const extraPropertyContexts: Readonly<Record<string, string>> =
|
|
2562
|
+
${JSON.stringify(extraContexts)};
|
|
2563
|
+
|
|
2564
|
+
function getExtraContexts(document: unknown): string[] {
|
|
2565
|
+
const contexts = new Set<string>();
|
|
2566
|
+
const pending: unknown[] = [document];
|
|
2567
|
+
while (pending.length > 0) {
|
|
2568
|
+
const value = pending.pop();
|
|
2569
|
+
if (value == null || typeof value !== "object") continue;
|
|
2570
|
+
if (Array.isArray(value)) {
|
|
2571
|
+
for (const item of value) pending.push(item);
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if ("@value" in value) continue;
|
|
2575
|
+
for (const [key, child] of globalThis.Object.entries(value)) {
|
|
2576
|
+
if (key === "@context") continue;
|
|
2577
|
+
if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
|
|
2578
|
+
contexts.add(extraPropertyContexts[key]);
|
|
2579
|
+
}
|
|
2580
|
+
pending.push(child);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
return [...contexts];
|
|
2584
|
+
}
|
|
2585
|
+
`;
|
|
2586
|
+
yield "\n\n";
|
|
2587
|
+
const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
|
|
2117
2588
|
for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
|
|
2118
2589
|
yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
|
|
2119
2590
|
yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;
|