@fedify/vocab-runtime 2.0.29-dev.1989 → 2.0.29-dev.2018

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.0.29-dev.1989+6b9d933b",
3
+ "version": "2.0.29-dev.2018+2e67253f",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
package/dist/mod.cjs CHANGED
@@ -6,6 +6,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
6
6
  let node_process = require("node:process");
7
7
  node_process = require_chunk.__toESM(node_process);
8
8
  let node_dns_promises = require("node:dns/promises");
9
+ node_dns_promises = require_chunk.__toESM(node_dns_promises);
9
10
  let node_net = require("node:net");
10
11
  let asn1js = require("asn1js");
11
12
  let byte_encodings_base64 = require("byte-encodings/base64");
@@ -4434,7 +4435,7 @@ const preloadedContexts = {
4434
4435
  //#endregion
4435
4436
  //#region deno.json
4436
4437
  var name = "@fedify/vocab-runtime";
4437
- var version = "2.0.29-dev.1989+6b9d933b";
4438
+ var version = "2.0.29-dev.2018+2e67253f";
4438
4439
  //#endregion
4439
4440
  //#region src/request.ts
4440
4441
  /**
@@ -4757,9 +4758,16 @@ var HttpHeaderLink = class HttpHeaderLink {
4757
4758
  //#endregion
4758
4759
  //#region src/url.ts
4759
4760
  var UrlError = class extends Error {
4761
+ /**
4762
+ * The failure category. Defaults to `"disallowed"`.
4763
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
4764
+ * @since 2.0.29
4765
+ */
4766
+ reason;
4760
4767
  constructor(message, options) {
4761
4768
  super(message, options);
4762
4769
  this.name = "UrlError";
4770
+ this.reason = options?.reason ?? "disallowed";
4763
4771
  }
4764
4772
  };
4765
4773
  /**
@@ -4785,9 +4793,12 @@ async function validatePublicUrl(url) {
4785
4793
  }
4786
4794
  let addresses;
4787
4795
  try {
4788
- addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
4796
+ addresses = await node_dns_promises.default.lookup(hostname, { all: true });
4789
4797
  } catch (error) {
4790
- throw new UrlError("DNS lookup failed", { cause: error });
4798
+ throw new UrlError("DNS lookup failed", {
4799
+ cause: error,
4800
+ reason: "dns"
4801
+ });
4791
4802
  }
4792
4803
  validateLookupAddresses(addresses);
4793
4804
  }
@@ -4816,7 +4827,7 @@ function validateLookupAddresses(addresses) {
4816
4827
  ipAddressCount++;
4817
4828
  validatePublicIpAddress(address, family);
4818
4829
  }
4819
- if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
4830
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
4820
4831
  }
4821
4832
  function validatePublicIpAddress(address, family) {
4822
4833
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
package/dist/mod.d.cts CHANGED
@@ -320,8 +320,15 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
320
320
  //#endregion
321
321
  //#region src/url.d.ts
322
322
  declare class UrlError extends Error {
323
+ /**
324
+ * The failure category. Defaults to `"disallowed"`.
325
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
326
+ * @since 2.0.29
327
+ */
328
+ readonly reason: "dns" | "disallowed";
323
329
  constructor(message: string, options?: {
324
330
  cause?: unknown;
331
+ reason?: "dns" | "disallowed";
325
332
  });
326
333
  }
327
334
  /**
package/dist/mod.d.ts CHANGED
@@ -319,8 +319,15 @@ declare function encodingFromBaseData(data: string | Uint8Array): Base;
319
319
  //#endregion
320
320
  //#region src/url.d.ts
321
321
  declare class UrlError extends Error {
322
+ /**
323
+ * The failure category. Defaults to `"disallowed"`.
324
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
325
+ * @since 2.0.29
326
+ */
327
+ readonly reason: "dns" | "disallowed";
322
328
  constructor(message: string, options?: {
323
329
  cause?: unknown;
330
+ reason?: "dns" | "disallowed";
324
331
  });
325
332
  }
