authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-07-03 21:30:31-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-07-03 21:33:40-07:00
loga6f7722b32bd9e0c1b86a978adcb5db270a23d59
treee43bedbfc8c5204fdda40dfea936f89b9e3b9314
parentf1531406979b7c196ec535008a159b5f411cf78f

HostName: Include trailing . in max length calculation

The 255 max length restriction is based on the representation in a packet, where each label has a byte prefixed denoting the length and the trailing dot is non-optional but specified as a zero-length label (so one byte with value 0 at the end). This means that for the purposes of validation, the trailing dot should be included in the length. This also ensures that max_len is always enough to hold a validated HostName, whereas before any code relying on that assumption was technically invalid as it was possible to pass through `validate` with a 256-length slice.

1 files changed, 5 insertions(+), 6 deletions(-)

lib/std/Io/net/HostName.zig+5-6
...@@ -28,11 +28,11 @@ pub const ValidateError = error{...@@ -28,11 +28,11 @@ pub const ValidateError = error{
28pub fn validate(bytes: []const u8) ValidateError!void {28pub fn validate(bytes: []const u8) ValidateError!void {
29 if (bytes.len == 0) return error.InvalidHostName;29 if (bytes.len == 0) return error.InvalidHostName;
3030
31 // Ignore trailing dot (FQDN). It doesn't count toward our length.
32 const end = if (bytes[bytes.len - 1] == '.') bytes.len - 1 else bytes.len;
33
34 // The accepted maximum length of a hostname, including labels and dots.31 // The accepted maximum length of a hostname, including labels and dots.
35 if (end > max_len) return error.NameTooLong;32 if (bytes.len > max_len) return error.NameTooLong;
33
34 // Ignore trailing dot (FQDN).
35 const end = if (bytes[bytes.len - 1] == '.') bytes.len - 1 else bytes.len;
3636
37 // Hostnames are divided into dot-separated "labels", which:37 // Hostnames are divided into dot-separated "labels", which:
38 //38 //
...@@ -83,7 +83,6 @@ test validate {...@@ -83,7 +83,6 @@ test validate {
83 const many_a_dot_buf: [127][2]u8 = @splat(.{ 'a', '.' });83 const many_a_dot_buf: [127][2]u8 = @splat(.{ 'a', '.' });
84 const many_a_dot: []const u8 = @ptrCast(&many_a_dot_buf);84 const many_a_dot: []const u8 = @ptrCast(&many_a_dot_buf);
85 try validate(many_a_dot ++ "a"); // Total length 255 (valid)85 try validate(many_a_dot ++ "a"); // Total length 255 (valid)
86 try validate(many_a_dot ++ "a."); // Total length 255 + trailing dot (valid)
8786
88 // Invalid hostnames87 // Invalid hostnames
89 try std.testing.expectError(error.InvalidHostName, validate(""));88 try std.testing.expectError(error.InvalidHostName, validate(""));
...@@ -98,8 +97,8 @@ test validate {...@@ -98,8 +97,8 @@ test validate {
98 try std.testing.expectError(error.InvalidHostName, validate("."));97 try std.testing.expectError(error.InvalidHostName, validate("."));
99 try std.testing.expectError(error.InvalidHostName, validate(".."));98 try std.testing.expectError(error.InvalidHostName, validate(".."));
100 try std.testing.expectError(error.InvalidHostName, validate(&many_a ++ "a.com")); // Label length 64 (too long)99 try std.testing.expectError(error.InvalidHostName, validate(&many_a ++ "a.com")); // Label length 64 (too long)
100 try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "a.")); // Total length 255 + trailing dot (too long)
101 try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab")); // Total length 256 (too long)101 try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab")); // Total length 256 (too long)
102 try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab.")); // Total length 256 + trailing dot (too long)
103}102}
104103
105pub fn init(bytes: []const u8) ValidateError!HostName {104pub fn init(bytes: []const u8) ValidateError!HostName {