48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
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;
|
|
}
|