326
333
  /**
package/dist/mod.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
4
4
  import process from "node:process";
5
- import { lookup } from "node:dns/promises";
5
+ import dns from "node:dns/promises";
6
6
  import { isIP } from "node:net";
7
7
  import { Integer, Sequence } from "asn1js";
8
8
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
@@ -4430,7 +4430,7 @@ const preloadedContexts = {
4430
4430
  //#endregion
4431
4431
  //#region deno.json
4432
4432
  var name = "@fedify/vocab-runtime";
4433
- var version = "2.0.29-dev.1989+6b9d933b";
4433
+ var version = "2.0.29-dev.2018+2e67253f";
4434
4434
  //#endregion
4435
4435
  //#region src/request.ts
4436
4436
  /**
@@ -4753,9 +4753,16 @@ var HttpHeaderLink = class HttpHeaderLink {
4753
4753
  //#endregion
4754
4754
  //#region src/url.ts
4755
4755
  var UrlError = class extends Error {
4756
+ /**
4757
+ * The failure category. Defaults to `"disallowed"`.
4758
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
4759
+ * @since 2.0.29
4760
+ */
4761
+ reason;
4756
4762
  constructor(message, options) {
4757
4763
  super(message, options);
4758
4764
  this.name = "UrlError";
4765
+ this.reason = options?.reason ?? "disallowed";
4759
4766
  }
4760
4767
  };
4761
4768
  /**
@@ -4781,9 +4788,12 @@ async function validatePublicUrl(url) {
4781
4788
  }
4782
4789
  let addresses;
4783
4790
  try {
4784
- addresses = await lookup(hostname, { all: true });
4791
+ addresses = await dns.lookup(hostname, { all: true });
4785
4792
  } catch (error) {
4786
- throw new UrlError("DNS lookup failed", { cause: error });
4793
+ throw new UrlError("DNS lookup failed", {
4794
+ cause: error,
4795
+ reason: "dns"
4796
+ });
4787
4797
  }
4788
4798
  validateLookupAddresses(addresses);
4789
4799
  }
@@ -4812,7 +4822,7 @@ function validateLookupAddresses(addresses) {
4812
4822
  ipAddressCount++;
4813
4823
  validatePublicIpAddress(address, family);
4814
4824
  }
4815
- if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
4825
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
4816
4826
  }
4817
4827
  function validatePublicIpAddress(address, family) {
4818
4828
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-Dz-c-sU9.mjs";
1
+ import { t as FetchError } from "./request-D5j8WVks.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-Do9eywBl.cjs");
2
- const require_request = require("./request-CYnGwxbv.cjs");
2
+ const require_request = require("./request-dIRBY7QP.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,5 +1,5 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_body = require("./body-BPzg1k_5.cjs");
2
+ const require_body = require("./body-DPMAUTtz.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-D_AlSh8c.mjs";
1
+ import { r as readBoundedText, t as BodyTooLargeError } from "./body-DNFbzsxI.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,8 +1,8 @@
1
1
  const require_chunk = require("./chunk-Do9eywBl.cjs");
2
- const require_request = require("./request-CYnGwxbv.cjs");
3
- const require_body = require("./body-BPzg1k_5.cjs");
2
+ const require_request = require("./request-dIRBY7QP.cjs");
3
+ const require_body = require("./body-DPMAUTtz.cjs");
4
4
  const require_link = require("./link-FguCydMA.cjs");
5
- const require_url = require("./url-CWU5HT3Y.cjs");
5
+ const require_url = require("./url-Qk1svKZU.cjs");
6
6
  let node_test = require("node:test");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,7 +1,7 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-Dz-c-sU9.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-D_AlSh8c.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-D5j8WVks.mjs";
2
+ import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-DNFbzsxI.mjs";
3
3
  import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
4
- import { o as validatePublicUrl, t as UrlError } from "./url-o0e-aXQY.mjs";
4
+ import { o as validatePublicUrl, t as UrlError } from "./url-DzlMS01z.mjs";
5
5
  import { test } from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { getLogger } from "@logtape/logtape";
@@ -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.0.29-dev.1989+6b9d933b";
4
+ var version = "2.0.29-dev.2018+2e67253f";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_chunk.__toESM(node_process);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.0.29-dev.1989+6b9d933b";
6
+ var version = "2.0.29-dev.2018+2e67253f";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Do9eywBl.cjs");
2
- const require_request = require("./request-CYnGwxbv.cjs");
2
+ const require_request = require("./request-dIRBY7QP.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,4 +1,4 @@
1
- import { o as version, r as getUserAgent } from "./request-Dz-c-sU9.mjs";
1
+ import { o as version, r as getUserAgent } from "./request-D5j8WVks.mjs";
2
2
  import { test } from "node:test";
3
3
  import process from "node:process";
4
4
  import { deepStrictEqual } from "node:assert";
@@ -1,10 +1,17 @@
1
- import { lookup } from "node:dns/promises";
1
+ import dns from "node:dns/promises";
2
2
  import { isIP } from "node:net";
3
3
  //#region src/url.ts
4
4
  var UrlError = class extends Error {
5
+ /**
6
+ * The failure category. Defaults to `"disallowed"`.
7
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
8
+ * @since 2.0.29
9
+ */
10
+ reason;
5
11
  constructor(message, options) {
6
12
  super(message, options);
7
13
  this.name = "UrlError";
14
+ this.reason = options?.reason ?? "disallowed";
8
15
  }
