// The same rule the server applies before it saves a portal link, and that its email readers apply // before they use one: https, and a host people outside the office can reach. A developer install // reports https://.local, which passes a protocol check and fails in a patient's inbox. const UNREACHABLE_HOST = /(?:^|[.])(?:local|localhost|invalid)$/i; // Loopback, private, link-local and shared-address IPv4 ranges. URL parsing has already normalised the host. const isPrivateIPv4 = (host: string): boolean => { const octets = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)?.slice(1).map(Number); if (!octets) return false; const [a, b] = octets; return a === 0 || a === 10 || a === 127 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127); }; // Loopback, unspecified, unique-local, link-local, and IPv4-mapped private addresses. The parser writes a // mapped address as two hex groups, so those are turned back into dotted form. const isPrivateIPv6 = (bracketed: string): boolean => { const host = bracketed.slice(1, -1).toLowerCase(); if (host === '::1' || host === '::' || /^f[cd]/.test(host) || /^fe[89ab]/.test(host)) return true; const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/); if (!mapped) return false; const [high, low] = [parseInt(mapped[1], 16), parseInt(mapped[2], 16)]; return isPrivateIPv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`); }; const isUnreachableHost = (host: string): boolean => { if (host.startsWith('[')) return isPrivateIPv6(host); return UNREACHABLE_HOST.test(host) || !host.includes('.') || isPrivateIPv4(host); }; // A host nobody outside the office can reach: a .local or private address. A public host on plain http is // not one - that is a site to move to https, and the link waits for it. export const isLocalPortalAddress = (url: string | undefined): boolean => { if (!url) return false; try { return isUnreachableHost(new URL(url).hostname); } catch { return false; } }; export const isPublicPortalAddress = (url: string | undefined): boolean => { if (!url) return false; try { const parsed = new URL(url); return parsed.protocol === 'https:' && !isUnreachableHost(parsed.hostname); } catch { return false; } };