@fedify/vocab-runtime 2.4.0-dev.1567 → 2.4.0-dev.1570

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.
Files changed (48) hide show
  1. package/deno.json +1 -2
  2. package/dist/mod.cjs +190 -178
  3. package/dist/mod.d.cts +190 -59
  4. package/dist/mod.d.ts +190 -59
  5. package/dist/mod.js +184 -165
  6. package/dist/tests/decimal.test.cjs +3 -3
  7. package/dist/tests/decimal.test.mjs +3 -3
  8. package/dist/tests/{docloader-D61oV0K1.mjs → docloader-C76ldE5C.mjs} +10 -97
  9. package/dist/tests/{docloader-BcaQHb0Q.cjs → docloader-mvgIWKI7.cjs} +9 -102
  10. package/dist/tests/docloader.test.cjs +6 -58
  11. package/dist/tests/docloader.test.mjs +6 -58
  12. package/dist/tests/{key-CDGDH_vC.mjs → key-CrrK9mYh.mjs} +1 -69
  13. package/dist/tests/{key-_wXwomh_.cjs → key-pMmqUKuo.cjs} +0 -86
  14. package/dist/tests/key.test.cjs +1 -48
  15. package/dist/tests/key.test.mjs +1 -48
  16. package/dist/tests/{request-Cn0vP7Hj.mjs → request-BEXkv1ul.mjs} +1 -1
  17. package/dist/tests/{request-DnNWah97.cjs → request-SworbvLc.cjs} +1 -1
  18. package/dist/tests/request.test.cjs +1 -1
  19. package/dist/tests/request.test.mjs +1 -1
  20. package/dist/tests/{url-2XwVbUS_.cjs → url-C20FhC7p.cjs} +0 -108
  21. package/dist/tests/{url-YWJbnRlf.mjs → url-m9Qzxy-Y.mjs} +1 -85
  22. package/dist/tests/url.test.cjs +1 -104
  23. package/dist/tests/url.test.mjs +2 -105
  24. package/package.json +1 -11
  25. package/src/contexts.ts +0 -2
  26. package/src/docloader.test.ts +6 -102
  27. package/src/docloader.ts +8 -76
  28. package/src/key.test.ts +0 -86
  29. package/src/key.ts +0 -125
  30. package/src/mod.ts +0 -8
  31. package/src/url.test.ts +1 -252
  32. package/src/url.ts +0 -141
  33. package/tsdown.config.ts +1 -6
  34. package/dist/docloader-DnUMWHaJ.d.cts +0 -202
  35. package/dist/docloader-xRGn1azD.d.ts +0 -202
  36. package/dist/internal/jsonld-cache.cjs +0 -279
  37. package/dist/internal/jsonld-cache.d.cts +0 -48
  38. package/dist/internal/jsonld-cache.d.ts +0 -48
  39. package/dist/internal/jsonld-cache.js +0 -275
  40. package/dist/tests/jsonld-cache.test.cjs +0 -652
  41. package/dist/tests/jsonld-cache.test.d.cts +0 -1
  42. package/dist/tests/jsonld-cache.test.d.mts +0 -1
  43. package/dist/tests/jsonld-cache.test.mjs +0 -651
  44. package/dist/url-BAdyyqAa.cjs +0 -315
  45. package/dist/url-BuxPHxK2.js +0 -261
  46. package/src/contexts/fep-7aa9.json +0 -24
  47. package/src/internal/jsonld-cache.ts +0 -538
  48. package/src/jsonld-cache.test.ts +0 -554
@@ -2,7 +2,7 @@ import fetchMock from "fetch-mock";
2
2
  import { deepStrictEqual, ok, rejects } from "node:assert";
3
3
  import { test } from "node:test";
4
4
  import preloadedContexts from "./contexts.ts";
5
- import { getDocumentLoader, getRemoteDocument } from "./docloader.ts";
5
+ import { getDocumentLoader } from "./docloader.ts";
6
6
  import { FetchError } from "./request.ts";
7
7
  import { UrlError } from "./url.ts";
8
8
 