9
16
  };
10
17
  /**
@@ -30,9 +37,12 @@ async function validatePublicUrl(url) {
30
37
  }
31
38
  let addresses;
32
39
  try {
33
- addresses = await lookup(hostname, { all: true });
40
+ addresses = await dns.lookup(hostname, { all: true });
34
41
  } catch (error) {
35
- throw new UrlError("DNS lookup failed", { cause: error });
42
+ throw new UrlError("DNS lookup failed", {
43
+ cause: error,
44
+ reason: "dns"
45
+ });
36
46
  }
37
47
  validateLookupAddresses(addresses);
38
48
  }
@@ -61,7 +71,7 @@ function validateLookupAddresses(addresses) {
61
71
  ipAddressCount++;
62
72
  validatePublicIpAddress(address, family);
63
73
  }
64
- if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
74
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
65
75
  }
66
76
  function validatePublicIpAddress(address, family) {
67
77
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
@@ -1,11 +1,19 @@
1
- require("./chunk-Do9eywBl.cjs");
1
+ const require_chunk = require("./chunk-Do9eywBl.cjs");
2
2
  let node_dns_promises = require("node:dns/promises");
3
+ node_dns_promises = require_chunk.__toESM(node_dns_promises);
3
4
  let node_net = require("node:net");
4
5
  //#region src/url.ts
5
6
  var UrlError = class extends Error {
7
+ /**
8
+ * The failure category. Defaults to `"disallowed"`.
9
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
10
+ * @since 2.0.29
11
+ */
12
+ reason;
6
13
  constructor(message, options) {
7
14
  super(message, options);
8
15
  this.name = "UrlError";
16
+ this.reason = options?.reason ?? "disallowed";
9
17
  }
10
18
  };
11
19
  /**
@@ -31,9 +39,12 @@ async function validatePublicUrl(url) {
31
39
  }
32
40
  let addresses;
33
41
  try {
34
- addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
42
+ addresses = await node_dns_promises.default.lookup(hostname, { all: true });
35
43
  } catch (error) {
36
- throw new UrlError("DNS lookup failed", { cause: error });
44
+ throw new UrlError("DNS lookup failed", {
45
+ cause: error,
46
+ reason: "dns"
47
+ });
37
48
  }
38
49
  validateLookupAddresses(addresses);
39
50
  }
@@ -62,7 +73,7 @@ function validateLookupAddresses(addresses) {
62
73
  ipAddressCount++;
63
74
  validatePublicIpAddress(address, family);
64
75
  }
65
- if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
76
+ if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address", { reason: "dns" });
66
77
  }
67
78
  function validatePublicIpAddress(address, family) {
68
79
  if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
@@ -1,8 +1,31 @@
1
1
  require("./chunk-Do9eywBl.cjs");
2
- const require_url = require("./url-CWU5HT3Y.cjs");
2
+ const require_url = require("./url-Qk1svKZU.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_assert = require("node:assert");
5
5
  //#region src/url.test.ts
6
+ (0, node_test.test)("UrlError reason is independent of its cause", () => {
7
+ const cause = /* @__PURE__ */ new Error("Underlying failure");
8
+ (0, node_assert.strictEqual)(new require_url.UrlError("Rejected URL").reason, "disallowed");
9
+ const disallowedError = new require_url.UrlError("Rejected URL", { cause });
10
+ (0, node_assert.strictEqual)(disallowedError.reason, "disallowed");
11
+ (0, node_assert.strictEqual)(disallowedError.cause, cause);
12
+ const dnsError = new require_url.UrlError("Lookup failed", {
13
+ reason: "dns",
14
+ cause
15
+ });
16
+ (0, node_assert.strictEqual)(dnsError.reason, "dns");
17
+ (0, node_assert.strictEqual)(dnsError.cause, cause);
18
+ });
19
+ (0, node_test.test)("validatePublicUrl() classifies disallowed URLs", async () => {
20
+ for (const url of [
21
+ "ftp://example.com",
22
+ "https://localhost",
23
+ "https://127.0.0.1"
24
+ ]) await (0, node_assert.rejects)(() => require_url.validatePublicUrl(url), {
25
+ name: "UrlError",
26
+ reason: "disallowed"
27
+ });
28
+ });
6
29
  (0, node_test.test)("validatePublicUrl()", async () => {
7
30
  await (0, node_assert.rejects)(() => require_url.validatePublicUrl("ftp://localhost"), require_url.UrlError);
8
31
  await (0, node_assert.rejects)(() => require_url.validatePublicUrl("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="), require_url.UrlError);
@@ -52,11 +75,21 @@ let node_assert = require("node:assert");
52
75
  }, {
53
76
  address: "127.0.0.1",
54
77
  family: 4
55
- }]), require_url.UrlError);
78
+ }]), {
79
+ name: "UrlError",
80
+ reason: "disallowed"
81
+ });
56
82
  (0, node_assert.throws)(() => require_url.validateLookupAddresses([{
57
83
  address: "app-host.example.net.",
58
84
  family: 4
59
- }]), require_url.UrlError);
85
+ }]), {
86
+ name: "UrlError",
87
+ reason: "dns"
88
+ });
89
+ (0, node_assert.throws)(() => require_url.validateLookupAddresses([]), {
90
+ name: "UrlError",
91
+ reason: "dns"
92
+ });
60
93
  });
