authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-13 18:59:40-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log539808239ecae050e383dd11f395a363b4404e21
tree23aba7596794d31d96c19a3f1eef3b0db3694599
parentd3f0c460ecb0ca5b1ba9c57f443463e1f3f49012

std.net: IPv6 parsing fixes


2 files changed, 35 insertions(+), 21 deletions(-)

lib/std/Io/net.zig+21-11
...@@ -70,7 +70,7 @@ pub const IpAddress = union(enum) {...@@ -70,7 +70,7 @@ pub const IpAddress = union(enum) {
70 pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {70 pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
71 if (text.len == 0) return error.InvalidAddress;71 if (text.len == 0) return error.InvalidAddress;
72 if (text[0] == '[') {72 if (text[0] == '[') {
73 const addr_end = std.mem.indexOfScalar(u8, text, ']') orelse73 const addr_end = std.mem.findScalar(u8, text, ']') orelse
74 return error.InvalidAddress;74 return error.InvalidAddress;
75 const addr_text = text[1..addr_end];75 const addr_text = text[1..addr_end];
76 const port: u16 = p: {76 const port: u16 = p: {
...@@ -80,7 +80,7 @@ pub const IpAddress = union(enum) {...@@ -80,7 +80,7 @@ pub const IpAddress = union(enum) {
80 };80 };
81 return parseIp6(addr_text, port) catch error.InvalidAddress;81 return parseIp6(addr_text, port) catch error.InvalidAddress;
82 }82 }
83 if (std.mem.indexOfScalar(u8, text, ':')) |i| {83 if (std.mem.findScalar(u8, text, ':')) |i| {
84 const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;84 const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
85 return .{ .ip4 = .{85 return .{ .ip4 = .{
86 .bytes = addr.bytes,86 .bytes = addr.bytes,
...@@ -431,17 +431,22 @@ pub const Ip6Address = struct {...@@ -431,17 +431,22 @@ pub const Ip6Address = struct {
431 pub const Parsed = union(enum) {431 pub const Parsed = union(enum) {
432 success: Unresolved,432 success: Unresolved,
433 invalid_byte: usize,433 invalid_byte: usize,
434 unexpected_end,434 incomplete,
435 junk_after_end: usize,435 junk_after_end: usize,
436 interface_name_oversized: usize,436 interface_name_oversized: usize,
437 invalid_ip4_mapping: usize,
438 overflow: usize,
437 };439 };
438440
439 pub fn parse(text: []const u8) Parsed {441 pub fn parse(text: []const u8) Parsed {
440 if (text.len < 2) return .unexpected_end;442 if (text.len < 2) return .incomplete;
441 if (std.ascii.startsWithIgnoreCase(text, "::ffff:")) ip4_mapped: {443 const ip4_prefix = "::ffff:";
442 const a4 = (Ip4Address.parse(text["::ffff:".len..], 0) catch break :ip4_mapped).bytes;444 if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
445 const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
446 return .{ .invalid_ip4_mapping = ip4_prefix.len };
447 const b = parsed.bytes;
443 return .{ .success = .{448 return .{ .success = .{
444 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, a4[0], a4[1], a4[2], a4[3] },449 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
445 .interface_name = null,450 .interface_name = null,
446 } };451 } };
447 }452 }
...@@ -457,7 +462,9 @@ pub const Ip6Address = struct {...@@ -457,7 +462,9 @@ pub const Ip6Address = struct {
457 .digit => c: switch (text[text_i]) {462 .digit => c: switch (text[text_i]) {
458 'a'...'f' => |c| {463 'a'...'f' => |c| {
459 const digit = c - 'a' + 10;464 const digit = c - 'a' + 10;
460 parts[parts_i] = parts[parts_i] * 16 + digit;465 parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
466 .overflow = text_i,
467 }) + digit;
461 if (digit_i == 4) return .{ .invalid_byte = text_i };468 if (digit_i == 4) return .{ .invalid_byte = text_i };
462 digit_i += 1;469 digit_i += 1;
463 text_i += 1;470 text_i += 1;
...@@ -470,7 +477,9 @@ pub const Ip6Address = struct {...@@ -470,7 +477,9 @@ pub const Ip6Address = struct {
470 'A'...'F' => |c| continue :c c - 'A' + 'a',477 'A'...'F' => |c| continue :c c - 'A' + 'a',
471 '0'...'9' => |c| {478 '0'...'9' => |c| {
472 const digit = c - '0';479 const digit = c - '0';
473 parts[parts_i] = parts[parts_i] * 16 + digit;480 parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
481 .overflow = text_i,
482 }) + digit;
474 if (digit_i == 4) return .{ .invalid_byte = text_i };483 if (digit_i == 4) return .{ .invalid_byte = text_i };
475 digit_i += 1;484 digit_i += 1;
476 text_i += 1;485 text_i += 1;
...@@ -497,7 +506,7 @@ pub const Ip6Address = struct {...@@ -497,7 +506,7 @@ pub const Ip6Address = struct {
497 if (parts.len - parts_i == 0) continue :state .end;506 if (parts.len - parts_i == 0) continue :state .end;
498 digit_i = 0;507 digit_i = 0;
499 text_i += 1;508 text_i += 1;
500 if (text.len - text_i == 0) return .unexpected_end;509 if (text.len - text_i == 0) return .incomplete;
501 continue :c text[text_i];510 continue :c text[text_i];
502 }511 }
503 },512 },
...@@ -507,6 +516,7 @@ pub const Ip6Address = struct {...@@ -507,6 +516,7 @@ pub const Ip6Address = struct {
507 text_i += 1;516 text_i += 1;
508 const name = text[text_i..];517 const name = text[text_i..];
509 if (name.len > Interface.Name.max_len) return .{ .interface_name_oversized = text_i };518 if (name.len > Interface.Name.max_len) return .{ .interface_name_oversized = text_i };
519 if (name.len == 0) return .incomplete;
510 interface_name_text = name;520 interface_name_text = name;
511 text_i = @intCast(text.len);521 text_i = @intCast(text.len);
512 continue :state .end;522 continue :state .end;
...@@ -521,7 +531,7 @@ pub const Ip6Address = struct {...@@ -521,7 +531,7 @@ pub const Ip6Address = struct {
521 @memmove(parts[parts.len - src.len ..], src);531 @memmove(parts[parts.len - src.len ..], src);
522 @memset(parts[s..][0..remaining], 0);532 @memset(parts[s..][0..remaining], 0);
523 } else {533 } else {
524 if (remaining != 0) return .unexpected_end;534 if (remaining != 0) return .incomplete;
525 }535 }
526536
527 // Workaround that can be removed when this proposal is537 // Workaround that can be removed when this proposal is
lib/std/Io/net/test.zig+14-10
...@@ -56,6 +56,7 @@ test "parse and render IPv6 addresses" {...@@ -56,6 +56,7 @@ test "parse and render IPv6 addresses" {
56 try testParseAndRenderIp6Address("2001:db8::1234:5678", "2001:db8::1234:5678");56 try testParseAndRenderIp6Address("2001:db8::1234:5678", "2001:db8::1234:5678");
57 try testParseAndRenderIp6Address("FF01::FB%1234", "ff01::fb%1234");57 try testParseAndRenderIp6Address("FF01::FB%1234", "ff01::fb%1234");
58 try testParseAndRenderIp6Address("::ffff:123.5.123.5", "::ffff:123.5.123.5");58 try testParseAndRenderIp6Address("::ffff:123.5.123.5", "::ffff:123.5.123.5");
59 try testParseAndRenderIp6Address("ff01::fb%12345678901234", "ff01::fb%12345678901234");
59}60}
6061
61fn testParseAndRenderIp6Address(input: []const u8, expected_output: []const u8) !void {62fn testParseAndRenderIp6Address(input: []const u8, expected_output: []const u8) !void {
...@@ -66,16 +67,19 @@ fn testParseAndRenderIp6Address(input: []const u8, expected_output: []const u8)...@@ -66,16 +67,19 @@ fn testParseAndRenderIp6Address(input: []const u8, expected_output: []const u8)
66}67}
6768
68test "IPv6 address parse failures" {69test "IPv6 address parse failures" {
69 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6(":::", 0));70 try testing.expectError(error.ParseFailed, net.IpAddress.parseIp6(":::", 0));
70 try testing.expectError(error.Overflow, net.IpAddress.parseIp6("FF001::FB", 0));71
71 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp6("FF01::Fb:zig", 0));72 const Unresolved = net.Ip6Address.Unresolved;
72 try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));73
73 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("FF01:", 0));74 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 2 }, Unresolved.parse(":::"));
74 try testing.expectError(error.InvalidIpv4Mapping, net.IpAddress.parseIp6("::123.123.123.123", 0));75 try testing.expectEqual(Unresolved.Parsed{ .overflow = 4 }, Unresolved.parse("FF001::FB"));
75 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("1", 0));76 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 9 }, Unresolved.parse("FF01::Fb:zig"));
76 try testing.expectError(error.Incomplete, net.IpAddress.parseIp6("ff01::fb%", 0));77 try testing.expectEqual(Unresolved.Parsed{ .junk_after_end = 19 }, Unresolved.parse("FF01:0:0:0:0:0:0:FB:"));
77 try testing.expectError(error.Overflow, net.IpAddress.parseIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));78 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("FF01:"));
78 try testing.expectError(error.Overflow, net.IpAddress.parseIp6("ff01::fb%12345678901234", 0));79 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 5 }, Unresolved.parse("::123.123.123.123"));
80 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("1"));
81 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("ff01::fb%"));
82 try testing.expectEqual(Unresolved.Parsed{ .interface_name_oversized = 9 }, Unresolved.parse("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2)));
79}83}
8084
81test "invalid but parseable IPv6 scope ids" {85test "invalid but parseable IPv6 scope ids" {