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