UNPKG

@ocap/util

Version:

utils shared across multiple forge js libs, works in both node.js and browser

81 lines (79 loc) 3.34 kB
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs'); let node_dns_promises = require("node:dns/promises"); let url = require("url"); let ipaddr_js = require("ipaddr.js"); ipaddr_js = require_rolldown_runtime.__toESM(ipaddr_js); //#region src/url.ts const MAX_URL_LENGTH = 256; function parseIp(host) { if (!ipaddr_js.default.isValid(host)) return null; try { return ipaddr_js.default.parse(host); } catch { return null; } } function stripTrailingDots(host) { return host.replace(/\.+$/, ""); } function isBlockedHostname(host, blockedHosts, blockedSuffixes) { const normalized = host.toLowerCase(); if (blockedHosts.includes(normalized)) return true; if (blockedSuffixes.some((suffix) => normalized.endsWith(suffix))) return true; return false; } function isUnicastAddress(host) { const parsed = parseIp(host); if (!parsed) return false; const range = parsed.range(); if (range === "ipv4Mapped" && parsed.kind() === "ipv6") return parsed.toIPv4Address().range() === "unicast"; return range === "unicast"; } async function resolveHostAddresses(host) { try { const resolver = new node_dns_promises.Resolver(); resolver.setServers(["8.8.8.8", "1.1.1.1"]); const addresses = []; try { const ipv4Addresses = await resolver.resolve4(host); addresses.push(...ipv4Addresses); } catch (error) { console.warn(`IPv4 resolution failed for ${host}:`, error?.message); } if (!addresses.length) try { const ipv6Addresses = await resolver.resolve6(host); addresses.push(...ipv6Addresses); } catch (error) { console.warn(`IPv6 resolution failed for ${host}:`, error?.message); } return addresses.filter(Boolean); } catch (error) { console.warn(`DNS resolution failed for host: ${host}, error: ${error?.message}`); return []; } } async function verifyUrl(url$1, options = {}) { if (!url$1) throw new Error("URL is required"); const { maxLength = MAX_URL_LENGTH, protocols = ["https:"], blockedHosts = ["localhost"], blockedSuffixes = [], allowIp = false, checkDns = false } = options; if (url$1.length > maxLength) throw new Error(`URL exceeds maximum length of ${maxLength} characters: ${url$1.length}`); const { hostname, protocol } = new url.URL(url$1); if (!protocols.includes(protocol)) throw new Error(`Protocol '${protocol}' is not allowed. Allowed protocols: ${protocols.join(", ")}`); const normalizedHost = stripTrailingDots(hostname); if (isBlockedHostname(normalizedHost, blockedHosts, blockedSuffixes)) throw new Error(`Hostname '${normalizedHost}' is blocked`); const isIP = Boolean(parseIp(normalizedHost)); if (isIP) { if (!allowIp) throw new Error(`IP addresses are not allowed: ${normalizedHost}`); if (!isUnicastAddress(normalizedHost)) throw new Error(`IP address is not unicast: ${normalizedHost}`); } if (checkDns && !isIP) { const resolved = await resolveHostAddresses(normalizedHost); if (resolved.length === 0) throw new Error(`DNS resolution failed: no addresses found for ${normalizedHost}`); if (!resolved.every((address) => isUnicastAddress(address))) throw new Error(`DNS resolved to non-unicast addresses for ${normalizedHost}`); } return true; } //#endregion exports.isUnicastAddress = isUnicastAddress; exports.parseIp = parseIp; exports.resolveHostAddresses = resolveHostAddresses; exports.verifyUrl = verifyUrl;