| author | |
| committer | |
| log | 7796e5cdffd52073b56f6b9176dc0b99dedb62ef |
| tree | 50b49c9bbdcb43879feaebf9daba4608dcc66bf9 |
| parent | e07edda6e796c3488af178c5c1754dfc766a9391 |
| parent | b529a94e14725ea5f56ab4f48de0801c9f7e0b8b |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/360363 files changed, 35 insertions(+), 52 deletions(-)
lib/std/Io/net/HostName.zig+17-6| ... | @@ -19,6 +19,18 @@ bytes: []const u8, | ... | @@ -19,6 +19,18 @@ bytes: []const u8, |
| 19 | 19 | ||
| 20 | pub const max_len = 255; | 20 | pub const max_len = 255; |
| 21 | 21 | ||
| 22 | pub const FromUriError = error{UriMissingHost} || ValidateError; | ||
| 23 | |||
| 24 | /// Returned `HostName.bytes` may point into `buffer` or `uri.host`. | ||
| 25 | pub fn fromUri(uri: std.Uri, buffer: *[HostName.max_len]u8) FromUriError!HostName { | ||
| 26 | const component = uri.host orelse return error.UriMissingHost; | ||
| 27 | const bytes = component.toRaw(buffer) catch |err| switch (err) { | ||
| 28 | error.NoSpaceLeft => return error.NameTooLong, | ||
| 29 | }; | ||
| 30 | try validate(bytes); | ||
| 31 | return .{ .bytes = bytes }; | ||
| 32 | } | ||
| 33 | |||
| 22 | pub const ValidateError = error{ | 34 | pub const ValidateError = error{ |
| 23 | NameTooLong, | 35 | NameTooLong, |
| 24 | InvalidHostName, | 36 | InvalidHostName, |
| ... | @@ -28,11 +40,11 @@ pub const ValidateError = error{ | ... | @@ -28,11 +40,11 @@ pub const ValidateError = error{ |
| 28 | pub fn validate(bytes: []const u8) ValidateError!void { | 40 | pub fn validate(bytes: []const u8) ValidateError!void { |
| 29 | if (bytes.len == 0) return error.InvalidHostName; | 41 | if (bytes.len == 0) return error.InvalidHostName; |
| 30 | 42 | ||
| 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. | 43 | // The accepted maximum length of a hostname, including labels and dots. |
| 35 | if (end > max_len) return error.NameTooLong; | 44 | if (bytes.len > max_len) return error.NameTooLong; |
| 45 | |||
| 46 | // Ignore trailing dot (FQDN). | ||
| 47 | const end = if (bytes[bytes.len - 1] == '.') bytes.len - 1 else bytes.len; | ||
| 36 | 48 | ||
| 37 | // Hostnames are divided into dot-separated "labels", which: | 49 | // Hostnames are divided into dot-separated "labels", which: |
| 38 | // | 50 | // |
| ... | @@ -83,7 +95,6 @@ test validate { | ... | @@ -83,7 +95,6 @@ test validate { |
| 83 | const many_a_dot_buf: [127][2]u8 = @splat(.{ 'a', '.' }); | 95 | const many_a_dot_buf: [127][2]u8 = @splat(.{ 'a', '.' }); |
| 84 | const many_a_dot: []const u8 = @ptrCast(&many_a_dot_buf); | 96 | const many_a_dot: []const u8 = @ptrCast(&many_a_dot_buf); |
| 85 | try validate(many_a_dot ++ "a"); // Total length 255 (valid) | 97 | try validate(many_a_dot ++ "a"); // Total length 255 (valid) |
| 86 | try validate(many_a_dot ++ "a."); // Total length 255 + trailing dot (valid) | ||
| 87 | 98 | ||
| 88 | // Invalid hostnames | 99 | // Invalid hostnames |
| 89 | try std.testing.expectError(error.InvalidHostName, validate("")); | 100 | try std.testing.expectError(error.InvalidHostName, validate("")); |
| ... | @@ -98,8 +109,8 @@ test validate { | ... | @@ -98,8 +109,8 @@ test validate { |
| 98 | try std.testing.expectError(error.InvalidHostName, validate(".")); | 109 | try std.testing.expectError(error.InvalidHostName, validate(".")); |
| 99 | try std.testing.expectError(error.InvalidHostName, validate("..")); | 110 | try std.testing.expectError(error.InvalidHostName, validate("..")); |
| 100 | try std.testing.expectError(error.InvalidHostName, validate(&many_a ++ "a.com")); // Label length 64 (too long) | 111 | 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) | ||
| 101 | try std.testing.expectError(error.NameTooLong, validate(many_a_dot ++ "ab")); // Total length 256 (too long) | 113 | 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 | } | 114 | } |
| 104 | 115 | ||
| 105 | pub fn init(bytes: []const u8) ValidateError!HostName { | 116 | pub fn init(bytes: []const u8) ValidateError!HostName { |
lib/std/Uri.zig+5-42| ... | @@ -7,43 +7,18 @@ const testing = std.testing; | ... | @@ -7,43 +7,18 @@ const testing = std.testing; |
| 7 | const Uri = @This(); | 7 | const Uri = @This(); |
| 8 | const Allocator = std.mem.Allocator; | 8 | const Allocator = std.mem.Allocator; |
| 9 | const Writer = std.Io.Writer; | 9 | const Writer = std.Io.Writer; |
| 10 | const HostName = std.Io.net.HostName; | ||
| 11 | 10 | ||
| 12 | scheme: []const u8, | 11 | scheme: []const u8, |
| 13 | user: ?Component = null, | 12 | user: ?Component = null, |
| 14 | password: ?Component = null, | 13 | password: ?Component = null, |
| 15 | /// If non-null, already validated. | ||
| 16 | host: ?Component = null, | 14 | host: ?Component = null, |
| 17 | port: ?u16 = null, | 15 | port: ?u16 = null, |
| 18 | path: Component = Component.empty, | 16 | path: Component = Component.empty, |
| 19 | query: ?Component = null, | 17 | query: ?Component = null, |
| 20 | fragment: ?Component = null, | 18 | fragment: ?Component = null, |
| 21 | 19 | ||
| 22 | pub const GetHostError = error{UriMissingHost}; | 20 | pub const getHost = @compileError("This function has been moved to std.Io.net.HostName.fromUri"); |
| 23 | 21 | pub const getHostAlloc = @compileError("This function has been deleted. See std.Io.net.HostName.fromUri instead"); | |
| 24 | /// Returned value may point into `buffer` or be the original string. | ||
| 25 | /// | ||
| 26 | /// See also: | ||
| 27 | /// * `getHostAlloc` | ||
| 28 | pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName { | ||
| 29 | const component = uri.host orelse return error.UriMissingHost; | ||
| 30 | const bytes = component.toRaw(buffer) catch |err| switch (err) { | ||
| 31 | error.NoSpaceLeft => unreachable, // `host` already validated. | ||
| 32 | }; | ||
| 33 | return .{ .bytes = bytes }; | ||
| 34 | } | ||
| 35 | |||
| 36 | pub const GetHostAllocError = GetHostError || error{OutOfMemory}; | ||
| 37 | |||
| 38 | /// Returned value may point into `buffer` or be the original string. | ||
| 39 | /// | ||
| 40 | /// See also: | ||
| 41 | /// * `getHost` | ||
| 42 | pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError!HostName { | ||
| 43 | const component = uri.host orelse return error.UriMissingHost; | ||
| 44 | const bytes = try component.toRawMaybeAlloc(arena); | ||
| 45 | return .{ .bytes = bytes }; | ||
| 46 | } | ||
| 47 | 22 | ||
| 48 | pub const Component = union(enum) { | 23 | pub const Component = union(enum) { |
| 49 | /// Invalid characters in this component must be percent encoded | 24 | /// Invalid characters in this component must be percent encoded |
| ... | @@ -400,7 +375,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -400,7 +375,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 400 | .scheme = new_parsed.scheme, | 375 | .scheme = new_parsed.scheme, |
| 401 | .user = new_parsed.user, | 376 | .user = new_parsed.user, |
| 402 | .password = new_parsed.password, | 377 | .password = new_parsed.password, |
| 403 | .host = try validateHostComponent(new_parsed.host), | 378 | .host = new_parsed.host, |
| 404 | .port = new_parsed.port, | 379 | .port = new_parsed.port, |
| 405 | .path = remove_dot_segments(new_path), | 380 | .path = remove_dot_segments(new_path), |
| 406 | .query = new_parsed.query, | 381 | .query = new_parsed.query, |
| ... | @@ -411,7 +386,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -411,7 +386,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 411 | .scheme = base.scheme, | 386 | .scheme = base.scheme, |
| 412 | .user = new_parsed.user, | 387 | .user = new_parsed.user, |
| 413 | .password = new_parsed.password, | 388 | .password = new_parsed.password, |
| 414 | .host = try validateHostComponent(host), | 389 | .host = host, |
| 415 | .port = new_parsed.port, | 390 | .port = new_parsed.port, |
| 416 | .path = remove_dot_segments(new_path), | 391 | .path = remove_dot_segments(new_path), |
| 417 | .query = new_parsed.query, | 392 | .query = new_parsed.query, |
| ... | @@ -433,7 +408,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -433,7 +408,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 433 | .scheme = base.scheme, | 408 | .scheme = base.scheme, |
| 434 | .user = base.user, | 409 | .user = base.user, |
| 435 | .password = base.password, | 410 | .password = base.password, |
| 436 | .host = try validateHostComponent(base.host), | 411 | .host = base.host, |
| 437 | .port = base.port, | 412 | .port = base.port, |
| 438 | .path = path, | 413 | .path = path, |
| 439 | .query = query, | 414 | .query = query, |
| ... | @@ -441,18 +416,6 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE | ... | @@ -441,18 +416,6 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE |
| 441 | }; | 416 | }; |
| 442 | } | 417 | } |
| 443 | 418 | ||
| 444 | fn validateHostComponent(optional_component: ?Component) error{InvalidHostName}!?Component { | ||
| 445 | const component = optional_component orelse return null; | ||
| 446 | switch (component) { | ||
| 447 | .raw => |raw| HostName.validate(raw) catch return error.InvalidHostName, | ||
| 448 | .percent_encoded => |encoded| { | ||
| 449 | // TODO validate decoded name instead | ||
| 450 | HostName.validate(encoded) catch return error.InvalidHostName; | ||
| 451 | }, | ||
| 452 | } | ||
| 453 | return component; | ||
| 454 | } | ||
| 455 | |||
| 456 | /// In-place implementation of RFC 3986, Section 5.2.4. | 419 | /// In-place implementation of RFC 3986, Section 5.2.4. |
| 457 | fn remove_dot_segments(path: []u8) Component { | 420 | fn remove_dot_segments(path: []u8) Component { |
| 458 | var in_i: usize = 0; | 421 | var in_i: usize = 0; |
lib/std/http/Client.zig+13-4| ... | @@ -1229,7 +1229,11 @@ pub const Request = struct { | ... | @@ -1229,7 +1229,11 @@ pub const Request = struct { |
| 1229 | const old_connection = r.connection.?; | 1229 | const old_connection = r.connection.?; |
| 1230 | const old_host = old_connection.host(); | 1230 | const old_host = old_connection.host(); |
| 1231 | var new_host_name_buffer: [HostName.max_len]u8 = undefined; | 1231 | var new_host_name_buffer: [HostName.max_len]u8 = undefined; |
| 1232 | const new_host = try new_uri.getHost(&new_host_name_buffer); | 1232 | const new_host = HostName.fromUri(new_uri, &new_host_name_buffer) catch |err| switch (err) { |
| 1233 | error.UriMissingHost => return error.HttpRedirectLocationInvalid, | ||
| 1234 | error.InvalidHostName => return error.HttpRedirectLocationInvalid, | ||
| 1235 | error.NameTooLong => return error.HttpRedirectLocationOversize, | ||
| 1236 | }; | ||
| 1233 | const keep_privileged_headers = | 1237 | const keep_privileged_headers = |
| 1234 | std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and | 1238 | std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and |
| 1235 | old_host.sameParentDomain(new_host); | 1239 | old_host.sameParentDomain(new_host); |
| ... | @@ -1349,7 +1353,8 @@ fn createProxyFromEnvVar( | ... | @@ -1349,7 +1353,8 @@ fn createProxyFromEnvVar( |
| 1349 | 1353 | ||
| 1350 | const uri = Uri.parse(content) catch try Uri.parseAfterScheme("http", content); | 1354 | const uri = Uri.parse(content) catch try Uri.parseAfterScheme("http", content); |
| 1351 | const protocol = Protocol.fromUri(uri) orelse return null; | 1355 | const protocol = Protocol.fromUri(uri) orelse return null; |
| 1352 | const raw_host = try uri.getHostAlloc(arena); | 1356 | var host_buf: [HostName.max_len]u8 = undefined; |
| 1357 | const raw_host = try HostName.fromUri(uri, &host_buf); | ||
| 1353 | 1358 | ||
| 1354 | const authorization: ?[]const u8 = if (uri.user != null or uri.password != null) a: { | 1359 | const authorization: ?[]const u8 = if (uri.user != null or uri.password != null) a: { |
| 1355 | const authorization = try arena.alloc(u8, basic_authorization.valueLengthFromUri(uri)); | 1360 | const authorization = try arena.alloc(u8, basic_authorization.valueLengthFromUri(uri)); |
| ... | @@ -1360,7 +1365,7 @@ fn createProxyFromEnvVar( | ... | @@ -1360,7 +1365,7 @@ fn createProxyFromEnvVar( |
| 1360 | const proxy = try arena.create(Proxy); | 1365 | const proxy = try arena.create(Proxy); |
| 1361 | proxy.* = .{ | 1366 | proxy.* = .{ |
| 1362 | .protocol = protocol, | 1367 | .protocol = protocol, |
| 1363 | .host = raw_host, | 1368 | .host = .{ .bytes = try arena.dupe(u8, raw_host.bytes) }, |
| 1364 | .authorization = authorization, | 1369 | .authorization = authorization, |
| 1365 | .port = uriPort(uri, protocol), | 1370 | .port = uriPort(uri, protocol), |
| 1366 | .supports_connect = true, | 1371 | .supports_connect = true, |
| ... | @@ -1624,6 +1629,7 @@ pub fn connect( | ... | @@ -1624,6 +1629,7 @@ pub fn connect( |
| 1624 | pub const RequestError = ConnectTcpError || error{ | 1629 | pub const RequestError = ConnectTcpError || error{ |
| 1625 | UnsupportedUriScheme, | 1630 | UnsupportedUriScheme, |
| 1626 | UriMissingHost, | 1631 | UriMissingHost, |
| 1632 | InvalidHostName, | ||
| 1627 | CertificateBundleLoadFailure, | 1633 | CertificateBundleLoadFailure, |
| 1628 | }; | 1634 | }; |
| 1629 | 1635 | ||
| ... | @@ -1721,7 +1727,10 @@ pub fn request( | ... | @@ -1721,7 +1727,10 @@ pub fn request( |
| 1721 | 1727 | ||
| 1722 | const connection = options.connection orelse c: { | 1728 | const connection = options.connection orelse c: { |
| 1723 | var host_name_buffer: [HostName.max_len]u8 = undefined; | 1729 | var host_name_buffer: [HostName.max_len]u8 = undefined; |
| 1724 | const host_name = try uri.getHost(&host_name_buffer); | 1730 | const host_name = HostName.fromUri(uri, &host_name_buffer) catch |err| switch (err) { |
| 1731 | error.UriMissingHost => |e| return e, | ||
| 1732 | error.NameTooLong, error.InvalidHostName => return error.InvalidHostName, | ||
| 1733 | }; | ||
| 1725 | break :c try client.connect(host_name, uriPort(uri, protocol), protocol); | 1734 | break :c try client.connect(host_name, uriPort(uri, protocol), protocol); |
| 1726 | }; | 1735 | }; |
| 1727 | 1736 |