authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-23 03:24:57 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-23 03:24:57 -08:00
log8dbaebfd79b25b8f59072dae69122bb9d05e2550
treebf5dca8ac19431afaf67517a6aed910685d39974
parent3b7a4b78acbba53bfeeed122c691ada2c400add4

populate hostname field


3 files changed, 78 insertions(+), 23 deletions(-)

generate.ts+7-6
......@@ -47,17 +47,17 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin:
4747 const allocator = std.testing.allocator;
4848 const u = try url.URL.parse(allocator, input, base);
4949 defer allocator.free(u.href);
50 _ = href;
51 _ = origin;
5250 try expect(u.protocol).toEqualString(protocol);
5351 try expect(u.username).toEqualString(username);
5452 try expect(u.password).toEqualString(password);
55 _ = host;
56 _ = hostname;
53 try expect(u.hostname).toEqualString(hostname);
5754 try expect(u.port).toEqualString(port);
55 _ = host;
56 _ = origin;
5857 _ = pathname;
5958 try expect(u.search).toEqualString(search);
6059 try expect(u.hash).toEqualString(hash);
60 _ = href;
6161}
6262
6363pub fn parseIDNAFail(comptime input: []const u8) !void {
......@@ -70,13 +70,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {
7070}
7171
7272pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {
73 _ = output;
7473 // new URL('https://{idnaTest.input}/x');
7574 const allocator = std.testing.allocator;
7675 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);
7776 defer allocator.free(u.href);
77 try expect(u.hostname).toEqualString(output);
7878 // assert_equals(url.host, idnaTest.output);
79 // assert_equals(url.hostname, idnaTest.output);
8079 // assert_equals(url.pathname, "/x");
8180 // assert_equals(url.href, 'https://{idnaTest.output}/x');
8281}
......@@ -108,11 +107,13 @@ for (const c of cases.filter((v) => v.base != null && !v.failure)) {
108107
109108w.write(`\n`);
110109for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output === null)) {
110 if (c.input.length === 0) continue;
111111 w.write(`test { try parseIDNAFail("${stringEscape(c.input)}"); }\n`);
112112}
113113
114114w.write(`\n`);
115115for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output !== null)) {
116 if (c.input.length === 0) continue;
116117 w.write(`test { try parseIDNAPass("${stringEscape(c.input)}", "${stringEscape(c.output!)}"); }\n`);
117118}
118119
url.zig+66-10
......@@ -10,6 +10,8 @@ pub const URL = struct {
1010 protocol: []const u8,
1111 username: []const u8,
1212 password: []const u8,
13 hostname: []const u8,
14 hostname_kind: HostKind = .name,
1315 port: []const u8,
1416 search: []const u8,
1517 hash: []const u8,
......@@ -83,6 +85,7 @@ pub const URL = struct {
8385
8486 var href = ManyArrayList(8 + 7, u8).init(alloc);
8587 defer href.deinit();
88 var hostname_kind: HostKind = .name;
8689 // scheme
8790 // :
8891 // //
......@@ -90,8 +93,7 @@ pub const URL = struct {
9093 // :
9194 // password
9295 // @
93 var host: Host = .{ .name = "" };
94 defer if (host == .name) alloc.free(host.name);
96 // hostname
9597 // :
9698 // port
9799 // path
......@@ -391,9 +393,10 @@ pub const URL = struct {
391393 // 3. Let host be the result of host parsing buffer with url is not special.
392394 // 4. If host is failure, then return failure.
393395 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
396 defer if (h == .name) alloc.free(h.name);
394397 // 5. Set url’s host to host, buffer to the empty string, and state to port state.
395 // try href.set(7, h);
396 host = h;
398 try setHost(&href, h);
399 hostname_kind = h;
397400 buffer.clearRetainingCapacity();
398401 state = .port;
399402 }
......@@ -412,9 +415,10 @@ pub const URL = struct {
412415 // 3. Let host be the result of host parsing buffer with url is not special.
413416 // 4. If host is failure, then return failure.
414417 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
418 defer if (h == .name) alloc.free(h.name);
415419 // 5. Set url’s host to host, buffer to the empty string, and state to path start state.
416 // try href.set(7, h);
417 host = h;
420 try setHost(&href, h);
421 hostname_kind = h;
418422 buffer.clearRetainingCapacity();
419423 state = .path_start;
420424 // 6. If state override is given, then return.
......@@ -573,14 +577,15 @@ pub const URL = struct {
573577 // 1. Let host be the result of host parsing buffer with url is not special.
574578 // 2. If host is failure, then return failure.
575579 var h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
580 defer if (h == .name) alloc.free(h.name);
576581 // 3. If host is "localhost", then set host to the empty string.
577582 if (h == .name and std.mem.eql(u8, h.name, "localhost")) {
578583 alloc.free(h.name);
579584 h = .{ .name = "" };
580585 }
581586 // 4. Set url’s host to host.
582 // try href.set(7, h);
583 host = h;
587 try setHost(&href, h);
588 hostname_kind = h;
584589 // 5. If state override is given, then return.
585590 if (state_override != null) break;
586591 // 6. Set buffer to the empty string and state to path start state.
......@@ -797,6 +802,8 @@ pub const URL = struct {
797802 .protocol = _href[0..extras.sum(usize, href.lengths[0..2])],
798803 .username = _href[extras.sum(usize, href.lengths[0..2])..][0..href.lengths[3]],
799804 .password = _href[extras.sum(usize, href.lengths[0..4])..][0..href.lengths[5]],
805 .hostname = _href[extras.sum(usize, href.lengths[0..7])..][0..href.lengths[7]],
806 .hostname_kind = hostname_kind,
800807 .port = _href[extras.sum(usize, href.lengths[0..8])..][0..href.lengths[9]],
801808 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],
802809 .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])],
......@@ -1068,7 +1075,7 @@ fn parseHostOpaque(allocator: std.mem.Allocator, input: []const u8) ![]const u8
10681075 // 3. If input contains a U+0025 (%) and the two code points following it are not ASCII hex digits, invalid-URL-unit validation error.
10691076 {}
10701077 // 4. Return the result of running UTF-8 percent-encode on input using the C0 control percent-encode set.
1071 return percentEncode(allocator, input, is_c0control);
1078 return percentEncode(allocator, input, is_c0control_percent_char);
10721079}
10731080
10741081/// https://url.spec.whatwg.org/#utf-8-percent-encode
......@@ -1190,7 +1197,7 @@ fn parseIPv4(input: []const u8) !u32 {
11901197 numbers_len -= 1;
11911198 // 11. Let counter be 0.
11921199 // 12. For each n of numbers:
1193 for (0..numbers_len, 0..) |counter, n| {
1200 for (numbers[0..numbers_len], 0..) |n, counter| {
11941201 // 1. Increment ipv4 by n × 256^(3 − counter).
11951202 ipv4 += @as(u32, @intCast(n)) * std.math.pow(u32, 256, 3 - @as(u8, @intCast(counter)));
11961203 // 2. Increment counter by 1.
......@@ -1435,6 +1442,55 @@ fn percentEncodeML(list: *ManyArrayList(15, u8), n: usize, input: []const u8, co
14351442 }
14361443 }
14371444}
1445fn setHost(href: *ManyArrayList(15, u8), h: URL.Host) !void {
1446 switch (h) {
1447 .name => {
1448 try href.set(7, h.name);
1449 },
1450 .ipv4 => {
1451 const bytes: [4]u8 = @bitCast(h.ipv4);
1452 try href.print(7, "{d}.{d}.{d}.{d}", .{ bytes[3], bytes[2], bytes[1], bytes[0] });
1453 },
1454 .ipv6 => {
1455 const pieces: [8]u16 = @bitCast(h.ipv6);
1456 try href.appendSlice(7, "[");
1457 const compress = blk: {
1458 var longest: struct { ?usize, u8 } = .{ null, 1 };
1459 var found: struct { ?usize, u8 } = .{ null, 0 };
1460 for (&pieces, 0..) |piece, pieceIndex| {
1461 if (piece != 0) {
1462 if (found[1] > longest[1]) {
1463 longest[0] = found[0];
1464 longest[1] = found[1];
1465 }
1466 found[0] = null;
1467 found[1] = 0;
1468 } else {
1469 if (found[0] == null) found[0] = pieceIndex;
1470 found[1] += 1;
1471 }
1472 }
1473 if (found[1] > longest[1]) break :blk found[0];
1474 break :blk longest[0];
1475 };
1476 var ignore0 = false;
1477 for (&pieces, 0..) |piece, pieceIndex| {
1478 if (ignore0) {
1479 if (piece == 0) continue;
1480 ignore0 = false;
1481 }
1482 if (compress == pieceIndex) {
1483 try href.appendSlice(7, if (pieceIndex == 0) "::" else ":");
1484 ignore0 = true;
1485 continue;
1486 }
1487 try href.print(7, "{x}", .{piece});
1488 if (pieceIndex != 7) try href.appendSlice(7, ":");
1489 }
1490 try href.appendSlice(7, "]");
1491 },
1492 }
1493}
14381494
14391495pub fn ManyArrayList(N: usize, T: type) type {
14401496 return struct {
zig-out/test.zig+5-7
......@@ -17,17 +17,17 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin:
1717 const allocator = std.testing.allocator;
1818 const u = try url.URL.parse(allocator, input, base);
1919 defer allocator.free(u.href);
20 _ = href;
21 _ = origin;
2220 try expect(u.protocol).toEqualString(protocol);
2321 try expect(u.username).toEqualString(username);
2422 try expect(u.password).toEqualString(password);
25 _ = host;
26 _ = hostname;
23 try expect(u.hostname).toEqualString(hostname);
2724 try expect(u.port).toEqualString(port);
25 _ = host;
26 _ = origin;
2827 _ = pathname;
2928 try expect(u.search).toEqualString(search);
3029 try expect(u.hash).toEqualString(hash);
30 _ = href;
3131}
3232
3333pub fn parseIDNAFail(comptime input: []const u8) !void {
......@@ -40,13 +40,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {
4040}
4141
4242pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {
43 _ = output;
4443 // new URL('https://{idnaTest.input}/x');
4544 const allocator = std.testing.allocator;
4645 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);
4746 defer allocator.free(u.href);
47 try expect(u.hostname).toEqualString(output);
4848 // assert_equals(url.host, idnaTest.output);
49 // assert_equals(url.hostname, idnaTest.output);
5049 // assert_equals(url.pathname, "/x");
5150 // assert_equals(url.href, 'https://{idnaTest.output}/x');
5251}
......@@ -2506,7 +2505,6 @@ test { try parseIDNAPass("\xc2\xa1", "xn--7a"); }
25062505test { try parseIDNAPass("xn--7a", "xn--7a"); }
25072506test { try parseIDNAPass("\xe1\xa7\x9a", "xn--pkf"); }
25082507test { try parseIDNAPass("xn--pkf", "xn--pkf"); }
2509test { try parseIDNAPass("", ""); }
25102508test { try parseIDNAPass("\xe3\x80\x82", "."); }
25112509test { try parseIDNAPass(".", "."); }
25122510test { try parseIDNAPass("\xea\xad\xa0", "xn--3y9a"); }