diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 692339c8..2c5b09ff 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -587,11 +587,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 URL.stitch(arena, src, _base); } + var origin_url = &self.url; const url = try origin_url.resolve(arena, res_src); 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); +}