| ... | @@ -27,13 +27,9 @@ pub const ValidateError = error{ | ... | @@ -27,13 +27,9 @@ pub const ValidateError = error{ |
| 27 | /// Validates a hostname according to [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123) | 27 | /// Validates a hostname according to [RFC 1123](https://www.rfc-editor.org/rfc/rfc1123) |
| 28 | pub fn validate(bytes: []const u8) ValidateError!void { | 28 | pub fn validate(bytes: []const u8) ValidateError!void { |
| 29 | if (bytes.len == 0) return error.InvalidHostName; | 29 | if (bytes.len == 0) return error.InvalidHostName; |
| 30 | if (bytes[0] == '.') return error.InvalidHostName; | | |
| 31 | | 30 | |
| 32 | // Ignore trailing dot (FQDN). It doesn't count toward our length. | 31 | // Ignore trailing dot (FQDN). It doesn't count toward our length. |
| 33 | const end = if (bytes[bytes.len - 1] == '.') end: { | 32 | const end = if (bytes[bytes.len - 1] == '.') bytes.len - 1 else bytes.len; |
| 34 | if (bytes.len == 1) return error.InvalidHostName; | | |
| 35 | break :end bytes.len - 1; | | |
| 36 | } else bytes.len; | | |
| 37 | | 33 | |
| 38 | // The accepted maximum length of a hostname, including labels and dots. | 34 | // The accepted maximum length of a hostname, including labels and dots. |
| 39 | if (end > max_len) return error.NameTooLong; | 35 | if (end > max_len) return error.NameTooLong; |
| ... | @@ -44,19 +40,16 @@ pub fn validate(bytes: []const u8) ValidateError!void { | ... | @@ -44,19 +40,16 @@ pub fn validate(bytes: []const u8) ValidateError!void { |
| 44 | // - Can contain letters, digits, or hyphens | 40 | // - Can contain letters, digits, or hyphens |
| 45 | // - Must end with a letter or digit | 41 | // - Must end with a letter or digit |
| 46 | // - Have a minimum of 1 character and a maximum of 63 | 42 | // - Have a minimum of 1 character and a maximum of 63 |
| 47 | var label_start: usize = 0; | | |
| 48 | var label_len: usize = 0; | 43 | var label_len: usize = 0; |
| 49 | for (bytes[0..end], 0..) |c, i| { | 44 | for (bytes[0..end], 0..) |c, i| { |
| 50 | switch (c) { | 45 | switch (c) { |
| 51 | '.' => { | 46 | '.' => { |
| 52 | if (label_len == 0 or label_len > 63) return error.InvalidHostName; | 47 | if (label_len == 0 or label_len > 63) return error.InvalidHostName; |
| 53 | if (!std.ascii.isAlphanumeric(bytes[label_start])) return error.InvalidHostName; | | |
| 54 | if (!std.ascii.isAlphanumeric(bytes[i - 1])) return error.InvalidHostName; | 48 | if (!std.ascii.isAlphanumeric(bytes[i - 1])) return error.InvalidHostName; |
| 55 | | | |
| 56 | label_start = i + 1; | | |
| 57 | label_len = 0; | 49 | label_len = 0; |
| 58 | }, | 50 | }, |
| 59 | '-' => { | 51 | '-' => { |
| | 52 | if (label_len == 0) return error.InvalidHostName; |
| 60 | label_len += 1; | 53 | label_len += 1; |
| 61 | }, | 54 | }, |
| 62 | else => { | 55 | else => { |
| ... | @@ -68,7 +61,6 @@ pub fn validate(bytes: []const u8) ValidateError!void { | ... | @@ -68,7 +61,6 @@ pub fn validate(bytes: []const u8) ValidateError!void { |
| 68 | | 61 | |
| 69 | // Validate the final label | 62 | // Validate the final label |
| 70 | if (label_len == 0 or label_len > 63) return error.InvalidHostName; | 63 | if (label_len == 0 or label_len > 63) return error.InvalidHostName; |
| 71 | if (!std.ascii.isAlphanumeric(bytes[label_start])) return error.InvalidHostName; | | |
| 72 | if (!std.ascii.isAlphanumeric(bytes[end - 1])) return error.InvalidHostName; | 64 | if (!std.ascii.isAlphanumeric(bytes[end - 1])) return error.InvalidHostName; |
| 73 | } | 65 | } |
| 74 | | 66 | |
| ... | @@ -86,6 +78,7 @@ test validate { | ... | @@ -86,6 +78,7 @@ test validate { |
| 86 | try validate("127.0.0.1"); // Also a valid hostname | 78 | try validate("127.0.0.1"); // Also a valid hostname |
| 87 | try validate("a" ** 63 ++ ".com"); // Label exactly 63 chars (valid) | 79 | try validate("a" ** 63 ++ ".com"); // Label exactly 63 chars (valid) |
| 88 | try validate("a." ** 127 ++ "a"); // Total length 255 (valid) | 80 | try validate("a." ** 127 ++ "a"); // Total length 255 (valid) |
| | 81 | try validate("a." ** 127 ++ "a."); // Total length 255 + trailing dot (valid) |
| 89 | | 82 | |
| 90 | // Invalid hostnames | 83 | // Invalid hostnames |
| 91 | try std.testing.expectError(error.InvalidHostName, validate("")); | 84 | try std.testing.expectError(error.InvalidHostName, validate("")); |
| ... | @@ -94,12 +87,14 @@ test validate { | ... | @@ -94,12 +87,14 @@ test validate { |
| 94 | try std.testing.expectError(error.InvalidHostName, validate("host..domain")); | 87 | try std.testing.expectError(error.InvalidHostName, validate("host..domain")); |
| 95 | try std.testing.expectError(error.InvalidHostName, validate("-hostname")); | 88 | try std.testing.expectError(error.InvalidHostName, validate("-hostname")); |
| 96 | try std.testing.expectError(error.InvalidHostName, validate("hostname-")); | 89 | try std.testing.expectError(error.InvalidHostName, validate("hostname-")); |
| | 90 | try std.testing.expectError(error.InvalidHostName, validate("hostname-.com")); |
| 97 | try std.testing.expectError(error.InvalidHostName, validate("a.-.b")); | 91 | try std.testing.expectError(error.InvalidHostName, validate("a.-.b")); |
| 98 | try std.testing.expectError(error.InvalidHostName, validate("host_name.com")); | 92 | try std.testing.expectError(error.InvalidHostName, validate("host_name.com")); |
| 99 | try std.testing.expectError(error.InvalidHostName, validate(".")); | 93 | try std.testing.expectError(error.InvalidHostName, validate(".")); |
| 100 | try std.testing.expectError(error.InvalidHostName, validate("..")); | 94 | try std.testing.expectError(error.InvalidHostName, validate("..")); |
| 101 | try std.testing.expectError(error.InvalidHostName, validate("a" ** 64 ++ ".com")); // Label length 64 (too long) | 95 | try std.testing.expectError(error.InvalidHostName, validate("a" ** 64 ++ ".com")); // Label length 64 (too long) |
| 102 | try std.testing.expectError(error.NameTooLong, validate("a." ** 127 ++ "ab")); // Total length 256 (too long) | 96 | try std.testing.expectError(error.NameTooLong, validate("a." ** 127 ++ "ab")); // Total length 256 (too long) |
| | 97 | try std.testing.expectError(error.NameTooLong, validate("a." ** 127 ++ "ab.")); // Total length 256 + trailing dot (too long) |
| 103 | } | 98 | } |
| 104 | | 99 | |
| 105 | pub fn init(bytes: []const u8) ValidateError!HostName { | 100 | pub fn init(bytes: []const u8) ValidateError!HostName { |