diff --git a/src/lib/router.ts b/src/lib/router.ts index 285d1e61a..688fbf5da 100644 --- a/src/lib/router.ts +++ b/src/lib/router.ts @@ -243,4 +243,4 @@ export function routeToPath(route: Route): string { } } -export const testExports = { urlToRoute, routeToPath }; +export const testExports = { urlToRoute, routeToPath, extractBaseUrl }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 5c5c3eb87..a6c5f71d5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -203,9 +203,9 @@ export function isMarkdownPath(path: string): boolean { return /\.(md|mkd|markdown)$/i.test(path); } -// Check whether the given address is a localhost address. export function isLocal(addr: string): boolean { - return addr.startsWith("127.0.0.1") || addr.startsWith("localhost"); + // Support *.localhost subdomains and 127.0.0.1. + return addr.endsWith("localhost") || addr.startsWith("127.0.0.1"); } // Check whether the given domain name is an onion domain name. diff --git a/tests/unit/router.test.ts b/tests/unit/router.test.ts index e220f816b..b08e3d7ff 100644 --- a/tests/unit/router.test.ts +++ b/tests/unit/router.test.ts @@ -269,3 +269,95 @@ describe("pathToRoute", () => { ).toEqual(route); } }); + +describe("extractBaseUrl", () => { + test("hostname with explicit port", () => { + const result = testExports.extractBaseUrl("example.com:9000"); + expect(result).toEqual({ + hostname: "example.com", + port: 9000, + scheme: config.nodes.defaultHttpdScheme, + }); + }); + + test("hostname without port uses default port and scheme", () => { + const result = testExports.extractBaseUrl("example.com"); + expect(result).toEqual({ + hostname: "example.com", + port: config.nodes.defaultHttpdPort, + scheme: config.nodes.defaultHttpdScheme, + }); + }); + + test("localhost without port uses local port and http scheme", () => { + const result = testExports.extractBaseUrl("localhost"); + expect(result).toEqual({ + hostname: "localhost", + port: config.nodes.defaultLocalHttpdPort, + scheme: "http", + }); + }); + + test("localhost with explicit port uses http scheme", () => { + const result = testExports.extractBaseUrl("localhost:3000"); + expect(result).toEqual({ + hostname: "localhost", + port: 3000, + scheme: "http", + }); + }); + + test("wildcard localhost domain without port uses local port and http scheme", () => { + const result = testExports.extractBaseUrl("app.localhost"); + expect(result).toEqual({ + hostname: "app.localhost", + port: config.nodes.defaultLocalHttpdPort, + scheme: "http", + }); + }); + + test("127.0.0.1 without port uses local port and http scheme", () => { + const result = testExports.extractBaseUrl("127.0.0.1"); + expect(result).toEqual({ + hostname: "127.0.0.1", + port: config.nodes.defaultLocalHttpdPort, + scheme: "http", + }); + }); + + test("127.0.0.1 with explicit port uses http scheme", () => { + const result = testExports.extractBaseUrl("127.0.0.1:8080"); + expect(result).toEqual({ + hostname: "127.0.0.1", + port: 8080, + scheme: "http", + }); + }); + + test("onion domain without port uses default port and http scheme", () => { + const result = testExports.extractBaseUrl("example.onion"); + expect(result).toEqual({ + hostname: "example.onion", + port: config.nodes.defaultHttpdPort, + scheme: "http", + }); + }); + + test("onion domain with explicit port uses http scheme", () => { + const result = testExports.extractBaseUrl("example.onion:9050"); + expect(result).toEqual({ + hostname: "example.onion", + port: 9050, + scheme: "http", + }); + }); + + test("URL-encoded hostname is decoded", () => { + const result = testExports.extractBaseUrl("example.com%3A8000"); + expect(result).toEqual({ + hostname: "example.com", + port: 8000, + scheme: config.nodes.defaultHttpdScheme, + }); + }); +}); diff --git a/tests/unit/utils.test.ts b/tests/unit/utils.test.ts index 85446a33a..9cabb63b5 100644 --- a/tests/unit/utils.test.ts +++ b/tests/unit/utils.test.ts @@ -234,3 +234,23 @@ describe("Date Manipulation", () => { ); }); }); + +describe("isLocal", () => { + test("returns true for localhost", () => { + expect(utils.isLocal("localhost")).toBe(true); + }); + + test("returns true for 127.0.0.1", () => { + expect(utils.isLocal("127.0.0.1")).toBe(true); + }); + + test("returns true for localhost subdomains", () => { + expect(utils.isLocal("test.localhost")).toBe(true); + expect(utils.isLocal("deeper.test.localhost")).toBe(true); + }); + + test("returns false for any other tld", () => { + expect(utils.isLocal("example.com")).toBe(false); + expect(utils.isLocal("radicle.garden")).toBe(false); + }); +});