@fedify/vocab-tools 2.4.0-pr.936.41 → 2.5.0-dev.2271
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 +563 -118
- package/dist/mod.d.cts +14 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +563 -118
- package/dist/schema.yaml +15 -0
- package/package.json +2 -2
- package/src/__snapshots__/class.test.ts.deno.snap +53887 -11652
- package/src/__snapshots__/class.test.ts.node.snap +53887 -11652
- package/src/__snapshots__/class.test.ts.snap +53887 -11652
- package/src/class.ts +115 -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 +19 -7
package/dist/mod.cjs
CHANGED
|
@@ -8,7 +8,7 @@ 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.
|
|
11
|
+
var version = "2.5.0-dev.2271+49d937e2";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/type.ts
|
|
14
14
|
const HEURISTICS_CONTEXTS = [
|
|
@@ -186,7 +186,8 @@ const scalarTypes = {
|
|
|
186
186
|
dataCheck(v) {
|
|
187
187
|
return `typeof ${v} === "object" && "@language" in ${v} && "@value" in ${v}
|
|
188
188
|
&& typeof ${v}["@language"] === "string"
|
|
189
|
-
&& typeof ${v}["@value"] === "string"
|
|
189
|
+
&& typeof ${v}["@value"] === "string"
|
|
190
|
+
&& isValidLanguageTag(${v}["@language"])`;
|
|
190
191
|
},
|
|
191
192
|
decoder(v) {
|
|
192
193
|
return `new LanguageString(${v}["@value"], ${v}["@language"])`;
|
|
@@ -317,13 +318,14 @@ const scalarTypes = {
|
|
|
317
318
|
return `formatIri(${v})`;
|
|
318
319
|
},
|
|
319
320
|
dataCheck(v) {
|
|
320
|
-
return `typeof ${v} === "object" &&
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
return `typeof ${v} === "object" &&
|
|
322
|
+
((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
|
|
323
|
+
(typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
|
|
323
324
|
},
|
|
324
325
|
decoder(v) {
|
|
325
|
-
return `
|
|
326
|
-
}
|
|
326
|
+
return `decodeIri(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
|
|
327
|
+
},
|
|
328
|
+
skipUnparsable: true
|
|
327
329
|
},
|
|
328
330
|
"fedify:gatewayUrl": {
|
|
329
331
|
name: "URL",
|
|
@@ -331,10 +333,10 @@ const scalarTypes = {
|
|
|
331
333
|
return `${v} instanceof URL && isGatewayUrl(${v})`;
|
|
332
334
|
},
|
|
333
335
|
encoder(v) {
|
|
334
|
-
return `{ "@id":
|
|
336
|
+
return `{ "@id": ${v}.origin }`;
|
|
335
337
|
},
|
|
336
338
|
compactEncoder(v) {
|
|
337
|
-
return
|
|
339
|
+
return `${v}.origin`;
|
|
338
340
|
},
|
|
339
341
|
dataCheck(v) {
|
|
340
342
|
return `${v} != null && typeof ${v} === "object" &&
|
|
@@ -557,6 +559,9 @@ function getDecoder(typeUri, types, variable, optionsVariable, baseUrlVariable)
|
|
|
557
559
|
)`;
|
|
558
560
|
throw new Error(`Unknown type: ${typeUri}`);
|
|
559
561
|
}
|
|
562
|
+
function skipsUnparsable(typeUri) {
|
|
563
|
+
return scalarTypes[typeUri]?.skipUnparsable ?? false;
|
|
564
|
+
}
|
|
560
565
|
function getDataCheck(typeUri, types, variable) {
|
|
561
566
|
if (typeUri in scalarTypes) return scalarTypes[typeUri].dataCheck(variable);
|
|
562
567
|
if (typeUri in types) {
|
|
@@ -736,6 +741,7 @@ function* generatePreprocessorBlock(property, rangeTypeName, variable, baseUrlEx
|
|
|
736
741
|
documentLoader: options.documentLoader,
|
|
737
742
|
contextLoader: options.contextLoader,
|
|
738
743
|
tracerProvider: options.tracerProvider,
|
|
744
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
739
745
|
baseUrl: ${baseUrlExpr},
|
|
740
746
|
});
|
|
741
747
|
if (_result instanceof Error) throw _result;
|
|
@@ -784,6 +790,10 @@ async function* generateEncoder(typeUri, types) {
|
|
|
784
790
|
...options,
|
|
785
791
|
contextLoader: options.contextLoader ?? getDocumentLoader(),
|
|
786
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);
|
|
787
797
|
`;
|
|
788
798
|
if (isCompactableType(typeUri, types)) {
|
|
789
799
|
yield `
|
|
@@ -814,9 +824,13 @@ async function* generateEncoder(typeUri, types) {
|
|
|
814
824
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
815
825
|
const item = (
|
|
816
826
|
`;
|
|
817
|
-
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
|
+
}
|
|
818
831
|
const encoders = getEncoders(property.range, types, "v", "options", true);
|
|
819
832
|
for (const code of encoders) yield code;
|
|
833
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
820
834
|
yield `
|
|
821
835
|
);
|
|
822
836
|
compactItems.push(item);
|
|
@@ -847,7 +861,9 @@ async function* generateEncoder(typeUri, types) {
|
|
|
847
861
|
${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
|
|
848
862
|
if (this.id != null) result["id"] = formatIri(this.id);
|
|
849
863
|
result["@context"] = ${JSON.stringify(type.defaultContext)};
|
|
850
|
-
return
|
|
864
|
+
return signedValues.owner
|
|
865
|
+
? resolveSignedValues(result, signedValues.scope)
|
|
866
|
+
: result;
|
|
851
867
|
}
|
|
852
868
|
`;
|
|
853
869
|
}
|
|
@@ -871,12 +887,21 @@ async function* generateEncoder(typeUri, types) {
|
|
|
871
887
|
yield `
|
|
872
888
|
array = [];
|
|
873
889
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
874
|
-
|
|
890
|
+
let element = (
|
|
875
891
|
`;
|
|
876
|
-
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
|
+
}
|
|
877
896
|
for (const code of getEncoders(property.range, types, "v", "options")) yield code;
|
|
897
|
+
if (!areAllScalarTypes(property.range, types)) yield ")";
|
|
878
898
|
yield `
|
|
879
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;
|
|
880
905
|
`;
|
|
881
906
|
if (isNonFunctionalProperty(property) && property.container === "graph") yield `array.push({ "@graph": element });`;
|
|
882
907
|
else yield `array.push(element);`;
|
|
@@ -904,16 +929,35 @@ async function* generateEncoder(typeUri, types) {
|
|
|
904
929
|
if (options.format === "expand") {
|
|
905
930
|
return await jsonld.expand(
|
|
906
931
|
values,
|
|
907
|
-
{
|
|
932
|
+
{
|
|
933
|
+
documentLoader: options.contextLoader,
|
|
934
|
+
keepFreeFloatingNodes: true,
|
|
935
|
+
},
|
|
908
936
|
);
|
|
909
937
|
}
|
|
910
|
-
|
|
938
|
+
let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
|
|
911
939
|
${JSON.stringify(type.defaultContext)};
|
|
912
|
-
|
|
940
|
+
let compacted = await jsonld.compact(
|
|
913
941
|
values,
|
|
914
942
|
docContext,
|
|
915
943
|
{ documentLoader: options.contextLoader },
|
|
916
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 `
|
|
917
961
|
if (docContext != null) {
|
|
918
962
|
// Embed context
|
|
919
963
|
`;
|
|
@@ -937,12 +981,14 @@ async function* generateEncoder(typeUri, types) {
|
|
|
937
981
|
}
|
|
938
982
|
yield `
|
|
939
983
|
}
|
|
940
|
-
return
|
|
984
|
+
return signedValues.owner
|
|
985
|
+
? resolveSignedValues(compacted, signedValues.scope)
|
|
986
|
+
: compacted;
|
|
941
987
|
}
|
|
942
988
|
|
|
943
989
|
protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
|
|
944
990
|
`;
|
|
945
|
-
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 `
|
|
946
992
|
if (
|
|
947
993
|
this.${await getFieldName(property.uri)} != null &&
|
|
948
994
|
this.${await getFieldName(property.uri)}.length > 0
|
|
@@ -964,6 +1010,10 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
964
1010
|
* - \`contextLoader\`: The loader for remote JSON-LD contexts.
|
|
965
1011
|
* - \`tracerProvider\`: The OpenTelemetry tracer provider to use.
|
|
966
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.
|
|
967
1017
|
* @returns The object of this type.
|
|
968
1018
|
* @throws {TypeError} If the given \`json\` is invalid.
|
|
969
1019
|
*/
|
|
@@ -973,6 +1023,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
973
1023
|
documentLoader?: DocumentLoader,
|
|
974
1024
|
contextLoader?: DocumentLoader,
|
|
975
1025
|
tracerProvider?: TracerProvider,
|
|
1026
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
976
1027
|
baseUrl?: URL,
|
|
977
1028
|
} = {},
|
|
978
1029
|
): Promise<${type.name}> {
|
|
@@ -1011,6 +1062,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1011
1062
|
documentLoader?: DocumentLoader,
|
|
1012
1063
|
contextLoader?: DocumentLoader,
|
|
1013
1064
|
tracerProvider?: TracerProvider,
|
|
1065
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1014
1066
|
baseUrl?: URL,
|
|
1015
1067
|
} = {},
|
|
1016
1068
|
): Promise<${type.name}> {
|
|
@@ -1027,7 +1079,17 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1027
1079
|
// deno-lint-ignore no-explicit-any
|
|
1028
1080
|
let values: Record<string, any[]> & { "@id"?: string };
|
|
1029
1081
|
let expanded: unknown[];
|
|
1030
|
-
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) {
|
|
1031
1093
|
values = {};
|
|
1032
1094
|
expanded = [values];
|
|
1033
1095
|
} else {
|
|
@@ -1084,8 +1146,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1084
1146
|
);
|
|
1085
1147
|
`;
|
|
1086
1148
|
else yield `
|
|
1087
|
-
|
|
1088
|
-
|
|
1149
|
+
const superValues = structuredClone(values);
|
|
1150
|
+
delete superValues["@type"];
|
|
1151
|
+
const instance = await super.fromJsonLd(superValues, {
|
|
1089
1152
|
...options,
|
|
1090
1153
|
// @ts-ignore: an internal option
|
|
1091
1154
|
_fromSubclass: true,
|
|
@@ -1094,25 +1157,35 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1094
1157
|
throw new TypeError("Unexpected type: " + instance.constructor.name);
|
|
1095
1158
|
}
|
|
1096
1159
|
`;
|
|
1160
|
+
yield `
|
|
1161
|
+
let shouldCacheJsonLd = instance._shouldCacheJsonLd;
|
|
1162
|
+
`;
|
|
1097
1163
|
for (const property of type.properties) {
|
|
1098
1164
|
const variable = await getFieldName(property.uri, "");
|
|
1099
1165
|
yield await generateField(property, types, "const ");
|
|
1100
1166
|
const arrayVariable = `${variable}__array`;
|
|
1101
1167
|
yield `
|
|
1102
|
-
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)}]`};
|
|
1103
1169
|
`;
|
|
1104
1170
|
if (property.functional && property.redundantProperties != null) for (const prop of property.redundantProperties) yield `
|
|
1105
1171
|
if (${arrayVariable} == null || ${arrayVariable}.length < 1) {
|
|
1106
1172
|
${arrayVariable} = values[${JSON.stringify(prop.uri)}];
|
|
1107
1173
|
}
|
|
1108
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
|
+
)`;
|
|
1109
1184
|
yield `
|
|
1110
1185
|
for (
|
|
1111
1186
|
const v of ${arrayVariable} == null
|
|
1112
1187
|
? []
|
|
1113
|
-
: ${
|
|
1114
|
-
? ${arrayVariable}[0]["@list"]
|
|
1115
|
-
: ${arrayVariable}
|
|
1188
|
+
: ${itemsExpression}
|
|
1116
1189
|
) {
|
|
1117
1190
|
if (v == null) continue;
|
|
1118
1191
|
`;
|
|
@@ -1126,29 +1199,47 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1126
1199
|
continue;
|
|
1127
1200
|
}
|
|
1128
1201
|
`;
|
|
1129
|
-
|
|
1130
|
-
const decoded = ${getDecoder(property.range[0], types, "v", "options", propertyBaseUrl)};
|
|
1131
|
-
if (typeof decoded === "undefined") continue;
|
|
1132
|
-
${variable}.push(decoded);`;
|
|
1133
|
-
else {
|
|
1134
|
-
yield `
|
|
1202
|
+
yield `
|
|
1135
1203
|
const decoded =
|
|
1136
|
-
|
|
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 {
|
|
1137
1214
|
const decoders = getDecoders(property.range, types, "v", "options", propertyBaseUrl);
|
|
1138
1215
|
for (const code of decoders) yield code;
|
|
1139
|
-
|
|
1216
|
+
}
|
|
1217
|
+
yield `
|
|
1140
1218
|
;
|
|
1141
|
-
|
|
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
|
+
}
|
|
1142
1230
|
${variable}.push(decoded);
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1231
|
+
`;
|
|
1145
1232
|
yield `
|
|
1146
1233
|
}
|
|
1147
1234
|
instance.${await getFieldName(property.uri)} = ${variable};
|
|
1148
1235
|
`;
|
|
1149
1236
|
}
|
|
1150
1237
|
yield `
|
|
1151
|
-
|
|
1238
|
+
instance._shouldCacheJsonLd = shouldCacheJsonLd;
|
|
1239
|
+
if (
|
|
1240
|
+
shouldCacheJsonLd &&
|
|
1241
|
+
(!("_fromSubclass" in options) || !options._fromSubclass)
|
|
1242
|
+
) {
|
|
1152
1243
|
try {
|
|
1153
1244
|
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
1154
1245
|
const compactArray = Array.isArray(json) && json.length === 1;
|
|
@@ -1214,6 +1305,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1214
1305
|
documentLoader?: DocumentLoader,
|
|
1215
1306
|
contextLoader?: DocumentLoader,
|
|
1216
1307
|
tracerProvider?: TracerProvider,
|
|
1308
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1217
1309
|
} = {},
|
|
1218
1310
|
) {
|
|
1219
1311
|
`;
|
|
@@ -1221,6 +1313,7 @@ async function* generateConstructor(typeUri, types) {
|
|
|
1221
1313
|
this.#documentLoader = options.documentLoader;
|
|
1222
1314
|
this.#contextLoader = options.contextLoader;
|
|
1223
1315
|
this.#tracerProvider = options.tracerProvider;
|
|
1316
|
+
this.#verifyPortableObject = options.verifyPortableObject;
|
|
1224
1317
|
const baseUrl = (options as { baseUrl?: URL }).baseUrl;
|
|
1225
1318
|
this.#_baseUrl = baseUrl == null ? undefined : new URL(baseUrl.href);
|
|
1226
1319
|
if ("$warning" in options) {
|
|
@@ -1315,6 +1408,13 @@ async function* generateCloner(typeUri, types) {
|
|
|
1315
1408
|
yield `
|
|
1316
1409
|
/**
|
|
1317
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
|
+
*
|
|
1318
1418
|
* @param values The values to update the clone with.
|
|
1319
1419
|
* @param options The options to use for cloning.
|
|
1320
1420
|
* @returns The cloned instance.
|
|
@@ -1328,6 +1428,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1328
1428
|
options: {
|
|
1329
1429
|
documentLoader?: DocumentLoader,
|
|
1330
1430
|
contextLoader?: DocumentLoader,
|
|
1431
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1331
1432
|
} = {}
|
|
1332
1433
|
): ${type.name} {
|
|
1333
1434
|
if (this._warning != null) {
|
|
@@ -1345,6 +1446,7 @@ async function* generateCloner(typeUri, types) {
|
|
|
1345
1446
|
{ id: values.id ?? this.id },
|
|
1346
1447
|
options
|
|
1347
1448
|
);
|
|
1449
|
+
copyPortableProvenance(this, clone);
|
|
1348
1450
|
`;
|
|
1349
1451
|
else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
|
|
1350
1452
|
for (const property of type.properties) {
|
|
@@ -1440,12 +1542,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1440
1542
|
[Symbol.for("Deno.customInspect")]: (
|
|
1441
1543
|
inspect: typeof Deno.inspect,
|
|
1442
1544
|
options: Deno.InspectOptions,
|
|
1443
|
-
): string => "URL " + inspect(this.id
|
|
1545
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1444
1546
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1445
1547
|
_depth: number,
|
|
1446
1548
|
options: unknown,
|
|
1447
1549
|
inspect: (value: unknown, options: unknown) => string,
|
|
1448
|
-
): string => "URL " + inspect(this.id
|
|
1550
|
+
): string => "URL " + inspect(inspectIri(this.id!), options),
|
|
1449
1551
|
};
|
|
1450
1552
|
}
|
|
1451
1553
|
`;
|
|
@@ -1461,12 +1563,12 @@ async function* generateInspector(typeUri, types) {
|
|
|
1461
1563
|
[Symbol.for("Deno.customInspect")]: (
|
|
1462
1564
|
inspect: typeof Deno.inspect,
|
|
1463
1565
|
options: Deno.InspectOptions,
|
|
1464
|
-
): string => "URL " + inspect(v
|
|
1566
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1465
1567
|
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
1466
1568
|
_depth: number,
|
|
1467
1569
|
options: unknown,
|
|
1468
1570
|
inspect: (value: unknown, options: unknown) => string,
|
|
1469
|
-
): string => "URL " + inspect(v
|
|
1571
|
+
): string => "URL " + inspect(inspectIri(v), options),
|
|
1470
1572
|
}
|
|
1471
1573
|
: v);
|
|
1472
1574
|
`;
|
|
@@ -1536,6 +1638,7 @@ function emitOverride(typeUri, types, property) {
|
|
|
1536
1638
|
}
|
|
1537
1639
|
async function* generateProperty(type, property, types, moduleVarNames) {
|
|
1538
1640
|
const override = emitOverride(type.uri, types, property);
|
|
1641
|
+
const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
|
|
1539
1642
|
const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
|
|
1540
1643
|
const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
|
|
1541
1644
|
this._baseUrl ??
|
|
@@ -1576,6 +1679,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1576
1679
|
suppressError?: boolean,
|
|
1577
1680
|
tracerProvider?: TracerProvider,
|
|
1578
1681
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
1682
|
+
gateways?: readonly (string | URL)[];
|
|
1683
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
1684
|
+
inheritPortableObjectVerifier?: boolean;
|
|
1579
1685
|
} = {},
|
|
1580
1686
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1581
1687
|
const documentLoader =
|
|
@@ -1588,11 +1694,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1588
1694
|
${JSON.stringify(name)},
|
|
1589
1695
|
${JSON.stringify(version)},
|
|
1590
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;
|
|
1591
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
|
+
}
|
|
1592
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
|
+
}
|
|
1593
1782
|
let fetchResult: RemoteDocument;
|
|
1594
1783
|
try {
|
|
1595
|
-
fetchResult = await documentLoader(lookupUrl
|
|
1784
|
+
fetchResult = await documentLoader(lookupUrl, {
|
|
1785
|
+
suppressError: options.suppressError,
|
|
1786
|
+
});
|
|
1596
1787
|
} catch (error) {
|
|
1597
1788
|
span.setStatus({
|
|
1598
1789
|
code: SpanStatusCode.ERROR,
|
|
@@ -1600,7 +1791,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1600
1791
|
});
|
|
1601
1792
|
span.end();
|
|
1602
1793
|
if (options.suppressError) {
|
|
1603
|
-
getLogger(["fedify", "vocab"]).
|
|
1794
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
1604
1795
|
"Failed to fetch {url}: {error}",
|
|
1605
1796
|
{ error, url: lookupUrl }
|
|
1606
1797
|
);
|
|
@@ -1609,52 +1800,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1609
1800
|
throw error;
|
|
1610
1801
|
}
|
|
1611
1802
|
const { document, documentUrl } = fetchResult;
|
|
1612
|
-
|
|
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
|
+
);
|
|
1613
1812
|
try {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
|
1625
1859
|
);
|
|
1860
|
+
span.end();
|
|
1861
|
+
return obj;
|
|
1626
1862
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
"
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
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;
|
|
1635
1878
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
);
|
|
1642
|
-
return obj;
|
|
1643
|
-
} catch (e) {
|
|
1644
|
-
if (options.suppressError) {
|
|
1645
|
-
getLogger(["fedify", "vocab"]).error(
|
|
1646
|
-
"Failed to parse {url}: {error}",
|
|
1647
|
-
{ error: e, url: lookupUrl }
|
|
1648
|
-
);
|
|
1649
|
-
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);
|
|
1650
1884
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1885
|
+
return await dereferencePortable(claim.id, {
|
|
1886
|
+
inferredGateways: claim.inferredGateways,
|
|
1887
|
+
response: fetchResult,
|
|
1888
|
+
contextLoader: snapshot?.loader,
|
|
1654
1889
|
});
|
|
1655
|
-
throw e;
|
|
1656
1890
|
} finally {
|
|
1657
|
-
|
|
1891
|
+
scopedContext.release();
|
|
1892
|
+
snapshot?.release();
|
|
1658
1893
|
}
|
|
1659
1894
|
});
|
|
1660
1895
|
}
|
|
@@ -1665,6 +1900,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1665
1900
|
documentLoader?: DocumentLoader,
|
|
1666
1901
|
contextLoader?: DocumentLoader,
|
|
1667
1902
|
tracerProvider?: TracerProvider,
|
|
1903
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
1668
1904
|
baseUrl?: URL
|
|
1669
1905
|
}
|
|
1670
1906
|
): Promise<${getTypeNames(property.range, types)}> {
|
|
@@ -1675,6 +1911,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1675
1911
|
const tracerProvider = options.tracerProvider ??
|
|
1676
1912
|
this._tracerProvider ?? trace.getTracerProvider();
|
|
1677
1913
|
const baseUrl = options.baseUrl;
|
|
1914
|
+
const verifyPortableObject = options.verifyPortableObject;
|
|
1678
1915
|
`;
|
|
1679
1916
|
if (property.preprocessors != null && property.preprocessors.length > 0) {
|
|
1680
1917
|
yield `
|
|
@@ -1694,6 +1931,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1694
1931
|
documentLoader,
|
|
1695
1932
|
contextLoader,
|
|
1696
1933
|
tracerProvider,
|
|
1934
|
+
verifyPortableObject,
|
|
1697
1935
|
baseUrl,
|
|
1698
1936
|
});
|
|
1699
1937
|
if (_result instanceof Error) throw _result;
|
|
@@ -1712,7 +1950,13 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1712
1950
|
try {
|
|
1713
1951
|
return await ${types[range].name}.fromJsonLd(
|
|
1714
1952
|
jsonLd,
|
|
1715
|
-
{
|
|
1953
|
+
{
|
|
1954
|
+
documentLoader,
|
|
1955
|
+
contextLoader,
|
|
1956
|
+
tracerProvider,
|
|
1957
|
+
verifyPortableObject,
|
|
1958
|
+
baseUrl,
|
|
1959
|
+
},
|
|
1716
1960
|
);
|
|
1717
1961
|
} catch (e) {
|
|
1718
1962
|
if (!(e instanceof TypeError)) throw e;
|
|
@@ -1754,6 +1998,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1754
1998
|
suppressError?: boolean,
|
|
1755
1999
|
tracerProvider?: TracerProvider,
|
|
1756
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;
|
|
1757
2016
|
} = {}
|
|
1758
2017
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
1759
2018
|
if (this._warning != null) {
|
|
@@ -1763,25 +2022,46 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1763
2022
|
);
|
|
1764
2023
|
}
|
|
1765
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
|
+
}
|
|
1766
2034
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
1767
2035
|
if (!(v instanceof URL) &&
|
|
1768
|
-
v.id != null &&
|
|
1769
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1770
2036
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1771
|
-
|
|
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
|
+
}
|
|
1772
2049
|
}
|
|
1773
2050
|
if (v instanceof URL) {
|
|
1774
2051
|
const fetched =
|
|
1775
2052
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1776
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;
|
|
1777
2057
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
1778
2058
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
1779
2059
|
this._cachedJsonLd = undefined;
|
|
2060
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1780
2061
|
return fetched;
|
|
1781
2062
|
}
|
|
1782
2063
|
`;
|
|
1783
|
-
if (property.compactName != null)
|
|
1784
|
-
yield `
|
|
2064
|
+
if (property.compactName != null) yield `
|
|
1785
2065
|
if (
|
|
1786
2066
|
this._cachedJsonLd != null &&
|
|
1787
2067
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1792,26 +2072,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1792
2072
|
${JSON.stringify(property.compactName)}];
|
|
1793
2073
|
const doc = Array.isArray(prop) ? prop[0] : prop;
|
|
1794
2074
|
if (doc != null && typeof doc === "object" && "@context" in doc) {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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
|
+
}
|
|
1803
2090
|
}
|
|
1804
2091
|
}
|
|
1805
2092
|
`;
|
|
1806
|
-
}
|
|
1807
2093
|
yield `
|
|
1808
2094
|
if (v?.id != null &&
|
|
1809
|
-
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
|
+
)) &&
|
|
1810
2100
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1811
2101
|
if (options.crossOrigin === "throw") {
|
|
1812
2102
|
throw new Error(
|
|
1813
2103
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1814
|
-
"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)"} + "); " +
|
|
1815
2105
|
"refusing to return the object. If you want to bypass this " +
|
|
1816
2106
|
"check and are aware of the security implications, set the " +
|
|
1817
2107
|
'crossOrigin option to "trust".'
|
|
@@ -1823,10 +2113,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1823
2113
|
"return the object. If you want to bypass this check and are " +
|
|
1824
2114
|
"aware of the security implications, set the crossOrigin option " +
|
|
1825
2115
|
'to "trust".',
|
|
1826
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2116
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1827
2117
|
);
|
|
1828
2118
|
return null;
|
|
1829
2119
|
}
|
|
2120
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1830
2121
|
return v;
|
|
1831
2122
|
}
|
|
1832
2123
|
`;
|
|
@@ -1859,6 +2150,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1859
2150
|
suppressError?: boolean,
|
|
1860
2151
|
tracerProvider?: TracerProvider,
|
|
1861
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;
|
|
1862
2168
|
} = {}
|
|
1863
2169
|
): AsyncIterable<${getTypeNames(property.range, types)}> {
|
|
1864
2170
|
if (this._warning != null) {
|
|
@@ -1867,28 +2173,52 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1867
2173
|
this._warning.values
|
|
1868
2174
|
);
|
|
1869
2175
|
}
|
|
2176
|
+
if (
|
|
2177
|
+
options.verifyPortableObject == null &&
|
|
2178
|
+
this._verifyPortableObject != null
|
|
2179
|
+
) {
|
|
2180
|
+
options = {
|
|
2181
|
+
...options,
|
|
2182
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
1870
2185
|
const vs = this.${await getFieldName(property.uri)};
|
|
1871
2186
|
for (let i = 0; i < vs.length; i++) {
|
|
1872
2187
|
let v = vs[i];
|
|
1873
2188
|
if (!(v instanceof URL) &&
|
|
1874
|
-
v.id != null &&
|
|
1875
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1876
2189
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1877
|
-
|
|
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
|
+
}
|
|
1878
2202
|
}
|
|
1879
2203
|
if (v instanceof URL) {
|
|
1880
2204
|
const fetched =
|
|
1881
2205
|
await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
|
|
1882
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
|
+
}
|
|
1883
2213
|
vs[i] = fetched;
|
|
1884
2214
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
1885
2215
|
this._cachedJsonLd = undefined;
|
|
2216
|
+
recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
|
|
1886
2217
|
yield fetched;
|
|
1887
2218
|
continue;
|
|
1888
2219
|
}
|
|
1889
2220
|
`;
|
|
1890
|
-
if (property.compactName != null)
|
|
1891
|
-
yield `
|
|
2221
|
+
if (property.compactName != null) yield `
|
|
1892
2222
|
if (
|
|
1893
2223
|
this._cachedJsonLd != null &&
|
|
1894
2224
|
typeof this._cachedJsonLd === "object" &&
|
|
@@ -1899,26 +2229,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1899
2229
|
${JSON.stringify(property.compactName)}];
|
|
1900
2230
|
const obj = Array.isArray(prop) ? prop[i] : prop;
|
|
1901
2231
|
if (obj != null && typeof obj === "object" && "@context" in obj) {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
+
}
|
|
1910
2247
|
}
|
|
1911
2248
|
}
|
|
1912
2249
|
`;
|
|
1913
|
-
}
|
|
1914
2250
|
yield `
|
|
1915
2251
|
if (v?.id != null &&
|
|
1916
|
-
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
|
+
)) &&
|
|
1917
2257
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1918
2258
|
if (options.crossOrigin === "throw") {
|
|
1919
2259
|
throw new Error(
|
|
1920
2260
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
1921
|
-
"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)"} + "); " +
|
|
1922
2262
|
"refusing to return the object. If you want to bypass this " +
|
|
1923
2263
|
"check and are aware of the security implications, set the " +
|
|
1924
2264
|
'crossOrigin option to "trust".'
|
|
@@ -1930,10 +2270,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1930
2270
|
"return the object. If you want to bypass this check and are " +
|
|
1931
2271
|
"aware of the security implications, set the crossOrigin " +
|
|
1932
2272
|
'option to "trust".',
|
|
1933
|
-
{ objectId: v.id.href, parentObjectId: this.id
|
|
2273
|
+
{ objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
|
|
1934
2274
|
);
|
|
1935
2275
|
continue;
|
|
1936
2276
|
}
|
|
2277
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
1937
2278
|
yield v;
|
|
1938
2279
|
}
|
|
1939
2280
|
}
|
|
@@ -1951,10 +2292,35 @@ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
|
|
|
1951
2292
|
const FEDIFY_URL = "fedify:url";
|
|
1952
2293
|
const INTERNAL_RUNTIME_IMPORTS = [
|
|
1953
2294
|
"compactJsonLdCache",
|
|
2295
|
+
"createScopedContextLoader",
|
|
1954
2296
|
"getJsonLdContext",
|
|
2297
|
+
"getPortableActorGateways",
|
|
1955
2298
|
"isTrustedIriOrigin",
|
|
1956
2299
|
"normalizeJsonLdIris"
|
|
1957
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 ");
|
|
1958
2324
|
const RUNTIME_IMPORTS = [
|
|
1959
2325
|
"canParseDecimal",
|
|
1960
2326
|
"decodeMultibase",
|
|
@@ -1974,6 +2340,7 @@ const RUNTIME_IMPORTS = [
|
|
|
1974
2340
|
"parseGatewayUrl",
|
|
1975
2341
|
"parseIri",
|
|
1976
2342
|
"parseJsonLdId",
|
|
2343
|
+
"type PortableObjectVerifier",
|
|
1977
2344
|
"type RemoteDocument"
|
|
1978
2345
|
].join(",\n ");
|
|
1979
2346
|
/**
|
|
@@ -2009,6 +2376,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2009
2376
|
readonly #documentLoader?: DocumentLoader;
|
|
2010
2377
|
readonly #contextLoader?: DocumentLoader;
|
|
2011
2378
|
readonly #tracerProvider?: TracerProvider;
|
|
2379
|
+
readonly #verifyPortableObject?: PortableObjectVerifier;
|
|
2012
2380
|
readonly #warning?: {
|
|
2013
2381
|
category: string[];
|
|
2014
2382
|
message: string;
|
|
@@ -2016,6 +2384,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2016
2384
|
};
|
|
2017
2385
|
#cachedJsonLd?: unknown;
|
|
2018
2386
|
readonly #_baseUrl?: URL;
|
|
2387
|
+
#shouldCacheJsonLd = true;
|
|
2019
2388
|
readonly id: URL | null;
|
|
2020
2389
|
|
|
2021
2390
|
protected get _documentLoader(): DocumentLoader | undefined {
|
|
@@ -2030,6 +2399,10 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2030
2399
|
return this.#tracerProvider;
|
|
2031
2400
|
}
|
|
2032
2401
|
|
|
2402
|
+
protected get _verifyPortableObject(): PortableObjectVerifier | undefined {
|
|
2403
|
+
return this.#verifyPortableObject;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2033
2406
|
protected get _warning(): {
|
|
2034
2407
|
category: string[];
|
|
2035
2408
|
message: string;
|
|
@@ -2049,6 +2422,20 @@ async function* generateClass(typeUri, types, moduleVarNames) {
|
|
|
2049
2422
|
protected get _baseUrl(): URL | undefined {
|
|
2050
2423
|
return this.#_baseUrl;
|
|
2051
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
|
+
}
|
|
2052
2439
|
`;
|
|
2053
2440
|
yield `
|
|
2054
2441
|
/**
|
|
@@ -2135,11 +2522,69 @@ async function* generateClasses(types) {
|
|
|
2135
2522
|
from "@opentelemetry/api";\n`;
|
|
2136
2523
|
yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
|
|
2137
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`;
|
|
2138
2527
|
yield `import {
|
|
2139
2528
|
isTemporalDuration,
|
|
2140
2529
|
isTemporalInstant,
|
|
2141
2530
|
} from "@fedify/vocab-runtime/temporal";\n`;
|
|
2142
|
-
|
|
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"]);
|
|
2143
2588
|
for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
|
|
2144
2589
|
yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
|
|
2145
2590
|
yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;
|