@fedify/vocab-tools 2.4.0-dev.2056 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.4.0-dev.2056+e6394f3b",
3
+ "version": "2.4.0-dev.2065+b09fdd33",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts"
package/dist/mod.cjs CHANGED
@@ -8,7 +8,7 @@ let yaml = require("yaml");
8
8
  let es_toolkit = require("es-toolkit");
9
9
  //#region deno.json
10
10
  var name = "@fedify/vocab-tools";
11
- var version = "2.4.0-dev.2056+e6394f3b";
11
+ var version = "2.4.0-dev.2065+b09fdd33";
12
12
  //#endregion
13
13
  //#region src/type.ts
14
14
  const HEURISTICS_CONTEXTS = [
@@ -1425,6 +1425,7 @@ async function* generateCloner(typeUri, types) {
1425
1425
  { id: values.id ?? this.id },
1426
1426
  options
1427
1427
  );
1428
+ copyPortableProvenance(this, clone);
1428
1429
  `;
1429
1430
  else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
1430
1431
  for (const property of type.properties) {
@@ -1672,14 +1673,27 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1672
1673
  ${JSON.stringify(version)},
1673
1674
  );
1674
1675
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
1675
- if (isPortableIri(url)) {
1676
+ const dereferencePortable = async (
1677
+ portableUrl: URL,
1678
+ extra: {
1679
+ inferredGateways?: readonly URL[];
1680
+ response?: RemoteDocument;
1681
+ contextLoader?: DocumentLoader;
1682
+ } = {},
1683
+ ) => {
1676
1684
  try {
1677
- const obj = await dereferencePortableIri(url, {
1685
+ const obj = await dereferencePortableIri(portableUrl, {
1678
1686
  documentLoader,
1679
- contextLoader,
1687
+ contextLoader: extra.contextLoader ?? contextLoader,
1680
1688
  tracerProvider,
1681
1689
  gateways: options.gateways,
1690
+ inferredGateways: extra.inferredGateways,
1691
+ response: extra.response,
1682
1692
  verifyPortableObject: options.verifyPortableObject,
1693
+ referrer: {
1694
+ object: this,
1695
+ property: ${JSON.stringify(property.uri)},
1696
+ },
1683
1697
  suppressError: options.suppressError,
1684
1698
  crossOrigin: options.crossOrigin,
1685
1699
  span,
@@ -1692,7 +1706,10 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1692
1706
  }),
1693
1707
  });
1694
1708
  if (obj != null) {
1695
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1709
+ span.setAttribute(
1710
+ "activitypub.object.id",
1711
+ (obj.id ?? portableUrl).href,
1712
+ );
1696
1713
  span.setAttribute(
1697
1714
  "activitypub.object.type",
1698
1715
  // @ts-ignore: obj.constructor always has a typeId.
@@ -1709,8 +1726,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1709
1726
  } finally {
1710
1727
  span.end();
1711
1728
  }
1712
- }
1729
+ };
1730
+ if (isPortableIri(url)) return await dereferencePortable(url);
1713
1731
  const lookupUrl = formatIri(url);
1732
+ const portableMode = isPortableMode(this, options);
1733
+ if (portableMode) {
1734
+ const compatible = parseCompatibleEf61Reference(url);
1735
+ if (compatible === null) {
1736
+ span.end();
1737
+ return rejectMalformedCompatibleReference(lookupUrl, options);
1738
+ } else if (compatible != null) {
1739
+ return await dereferencePortable(compatible.id, {
1740
+ inferredGateways: [compatible.gateway],
1741
+ });
1742
+ }
1743
+ }
1714
1744
  let fetchResult: RemoteDocument;
1715
1745
  try {
1716
1746
  fetchResult = await documentLoader(lookupUrl);
@@ -1730,52 +1760,90 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1730
1760
  throw error;
1731
1761
  }
1732
1762
  const { document, documentUrl } = fetchResult;
1733
- const baseUrl = parseIri(documentUrl);
1763
+ // In portable mode, the document may turn out to be a portable object,
1764
+ // which has to be verified with the same context documents:
1765
+ const snapshot = portableMode
1766
+ ? createSnapshotContextLoader(contextLoader)
1767
+ : null;
1734
1768
  try {
1735
- const obj = await this.#${property.singularName}_fromJsonLd(
1736
- document,
1737
- { documentLoader, contextLoader, tracerProvider, baseUrl }
1738
- );
1739
- if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
1740
- if (options.crossOrigin === "throw") {
1741
- throw new Error(
1742
- "The object's @id (" + obj.id.href + ") has a different origin " +
1743
- "than the document URL (" + baseUrl.href + "); refusing to return " +
1744
- "the object. If you want to bypass this check and are aware of" +
1745
- 'the security implications, set the crossOrigin option to "trust".'
1769
+ let claim: PortableResponseClaim | null | undefined;
1770
+ try {
1771
+ const baseUrl = parseIri(documentUrl);
1772
+ const obj = await this.#${property.singularName}_fromJsonLd(
1773
+ document,
1774
+ {
1775
+ documentLoader,
1776
+ contextLoader: snapshot?.loader ?? contextLoader,
1777
+ tracerProvider,
1778
+ baseUrl,
1779
+ }
1780
+ );
1781
+ if (snapshot == null) {
1782
+ markUnverifiedPortableClaim(obj, url, documentUrl);
1783
+ } else {
1784
+ claim = getPortableResponseClaim(documentUrl, obj.id);
1785
+ }
1786
+ if (claim === undefined) {
1787
+ if (
1788
+ obj?.id != null &&
1789
+ !isTrustedIriOrigin(options, obj.id, baseUrl)
1790
+ ) {
1791
+ if (options.crossOrigin === "throw") {
1792
+ throw new Error(
1793
+ "The object's @id (" + obj.id.href + ") has a different origin " +
1794
+ "than the document URL (" + baseUrl.href + "); refusing to return " +
1795
+ "the object. If you want to bypass this check and are aware of" +
1796
+ 'the security implications, set the crossOrigin option to "trust".'
1797
+ );
1798
+ }
1799
+ getLogger(["fedify", "vocab"]).warn(
1800
+ "The object's @id ({objectId}) has a different origin than the document " +
1801
+ "URL ({documentUrl}); refusing to return the object. If you want to " +
1802
+ "bypass this check and are aware of the security implications, " +
1803
+ 'set the crossOrigin option to "trust".',
1804
+ { ...fetchResult, objectId: obj.id.href },
1805
+ );
1806
+ span.end();
1807
+ return null;
1808
+ }
1809
+ span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1810
+ span.setAttribute(
1811
+ "activitypub.object.type",
1812
+ // @ts-ignore: obj.constructor always has a typeId.
1813
+ obj.constructor.typeId.href
1746
1814
  );
1815
+ span.end();
1816
+ return obj;
1747
1817
  }
1748
- getLogger(["fedify", "vocab"]).warn(
1749
- "The object's @id ({objectId}) has a different origin than the document " +
1750
- "URL ({documentUrl}); refusing to return the object. If you want to " +
1751
- "bypass this check and are aware of the security implications, " +
1752
- 'set the crossOrigin option to "trust".',
1753
- { ...fetchResult, objectId: obj.id.href },
1754
- );
1755
- return null;
1818
+ } catch (e) {
1819
+ if (options.suppressError) {
1820
+ getLogger(["fedify", "vocab"]).error(
1821
+ "Failed to parse {url}: {error}",
1822
+ { error: e, url: lookupUrl }
1823
+ );
1824
+ span.end();
1825
+ return null;
1826
+ }
1827
+ span.setStatus({
1828
+ code: SpanStatusCode.ERROR,
1829
+ message: String(e),
1830
+ });
1831
+ span.end();
1832
+ throw e;
1756
1833
  }
1757
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1758
- span.setAttribute(
1759
- "activitypub.object.type",
1760
- // @ts-ignore: obj.constructor always has a typeId.
1761
- obj.constructor.typeId.href
1762
- );
1763
- return obj;
1764
- } catch (e) {
1765
- if (options.suppressError) {
1766
- getLogger(["fedify", "vocab"]).error(
1767
- "Failed to parse {url}: {error}",
1768
- { error: e, url: lookupUrl }
1769
- );
1770
- return null;
1834
+ // The document stands for a portable object, so it is verified as
1835
+ // one instead of being trusted because of where it came from:
1836
+ if (claim === null) {
1837
+ span.end();
1838
+ return rejectMalformedCompatibleReference(documentUrl, options);
1771
1839
  }
1772
- span.setStatus({
1773
- code: SpanStatusCode.ERROR,
1774
- message: String(e),
1840
+ return await dereferencePortable(claim.id, {
1841
+ inferredGateways: claim.inferredGateways,
1842
+ response: fetchResult,
1843
+ contextLoader: snapshot?.loader,
1775
1844
  });
1776
- throw e;
1777
1845
  } finally {
1778
- span.end();
1846
+ snapshot?.release();
1779
1847
  }
1780
1848
  });
1781
1849
  }
@@ -1888,18 +1956,31 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1888
1956
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
1889
1957
  let v = this.${await getFieldName(property.uri)}[0];
1890
1958
  if (!(v instanceof URL) &&
1891
- v.id != null &&
1892
- !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1893
1959
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1894
- v = v.id;
1960
+ if (v.id == null) {
1961
+ if (isUnsecuredPortableObject(this)) {
1962
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
1963
+ return null;
1964
+ }
1965
+ } else if (
1966
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
1967
+ isUnsecuredPortableObject(this) ||
1968
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
1969
+ ) {
1970
+ v = v.id;
1971
+ }
1895
1972
  }
1896
1973
  if (v instanceof URL) {
1897
1974
  const fetched =
1898
1975
  await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
1899
1976
  if (fetched == null) return null;
1977
+ // Not cached, so that it is verified when the portable object
1978
+ // policy applies later:
1979
+ if (isUnverifiedPortableClaim(fetched)) return fetched;
1900
1980
  this.${await getFieldName(property.uri)}[0] = fetched;
1901
1981
  this.${await getFieldName(property.uri, "#_trust")}.add(0);
1902
1982
  this._cachedJsonLd = undefined;
1983
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1903
1984
  return fetched;
1904
1985
  }
1905
1986
  `;
@@ -1929,12 +2010,16 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1929
2010
  }
