mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Merge pull request #901 from lightpanda-io/url_stitch
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (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 / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / zig test (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 / browser fetch (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Rework URL.stitch, handle ../ (for yahoo)
This commit is contained in:
293
src/url.zig
293
src/url.zig
@@ -100,52 +100,72 @@ pub const URL = struct {
|
|||||||
/// For URLs without a path, it will add src as the path.
|
/// For URLs without a path, it will add src as the path.
|
||||||
pub fn stitch(
|
pub fn stitch(
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
src: []const u8,
|
path: []const u8,
|
||||||
base: []const u8,
|
base: []const u8,
|
||||||
opts: StitchOpts,
|
opts: StitchOpts,
|
||||||
) ![]const u8 {
|
) ![]const u8 {
|
||||||
if (base.len == 0 or isURL(src)) {
|
if (base.len == 0 or isComleteHTTPUrl(path)) {
|
||||||
if (opts.alloc == .always) {
|
if (opts.alloc == .always) {
|
||||||
return allocator.dupe(u8, src);
|
return allocator.dupe(u8, path);
|
||||||
}
|
}
|
||||||
return src;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalized_src = src;
|
if (path.len == 0) {
|
||||||
while (std.mem.startsWith(u8, normalized_src, "./")) {
|
|
||||||
normalized_src = normalized_src[2..];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalized_src.len == 0) {
|
|
||||||
if (opts.alloc == .always) {
|
if (opts.alloc == .always) {
|
||||||
return allocator.dupe(u8, base);
|
return allocator.dupe(u8, base);
|
||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocol_end: usize = blk: {
|
// Quick hack because domains have to be at least 3 characters.
|
||||||
if (std.mem.indexOf(u8, base, "://")) |protocol_index| {
|
// Given https://a.b this will point to 'a'
|
||||||
break :blk protocol_index + 3;
|
// Given http://a.b this will point '.'
|
||||||
} else {
|
// Either way, we just care about this value to find the start of the path
|
||||||
break :blk 0;
|
const protocol_end: usize = if (isComleteHTTPUrl(base)) 8 else 0;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (normalized_src[0] == '/') {
|
if (path[0] == '/') {
|
||||||
if (std.mem.indexOfScalarPos(u8, base, protocol_end, '/')) |pos| {
|
const pos = std.mem.indexOfScalarPos(u8, base, protocol_end, '/') orelse base.len;
|
||||||
return std.fmt.allocPrint(allocator, "{s}{s}", .{ base[0..pos], normalized_src });
|
return std.fmt.allocPrint(allocator, "{s}{s}", .{ base[0..pos], path });
|
||||||
}
|
|
||||||
// not sure what to do here...error? Just let it fallthrough for now.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.lastIndexOfScalar(u8, base[protocol_end..], '/')) |index| {
|
var normalized_base = base;
|
||||||
const last_slash_pos = index + protocol_end;
|
if (std.mem.lastIndexOfScalar(u8, base[protocol_end..], '/')) |pos| {
|
||||||
if (last_slash_pos == base.len - 1) {
|
normalized_base = base[0 .. pos + protocol_end];
|
||||||
return std.fmt.allocPrint(allocator, "{s}{s}", .{ base, normalized_src });
|
|
||||||
}
|
|
||||||
return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base[0..last_slash_pos], normalized_src });
|
|
||||||
}
|
}
|
||||||
return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, normalized_src });
|
|
||||||
|
var out = try std.fmt.allocPrint(allocator, "{s}/{s}", .{
|
||||||
|
normalized_base,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Strip out ./ and ../. This is done in-place, because doing so can
|
||||||
|
// only ever make `out` smaller. After this, `out` cannot be freed by
|
||||||
|
// an allocator, which is ok, because we expect allocator to be an arena.
|
||||||
|
var in_i: usize = 0;
|
||||||
|
var out_i: usize = 0;
|
||||||
|
while (in_i < out.len) {
|
||||||
|
if (std.mem.startsWith(u8, out[in_i..], "./")) {
|
||||||
|
in_i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std.mem.startsWith(u8, out[in_i..], "../")) {
|
||||||
|
std.debug.assert(out[out_i - 1] == '/');
|
||||||
|
|
||||||
|
out_i -= 2;
|
||||||
|
while (out_i > 1 and out[out_i - 1] != '/') {
|
||||||
|
out_i -= 1;
|
||||||
|
}
|
||||||
|
// <= to deal with the hack-ish protocol_end which will be off-by-one between http and https
|
||||||
|
if (out_i <= protocol_end) return error.InvalidURL;
|
||||||
|
in_i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[out_i] = out[in_i];
|
||||||
|
in_i += 1;
|
||||||
|
out_i += 1;
|
||||||
|
}
|
||||||
|
return out[0..out_i];
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -174,7 +194,7 @@ pub const URL = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn isURL(url: []const u8) bool {
|
fn isComleteHTTPUrl(url: []const u8) bool {
|
||||||
if (std.mem.startsWith(u8, url, "://")) {
|
if (std.mem.startsWith(u8, url, "://")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -195,17 +215,17 @@ fn isURL(url: []const u8) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("testing.zig");
|
const testing = @import("testing.zig");
|
||||||
test "URL: isURL" {
|
test "URL: isComleteHTTPUrl" {
|
||||||
try testing.expectEqual(true, isURL("://lightpanda.io"));
|
try testing.expectEqual(true, isComleteHTTPUrl("://lightpanda.io"));
|
||||||
try testing.expectEqual(true, isURL("://lightpanda.io/about"));
|
try testing.expectEqual(true, isComleteHTTPUrl("://lightpanda.io/about"));
|
||||||
try testing.expectEqual(true, isURL("http://lightpanda.io/about"));
|
try testing.expectEqual(true, isComleteHTTPUrl("http://lightpanda.io/about"));
|
||||||
try testing.expectEqual(true, isURL("HttP://lightpanda.io/about"));
|
try testing.expectEqual(true, isComleteHTTPUrl("HttP://lightpanda.io/about"));
|
||||||
try testing.expectEqual(true, isURL("httpS://lightpanda.io/about"));
|
try testing.expectEqual(true, isComleteHTTPUrl("httpS://lightpanda.io/about"));
|
||||||
try testing.expectEqual(true, isURL("HTTPs://lightpanda.io/about"));
|
try testing.expectEqual(true, isComleteHTTPUrl("HTTPs://lightpanda.io/about"));
|
||||||
|
|
||||||
try testing.expectEqual(false, isURL("/lightpanda.io"));
|
try testing.expectEqual(false, isComleteHTTPUrl("/lightpanda.io"));
|
||||||
try testing.expectEqual(false, isURL("../../about"));
|
try testing.expectEqual(false, isComleteHTTPUrl("../../about"));
|
||||||
try testing.expectEqual(false, isURL("about"));
|
try testing.expectEqual(false, isComleteHTTPUrl("about"));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "URL: resolve size" {
|
test "URL: resolve size" {
|
||||||
@@ -224,93 +244,122 @@ test "URL: resolve size" {
|
|||||||
try std.testing.expectEqualStrings(out_url.raw[26..], &url_string);
|
try std.testing.expectEqualStrings(out_url.raw[26..], &url_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "URL: Stitching Base & Src URLs (Basic)" {
|
test "URL: stitch" {
|
||||||
const allocator = testing.allocator;
|
defer testing.reset();
|
||||||
|
|
||||||
const base = "https://lightpanda.io/xyz/abc/123";
|
const Case = struct {
|
||||||
const src = "something.js";
|
base: []const u8,
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
path: []const u8,
|
||||||
defer allocator.free(result);
|
expected: []const u8,
|
||||||
try testing.expectString("https://lightpanda.io/xyz/abc/something.js", result);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
test "URL: Stitching Base & Src URLs (Just Ending Slash)" {
|
const cases = [_]Case{
|
||||||
const allocator = testing.allocator;
|
.{
|
||||||
|
.base = "https://lightpanda.io/xyz/abc/123",
|
||||||
|
.path = "something.js",
|
||||||
|
.expected = "https://lightpanda.io/xyz/abc/something.js",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/xyz/abc/123",
|
||||||
|
.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",
|
||||||
|
.expected = "https://lightpanda.io/something.js",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io",
|
||||||
|
.path = "something.js",
|
||||||
|
.expected = "https://lightpanda.io/something.js",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io",
|
||||||
|
.path = "abc/something.js",
|
||||||
|
.expected = "https://lightpanda.io/abc/something.js",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/nested",
|
||||||
|
.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",
|
||||||
|
.expected = "https://lightpanda.io/abc/something.js",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/nested/",
|
||||||
|
.path = "http://www.github.com/lightpanda-io/",
|
||||||
|
.expected = "http://www.github.com/lightpanda-io/",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/nested/",
|
||||||
|
.path = "",
|
||||||
|
.expected = "https://lightpanda.io/nested/",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/abc/aaa",
|
||||||
|
.path = "./hello/./world",
|
||||||
|
.expected = "https://lightpanda.io/abc/hello/world",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/abc/aaa/",
|
||||||
|
.path = "../hello",
|
||||||
|
.expected = "https://lightpanda.io/abc/hello",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/abc/aaa",
|
||||||
|
.path = "../hello",
|
||||||
|
.expected = "https://lightpanda.io/hello",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "https://lightpanda.io/abc/aaa/",
|
||||||
|
.path = "./.././.././hello",
|
||||||
|
.expected = "https://lightpanda.io/hello",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "some/page",
|
||||||
|
.path = "hello",
|
||||||
|
.expected = "some/hello",
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.base = "some/page/",
|
||||||
|
.path = "hello",
|
||||||
|
.expected = "some/page/hello",
|
||||||
|
},
|
||||||
|
|
||||||
const base = "https://lightpanda.io/";
|
.{
|
||||||
const src = "something.js";
|
.base = "some/page/other",
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
.path = ".././hello",
|
||||||
defer allocator.free(result);
|
.expected = "some/hello",
|
||||||
try testing.expectString("https://lightpanda.io/something.js", result);
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
test "URL: Stitching Base & Src URLs with leading slash" {
|
for (cases) |case| {
|
||||||
const allocator = testing.allocator;
|
const result = try stitch(testing.arena_allocator, case.path, case.base, .{});
|
||||||
|
try testing.expectString(case.expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
const base = "https://lightpanda.io/";
|
try testing.expectError(
|
||||||
const src = "/something.js";
|
error.InvalidURL,
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
stitch(testing.arena_allocator, "../hello", "https://lightpanda.io/", .{}),
|
||||||
defer allocator.free(result);
|
);
|
||||||
try testing.expectString("https://lightpanda.io/something.js", result);
|
try testing.expectError(
|
||||||
}
|
error.InvalidURL,
|
||||||
|
stitch(testing.arena_allocator, "../hello", "http://lightpanda.io/", .{}),
|
||||||
test "URL: Stitching Base & Src URLs (No Ending Slash)" {
|
);
|
||||||
const allocator = testing.allocator;
|
|
||||||
|
|
||||||
const base = "https://lightpanda.io";
|
|
||||||
const src = "something.js";
|
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
|
||||||
defer allocator.free(result);
|
|
||||||
try testing.expectString("https://lightpanda.io/something.js", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "URL: Stitching Base with absolute src" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
|
|
||||||
const base = "https://lightpanda.io/hello";
|
|
||||||
const src = "/abc/something.js";
|
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
|
||||||
defer allocator.free(result);
|
|
||||||
try testing.expectString("https://lightpanda.io/abc/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);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "URL: Stiching src as full path" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
|
|
||||||
const base = "https://www.lightpanda.io/";
|
|
||||||
const src = "https://lightpanda.io/something.js";
|
|
||||||
const result = try URL.stitch(allocator, src, base, .{ .alloc = .if_needed });
|
|
||||||
try testing.expectString("https://lightpanda.io/something.js", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "URL: Stitching Base & Src URLs (empty src)" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
|
|
||||||
const base = "https://lightpanda.io/xyz/abc/123";
|
|
||||||
const src = "";
|
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
|
||||||
defer allocator.free(result);
|
|
||||||
try testing.expectString("https://lightpanda.io/xyz/abc/123", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "URL: Stitching dotslash" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
|
|
||||||
const base = "https://lightpanda.io/hello/";
|
|
||||||
const src = "./something.js";
|
|
||||||
const result = try URL.stitch(allocator, src, base, .{});
|
|
||||||
defer allocator.free(result);
|
|
||||||
try testing.expectString("https://lightpanda.io/hello/something.js", result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "URL: concatQueryString" {
|
test "URL: concatQueryString" {
|
||||||
|
|||||||
Reference in New Issue
Block a user