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