| ... | ... | @@ -17,7 +17,17 @@ const Stream = Io.net.Stream; |
| 17 | 17 | /// Externally managed memory. Already checked to be valid. |
| 18 | 18 | bytes: []const u8, |
| 19 | 19 | |
| 20 | | pub const max_len = 255; |
| 20 | /// The maximum number of bytes needed to store the text representation of |
| 21 | /// a max length host name, where labels are separated by dots, including |
| 22 | /// the trailing dot and the zero-length root label. |
| 23 | /// |
| 24 | /// The max length of a host name is determined by its packet representation, |
| 25 | /// where each label has a length prefix of 1 octet, the root label has a |
| 26 | /// length of 0, and the maximum total number of octets is 255. |
| 27 | /// |
| 28 | /// See [RFC 1035, Section 3.1](https://datatracker.ietf.org/doc/html/rfc1035#section-3.1) |
| 29 | pub const max_len = 254; |
| 30 | const max_len_without_root = max_len - 1; |
| 21 | 31 | |
| 22 | 32 | pub const FromUriError = error{UriMissingHost} || ValidateError; |
| 23 | 33 | |
| ... | ... | @@ -40,12 +50,12 @@ pub const ValidateError = error{ |
| 40 | 50 | pub fn validate(bytes: []const u8) ValidateError!void { |
| 41 | 51 | if (bytes.len == 0) return error.InvalidHostName; |
| 42 | 52 | |
| 43 | | // The accepted maximum length of a hostname, including labels and dots. |
| 44 | | if (bytes.len > max_len) return error.NameTooLong; |
| 45 | | |
| 46 | 53 | // Ignore trailing dot (FQDN). |
| 47 | 54 | const end = if (bytes[bytes.len - 1] == '.') bytes.len - 1 else bytes.len; |
| 48 | 55 | |
| 56 | // The accepted maximum length of a hostname, including labels and dots. |
| 57 | if (end > max_len_without_root) return error.NameTooLong; |
| 58 | |
| 49 | 59 | // Hostnames are divided into dot-separated "labels", which: |
| 50 | 60 | // |
| 51 | 61 | // - Start with a letter or digit |
| ... | ... | @@ -92,9 +102,10 @@ test validate { |
| 92 | 102 | const many_a: [63]u8 = @splat('a'); |
| 93 | 103 | try validate(&many_a ++ ".com"); // Label exactly 63 chars (valid) |
| 94 | 104 | |
| 95 | | const many_a_dot_buf: [127][2]u8 = @splat(.{ 'a', '.' }); |
| 105 | const many_a_dot_buf: [126][2]u8 = @splat(.{ 'a', '.' }); |
| 96 | 106 | const many_a_dot: []const u8 = @ptrCast(&many_a_dot_buf); |
| 97 | | try validate(many_a_dot ++ "a"); // Total length 255 (valid) |
| 107 | try validate(many_a_dot ++ "a"); // Total length 253 (without the trailing dot) |
| 108 | try validate(many_a_dot ++ "a."); // Total length 254 (with the trailing dot) |
| 98 | 109 | |
| 99 | 110 | // Invalid hostnames |
| 100 | 111 | try std.testing.expectError(error.InvalidHostName, validate("")); |
| ... | ... | @@ -109,8 +120,8 @@ test validate { |
| 109 | 120 | try std.testing.expectError(error.InvalidHostName, validate(".")); |
| 110 | 121 | try std.testing.expectError(error.InvalidHostName, validate("..")); |
| 111 | 122 | try std.testing.expectError(error.InvalidHostName, validate(&many_a ++ "a.com")); // Label length 64 (too long) |
| 112 | | try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "a.")); // Total length 255 + trailing dot (too long) |
| 113 | | try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab")); // Total length 256 (too long) |
| 123 | try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab")); // Total length 254 (without the trailing dot) |
| 124 | try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab.")); // Total length 255 (with the trailing dot) |
| 114 | 125 | } |
| 115 | 126 | |
| 116 | 127 | pub fn init(bytes: []const u8) ValidateError!HostName { |