1930
2011
  yield `
1931
2012
  if (v?.id != null &&
1932
- ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2013
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2014
+ isUnsecuredPortableObject(this) ||
2015
+ mustDereferencePortableObject(
2016
+ this, ${trustOwner}, v.id, options,
2017
+ )) &&
1933
2018
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1934
2019
  if (options.crossOrigin === "throw") {
1935
2020
  throw new Error(
1936
2021
  "The property object's @id (" + v.id.href + ") has a different " +
1937
- "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
2022
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1938
2023
  "refusing to return the object. If you want to bypass this " +
1939
2024
  "check and are aware of the security implications, set the " +
1940
2025
  'crossOrigin option to "trust".'
@@ -1946,10 +2031,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1946
2031
  "return the object. If you want to bypass this check and are " +
1947
2032
  "aware of the security implications, set the crossOrigin option " +
1948
2033
  'to "trust".',
1949
- { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
2034
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1950
2035
  );
1951
2036
  return null;
1952
2037
  }
2038
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1953
2039
  return v;
1954
2040
  }
1955
2041
  `;
@@ -1996,18 +2082,34 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1996
2082
  for (let i = 0; i < vs.length; i++) {
1997
2083
  let v = vs[i];
1998
2084
  if (!(v instanceof URL) &&
1999
- v.id != null &&
2000
- !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2001
2085
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
2002
- v = v.id;
2086
+ if (v.id == null) {
2087
+ if (isUnsecuredPortableObject(this)) {
2088
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2089
+ continue;
2090
+ }
2091
+ } else if (
2092
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2093
+ isUnsecuredPortableObject(this) ||
2094
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2095
+ ) {
2096
+ v = v.id;
2097
+ }
2003
2098
  }
2004
2099
  if (v instanceof URL) {
2005
2100
  const fetched =
2006
2101
  await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
2007
2102
  if (fetched == null) continue;
2103
+ // Not cached, so that it is verified when the portable object
2104
+ // policy applies later:
2105
+ if (isUnverifiedPortableClaim(fetched)) {
2106
+ yield fetched;
2107
+ continue;
2108
+ }
2008
2109
  vs[i] = fetched;
2009
2110
  this.${await getFieldName(property.uri, "#_trust")}.add(i);
2010
2111
  this._cachedJsonLd = undefined;
2112
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
2011
2113
  yield fetched;
2012
2114
  continue;
2013
2115
  }
@@ -2038,12 +2140,16 @@ async function* generateProperty(type, property, types, moduleVarNames) {
2038
2140
  }
2039
2141
  yield `
2040
2142
  if (v?.id != null &&
2041
- ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
2143
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2144
+ isUnsecuredPortableObject(this) ||
2145
+ mustDereferencePortableObject(
2146
+ this, ${trustOwner}, v.id, options,
2147
+ )) &&
2042
2148
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
2043
2149
  if (options.crossOrigin === "throw") {
2044
2150
  throw new Error(
2045
2151
  "The property object's @id (" + v.id.href + ") has a different " +
2046
- "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
2152
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
2047
2153
  "refusing to return the object. If you want to bypass this " +
2048
2154
  "check and are aware of the security implications, set the " +
2049
2155
  'crossOrigin option to "trust".'
@@ -2055,10 +2161,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
2055
2161
  "return the object. If you want to bypass this check and are " +
2056
2162
  "aware of the security implications, set the crossOrigin " +
2057
2163
  'option to "trust".',
2058
- { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
2164
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
2059
2165
  );
2060
2166
  continue;
2061
2167
  }
2168
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
2062
2169
  yield v;
2063
2170
  }
2064
2171
  }
@@ -2080,7 +2187,23 @@ const INTERNAL_RUNTIME_IMPORTS = [
2080
2187
  "isTrustedIriOrigin",
2081
2188
  "normalizeJsonLdIris"
2082
2189
  ].join(",\n ");
2083
- const PORTABLE_DEREFERENCE_IMPORTS = ["dereferencePortableIri", "isPortableIri"].join(",\n ");
2190
+ const PORTABLE_DEREFERENCE_IMPORTS = [
2191
+ "copyPortableProvenance",
2192
+ "createSnapshotContextLoader",
2193
+ "dereferencePortableIri",
2194
+ "getPortableResponseClaim",
2195
+ "isPortableIri",
2196
+ "isPortableMode",
2197
+ "isUnsecuredPortableObject",
2198
+ "isUnverifiedPortableClaim",
2199
+ "markUnverifiedPortableClaim",
2200
+ "mustDereferencePortableObject",
2201
+ "parseCompatibleEf61Reference",
2202
+ "type PortableResponseClaim",
2203
+ "recordPortableReferrer",
2204
+ "rejectMalformedCompatibleReference",
2205
+ "warnUnverifiableEmbeddedObject"
2206
+ ].join(",\n ");
2084
2207
  const SIGNED_REPRESENTATION_IMPORTS = [
2085
2208
  "enterSignedValueScope",
2086
2209
  "resolveSignedValues",