61
94
  (0, node_test.test)("isValidPublicIPv4Address()", () => {
62
95
  (0, node_assert.ok)(require_url.isValidPublicIPv4Address("8.8.8.8"));
@@ -1,7 +1,30 @@
1
- import { a as validateLookupAddresses, i as isValidPublicIPv6Address, n as expandIPv6Address, o as validatePublicUrl, r as isValidPublicIPv4Address, t as UrlError } from "./url-o0e-aXQY.mjs";
1
+ import { a as validateLookupAddresses, i as isValidPublicIPv6Address, n as expandIPv6Address, o as validatePublicUrl, r as isValidPublicIPv4Address, t as UrlError } from "./url-DzlMS01z.mjs";
2
2
  import { test } from "node:test";
3
- import { deepStrictEqual, ok, rejects, throws } from "node:assert";
3
+ import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert";
4
4
  //#region src/url.test.ts
5
+ test("UrlError reason is independent of its cause", () => {
6
+ const cause = /* @__PURE__ */ new Error("Underlying failure");
7
+ strictEqual(new UrlError("Rejected URL").reason, "disallowed");
8
+ const disallowedError = new UrlError("Rejected URL", { cause });
9
+ strictEqual(disallowedError.reason, "disallowed");
10
+ strictEqual(disallowedError.cause, cause);
11
+ const dnsError = new UrlError("Lookup failed", {
12
+ reason: "dns",
13
+ cause
14
+ });
15
+ strictEqual(dnsError.reason, "dns");
16
+ strictEqual(dnsError.cause, cause);
17
+ });
18
+ test("validatePublicUrl() classifies disallowed URLs", async () => {
19
+ for (const url of [
20
+ "ftp://example.com",
21
+ "https://localhost",
22
+ "https://127.0.0.1"
23
+ ]) await rejects(() => validatePublicUrl(url), {
24
+ name: "UrlError",
25
+ reason: "disallowed"
26
+ });
27
+ });
5
28
  test("validatePublicUrl()", async () => {
6
29
  await rejects(() => validatePublicUrl("ftp://localhost"), UrlError);
7
30
  await rejects(() => validatePublicUrl("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="), UrlError);
@@ -51,11 +74,21 @@ test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers
51
74
  }, {
52
75
  address: "127.0.0.1",
53
76
  family: 4
54
- }]), UrlError);
77
+ }]), {
78
+ name: "UrlError",
79
+ reason: "disallowed"
80
+ });
55
81
  throws(() => validateLookupAddresses([{
56
82
  address: "app-host.example.net.",
57
83
  family: 4
58
- }]), UrlError);
84
+ }]), {
85
+ name: "UrlError",
86
+ reason: "dns"
87
+ });
88
+ throws(() => validateLookupAddresses([]), {
89
+ name: "UrlError",
90
+ reason: "dns"
91
+ });
59
92
  });
