From 20a830b847b756cea94e66812a39bfaf497cd2fc Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Fri, 23 Jan 2026 18:28:06 -0800 Subject: [PATCH] populate pathname field --- generate.ts | 6 ++--- url.zig | 60 +++++++++++++++++++++++++++++++++++++++++------- zig-out/test.zig | 6 ++--- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/generate.ts b/generate.ts index 02792db6aebae732aa46ccf8d76109d28d028a73..1f145ca4c3ffa8354fae3bd89743dfd7524b477e 100644 --- a/generate.ts +++ b/generate.ts @@ -47,14 +47,14 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin: const allocator = std.testing.allocator; const u = try url.URL.parse(allocator, input, base); defer allocator.free(u.href); + _ = origin; try expect(u.protocol).toEqualString(protocol); try expect(u.username).toEqualString(username); try expect(u.password).toEqualString(password); try expect(u.hostname).toEqualString(hostname); try expect(u.port).toEqualString(port); try expect(u.host).toEqualString(host); - _ = origin; - _ = pathname; + try expect(u.pathname).toEqualString(pathname); try expect(u.search).toEqualString(search); try expect(u.hash).toEqualString(hash); _ = href; @@ -76,7 +76,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v defer allocator.free(u.href); try expect(u.hostname).toEqualString(output); try expect(u.host).toEqualString(output); - // assert_equals(url.pathname, "/x"); + try expect(u.pathname).toEqualString("/x"); // assert_equals(url.href, 'https://{idnaTest.output}/x'); } `); diff --git a/url.zig b/url.zig index 7e5b76e49f132130a469e4bb84b090b0cb7606b6..d7c224f8982e96f66300face37213e5aebef8ab8 100644 --- a/url.zig +++ b/url.zig @@ -14,6 +14,7 @@ pub const URL = struct { hostname_kind: HostKind = .name, port: []const u8, host: []const u8, + pathname: []const u8, search: []const u8, hash: []const u8, @@ -225,6 +226,7 @@ pub const URL = struct { pointer -= 1; i = lastcpi(inputl.items[0..i]); c = inputl.items[i]; + try href.appendSlice(10, "/"); } }, .relative => { @@ -413,7 +415,7 @@ pub const URL = struct { // 1. If url is special and buffer is the empty string, host-missing validation error, return failure. if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL // 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. - 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; + else if (state_override != null and buffer.items.len == 0 and (href.lengths[3] > 0 or href.lengths[5] > 0)) return error.InvalidURL; // 3. Let host be the result of host parsing buffer with url is not special. // 4. If host is failure, then return failure. const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0))); @@ -527,6 +529,7 @@ pub const URL = struct { pointer -= 1; i = lastcpi(inputl.items[0..i]); c = inputl.items[i]; + try href.appendSlice(10, "/"); } }, .file_slash => { @@ -552,6 +555,7 @@ pub const URL = struct { pointer -= 1; i = lastcpi(inputl.items[0..i]); c = inputl.items[i]; + try href.appendSlice(10, "/"); } }, .file_host => { @@ -564,6 +568,7 @@ pub const URL = struct { // > This is a (platform-independent) Windows drive letter quirk. buffer is not reset here and instead used in the path state. if (state_override == null and isWindowsDriveLetter(buffer.items)) { state = .path; + try href.appendSlice(10, "/"); } // 2. Otherwise, if buffer is the empty string, then: else if (buffer.items.len == 0) { @@ -596,7 +601,9 @@ pub const URL = struct { } } // 2. Otherwise, append c to buffer. - try buffer.appendSlice(inputl.items[i..][0..l(c)]); + else { + try buffer.appendSlice(inputl.items[i..][0..l(c)]); + } }, .path_start => { // 1. If url is special, then: @@ -611,6 +618,7 @@ pub const URL = struct { i = lastcpi(inputl.items[0..i]); c = inputl.items[i]; } + try href.appendSlice(10, "/"); } // 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. else if (state_override == null and c == '?') { @@ -633,6 +641,8 @@ pub const URL = struct { pointer -= 1; i = lastcpi(inputl.items[0..i]); c = inputl.items[i]; + } else { + try href.appendSlice(10, &.{c}); } } // 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 { } }, .path => { + const is_lsep = c == '/'; + const is_rsep = isSchemeSpecial(href.items(0)) and c == '\\'; // 1. If one of the following is true: // - c is the EOF code point or U+002F (/) // - url is special and c is U+005C (\) // - state override is not given and c is U+003F (?) or U+0023 (#) // then: - if ((i == length or c == '/') or (isSchemeSpecial(href.items(0)) and c == '\\') or (state_override == null and (c == '?' or c == '#'))) { + if ((i == length or is_lsep) or (is_rsep) or (state_override == null and (c == '?' or c == '#'))) { // 1. If url is special and c is U+005C (\), invalid-reverse-solidus validation error. {} // 2. If buffer is a double-dot URL path segment, then: if (isDoubleDotPathSeg(buffer.items)) { - // @panic("TODO"); + const olen = href.lengths[10]; + const idx = std.mem.lastIndexOfScalar(u8, href.items(10)[0 .. olen - 1], '/') orelse 0; + try href.replace(10, idx + 1, olen - idx - 1, ""); // 1. Shorten url’s path. // 2. If neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path. // > This means that for input /usr/.. the result is / and not a lack of a path. } // 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. - else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(isSchemeSpecial(href.items(0)) and c == '\\'))) { - // @panic("TODO"); + else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(is_rsep))) { + // =no action needed } // 4. Otherwise, if buffer is not a single-dot URL path segment, then: else if (!isSingleDotPathSeg(buffer.items)) { // 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 (:). // > This is a (platform-independent) Windows drive letter quirk. - if (std.mem.eql(u8, href.items(0), "file") and href.items(10).len == 0 and isWindowsDriveLetter(buffer.items)) { + if (std.mem.eql(u8, href.items(0), "file") and href.lengths[10] <= 1 and isWindowsDriveLetter(buffer.items)) { buffer.items[1] = ':'; } // 2. Append buffer to url’s path. + // if (!std.mem.endsWith(u8, href.items(10), "/")) try href.appendSlice(10, "/"); try href.appendSlice(10, buffer.items); + if (is_lsep) try href.appendSlice(10, "/"); + if (is_rsep) try href.appendSlice(10, "/"); } // 5. Set buffer to the empty string. buffer.clearRetainingCapacity(); @@ -808,6 +825,7 @@ pub const URL = struct { .hostname_kind = hostname_kind, .port = _href[extras.sum(usize, href.lengths[0..9])..][0..href.lengths[9]], .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])], + .pathname = _href[extras.sum(usize, href.lengths[0..10])..][0..href.lengths[10]], .search = if (href.lengths[12] == 0) "" else _href[extras.sum(usize, href.lengths[0..11])..][0..extras.sum(usize, href.lengths[11..][0..2])], .hash = if (href.lengths[14] == 0) "" else _href[extras.sum(usize, href.lengths[0..13])..][0..extras.sum(usize, href.lengths[13..][0..2])], }; @@ -1345,6 +1363,7 @@ fn is_query_percent_char(c: u8) bool { /// https://url.spec.whatwg.org/#path-percent-encode-set fn is_path_percent_char(c: u8) bool { if (c == '?') return true; + if (c == '^') return true; if (c == '`') return true; if (c == '{') return true; if (c == '}') return true; @@ -1371,7 +1390,7 @@ fn is_userinfo_percent_char(c: u8) bool { if (c == ';') return true; if (c == '=') return true; if (c == '@') return true; - if (c >= '[' and c <= '^') return true; + if (c >= '[' and c <= ']') return true; if (c == '|') return true; return is_path_percent_char(c); } @@ -1494,6 +1513,23 @@ fn setHost(href: *ManyArrayList(15, u8), h: URL.Host) !void { }, } } +pub fn replaceInPlace(comptime T: type, input: []T, needle: []const T, replacement: []const T) usize { + // Empty needle will loop until output buffer overflows. + std.debug.assert(needle.len > 0); + std.debug.assert(needle.len >= replacement.len); + + var slide: usize = 0; + var replacements: usize = 0; + while (std.mem.indexOf(u8, input[slide..], needle)) |idx| { + slide += idx; + std.mem.copyForwards(u8, input[slide..], replacement); + slide += replacement.len; + std.mem.copyForwards(u8, input[slide..], input[slide..][needle.len - replacement.len ..]); + slide += needle.len - replacement.len; + replacements += 1; + } + return replacements; +} pub fn ManyArrayList(N: usize, T: type) type { return struct { @@ -1540,5 +1576,13 @@ pub fn ManyArrayList(N: usize, T: type) type { try self.list.insertSlice(real_n, slice); self.lengths[n] += slice.len; } + + pub fn replace(self: *@This(), n: usize, o: usize, c: usize, slice: []const T) !void { + std.debug.assert(o + c <= self.lengths[n]); + const real_n = extras.sum(usize, self.lengths[0..n]); + try self.list.replaceRange(real_n + o, c, slice); + self.lengths[n] -= c; + self.lengths[n] += slice.len; + } }; } diff --git a/zig-out/test.zig b/zig-out/test.zig index 157a065f0a204dafef6980f0f117ab3cfc0c5bd4..a61ceb1eaa7a17786a24a989db12dd824178bd9a 100644 --- a/zig-out/test.zig +++ b/zig-out/test.zig @@ -17,14 +17,14 @@ pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin: const allocator = std.testing.allocator; const u = try url.URL.parse(allocator, input, base); defer allocator.free(u.href); + _ = origin; try expect(u.protocol).toEqualString(protocol); try expect(u.username).toEqualString(username); try expect(u.password).toEqualString(password); try expect(u.hostname).toEqualString(hostname); try expect(u.port).toEqualString(port); try expect(u.host).toEqualString(host); - _ = origin; - _ = pathname; + try expect(u.pathname).toEqualString(pathname); try expect(u.search).toEqualString(search); try expect(u.hash).toEqualString(hash); _ = href; @@ -46,7 +46,7 @@ pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !v defer allocator.free(u.href); try expect(u.hostname).toEqualString(output); try expect(u.host).toEqualString(output); - // assert_equals(url.pathname, "/x"); + try expect(u.pathname).toEqualString("/x"); // assert_equals(url.href, 'https://{idnaTest.output}/x'); } -- 2.54.0