Merge pull request #949 from lightpanda-io/network_path_reference_stitch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled

Fix network-path reference stitching
This commit is contained in:
Karl Seguin
2025-08-19 15:38:43 +08:00
committed by GitHub

View File

@@ -85,16 +85,6 @@ pub const URL = struct {
return WebApiURL.init(allocator, self.uri); return WebApiURL.init(allocator, self.uri);
} }
const StitchOpts = struct {
alloc: AllocWhen = .always,
null_terminated: bool = false,
const AllocWhen = enum {
always,
if_needed,
};
};
/// Properly stitches two URL fragments together. /// Properly stitches two URL fragments together.
/// ///
/// For URLs with a path, it will replace the last entry with the src. /// For URLs with a path, it will replace the last entry with the src.
@@ -106,25 +96,24 @@ pub const URL = struct {
comptime opts: StitchOpts, comptime opts: StitchOpts,
) !StitchReturn(opts) { ) !StitchReturn(opts) {
if (base.len == 0 or isComleteHTTPUrl(path)) { if (base.len == 0 or isComleteHTTPUrl(path)) {
if (comptime opts.null_terminated) { return simpleStitch(allocator, path, opts);
return allocator.dupeZ(u8, path);
}
if (opts.alloc == .always) {
return allocator.dupe(u8, path);
}
return path;
} }
if (path.len == 0) { if (path.len == 0) {
if (comptime opts.null_terminated) { return simpleStitch(allocator, base, opts);
return allocator.dupeZ(u8, base); }
}
if (opts.alloc == .always) { if (std.mem.startsWith(u8, path, "//")) {
return allocator.dupe(u8, base); // network-path reference
const index = std.mem.indexOfScalar(u8, base, ':') orelse {
return simpleStitch(allocator, path, opts);
};
const protocol = base[0..index];
if (comptime opts.null_terminated) {
return std.fmt.allocPrintZ(allocator, "{s}:{s}", .{ protocol, path });
} }
return base; return std.fmt.allocPrint(allocator, "{s}:{s}", .{ protocol, path });
} }
// Quick hack because domains have to be at least 3 characters. // Quick hack because domains have to be at least 3 characters.
@@ -191,10 +180,6 @@ pub const URL = struct {
return out[0..out_i]; return out[0..out_i];
} }
fn StitchReturn(comptime opts: StitchOpts) type {
return if (opts.null_terminated) [:0]const u8 else []const u8;
}
pub fn concatQueryString(arena: Allocator, url: []const u8, query_string: []const u8) ![]const u8 { pub fn concatQueryString(arena: Allocator, url: []const u8, query_string: []const u8) ![]const u8 {
std.debug.assert(url.len != 0); std.debug.assert(url.len != 0);
@@ -221,11 +206,33 @@ pub const URL = struct {
} }
}; };
fn isComleteHTTPUrl(url: []const u8) bool { const StitchOpts = struct {
if (std.mem.startsWith(u8, url, "://")) { alloc: AllocWhen = .always,
return true; null_terminated: bool = false,
const AllocWhen = enum {
always,
if_needed,
};
};
fn StitchReturn(comptime opts: StitchOpts) type {
return if (opts.null_terminated) [:0]const u8 else []const u8;
}
fn simpleStitch(allocator: Allocator, url: []const u8, comptime opts: StitchOpts) !StitchReturn(opts) {
if (comptime opts.null_terminated) {
return allocator.dupeZ(u8, url);
} }
if (comptime opts.alloc == .always) {
return allocator.dupe(u8, url);
}
return url;
}
fn isComleteHTTPUrl(url: []const u8) bool {
if (url.len < 8) { if (url.len < 8) {
return false; return false;
} }
@@ -243,8 +250,6 @@ fn isComleteHTTPUrl(url: []const u8) bool {
const testing = @import("testing.zig"); const testing = @import("testing.zig");
test "URL: isComleteHTTPUrl" { test "URL: isComleteHTTPUrl" {
try testing.expectEqual(true, isComleteHTTPUrl("://lightpanda.io"));
try testing.expectEqual(true, isComleteHTTPUrl("://lightpanda.io/about"));
try testing.expectEqual(true, isComleteHTTPUrl("http://lightpanda.io/about")); try testing.expectEqual(true, isComleteHTTPUrl("http://lightpanda.io/about"));
try testing.expectEqual(true, isComleteHTTPUrl("HttP://lightpanda.io/about")); try testing.expectEqual(true, isComleteHTTPUrl("HttP://lightpanda.io/about"));
try testing.expectEqual(true, isComleteHTTPUrl("httpS://lightpanda.io/about")); try testing.expectEqual(true, isComleteHTTPUrl("httpS://lightpanda.io/about"));
@@ -253,6 +258,8 @@ test "URL: isComleteHTTPUrl" {
try testing.expectEqual(false, isComleteHTTPUrl("/lightpanda.io")); try testing.expectEqual(false, isComleteHTTPUrl("/lightpanda.io"));
try testing.expectEqual(false, isComleteHTTPUrl("../../about")); try testing.expectEqual(false, isComleteHTTPUrl("../../about"));
try testing.expectEqual(false, isComleteHTTPUrl("about")); try testing.expectEqual(false, isComleteHTTPUrl("about"));
try testing.expectEqual(false, isComleteHTTPUrl("//lightpanda.io"));
try testing.expectEqual(false, isComleteHTTPUrl("//lightpanda.io/about"));
} }
test "URL: resolve size" { test "URL: resolve size" {
@@ -280,99 +287,83 @@ test "URL: stitch" {
expected: []const u8, expected: []const u8,
}; };
const cases = [_]Case{ const cases = [_]Case{ .{
.{ .base = "https://lightpanda.io/xyz/abc/123",
.base = "https://lightpanda.io/xyz/abc/123", .path = "something.js",
.path = "something.js", .expected = "https://lightpanda.io/xyz/abc/something.js",
.expected = "https://lightpanda.io/xyz/abc/something.js", }, .{
}, .base = "https://lightpanda.io/xyz/abc/123",
.{ .path = "/something.js",
.base = "https://lightpanda.io/xyz/abc/123", .expected = "https://lightpanda.io/something.js",
.path = "/something.js", }, .{
.expected = "https://lightpanda.io/something.js", .base = "https://lightpanda.io/",
}, .path = "something.js",
.{ .expected = "https://lightpanda.io/something.js",
.base = "https://lightpanda.io/", }, .{
.path = "something.js", .base = "https://lightpanda.io/",
.expected = "https://lightpanda.io/something.js", .path = "/something.js",
}, .expected = "https://lightpanda.io/something.js",
.{ }, .{
.base = "https://lightpanda.io/", .base = "https://lightpanda.io",
.path = "/something.js", .path = "something.js",
.expected = "https://lightpanda.io/something.js", .expected = "https://lightpanda.io/something.js",
}, }, .{
.{ .base = "https://lightpanda.io",
.base = "https://lightpanda.io", .path = "abc/something.js",
.path = "something.js", .expected = "https://lightpanda.io/abc/something.js",
.expected = "https://lightpanda.io/something.js", }, .{
}, .base = "https://lightpanda.io/nested",
.{ .path = "abc/something.js",
.base = "https://lightpanda.io", .expected = "https://lightpanda.io/abc/something.js",
.path = "abc/something.js", }, .{
.expected = "https://lightpanda.io/abc/something.js", .base = "https://lightpanda.io/nested/",
}, .path = "abc/something.js",
.{ .expected = "https://lightpanda.io/nested/abc/something.js",
.base = "https://lightpanda.io/nested", }, .{
.path = "abc/something.js", .base = "https://lightpanda.io/nested/",
.expected = "https://lightpanda.io/abc/something.js", .path = "/abc/something.js",
}, .expected = "https://lightpanda.io/abc/something.js",
.{ }, .{
.base = "https://lightpanda.io/nested/", .base = "https://lightpanda.io/nested/",
.path = "abc/something.js", .path = "http://www.github.com/lightpanda-io/",
.expected = "https://lightpanda.io/nested/abc/something.js", .expected = "http://www.github.com/lightpanda-io/",
}, }, .{
.{ .base = "https://lightpanda.io/nested/",
.base = "https://lightpanda.io/nested/", .path = "",
.path = "/abc/something.js", .expected = "https://lightpanda.io/nested/",
.expected = "https://lightpanda.io/abc/something.js", }, .{
}, .base = "https://lightpanda.io/abc/aaa",
.{ .path = "./hello/./world",
.base = "https://lightpanda.io/nested/", .expected = "https://lightpanda.io/abc/hello/world",
.path = "http://www.github.com/lightpanda-io/", }, .{
.expected = "http://www.github.com/lightpanda-io/", .base = "https://lightpanda.io/abc/aaa/",
}, .path = "../hello",
.{ .expected = "https://lightpanda.io/abc/hello",
.base = "https://lightpanda.io/nested/", }, .{
.path = "", .base = "https://lightpanda.io/abc/aaa",
.expected = "https://lightpanda.io/nested/", .path = "../hello",
}, .expected = "https://lightpanda.io/hello",
.{ }, .{
.base = "https://lightpanda.io/abc/aaa", .base = "https://lightpanda.io/abc/aaa/",
.path = "./hello/./world", .path = "./.././.././hello",
.expected = "https://lightpanda.io/abc/hello/world", .expected = "https://lightpanda.io/hello",
}, }, .{
.{ .base = "some/page",
.base = "https://lightpanda.io/abc/aaa/", .path = "hello",
.path = "../hello", .expected = "some/hello",
.expected = "https://lightpanda.io/abc/hello", }, .{
}, .base = "some/page/",
.{ .path = "hello",
.base = "https://lightpanda.io/abc/aaa", .expected = "some/page/hello",
.path = "../hello", }, .{
.expected = "https://lightpanda.io/hello", .base = "some/page/other",
}, .path = ".././hello",
.{ .expected = "some/hello",
.base = "https://lightpanda.io/abc/aaa/", }, .{
.path = "./.././.././hello", .path = "//static.lightpanda.io/hello.js",
.expected = "https://lightpanda.io/hello", .base = "https://lightpanda.io/about/",
}, .expected = "https://static.lightpanda.io/hello.js",
.{ } };
.base = "some/page",
.path = "hello",
.expected = "some/hello",
},
.{
.base = "some/page/",
.path = "hello",
.expected = "some/page/hello",
},
.{
.base = "some/page/other",
.path = ".././hello",
.expected = "some/hello",
},
};
for (cases) |case| { for (cases) |case| {
const result = try stitch(testing.arena_allocator, case.path, case.base, .{}); const result = try stitch(testing.arena_allocator, case.path, case.base, .{});