@fedify/vocab-runtime 2.3.12 → 2.3.13

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.3.12",
3
+ "version": "2.3.13",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
package/dist/mod.cjs CHANGED
@@ -4537,7 +4537,7 @@ const preloadedContexts = {
4537
4537
  //#endregion
4538
4538
  //#region deno.json
4539
4539
  var name = "@fedify/vocab-runtime";
4540
- var version = "2.3.12";
4540
+ var version = "2.3.13";
4541
4541
  //#endregion
4542
4542
  //#region src/request.ts
4543
4543
  /**
@@ -5198,7 +5198,9 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
5198
5198
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
5199
5199
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
5200
5200
  options?.signal?.throwIfAborted();
5201
- const currentUrl = new URL(url).href;
5201
+ const parsedUrl = new URL(url);
5202
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
5203
+ const currentUrl = parsedUrl.href;
5202
5204
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
5203
5205
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
5204
5206
  return {
package/dist/mod.js CHANGED
@@ -4532,7 +4532,7 @@ const preloadedContexts = {
4532
4532
  //#endregion
4533
4533
  //#region deno.json
4534
4534
  var name = "@fedify/vocab-runtime";
4535
- var version = "2.3.12";
4535
+ var version = "2.3.13";
4536
4536
  //#endregion
4537
4537
  //#region src/request.ts
4538
4538
  /**
@@ -5193,7 +5193,9 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
5193
5193
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
5194
5194
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
5195
5195
  options?.signal?.throwIfAborted();
5196
- const currentUrl = new URL(url).href;
5196
+ const parsedUrl = new URL(url);
5197
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
5198
+ const currentUrl = parsedUrl.href;
5197
5199
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
5198
5200
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
5199
5201
  return {
@@ -1,5 +1,5 @@
1
1
  require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-CpyyucIs.cjs");
2
+ const require_request = require("./request-C4XcOfmB.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  //#region src/body.ts
5
5
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-BZ0Eo5jP.mjs";
1
+ import { t as FetchError } from "./request-DkWzsggq.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,5 +1,5 @@
1
1
  require("./chunk-C2EiDwsr.cjs");
2
- const require_body = require("./body-BQR9_LMA.cjs");
2
+ const require_body = require("./body-BzkzlcFe.cjs");
3
3
  let node_assert_strict = require("node:assert/strict");
4
4
  let node_test = require("node:test");
5
5
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { r as readBoundedText, t as BodyTooLargeError } from "./body-B9Pjgctr.mjs";
1
+ import { r as readBoundedText, t as BodyTooLargeError } from "./body-DZC-BUmp.mjs";
2
2
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
3
3
  import { test } from "node:test";
4
4
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1,5 +1,5 @@
1
1
  require("./chunk-C2EiDwsr.cjs");
2
- require("./docloader-DmbucBPj.cjs");
2
+ require("./docloader-BMxfYjxN.cjs");
3
3
  require("./url-4PAkYhZC.cjs");
4
4
  require("./key-CWDxavIR.cjs");
5
5
  require("./multibase-BL45eko3.cjs");
@@ -1,4 +1,4 @@
1
- import "./docloader-CY0bQY42.mjs";
1
+ import "./docloader-DtJnwB0I.mjs";
2
2
  import "./url-3fyJuqC0.mjs";
3
3
  import "./key-Qj7u1qmH.mjs";
4
4
  import "./multibase-Da9VJCw8.mjs";
@@ -1,6 +1,6 @@
1
1
  require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-CpyyucIs.cjs");
3
- const require_body = require("./body-BQR9_LMA.cjs");
2
+ const require_request = require("./request-C4XcOfmB.cjs");
3
+ const require_body = require("./body-BzkzlcFe.cjs");
4
4
  const require_link = require("./link-qDTmjPw_.cjs");
5
5
  const require_url = require("./url-4PAkYhZC.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -4654,7 +4654,9 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4654
4654
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
4655
4655
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
4656
4656
  options?.signal?.throwIfAborted();
4657
- const currentUrl = new URL(url).href;
4657
+ const parsedUrl = new URL(url);
4658
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new require_url.UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4659
+ const currentUrl = parsedUrl.href;
4658
4660
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
4659
4661
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
4660
4662
  return {
@@ -1,5 +1,5 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-BZ0Eo5jP.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-B9Pjgctr.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-DkWzsggq.mjs";
2
+ import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-DZC-BUmp.mjs";
3
3
  import { t as HttpHeaderLink } from "./link-CYszIegt.mjs";
4
4
  import { o as validatePublicUrl, t as UrlError } from "./url-3fyJuqC0.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
@@ -4653,7 +4653,9 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
4653
4653
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
4654
4654
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
4655
4655
  options?.signal?.throwIfAborted();
4656
- const currentUrl = new URL(url).href;
4656
+ const parsedUrl = new URL(url);
4657
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
4658
+ const currentUrl = parsedUrl.href;
4657
4659
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
4658
4660
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
4659
4661
  return {
@@ -1,6 +1,6 @@
1
1
  const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-CpyyucIs.cjs");
3
- const require_docloader = require("./docloader-DmbucBPj.cjs");
2
+ const require_request = require("./request-C4XcOfmB.cjs");
3
+ const require_docloader = require("./docloader-BMxfYjxN.cjs");
4
4
  const require_url = require("./url-4PAkYhZC.cjs");
5
5
  let node_zlib = require("node:zlib");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -2009,4 +2009,52 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
2009
2009
  esm_default.hardReset();
2010
2010
  }
2011
2011
  });
2012
+ const unsupportedUrls = [
2013
+ "ftp://example.com/collection",
2014
+ "file:///collection",
2015
+ `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
2016
+ ];
2017
+ function isUnsupportedProtocolError(error, url) {
2018
+ return error instanceof require_url.UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
2019
+ }
2020
+ async function withFetchStub(respond, callback) {
2021
+ const fetched = [];
2022
+ const originalFetch = globalThis.fetch;
2023
+ globalThis.fetch = (input) => {
2024
+ const url = input instanceof Request ? input.url : String(input);
2025
+ fetched.push(url);
2026
+ return Promise.resolve(respond(url));
2027
+ };
2028
+ try {
2029
+ await callback(fetched);
2030
+ } finally {
2031
+ globalThis.fetch = originalFetch;
2032
+ }
2033
+ }
2034
+ for (const url of unsupportedUrls) {
2035
+ const scheme = new URL(url).protocol;
2036
+ (0, _fedify_fixture.test)(`getDocumentLoader() rejects ${scheme} URLs with private addresses allowed before fetching`, async () => {
2037
+ await withFetchStub(() => Response.json({}), async (fetched) => {
2038
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(url), (e) => isUnsupportedProtocolError(e, url));
2039
+ (0, node_assert.deepStrictEqual)(fetched, []);
2040
+ });
2041
+ });
2042
+ (0, _fedify_fixture.test)(`getDocumentLoader() rejects redirects to ${scheme} URLs with private addresses allowed`, async () => {
2043
+ const start = "https://example.com/redirect-to-unsupported";
2044
+ await withFetchStub(() => Response.redirect(url, 302), async (fetched) => {
2045
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(start), (e) => isUnsupportedProtocolError(e, url));
2046
+ (0, node_assert.deepStrictEqual)(fetched, [start]);
2047
+ });
2048
+ });
2049
+ for (const html of [false, true]) (0, _fedify_fixture.test)(`getDocumentLoader() rejects ${html ? "HTML" : "Link header"} alternates to ${scheme} URLs with private addresses allowed`, async () => {
2050
+ const start = "https://example.com/alternate-to-unsupported";
2051
+ await withFetchStub(() => html ? new Response(`<link rel="alternate" type="application/activity+json" href="${url}">`, { headers: { "Content-Type": "text/html" } }) : new Response("", { headers: {
2052
+ "Content-Type": "text/plain",
2053
+ Link: `<${url}>; rel="alternate"; type="application/activity+json"`
2054
+ } }), async (fetched) => {
2055
+ await (0, node_assert.rejects)(require_docloader.getDocumentLoader({ allowPrivateAddress: true })(start), (e) => isUnsupportedProtocolError(e, url));
2056
+ (0, node_assert.deepStrictEqual)(fetched, [start]);
2057
+ });
2058
+ });
2059
+ }
2012
2060
  //#endregion
@@ -1,5 +1,5 @@
1
- import { t as FetchError } from "./request-BZ0Eo5jP.mjs";
2
- import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-CY0bQY42.mjs";
1
+ import { t as FetchError } from "./request-DkWzsggq.mjs";
2
+ import { i as cid_v1_default, n as getRemoteDocument, r as preloadedContexts, t as getDocumentLoader } from "./docloader-DtJnwB0I.mjs";
3
3
  import { t as UrlError } from "./url-3fyJuqC0.mjs";
4
4
  import { gzipSync } from "node:zlib";
5
5
  import { configure, reset } from "@logtape/logtape";
@@ -2026,5 +2026,53 @@ test("getDocumentLoader() rejects cancellation before fetching", async () => {
2026
2026
  esm_default.hardReset();
2027
2027
  }
2028
2028
  });
2029
+ const unsupportedUrls = [
2030
+ "ftp://example.com/collection",
2031
+ "file:///collection",
2032
+ `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
2033
+ ];
2034
+ function isUnsupportedProtocolError(error, url) {
2035
+ return error instanceof UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
2036
+ }
2037
+ async function withFetchStub(respond, callback) {
2038
+ const fetched = [];
2039
+ const originalFetch = globalThis.fetch;
2040
+ globalThis.fetch = (input) => {
2041
+ const url = input instanceof Request ? input.url : String(input);
2042
+ fetched.push(url);
2043
+ return Promise.resolve(respond(url));
2044
+ };
2045
+ try {
2046
+ await callback(fetched);
2047
+ } finally {
2048
+ globalThis.fetch = originalFetch;
2049
+ }
2050
+ }
2051
+ for (const url of unsupportedUrls) {
2052
+ const scheme = new URL(url).protocol;
2053
+ test(`getDocumentLoader() rejects ${scheme} URLs with private addresses allowed before fetching`, async () => {
2054
+ await withFetchStub(() => Response.json({}), async (fetched) => {
2055
+ await rejects(getDocumentLoader({ allowPrivateAddress: true })(url), (e) => isUnsupportedProtocolError(e, url));
2056
+ deepStrictEqual(fetched, []);
2057
+ });
2058
+ });
2059
+ test(`getDocumentLoader() rejects redirects to ${scheme} URLs with private addresses allowed`, async () => {
2060
+ const start = "https://example.com/redirect-to-unsupported";
2061
+ await withFetchStub(() => Response.redirect(url, 302), async (fetched) => {
2062
+ await rejects(getDocumentLoader({ allowPrivateAddress: true })(start), (e) => isUnsupportedProtocolError(e, url));
2063
+ deepStrictEqual(fetched, [start]);
2064
+ });
2065
+ });
2066
+ for (const html of [false, true]) test(`getDocumentLoader() rejects ${html ? "HTML" : "Link header"} alternates to ${scheme} URLs with private addresses allowed`, async () => {
2067
+ const start = "https://example.com/alternate-to-unsupported";
2068
+ await withFetchStub(() => html ? new Response(`<link rel="alternate" type="application/activity+json" href="${url}">`, { headers: { "Content-Type": "text/html" } }) : new Response("", { headers: {
2069
+ "Content-Type": "text/plain",
2070
+ Link: `<${url}>; rel="alternate"; type="application/activity+json"`
2071
+ } }), async (fetched) => {
2072
+ await rejects(getDocumentLoader({ allowPrivateAddress: true })(start), (e) => isUnsupportedProtocolError(e, url));
2073
+ deepStrictEqual(fetched, [start]);
2074
+ });
2075
+ });
2076
+ }
2029
2077
  //#endregion
2030
2078
  export {};
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_chunk.__toESM(node_process, 1);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.3.12";
6
+ var version = "2.3.13";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  //#region deno.json
3
3
  var name = "@fedify/vocab-runtime";
4
- var version = "2.3.12";
4
+ var version = "2.3.13";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-C2EiDwsr.cjs");
2
- const require_request = require("./request-CpyyucIs.cjs");
2
+ const require_request = require("./request-C4XcOfmB.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_process = require("node:process");
5
5
  node_process = require_chunk.__toESM(node_process, 1);
@@ -1,4 +1,4 @@
1
- import { o as version, r as getUserAgent } from "./request-BZ0Eo5jP.mjs";
1
+ import { o as version, r as getUserAgent } from "./request-DkWzsggq.mjs";
2
2
  import { test } from "node:test";
3
3
  import process from "node:process";
4
4
  import { deepStrictEqual } from "node:assert";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.3.12",
3
+ "version": "2.3.13",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1103,3 +1103,93 @@ test("getDocumentLoader() rejects cancellation before fetching", async () => {
1103
1103
  fetchMock.hardReset();
1104
1104
  }
1105
1105
  });
1106
+
1107
+ const unsupportedUrls = [
1108
+ "ftp://example.com/collection",
1109
+ "file:///collection",
1110
+ `data:application/ld+json,${
1111
+ encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))
1112
+ }`,
1113
+ ];
1114
+
1115
+ function isUnsupportedProtocolError(error: unknown, url: string): boolean {
1116
+ return error instanceof UrlError &&
1117
+ error.message === `Unsupported protocol: ${new URL(url).protocol}` &&
1118
+ error.reason === "disallowed";
1119
+ }
1120
+
1121
+ async function withFetchStub(
1122
+ respond: (url: string) => Response,
1123
+ callback: (fetched: string[]) => Promise<void>,
1124
+ ): Promise<void> {
1125
+ const fetched: string[] = [];
1126
+ const originalFetch = globalThis.fetch;
1127
+ globalThis.fetch = (input: RequestInfo | URL) => {
1128
+ const url = input instanceof Request ? input.url : String(input);
1129
+ fetched.push(url);
1130
+ return Promise.resolve(respond(url));
1131
+ };
1132
+ try {
1133
+ await callback(fetched);
1134
+ } finally {
1135
+ globalThis.fetch = originalFetch;
1136
+ }
1137
+ }
1138
+
1139
+ for (const url of unsupportedUrls) {
1140
+ const scheme = new URL(url).protocol;
1141
+
1142
+ test(`getDocumentLoader() rejects ${scheme} URLs with private addresses allowed before fetching`, async () => {
1143
+ await withFetchStub(() => Response.json({}), async (fetched) => {
1144
+ await rejects(
1145
+ getDocumentLoader({ allowPrivateAddress: true })(url),
1146
+ (e) => isUnsupportedProtocolError(e, url),
1147
+ );
1148
+ deepStrictEqual(fetched, []);
1149
+ });
1150
+ });
1151
+
1152
+ test(`getDocumentLoader() rejects redirects to ${scheme} URLs with private addresses allowed`, async () => {
1153
+ const start = "https://example.com/redirect-to-unsupported";
1154
+ await withFetchStub(() => Response.redirect(url, 302), async (fetched) => {
1155
+ await rejects(
1156
+ getDocumentLoader({ allowPrivateAddress: true })(start),
1157
+ (e) => isUnsupportedProtocolError(e, url),
1158
+ );
1159
+ deepStrictEqual(fetched, [start]);
1160
+ });
1161
+ });
1162
+
1163
+ for (const html of [false, true]) {
1164
+ test(
1165
+ `getDocumentLoader() rejects ${
1166
+ html ? "HTML" : "Link header"
1167
+ } alternates to ${scheme} URLs with private addresses allowed`,
1168
+ async () => {
1169
+ const start = "https://example.com/alternate-to-unsupported";
1170
+ await withFetchStub(
1171
+ () =>
1172
+ html
1173
+ ? new Response(
1174
+ `<link rel="alternate" type="application/activity+json" href="${url}">`,
1175
+ { headers: { "Content-Type": "text/html" } },
1176
+ )
1177
+ : new Response("", {
1178
+ headers: {
1179
+ "Content-Type": "text/plain",
1180
+ Link:
1181
+ `<${url}>; rel="alternate"; type="application/activity+json"`,
1182
+ },
1183
+ }),
1184
+ async (fetched) => {
1185
+ await rejects(
1186
+ getDocumentLoader({ allowPrivateAddress: true })(start),
1187
+ (e) => isUnsupportedProtocolError(e, url),
1188
+ );
1189
+ deepStrictEqual(fetched, [start]);
1190
+ },
1191
+ );
1192
+ },
1193
+ );
1194
+ }
1195
+ }
package/src/docloader.ts CHANGED
@@ -311,7 +311,11 @@ export function getDocumentLoader(
311
311
  visited = new Set<string>(),
312
312
  ): Promise<RemoteDocument> {
313
313
  options?.signal?.throwIfAborted();
314
- const currentUrl = new URL(url).href;
314
+ const parsedUrl = new URL(url);
315
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
316
+ throw new UrlError(`Unsupported protocol: ${parsedUrl.protocol}`);
317
+ }
318
+ const currentUrl = parsedUrl.href;
315
319
  if (!skipPreloadedContexts && currentUrl in preloadedContexts) {
316
320
  logger.debug("Using preloaded context: {url}.", { url: currentUrl });
317
321
  return {