From f10bee8cb033a5ef8c32f88ba5c94a9b864b7cac Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 7 May 2025 08:25:14 -0700 Subject: [PATCH 1/2] fix single slash url resolving issue --- src/browser/browser.zig | 74 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index e0ab117a..269d7f20 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -205,6 +205,44 @@ pub const Session = struct { } }; +// Properly stitches two URLs together. +// +// For URLs with a path, it will replace the last entry with the src. +// For URLs without a path, it will add src as the path. +fn stitchUrl(allocator: std.mem.Allocator, src: []const u8, base: []const u8) ![]const u8 { + // Traversing until the path + var slash_iter = std.mem.splitScalar(u8, base, '/'); + _ = slash_iter.next(); + _ = slash_iter.next(); + _ = slash_iter.next(); + + if (slash_iter.index) |path_index| { + // Remove final slash from pathless base slice. + const pathless_base = base[0 .. path_index - 1]; + const path = slash_iter.rest(); + + if (path.len > 0) { + var split_halves = std.mem.splitBackwardsScalar(u8, path, '/'); + _ = split_halves.first(); + const stripped_path = split_halves.rest(); + + if (stripped_path.len > 0) { + // Multi path entry + return try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ pathless_base, stripped_path, src }); + } else { + // Single path entry + return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ pathless_base, src }); + } + } else { + // Slash at the end case + return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ pathless_base, src }); + } + } else { + // No path case + return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, src }); + } +} + // Page navigates to an url. // You can navigates multiple urls with the same page, but you have to call // end() to stop the previous navigation before starting a new one. @@ -599,11 +637,9 @@ pub const Page = struct { // if a base path is given, we resolve src using base. if (base) |_base| { - const dir = std.fs.path.dirname(_base); - if (dir) |_dir| { - res_src = try std.fs.path.resolve(arena, &.{ _dir, src }); - } + res_src = try stitchUrl(arena, src, _base); } + var origin_url = &self.url; const url = try origin_url.resolve(arena, res_src); @@ -898,3 +934,33 @@ test "Browser" { .{ "new Intl.DateTimeFormat()", "[object Intl.DateTimeFormat]" }, }, .{}); } + +test "Stitching Base & Src URLs (Basic)" { + const allocator = testing.allocator; + + const base = "https://www.google.com/xyz/abc/123"; + const src = "something.js"; + const result = try stitchUrl(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/xyz/abc/something.js", result); +} + +test "Stitching Base & Src URLs (Just Ending Slash)" { + const allocator = testing.allocator; + + const base = "https://www.google.com/"; + const src = "something.js"; + const result = try stitchUrl(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/something.js", result); +} + +test "Stitching Base & Src URLs (No Ending Slash)" { + const allocator = testing.allocator; + + const base = "https://www.google.com"; + const src = "something.js"; + const result = try stitchUrl(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/something.js", result); +} From 15be42340d741f2941ee9a8a5b0207e135ccf9a9 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Fri, 9 May 2025 06:57:53 -0700 Subject: [PATCH 2/2] handling relative base URLs --- src/browser/browser.zig | 70 +--------------------------------------- src/url.zig | 71 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 269d7f20..543c0640 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -205,44 +205,6 @@ pub const Session = struct { } }; -// Properly stitches two URLs together. -// -// For URLs with a path, it will replace the last entry with the src. -// For URLs without a path, it will add src as the path. -fn stitchUrl(allocator: std.mem.Allocator, src: []const u8, base: []const u8) ![]const u8 { - // Traversing until the path - var slash_iter = std.mem.splitScalar(u8, base, '/'); - _ = slash_iter.next(); - _ = slash_iter.next(); - _ = slash_iter.next(); - - if (slash_iter.index) |path_index| { - // Remove final slash from pathless base slice. - const pathless_base = base[0 .. path_index - 1]; - const path = slash_iter.rest(); - - if (path.len > 0) { - var split_halves = std.mem.splitBackwardsScalar(u8, path, '/'); - _ = split_halves.first(); - const stripped_path = split_halves.rest(); - - if (stripped_path.len > 0) { - // Multi path entry - return try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ pathless_base, stripped_path, src }); - } else { - // Single path entry - return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ pathless_base, src }); - } - } else { - // Slash at the end case - return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ pathless_base, src }); - } - } else { - // No path case - return try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, src }); - } -} - // Page navigates to an url. // You can navigates multiple urls with the same page, but you have to call // end() to stop the previous navigation before starting a new one. @@ -637,7 +599,7 @@ pub const Page = struct { // if a base path is given, we resolve src using base. if (base) |_base| { - res_src = try stitchUrl(arena, src, _base); + res_src = try URL.stitch(arena, src, _base); } var origin_url = &self.url; @@ -934,33 +896,3 @@ test "Browser" { .{ "new Intl.DateTimeFormat()", "[object Intl.DateTimeFormat]" }, }, .{}); } - -test "Stitching Base & Src URLs (Basic)" { - const allocator = testing.allocator; - - const base = "https://www.google.com/xyz/abc/123"; - const src = "something.js"; - const result = try stitchUrl(allocator, src, base); - defer allocator.free(result); - try testing.expectString("https://www.google.com/xyz/abc/something.js", result); -} - -test "Stitching Base & Src URLs (Just Ending Slash)" { - const allocator = testing.allocator; - - const base = "https://www.google.com/"; - const src = "something.js"; - const result = try stitchUrl(allocator, src, base); - defer allocator.free(result); - try testing.expectString("https://www.google.com/something.js", result); -} - -test "Stitching Base & Src URLs (No Ending Slash)" { - const allocator = testing.allocator; - - const base = "https://www.google.com"; - const src = "something.js"; - const result = try stitchUrl(allocator, src, base); - defer allocator.free(result); - try testing.expectString("https://www.google.com/something.js", result); -} diff --git a/src/url.zig b/src/url.zig index d6ae81e3..3313eca5 100644 --- a/src/url.zig +++ b/src/url.zig @@ -81,6 +81,35 @@ pub const URL = struct { pub fn toWebApi(self: *const URL, allocator: Allocator) !WebApiURL { return WebApiURL.init(allocator, self.uri); } + + /// Properly stitches two URL fragments together. + /// + /// For URLs with a path, it will replace the last entry with the src. + /// For URLs without a path, it will add src as the path. + pub fn stitch(allocator: std.mem.Allocator, src: []const u8, base: []const u8) ![]const u8 { + if (base.len == 0) { + return src; + } + + const protocol_end: usize = blk: { + if (std.mem.indexOf(u8, base, "://")) |protocol_index| { + break :blk protocol_index + 3; + } else { + break :blk 0; + } + }; + + if (std.mem.lastIndexOfScalar(u8, base[protocol_end..], '/')) |index| { + const last_slash_pos = index + protocol_end; + if (last_slash_pos == base.len - 1) { + return std.fmt.allocPrint(allocator, "{s}{s}", .{ base, src }); + } else { + return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base[0..last_slash_pos], src }); + } + } else { + return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, src }); + } + } }; test "Url resolve size" { @@ -98,3 +127,45 @@ test "Url resolve size" { try std.testing.expectEqual(out_url.raw[25], '/'); try std.testing.expectEqualStrings(out_url.raw[26..], &url_string); } + +const testing = @import("testing.zig"); + +test "URL: Stitching Base & Src URLs (Basic)" { + const allocator = testing.allocator; + + const base = "https://www.google.com/xyz/abc/123"; + const src = "something.js"; + const result = try URL.stitch(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/xyz/abc/something.js", result); +} + +test "URL: Stitching Base & Src URLs (Just Ending Slash)" { + const allocator = testing.allocator; + + const base = "https://www.google.com/"; + const src = "something.js"; + const result = try URL.stitch(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/something.js", result); +} + +test "URL: Stitching Base & Src URLs (No Ending Slash)" { + const allocator = testing.allocator; + + const base = "https://www.google.com"; + const src = "something.js"; + const result = try URL.stitch(allocator, src, base); + defer allocator.free(result); + try testing.expectString("https://www.google.com/something.js", result); +} + +test "URL: Stiching Base & Src URLs (Both Local)" { + const allocator = testing.allocator; + + const base = "./abcdef/123.js"; + const src = "something.js"; + const result = try URL.stitch(allocator, src, base); + defer allocator.free(result); + try testing.expectString("./abcdef/something.js", result); +}