🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@fedify/vocab-runtime

Package Overview
Dependencies
Maintainers
3
Versions
312
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fedify/vocab-runtime - npm Package Compare versions

Comparing version
2.1.18
to
2.1.19
dist/tests/docloader-BPX9K2hq.cjs

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

+76
import process from "node:process";
//#region deno.json
var name = "@fedify/vocab-runtime";
var version = "2.1.19";
//#endregion
//#region src/request.ts
/**
* Error thrown when fetching a JSON-LD document failed.
*/
var FetchError = class extends Error {
/**
* The URL that failed to fetch.
*/
url;
/**
* The HTTP response that failed, if available.
*/
response;
/**
* Constructs a new `FetchError`.
*
* @param url The URL that failed to fetch.
* @param message Error message.
* @param response The failed HTTP response, if available.
*/
constructor(url, message, response) {
super(message == null ? url.toString() : `${url}: ${message}`);
this.name = "FetchError";
this.url = typeof url === "string" ? new URL(url) : url;
this.response = response;
}
};
/**
* Creates a request for the given URL.
* @param url The URL to create the request for.
* @param options The options for the request.
* @returns The created request.
* @internal
*/
function createActivityPubRequest(url, options = {}) {
return new Request(url, {
headers: {
Accept: "application/activity+json, application/ld+json",
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
},
redirect: "manual"
});
}
/**
* Gets the user agent string for the given application and URL.
* @param options The options for making the user agent string.
* @returns The user agent string.
* @since 1.3.0
*/
function getUserAgent({ software, url } = {}) {
const fedify = `Fedify/${version}`;
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${process.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${process.versions.node}` : null;
const userAgent = software == null ? [fedify] : [software, fedify];
if (runtime != null) userAgent.push(runtime);
if (url != null) userAgent.push(`+${url.toString()}`);
return `${userAgent.shift()} (${userAgent.join("; ")})`;
}
/**
* Logs the request.
* @param request The request to log.
* @internal
*/
function logRequest(logger, request) {
logger.debug("Fetching document: {method} {url} {headers}", {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries())
});
}
//#endregion
export { name as a, logRequest as i, createActivityPubRequest as n, version as o, getUserAgent as r, FetchError as t };
const require_chunk = require("./chunk-Do9eywBl.cjs");
let node_process = require("node:process");
node_process = require_chunk.__toESM(node_process);
//#region deno.json
var name = "@fedify/vocab-runtime";
var version = "2.1.19";
//#endregion
//#region src/request.ts
/**
* Error thrown when fetching a JSON-LD document failed.
*/
var FetchError = class extends Error {
/**
* The URL that failed to fetch.
*/
url;
/**
* The HTTP response that failed, if available.
*/
response;
/**
* Constructs a new `FetchError`.
*
* @param url The URL that failed to fetch.
* @param message Error message.
* @param response The failed HTTP response, if available.
*/
constructor(url, message, response) {
super(message == null ? url.toString() : `${url}: ${message}`);
this.name = "FetchError";
this.url = typeof url === "string" ? new URL(url) : url;
this.response = response;
}
};
/**
* Creates a request for the given URL.
* @param url The URL to create the request for.
* @param options The options for the request.
* @returns The created request.
* @internal
*/
function createActivityPubRequest(url, options = {}) {
return new Request(url, {
headers: {
Accept: "application/activity+json, application/ld+json",
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
},
redirect: "manual"
});
}
/**
* Gets the user agent string for the given application and URL.
* @param options The options for making the user agent string.
* @returns The user agent string.
* @since 1.3.0
*/
function getUserAgent({ software, url } = {}) {
const fedify = `Fedify/${version}`;
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
const userAgent = software == null ? [fedify] : [software, fedify];
if (runtime != null) userAgent.push(runtime);
if (url != null) userAgent.push(`+${url.toString()}`);
return `${userAgent.shift()} (${userAgent.join("; ")})`;
}
/**
* Logs the request.
* @param request The request to log.
* @internal
*/
function logRequest(logger, request) {
logger.debug("Fetching document: {method} {url} {headers}", {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries())
});
}
//#endregion
Object.defineProperty(exports, "FetchError", {
enumerable: true,
get: function() {
return FetchError;
}
});
Object.defineProperty(exports, "createActivityPubRequest", {
enumerable: true,
get: function() {
return createActivityPubRequest;
}
});
Object.defineProperty(exports, "getUserAgent", {
enumerable: true,
get: function() {
return getUserAgent;
}
});
Object.defineProperty(exports, "logRequest", {
enumerable: true,
get: function() {
return logRequest;
}
});
Object.defineProperty(exports, "name", {
enumerable: true,
get: function() {
return name;
}
});
Object.defineProperty(exports, "version", {
enumerable: true,
get: function() {
return version;
}
});
require("./chunk-Do9eywBl.cjs");
let node_dns_promises = require("node:dns/promises");
let node_net = require("node:net");
//#region src/url.ts
var UrlError = class extends Error {
constructor(message, options) {
super(message, options);
this.name = "UrlError";
}
};
/**
* Validates a URL to prevent SSRF attacks.
*/
async function validatePublicUrl(url) {
const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
let hostname = parsed.hostname;
if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
const hostnameFamily = (0, node_net.isIP)(hostname);
if (hostnameFamily !== 0) {
validatePublicIpAddress(hostname, hostnameFamily);
return;
}
if ("Deno" in globalThis && !(0, node_net.isIP)(hostname)) {
if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
}
if ("Bun" in globalThis) {
if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
}
let addresses;
try {
addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
} catch (error) {
throw new UrlError("DNS lookup failed", { cause: error });
}
validateLookupAddresses(addresses);
}
/**
* Validates the IP addresses returned by `node:dns.lookup()`.
*
* Cloudflare Workers' `node:dns` implementation currently maps every record
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
* `LookupAddress`, even though Node.js specifies that the `address` field must
* contain an IPv4 or IPv6 literal. See:
* https://github.com/cloudflare/workerd/issues/6886
*
* Work around that bug by ignoring non-IP entries only when the lookup also
* returns at least one actual IP address. This remains fail-closed: a result
* containing no IP addresses is rejected, and every returned IP address is
* still validated and must be public. This workaround can be revisited once
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
*
* @internal
*/
function validateLookupAddresses(addresses) {
let ipAddressCount = 0;
for (const { address } of addresses) {
const family = (0, node_net.isIP)(address);
if (family === 0) continue;
ipAddressCount++;
validatePublicIpAddress(address, family);
}
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
}
function validatePublicIpAddress(address, family) {
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
throw new UrlError(`Invalid or private address: ${address}`);
}
function isValidPublicIPv4Address(address) {
const parts = parseIPv4Address(address);
if (parts == null) return false;
const value = ipv4PartsToNumber(parts);
return !nonPublicIPv4Prefixes.some(({ base, prefix }) => matchesIPv4Prefix(value, base, prefix));
}
function isValidPublicIPv6Address(address) {
const words = parseIPv6Address(address);
if (words == null) return false;
if (nonPublicIPv6Prefixes.some(({ words: prefixWords, prefix }) => matchesIPv6Prefix(words, prefixWords, prefix))) return false;
for (const { extractIPv4, prefix, words: prefixWords } of ipv6WithIPv4Prefixes) {
if (!matchesIPv6Prefix(words, prefixWords, prefix)) continue;
const ipv4Address = extractIPv4(words);
if (ipv4Address != null && !isValidPublicIPv4Address(ipv4Address)) return false;
}
return true;
}
function expandIPv6Address(address) {
address = address.toLowerCase();
const ipv4Delimiter = address.lastIndexOf(":");
if (address.includes(".") && ipv4Delimiter >= 0) {
const ipv4Parts = parseIPv4Address(address.substring(ipv4Delimiter + 1));
if (ipv4Parts == null) return address;
const high = (ipv4Parts[0] << 8) + ipv4Parts[1];
const low = (ipv4Parts[2] << 8) + ipv4Parts[3];
address = address.substring(0, ipv4Delimiter + 1) + high.toString(16) + ":" + low.toString(16);
}
if (address === "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
if (address.startsWith("::")) address = "0000" + address;
if (address.endsWith("::")) address = address + "0000";
address = address.replace("::", ":0000".repeat(8 - (address.match(/:/g) || []).length) + ":");
return address.split(":").map((part) => part.padStart(4, "0")).join(":");
}
const nonPublicIPv4Prefixes = [
ipv4Prefix("0.0.0.0/8", "RFC 6890"),
ipv4Prefix("10.0.0.0/8", "RFC 1918"),
ipv4Prefix("100.64.0.0/10", "RFC 6598"),
ipv4Prefix("127.0.0.0/8", "RFC 1122"),
ipv4Prefix("169.254.0.0/16", "RFC 3927"),
ipv4Prefix("172.16.0.0/12", "RFC 1918"),
ipv4Prefix("192.0.0.0/24", "RFC 6890"),
ipv4Prefix("192.0.2.0/24", "RFC 5737"),
ipv4Prefix("192.88.99.0/24", "RFC 7526"),
ipv4Prefix("192.168.0.0/16", "RFC 1918"),
ipv4Prefix("198.18.0.0/15", "RFC 2544"),
ipv4Prefix("198.51.100.0/24", "RFC 5737"),
ipv4Prefix("203.0.113.0/24", "RFC 5737"),
ipv4Prefix("224.0.0.0/4", "RFC 5771"),
ipv4Prefix("240.0.0.0/4", "RFC 1112")
];
const nonPublicIPv6Prefixes = [
ipv6Prefix("::/16", "RFC 4291"),
ipv6Prefix("2001::/32", "RFC 4380"),
ipv6Prefix("2002::/16", "RFC 3056"),
ipv6Prefix("64:ff9b:1::/48", "RFC 8215"),
ipv6Prefix("fc00::/7", "RFC 4193"),
ipv6Prefix("fe80::/10", "RFC 4291"),
ipv6Prefix("ff00::/8", "RFC 4291")
];
const ipv6WithIPv4Prefixes = [{
...ipv6Prefix("64:ff9b::/96", "RFC 6052"),
extractIPv4: (words) => ipv4FromWords(words[6], words[7])
}];
function ipv4Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const parts = parseIPv4Address(address);
if (parts == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) throw new Error(`Invalid IPv4 prefix: ${cidr}`);
return {
cidr,
base: ipv4PartsToNumber(parts),
prefix,
rfc
};
}
function ipv6Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const words = parseIPv6Address(address);
if (words == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 128) throw new Error(`Invalid IPv6 prefix: ${cidr}`);
return {
cidr,
words,
prefix,
rfc
};
}
function parseIPv4Address(address) {
const parts = address.split(".").map((part) => {
if (!/^\d+$/.test(part)) return NaN;
return parseInt(part, 10);
});
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
return parts;
}
function parseIPv6Address(address) {
const parts = expandIPv6Address(address).split(":");
if (parts.length !== 8) return null;
const words = parts.map((part) => {
if (!/^[0-9a-f]{1,4}$/i.test(part)) return NaN;
return parseInt(part, 16);
});
if (words.some((word) => !Number.isInteger(word) || word < 0 || word > 65535)) return null;
return words;
}
function ipv4PartsToNumber(parts) {
return parts[0] * 2 ** 24 + parts[1] * 2 ** 16 + parts[2] * 2 ** 8 + parts[3];
}
function ipv4FromWords(highWord, lowWord) {
return [
highWord >> 8,
highWord & 255,
lowWord >> 8,
lowWord & 255
].join(".");
}
function matchesIPv4Prefix(address, prefixBase, prefixLength) {
const blockSize = 2 ** (32 - prefixLength);
return Math.floor(address / blockSize) === Math.floor(prefixBase / blockSize);
}
function matchesIPv6Prefix(address, prefixWords, prefixLength) {
let remaining = prefixLength;
for (let i = 0; i < 8 && remaining > 0; i++) if (remaining >= 16) {
if (address[i] !== prefixWords[i]) return false;
remaining -= 16;
} else {
const mask = 65535 << 16 - remaining & 65535;
if ((address[i] & mask) !== (prefixWords[i] & mask)) return false;
remaining = 0;
}
return true;
}
//#endregion
Object.defineProperty(exports, "UrlError", {
enumerable: true,
get: function() {
return UrlError;
}
});
Object.defineProperty(exports, "expandIPv6Address", {
enumerable: true,
get: function() {
return expandIPv6Address;
}
});
Object.defineProperty(exports, "isValidPublicIPv4Address", {
enumerable: true,
get: function() {
return isValidPublicIPv4Address;
}
});
Object.defineProperty(exports, "isValidPublicIPv6Address", {
enumerable: true,
get: function() {
return isValidPublicIPv6Address;
}
});
Object.defineProperty(exports, "validateLookupAddresses", {
enumerable: true,
get: function() {
return validateLookupAddresses;
}
});
Object.defineProperty(exports, "validatePublicUrl", {
enumerable: true,
get: function() {
return validatePublicUrl;
}
});
import { lookup } from "node:dns/promises";
import { isIP } from "node:net";
//#region src/url.ts
var UrlError = class extends Error {
constructor(message, options) {
super(message, options);
this.name = "UrlError";
}
};
/**
* Validates a URL to prevent SSRF attacks.
*/
async function validatePublicUrl(url) {
const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
let hostname = parsed.hostname;
if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
const hostnameFamily = isIP(hostname);
if (hostnameFamily !== 0) {
validatePublicIpAddress(hostname, hostnameFamily);
return;
}
if ("Deno" in globalThis && !isIP(hostname)) {
if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
}
if ("Bun" in globalThis) {
if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
}
let addresses;
try {
addresses = await lookup(hostname, { all: true });
} catch (error) {
throw new UrlError("DNS lookup failed", { cause: error });
}
validateLookupAddresses(addresses);
}
/**
* Validates the IP addresses returned by `node:dns.lookup()`.
*
* Cloudflare Workers' `node:dns` implementation currently maps every record
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
* `LookupAddress`, even though Node.js specifies that the `address` field must
* contain an IPv4 or IPv6 literal. See:
* https://github.com/cloudflare/workerd/issues/6886
*
* Work around that bug by ignoring non-IP entries only when the lookup also
* returns at least one actual IP address. This remains fail-closed: a result
* containing no IP addresses is rejected, and every returned IP address is
* still validated and must be public. This workaround can be revisited once
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
*
* @internal
*/
function validateLookupAddresses(addresses) {
let ipAddressCount = 0;
for (const { address } of addresses) {
const family = isIP(address);
if (family === 0) continue;
ipAddressCount++;
validatePublicIpAddress(address, family);
}
if (ipAddressCount === 0) throw new UrlError("DNS lookup did not return any IP address");
}
function validatePublicIpAddress(address, family) {
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
throw new UrlError(`Invalid or private address: ${address}`);
}
function isValidPublicIPv4Address(address) {
const parts = parseIPv4Address(address);
if (parts == null) return false;
const value = ipv4PartsToNumber(parts);
return !nonPublicIPv4Prefixes.some(({ base, prefix }) => matchesIPv4Prefix(value, base, prefix));
}
function isValidPublicIPv6Address(address) {
const words = parseIPv6Address(address);
if (words == null) return false;
if (nonPublicIPv6Prefixes.some(({ words: prefixWords, prefix }) => matchesIPv6Prefix(words, prefixWords, prefix))) return false;
for (const { extractIPv4, prefix, words: prefixWords } of ipv6WithIPv4Prefixes) {
if (!matchesIPv6Prefix(words, prefixWords, prefix)) continue;
const ipv4Address = extractIPv4(words);
if (ipv4Address != null && !isValidPublicIPv4Address(ipv4Address)) return false;
}
return true;
}
function expandIPv6Address(address) {
address = address.toLowerCase();
const ipv4Delimiter = address.lastIndexOf(":");
if (address.includes(".") && ipv4Delimiter >= 0) {
const ipv4Parts = parseIPv4Address(address.substring(ipv4Delimiter + 1));
if (ipv4Parts == null) return address;
const high = (ipv4Parts[0] << 8) + ipv4Parts[1];
const low = (ipv4Parts[2] << 8) + ipv4Parts[3];
address = address.substring(0, ipv4Delimiter + 1) + high.toString(16) + ":" + low.toString(16);
}
if (address === "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
if (address.startsWith("::")) address = "0000" + address;
if (address.endsWith("::")) address = address + "0000";
address = address.replace("::", ":0000".repeat(8 - (address.match(/:/g) || []).length) + ":");
return address.split(":").map((part) => part.padStart(4, "0")).join(":");
}
const nonPublicIPv4Prefixes = [
ipv4Prefix("0.0.0.0/8", "RFC 6890"),
ipv4Prefix("10.0.0.0/8", "RFC 1918"),
ipv4Prefix("100.64.0.0/10", "RFC 6598"),
ipv4Prefix("127.0.0.0/8", "RFC 1122"),
ipv4Prefix("169.254.0.0/16", "RFC 3927"),
ipv4Prefix("172.16.0.0/12", "RFC 1918"),
ipv4Prefix("192.0.0.0/24", "RFC 6890"),
ipv4Prefix("192.0.2.0/24", "RFC 5737"),
ipv4Prefix("192.88.99.0/24", "RFC 7526"),
ipv4Prefix("192.168.0.0/16", "RFC 1918"),
ipv4Prefix("198.18.0.0/15", "RFC 2544"),
ipv4Prefix("198.51.100.0/24", "RFC 5737"),
ipv4Prefix("203.0.113.0/24", "RFC 5737"),
ipv4Prefix("224.0.0.0/4", "RFC 5771"),
ipv4Prefix("240.0.0.0/4", "RFC 1112")
];
const nonPublicIPv6Prefixes = [
ipv6Prefix("::/16", "RFC 4291"),
ipv6Prefix("2001::/32", "RFC 4380"),
ipv6Prefix("2002::/16", "RFC 3056"),
ipv6Prefix("64:ff9b:1::/48", "RFC 8215"),
ipv6Prefix("fc00::/7", "RFC 4193"),
ipv6Prefix("fe80::/10", "RFC 4291"),
ipv6Prefix("ff00::/8", "RFC 4291")
];
const ipv6WithIPv4Prefixes = [{
...ipv6Prefix("64:ff9b::/96", "RFC 6052"),
extractIPv4: (words) => ipv4FromWords(words[6], words[7])
}];
function ipv4Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const parts = parseIPv4Address(address);
if (parts == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) throw new Error(`Invalid IPv4 prefix: ${cidr}`);
return {
cidr,
base: ipv4PartsToNumber(parts),
prefix,
rfc
};
}
function ipv6Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const words = parseIPv6Address(address);
if (words == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 128) throw new Error(`Invalid IPv6 prefix: ${cidr}`);
return {
cidr,
words,
prefix,
rfc
};
}
function parseIPv4Address(address) {
const parts = address.split(".").map((part) => {
if (!/^\d+$/.test(part)) return NaN;
return parseInt(part, 10);
});
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
return parts;
}
function parseIPv6Address(address) {
const parts = expandIPv6Address(address).split(":");
if (parts.length !== 8) return null;
const words = parts.map((part) => {
if (!/^[0-9a-f]{1,4}$/i.test(part)) return NaN;
return parseInt(part, 16);
});
if (words.some((word) => !Number.isInteger(word) || word < 0 || word > 65535)) return null;
return words;
}
function ipv4PartsToNumber(parts) {
return parts[0] * 2 ** 24 + parts[1] * 2 ** 16 + parts[2] * 2 ** 8 + parts[3];
}
function ipv4FromWords(highWord, lowWord) {
return [
highWord >> 8,
highWord & 255,
lowWord >> 8,
lowWord & 255
].join(".");
}
function matchesIPv4Prefix(address, prefixBase, prefixLength) {
const blockSize = 2 ** (32 - prefixLength);
return Math.floor(address / blockSize) === Math.floor(prefixBase / blockSize);
}
function matchesIPv6Prefix(address, prefixWords, prefixLength) {
let remaining = prefixLength;
for (let i = 0; i < 8 && remaining > 0; i++) if (remaining >= 16) {
if (address[i] !== prefixWords[i]) return false;
remaining -= 16;
} else {
const mask = 65535 << 16 - remaining & 65535;
if ((address[i] & mask) !== (prefixWords[i] & mask)) return false;
remaining = 0;
}
return true;
}
//#endregion
export { validateLookupAddresses as a, isValidPublicIPv6Address as i, expandIPv6Address as n, validatePublicUrl as o, isValidPublicIPv4Address as r, UrlError as t };
+1
-1
{
"name": "@fedify/vocab-runtime",
"version": "2.1.18",
"version": "2.1.19",
"license": "MIT",

@@ -5,0 +5,0 @@ "exports": {

@@ -420,3 +420,5 @@ /// <reference lib="esnext.temporal" />

declare class UrlError extends Error {
constructor(message: string);
constructor(message: string, options?: {
cause?: unknown;
});
}

@@ -423,0 +425,0 @@ /**

/// <reference lib="esnext.temporal" />
import { Logger } from "@logtape/logtape";
//#region src/contexts.d.ts

@@ -420,3 +419,5 @@ declare const preloadedContexts: Record<string, unknown>;

declare class UrlError extends Error {
constructor(message: string);
constructor(message: string, options?: {
cause?: unknown;
});
}

@@ -423,0 +424,0 @@ /**

require("./chunk-Do9eywBl.cjs");
require("./docloader-CNNuAaeD.cjs");
require("./url-Ds8K8vxN.cjs");
require("./docloader-BPX9K2hq.cjs");
require("./url-CWU5HT3Y.cjs");
require("./key-DTTIntwb.cjs");

@@ -5,0 +5,0 @@ require("./multibase-F7LtMMsK.cjs");

@@ -1,3 +0,3 @@

import "./docloader-DhbeXWJm.mjs";
import "./url-m9Qzxy-Y.mjs";
import "./docloader-BU_97FNC.mjs";
import "./url-o0e-aXQY.mjs";
import "./key-BeTHFQJK.mjs";

@@ -4,0 +4,0 @@ import "./multibase-BgU9XRf7.mjs";

const require_chunk = require("./chunk-Do9eywBl.cjs");
const require_docloader = require("./docloader-CNNuAaeD.cjs");
const require_request = require("./request-B_7NkCEW.cjs");
const require_url = require("./url-Ds8K8vxN.cjs");
const require_docloader = require("./docloader-BPX9K2hq.cjs");
const require_request = require("./request-Dn7JEQCQ.cjs");
const require_url = require("./url-CWU5HT3Y.cjs");
let node_assert = require("node:assert");

@@ -6,0 +6,0 @@ let node_test = require("node:test");

@@ -1,4 +0,4 @@

import { n as preloadedContexts, t as getDocumentLoader } from "./docloader-DhbeXWJm.mjs";
import { t as FetchError } from "./request-IHuVvkne.mjs";
import { t as UrlError } from "./url-m9Qzxy-Y.mjs";
import { n as preloadedContexts, t as getDocumentLoader } from "./docloader-BU_97FNC.mjs";
import { t as FetchError } from "./request-DhGuwatz.mjs";
import { t as UrlError } from "./url-o0e-aXQY.mjs";
import { deepStrictEqual, ok, rejects } from "node:assert";

@@ -5,0 +5,0 @@ import { test } from "node:test";

const require_chunk = require("./chunk-Do9eywBl.cjs");
const require_request = require("./request-B_7NkCEW.cjs");
const require_request = require("./request-Dn7JEQCQ.cjs");
let node_assert = require("node:assert");

@@ -4,0 +4,0 @@ let node_test = require("node:test");

@@ -1,2 +0,2 @@

import { o as version, r as getUserAgent } from "./request-IHuVvkne.mjs";
import { o as version, r as getUserAgent } from "./request-DhGuwatz.mjs";
import { deepStrictEqual } from "node:assert";

@@ -3,0 +3,0 @@ import { test } from "node:test";

require("./chunk-Do9eywBl.cjs");
const require_url = require("./url-Ds8K8vxN.cjs");
const require_url = require("./url-CWU5HT3Y.cjs");
let node_assert = require("node:assert");

@@ -32,2 +32,31 @@ let node_test = require("node:test");

});
(0, node_test.test)("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
require_url.validateLookupAddresses([
{
address: "app-host.example.net.",
family: 4
},
{
address: "93.184.216.34",
family: 4
},
{
address: "2606:2800:220:1:248:1893:25c8:1946",
family: 6
}
]);
});
(0, node_test.test)("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
(0, node_assert.throws)(() => require_url.validateLookupAddresses([{
address: "private-host.example.net.",
family: 4
}, {
address: "127.0.0.1",
family: 4
}]), require_url.UrlError);
(0, node_assert.throws)(() => require_url.validateLookupAddresses([{
address: "app-host.example.net.",
family: 4
}]), require_url.UrlError);
});
(0, node_test.test)("isValidPublicIPv4Address()", () => {

@@ -34,0 +63,0 @@ (0, node_assert.ok)(require_url.isValidPublicIPv4Address("8.8.8.8"));

@@ -1,3 +0,3 @@

import { a as validatePublicUrl, i as isValidPublicIPv6Address, n as expandIPv6Address, r as isValidPublicIPv4Address, t as UrlError } from "./url-m9Qzxy-Y.mjs";
import { deepStrictEqual, ok, rejects } from "node:assert";
import { a as validateLookupAddresses, i as isValidPublicIPv6Address, n as expandIPv6Address, o as validatePublicUrl, r as isValidPublicIPv4Address, t as UrlError } from "./url-o0e-aXQY.mjs";
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
import { test } from "node:test";

@@ -31,2 +31,31 @@ //#region src/url.test.ts

});
test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
validateLookupAddresses([
{
address: "app-host.example.net.",
family: 4
},
{
address: "93.184.216.34",
family: 4
},
{
address: "2606:2800:220:1:248:1893:25c8:1946",
family: 6
}
]);
});
test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
throws(() => validateLookupAddresses([{
address: "private-host.example.net.",
family: 4
}, {
address: "127.0.0.1",
family: 4
}]), UrlError);
throws(() => validateLookupAddresses([{
address: "app-host.example.net.",
family: 4
}]), UrlError);
});
test("isValidPublicIPv4Address()", () => {

@@ -33,0 +62,0 @@ ok(isValidPublicIPv4Address("8.8.8.8"));

{
"name": "@fedify/vocab-runtime",
"version": "2.1.18",
"version": "2.1.19",
"homepage": "https://fedify.dev/",

@@ -5,0 +5,0 @@ "repository": {

@@ -1,2 +0,2 @@

import { deepStrictEqual, ok, rejects } from "node:assert";
import { deepStrictEqual, ok, rejects, throws } from "node:assert";
import { test } from "node:test";

@@ -8,2 +8,3 @@ import {

UrlError,
validateLookupAddresses,
validatePublicUrl,

@@ -69,2 +70,28 @@ } from "./url.ts";

test("validateLookupAddresses() tolerates Cloudflare Workers CNAME entries", () => {
validateLookupAddresses([
{ address: "app-host.example.net.", family: 4 },
{ address: "93.184.216.34", family: 4 },
{ address: "2606:2800:220:1:248:1893:25c8:1946", family: 6 },
]);
});
test("validateLookupAddresses() rejects unsafe or CNAME-only Cloudflare Workers results", () => {
throws(
() =>
validateLookupAddresses([
{ address: "private-host.example.net.", family: 4 },
{ address: "127.0.0.1", family: 4 },
]),
UrlError,
);
throws(
() =>
validateLookupAddresses([
{ address: "app-host.example.net.", family: 4 },
]),
UrlError,
);
});
test("isValidPublicIPv4Address()", () => {

@@ -71,0 +98,0 @@ ok(isValidPublicIPv4Address("8.8.8.8")); // Google DNS

@@ -6,4 +6,4 @@ import type { LookupAddress } from "node:dns";

export class UrlError extends Error {
constructor(message: string) {
super(message);
constructor(message: string, options?: { cause?: unknown }) {
super(message, options);
this.name = "UrlError";

@@ -55,8 +55,38 @@ }

addresses = await lookup(hostname, { all: true });
} catch {
addresses = [];
} catch (error) {
throw new UrlError("DNS lookup failed", { cause: error });
}
for (const { address, family } of addresses) {
validateLookupAddresses(addresses);
}
/**
* Validates the IP addresses returned by `node:dns.lookup()`.
*
* Cloudflare Workers' `node:dns` implementation currently maps every record
* in a DNS-over-HTTPS `Answer` array—including CNAME records—to a
* `LookupAddress`, even though Node.js specifies that the `address` field must
* contain an IPv4 or IPv6 literal. See:
* https://github.com/cloudflare/workerd/issues/6886
*
* Work around that bug by ignoring non-IP entries only when the lookup also
* returns at least one actual IP address. This remains fail-closed: a result
* containing no IP addresses is rejected, and every returned IP address is
* still validated and must be public. This workaround can be revisited once
* the Workerd issue is fixed in supported Cloudflare Workers runtimes.
*
* @internal
*/
export function validateLookupAddresses(
addresses: readonly LookupAddress[],
): void {
let ipAddressCount = 0;
for (const { address } of addresses) {
const family = isIP(address);
if (family === 0) continue;
ipAddressCount++;
validatePublicIpAddress(address, family);
}
if (ipAddressCount === 0) {
throw new UrlError("DNS lookup did not return any IP address");
}
}

@@ -63,0 +93,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

const require_chunk = require("./chunk-Do9eywBl.cjs");
let node_process = require("node:process");
node_process = require_chunk.__toESM(node_process);
//#region deno.json
var name = "@fedify/vocab-runtime";
var version = "2.1.18";
//#endregion
//#region src/request.ts
/**
* Error thrown when fetching a JSON-LD document failed.
*/
var FetchError = class extends Error {
/**
* The URL that failed to fetch.
*/
url;
/**
* The HTTP response that failed, if available.
*/
response;
/**
* Constructs a new `FetchError`.
*
* @param url The URL that failed to fetch.
* @param message Error message.
* @param response The failed HTTP response, if available.
*/
constructor(url, message, response) {
super(message == null ? url.toString() : `${url}: ${message}`);
this.name = "FetchError";
this.url = typeof url === "string" ? new URL(url) : url;
this.response = response;
}
};
/**
* Creates a request for the given URL.
* @param url The URL to create the request for.
* @param options The options for the request.
* @returns The created request.
* @internal
*/
function createActivityPubRequest(url, options = {}) {
return new Request(url, {
headers: {
Accept: "application/activity+json, application/ld+json",
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
},
redirect: "manual"
});
}
/**
* Gets the user agent string for the given application and URL.
* @param options The options for making the user agent string.
* @returns The user agent string.
* @since 1.3.0
*/
function getUserAgent({ software, url } = {}) {
const fedify = `Fedify/${version}`;
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${node_process.default.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${node_process.default.versions.node}` : null;
const userAgent = software == null ? [fedify] : [software, fedify];
if (runtime != null) userAgent.push(runtime);
if (url != null) userAgent.push(`+${url.toString()}`);
return `${userAgent.shift()} (${userAgent.join("; ")})`;
}
/**
* Logs the request.
* @param request The request to log.
* @internal
*/
function logRequest(logger, request) {
logger.debug("Fetching document: {method} {url} {headers}", {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries())
});
}
//#endregion
Object.defineProperty(exports, "FetchError", {
enumerable: true,
get: function() {
return FetchError;
}
});
Object.defineProperty(exports, "createActivityPubRequest", {
enumerable: true,
get: function() {
return createActivityPubRequest;
}
});
Object.defineProperty(exports, "getUserAgent", {
enumerable: true,
get: function() {
return getUserAgent;
}
});
Object.defineProperty(exports, "logRequest", {
enumerable: true,
get: function() {
return logRequest;
}
});
Object.defineProperty(exports, "name", {
enumerable: true,
get: function() {
return name;
}
});
Object.defineProperty(exports, "version", {
enumerable: true,
get: function() {
return version;
}
});
import process from "node:process";
//#region deno.json
var name = "@fedify/vocab-runtime";
var version = "2.1.18";
//#endregion
//#region src/request.ts
/**
* Error thrown when fetching a JSON-LD document failed.
*/
var FetchError = class extends Error {
/**
* The URL that failed to fetch.
*/
url;
/**
* The HTTP response that failed, if available.
*/
response;
/**
* Constructs a new `FetchError`.
*
* @param url The URL that failed to fetch.
* @param message Error message.
* @param response The failed HTTP response, if available.
*/
constructor(url, message, response) {
super(message == null ? url.toString() : `${url}: ${message}`);
this.name = "FetchError";
this.url = typeof url === "string" ? new URL(url) : url;
this.response = response;
}
};
/**
* Creates a request for the given URL.
* @param url The URL to create the request for.
* @param options The options for the request.
* @returns The created request.
* @internal
*/
function createActivityPubRequest(url, options = {}) {
return new Request(url, {
headers: {
Accept: "application/activity+json, application/ld+json",
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
},
redirect: "manual"
});
}
/**
* Gets the user agent string for the given application and URL.
* @param options The options for making the user agent string.
* @returns The user agent string.
* @since 1.3.0
*/
function getUserAgent({ software, url } = {}) {
const fedify = `Fedify/${version}`;
const runtime = globalThis.Deno?.version?.deno != null ? `Deno/${Deno.version.deno}` : globalThis.process?.versions?.bun != null ? `Bun/${process.versions.bun}` : "navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers" ? navigator.userAgent : globalThis.process?.versions?.node != null ? `Node.js/${process.versions.node}` : null;
const userAgent = software == null ? [fedify] : [software, fedify];
if (runtime != null) userAgent.push(runtime);
if (url != null) userAgent.push(`+${url.toString()}`);
return `${userAgent.shift()} (${userAgent.join("; ")})`;
}
/**
* Logs the request.
* @param request The request to log.
* @internal
*/
function logRequest(logger, request) {
logger.debug("Fetching document: {method} {url} {headers}", {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries())
});
}
//#endregion
export { name as a, logRequest as i, createActivityPubRequest as n, version as o, getUserAgent as r, FetchError as t };
require("./chunk-Do9eywBl.cjs");
let node_dns_promises = require("node:dns/promises");
let node_net = require("node:net");
//#region src/url.ts
var UrlError = class extends Error {
constructor(message) {
super(message);
this.name = "UrlError";
}
};
/**
* Validates a URL to prevent SSRF attacks.
*/
async function validatePublicUrl(url) {
const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
let hostname = parsed.hostname;
if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
const hostnameFamily = (0, node_net.isIP)(hostname);
if (hostnameFamily !== 0) {
validatePublicIpAddress(hostname, hostnameFamily);
return;
}
if ("Deno" in globalThis && !(0, node_net.isIP)(hostname)) {
if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
}
if ("Bun" in globalThis) {
if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
}
let addresses;
try {
addresses = await (0, node_dns_promises.lookup)(hostname, { all: true });
} catch {
addresses = [];
}
for (const { address, family } of addresses) validatePublicIpAddress(address, family);
}
function validatePublicIpAddress(address, family) {
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
throw new UrlError(`Invalid or private address: ${address}`);
}
function isValidPublicIPv4Address(address) {
const parts = parseIPv4Address(address);
if (parts == null) return false;
const value = ipv4PartsToNumber(parts);
return !nonPublicIPv4Prefixes.some(({ base, prefix }) => matchesIPv4Prefix(value, base, prefix));
}
function isValidPublicIPv6Address(address) {
const words = parseIPv6Address(address);
if (words == null) return false;
if (nonPublicIPv6Prefixes.some(({ words: prefixWords, prefix }) => matchesIPv6Prefix(words, prefixWords, prefix))) return false;
for (const { extractIPv4, prefix, words: prefixWords } of ipv6WithIPv4Prefixes) {
if (!matchesIPv6Prefix(words, prefixWords, prefix)) continue;
const ipv4Address = extractIPv4(words);
if (ipv4Address != null && !isValidPublicIPv4Address(ipv4Address)) return false;
}
return true;
}
function expandIPv6Address(address) {
address = address.toLowerCase();
const ipv4Delimiter = address.lastIndexOf(":");
if (address.includes(".") && ipv4Delimiter >= 0) {
const ipv4Parts = parseIPv4Address(address.substring(ipv4Delimiter + 1));
if (ipv4Parts == null) return address;
const high = (ipv4Parts[0] << 8) + ipv4Parts[1];
const low = (ipv4Parts[2] << 8) + ipv4Parts[3];
address = address.substring(0, ipv4Delimiter + 1) + high.toString(16) + ":" + low.toString(16);
}
if (address === "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
if (address.startsWith("::")) address = "0000" + address;
if (address.endsWith("::")) address = address + "0000";
address = address.replace("::", ":0000".repeat(8 - (address.match(/:/g) || []).length) + ":");
return address.split(":").map((part) => part.padStart(4, "0")).join(":");
}
const nonPublicIPv4Prefixes = [
ipv4Prefix("0.0.0.0/8", "RFC 6890"),
ipv4Prefix("10.0.0.0/8", "RFC 1918"),
ipv4Prefix("100.64.0.0/10", "RFC 6598"),
ipv4Prefix("127.0.0.0/8", "RFC 1122"),
ipv4Prefix("169.254.0.0/16", "RFC 3927"),
ipv4Prefix("172.16.0.0/12", "RFC 1918"),
ipv4Prefix("192.0.0.0/24", "RFC 6890"),
ipv4Prefix("192.0.2.0/24", "RFC 5737"),
ipv4Prefix("192.88.99.0/24", "RFC 7526"),
ipv4Prefix("192.168.0.0/16", "RFC 1918"),
ipv4Prefix("198.18.0.0/15", "RFC 2544"),
ipv4Prefix("198.51.100.0/24", "RFC 5737"),
ipv4Prefix("203.0.113.0/24", "RFC 5737"),
ipv4Prefix("224.0.0.0/4", "RFC 5771"),
ipv4Prefix("240.0.0.0/4", "RFC 1112")
];
const nonPublicIPv6Prefixes = [
ipv6Prefix("::/16", "RFC 4291"),
ipv6Prefix("2001::/32", "RFC 4380"),
ipv6Prefix("2002::/16", "RFC 3056"),
ipv6Prefix("64:ff9b:1::/48", "RFC 8215"),
ipv6Prefix("fc00::/7", "RFC 4193"),
ipv6Prefix("fe80::/10", "RFC 4291"),
ipv6Prefix("ff00::/8", "RFC 4291")
];
const ipv6WithIPv4Prefixes = [{
...ipv6Prefix("64:ff9b::/96", "RFC 6052"),
extractIPv4: (words) => ipv4FromWords(words[6], words[7])
}];
function ipv4Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const parts = parseIPv4Address(address);
if (parts == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) throw new Error(`Invalid IPv4 prefix: ${cidr}`);
return {
cidr,
base: ipv4PartsToNumber(parts),
prefix,
rfc
};
}
function ipv6Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const words = parseIPv6Address(address);
if (words == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 128) throw new Error(`Invalid IPv6 prefix: ${cidr}`);
return {
cidr,
words,
prefix,
rfc
};
}
function parseIPv4Address(address) {
const parts = address.split(".").map((part) => {
if (!/^\d+$/.test(part)) return NaN;
return parseInt(part, 10);
});
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
return parts;
}
function parseIPv6Address(address) {
const parts = expandIPv6Address(address).split(":");
if (parts.length !== 8) return null;
const words = parts.map((part) => {
if (!/^[0-9a-f]{1,4}$/i.test(part)) return NaN;
return parseInt(part, 16);
});
if (words.some((word) => !Number.isInteger(word) || word < 0 || word > 65535)) return null;
return words;
}
function ipv4PartsToNumber(parts) {
return parts[0] * 2 ** 24 + parts[1] * 2 ** 16 + parts[2] * 2 ** 8 + parts[3];
}
function ipv4FromWords(highWord, lowWord) {
return [
highWord >> 8,
highWord & 255,
lowWord >> 8,
lowWord & 255
].join(".");
}
function matchesIPv4Prefix(address, prefixBase, prefixLength) {
const blockSize = 2 ** (32 - prefixLength);
return Math.floor(address / blockSize) === Math.floor(prefixBase / blockSize);
}
function matchesIPv6Prefix(address, prefixWords, prefixLength) {
let remaining = prefixLength;
for (let i = 0; i < 8 && remaining > 0; i++) if (remaining >= 16) {
if (address[i] !== prefixWords[i]) return false;
remaining -= 16;
} else {
const mask = 65535 << 16 - remaining & 65535;
if ((address[i] & mask) !== (prefixWords[i] & mask)) return false;
remaining = 0;
}
return true;
}
//#endregion
Object.defineProperty(exports, "UrlError", {
enumerable: true,
get: function() {
return UrlError;
}
});
Object.defineProperty(exports, "expandIPv6Address", {
enumerable: true,
get: function() {
return expandIPv6Address;
}
});
Object.defineProperty(exports, "isValidPublicIPv4Address", {
enumerable: true,
get: function() {
return isValidPublicIPv4Address;
}
});
Object.defineProperty(exports, "isValidPublicIPv6Address", {
enumerable: true,
get: function() {
return isValidPublicIPv6Address;
}
});
Object.defineProperty(exports, "validatePublicUrl", {
enumerable: true,
get: function() {
return validatePublicUrl;
}
});
import { lookup } from "node:dns/promises";
import { isIP } from "node:net";
//#region src/url.ts
var UrlError = class extends Error {
constructor(message) {
super(message);
this.name = "UrlError";
}
};
/**
* Validates a URL to prevent SSRF attacks.
*/
async function validatePublicUrl(url) {
const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new UrlError(`Unsupported protocol: ${parsed.protocol}`);
let hostname = parsed.hostname;
if (hostname.startsWith("[") && hostname.endsWith("]")) hostname = hostname.slice(1, -1);
if (hostname === "localhost") throw new UrlError("Localhost is not allowed");
const hostnameFamily = isIP(hostname);
if (hostnameFamily !== 0) {
validatePublicIpAddress(hostname, hostnameFamily);
return;
}
if ("Deno" in globalThis && !isIP(hostname)) {
if ((await Deno.permissions.query({ name: "net" })).state !== "granted") return;
}
if ("Bun" in globalThis) {
if (hostname === "example.com" || hostname.endsWith(".example.com")) return;
else if (hostname === "fedify-test.internal") throw new UrlError("Invalid or private address: fedify-test.internal");
}
let addresses;
try {
addresses = await lookup(hostname, { all: true });
} catch {
addresses = [];
}
for (const { address, family } of addresses) validatePublicIpAddress(address, family);
}
function validatePublicIpAddress(address, family) {
if (family === 4 && isValidPublicIPv4Address(address) || family === 6 && isValidPublicIPv6Address(address)) return;
throw new UrlError(`Invalid or private address: ${address}`);
}
function isValidPublicIPv4Address(address) {
const parts = parseIPv4Address(address);
if (parts == null) return false;
const value = ipv4PartsToNumber(parts);
return !nonPublicIPv4Prefixes.some(({ base, prefix }) => matchesIPv4Prefix(value, base, prefix));
}
function isValidPublicIPv6Address(address) {
const words = parseIPv6Address(address);
if (words == null) return false;
if (nonPublicIPv6Prefixes.some(({ words: prefixWords, prefix }) => matchesIPv6Prefix(words, prefixWords, prefix))) return false;
for (const { extractIPv4, prefix, words: prefixWords } of ipv6WithIPv4Prefixes) {
if (!matchesIPv6Prefix(words, prefixWords, prefix)) continue;
const ipv4Address = extractIPv4(words);
if (ipv4Address != null && !isValidPublicIPv4Address(ipv4Address)) return false;
}
return true;
}
function expandIPv6Address(address) {
address = address.toLowerCase();
const ipv4Delimiter = address.lastIndexOf(":");
if (address.includes(".") && ipv4Delimiter >= 0) {
const ipv4Parts = parseIPv4Address(address.substring(ipv4Delimiter + 1));
if (ipv4Parts == null) return address;
const high = (ipv4Parts[0] << 8) + ipv4Parts[1];
const low = (ipv4Parts[2] << 8) + ipv4Parts[3];
address = address.substring(0, ipv4Delimiter + 1) + high.toString(16) + ":" + low.toString(16);
}
if (address === "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
if (address.startsWith("::")) address = "0000" + address;
if (address.endsWith("::")) address = address + "0000";
address = address.replace("::", ":0000".repeat(8 - (address.match(/:/g) || []).length) + ":");
return address.split(":").map((part) => part.padStart(4, "0")).join(":");
}
const nonPublicIPv4Prefixes = [
ipv4Prefix("0.0.0.0/8", "RFC 6890"),
ipv4Prefix("10.0.0.0/8", "RFC 1918"),
ipv4Prefix("100.64.0.0/10", "RFC 6598"),
ipv4Prefix("127.0.0.0/8", "RFC 1122"),
ipv4Prefix("169.254.0.0/16", "RFC 3927"),
ipv4Prefix("172.16.0.0/12", "RFC 1918"),
ipv4Prefix("192.0.0.0/24", "RFC 6890"),
ipv4Prefix("192.0.2.0/24", "RFC 5737"),
ipv4Prefix("192.88.99.0/24", "RFC 7526"),
ipv4Prefix("192.168.0.0/16", "RFC 1918"),
ipv4Prefix("198.18.0.0/15", "RFC 2544"),
ipv4Prefix("198.51.100.0/24", "RFC 5737"),
ipv4Prefix("203.0.113.0/24", "RFC 5737"),
ipv4Prefix("224.0.0.0/4", "RFC 5771"),
ipv4Prefix("240.0.0.0/4", "RFC 1112")
];
const nonPublicIPv6Prefixes = [
ipv6Prefix("::/16", "RFC 4291"),
ipv6Prefix("2001::/32", "RFC 4380"),
ipv6Prefix("2002::/16", "RFC 3056"),
ipv6Prefix("64:ff9b:1::/48", "RFC 8215"),
ipv6Prefix("fc00::/7", "RFC 4193"),
ipv6Prefix("fe80::/10", "RFC 4291"),
ipv6Prefix("ff00::/8", "RFC 4291")
];
const ipv6WithIPv4Prefixes = [{
...ipv6Prefix("64:ff9b::/96", "RFC 6052"),
extractIPv4: (words) => ipv4FromWords(words[6], words[7])
}];
function ipv4Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const parts = parseIPv4Address(address);
if (parts == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 32) throw new Error(`Invalid IPv4 prefix: ${cidr}`);
return {
cidr,
base: ipv4PartsToNumber(parts),
prefix,
rfc
};
}
function ipv6Prefix(cidr, rfc) {
const [address, prefixText] = cidr.split("/");
const prefix = parseInt(prefixText, 10);
const words = parseIPv6Address(address);
if (words == null || !Number.isInteger(prefix) || prefix < 0 || prefix > 128) throw new Error(`Invalid IPv6 prefix: ${cidr}`);
return {
cidr,
words,
prefix,
rfc
};
}
function parseIPv4Address(address) {
const parts = address.split(".").map((part) => {
if (!/^\d+$/.test(part)) return NaN;
return parseInt(part, 10);
});
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return null;
return parts;
}
function parseIPv6Address(address) {
const parts = expandIPv6Address(address).split(":");
if (parts.length !== 8) return null;
const words = parts.map((part) => {
if (!/^[0-9a-f]{1,4}$/i.test(part)) return NaN;
return parseInt(part, 16);
});
if (words.some((word) => !Number.isInteger(word) || word < 0 || word > 65535)) return null;
return words;
}
function ipv4PartsToNumber(parts) {
return parts[0] * 2 ** 24 + parts[1] * 2 ** 16 + parts[2] * 2 ** 8 + parts[3];
}
function ipv4FromWords(highWord, lowWord) {
return [
highWord >> 8,
highWord & 255,
lowWord >> 8,
lowWord & 255
].join(".");
}
function matchesIPv4Prefix(address, prefixBase, prefixLength) {
const blockSize = 2 ** (32 - prefixLength);
return Math.floor(address / blockSize) === Math.floor(prefixBase / blockSize);
}
function matchesIPv6Prefix(address, prefixWords, prefixLength) {
let remaining = prefixLength;
for (let i = 0; i < 8 && remaining > 0; i++) if (remaining >= 16) {
if (address[i] !== prefixWords[i]) return false;
remaining -= 16;
} else {
const mask = 65535 << 16 - remaining & 65535;
if ((address[i] & mask) !== (prefixWords[i] & mask)) return false;
remaining = 0;
}
return true;
}
//#endregion
export { validatePublicUrl as a, isValidPublicIPv6Address as i, expandIPv6Address as n, isValidPublicIPv4Address as r, UrlError as t };

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display