authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-23 18:28:06 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-23 18:28:06 -08:00
log20a830b847b756cea94e66812a39bfaf497cd2fc
treec28eac7948a50254d3369de8e4c980a8bae12f06
parent7bd9b75b2cc7ca58cfcd543ad912790c6c8678eb

populate pathname field


3 files changed, 58 insertions(+), 14 deletions(-)

generate.ts+3-3
......@@ -47,14 +47,14 @@ 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 _ = origin;
5051 try expect(u.protocol).toEqualString(protocol);
5152 try expect(u.username).toEqualString(username);
5253 try expect(u.password).toEqualString(password);
5354 try expect(u.hostname).toEqualString(hostname);
5455 try expect(u.port).toEqualString(port);
5556 try expect(u.host).toEqualString(host);
56 _ = origin;
57 _ = pathname;
57 try expect(u.pathname).toEqualString(pathname);
5858 try expect(u.search).toEqualString(search);
5959 try expect(u.hash).toEqualString(hash);
6060 _ = href;
......@@ -76,7 +76,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v
7676 defer allocator.free(u.href);
7777 try expect(u.hostname).toEqualString(output);
7878 try expect(u.host).toEqualString(output);
79 // assert_equals(url.pathname, "/x");
79 try expect(u.pathname).toEqualString("/x");
8080 // assert_equals(url.href, 'https://{idnaTest.output}/x');
8181}
8282`);
url.zig+52-8
......@@ -14,6 +14,7 @@ pub const URL = struct {
1414 hostname_kind: HostKind = .name,
1515 port: []const u8,
1616 host: []const u8,
17 pathname: []const u8,
1718 search: []const u8,
1819 hash: []const u8,
1920
......@@ -225,6 +226,7 @@ pub const URL = struct {
225226 pointer -= 1;
226227 i = lastcpi(inputl.items[0..i]);
227228 c = inputl.items[i];
229 try href.appendSlice(10, "/");
228230 }
229231 },
230232 .relative => {
......@@ -413,7 +415,7 @@ pub const URL = struct {
413415 // 1. If url is special and buffer is the empty string, host-missing validation error, return failure.
414416 if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL
415417 // 2. Otherwise, if state override is given, buffer is the empty string, and either url includes credentials or url’s port is non-null, then return failure.
416 else if (state_override != null and buffer.items.len == 0 and (href.items(3).len > 0 or href.items(5).len > 0)) return error.InvalidURL;
418 else if (state_override != null and buffer.items.len == 0 and (href.lengths[3] > 0 or href.lengths[5] > 0)) return error.InvalidURL;
417419 // 3. Let host be the result of host parsing buffer with url is not special.
418420 // 4. If host is failure, then return failure.
419421 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
......@@ -527,6 +529,7 @@ pub const URL = struct {
527529 pointer -= 1;
528530 i = lastcpi(inputl.items[0..i]);
529531 c = inputl.items[i];
532 try href.appendSlice(10, "/");
530533 }
531534 },
532535 .file_slash => {
......@@ -552,6 +555,7 @@ pub const URL = struct {
552555 pointer -= 1;
553556 i = lastcpi(inputl.items[0..i]);
554557 c = inputl.items[i];
558 try href.appendSlice(10, "/");
555559 }
556560 },
557561 .file_host => {
......@@ -564,6 +568,7 @@ pub const URL = struct {
564568 // > This is a (platform-independent) Windows drive letter quirk. buffer is not reset here and instead used in the path state.
565569 if (state_override == null and isWindowsDriveLetter(buffer.items)) {
566570 state = .path;
571 try href.appendSlice(10, "/");
567572 }
568573 // 2. Otherwise, if buffer is the empty string, then:
569574 else if (buffer.items.len == 0) {
......@@ -596,7 +601,9 @@ pub const URL = struct {
596601 }
597602 }
598603 // 2. Otherwise, append c to buffer.
599 try buffer.appendSlice(inputl.items[i..][0..l(c)]);
604 else {
605 try buffer.appendSlice(inputl.items[i..][0..l(c)]);
606 }
600607 },
601608 .path_start => {
602609 // 1. If url is special, then:
......@@ -611,6 +618,7 @@ pub const URL = struct {
611618 i = lastcpi(inputl.items[0..i]);
612619 c = inputl.items[i];
613620 }
621 try href.appendSlice(10, "/");
614622 }
615623 // 2. Otherwise, if state override is not given and c is U+003F (?), set url’s query to the empty string and state to query state.
616624 else if (state_override == null and c == '?') {
......@@ -633,6 +641,8 @@ pub const URL = struct {
633641 pointer -= 1;
634642 i = lastcpi(inputl.items[0..i]);
635643 c = inputl.items[i];
644 } else {
645 try href.appendSlice(10, &.{c});
636646 }
637647 }
638648 // 5. Otherwise, if state override is given and url’s host is null, append the empty string to url’s path.
......@@ -641,34 +651,41 @@ pub const URL = struct {
641651 }
642652 },
643653 .path => {
654 const is_lsep = c == '/';
655 const is_rsep = isSchemeSpecial(href.items(0)) and c == '\\';
644656 // 1. If one of the following is true:
645657 // - c is the EOF code point or U+002F (/)
646658 // - url is special and c is U+005C (\)
647659 // - state override is not given and c is U+003F (?) or U+0023 (#)
648660 // then:
649 if ((i == length or c == '/') or (isSchemeSpecial(href.items(0)) and c == '\\') or (state_override == null and (c == '?' or c == '#'))) {
661 if ((i == length or is_lsep) or (is_rsep) or (state_override == null and (c == '?' or c == '#'))) {
650662 // 1. If url is special and c is U+005C (\), invalid-reverse-solidus validation error.
651663 {}
652664 // 2. If buffer is a double-dot URL path segment, then:
653665 if (isDoubleDotPathSeg(buffer.items)) {
654 // @panic("TODO");
666 const olen = href.lengths[10];
667 const idx = std.mem.lastIndexOfScalar(u8, href.items(10)[0 .. olen - 1], '/') orelse 0;
668 try href.replace(10, idx + 1, olen - idx - 1, "");
655669 // 1. Shorten url’s path.
656670 // 2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path.
657671 // > This means that for input /usr/.. the result is / and not a lack of a path.
658672 }
659673 // 3. Otherwise, if buffer is a single-dot URL path segment and if neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path.
660 else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(isSchemeSpecial(href.items(0)) and c == '\\'))) {
661 // @panic("TODO");
674 else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(is_rsep))) {
675 // =no action needed
662676 }
663677 // 4. Otherwise, if buffer is not a single-dot URL path segment, then:
664678 else if (!isSingleDotPathSeg(buffer.items)) {
665679 // 1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then replace the second code point in buffer with U+003A (:).
666680 // > This is a (platform-independent) Windows drive letter quirk.
667 if (std.mem.eql(u8, href.items(0), "file") and href.items(10).len == 0 and isWindowsDriveLetter(buffer.items)) {
681 if (std.mem.eql(u8, href.items(0), "file") and href.lengths[10] <= 1 and isWindowsDriveLetter(buffer.items)) {
668682 buffer.items[1] = ':';
669683 }
670684 // 2. Append buffer to url’s path.
685 // if (!std.mem.endsWith(u8, href.items(10), "/")) try href.appendSlice(10, "/");
671686 try href.appendSlice(10, buffer.items);
687 if (is_lsep) try href.appendSlice(10, "/");
688 if (is_rsep) try href.appendSlice(10, "/");
672689 }
673690 // 5. Set buffer to the empty string.
674691 buffer.clearRetainingCapacity();
......@@ -808,6 +825,7 @@ pub const URL = struct {
808825 .hostname_kind = hostname_kind,
809826 .port = _href[extras.sum(usize, href.lengths[0..9])..][0..href.lengths[9]],
810827 .host = _href[extras.sum(usize, href.lengths[0..7])..][0..extras.sum(usize, href.lengths[7..][0..if (href.lengths[9] == 0) 1 else 3])],
828 .pathname = _href[extras.sum(usize, href.lengths[0..10])..][0..href.lengths[10]],
811829 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],
812830 .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])],
813831 };
......@@ -1345,6 +1363,7 @@ fn is_query_percent_char(c: u8) bool {
13451363/// https://url.spec.whatwg.org/#path-percent-encode-set
13461364fn is_path_percent_char(c: u8) bool {
13471365 if (c == '?') return true;
1366 if (c == '^') return true;
13481367 if (c == '`') return true;
13491368 if (c == '{') return true;
13501369 if (c == '}') return true;
......@@ -1371,7 +1390,7 @@ fn is_userinfo_percent_char(c: u8) bool {
13711390 if (c == ';') return true;
13721391 if (c == '=') return true;
13731392 if (c == '@') return true;
1374 if (c >= '[' and c <= '^') return true;
1393 if (c >= '[' and c <= ']') return true;
13751394 if (c == '|') return true;
13761395 return is_path_percent_char(c);
13771396}
......@@ -1494,6 +1513,23 @@ fn setHost(href: *ManyArrayList(15, u8), h: URL.Host) !void {
14941513 },
14951514 }
14961515}
1516pub fn replaceInPlace(comptime T: type, input: []T, needle: []const T, replacement: []const T) usize {
1517 // Empty needle will loop until output buffer overflows.
1518 std.debug.assert(needle.len > 0);
1519 std.debug.assert(needle.len >= replacement.len);
1520
1521 var slide: usize = 0;
1522 var replacements: usize = 0;
1523 while (std.mem.indexOf(u8, input[slide..], needle)) |idx| {
1524 slide += idx;
1525 std.mem.copyForwards(u8, input[slide..], replacement);
1526 slide += replacement.len;
1527 std.mem.copyForwards(u8, input[slide..], input[slide..][needle.len - replacement.len ..]);
1528 slide += needle.len - replacement.len;
1529 replacements += 1;
1530 }
1531 return replacements;
1532}
14971533
14981534pub fn ManyArrayList(N: usize, T: type) type {
14991535 return struct {
......@@ -1540,5 +1576,13 @@ pub fn ManyArrayList(N: usize, T: type) type {
15401576 try self.list.insertSlice(real_n, slice);
15411577 self.lengths[n] += slice.len;
15421578 }
1579
1580 pub fn replace(self: *@This(), n: usize, o: usize, c: usize, slice: []const T) !void {
1581 std.debug.assert(o + c <= self.lengths[n]);
1582 const real_n = extras.sum(usize, self.lengths[0..n]);
1583 try self.list.replaceRange(real_n + o, c, slice);
1584 self.lengths[n] -= c;
1585 self.lengths[n] += slice.len;
1586 }
15431587 };
15441588}
zig-out/test.zig+3-3
......@@ -17,14 +17,14 @@ 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 _ = origin;
2021 try expect(u.protocol).toEqualString(protocol);
2122 try expect(u.username).toEqualString(username);
2223 try expect(u.password).toEqualString(password);
2324 try expect(u.hostname).toEqualString(hostname);
2425 try expect(u.port).toEqualString(port);
2526 try expect(u.host).toEqualString(host);
26 _ = origin;
27 _ = pathname;
27 try expect(u.pathname).toEqualString(pathname);
2828 try expect(u.search).toEqualString(search);
2929 try expect(u.hash).toEqualString(hash);
3030 _ = href;
......@@ -46,7 +46,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v
4646 defer allocator.free(u.href);
4747 try expect(u.hostname).toEqualString(output);
4848 try expect(u.host).toEqualString(output);
49 // assert_equals(url.pathname, "/x");
49 try expect(u.pathname).toEqualString("/x");
5050 // assert_equals(url.href, 'https://{idnaTest.output}/x');
5151}
5252