60
93
  test("isValidPublicIPv4Address()", () => {
61
94
  ok(isValidPublicIPv4Address("8.8.8.8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.0.29-dev.1989+6b9d933b",
3
+ "version": "2.0.29-dev.2018+2e67253f",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
package/src/url.test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { deepStrictEqual, ok, rejects, throws } from "node:assert";
1
+ import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert";
2
2
  import { test } from "node:test";
3
3
  import {
4
4
  expandIPv6Address,
@@ -9,6 +9,28 @@ import {
9
9
  validatePublicUrl,
10
10
  } from "./url.ts";
11
11
 
12
+ test("UrlError reason is independent of its cause", () => {
13
+ const cause = new Error("Underlying failure");
14
+ strictEqual(new UrlError("Rejected URL").reason, "disallowed");
15
+ const disallowedError = new UrlError("Rejected URL", { cause });
16
+ strictEqual(disallowedError.reason, "disallowed");
17
+ strictEqual(disallowedError.cause, cause);
18
+ const dnsError = new UrlError("Lookup failed", { reason: "dns", cause });
19
+ strictEqual(dnsError.reason, "dns");
20
+ strictEqual(dnsError.cause, cause);
21
+ });
22
+
23
+ test("validatePublicUrl() classifies disallowed URLs", async () => {
24
+ for (
25
+ const url of ["ftp://example.com", "https://localhost", "https://127.0.0.1"]
26
+ ) {
27
+ await rejects(() => validatePublicUrl(url), {
28
+ name: "UrlError",
29
+ reason: "disallowed",
30
+ });
31
+ }
32
+ });
33
+
12
34
  test("validatePublicUrl()", async () => {
13
35
  await rejects(() => validatePublicUrl("ftp://localhost"), UrlError);
14
36
  await rejects(
@@ -81,15 +103,19 @@ test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers
81
103
  { address: "private-host.example.net.", family: 4 },
82
104
  { address: "127.0.0.1", family: 4 },
83
105
  ]),
84
- UrlError,
106
+ { name: "UrlError", reason: "disallowed" },
85
107
  );
86
108
  throws(
87
109
  () =>
88
110
  validateLookupAddresses([
89
111
  { address: "app-host.example.net.", family: 4 },
90
112
  ]),
91
- UrlError,
113
+ { name: "UrlError", reason: "dns" },
92
114
  );
115
+ throws(() => validateLookupAddresses([]), {
116
+ name: "UrlError",
117
+ reason: "dns",
118
+ });
93
119
  });
94
120
 
95
121
  test("isValidPublicIPv4Address()", () => {
package/src/url.ts CHANGED
@@ -1,11 +1,25 @@
1
1
  import type { LookupAddress } from "node:dns";
2
- import { lookup } from "node:dns/promises";
2
+ // FIXME: the default dns import exists since tests can stub `dns.lookup()`.
3
+ // This should be replaced by injecting the lookup function such as an internal
4
+ // option on `validatePublicUrl()`
5
+ import dns from "node:dns/promises";
3
6
  import { isIP } from "node:net";
4
7
 
5
8
  export class UrlError extends Error {
6
- constructor(message: string, options?: { cause?: unknown }) {
9
+ /**
10
+ * The failure category. Defaults to `"disallowed"`.
11
+ * `"dns"` includes lookup errors and results with no usable IP addresses.
12
+ * @since 2.0.29
13
+ */
14
+ readonly reason: "dns" | "disallowed";
15
+
16
+ constructor(
17
+ message: string,
18
+ options?: { cause?: unknown; reason?: "dns" | "disallowed" },
19
+ ) {
7
20
  super(message, options);
8
21
  this.name = "UrlError";
22
+ this.reason = options?.reason ?? "disallowed";
9
23
  }
10
24
  }
11
25
 
@@ -50,9 +64,9 @@ export async function validatePublicUrl(url: string): Promise<void> {
50
64
  // and ensure that they are all public:
51
65
  let addresses: LookupAddress[];
52
66
  try {
53
- addresses = await lookup(hostname, { all: true });
67
+ addresses = await dns.lookup(hostname, { all: true });
54
68
  } catch (error) {
55
- throw new UrlError("DNS lookup failed", { cause: error });
69
+ throw new UrlError("DNS lookup failed", { cause: error, reason: "dns" });
56
70
  }
57
71
  validateLookupAddresses(addresses);
58
72
  }
@@ -85,7 +99,9 @@ export function validateLookupAddresses(
85
99
  validatePublicIpAddress(address, family);
86
100
  }
87
101
  if (ipAddressCount === 0) {
88
- throw new UrlError("DNS lookup did not return any IP address");
102
+ throw new UrlError("DNS lookup did not return any IP address", {
103
+ reason: "dns",
104
+ });
89
105
  }
90
106
  }
91
107