// node:url. URL and URLSearchParams are the browser's own; the legacy API // (url.parse, url.format, url.resolve, Url) follows Node's lib/url.js closely // enough for parseurl, express and serve-static. Hostnames are lowercased and // IDNA-encoded through the browser's URL parser. import { Buffer } from "./buffer"; import { qs } from "./querystring"; import { refusedClass } from "./refuse"; const protocolPattern = /^[a-z0-9.+-]+:/i; const portPattern = /:[0-9]*$/; const hostPattern = /^\/\/[^@/]+@[^@/]+/; const simplePathPattern = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/; const hostlessProtocol = new Set(["javascript", "javascript:"]); const unsafeProtocol = hostlessProtocol; const slashedProtocol = new Set([ "http", "http:", "https", "https:", "ftp", "ftp:", "gopher", "gopher:", "file", "file:", "ws", "ws:", "wss", "wss:", ]); const AUTO_ESCAPE: Record = { "\t": "%09", "\n": "%0A", "\r": "%0D", " ": "%20", '"': "%22", "'": "%27", "<": "%3C", ">": "%3E", "\\": "%5C", "^": "%5E", "`": "%60", "{": "%7B", "|": "%7C", "}": "%7D", }; // RFC 2396 never allows these in a hostname; a host-ending char also ends it. const NON_HOST = new Set("\t\n\r \"%';<>\\^`{|}"); const HOST_END = new Set("#/?"); function invalidArgType(name: string, expected: string, value: unknown): TypeError { const got = value === null ? "null" : value === undefined ? "undefined" : `type ${typeof value}`; return Object.assign(new TypeError(`The "${name}" argument must be ${expected}. Received ${got}`), { code: "ERR_INVALID_ARG_TYPE" }); } function toASCII(hostname: string): string { if (/^[\x21-\x7e]*$/.test(hostname)) return hostname; try { return new URL(`http://${hostname}`).hostname; } catch { return ""; } } const isIpv6Hostname = (h: string): boolean => h.startsWith("[") && h.endsWith("]"); // --- url.domainToASCII / url.domainToUnicode (IDNA/UTS#46) --- // Node (via ada) builds a URL with a special scheme, sets its hostname to the input — // the WHATWG "set the host" setter (state override "hostname"): a ':' outside brackets, // or any forbidden host code point, fails outright; '/', '?', '#' or '\' truncate // rather than failing — then runs UTS#46 ToASCII on what's left (non-strict: no // Std3ASCIIRules, no DNS length check), collapsing an all-numeric trailing label to // IPv4. domainToUnicode runs the same pipeline, then Punycode-decodes any xn-- label // back to Unicode. Invalid input answers '' rather than throwing, matching Node. const PUNY_BASE = 36, PUNY_TMIN = 1, PUNY_TMAX = 26, PUNY_SKEW = 38, PUNY_DAMP = 700; const PUNY_INITIAL_BIAS = 72, PUNY_INITIAL_N = 0x80; const MAX_INT = 0x7fffffff; function punyAdapt(delta: number, numPoints: number, firstTime: boolean): number { let k = 0; delta = firstTime ? Math.floor(delta / PUNY_DAMP) : delta >> 1; delta += Math.floor(delta / numPoints); const threshold = ((PUNY_BASE - PUNY_TMIN) * PUNY_TMAX) >> 1; while (delta > threshold) { delta = Math.floor(delta / (PUNY_BASE - PUNY_TMIN)); k += PUNY_BASE; } return Math.floor(k + ((PUNY_BASE - PUNY_TMIN + 1) * delta) / (delta + PUNY_SKEW)); } /** A basic code point's digit value (a-z -> 0-25, 0-9 -> 26-35), or BASE if none. */ function punyBasicToDigit(cp: number): number { if (cp >= 0x30 && cp < 0x3a) return 26 + (cp - 0x30); if (cp >= 0x41 && cp < 0x5b) return cp - 0x41; if (cp >= 0x61 && cp < 0x7b) return cp - 0x61; return PUNY_BASE; } function punyDigitToBasic(digit: number): number { return digit + 22 + 75 * (digit < 26 ? 1 : 0); } /** RFC 3492 Punycode decode: an "xn--" label's suffix to its Unicode code points. * Throws (RangeError) on malformed input — the same algorithm bestiejs/punycode.js * implements, which Node's own now-deprecated `punycode` module ported. */ function punycodeDecode(input: string): number[] { const output: number[] = []; const inputLength = input.length; let i = 0, n = PUNY_INITIAL_N, bias = PUNY_INITIAL_BIAS; let basic = input.lastIndexOf("-"); if (basic < 0) basic = 0; for (let j = 0; j < basic; ++j) { const cp = input.charCodeAt(j); if (cp >= 0x80) throw new RangeError("not-basic"); output.push(cp); } for (let index = basic > 0 ? basic + 1 : 0; index < inputLength;) { const oldi = i; for (let w = 1, k = PUNY_BASE; ; k += PUNY_BASE) { if (index >= inputLength) throw new RangeError("invalid-input"); const digit = punyBasicToDigit(input.charCodeAt(index++)); if (digit >= PUNY_BASE) throw new RangeError("invalid-input"); if (digit > Math.floor((MAX_INT - i) / w)) throw new RangeError("overflow"); i += digit * w; const t = k <= bias ? PUNY_TMIN : (k >= bias + PUNY_TMAX ? PUNY_TMAX : k - bias); if (digit < t) break; const baseMinusT = PUNY_BASE - t; if (w > Math.floor(MAX_INT / baseMinusT)) throw new RangeError("overflow"); w *= baseMinusT; } const out = output.length + 1; bias = punyAdapt(i - oldi, out, oldi === 0); if (Math.floor(i / out) > MAX_INT - n) throw new RangeError("overflow"); n += Math.floor(i / out); i %= out; output.splice(i++, 0, n); } return output; } /** RFC 3492 Punycode encode: Unicode code points to an "xn--" label's suffix. */ function punycodeEncode(input: number[]): string { const output: string[] = []; const inputLength = input.length; let n = PUNY_INITIAL_N, delta = 0, bias = PUNY_INITIAL_BIAS; for (const cp of input) if (cp < 0x80) output.push(String.fromCharCode(cp)); const basicLength = output.length; let handled = basicLength; if (basicLength) output.push("-"); while (handled < inputLength) { let m = MAX_INT; for (const cp of input) if (cp >= n && cp < m) m = cp; const handledPlusOne = handled + 1; if (m - n > Math.floor((MAX_INT - delta) / handledPlusOne)) throw new RangeError("overflow"); delta += (m - n) * handledPlusOne; n = m; for (const cp of input) { if (cp < n) { delta++; if (delta > MAX_INT) throw new RangeError("overflow"); } if (cp === n) { let q = delta; for (let k = PUNY_BASE; ; k += PUNY_BASE) { const t = k <= bias ? PUNY_TMIN : (k >= bias + PUNY_TMAX ? PUNY_TMAX : k - bias); if (q < t) break; const baseMinusT = PUNY_BASE - t; output.push(String.fromCharCode(punyDigitToBasic(t + ((q - t) % baseMinusT)))); q = Math.floor((q - t) / baseMinusT); } output.push(String.fromCharCode(punyDigitToBasic(q))); bias = punyAdapt(delta, handledPlusOne, handled === basicLength); delta = 0; ++handled; } } ++delta; ++n; } return output.join(""); } const isAsciiLabel = (s: string): boolean => /^[\x00-\x7f]*$/.test(s); /** Disallowed in a decoded Punycode label: C0/C1 controls and surrogate halves — the * real UTS#46 disallowed-code-point table is much larger, but this covers what comes * up in practice (a malformed "xn--" label that decodes to a control code point). */ const isDisallowedDecoded = (cp: number): boolean => cp <= 0x1f || (cp >= 0x7f && cp <= 0x9f) || (cp >= 0xd800 && cp <= 0xdfff); /** Forbidden (WHATWG forbidden-domain-code-point) in an already-ASCII-ized domain: * '/', '?', '#', '\\' and ':' can't reach here (the hostname setter already failed * or truncated on them), so only these remain to check. */ function hasForbiddenDomainChar(s: string): boolean { for (let i = 0; i < s.length; i++) { const c = s.charCodeAt(i); if (c <= 0x20 || c === 0x25 || c === 0x3c || c === 0x3e || c === 0x40 || c === 0x5b || c === 0x5c || c === 0x5d || c === 0x5e || c === 0x7c) return true; } return false; } /** A Unicode string to its ASCII form, or null if it can't Punycode-encode. */ function encodeNonAsciiLabel(label: string): string | null { if (isAsciiLabel(label)) return label; try { return `xn--${punycodeEncode(Array.from(label, (c) => c.codePointAt(0)!))}`; } catch { return null; } } /** A domain label to its ASCII form, or null if invalid. An already-ASCII label is * only lowercased (Node's non-strict mode skips Std3ASCIIRules and DNS length * checks); a label with non-ASCII content is Punycode-encoded; an "xn--" label is * decoded to validate it — it must hold no disallowed code point and re-encode back * to itself — and then kept as its lowercased self. */ function domainLabelToAscii(label: string): string | null { const lower = label.toLowerCase(); if (lower.startsWith("xn--")) { let decoded: number[]; try { decoded = punycodeDecode(lower.slice(4)); } catch { return null; } if (decoded.length === 0 || decoded.some(isDisallowedDecoded)) return null; return encodeNonAsciiLabel(String.fromCodePoint(...decoded)) === lower ? lower : null; } return isAsciiLabel(label) ? lower : encodeNonAsciiLabel(lower); } /** Byte-level percent-decode (the URL spec's UTF-8-decode-of-percent-decode), * tolerant of a stray '%' not followed by two hex digits. */ function percentDecodeUtf8(input: string): string { const bytes = new TextEncoder().encode(input); const out: number[] = []; for (let i = 0; i < bytes.length; i++) { const b = bytes[i]!; if (b === 0x25 && i + 2 < bytes.length) { const hex = String.fromCharCode(bytes[i + 1]!, bytes[i + 2]!); if (/^[0-9a-fA-F]{2}$/.test(hex)) { out.push(parseInt(hex, 16)); i += 2; continue; } } out.push(b); } return new TextDecoder("utf-8", { fatal: false }).decode(new Uint8Array(out)); } function parseIPv4Part(part: string): number | null { if (part === "") return null; let radix = 10; let digits = part; if (/^0x/i.test(digits)) { radix = 16; digits = digits.slice(2); } else if (digits.length > 1 && digits[0] === "0") { radix = 8; digits = digits.slice(1); } if (digits === "") return 0; const re = radix === 16 ? /^[0-9a-f]+$/i : radix === 8 ? /^[0-7]+$/ : /^[0-9]+$/; return re.test(digits) ? parseInt(digits, radix) : null; } function endsInANumber(domain: string): boolean { const parts = domain.split("."); if (parts.length > 1 && parts[parts.length - 1] === "") parts.pop(); const last = parts[parts.length - 1]; if (!last) return false; return /^[0-9]+$/.test(last) || parseIPv4Part(last) !== null; } /** The WHATWG IPv4 parser: up to 4 dot-separated parts (decimal, octal "0..." or hex * "0x..."), the last absorbing whatever bits remain, folded to a dotted-quad. */ function parseIPv4(domain: string): string | null { const parts = domain.split("."); if (parts.length > 1 && parts[parts.length - 1] === "") parts.pop(); if (parts.length > 4) return null; const numbers: number[] = []; for (const part of parts) { const n = parseIPv4Part(part); if (n === null) return null; numbers.push(n); } for (let i = 0; i < numbers.length - 1; i++) if (numbers[i]! > 255) return null; if (numbers[numbers.length - 1]! >= 256 ** (5 - numbers.length)) return null; let ipv4 = numbers.pop()!; for (let i = 0; i < numbers.length; i++) ipv4 += numbers[i]! * 256 ** (3 - i); return [(ipv4 >>> 24) & 255, (ipv4 >>> 16) & 255, (ipv4 >>> 8) & 255, ipv4 & 255].join("."); } /** The "set the host" setter (state override "hostname") plus UTS#46 ToASCII, over a * special-scheme URL — what bindingUrl.domainToASCII/domainToUnicode run before * either answers. null means the setter or ToASCII failed (both exports then answer * ''). A "[...]" form is assumed to be IPv6 and passed through unvalidated. */ function hostnameSetterToAscii(input: string): string | null { if (input.startsWith("[")) { return input.endsWith("]") && /^[0-9a-fA-F:.]*$/.test(input.slice(1, -1)) ? input : null; } let buffer = ""; let insideBrackets = false; for (let i = 0; i < input.length; i++) { const ch = input[i]!; if (ch === ":" && !insideBrackets) return null; if (ch === "/" || ch === "?" || ch === "#" || ch === "\\") break; if (ch === "[") insideBrackets = true; else if (ch === "]") insideBrackets = false; buffer += ch; } if (buffer === "") return null; const labels: string[] = []; for (const label of percentDecodeUtf8(buffer).split(".")) { if (label === "") { labels.push(label); continue; } const ascii = domainLabelToAscii(label); if (ascii === null) return null; labels.push(ascii); } const ascii = labels.join("."); if (hasForbiddenDomainChar(ascii)) return null; return endsInANumber(ascii) ? parseIPv4(ascii) : ascii; } function missingArg(name: string): Error { return Object.assign(new TypeError(`The "${name}" argument must be specified`), { code: "ERR_MISSING_ARGS" }); } /** url.domainToASCII: Node throws ERR_MISSING_ARGS with no argument at all (so * `arguments.length`, not a default parameter), then coerces whatever was passed * like a template literal would (a Symbol throws the ordinary TypeError that is). */ function domainToASCII(domain?: unknown): string { if (arguments.length < 1) throw missingArg("domain"); return hostnameSetterToAscii(`${domain}`) ?? ""; } function domainToUnicode(domain?: unknown): string { if (arguments.length < 1) throw missingArg("domain"); const ascii = hostnameSetterToAscii(`${domain}`); if (ascii === null) return ""; return ascii.split(".").map((label) => { if (!label.startsWith("xn--")) return label; try { return String.fromCodePoint(...punycodeDecode(label.slice(4))); } catch { return label; } }).join("."); } export interface Url { protocol: string | null; slashes: boolean | null; auth: string | null; host: string | null; port: string | null; hostname: string | null; hash: string | null; search: string | null; query: string | Record | null; pathname: string | null; path: string | null; href: string | null; parse(url: string, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url; format(): string; resolve(relative: string): string; resolveObject(relative: string | Url): Url; parseHost(): void; } export const Url = function Url(this: Url) { this.protocol = null; this.slashes = null; this.auth = null; this.host = null; this.port = null; this.hostname = null; this.hash = null; this.search = null; this.query = null; this.pathname = null; this.path = null; this.href = null; } as unknown as { new (): Url; prototype: Url }; Url.prototype.parse = function parse(this: Url, url: string, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url { if (typeof url !== "string") throw invalidArgType("url", "of type string", url); // Trim whitespace, and turn backslashes before the query or hash into slashes. let hasHash = false; let hasAt = false; let rest = ""; let split = false; const trimmed = url.replace(/^[\x00-\x20\u00a0\ufeff]+|[\x00-\x20\u00a0\ufeff]+$/g, ""); for (const ch of trimmed) { if (!split) { if (ch === "@") hasAt = true; else if (ch === "#") { hasHash = true; split = true; } else if (ch === "?") split = true; rest += ch === "\\" ? "/" : ch; } else { if (ch === "#") hasHash = true; rest += ch; } } if (!slashesDenoteHost && !hasHash && !hasAt) { const simplePath = simplePathPattern.exec(rest); if (simplePath) { this.path = rest; this.href = rest; this.pathname = simplePath[1]!; if (simplePath[2]) { this.search = simplePath[2]; this.query = parseQueryString ? qs.parse(this.search.slice(1)) : this.search.slice(1); } else if (parseQueryString) { this.search = null; this.query = Object.create(null); } return this; } } const protoMatch = protocolPattern.exec(rest); let proto: string | undefined; let lowerProto: string | undefined; if (protoMatch) { proto = protoMatch[0]; lowerProto = proto.toLowerCase(); this.protocol = lowerProto; rest = rest.slice(proto.length); } let slashes = false; if (slashesDenoteHost || proto || hostPattern.test(rest)) { slashes = rest.startsWith("//"); if (slashes && !(lowerProto && hostlessProtocol.has(lowerProto))) { rest = rest.slice(2); this.slashes = true; } } if (!(lowerProto && hostlessProtocol.has(lowerProto)) && (slashes || (proto && !slashedProtocol.has(proto)))) { let hostEnd = -1; let atSign = -1; let nonHost = -1; for (let i = 0; i < rest.length; ++i) { const ch = rest[i]!; if (NON_HOST.has(ch)) { if (nonHost === -1) nonHost = i; } else if (HOST_END.has(ch)) { if (nonHost === -1) nonHost = i; hostEnd = i; } else if (ch === "@") { atSign = i; nonHost = -1; } if (hostEnd !== -1) break; } let start = 0; if (atSign !== -1) { this.auth = decodeURIComponent(rest.slice(0, atSign)); start = atSign + 1; } if (nonHost === -1) { this.host = rest.slice(start); rest = ""; } else { this.host = rest.slice(start, nonHost); rest = rest.slice(nonHost); } this.parseHost(); if (typeof this.hostname !== "string") this.hostname = ""; const hostname = this.hostname; const ipv6 = isIpv6Hostname(hostname); if (!ipv6) { for (let i = 0; i < hostname.length; ++i) { if ("/\\#?:".includes(hostname[i]!)) { this.hostname = hostname.slice(0, i); rest = `/${hostname.slice(i)}${rest}`; break; } } } this.hostname = this.hostname.length > 255 ? "" : this.hostname.toLowerCase(); if (this.hostname !== "" && !ipv6) this.hostname = toASCII(this.hostname); this.host = (this.hostname || "") + (this.port ? `:${this.port}` : ""); if (ipv6) { this.hostname = this.hostname.slice(1, -1); if (rest[0] !== "/") rest = `/${rest}`; } } if (!(lowerProto && unsafeProtocol.has(lowerProto))) { rest = rest.replace(/[\t\n\r "'<>\\^`{|}]/g, (c) => AUTO_ESCAPE[c]!); } let questionIdx = -1; let hashIdx = -1; for (let i = 0; i < rest.length; ++i) { if (rest[i] === "#") { this.hash = rest.slice(i); hashIdx = i; break; } else if (rest[i] === "?" && questionIdx === -1) { questionIdx = i; } } if (questionIdx !== -1) { const end = hashIdx === -1 ? rest.length : hashIdx; this.search = rest.slice(questionIdx, end); this.query = rest.slice(questionIdx + 1, end); if (parseQueryString) this.query = qs.parse(this.query); } else if (parseQueryString) { this.search = null; this.query = Object.create(null); } const firstIdx = questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx) ? questionIdx : hashIdx; if (firstIdx === -1) { if (rest.length > 0) this.pathname = rest; } else if (firstIdx > 0) { this.pathname = rest.slice(0, firstIdx); } if (lowerProto && slashedProtocol.has(lowerProto) && this.hostname && !this.pathname) this.pathname = "/"; if (this.pathname || this.search) this.path = (this.pathname || "") + (this.search || ""); this.href = this.format(); return this; }; Url.prototype.parseHost = function parseHost(this: Url): void { let host = this.host ?? ""; const port = portPattern.exec(host); if (port) { if (port[0] !== ":") this.port = port[0].slice(1); host = host.slice(0, host.length - port[0].length); } if (host) this.hostname = host; }; Url.prototype.format = function format(this: Url): string { let auth = this.auth || ""; if (auth) auth = `${encodeURIComponent(auth).replace(/%3A/g, ":")}@`; let protocol = this.protocol || ""; let pathname = this.pathname || ""; let hash = this.hash || ""; let host = ""; let query = ""; if (this.host) { host = auth + this.host; } else if (this.hostname) { host = auth + (this.hostname.includes(":") && !isIpv6Hostname(this.hostname) ? `[${this.hostname}]` : this.hostname); if (this.port) host += `:${this.port}`; } if (this.query !== null && typeof this.query === "object") query = qs.stringify(this.query); let search = this.search || (query && `?${query}`) || ""; if (protocol && !protocol.endsWith(":")) protocol += ":"; pathname = pathname.replace(/[#?]/g, (c) => (c === "#" ? "%23" : "%3F")); if (this.slashes || slashedProtocol.has(protocol)) { if (this.slashes || host) { if (pathname && pathname[0] !== "/") pathname = `/${pathname}`; host = `//${host}`; } else if (protocol.startsWith("file")) { host = "//"; } } search = search.replace(/#/g, "%23"); if (hash && hash[0] !== "#") hash = `#${hash}`; if (search && search[0] !== "?") search = `?${search}`; return protocol + host + pathname + search + hash; }; /** Legacy resolution, via WHATWG resolution against a placeholder base for relative inputs. */ Url.prototype.resolve = function resolve(this: Url, relative: string): string { const from = this.format(); const base = new URL(from, "resolve://"); const resolved = new URL(relative, base); if (resolved.protocol === "resolve:") { const { pathname, search, hash } = resolved; return from.startsWith("/") || from === "" ? `${pathname}${search}${hash}` : `${pathname.slice(1)}${search}${hash}`; } return resolved.toString(); }; Url.prototype.resolveObject = function resolveObject(this: Url, relative: string | Url): Url { const rel = typeof relative === "string" ? relative : relative.format(); return parse(this.resolve(rel)); }; export function parse(url: string | Url, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url { if (url instanceof Url) return url; return new Url().parse(url, parseQueryString, slashesDenoteHost); } export function format(urlObject: unknown, options?: { auth?: boolean; fragment?: boolean; search?: boolean; unicode?: boolean }): string { if (typeof urlObject === "string") return parse(urlObject).format(); if (typeof urlObject !== "object" || urlObject === null) throw invalidArgType("urlObject", "one of type object or string", urlObject); if (urlObject instanceof URL) { const u = new URL(urlObject.href); if (options?.auth === false) { u.username = ""; u.password = ""; } if (options?.fragment === false) u.hash = ""; if (options?.search === false) u.search = ""; return u.href; } return Url.prototype.format.call(urlObject as Url); } export function resolve(from: string, to: string): string { return parse(from, false, true).resolve(to); } function fileUrlError(code: string, message: string): TypeError { return Object.assign(new TypeError(message), { code }); } export function fileURLToPath(input: string | URL): string { const url = typeof input === "string" ? new URL(input) : input; if (!(url instanceof URL)) throw invalidArgType("path", "of type string or an instance of URL", input); if (url.protocol !== "file:") throw fileUrlError("ERR_INVALID_URL_SCHEME", "The URL must be of scheme file"); if (url.hostname !== "") throw fileUrlError("ERR_INVALID_FILE_URL_HOST", 'File URL host must be "localhost" or empty on linux'); if (/%2f/i.test(url.pathname)) throw fileUrlError("ERR_INVALID_FILE_URL_PATH", "File URL path must not include encoded / characters"); return decodeURIComponent(url.pathname); } /** fileURLToPath, as a Buffer — for a path whose bytes are not valid UTF-8 on a * case-sensitive filesystem; this runtime's VFS is UTF-8, so it is fileURLToPath * wrapped, kept for API parity. */ export function fileURLToPathBuffer(input: string | URL): Buffer { return Buffer.from(fileURLToPath(input)); } /** url.URLPattern: the browser's own, re-exported — it is the same WHATWG URL Pattern * API Node ports (see this chunk's report for where it is missing). */ export const URLPattern = (globalThis as { URLPattern?: unknown }).URLPattern as typeof globalThis.URLPattern ?? refusedClass("url", "URLPattern", "this engine has no native URLPattern"); // The runtime worker's own script URL, however it was served (e.g. /dist/runtime/runtime.js or // /__nib/0.1.0/runtime.js): import.meta.url, since this module is bundled into that one // worker file, resolves to the worker's own address, with any `?nib-host`-style query // string stripped so it still matches frames that include one. const RUNTIME_SCRIPT_URL = import.meta.url.split("?")[0]!; /** Node's isInsideNodeModules: the first frame outside the runtime decides. */ function calledFromNodeModules(): boolean { const frames = (new Error().stack ?? "").split("\n").slice(1); const guest = frames.find((f) => !f.includes(RUNTIME_SCRIPT_URL)); return guest?.includes("/node_modules/") ?? false; } export function makeUrl(resolvePath: (p: string) => string, emitWarning: (msg: string, type: string, code: string) => void) { let warnedParse = false; /** url.parse warns once (DEP0169), and only for calls from outside node_modules. */ function parseWithWarning(url: string | Url, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url { if (!warnedParse && !calledFromNodeModules()) { warnedParse = true; emitWarning("`url.parse()` behavior is not standardized and prone to errors that have security implications. " + "Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.", "DeprecationWarning", "DEP0169"); } return parse(url, parseQueryString, slashesDenoteHost); } function pathToFileURL(filepath: string): URL { if (typeof filepath !== "string") throw invalidArgType("path", "of type string", filepath); let resolved = resolvePath(filepath); if (filepath.endsWith("/") && !resolved.endsWith("/")) resolved += "/"; const out = new URL("file://"); out.pathname = resolved.replace(/[%\\\n\r\t]/g, (c) => ({ "%": "%25", "\\": "%5C", "\n": "%0A", "\r": "%0D", "\t": "%09" })[c]!); return out; } return { URL, URLSearchParams, Url, parse: parseWithWarning, format, resolve, resolveObject: (from: string, to: string) => parse(from, false, true).resolveObject(to), fileURLToPath, fileURLToPathBuffer, pathToFileURL, URLPattern, domainToASCII, domainToUnicode, urlToHttpOptions: (url: URL) => ({ protocol: url.protocol, hostname: url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname, hash: url.hash, search: url.search, pathname: url.pathname, path: `${url.pathname || ""}${url.search || ""}`, href: url.href, ...(url.port !== "" ? { port: Number(url.port) } : {}), ...(url.username || url.password ? { auth: `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}` } : {}), }), }; }