UNPKG

@ocap/util

Version:

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

76 lines (74 loc) 3.05 kB
import { Resolver } from "node:dns/promises"; import { URL } from "url"; import ipaddr from "ipaddr.js"; //#region src/url.ts const MAX_URL_LENGTH = 256; function parseIp(host) { if (!ipaddr.isValid(host)) return null; try { return ipaddr.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 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, options = {}) { if (!url) throw new Error("URL is required"); const { maxLength = MAX_URL_LENGTH, protocols = ["https:"], blockedHosts = ["localhost"], blockedSuffixes = [], allowIp = false, checkDns = false } = options; if (url.length > maxLength) throw new Error(`URL exceeds maximum length of ${maxLength} characters: ${url.length}`); const { hostname, protocol } = new URL(url); 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 export { isUnicastAddress, parseIp, resolveHostAddresses, verifyUrl };