export interface ConfiguredTransportUrlOptions { allowLoopbackHttp: boolean; originOnly?: boolean; } export function parseCredentialFreeHttpUrl(value: string): URL | undefined { let url: URL; try { url = new URL(value); } catch { return undefined; } if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) return undefined; return url; } function canonicalLoopbackAuthority(value: string): boolean { const match = /^http:\/\/([^/?#]+)(?:[/?#]|$)/.exec(value); if (!match) return false; const authority = match[1]; let port: string | undefined; if (authority.startsWith("[")) { const ipv6 = /^(\[::1\])(?::([^:]+))?$/.exec(authority); if (!ipv6) return false; port = ipv6[2]; } else { const ipv4 = /^([^:]+)(?::([^:]+))?$/.exec(authority); if (!ipv4) return false; const octets = ipv4[1].split("."); if (octets.length !== 4 || octets.some((octet) => !/^(?:0|[1-9]\d{0,2})$/.test(octet) || Number(octet) > 255) || Number(octets[0]) !== 127) return false; port = ipv4[2]; } return port === undefined || (/^(?:0|[1-9]\d{0,4})$/.test(port) && Number(port) <= 65_535); } export function parseConfiguredTransportUrl( value: string, options: ConfiguredTransportUrlOptions, ): URL | undefined { const url = parseCredentialFreeHttpUrl(value); if (!url || (options.originOnly && url.pathname !== "/")) return undefined; if (url.protocol === "https:") return url; if (options.allowLoopbackHttp && url.protocol === "http:" && canonicalLoopbackAuthority(value)) return url; return undefined; }