* URL hygiene helpers for `web_fetch` (mirrors §5.2 W1, W2, W3, W7).
*/
export const MAX_URL_LENGTH = 2000;
* W2: validate that the URL is well-formed, has no embedded credentials,
* and resolves to a publicly-routable hostname (multi-part DNS name).
*/
export function validateURL(url: string): boolean {
if (url.length > MAX_URL_LENGTH) return false;
let parsed: URL;
try {
parsed = new URL(url);
} catch {
return false;
}
if (parsed.username || parsed.password) return false;
const parts = parsed.hostname.split(".").filter((p) => p.length > 0);
if (parts.length < 2) return false;
return true;
}
* W3: upgrade `http://` to `https://`. Returns the upgraded URL string and
* the parsed URL for reuse.
*/
export function upgradeHttpToHttps(url: string): { upgraded: string; parsed: URL } {
const parsed = new URL(url);
if (parsed.protocol === "http:") {
parsed.protocol = "https:";
}
return { upgraded: parsed.toString(), parsed };
}
* W7: a redirect is "permitted" when:
* - protocol matches,
* - port matches,
* - destination has no embedded credentials,
* - hostname differs only in the optional `www.` prefix (or matches
* exactly).
*
* This is checked *before* following the redirect; if it fails, the caller
* should surface the redirect target to the user (one-shot redirect info)
* rather than auto-following.
*/
export function isPermittedRedirect(originalUrl: string, redirectUrl: string): boolean {
let original: URL;
let redirect: URL;
try {
original = new URL(originalUrl);
redirect = new URL(redirectUrl);
} catch {
return false;
}
if (redirect.protocol !== original.protocol) return false;
if (redirect.port !== original.port) return false;
if (redirect.username || redirect.password) return false;
const stripWww = (h: string) => h.replace(/^www\./, "");
return stripWww(original.hostname) === stripWww(redirect.hostname);
}