diff --git a/src/cli/ssh-cache/DiskCache.zig b/src/cli/ssh-cache/DiskCache.zig index 608155dfd..25d2cd42e 100644 --- a/src/cli/ssh-cache/DiskCache.zig +++ b/src/cli/ssh-cache/DiskCache.zig @@ -7,8 +7,9 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const xdg = @import("../../os/main.zig").xdg; -const TempDir = @import("../../os/main.zig").TempDir; +const internal_os = @import("../../os/main.zig"); +const xdg = internal_os.xdg; +const TempDir = internal_os.TempDir; const Entry = @import("Entry.zig"); // 512KB - sufficient for approximately 10k entries @@ -334,48 +335,28 @@ fn isValidCacheKey(key: []const u8) bool { if (std.mem.indexOf(u8, key, "@")) |at_pos| { const user = key[0..at_pos]; const hostname = key[at_pos + 1 ..]; - return isValidUser(user) and isValidHostname(hostname); + return isValidUser(user) and isValidHost(hostname); } - return isValidHostname(key); + return isValidHost(key); } -// Basic hostname validation - accepts domains and IPs -// (including IPv6 in brackets) -fn isValidHostname(host: []const u8) bool { - if (host.len == 0 or host.len > 253) return false; - - // Handle IPv6 addresses in brackets - if (host.len >= 4 and host[0] == '[' and host[host.len - 1] == ']') { - const ipv6_part = host[1 .. host.len - 1]; - if (ipv6_part.len == 0) return false; - var has_colon = false; - for (ipv6_part) |c| { - switch (c) { - 'a'...'f', 'A'...'F', '0'...'9' => {}, - ':' => has_colon = true, - else => return false, - } - } - return has_colon; +// Checks if a host is a valid hostname or IP address +fn isValidHost(host: []const u8) bool { + // First check for valid hostnames because this is assumed to be the more + // likely ssh host format. + if (internal_os.hostname.isValid(host)) { + return true; } - // Standard hostname/domain validation - for (host) |c| { - switch (c) { - 'a'...'z', 'A'...'Z', '0'...'9', '.', '-' => {}, - else => return false, - } - } - - // No leading/trailing dots or hyphens, no consecutive dots - if (host[0] == '.' or host[0] == '-' or - host[host.len - 1] == '.' or host[host.len - 1] == '-') - { + // We also accept valid IP addresses. In practice, IPv4 addresses are also + // considered valid hostnames due to their overlapping syntax, so we can + // simplify this check to be IPv6-specific. + if (std.net.Address.parseIp6(host, 0)) |_| { + return true; + } else |_| { return false; } - - return std.mem.indexOf(u8, host, "..") == null; } fn isValidUser(user: []const u8) bool { @@ -475,42 +456,36 @@ test "disk cache operations" { } // Tests -test "hostname validation - valid cases" { - const testing = std.testing; - try testing.expect(isValidHostname("example.com")); - try testing.expect(isValidHostname("sub.example.com")); - try testing.expect(isValidHostname("host-name.domain.org")); - try testing.expect(isValidHostname("192.168.1.1")); - try testing.expect(isValidHostname("a")); - try testing.expect(isValidHostname("1")); -} -test "hostname validation - IPv6 addresses" { +test isValidHost { const testing = std.testing; - try testing.expect(isValidHostname("[::1]")); - try testing.expect(isValidHostname("[2001:db8::1]")); - try testing.expect(!isValidHostname("[fe80::1%eth0]")); // Interface notation not supported - try testing.expect(!isValidHostname("[]")); // Empty IPv6 - try testing.expect(!isValidHostname("[invalid]")); // No colons -} -test "hostname validation - invalid cases" { - const testing = std.testing; - try testing.expect(!isValidHostname("")); - try testing.expect(!isValidHostname("host\nname")); - try testing.expect(!isValidHostname(".example.com")); - try testing.expect(!isValidHostname("example.com.")); - try testing.expect(!isValidHostname("host..domain")); - try testing.expect(!isValidHostname("-hostname")); - try testing.expect(!isValidHostname("hostname-")); - try testing.expect(!isValidHostname("host name")); - try testing.expect(!isValidHostname("host_name")); - try testing.expect(!isValidHostname("host@domain")); - try testing.expect(!isValidHostname("host:port")); + // Valid hostnames + try testing.expect(isValidHost("localhost")); + try testing.expect(isValidHost("example.com")); + try testing.expect(isValidHost("sub.example.com")); - // Too long - const long_host = "a" ** 254; - try testing.expect(!isValidHostname(long_host)); + // IPv4 addresses + try testing.expect(isValidHost("127.0.0.1")); + try testing.expect(isValidHost("192.168.1.1")); + + // IPv6 addresses + try testing.expect(isValidHost("::1")); + try testing.expect(isValidHost("2001:db8::1")); + try testing.expect(isValidHost("2001:db8:0:1:1:1:1:1")); + try testing.expect(!isValidHost("fe80::1%eth0")); // scopes not supported + + // Invalid hosts + try testing.expect(!isValidHost("")); + try testing.expect(!isValidHost("host\nname")); + try testing.expect(!isValidHost(".example.com")); + try testing.expect(!isValidHost("host..domain")); + try testing.expect(!isValidHost("-hostname")); + try testing.expect(!isValidHost("hostname-")); + try testing.expect(!isValidHost("host name")); + try testing.expect(!isValidHost("host_name")); + try testing.expect(!isValidHost("host@domain")); + try testing.expect(!isValidHost("host:port")); } test "user validation - valid cases" { @@ -551,7 +526,7 @@ test "cache key validation - hostname format" { try testing.expect(isValidCacheKey("example.com")); try testing.expect(isValidCacheKey("sub.example.com")); try testing.expect(isValidCacheKey("192.168.1.1")); - try testing.expect(isValidCacheKey("[::1]")); + try testing.expect(isValidCacheKey("::1")); try testing.expect(!isValidCacheKey("")); try testing.expect(!isValidCacheKey(".invalid.com")); } @@ -563,7 +538,7 @@ test "cache key validation - user@hostname format" { try testing.expect(isValidCacheKey("test-user@192.168.1.1")); try testing.expect(isValidCacheKey("user_name@host.domain.org")); try testing.expect(isValidCacheKey("git@github.com")); - try testing.expect(isValidCacheKey("ubuntu@[::1]")); + try testing.expect(isValidCacheKey("ubuntu@::1")); try testing.expect(!isValidCacheKey("@example.com")); try testing.expect(!isValidCacheKey("user@")); try testing.expect(!isValidCacheKey("user@@host"));