cli: fix +ssh-cache IPv6 address validation
The host validation code previously expected IPv6 addresses to be enclosed in [brackets], but that's not how ssh(1) expects them. This change removes that requirement and reimplements the host validation routine to check for valid hostnames and IP addresses (IPv4 and IPv6) using standard routines rather than custom logic.pull/9281/head
parent
40e558f953
commit
e522482710
|
|
@ -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"));
|
||||
|
|
|
|||
Loading…
Reference in New Issue