@fedify/vocab-runtime 2.4.0-dev.2056 → 2.4.0-dev.2063

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2056+e6394f3b",
3
+ "version": "2.4.0-dev.2063+366c0362",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
@@ -25,6 +25,156 @@ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
25
25
  "https://w3id.org/security/data-integrity/v1"
26
26
  ]);
27
27
  /**
28
+ * The maximum number of links in a referrer chain passed to a
29
+ * {@link PortableObjectVerifier}. Longer chains are cut off and marked as
30
+ * truncated.
31
+ */
32
+ const MAX_REFERRER_CHAIN_LENGTH = 32;
33
+ const provenances = /* @__PURE__ */ new WeakMap();
34
+ function getObjectId(object) {
35
+ if (object == null || typeof object !== "object" || !("id" in object)) return null;
36
+ const id = object.id;
37
+ return id instanceof URL ? id : null;
38
+ }
39
+ /**
40
+ * Checks whether a vocabulary object is part of a chain of portable objects,
41
+ * i.e., whether it has a portable ID or was obtained from a portable object.
42
+ *
43
+ * @internal Technically exported for generated vocabulary classes, but not
44
+ * part of the public API contract. This is not considered public API for
45
+ * Semantic Versioning decisions.
46
+ */
47
+ function isInPortableChain(object) {
48
+ if (provenances.has(object)) return true;
49
+ const id = getObjectId(object);
50
+ return id != null && isPortableIri(id);
51
+ }
52
+ /**
53
+ * Checks whether a vocabulary object was accepted without an integrity
54
+ * proof, e.g., as an unsecured portable collection served by a trusted
55
+ * gateway. Objects embedded in such an object must not be trusted because
56
+ * of their origin.
57
+ *
58
+ * @internal Technically exported for generated vocabulary classes, but not
59
+ * part of the public API contract. This is not considered public API for
60
+ * Semantic Versioning decisions.
61
+ */
62
+ function isUnsecuredPortableObject(object) {
63
+ return provenances.get(object)?.acceptance === "unsecured";
64
+ }
65
+ /**
66
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
67
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
68
+ * a portable object. Malformed compatible identifiers count as compatible
69
+ * identifiers too.
70
+ *
71
+ * @internal Technically exported for generated vocabulary classes, but not
72
+ * part of the public API contract. This is not considered public API for
73
+ * Semantic Versioning decisions.
74
+ */
75
+ function isCompatibleEf61Iri(url) {
76
+ try {
77
+ return require_url.fromCompatibleEf61Id(url) != null;
78
+ } catch (error) {
79
+ if (error instanceof TypeError) return true;
80
+ throw error;
81
+ }
82
+ }
83
+ /**
84
+ * Records that a vocabulary object returned by a property accessor was
85
+ * obtained through the given property of a portable object, so that later
86
+ * dereferences from it can tell where it came from. Nothing is recorded if
87
+ * the parent is not in a portable chain, or if the child already has its
88
+ * provenance.
89
+ *
90
+ * @internal Technically exported for generated vocabulary classes, but not
91
+ * part of the public API contract. This is not considered public API for
92
+ * Semantic Versioning decisions.
93
+ */
94
+ function recordPortableReferrer(parent, child, property) {
95
+ if (child == null || typeof child !== "object") return;
96
+ if (provenances.has(child) || !isInPortableChain(parent)) return;
97
+ provenances.set(child, { referrer: {
98
+ object: parent,
99
+ property
100
+ } });
101
+ }
102
+ /**
103
+ * Copies the provenance of a vocabulary object to its clone. The clone
104
+ * keeps where the original came from and the restriction on unsecured
105
+ * objects, but not a positive verification status nor a collection context,
106
+ * since the clone may have different property values.
107
+ *
108
+ * @internal Technically exported for generated vocabulary classes, but not
109
+ * part of the public API contract. This is not considered public API for
110
+ * Semantic Versioning decisions.
111
+ */
112
+ function copyPortableProvenance(from, to) {
113
+ const provenance = provenances.get(from);
114
+ if (provenance == null) return;
115
+ provenances.set(to, {
116
+ referrer: provenance.referrer,
117
+ ...provenance.acceptance === "unsecured" ? { acceptance: "unsecured" } : {}
118
+ });
119
+ }
120
+ /**
121
+ * Rejects a reference from a portable object that would be dereferenced as
122
+ * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
123
+ * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
124
+ * is a compatible identifier or a portable IRI. Such a document would skip
125
+ * the portable object policy, so it is rejected regardless of the
126
+ * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
127
+ *
128
+ * @param url The rejected URL, for the log message.
129
+ * @param crossOrigin The `crossOrigin` option of the accessor.
130
+ * @returns Always `null`.
131
+ * @throws {Error} If `crossOrigin` is `"throw"`.
132
+ * @internal Technically exported for generated vocabulary classes, but not
133
+ * part of the public API contract. This is not considered public API for
134
+ * Semantic Versioning decisions.
135
+ */
136
+ function rejectPortableChainReference(url, crossOrigin) {
137
+ const message = "Refusing to dereference {url} from a portable object as an ordinary HTTP(S) object, because it stands for an FEP-ef61 portable object that is not verified this way.";
138
+ if (crossOrigin === "throw") throw new Error(message.replace("{url}", url));
139
+ logger.warn(message, { url });
140
+ return null;
141
+ }
142
+ /**
143
+ * Logs that an embedded object without `@id` in an object accepted without
144
+ * an integrity proof is dropped, because it cannot be dereferenced and
145
+ * verified on its own.
146
+ *
147
+ * @internal Technically exported for generated vocabulary classes, but not
148
+ * part of the public API contract. This is not considered public API for
149
+ * Semantic Versioning decisions.
150
+ */
151
+ function warnUnverifiableEmbeddedObject(parent, property) {
152
+ logger.warn("Dropping an embedded object without @id in the {property} property of {parentId}, because the parent was accepted without an integrity proof and the embedded object cannot be verified on its own.", {
153
+ property,
154
+ parentId: getObjectId(parent)?.href ?? null
155
+ });
156
+ }
157
+ function buildReferrerChain(link, depth = 0) {
158
+ if (link == null) return void 0;
159
+ const provenance = provenances.get(link.object);
160
+ const base = {
161
+ object: link.object,
162
+ id: getObjectId(link.object),
163
+ property: link.property,
164
+ ...provenance?.acceptance == null ? {} : { acceptance: provenance.acceptance },
165
+ ...provenance?.collectionContext === void 0 ? {} : { collectionContext: provenance.collectionContext }
166
+ };
167
+ if (provenance?.referrer == null) return base;
168
+ if (depth + 1 >= MAX_REFERRER_CHAIN_LENGTH) return {
169
+ ...base,
170
+ truncated: true
171
+ };
172
+ return {
173
+ ...base,
174
+ referrer: buildReferrerChain(provenance.referrer, depth + 1)
175
+ };
176
+ }
177
+ /**
28
178
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
29
179
  *
30
180
  * @internal Technically exported for generated vocabulary classes, but not
@@ -205,6 +355,9 @@ async function dereferencePortableIri(url, options) {
205
355
  const contextLoader = snapshot.loader;
206
356
  const attempts = [];
207
357
  const { signal } = options;
358
+ const explicitGateways = options.gateways == null ? void 0 : gateways;
359
+ const gatewayHints = options.gateways == null && gateways.length > 0 ? gateways : void 0;
360
+ const referrer = buildReferrerChain(options.referrer);
208
361
  try {
209
362
  for (const { url: requestUrl, gateway } of requestUrls) {
210
363
  signal?.throwIfAborted();
@@ -227,12 +380,22 @@ async function dereferencePortableIri(url, options) {
227
380
  const { document } = remoteDocument;
228
381
  try {
229
382
  await checkPortableObjectId(document, expectedId, contextLoader);
383
+ let documentUrl;
384
+ try {
385
+ documentUrl = require_url.parseIri(remoteDocument.documentUrl);
386
+ } catch {
387
+ documentUrl = void 0;
388
+ }
230
389
  let result;
231
390
  try {
232
391
  result = await verify(document, {
233
392
  documentLoader: options.documentLoader,
234
393
  contextLoader,
235
- tracerProvider: options.tracerProvider
394
+ tracerProvider: options.tracerProvider,
395
+ ...documentUrl == null ? {} : { documentUrl },
396
+ ...explicitGateways == null ? {} : { gateways: explicitGateways },
397
+ ...gatewayHints == null ? {} : { gatewayHints },
398
+ ...referrer == null ? {} : { referrer }
236
399
  });
237
400
  } catch (error) {
238
401
  throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
@@ -243,6 +406,11 @@ async function dereferencePortableIri(url, options) {
243
406
  baseUrl: url
244
407
  });
245
408
  signal?.throwIfAborted();
409
+ if (object != null && typeof object === "object") provenances.set(object, {
410
+ acceptance: result.unsecured === true ? "unsecured" : "verified",
411
+ ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
412
+ ...options.referrer == null ? {} : { referrer: options.referrer }
413
+ });
246
414
  if (gateway != null) span?.setAttribute("activitypub.gateway", gateway.href);
247
415
  return object;
248
416
  } catch (error) {
@@ -310,6 +478,13 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
310
478
  }
311
479
  //#endregion
312
480
  exports.PortableObjectRejectedError = PortableObjectRejectedError;
481
+ exports.copyPortableProvenance = copyPortableProvenance;
313
482
  exports.dereferencePortableIri = dereferencePortableIri;
314
483
  exports.getPortableGatewayCandidates = getPortableGatewayCandidates;
484
+ exports.isCompatibleEf61Iri = isCompatibleEf61Iri;
485
+ exports.isInPortableChain = isInPortableChain;
315
486
  exports.isPortableIri = isPortableIri;
487
+ exports.isUnsecuredPortableObject = isUnsecuredPortableObject;
488
+ exports.recordPortableReferrer = recordPortableReferrer;
489
+ exports.rejectPortableChainReference = rejectPortableChainReference;
490
+ exports.warnUnverifiableEmbeddedObject = warnUnverifiableEmbeddedObject;
@@ -1,10 +1,101 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { n as DocumentLoader } from "../docloader-Dr8wEkif.cjs";
3
- import { t as PortableObjectVerifier } from "../portable-DxF2Ut-Z.cjs";
3
+ import { r as PortableObjectVerifier } from "../portable-BykOD45s.cjs";
4
4
  import { Span, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/internal/portable-dereference.d.ts
7
7
  /**
8
+ * A reference from a vocabulary object's property, which generated
9
+ * accessors pass to {@link dereferencePortableIri}.
10
+ *
11
+ * @internal
12
+ */
13
+ interface PortableReferrerLink {
14
+ readonly object: object;
15
+ readonly property: string;
16
+ }
17
+ /**
18
+ * Checks whether a vocabulary object is part of a chain of portable objects,
19
+ * i.e., whether it has a portable ID or was obtained from a portable object.
20
+ *
21
+ * @internal Technically exported for generated vocabulary classes, but not
22
+ * part of the public API contract. This is not considered public API for
23
+ * Semantic Versioning decisions.
24
+ */
25
+ declare function isInPortableChain(object: object): boolean;
26
+ /**
27
+ * Checks whether a vocabulary object was accepted without an integrity
28
+ * proof, e.g., as an unsecured portable collection served by a trusted
29
+ * gateway. Objects embedded in such an object must not be trusted because
30
+ * of their origin.
31
+ *
32
+ * @internal Technically exported for generated vocabulary classes, but not
33
+ * part of the public API contract. This is not considered public API for
34
+ * Semantic Versioning decisions.
35
+ */
36
+ declare function isUnsecuredPortableObject(object: object): boolean;
37
+ /**
38
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
39
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
40
+ * a portable object. Malformed compatible identifiers count as compatible
41
+ * identifiers too.
42
+ *
43
+ * @internal Technically exported for generated vocabulary classes, but not
44
+ * part of the public API contract. This is not considered public API for
45
+ * Semantic Versioning decisions.
46
+ */
47
+ declare function isCompatibleEf61Iri(url: URL | string): boolean;
48
+ /**
49
+ * Records that a vocabulary object returned by a property accessor was
50
+ * obtained through the given property of a portable object, so that later
51
+ * dereferences from it can tell where it came from. Nothing is recorded if
52
+ * the parent is not in a portable chain, or if the child already has its
53
+ * provenance.
54
+ *
55
+ * @internal Technically exported for generated vocabulary classes, but not
56
+ * part of the public API contract. This is not considered public API for
57
+ * Semantic Versioning decisions.
58
+ */
59
+ declare function recordPortableReferrer(parent: object, child: unknown, property: string): void;
60
+ /**
61
+ * Copies the provenance of a vocabulary object to its clone. The clone
62
+ * keeps where the original came from and the restriction on unsecured
63
+ * objects, but not a positive verification status nor a collection context,
64
+ * since the clone may have different property values.
65
+ *
66
+ * @internal Technically exported for generated vocabulary classes, but not
67
+ * part of the public API contract. This is not considered public API for
68
+ * Semantic Versioning decisions.
69
+ */
70
+ declare function copyPortableProvenance(from: object, to: object): void;
71
+ /**
72
+ * Rejects a reference from a portable object that would be dereferenced as
73
+ * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
74
+ * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
75
+ * is a compatible identifier or a portable IRI. Such a document would skip
76
+ * the portable object policy, so it is rejected regardless of the
77
+ * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
78
+ *
79
+ * @param url The rejected URL, for the log message.
80
+ * @param crossOrigin The `crossOrigin` option of the accessor.
81
+ * @returns Always `null`.
82
+ * @throws {Error} If `crossOrigin` is `"throw"`.
83
+ * @internal Technically exported for generated vocabulary classes, but not
84
+ * part of the public API contract. This is not considered public API for
85
+ * Semantic Versioning decisions.
86
+ */
87
+ declare function rejectPortableChainReference(url: string, crossOrigin: "ignore" | "throw" | "trust" | undefined): null;
88
+ /**
89
+ * Logs that an embedded object without `@id` in an object accepted without
90
+ * an integrity proof is dropped, because it cannot be dereferenced and
91
+ * verified on its own.
92
+ *
93
+ * @internal Technically exported for generated vocabulary classes, but not
94
+ * part of the public API contract. This is not considered public API for
95
+ * Semantic Versioning decisions.
96
+ */
97
+ declare function warnUnverifiableEmbeddedObject(parent: object, property: string): void;
98
+ /**
8
99
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
9
100
  *
10
101
  * @internal Technically exported for generated vocabulary classes, but not
@@ -37,6 +128,7 @@ interface DereferencePortableIriOptions<T> {
37
128
  tracerProvider: TracerProvider;
38
129
  gateways?: readonly (string | URL)[];
39
130
  verifyPortableObject?: PortableObjectVerifier;
131
+ referrer?: PortableReferrerLink;
40
132
  suppressError?: boolean;
41
133
  crossOrigin?: "ignore" | "throw" | "trust";
42
134
  /**
@@ -84,4 +176,4 @@ declare function dereferencePortableIri<T extends {
84
176
  id: URL | null;
85
177
  }>(url: URL, options: DereferencePortableIriOptions<T>): Promise<T | null>;
86
178
  //#endregion
87
- export { DereferencePortableIriOptions, PortableObjectRejectedError, dereferencePortableIri, getPortableGatewayCandidates, isPortableIri };
179
+ export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, copyPortableProvenance, dereferencePortableIri, getPortableGatewayCandidates, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isUnsecuredPortableObject, recordPortableReferrer, rejectPortableChainReference, warnUnverifiableEmbeddedObject };
@@ -1,10 +1,101 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { n as DocumentLoader } from "../docloader-Dr8wEkif.js";
3
- import { t as PortableObjectVerifier } from "../portable-CduElToy.js";
3
+ import { r as PortableObjectVerifier } from "../portable--NcyA1R_.js";
4
4
  import { Span, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/internal/portable-dereference.d.ts
7
7
  /**
8
+ * A reference from a vocabulary object's property, which generated
9
+ * accessors pass to {@link dereferencePortableIri}.
10
+ *
11
+ * @internal
12
+ */
13
+ interface PortableReferrerLink {
14
+ readonly object: object;
15
+ readonly property: string;
16
+ }
17
+ /**
18
+ * Checks whether a vocabulary object is part of a chain of portable objects,
19
+ * i.e., whether it has a portable ID or was obtained from a portable object.
20
+ *
21
+ * @internal Technically exported for generated vocabulary classes, but not
22
+ * part of the public API contract. This is not considered public API for
23
+ * Semantic Versioning decisions.
24
+ */
25
+ declare function isInPortableChain(object: object): boolean;
26
+ /**
27
+ * Checks whether a vocabulary object was accepted without an integrity
28
+ * proof, e.g., as an unsecured portable collection served by a trusted
29
+ * gateway. Objects embedded in such an object must not be trusted because
30
+ * of their origin.
31
+ *
32
+ * @internal Technically exported for generated vocabulary classes, but not
33
+ * part of the public API contract. This is not considered public API for
34
+ * Semantic Versioning decisions.
35
+ */
36
+ declare function isUnsecuredPortableObject(object: object): boolean;
37
+ /**
38
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
39
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
40
+ * a portable object. Malformed compatible identifiers count as compatible
41
+ * identifiers too.
42
+ *
43
+ * @internal Technically exported for generated vocabulary classes, but not
44
+ * part of the public API contract. This is not considered public API for
45
+ * Semantic Versioning decisions.
46
+ */
47
+ declare function isCompatibleEf61Iri(url: URL | string): boolean;
48
+ /**
49
+ * Records that a vocabulary object returned by a property accessor was
50
+ * obtained through the given property of a portable object, so that later
51
+ * dereferences from it can tell where it came from. Nothing is recorded if
52
+ * the parent is not in a portable chain, or if the child already has its
53
+ * provenance.
54
+ *
55
+ * @internal Technically exported for generated vocabulary classes, but not
56
+ * part of the public API contract. This is not considered public API for
57
+ * Semantic Versioning decisions.
58
+ */
59
+ declare function recordPortableReferrer(parent: object, child: unknown, property: string): void;
60
+ /**
61
+ * Copies the provenance of a vocabulary object to its clone. The clone
62
+ * keeps where the original came from and the restriction on unsecured
63
+ * objects, but not a positive verification status nor a collection context,
64
+ * since the clone may have different property values.
65
+ *
66
+ * @internal Technically exported for generated vocabulary classes, but not
67
+ * part of the public API contract. This is not considered public API for
68
+ * Semantic Versioning decisions.
69
+ */
70
+ declare function copyPortableProvenance(from: object, to: object): void;
71
+ /**
72
+ * Rejects a reference from a portable object that would be dereferenced as
73
+ * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
74
+ * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
75
+ * is a compatible identifier or a portable IRI. Such a document would skip
76
+ * the portable object policy, so it is rejected regardless of the
77
+ * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
78
+ *
79
+ * @param url The rejected URL, for the log message.
80
+ * @param crossOrigin The `crossOrigin` option of the accessor.
81
+ * @returns Always `null`.
82
+ * @throws {Error} If `crossOrigin` is `"throw"`.
83
+ * @internal Technically exported for generated vocabulary classes, but not
84
+ * part of the public API contract. This is not considered public API for
85
+ * Semantic Versioning decisions.
86
+ */
87
+ declare function rejectPortableChainReference(url: string, crossOrigin: "ignore" | "throw" | "trust" | undefined): null;
88
+ /**
89
+ * Logs that an embedded object without `@id` in an object accepted without
90
+ * an integrity proof is dropped, because it cannot be dereferenced and
91
+ * verified on its own.
92
+ *
93
+ * @internal Technically exported for generated vocabulary classes, but not
94
+ * part of the public API contract. This is not considered public API for
95
+ * Semantic Versioning decisions.
96
+ */
97
+ declare function warnUnverifiableEmbeddedObject(parent: object, property: string): void;
98
+ /**
8
99
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
9
100
  *
10
101
  * @internal Technically exported for generated vocabulary classes, but not
@@ -37,6 +128,7 @@ interface DereferencePortableIriOptions<T> {
37
128
  tracerProvider: TracerProvider;
38
129
  gateways?: readonly (string | URL)[];
39
130
  verifyPortableObject?: PortableObjectVerifier;
131
+ referrer?: PortableReferrerLink;
40
132
  suppressError?: boolean;
41
133
  crossOrigin?: "ignore" | "throw" | "trust";
42
134
  /**
@@ -84,4 +176,4 @@ declare function dereferencePortableIri<T extends {
84
176
  id: URL | null;
85
177
  }>(url: URL, options: DereferencePortableIriOptions<T>): Promise<T | null>;
86
178
  //#endregion
87
- export { DereferencePortableIriOptions, PortableObjectRejectedError, dereferencePortableIri, getPortableGatewayCandidates, isPortableIri };
179
+ export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, copyPortableProvenance, dereferencePortableIri, getPortableGatewayCandidates, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isUnsecuredPortableObject, recordPortableReferrer, rejectPortableChainReference, warnUnverifiableEmbeddedObject };
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { t as preloadedContexts } from "../contexts-DoZUIW7y.js";
3
- import { a as formatIri, g as toCompatibleEf61Id, m as parseIri, r as canonicalizePortableUri } from "../url-CEhi4hb4.js";
3
+ import { a as formatIri, g as toCompatibleEf61Id, m as parseIri, o as fromCompatibleEf61Id, r as canonicalizePortableUri } from "../url-CEhi4hb4.js";
4
4
  import jsonld_default from "../jsonld.js";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { SpanStatusCode } from "@opentelemetry/api";
@@ -24,6 +24,156 @@ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
24
24
  "https://w3id.org/security/data-integrity/v1"
25
25
  ]);
26
26
  /**
27
+ * The maximum number of links in a referrer chain passed to a
28
+ * {@link PortableObjectVerifier}. Longer chains are cut off and marked as
29
+ * truncated.
30
+ */
31
+ const MAX_REFERRER_CHAIN_LENGTH = 32;
32
+ const provenances = /* @__PURE__ */ new WeakMap();
33
+ function getObjectId(object) {
34
+ if (object == null || typeof object !== "object" || !("id" in object)) return null;
35
+ const id = object.id;
36
+ return id instanceof URL ? id : null;
37
+ }
38
+ /**
39
+ * Checks whether a vocabulary object is part of a chain of portable objects,
40
+ * i.e., whether it has a portable ID or was obtained from a portable object.
41
+ *
42
+ * @internal Technically exported for generated vocabulary classes, but not
43
+ * part of the public API contract. This is not considered public API for
44
+ * Semantic Versioning decisions.
45
+ */
46
+ function isInPortableChain(object) {
47
+ if (provenances.has(object)) return true;
48
+ const id = getObjectId(object);
49
+ return id != null && isPortableIri(id);
50
+ }
51
+ /**
52
+ * Checks whether a vocabulary object was accepted without an integrity
53
+ * proof, e.g., as an unsecured portable collection served by a trusted
54
+ * gateway. Objects embedded in such an object must not be trusted because
55
+ * of their origin.
56
+ *
57
+ * @internal Technically exported for generated vocabulary classes, but not
58
+ * part of the public API contract. This is not considered public API for
59
+ * Semantic Versioning decisions.
60
+ */
61
+ function isUnsecuredPortableObject(object) {
62
+ return provenances.get(object)?.acceptance === "unsecured";
63
+ }
64
+ /**
65
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
66
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
67
+ * a portable object. Malformed compatible identifiers count as compatible
68
+ * identifiers too.
69
+ *
70
+ * @internal Technically exported for generated vocabulary classes, but not
71
+ * part of the public API contract. This is not considered public API for
72
+ * Semantic Versioning decisions.
73
+ */
74
+ function isCompatibleEf61Iri(url) {
75
+ try {
76
+ return fromCompatibleEf61Id(url) != null;
77
+ } catch (error) {
78
+ if (error instanceof TypeError) return true;
79
+ throw error;
80
+ }
81
+ }
82
+ /**
83
+ * Records that a vocabulary object returned by a property accessor was
84
+ * obtained through the given property of a portable object, so that later
85
+ * dereferences from it can tell where it came from. Nothing is recorded if
86
+ * the parent is not in a portable chain, or if the child already has its
87
+ * provenance.
88
+ *
89
+ * @internal Technically exported for generated vocabulary classes, but not
90
+ * part of the public API contract. This is not considered public API for
91
+ * Semantic Versioning decisions.
92
+ */
93
+ function recordPortableReferrer(parent, child, property) {
94
+ if (child == null || typeof child !== "object") return;
95
+ if (provenances.has(child) || !isInPortableChain(parent)) return;
96
+ provenances.set(child, { referrer: {
97
+ object: parent,
98
+ property
99
+ } });
100
+ }
101
+ /**
102
+ * Copies the provenance of a vocabulary object to its clone. The clone
103
+ * keeps where the original came from and the restriction on unsecured
104
+ * objects, but not a positive verification status nor a collection context,
105
+ * since the clone may have different property values.
106
+ *
107
+ * @internal Technically exported for generated vocabulary classes, but not
108
+ * part of the public API contract. This is not considered public API for
109
+ * Semantic Versioning decisions.
110
+ */
111
+ function copyPortableProvenance(from, to) {
112
+ const provenance = provenances.get(from);
113
+ if (provenance == null) return;
114
+ provenances.set(to, {
115
+ referrer: provenance.referrer,
116
+ ...provenance.acceptance === "unsecured" ? { acceptance: "unsecured" } : {}
117
+ });
118
+ }
119
+ /**
120
+ * Rejects a reference from a portable object that would be dereferenced as
121
+ * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
122
+ * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
123
+ * is a compatible identifier or a portable IRI. Such a document would skip
124
+ * the portable object policy, so it is rejected regardless of the
125
+ * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
126
+ *
127
+ * @param url The rejected URL, for the log message.
128
+ * @param crossOrigin The `crossOrigin` option of the accessor.
129
+ * @returns Always `null`.
130
+ * @throws {Error} If `crossOrigin` is `"throw"`.
131
+ * @internal Technically exported for generated vocabulary classes, but not
132
+ * part of the public API contract. This is not considered public API for
133
+ * Semantic Versioning decisions.
134
+ */
135
+ function rejectPortableChainReference(url, crossOrigin) {
136
+ const message = "Refusing to dereference {url} from a portable object as an ordinary HTTP(S) object, because it stands for an FEP-ef61 portable object that is not verified this way.";
137
+ if (crossOrigin === "throw") throw new Error(message.replace("{url}", url));
138
+ logger.warn(message, { url });
139
+ return null;
140
+ }
141
+ /**
142
+ * Logs that an embedded object without `@id` in an object accepted without
143
+ * an integrity proof is dropped, because it cannot be dereferenced and
144
+ * verified on its own.
145
+ *
146
+ * @internal Technically exported for generated vocabulary classes, but not
147
+ * part of the public API contract. This is not considered public API for
148
+ * Semantic Versioning decisions.
149
+ */
150
+ function warnUnverifiableEmbeddedObject(parent, property) {
151
+ logger.warn("Dropping an embedded object without @id in the {property} property of {parentId}, because the parent was accepted without an integrity proof and the embedded object cannot be verified on its own.", {
152
+ property,
153
+ parentId: getObjectId(parent)?.href ?? null
154
+ });
155
+ }
156
+ function buildReferrerChain(link, depth = 0) {
157
+ if (link == null) return void 0;
158
+ const provenance = provenances.get(link.object);
159
+ const base = {
160
+ object: link.object,
161
+ id: getObjectId(link.object),
162
+ property: link.property,
163
+ ...provenance?.acceptance == null ? {} : { acceptance: provenance.acceptance },
164
+ ...provenance?.collectionContext === void 0 ? {} : { collectionContext: provenance.collectionContext }
165
+ };
166
+ if (provenance?.referrer == null) return base;
167
+ if (depth + 1 >= MAX_REFERRER_CHAIN_LENGTH) return {
168
+ ...base,
169
+ truncated: true
170
+ };
171
+ return {
172
+ ...base,
173
+ referrer: buildReferrerChain(provenance.referrer, depth + 1)
174
+ };
175
+ }
176
+ /**
27
177
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
28
178
  *
29
179
  * @internal Technically exported for generated vocabulary classes, but not
@@ -204,6 +354,9 @@ async function dereferencePortableIri(url, options) {
204
354
  const contextLoader = snapshot.loader;
205
355
  const attempts = [];
206
356
  const { signal } = options;
357
+ const explicitGateways = options.gateways == null ? void 0 : gateways;
358
+ const gatewayHints = options.gateways == null && gateways.length > 0 ? gateways : void 0;
359
+ const referrer = buildReferrerChain(options.referrer);
207
360
  try {
208
361
  for (const { url: requestUrl, gateway } of requestUrls) {
209
362
  signal?.throwIfAborted();
@@ -226,12 +379,22 @@ async function dereferencePortableIri(url, options) {
226
379
  const { document } = remoteDocument;
227
380
  try {
228
381
  await checkPortableObjectId(document, expectedId, contextLoader);
382
+ let documentUrl;
383
+ try {
384
+ documentUrl = parseIri(remoteDocument.documentUrl);
385
+ } catch {
386
+ documentUrl = void 0;
387
+ }
229
388
  let result;
230
389
  try {
231
390
  result = await verify(document, {
232
391
  documentLoader: options.documentLoader,
233
392
  contextLoader,
234
- tracerProvider: options.tracerProvider
393
+ tracerProvider: options.tracerProvider,
394
+ ...documentUrl == null ? {} : { documentUrl },
395
+ ...explicitGateways == null ? {} : { gateways: explicitGateways },
396
+ ...gatewayHints == null ? {} : { gatewayHints },
397
+ ...referrer == null ? {} : { referrer }
235
398
  });
236
399
  } catch (error) {
237
400
  throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
@@ -242,6 +405,11 @@ async function dereferencePortableIri(url, options) {
242
405
  baseUrl: url
243
406
  });
244
407
  signal?.throwIfAborted();
408
+ if (object != null && typeof object === "object") provenances.set(object, {
409
+ acceptance: result.unsecured === true ? "unsecured" : "verified",
410
+ ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
411
+ ...options.referrer == null ? {} : { referrer: options.referrer }
412
+ });
245
413
  if (gateway != null) span?.setAttribute("activitypub.gateway", gateway.href);
246
414
  return object;
247
415
  } catch (error) {
@@ -308,4 +476,4 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
308
476
  if (actualId !== expectedId) throw new PortableObjectRejection("its @id ({objectId}) does not match the requested portable ID", { objectId: id });
309
477
  }
310
478
  //#endregion
311
- export { PortableObjectRejectedError, dereferencePortableIri, getPortableGatewayCandidates, isPortableIri };
479
+ export { PortableObjectRejectedError, copyPortableProvenance, dereferencePortableIri, getPortableGatewayCandidates, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isUnsecuredPortableObject, recordPortableReferrer, rejectPortableChainReference, warnUnverifiableEmbeddedObject };
package/dist/mod.cjs CHANGED
@@ -16,7 +16,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
16
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
17
  //#region deno.json
18
18
  var name = "@fedify/vocab-runtime";
19
- var version = "2.4.0-dev.2056+e6394f3b";
19
+ var version = "2.4.0-dev.2063+366c0362";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**