Skip to content

Commit 5e439c0

Browse files
committed
std.net: make isValidHostName RFC 1123-compliant
The implementation of isValidHostName was too generous. It considered strings like ".example.com", "exa..mple.com", and "-example.com" to be valid hostnames, which is incorrect according to RFC 1123 (the currently accepted standard).
1 parent 6b4f57a commit 5e439c0

File tree

1 file changed

+75
-6
lines changed

1 file changed

+75
-6
lines changed

lib/std/net.zig

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,18 +1462,87 @@ test parseHosts {
14621462
try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
14631463
}
14641464

1465+
/// Validates a hostname according to [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123)
14651466
pub fn isValidHostName(hostname: []const u8) bool {
1466-
if (hostname.len >= 254) return false;
1467-
if (!std.unicode.utf8ValidateSlice(hostname)) return false;
1468-
for (hostname) |byte| {
1469-
if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
1470-
continue;
1467+
if (hostname.len == 0) return false;
1468+
if (hostname[0] == '.') return false;
1469+
1470+
// Ignore trailing dot (FQDN). It doesn't count toward our length.
1471+
const end = if (hostname[hostname.len - 1] == '.') end: {
1472+
if (hostname.len == 1) return false;
1473+
break :end hostname.len - 1;
1474+
} else hostname.len;
1475+
1476+
// The accepted maximum length of a hostname is 253 characters for the
1477+
// fully qualified domain name (FQDN), including labels and dots.
1478+
if (end > 253) return false;
1479+
1480+
// Hostnames are divided into dot-separated "labels", which:
1481+
//
1482+
// - Start with a letter or digit
1483+
// - Can contain letters, digits, or hyphens
1484+
// - Must end with a letter or digit
1485+
// - Have a minimum of 1 character and a maximum of 63
1486+
var label_start: usize = 0;
1487+
var label_len: usize = 0;
1488+
for (hostname[0..end], 0..) |c, i| {
1489+
switch (c) {
1490+
'.' => {
1491+
if (label_len == 0 or label_len > 63) return false;
1492+
if (!std.ascii.isAlphanumeric(hostname[label_start])) return false;
1493+
if (!std.ascii.isAlphanumeric(hostname[i - 1])) return false;
1494+
1495+
label_start = i + 1;
1496+
label_len = 0;
1497+
},
1498+
'-' => {
1499+
label_len += 1;
1500+
},
1501+
else => {
1502+
if (!std.ascii.isAlphanumeric(c)) return false;
1503+
label_len += 1;
1504+
},
14711505
}
1472-
return false;
14731506
}
1507+
1508+
// Validate the final label
1509+
if (label_len == 0 or label_len > 63) return false;
1510+
if (!std.ascii.isAlphanumeric(hostname[label_start])) return false;
1511+
if (!std.ascii.isAlphanumeric(hostname[end - 1])) return false;
1512+
14741513
return true;
14751514
}
14761515

1516+
test isValidHostName {
1517+
// Valid hostnames
1518+
try std.testing.expect(isValidHostName("example"));
1519+
try std.testing.expect(isValidHostName("example.com"));
1520+
try std.testing.expect(isValidHostName("www.example.com"));
1521+
try std.testing.expect(isValidHostName("sub.domain.example.com"));
1522+
try std.testing.expect(isValidHostName("example.com."));
1523+
try std.testing.expect(isValidHostName("host-name.example.com."));
1524+
try std.testing.expect(isValidHostName("123.example.com."));
1525+
try std.testing.expect(isValidHostName("a-b.com"));
1526+
try std.testing.expect(isValidHostName("a.b.c.d.e.f.g"));
1527+
try std.testing.expect(isValidHostName("127.0.0.1")); // Also a valid hostname
1528+
try std.testing.expect(isValidHostName("a" ** 63 ++ ".com")); // Label exactly 63 chars (valid)
1529+
try std.testing.expect(isValidHostName("a." ** 126 ++ "a")); // Total length 253 (valid)
1530+
1531+
// Invalid hostnames
1532+
try std.testing.expect(!isValidHostName(""));
1533+
try std.testing.expect(!isValidHostName(".example.com"));
1534+
try std.testing.expect(!isValidHostName("example.com.."));
1535+
try std.testing.expect(!isValidHostName("host..domain"));
1536+
try std.testing.expect(!isValidHostName("-hostname"));
1537+
try std.testing.expect(!isValidHostName("hostname-"));
1538+
try std.testing.expect(!isValidHostName("a.-.b"));
1539+
try std.testing.expect(!isValidHostName("host_name.com"));
1540+
try std.testing.expect(!isValidHostName("."));
1541+
try std.testing.expect(!isValidHostName(".."));
1542+
try std.testing.expect(!isValidHostName("a" ** 64 ++ ".com")); // Label length 64 (too long)
1543+
try std.testing.expect(!isValidHostName("a." ** 126 ++ "ab")); // Total length 254 (too long)
1544+
}
1545+
14771546
fn linuxLookupNameFromDnsSearch(
14781547
gpa: Allocator,
14791548
addrs: *ArrayList(LookupAddr),

0 commit comments

Comments
 (0)