@@ -90,7 +90,7 @@ test("getDocumentLoader()", async (t) => {
90
90
  type: "Object",
91
91
  },
92
92
  headers: {
93
- "Content-Type": "application/activity+json",
93
+ "Content-Type": "text/html; charset=utf-8",
94
94
  Link: '<https://example.com/object>; rel="alternate"; ' +
95
95
  'type="application/ld+json; profile="https://www.w3.org/ns/activitystreams""',
96
96
  },
@@ -247,44 +247,6 @@ test("getDocumentLoader()", async (t) => {
247
247
  });
248
248
  });
249
249
 
250
- fetchMock.get("https://example.com/html-no-alternate", {
251
- body: `<!DOCTYPE html>
252
- <html>
253
- <head>
254
- <title>Not an ActivityPub document</title>
255
- </head>
256
- <body>Not found</body>
257
- </html>`,
258
- headers: { "Content-Type": "text/html; charset=utf-8" },
259
- });
260
-
261
- await t.test("HTML without ActivityPub alternate link", async () => {
262
- await rejects(
263
- () => fetchDocumentLoader("https://example.com/html-no-alternate"),
264
- (error) => {
265
- ok(error instanceof FetchError);
266
- ok(
267
- error.message.includes(
268
- "HTML document has no ActivityPub alternate link",
269
- ),
270
- );
271
- ok(
272
- error.message.includes("Content-Type: text/html; charset=utf-8"),
273
- );
274
- deepStrictEqual(
275
- error.url,
276
- new URL("https://example.com/html-no-alternate"),
277
- );
278
- ok(error.response != null);
279
- deepStrictEqual(
280
- error.response.headers.get("Content-Type"),
281
- "text/html; charset=utf-8",
282
- );
283
- return true;
284
- },
285
- );
286
- });
287
-
288
250
  fetchMock.get("https://example.com/wrong-content-type", {
289
251
  body: {
290
252
  "@context": "https://www.w3.org/ns/activitystreams",
@@ -295,7 +257,7 @@ test("getDocumentLoader()", async (t) => {
295
257
  headers: { "Content-Type": "text/html; charset=utf-8" },
296
258
  });
297
259
 
298
- await t.test("wrong Content-Type with JSON body", async () => {
260
+ await t.test("Wrong Content-Type", async () => {
299
261
  deepStrictEqual(
300
262
  await fetchDocumentLoader("https://example.com/wrong-content-type"),
301
263
  {
@@ -311,64 +273,6 @@ test("getDocumentLoader()", async (t) => {
311
273
  );
312
274
  });
313
275
 
314
- fetchMock.get("https://example.com/large-html", {
315
- body: "<!DOCTYPE html>",
316
- headers: {
317
- "Content-Length": String(1024 * 1024 + 1),
318
- "Content-Type": "text/html; charset=utf-8",
319
- },
320
- });
321
-
322
- await t.test("HTML Content-Length over limit", async () => {
323
- await rejects(
324
- () => fetchDocumentLoader("https://example.com/large-html"),
325
- (error) => {
326
- ok(error instanceof FetchError);
327
- ok(
328
- error.message.includes(
329
- "HTML document is too large to scan for an ActivityPub alternate link",
330
- ),
331
- );
332
- ok(error.response != null);
333
- deepStrictEqual(error.response.status, 200);
334
- deepStrictEqual(
335
- error.response.headers.get("Content-Type"),
336
- "text/html; charset=utf-8",
337
- );
338
- return true;
339
- },
340
- );
341
- });
342
-
343
- await t.test("HTML Content-Length over limit cancels body", async () => {
344
- let canceled = false;
345
- const response = new Response("<!DOCTYPE html>", {
346
- headers: {
347
- "Content-Length": String(1024 * 1024 + 1),
348
- "Content-Type": "text/html; charset=utf-8",
349
- },
350
- });
351
- Object.defineProperty(response, "body", {
352
- value: {
353
- cancel: () => {
354
- canceled = true;
355
- },
356
- },
357
- });
358
- await rejects(
359
- () =>
360
- getRemoteDocument(
361
- "https://example.com/large-html-cancel",
362
- response,
363
- () => {
364
- throw new Error("unexpected alternate fetch");
365
- },
366
- ),
367
- FetchError,
368
- );
369
- deepStrictEqual(canceled, true);
370
- });
371
-
372
276
  fetchMock.get("https://example.com/404", { status: 404 });
373
277
 
374
278
  await t.test("not ok", async () => {
@@ -555,11 +459,11 @@ test("getDocumentLoader()", async (t) => {
555
459
 
556
460
  await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
557
461
  const start = performance.now();
558
- // The malicious HTML will fail alternate discovery, but the important
559
- // thing is that it should complete quickly (not hang due to ReDoS).
462
+ // The malicious HTML will fail JSON parsing, but the important thing is
463
+ // that it should complete quickly (not hang due to ReDoS)
560
464
  await rejects(
561
465
  () => fetchDocumentLoader("https://example.com/redos"),
562
- FetchError,
466
+ SyntaxError,
563
467
  );
564
468
  const elapsed = performance.now() - start;
565
469
 
package/src/docloader.ts CHANGED
@@ -13,7 +13,6 @@ import { UrlError, validatePublicUrl } from "./url.ts";
13
13
 
14
14
  const logger = getLogger(["fedify", "runtime", "docloader"]);
15
15
  const DEFAULT_MAX_REDIRECTION = 20;
16
- const MAX_HTML_SIZE = 1024 * 1024; // 1MB
17
16
 
18
17
  /**
19
18
  * A remote JSON-LD document and its context fetched by
@@ -113,59 +112,6 @@ export type AuthenticatedDocumentLoaderFactory = (
113
112
  options?: DocumentLoaderFactoryOptions,
114
113
  ) => DocumentLoader;
115
114
 
116
- function createResponseMetadata(response: Response): Response {
117
- return new Response(null, {
118
- headers: response.headers,
119
- status: response.status,
120
- statusText: response.statusText,
121
- });
122
- }
123
-
124
- async function cancelResponseBody(response: Response): Promise<void> {
125
- if (response.body != null) {
126
- await response.body.cancel();
127
- }
128
- }
129
-
130
- async function readBoundedText(
131
- response: Response,
132
- maxBytes: number,
133
- ): Promise<{ text: string; size: number; tooLarge: boolean }> {
134
- const contentLength = response.headers.get("Content-Length");
135
- if (contentLength != null) {
136
- const size = Number(contentLength);
137
- if (size > maxBytes) {
138
- await cancelResponseBody(response);
139
- return { text: "", size, tooLarge: true };
140
- }
141
- }
142
-
143
- if (response.body == null) return { text: "", size: 0, tooLarge: false };
144
-
145
- const reader = response.body.getReader();
146
- const decoder = new TextDecoder();
147
- let text = "";
148
- let size = 0;
149
- try {
150
- while (true) {
151
- const result = await reader.read();
152
- if (result.done) break;
153
- const chunkSize = result.value.byteLength;
154
- if (size + chunkSize > maxBytes) {
155
- size += chunkSize;
156
- await reader.cancel();
157
- return { text: "", size, tooLarge: true };
158
- }
159
- size += chunkSize;
160
- text += decoder.decode(result.value, { stream: true });
161
- }
162
- text += decoder.decode();
163
- return { text, size, tooLarge: false };
164
- } finally {
165
- reader.releaseLock();
166
- }
167
- }
168
-
169
115
  /**
170
116
  * Gets a {@link RemoteDocument} from the given response.
171
117
  * @param url The URL of the document to load.
@@ -254,19 +200,15 @@ export async function getRemoteDocument(
254
200
  contentType === "application/xhtml+xml" ||
255
201
  contentType?.startsWith("application/xhtml+xml;"))
256
202
  ) {
257
- const errorResponse = createResponseMetadata(response);
258
- const html = await readBoundedText(response, MAX_HTML_SIZE);
259
- if (html.tooLarge) {
203
+ // Security: Limit HTML response size to mitigate ReDoS attacks
204
+ const MAX_HTML_SIZE = 1024 * 1024; // 1MB
205
+ const html = await response.text();
206
+ if (html.length > MAX_HTML_SIZE) {
260
207
  logger.warn(
261
208
  "HTML response too large, skipping alternate link discovery: {url}",
262
- { url: documentUrl, size: html.size },
263
- );
264
- throw new FetchError(
265
- documentUrl,
266
- `HTML document is too large to scan for an ActivityPub alternate link ` +
267
- `(Content-Type: ${contentType})`,
268
- errorResponse,
209
+ { url: documentUrl, size: html.length },
269
210
  );
211
+ document = JSON.parse(html);
270
212
  } else {
271
213
  // Safe regex patterns without nested quantifiers to prevent ReDoS
272
214
  // (CVE-2025-68475)
@@ -277,7 +219,7 @@ export async function getRemoteDocument(
277
219
  /([a-z][a-z:_-]*)=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
278
220
 
279
221
  let tagMatch: RegExpExecArray | null;
280
- while ((tagMatch = tagPattern.exec(html.text)) !== null) {
222
+ while ((tagMatch = tagPattern.exec(html)) !== null) {
281
223
  const tagContent = tagMatch[2];
282
224
  let attrMatch: RegExpExecArray | null;
283
225
  const attribs: Record<string, string> = {};
@@ -305,17 +247,7 @@ export async function getRemoteDocument(
305
247
  return await fetch(new URL(attribs.href, docUrl).href);
306
248
  }
307
249
  }
308
- try {
309
- document = JSON.parse(html.text);
310
- } catch (error) {
311
- if (!(error instanceof SyntaxError)) throw error;
312
- throw new FetchError(
313
- documentUrl,
314
- `HTML document has no ActivityPub alternate link ` +
315
- `(Content-Type: ${contentType})`,
316
- errorResponse,
317
- );
318
- }
250
+ document = JSON.parse(html);
319
251
  }
320
252
  } else {
321
253
  document = await response.json();
package/src/key.test.ts CHANGED
@@ -2,17 +2,13 @@ import { deepStrictEqual, rejects } from "node:assert";
2
2
  import { test } from "node:test";
3
3
  import { exportJwk, importJwk } from "./jwk.ts";
4
4
  import {
5
- exportDidKey,
6
5
  exportMultibaseKey,
7
6
  exportSpki,
8
- importDidKey,
9
7
  importMultibaseKey,
10
8
  importPem,
11
9
  importPkcs1,
12
10
  importSpki,
13
- parseDidKeyVerificationMethod,
14
11
  } from "./key.ts";
15
- import { addMulticodecPrefix } from "./internal/multicodec.ts";
16
12
  import { encodeMultibase } from "./multibase/mod.ts";
17
13
 
18
14
  // cSpell: disable
@@ -202,85 +198,3 @@ test("exportMultibaseKey()", async () => {
202
198
  // cSpell: enable
203
199
  );
204
200
  });
205
-
206
- test("exportDidKey() and importDidKey()", async () => {
207
- const ed25519Key = await importJwk(ed25519Jwk, "public");
208
- const did = await exportDidKey(ed25519Key);
209
- deepStrictEqual(did, `did:key:${ed25519Multibase}`);
210
- deepStrictEqual(await exportJwk(await importDidKey(did)), ed25519Jwk);
211
- deepStrictEqual(
212
- await exportJwk(await importDidKey(new URL(did))),
213
- ed25519Jwk,
214
- );
215
-
216
- const rsaKey = await importJwk(rsaJwk, "public");
217
- await rejects(
218
- () => exportDidKey(rsaKey),
219
- TypeError,
220
- );
221
- await rejects(
222
- () => importDidKey(`did:key:${rsaMultibase}`),
223
- new TypeError("Unsupported did:key type: 0x1205"),
224
- );
225
- });
226
-
227
- test("parseDidKeyVerificationMethod()", async () => {
228
- const verificationMethod = `did:key:${ed25519Multibase}#${ed25519Multibase}`;
229
- const parsed = await parseDidKeyVerificationMethod(verificationMethod);
230
- deepStrictEqual(parsed.id, new URL(verificationMethod));
231
- deepStrictEqual(parsed.controller, new URL(`did:key:${ed25519Multibase}`));
232
- deepStrictEqual(parsed.publicKeyMultibase, ed25519Multibase);
233
- deepStrictEqual(await exportJwk(parsed.publicKey), ed25519Jwk);
234
- });
235
-
236
- test("did:key helpers reject malformed values", async () => {
237
- const ed25519Key = await importJwk(ed25519Jwk, "public");
238
- const raw = new Uint8Array(await crypto.subtle.exportKey("raw", ed25519Key));
239
- const shortEd25519 = new TextDecoder().decode(
240
- encodeMultibase(
241
- "base58btc",
242
- addMulticodecPrefix(0xed, raw.slice(0, 31)),
243
- ),
244
- );
245
- const base64UrlEd25519 = new TextDecoder().decode(
246
- encodeMultibase(
247
- "base64url",
248
- addMulticodecPrefix(0xed, raw),
249
- ),
250
- );
251
- const malformedMulticodec = new TextDecoder().decode(
252
- encodeMultibase("base58btc", Uint8Array.from([0x80])),
253
- );
254
-
255
- for (
256
- const value of [
257
- "did:web:example.com",
258
- "did:key:",
259
- `did:key:${ed25519Multibase}/path`,
260
- `did:key:${ed25519Multibase}?query`,
261
- `did:key:${ed25519Multibase}#${ed25519Multibase}`,
262
- "did:key:z0",
263
- `did:key:${base64UrlEd25519}`,
264
- `did:key:${shortEd25519}`,
265
- `did:key:${malformedMulticodec}`,
266
- ]
267
- ) {
268
- await rejects(() => importDidKey(value), TypeError);
269
- }
270
-
271
- for (
272
- const value of [
273
- `did:key:${ed25519Multibase}`,
274
- `did:key:${ed25519Multibase}#`,
275
- `did:key:${ed25519Multibase}#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK`,
276
- `did:key:${ed25519Multibase}#${base64UrlEd25519}`,
277
- "did:key:z0#z0",
278
- `did:key:${rsaMultibase}#${rsaMultibase}`,
279
- `did:key:${shortEd25519}#${shortEd25519}`,
280
- `did:key:${malformedMulticodec}#${malformedMulticodec}`,
281
- "https://example.com/key",
282
- ]
283
- ) {
284
- await rejects(() => parseDidKeyVerificationMethod(value), TypeError);
285
- }
286
- });
package/src/key.ts CHANGED
@@ -22,39 +22,6 @@ const algorithms: Record<
22
22
  "1.3.101.112": "Ed25519",
23
23
  };
24
24
 
25
- const DID_KEY_PREFIX = "did:key:";
26
- const ED25519_PUBLIC_KEY_MULTICODEC = 0xed;
27
- const ED25519_PUBLIC_KEY_LENGTH = 32;
28
- const DID_KEY_PATTERN = /^did:key:([^/?#]+)$/;
29
- const DID_KEY_VERIFICATION_METHOD_PATTERN = /^did:key:([^/?#]+)#([^/?#]+)$/;
30
-
31
- /**
32
- * Parsed `did:key` verification method.
33
- *
34
- * @since 2.4.0
35
- */
36
- export interface DidKeyVerificationMethod {
37
- /**
38
- * The DID URL identifying the verification method.
39
- */
40
- readonly id: URL;
41
-
42
- /**
43
- * The controller DID.
44
- */
45
- readonly controller: URL;
46
-
47
- /**
48
- * The Ed25519 public key encoded as a Multibase Multikey value.
49
- */
50
- readonly publicKeyMultibase: string;
51
-
52
- /**
53
- * The Ed25519 public key.
54
- */
55
- readonly publicKey: CryptoKey;
56
- }
57
-
58
25
  /**
59
26
  * Imports a PEM-SPKI formatted public key.
60
27
  * @param pem The PEM-SPKI formatted public key.
@@ -126,98 +93,6 @@ export function importPem(pem: string): Promise<CryptoKey> {
126
93
  return PKCS1_HEADER.test(pem) ? importPkcs1(pem) : importSpki(pem);
127
94
  }
128
95
 
129
- function decodeEd25519DidKeyMultibase(multibaseKey: string): Uint8Array {
130
- if (!multibaseKey.startsWith("z")) {
131
- throw new TypeError("did:key must use base58-btc Multibase encoding.");
132
- }
133
- let decoded: Uint8Array;
134
- try {
135
- decoded = decodeMultibase(multibaseKey);
136
- } catch (error) {
137
- throw new TypeError("Invalid did:key Multibase encoding.", {
138
- cause: error,
139
- });
140
- }
141
- const { code } = getMulticodecPrefix(decoded);
142
- if (code !== ED25519_PUBLIC_KEY_MULTICODEC) {
143
- throw new TypeError("Unsupported did:key type: 0x" + code.toString(16));
144
- }
145
- const content = removeMulticodecPrefix(decoded);
146
- if (content.length !== ED25519_PUBLIC_KEY_LENGTH) {
147
- throw new TypeError("Invalid Ed25519 did:key length.");
148
- }
149
- return content;
150
- }
151
-
152
- /**
153
- * Imports an Ed25519 `did:key` DID.
154
- *
155
- * @param did The `did:key` DID.
156
- * @returns The imported Ed25519 public key.
157
- * @throws {TypeError} If the DID is malformed or uses an unsupported key type.
158
- * @since 2.4.0
159
- */
160
- export async function importDidKey(did: string | URL): Promise<CryptoKey> {
161
- const didString = did instanceof URL ? did.href : did;
162
- const match = didString.match(DID_KEY_PATTERN);
163
- if (match == null) throw new TypeError("Invalid did:key DID.");
164
- const content = decodeEd25519DidKeyMultibase(match[1]);
165
- return await crypto.subtle.importKey(
166
- "raw",
167
- content.slice(),
168
- "Ed25519",
169
- true,
170
- ["verify"],
171
- );
172
- }
173
-
174
- /**
175
- * Exports an Ed25519 public key as a `did:key` DID.
176
- *
177
- * @param key The Ed25519 public key.
178
- * @returns The `did:key` DID.
179
- * @throws {TypeError} If the key is invalid or unsupported.
180
- * @since 2.4.0
181
- */
182
- export async function exportDidKey(key: CryptoKey): Promise<string> {
183
- if (key.algorithm.name !== "Ed25519") {
184
- throw new TypeError(
185
- "Unsupported key type: " + JSON.stringify(key.algorithm),
186
- );
187
- }
188
- return DID_KEY_PREFIX + await exportMultibaseKey(key);
189
- }
190
-
191
- /**
192
- * Parses an Ed25519 `did:key` verification method DID URL.
193
- *
194
- * @param didUrl The `did:key` DID URL.
195
- * @returns The parsed verification method.
196
- * @throws {TypeError} If the DID URL is malformed, unsupported, or its
197
- * fragment does not identify the same key as the DID.
198
- * @since 2.4.0
199
- */
200
- export async function parseDidKeyVerificationMethod(
201
- didUrl: string | URL,
202
- ): Promise<DidKeyVerificationMethod> {
203
- const didUrlString = didUrl instanceof URL ? didUrl.href : didUrl;
204
- const match = didUrlString.match(DID_KEY_VERIFICATION_METHOD_PATTERN);
205
- if (match == null) {
206
- throw new TypeError("Invalid did:key verification method.");
207
- }
208
- const [, publicKeyMultibase, fragment] = match;
209
- if (publicKeyMultibase !== fragment) {
210
- throw new TypeError("Invalid did:key verification method fragment.");
211
- }
212
- const publicKey = await importDidKey(DID_KEY_PREFIX + publicKeyMultibase);
213
- return {
214
- id: new URL(didUrlString),
215
- controller: new URL(DID_KEY_PREFIX + publicKeyMultibase),
216
- publicKeyMultibase,
217
- publicKey,
218
- };
219
- }
220
-
221
96
  /**
222
97
  * Imports a [Multibase]-encoded public key.
223
98
  *
package/src/mod.ts CHANGED
@@ -17,16 +17,12 @@ export {
17
17
  type RemoteDocument,
18
18
  } from "./docloader.ts";
19
19
  export {
20
- type DidKeyVerificationMethod,
21
- exportDidKey,
22
20
  exportMultibaseKey,
23
21
  exportSpki,
24
- importDidKey,
25
22
  importMultibaseKey,
26
23
  importPem,
27
24
  importPkcs1,
28
25
  importSpki,
29
- parseDidKeyVerificationMethod,
30
26
  } from "./key.ts";
31
27
  export {
32
28
  canParseDecimal,
@@ -55,12 +51,8 @@ export {
55
51
  } from "./preprocessor.ts";
56
52
  export {
57
53
  expandIPv6Address,
58
- formatIri,
59
- haveSameIriOrigin,
60
54
  isValidPublicIPv4Address,
61
55
  isValidPublicIPv6Address,
62
- parseIri,
63
- parseJsonLdId,
64
56
  UrlError,
65
57
  validatePublicUrl,
66
58
  } from "./url.ts";