@fedify/vocab-tools 2.4.0-dev.2063 → 2.4.0-dev.2065
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/deno.json +1 -1
- package/dist/mod.cjs +132 -66
- package/dist/mod.js +132 -66
- package/package.json +1 -1
- package/src/__snapshots__/class.test.ts.deno.snap +16928 -8905
- package/src/__snapshots__/class.test.ts.node.snap +16928 -8905
- package/src/__snapshots__/class.test.ts.snap +16928 -8905
- package/src/class.ts +9 -3
- package/src/property.ts +122 -62
package/src/class.ts
CHANGED
|
@@ -20,13 +20,19 @@ const INTERNAL_RUNTIME_IMPORTS = [
|
|
|
20
20
|
].join(",\n ");
|
|
21
21
|
const PORTABLE_DEREFERENCE_IMPORTS = [
|
|
22
22
|
"copyPortableProvenance",
|
|
23
|
+
"createSnapshotContextLoader",
|
|
23
24
|
"dereferencePortableIri",
|
|
24
|
-
"
|
|
25
|
-
"isInPortableChain",
|
|
25
|
+
"getPortableResponseClaim",
|
|
26
26
|
"isPortableIri",
|
|
27
|
+
"isPortableMode",
|
|
27
28
|
"isUnsecuredPortableObject",
|
|
29
|
+
"isUnverifiedPortableClaim",
|
|
30
|
+
"markUnverifiedPortableClaim",
|
|
31
|
+
"mustDereferencePortableObject",
|
|
32
|
+
"parseCompatibleEf61Reference",
|
|
33
|
+
"type PortableResponseClaim",
|
|
28
34
|
"recordPortableReferrer",
|
|
29
|
-
"
|
|
35
|
+
"rejectMalformedCompatibleReference",
|
|
30
36
|
"warnUnverifiableEmbeddedObject",
|
|
31
37
|
].join(",\n ");
|
|
32
38
|
const SIGNED_REPRESENTATION_IMPORTS = [
|
package/src/property.ts
CHANGED
|
@@ -89,13 +89,22 @@ async function* generateProperty(
|
|
|
89
89
|
${JSON.stringify(metadata.version)},
|
|
90
90
|
);
|
|
91
91
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
92
|
-
|
|
92
|
+
const dereferencePortable = async (
|
|
93
|
+
portableUrl: URL,
|
|
94
|
+
extra: {
|
|
95
|
+
inferredGateways?: readonly URL[];
|
|
96
|
+
response?: RemoteDocument;
|
|
97
|
+
contextLoader?: DocumentLoader;
|
|
98
|
+
} = {},
|
|
99
|
+
) => {
|
|
93
100
|
try {
|
|
94
|
-
const obj = await dereferencePortableIri(
|
|
101
|
+
const obj = await dereferencePortableIri(portableUrl, {
|
|
95
102
|
documentLoader,
|
|
96
|
-
contextLoader,
|
|
103
|
+
contextLoader: extra.contextLoader ?? contextLoader,
|
|
97
104
|
tracerProvider,
|
|
98
105
|
gateways: options.gateways,
|
|
106
|
+
inferredGateways: extra.inferredGateways,
|
|
107
|
+
response: extra.response,
|
|
99
108
|
verifyPortableObject: options.verifyPortableObject,
|
|
100
109
|
referrer: {
|
|
101
110
|
object: this,
|
|
@@ -113,7 +122,10 @@ async function* generateProperty(
|
|
|
113
122
|
}),
|
|
114
123
|
});
|
|
115
124
|
if (obj != null) {
|
|
116
|
-
span.setAttribute(
|
|
125
|
+
span.setAttribute(
|
|
126
|
+
"activitypub.object.id",
|
|
127
|
+
(obj.id ?? portableUrl).href,
|
|
128
|
+
);
|
|
117
129
|
span.setAttribute(
|
|
118
130
|
"activitypub.object.type",
|
|
119
131
|
// @ts-ignore: obj.constructor always has a typeId.
|
|
@@ -130,12 +142,20 @@ async function* generateProperty(
|
|
|
130
142
|
} finally {
|
|
131
143
|
span.end();
|
|
132
144
|
}
|
|
133
|
-
}
|
|
145
|
+
};
|
|
146
|
+
if (isPortableIri(url)) return await dereferencePortable(url);
|
|
134
147
|
const lookupUrl = formatIri(url);
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
const portableMode = isPortableMode(this, options);
|
|
149
|
+
if (portableMode) {
|
|
150
|
+
const compatible = parseCompatibleEf61Reference(url);
|
|
151
|
+
if (compatible === null) {
|
|
152
|
+
span.end();
|
|
153
|
+
return rejectMalformedCompatibleReference(lookupUrl, options);
|
|
154
|
+
} else if (compatible != null) {
|
|
155
|
+
return await dereferencePortable(compatible.id, {
|
|
156
|
+
inferredGateways: [compatible.gateway],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
139
159
|
}
|
|
140
160
|
let fetchResult: RemoteDocument;
|
|
141
161
|
try {
|
|
@@ -156,63 +176,90 @@ async function* generateProperty(
|
|
|
156
176
|
throw error;
|
|
157
177
|
}
|
|
158
178
|
const { document, documentUrl } = fetchResult;
|
|
159
|
-
|
|
179
|
+
// In portable mode, the document may turn out to be a portable object,
|
|
180
|
+
// which has to be verified with the same context documents:
|
|
181
|
+
const snapshot = portableMode
|
|
182
|
+
? createSnapshotContextLoader(contextLoader)
|
|
183
|
+
: null;
|
|
160
184
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
documentUrl,
|
|
173
|
-
options.crossOrigin,
|
|
185
|
+
let claim: PortableResponseClaim | null | undefined;
|
|
186
|
+
try {
|
|
187
|
+
const baseUrl = parseIri(documentUrl);
|
|
188
|
+
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
189
|
+
document,
|
|
190
|
+
{
|
|
191
|
+
documentLoader,
|
|
192
|
+
contextLoader: snapshot?.loader ?? contextLoader,
|
|
193
|
+
tracerProvider,
|
|
194
|
+
baseUrl,
|
|
195
|
+
}
|
|
174
196
|
);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
if (snapshot == null) {
|
|
198
|
+
markUnverifiedPortableClaim(obj, url, documentUrl);
|
|
199
|
+
} else {
|
|
200
|
+
claim = getPortableResponseClaim(documentUrl, obj.id);
|
|
201
|
+
}
|
|
202
|
+
if (claim === undefined) {
|
|
203
|
+
if (
|
|
204
|
+
obj?.id != null &&
|
|
205
|
+
!isTrustedIriOrigin(options, obj.id, baseUrl)
|
|
206
|
+
) {
|
|
207
|
+
if (options.crossOrigin === "throw") {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
210
|
+
"than the document URL (" + baseUrl.href + "); refusing to return " +
|
|
211
|
+
"the object. If you want to bypass this check and are aware of" +
|
|
212
|
+
'the security implications, set the crossOrigin option to "trust".'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
216
|
+
"The object's @id ({objectId}) has a different origin than the document " +
|
|
217
|
+
"URL ({documentUrl}); refusing to return the object. If you want to " +
|
|
218
|
+
"bypass this check and are aware of the security implications, " +
|
|
219
|
+
'set the crossOrigin option to "trust".',
|
|
220
|
+
{ ...fetchResult, objectId: obj.id.href },
|
|
221
|
+
);
|
|
222
|
+
span.end();
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
|
|
226
|
+
span.setAttribute(
|
|
227
|
+
"activitypub.object.type",
|
|
228
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
229
|
+
obj.constructor.typeId.href
|
|
183
230
|
);
|
|
231
|
+
span.end();
|
|
232
|
+
return obj;
|
|
184
233
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
234
|
+
} catch (e) {
|
|
235
|
+
if (options.suppressError) {
|
|
236
|
+
getLogger(["fedify", "vocab"]).error(
|
|
237
|
+
"Failed to parse {url}: {error}",
|
|
238
|
+
{ error: e, url: lookupUrl }
|
|
239
|
+
);
|
|
240
|
+
span.end();
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
span.setStatus({
|
|
244
|
+
code: SpanStatusCode.ERROR,
|
|
245
|
+
message: String(e),
|
|
246
|
+
});
|
|
247
|
+
span.end();
|
|
248
|
+
throw e;
|
|
193
249
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
return obj;
|
|
201
|
-
} catch (e) {
|
|
202
|
-
if (options.suppressError) {
|
|
203
|
-
getLogger(["fedify", "vocab"]).error(
|
|
204
|
-
"Failed to parse {url}: {error}",
|
|
205
|
-
{ error: e, url: lookupUrl }
|
|
206
|
-
);
|
|
207
|
-
return null;
|
|
250
|
+
// The document stands for a portable object, so it is verified as
|
|
251
|
+
// one instead of being trusted because of where it came from:
|
|
252
|
+
if (claim === null) {
|
|
253
|
+
span.end();
|
|
254
|
+
return rejectMalformedCompatibleReference(documentUrl, options);
|
|
208
255
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
256
|
+
return await dereferencePortable(claim.id, {
|
|
257
|
+
inferredGateways: claim.inferredGateways,
|
|
258
|
+
response: fetchResult,
|
|
259
|
+
contextLoader: snapshot?.loader,
|
|
212
260
|
});
|
|
213
|
-
throw e;
|
|
214
261
|
} finally {
|
|
215
|
-
|
|
262
|
+
snapshot?.release();
|
|
216
263
|
}
|
|
217
264
|
});
|
|
218
265
|
}
|
|
@@ -350,7 +397,7 @@ async function* generateProperty(
|
|
|
350
397
|
} else if (
|
|
351
398
|
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
352
399
|
isUnsecuredPortableObject(this) ||
|
|
353
|
-
(
|
|
400
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
354
401
|
) {
|
|
355
402
|
v = v.id;
|
|
356
403
|
}
|
|
@@ -359,6 +406,9 @@ async function* generateProperty(
|
|
|
359
406
|
const fetched =
|
|
360
407
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
361
408
|
if (fetched == null) return null;
|
|
409
|
+
// Not cached, so that it is verified when the portable object
|
|
410
|
+
// policy applies later:
|
|
411
|
+
if (isUnverifiedPortableClaim(fetched)) return fetched;
|
|
362
412
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
363
413
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
364
414
|
this._cachedJsonLd = undefined;
|
|
@@ -398,7 +448,9 @@ async function* generateProperty(
|
|
|
398
448
|
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
399
449
|
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
400
450
|
isUnsecuredPortableObject(this) ||
|
|
401
|
-
(
|
|
451
|
+
mustDereferencePortableObject(
|
|
452
|
+
this, ${trustOwner}, v.id, options,
|
|
453
|
+
)) &&
|
|
402
454
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
403
455
|
if (options.crossOrigin === "throw") {
|
|
404
456
|
throw new Error(
|
|
@@ -485,7 +537,7 @@ async function* generateProperty(
|
|
|
485
537
|
} else if (
|
|
486
538
|
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
487
539
|
isUnsecuredPortableObject(this) ||
|
|
488
|
-
(
|
|
540
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
489
541
|
) {
|
|
490
542
|
v = v.id;
|
|
491
543
|
}
|
|
@@ -494,6 +546,12 @@ async function* generateProperty(
|
|
|
494
546
|
const fetched =
|
|
495
547
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
496
548
|
if (fetched == null) continue;
|
|
549
|
+
// Not cached, so that it is verified when the portable object
|
|
550
|
+
// policy applies later:
|
|
551
|
+
if (isUnverifiedPortableClaim(fetched)) {
|
|
552
|
+
yield fetched;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
497
555
|
vs[i] = fetched;
|
|
498
556
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
499
557
|
this._cachedJsonLd = undefined;
|
|
@@ -534,7 +592,9 @@ async function* generateProperty(
|
|
|
534
592
|
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
535
593
|
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
536
594
|
isUnsecuredPortableObject(this) ||
|
|
537
|
-
(
|
|
595
|
+
mustDereferencePortableObject(
|
|
596
|
+
this, ${trustOwner}, v.id, options,
|
|
597
|
+
)) &&
|
|
538
598
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
539
599
|
if (options.crossOrigin === "throw") {
|
|
540
600
|
throw new Error(
|