@ocap/util
Version:
utils shared across multiple forge js libs, works in both node.js and browser
81 lines (79 loc) • 3.34 kB
JavaScript
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;