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:...@@ -47,14 +47,14 @@ 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 _ = origin;
50 try expect(u.protocol).toEqualString(protocol);51 try expect(u.protocol).toEqualString(protocol);
51 try expect(u.username).toEqualString(username);52 try expect(u.username).toEqualString(username);
52 try expect(u.password).toEqualString(password);53 try expect(u.password).toEqualString(password);
53 try expect(u.hostname).toEqualString(hostname);54 try expect(u.hostname).toEqualString(hostname);
54 try expect(u.port).toEqualString(port);55 try expect(u.port).toEqualString(port);
55 try expect(u.host).toEqualString(host);56 try expect(u.host).toEqualString(host);
56 _ = origin;57 try expect(u.pathname).toEqualString(pathname);
57 _ = pathname;
58 try expect(u.search).toEqualString(search);58 try expect(u.search).toEqualString(search);
59 try expect(u.hash).toEqualString(hash);59 try expect(u.hash).toEqualString(hash);
60 _ = href;60 _ = href;
...@@ -76,7 +76,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v...@@ -76,7 +76,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v
76 defer allocator.free(u.href);76 defer allocator.free(u.href);
77 try expect(u.hostname).toEqualString(output);77 try expect(u.hostname).toEqualString(output);
78 try expect(u.host).toEqualString(output);78 try expect(u.host).toEqualString(output);
79 // assert_equals(url.pathname, "/x");79 try expect(u.pathname).toEqualString("/x");
80 // assert_equals(url.href, 'https://{idnaTest.output}/x');80 // assert_equals(url.href, 'https://{idnaTest.output}/x');
81}81}
82`);82`);
url.zig+52-8
...@@ -14,6 +14,7 @@ pub const URL = struct {...@@ -14,6 +14,7 @@ pub const URL = struct {
14 hostname_kind: HostKind = .name,14 hostname_kind: HostKind = .name,
15 port: []const u8,15 port: []const u8,
16 host: []const u8,16 host: []const u8,
17 pathname: []const u8,
17 search: []const u8,18 search: []const u8,
18 hash: []const u8,19 hash: []const u8,
1920
...@@ -225,6 +226,7 @@ pub const URL = struct {...@@ -225,6 +226,7 @@ pub const URL = struct {
225 pointer -= 1;226 pointer -= 1;
226 i = lastcpi(inputl.items[0..i]);227 i = lastcpi(inputl.items[0..i]);
227 c = inputl.items[i];228 c = inputl.items[i];
229 try href.appendSlice(10, "/");
228 }230 }
229 },231 },
230 .relative => {232 .relative => {
...@@ -413,7 +415,7 @@ pub const URL = struct {...@@ -413,7 +415,7 @@ pub const URL = struct {
413 // 1. If url is special and buffer is the empty string, host-missing validation error, return failure.415 // 1. If url is special and buffer is the empty string, host-missing validation error, return failure.
414 if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL416 if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL
415 // 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.417 // 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;
417 // 3. Let host be the result of host parsing buffer with url is not special.419 // 3. Let host be the result of host parsing buffer with url is not special.
418 // 4. If host is failure, then return failure.420 // 4. If host is failure, then return failure.
419 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));421 const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0)));
...@@ -527,6 +529,7 @@ pub const URL = struct {...@@ -527,6 +529,7 @@ pub const URL = struct {
527 pointer -= 1;529 pointer -= 1;
528 i = lastcpi(inputl.items[0..i]);530 i = lastcpi(inputl.items[0..i]);
529 c = inputl.items[i];531 c = inputl.items[i];
532 try href.appendSlice(10, "/");
530 }533 }
531 },534 },
532 .file_slash => {535 .file_slash => {
...@@ -552,6 +555,7 @@ pub const URL = struct {...@@ -552,6 +555,7 @@ pub const URL = struct {
552 pointer -= 1;555 pointer -= 1;
553 i = lastcpi(inputl.items[0..i]);556 i = lastcpi(inputl.items[0..i]);
554 c = inputl.items[i];557 c = inputl.items[i];
558 try href.appendSlice(10, "/");
555 }559 }
556 },560 },
557 .file_host => {561 .file_host => {
...@@ -564,6 +568,7 @@ pub const URL = struct {...@@ -564,6 +568,7 @@ pub const URL = struct {
564 // > This is a (platform-independent) Windows drive letter quirk. buffer is not reset here and instead used in the path state.568 // > This is a (platform-independent) Windows drive letter quirk. buffer is not reset here and instead used in the path state.
565 if (state_override == null and isWindowsDriveLetter(buffer.items)) {569 if (state_override == null and isWindowsDriveLetter(buffer.items)) {
566 state = .path;570 state = .path;
571 try href.appendSlice(10, "/");
567 }572 }
568 // 2. Otherwise, if buffer is the empty string, then:573 // 2. Otherwise, if buffer is the empty string, then:
569 else if (buffer.items.len == 0) {574 else if (buffer.items.len == 0) {
...@@ -596,7 +601,9 @@ pub const URL = struct {...@@ -596,7 +601,9 @@ pub const URL = struct {
596 }601 }
597 }602 }
598 // 2. Otherwise, append c to buffer.603 // 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 }
600 },607 },
601 .path_start => {608 .path_start => {
602 // 1. If url is special, then:609 // 1. If url is special, then:
...@@ -611,6 +618,7 @@ pub const URL = struct {...@@ -611,6 +618,7 @@ pub const URL = struct {
611 i = lastcpi(inputl.items[0..i]);618 i = lastcpi(inputl.items[0..i]);
612 c = inputl.items[i];619 c = inputl.items[i];
613 }620 }
621 try href.appendSlice(10, "/");
614 }622 }
615 // 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.623 // 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.
616 else if (state_override == null and c == '?') {624 else if (state_override == null and c == '?') {
...@@ -633,6 +641,8 @@ pub const URL = struct {...@@ -633,6 +641,8 @@ pub const URL = struct {
633 pointer -= 1;641 pointer -= 1;
634 i = lastcpi(inputl.items[0..i]);642 i = lastcpi(inputl.items[0..i]);
635 c = inputl.items[i];643 c = inputl.items[i];
644 } else {
645 try href.appendSlice(10, &.{c});
636 }646 }
637 }647 }
638 // 5. Otherwise, if state override is given and url’s host is null, append the empty string to url’s path.648 // 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 {...@@ -641,34 +651,41 @@ pub const URL = struct {
641 }651 }
642 },652 },
643 .path => {653 .path => {
654 const is_lsep = c == '/';
655 const is_rsep = isSchemeSpecial(href.items(0)) and c == '\\';
644 // 1. If one of the following is true:656 // 1. If one of the following is true:
645 // - c is the EOF code point or U+002F (/)657 // - c is the EOF code point or U+002F (/)
646 // - url is special and c is U+005C (\)658 // - url is special and c is U+005C (\)
647 // - state override is not given and c is U+003F (?) or U+0023 (#)659 // - state override is not given and c is U+003F (?) or U+0023 (#)
648 // then:660 // 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 == '#'))) {
650 // 1. If url is special and c is U+005C (\), invalid-reverse-solidus validation error.662 // 1. If url is special and c is U+005C (\), invalid-reverse-solidus validation error.
651 {}663 {}
652 // 2. If buffer is a double-dot URL path segment, then:664 // 2. If buffer is a double-dot URL path segment, then:
653 if (isDoubleDotPathSeg(buffer.items)) {665 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, "");
655 // 1. Shorten url’s path.669 // 1. Shorten url’s path.
656 // 2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path.670 // 2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path.
657 // > This means that for input /usr/.. the result is / and not a lack of a path.671 // > This means that for input /usr/.. the result is / and not a lack of a path.
658 }672 }
659 // 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.673 // 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 == '\\'))) {674 else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(is_rsep))) {
661 // @panic("TODO");675 // =no action needed
662 }676 }
663 // 4. Otherwise, if buffer is not a single-dot URL path segment, then:677 // 4. Otherwise, if buffer is not a single-dot URL path segment, then:
664 else if (!isSingleDotPathSeg(buffer.items)) {678 else if (!isSingleDotPathSeg(buffer.items)) {
665 // 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 (:).679 // 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 (:).
666 // > This is a (platform-independent) Windows drive letter quirk.680 // > 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)) {
668 buffer.items[1] = ':';682 buffer.items[1] = ':';
669 }683 }
670 // 2. Append buffer to url’s path.684 // 2. Append buffer to url’s path.
685 // if (!std.mem.endsWith(u8, href.items(10), "/")) try href.appendSlice(10, "/");
671 try href.appendSlice(10, buffer.items);686 try href.appendSlice(10, buffer.items);
687 if (is_lsep) try href.appendSlice(10, "/");
688 if (is_rsep) try href.appendSlice(10, "/");
672 }689 }
673 // 5. Set buffer to the empty string.690 // 5. Set buffer to the empty string.
674 buffer.clearRetainingCapacity();691 buffer.clearRetainingCapacity();
...@@ -808,6 +825,7 @@ pub const URL = struct {...@@ -808,6 +825,7 @@ pub const URL = struct {
808 .hostname_kind = hostname_kind,825 .hostname_kind = hostname_kind,
809 .port = _href[extras.sum(usize, href.lengths[0..9])..][0..href.lengths[9]],826 .port = _href[extras.sum(usize, href.lengths[0..9])..][0..href.lengths[9]],
810 .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])],827 .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]],
811 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],829 .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])],
812 .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])],830 .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])],
813 };831 };
...@@ -1345,6 +1363,7 @@ fn is_query_percent_char(c: u8) bool {...@@ -1345,6 +1363,7 @@ fn is_query_percent_char(c: u8) bool {
1345/// https://url.spec.whatwg.org/#path-percent-encode-set1363/// https://url.spec.whatwg.org/#path-percent-encode-set
1346fn is_path_percent_char(c: u8) bool {1364fn is_path_percent_char(c: u8) bool {
1347 if (c == '?') return true;1365 if (c == '?') return true;
1366 if (c == '^') return true;
1348 if (c == '`') return true;1367 if (c == '`') return true;
1349 if (c == '{') return true;1368 if (c == '{') return true;
1350 if (c == '}') return true;1369 if (c == '}') return true;
...@@ -1371,7 +1390,7 @@ fn is_userinfo_percent_char(c: u8) bool {...@@ -1371,7 +1390,7 @@ fn is_userinfo_percent_char(c: u8) bool {
1371 if (c == ';') return true;1390 if (c == ';') return true;
1372 if (c == '=') return true;1391 if (c == '=') return true;
1373 if (c == '@') return true;1392 if (c == '@') return true;
1374 if (c >= '[' and c <= '^') return true;1393 if (c >= '[' and c <= ']') return true;
1375 if (c == '|') return true;1394 if (c == '|') return true;
1376 return is_path_percent_char(c);1395 return is_path_percent_char(c);
1377}1396}
...@@ -1494,6 +1513,23 @@ fn setHost(href: *ManyArrayList(15, u8), h: URL.Host) !void {...@@ -1494,6 +1513,23 @@ fn setHost(href: *ManyArrayList(15, u8), h: URL.Host) !void {
1494 },1513 },
1495 }1514 }
1496}1515}
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
1498pub fn ManyArrayList(N: usize, T: type) type {1534pub fn ManyArrayList(N: usize, T: type) type {
1499 return struct {1535 return struct {
...@@ -1540,5 +1576,13 @@ pub fn ManyArrayList(N: usize, T: type) type {...@@ -1540,5 +1576,13 @@ pub fn ManyArrayList(N: usize, T: type) type {
1540 try self.list.insertSlice(real_n, slice);1576 try self.list.insertSlice(real_n, slice);
1541 self.lengths[n] += slice.len;1577 self.lengths[n] += slice.len;
1542 }1578 }
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 }
1543 };1587 };
1544}1588}
zig-out/test.zig+3-3
...@@ -17,14 +17,14 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin:...@@ -17,14 +17,14 @@ 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 _ = origin;
20 try expect(u.protocol).toEqualString(protocol);21 try expect(u.protocol).toEqualString(protocol);
21 try expect(u.username).toEqualString(username);22 try expect(u.username).toEqualString(username);
22 try expect(u.password).toEqualString(password);23 try expect(u.password).toEqualString(password);
23 try expect(u.hostname).toEqualString(hostname);24 try expect(u.hostname).toEqualString(hostname);
24 try expect(u.port).toEqualString(port);25 try expect(u.port).toEqualString(port);
25 try expect(u.host).toEqualString(host);26 try expect(u.host).toEqualString(host);
26 _ = origin;27 try expect(u.pathname).toEqualString(pathname);
27 _ = pathname;
28 try expect(u.search).toEqualString(search);28 try expect(u.search).toEqualString(search);
29 try expect(u.hash).toEqualString(hash);29 try expect(u.hash).toEqualString(hash);
30 _ = href;30 _ = href;
...@@ -46,7 +46,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v...@@ -46,7 +46,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v
46 defer allocator.free(u.href);46 defer allocator.free(u.href);
47 try expect(u.hostname).toEqualString(output);47 try expect(u.hostname).toEqualString(output);
48 try expect(u.host).toEqualString(output);48 try expect(u.host).toEqualString(output);
49 // assert_equals(url.pathname, "/x");49 try expect(u.pathname).toEqualString("/x");
50 // assert_equals(url.href, 'https://{idnaTest.output}/x');50 // assert_equals(url.href, 'https://{idnaTest.output}/x');
51}51}
5252