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:...@@ -47,17 +47,17 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin:
47 const allocator = std.testing.allocator;47 const allocator = std.testing.allocator;
48 const u = try url.URL.parse(allocator, input, base);48 const u = try url.URL.parse(allocator, input, base);
49 defer allocator.free(u.href);49 defer allocator.free(u.href);
50 _ = href;
51 _ = origin;
52 try expect(u.protocol).toEqualString(protocol);50 try expect(u.protocol).toEqualString(protocol);
53 try expect(u.username).toEqualString(username);51 try expect(u.username).toEqualString(username);
54 try expect(u.password).toEqualString(password);52 try expect(u.password).toEqualString(password);
55 _ = host;53 try expect(u.hostname).toEqualString(hostname);
56 _ = hostname;
57 try expect(u.port).toEqualString(port);54 try expect(u.port).toEqualString(port);
55 _ = host;
56 _ = origin;
58 _ = pathname;57 _ = pathname;
59 try expect(u.search).toEqualString(search);58 try expect(u.search).toEqualString(search);
60 try expect(u.hash).toEqualString(hash);59 try expect(u.hash).toEqualString(hash);
60 _ = href;
61}61}
6262
63pub fn parseIDNAFail(comptime input: []const u8) !void {63pub fn parseIDNAFail(comptime input: []const u8) !void {
...@@ -70,13 +70,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {...@@ -70,13 +70,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {
70}70}
7171
72pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {72pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {
73 _ = output;
74 // new URL('https://{idnaTest.input}/x');73 // new URL('https://{idnaTest.input}/x');
75 const allocator = std.testing.allocator;74 const allocator = std.testing.allocator;
76 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);75 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);
77 defer allocator.free(u.href);76 defer allocator.free(u.href);
77 try expect(u.hostname).toEqualString(output);
78 // assert_equals(url.host, idnaTest.output);78 // assert_equals(url.host, idnaTest.output);
79 // assert_equals(url.hostname, idnaTest.output);
80 // assert_equals(url.pathname, "/x");79 // assert_equals(url.pathname, "/x");
81 // assert_equals(url.href, 'https://{idnaTest.output}/x');80 // assert_equals(url.href, 'https://{idnaTest.output}/x');
82}81}
...@@ -108,11 +107,13 @@ for (const c of cases.filter((v) => v.base != null && !v.failure)) {...@@ -108,11 +107,13 @@ for (const c of cases.filter((v) => v.base != null && !v.failure)) {
108107
109w.write(`\n`);108w.write(`\n`);
110for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output === null)) {109for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output === null)) {
110 if (c.input.length === 0) continue;
111 w.write(`test { try parseIDNAFail("${stringEscape(c.input)}"); }\n`);111 w.write(`test { try parseIDNAFail("${stringEscape(c.input)}"); }\n`);
112}112}
113113
114w.write(`\n`);114w.write(`\n`);
115for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output !== null)) {115for (const c of casesidna.filter((v) => typeof v !== "string").filter((v) => v.output !== null)) {
116 if (c.input.length === 0) continue;
116 w.write(`test { try parseIDNAPass("${stringEscape(c.input)}", "${stringEscape(c.output!)}"); }\n`);117 w.write(`test { try parseIDNAPass("${stringEscape(c.input)}", "${stringEscape(c.output!)}"); }\n`);
117}118}
118119
url.zig+66-10
...@@ -10,6 +10,8 @@ pub const URL = struct {...@@ -10,6 +10,8 @@ pub const URL = struct {
10 protocol: []const u8,10 protocol: []const u8,
11 username: []const u8,11 username: []const u8,
12 password: []const u8,12 password: []const u8,
13 hostname: []const u8,
14 hostname_kind: HostKind = .name,
13 port: []const u8,15 port: []const u8,
14 search: []const u8,16 search: []const u8,
15 hash: []const u8,17 hash: []const u8,
...@@ -83,6 +85,7 @@ pub const URL = struct {...@@ -83,6 +85,7 @@ pub const URL = struct {
8385
84 var href = ManyArrayList(8 + 7, u8).init(alloc);86 var href = ManyArrayList(8 + 7, u8).init(alloc);
85 defer href.deinit();87 defer href.deinit();
88 var hostname_kind: HostKind = .name;
86 // scheme89 // scheme
87 // :90 // :
88 // //91 // //
...@@ -90,8 +93,7 @@ pub const URL = struct {...@@ -90,8 +93,7 @@ pub const URL = struct {
90 // :93 // :
91 // password94 // password
92 // @95 // @
93 var host: Host = .{ .name = "" };96 // hostname
94 defer if (host == .name) alloc.free(host.name);
95 // :97 // :
96 // port98 // port
97 // path99 // path
...@@ -391,9 +393,10 @@ pub const URL = struct {...@@ -391,9 +393,10 @@ pub const URL = struct {
391 // 3. Let host be the result of host parsing buffer with url is not special.393 // 3. Let host be the result of host parsing buffer with url is not special.
392 // 4. If host is failure, then return failure.394 // 4. If host is failure, then return failure.
393 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));395 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
396 defer if (h == .name) alloc.free(h.name);
394 // 5. Set url’s host to host, buffer to the empty string, and state to port state.397 // 5. Set url’s host to host, buffer to the empty string, and state to port state.
395 // try href.set(7, h);398 try setHost(&href, h);
396 host = h;399 hostname_kind = h;
397 buffer.clearRetainingCapacity();400 buffer.clearRetainingCapacity();
398 state = .port;401 state = .port;
399 }402 }
...@@ -412,9 +415,10 @@ pub const URL = struct {...@@ -412,9 +415,10 @@ pub const URL = struct {
412 // 3. Let host be the result of host parsing buffer with url is not special.415 // 3. Let host be the result of host parsing buffer with url is not special.
413 // 4. If host is failure, then return failure.416 // 4. If host is failure, then return failure.
414 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));417 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
418 defer if (h == .name) alloc.free(h.name);
415 // 5. Set url’s host to host, buffer to the empty string, and state to path start state.419 // 5. Set url’s host to host, buffer to the empty string, and state to path start state.
416 // try href.set(7, h);420 try setHost(&href, h);
417 host = h;421 hostname_kind = h;
418 buffer.clearRetainingCapacity();422 buffer.clearRetainingCapacity();
419 state = .path_start;423 state = .path_start;
420 // 6. If state override is given, then return.424 // 6. If state override is given, then return.
...@@ -573,14 +577,15 @@ pub const URL = struct {...@@ -573,14 +577,15 @@ pub const URL = struct {
573 // 1. Let host be the result of host parsing buffer with url is not special.577 // 1. Let host be the result of host parsing buffer with url is not special.
574 // 2. If host is failure, then return failure.578 // 2. If host is failure, then return failure.
575 var h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));579 var h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
580 defer if (h == .name) alloc.free(h.name);
576 // 3. If host is "localhost", then set host to the empty string.581 // 3. If host is "localhost", then set host to the empty string.
577 if (h == .name and std.mem.eql(u8, h.name, "localhost")) {582 if (h == .name and std.mem.eql(u8, h.name, "localhost")) {
578 alloc.free(h.name);583 alloc.free(h.name);
579 h = .{ .name = "" };584 h = .{ .name = "" };
580 }585 }
581 // 4. Set url’s host to host.586 // 4. Set url’s host to host.
582 // try href.set(7, h);587 try setHost(&href, h);
583 host = h;588 hostname_kind = h;
584 // 5. If state override is given, then return.589 // 5. If state override is given, then return.
585 if (state_override != null) break;590 if (state_override != null) break;
586 // 6. Set buffer to the empty string and state to path start state.591 // 6. Set buffer to the empty string and state to path start state.
...@@ -797,6 +802,8 @@ pub const URL = struct {...@@ -797,6 +802,8 @@ pub const URL = struct {
797 .protocol = _href[0..extras.sum(usize, href.lengths[0..2])],802 .protocol = _href[0..extras.sum(usize, href.lengths[0..2])],
798 .username = _href[extras.sum(usize, href.lengths[0..2])..][0..href.lengths[3]],803 .username = _href[extras.sum(usize, href.lengths[0..2])..][0..href.lengths[3]],
799 .password = _href[extras.sum(usize, href.lengths[0..4])..][0..href.lengths[5]],804 .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,
800 .port = _href[extras.sum(usize, href.lengths[0..8])..][0..href.lengths[9]],807 .port = _href[extras.sum(usize, href.lengths[0..8])..][0..href.lengths[9]],
801 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],808 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],
802 .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])],809 .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...@@ -1068,7 +1075,7 @@ fn parseHostOpaque(allocator: std.mem.Allocator, input: []const u8) ![]const u8
1068 // 3. If input contains a U+0025 (%) and the two code points following it are not ASCII hex digits, invalid-URL-unit validation error.1075 // 3. If input contains a U+0025 (%) and the two code points following it are not ASCII hex digits, invalid-URL-unit validation error.
1069 {}1076 {}
1070 // 4. Return the result of running UTF-8 percent-encode on input using the C0 control percent-encode set.1077 // 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);
1072}1079}
10731080
1074/// https://url.spec.whatwg.org/#utf-8-percent-encode1081/// https://url.spec.whatwg.org/#utf-8-percent-encode
...@@ -1190,7 +1197,7 @@ fn parseIPv4(input: []const u8) !u32 {...@@ -1190,7 +1197,7 @@ fn parseIPv4(input: []const u8) !u32 {
1190 numbers_len -= 1;1197 numbers_len -= 1;
1191 // 11. Let counter be 0.1198 // 11. Let counter be 0.
1192 // 12. For each n of numbers:1199 // 12. For each n of numbers:
1193 for (0..numbers_len, 0..) |counter, n| {1200 for (numbers[0..numbers_len], 0..) |n, counter| {
1194 // 1. Increment ipv4 by n × 256^(3 − counter).1201 // 1. Increment ipv4 by n × 256^(3 − counter).
1195 ipv4 += @as(u32, @intCast(n)) * std.math.pow(u32, 256, 3 - @as(u8, @intCast(counter)));1202 ipv4 += @as(u32, @intCast(n)) * std.math.pow(u32, 256, 3 - @as(u8, @intCast(counter)));
1196 // 2. Increment counter by 1.1203 // 2. Increment counter by 1.
...@@ -1435,6 +1442,55 @@ fn percentEncodeML(list: *ManyArrayList(15, u8), n: usize, input: []const u8, co...@@ -1435,6 +1442,55 @@ fn percentEncodeML(list: *ManyArrayList(15, u8), n: usize, input: []const u8, co
1435 }1442 }
1436 }1443 }
1437}1444}
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
1439pub fn ManyArrayList(N: usize, T: type) type {1495pub fn ManyArrayList(N: usize, T: type) type {
1440 return struct {1496 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:...@@ -17,17 +17,17 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin:
17 const allocator = std.testing.allocator;17 const allocator = std.testing.allocator;
18 const u = try url.URL.parse(allocator, input, base);18 const u = try url.URL.parse(allocator, input, base);
19 defer allocator.free(u.href);19 defer allocator.free(u.href);
20 _ = href;
21 _ = origin;
22 try expect(u.protocol).toEqualString(protocol);20 try expect(u.protocol).toEqualString(protocol);
23 try expect(u.username).toEqualString(username);21 try expect(u.username).toEqualString(username);
24 try expect(u.password).toEqualString(password);22 try expect(u.password).toEqualString(password);
25 _ = host;23 try expect(u.hostname).toEqualString(hostname);
26 _ = hostname;
27 try expect(u.port).toEqualString(port);24 try expect(u.port).toEqualString(port);
25 _ = host;
26 _ = origin;
28 _ = pathname;27 _ = pathname;
29 try expect(u.search).toEqualString(search);28 try expect(u.search).toEqualString(search);
30 try expect(u.hash).toEqualString(hash);29 try expect(u.hash).toEqualString(hash);
30 _ = href;
31}31}
3232
33pub fn parseIDNAFail(comptime input: []const u8) !void {33pub fn parseIDNAFail(comptime input: []const u8) !void {
...@@ -40,13 +40,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {...@@ -40,13 +40,12 @@ pub fn parseIDNAFail(comptime input: []const u8) !void {
40}40}
4141
42pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {42pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {
43 _ = output;
44 // new URL('https://{idnaTest.input}/x');43 // new URL('https://{idnaTest.input}/x');
45 const allocator = std.testing.allocator;44 const allocator = std.testing.allocator;
46 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);45 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);
47 defer allocator.free(u.href);46 defer allocator.free(u.href);
47 try expect(u.hostname).toEqualString(output);
48 // assert_equals(url.host, idnaTest.output);48 // assert_equals(url.host, idnaTest.output);
49 // assert_equals(url.hostname, idnaTest.output);
50 // assert_equals(url.pathname, "/x");49 // assert_equals(url.pathname, "/x");
51 // assert_equals(url.href, 'https://{idnaTest.output}/x');50 // assert_equals(url.href, 'https://{idnaTest.output}/x');
52}51}
...@@ -2506,7 +2505,6 @@ test { try parseIDNAPass("\xc2\xa1", "xn--7a"); }...@@ -2506,7 +2505,6 @@ test { try parseIDNAPass("\xc2\xa1", "xn--7a"); }
2506test { try parseIDNAPass("xn--7a", "xn--7a"); }2505test { try parseIDNAPass("xn--7a", "xn--7a"); }
2507test { try parseIDNAPass("\xe1\xa7\x9a", "xn--pkf"); }2506test { try parseIDNAPass("\xe1\xa7\x9a", "xn--pkf"); }
2508test { try parseIDNAPass("xn--pkf", "xn--pkf"); }2507test { try parseIDNAPass("xn--pkf", "xn--pkf"); }
2509test { try parseIDNAPass("", ""); }
2510test { try parseIDNAPass("\xe3\x80\x82", "."); }2508test { try parseIDNAPass("\xe3\x80\x82", "."); }
2511test { try parseIDNAPass(".", "."); }2509test { try parseIDNAPass(".", "."); }
2512test { try parseIDNAPass("\xea\xad\xa0", "xn--3y9a"); }2510test { try parseIDNAPass("\xea\xad\xa0", "xn--3y9a"); }