From 11f8b4ce8226494156fbbdfa04c9c09d072c8884 Mon Sep 17 00:00:00 2001 From: "loongtao.zhang" Date: Tue, 28 Apr 2026 15:29:42 +0800 Subject: [PATCH 1/4] Update isLocalURL to include LAN addresses, .local domains, and hostnames without dots --- src/utils/url.test.ts | 50 +++++++++++++++++++++++++++++++++++++++++++ src/utils/url.ts | 18 +++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/utils/url.test.ts diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts new file mode 100644 index 0000000000..96ee3c9cd6 --- /dev/null +++ b/src/utils/url.test.ts @@ -0,0 +1,50 @@ + +import { isLocalURL } from './url'; + +describe('isLocalURL', () => { + it('should return true for localhost', () => { + expect(isLocalURL('http://localhost')).toBe(true); + expect(isLocalURL('http://localhost:3000')).toBe(true); + }); + + it('should return true for 127.0.0.1', () => { + expect(isLocalURL('http://127.0.0.1')).toBe(true); + }); + + it('should return true for ::1', () => { + expect(isLocalURL('http://[::1]')).toBe(true); + }); + + it('should return true for LAN addresses', () => { + expect(isLocalURL('http://10.0.0.1')).toBe(true); + expect(isLocalURL('http://10.255.255.255')).toBe(true); + expect(isLocalURL('http://172.16.0.1')).toBe(true); + expect(isLocalURL('http://172.31.255.255')).toBe(true); + expect(isLocalURL('http://192.168.1.1')).toBe(true); + }); + + it('should return false for other public IP addresses', () => { + expect(isLocalURL('http://8.8.8.8')).toBe(false); + expect(isLocalURL('http://172.15.255.255')).toBe(false); + expect(isLocalURL('http://172.32.0.0')).toBe(false); + }); + + it('should return true for .local domains', () => { + expect(isLocalURL('http://myserver.local')).toBe(true); + expect(isLocalURL('http://test.local:8080')).toBe(true); + }); + + it('should return true for hostnames without dots', () => { + expect(isLocalURL('http://mycomputer')).toBe(true); + expect(isLocalURL('http://dev-server:8000')).toBe(true); + }); + + it('should return false for public domains', () => { + expect(isLocalURL('https://firefox.com')).toBe(false); + expect(isLocalURL('https://profiler.firefox.com')).toBe(false); + }); + + it('should return false for invalid URLs', () => { + expect(isLocalURL('not-a-url')).toBe(false); + }); +}); diff --git a/src/utils/url.ts b/src/utils/url.ts index c42e9272f2..de7d2f6ab3 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -15,7 +15,23 @@ export const localhostHostnames: readonly string[] = [ export function isLocalURL(url: string | URL): boolean { try { const parsedUrl = url instanceof URL ? url : new URL(url); - return localhostHostnames.includes(parsedUrl.hostname); + const hostname = parsedUrl.hostname; + if (localhostHostnames.includes(hostname)) { + return true; + } + // LAN addresses: 10.x.x.x, 172.16.x.x-172.31.x.x, 192.168.x.x + if ( + /^10\./.test(hostname) || + /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname) || + /^192\.168\./.test(hostname) + ) { + return true; + } + // .local domains or hostnames without dots + if (hostname.endsWith('.local') || !hostname.includes('.')) { + return true; + } + return false; } catch (_e) { return false; } From 8ee35b361cf8bb8de41645b5f4e02da6a5345681 Mon Sep 17 00:00:00 2001 From: "loongtao.zhang" Date: Tue, 28 Apr 2026 15:32:15 +0800 Subject: [PATCH 2/4] Update isLocalURL to include IPv6 local addresses (Link-local and ULA) --- src/utils/url.test.ts | 6 ++++++ src/utils/url.ts | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts index 96ee3c9cd6..0b7e339bb6 100644 --- a/src/utils/url.test.ts +++ b/src/utils/url.test.ts @@ -15,6 +15,12 @@ describe('isLocalURL', () => { expect(isLocalURL('http://[::1]')).toBe(true); }); + it('should return true for IPv6 local addresses', () => { + expect(isLocalURL('http://[fe80::1]')).toBe(true); + expect(isLocalURL('http://[fd00::1]')).toBe(true); + expect(isLocalURL('http://[fc00::1]')).toBe(true); + }); + it('should return true for LAN addresses', () => { expect(isLocalURL('http://10.0.0.1')).toBe(true); expect(isLocalURL('http://10.255.255.255')).toBe(true); diff --git a/src/utils/url.ts b/src/utils/url.ts index de7d2f6ab3..715229af35 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -27,6 +27,16 @@ export function isLocalURL(url: string | URL): boolean { ) { return true; } + // IPv6 local addresses: + // [fe80::...] (Link-local) + // [fc00::...] or [fd00::...] (Unique Local Address) + if ( + hostname.startsWith('[fe80:') || + hostname.startsWith('[fc00:') || + hostname.startsWith('[fd00:') + ) { + return true; + } // .local domains or hostnames without dots if (hostname.endsWith('.local') || !hostname.includes('.')) { return true; From 7e1b79c4a99deec05c92409165052d0c34b256f3 Mon Sep 17 00:00:00 2001 From: "loongtao.zhang" Date: Tue, 28 Apr 2026 15:36:47 +0800 Subject: [PATCH 3/4] Further expand isLocalURL to include CGNAT, IPv4 link-local, and benchmark ranges --- src/utils/url.test.ts | 4 ++++ src/utils/url.ts | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/utils/url.test.ts b/src/utils/url.test.ts index 0b7e339bb6..c1b2f1f143 100644 --- a/src/utils/url.test.ts +++ b/src/utils/url.test.ts @@ -27,6 +27,10 @@ describe('isLocalURL', () => { expect(isLocalURL('http://172.16.0.1')).toBe(true); expect(isLocalURL('http://172.31.255.255')).toBe(true); expect(isLocalURL('http://192.168.1.1')).toBe(true); + expect(isLocalURL('http://100.100.100.100')).toBe(true); + expect(isLocalURL('http://169.254.1.1')).toBe(true); + expect(isLocalURL('http://198.18.0.1')).toBe(true); + expect(isLocalURL('http://127.0.0.2')).toBe(true); }); it('should return false for other public IP addresses', () => { diff --git a/src/utils/url.ts b/src/utils/url.ts index 715229af35..35a70bd87a 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -19,21 +19,27 @@ export function isLocalURL(url: string | URL): boolean { if (localhostHostnames.includes(hostname)) { return true; } - // LAN addresses: 10.x.x.x, 172.16.x.x-172.31.x.x, 192.168.x.x + // IPv4 ranges: if ( - /^10\./.test(hostname) || - /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname) || - /^192\.168\./.test(hostname) + /^127\./.test(hostname) || // Loopback 127.0.0.0/8 + /^10\./.test(hostname) || // Private 10.0.0.0/8 + /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname) || // Private 172.16.0.0/12 + /^192\.168\./.test(hostname) || // Private 192.168.0.0/16 + /^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./.test(hostname) || // CGNAT 100.64.0.0/10 + /^169\.254\./.test(hostname) || // Link-local 169.254.0.0/16 + /^198\.(1[8-9])\./.test(hostname) // Benchmark 198.18.0.0/15 ) { return true; } // IPv6 local addresses: // [fe80::...] (Link-local) // [fc00::...] or [fd00::...] (Unique Local Address) + // [ff00::...] (Multicast) if ( hostname.startsWith('[fe80:') || hostname.startsWith('[fc00:') || - hostname.startsWith('[fd00:') + hostname.startsWith('[fd00:') || + hostname.startsWith('[ff') ) { return true; } From 7b3c2f1d2d80b636c1db3b2088952fa22c837d4d Mon Sep 17 00:00:00 2001 From: "loongtao.zhang" Date: Thu, 30 Apr 2026 18:15:52 +0800 Subject: [PATCH 4/4] Apply suggestion --- src/{utils => test/unit}/url.test.ts | 5 ++-- src/utils/url.ts | 37 +++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) rename src/{utils => test/unit}/url.test.ts (92%) diff --git a/src/utils/url.test.ts b/src/test/unit/url.test.ts similarity index 92% rename from src/utils/url.test.ts rename to src/test/unit/url.test.ts index c1b2f1f143..2a3837dca2 100644 --- a/src/utils/url.test.ts +++ b/src/test/unit/url.test.ts @@ -1,5 +1,4 @@ - -import { isLocalURL } from './url'; +import { isLocalURL } from 'firefox-profiler/utils/url'; describe('isLocalURL', () => { it('should return true for localhost', () => { @@ -19,6 +18,8 @@ describe('isLocalURL', () => { expect(isLocalURL('http://[fe80::1]')).toBe(true); expect(isLocalURL('http://[fd00::1]')).toBe(true); expect(isLocalURL('http://[fc00::1]')).toBe(true); + expect(isLocalURL('http://[ff00::1]')).toBe(false); + expect(isLocalURL('http://[::ffff:1.1.1.1]')).toBe(false); }); it('should return true for LAN addresses', () => { diff --git a/src/utils/url.ts b/src/utils/url.ts index 35a70bd87a..e5fbdb7a22 100644 --- a/src/utils/url.ts +++ b/src/utils/url.ts @@ -5,7 +5,7 @@ export const localhostHostnames: readonly string[] = [ 'localhost', '127.0.0.1', - '::1', + '[::1]', ]; // Used to determine if a URL (usually one that's provided a profile) @@ -34,17 +34,14 @@ export function isLocalURL(url: string | URL): boolean { // IPv6 local addresses: // [fe80::...] (Link-local) // [fc00::...] or [fd00::...] (Unique Local Address) - // [ff00::...] (Multicast) if ( hostname.startsWith('[fe80:') || hostname.startsWith('[fc00:') || - hostname.startsWith('[fd00:') || - hostname.startsWith('[ff') + hostname.startsWith('[fd00:') ) { return true; } - // .local domains or hostnames without dots - if (hostname.endsWith('.local') || !hostname.includes('.')) { + if (isLocalHostName(hostname)) { return true; } return false; @@ -53,6 +50,34 @@ export function isLocalURL(url: string | URL): boolean { } } +/** + * http://hostname => true + * https://hostname => true + * http://hostname:8080 => true + * https://hostname.local => true + * http://hostname.local:8080 => true + * http://xxx.com=> false + * http://xxx.com:8080 => false + * http://1.1.1.1=> false + * http://1.1.1.1:8080 => false + * http://[::1]=> false + * http://[::1]:8080 => false + */ +function isLocalHostName(hostname: string): boolean { + if (!hostname) { + return false; + } + + // IPv6 literals are bracketed when parsed from a URL, and IPv4 literals are + // dot-delimited numeric segments. Neither should be treated as local + // hostnames here; those cases are handled separately in isLocalURL. + if (hostname.startsWith('[') || /^\d+(?:\.\d+){3}$/.test(hostname)) { + return false; + } + + return hostname.endsWith('.local') || !hostname.includes('.'); +} + /** * Escape a URL string so it can be safely embedded inside a double-quoted CSS * url("...").