UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

68 lines (64 loc) 2.93 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { lookup } from "node:dns/promises"; import { isIP } from "node:net"; //#region runtime/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.substring(1, hostname.length - 2); if (hostname === "localhost") throw new UrlError("Localhost is not allowed"); if ("Deno" in globalThis && !isIP(hostname)) { const netPermission = await Deno.permissions.query({ name: "net" }); if (netPermission.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) if (family === 4 && !isValidPublicIPv4Address(address) || family === 6 && !isValidPublicIPv6Address(address) || family < 4 || family === 5 || family > 6) throw new UrlError(`Invalid or private address: ${address}`); } function isValidPublicIPv4Address(address) { const parts = address.split("."); const first = parseInt(parts[0]); if (first === 0 || first === 10 || first === 127) return false; const second = parseInt(parts[1]); if (first === 169 && second === 254) return false; if (first === 172 && second >= 16 && second <= 31) return false; if (first === 192 && second === 168) return false; return true; } function isValidPublicIPv6Address(address) { address = expandIPv6Address(address); if (address.at(4) !== ":") return false; const firstWord = parseInt(address.substring(0, 4), 16); return !(firstWord >= 64512 && firstWord <= 65023 || firstWord >= 65152 && firstWord <= 65215 || firstWord === 0 || firstWord >= 65280); } function expandIPv6Address(address) { address = address.toLowerCase(); 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) + ":"); const parts = address.split(":"); return parts.map((part) => part.padStart(4, "0")).join(":"); } //#endregion export { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address, validatePublicUrl };