Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Improve localhost detection
Rūdolfs Ošiņš committed 3 months ago
commit ebe4e426e827dc9db2fed87b23317ac41d43f96d
parent 8f52b2b
2 files changed +59 -16
modified src/lib/utils.ts
@@ -204,8 +204,19 @@ export function isMarkdownPath(path: string): boolean {
}

export function isLocal(addr: string): boolean {
-
  // Support *.localhost subdomains and 127.0.0.1.
-
  return addr.endsWith("localhost") || addr.startsWith("127.0.0.1");
+
  try {
+
    const url = new URL(`scheme://${addr}`);
+
    const hostname = url.hostname;
+

+
    return (
+
      hostname === "[::1]" ||
+
      hostname === "127.0.0.1" ||
+
      hostname === "localhost" ||
+
      hostname.endsWith(".localhost")
+
    );
+
  } catch {
+
    return false;
+
  }
}

// Check whether the given domain name is an onion domain name.
modified tests/unit/utils.test.ts
@@ -236,21 +236,53 @@ 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);
+
  describe("returns true for valid local addresses", () => {
+
    test.each([
+
      { addr: "localhost", description: "bare localhost" },
+
      { addr: "app.localhost", description: "localhost subdomain" },
+
      {
+
        addr: "deeper.app.localhost",
+
        description: "nested localhost subdomain",
+
      },
+
      {
+
        addr: "deeper.app.localhost:8080",
+
        description: "nested localhost subdomain with port",
+
      },
+
      { addr: "127.0.0.1", description: "IPv4 loopback" },
+
      { addr: "127.0.0.1:8080", description: "IPv4 loopback with port" },
+
      { addr: "[::1]", description: "IPv6 loopback" },
+
      {
+
        addr: "localhost:3000",
+
        description: "localhost with port",
+
      },
+
    ])("$description: $addr", ({ addr }) => {
+
      expect(utils.isLocal(addr)).toBe(true);
+
    });
  });

-
  test("returns false for any other tld", () => {
-
    expect(utils.isLocal("example.com")).toBe(false);
-
    expect(utils.isLocal("radicle.garden")).toBe(false);
+
  describe("returns false for invalid/remote addresses", () => {
+
    test.each([
+
      { addr: "example.com", description: "remote domain" },
+
      {
+
        addr: "notlocalhost",
+
        description: "domain that ends with 'localhost'",
+
      },
+
      {
+
        addr: "127.0.0.1.evil.com",
+
        description: "malicious domain mimicking loopback",
+
      },
+
      { addr: "192.168.1.1", description: "private network IP" },
+
      { addr: "10.0.0.1", description: "another private network IP" },
+
      { addr: "", description: "empty string" },
+
      { addr: "not a url", description: "invalid URL format" },
+
      { addr: "localhost.com", description: "domain with localhost in name" },
+
      { addr: "mylocalhost.com", description: "domain containing localhost" },
+
      {
+
        addr: "http://example.com",
+
        description: "remote domain with scheme",
+
      },
+
    ])("$description: $addr", ({ addr }) => {
+
      expect(utils.isLocal(addr)).toBe(false);
+
    });
  });
});