From d4d8381ecbf257aae1dc294d6073750da086d71c Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Sat, 18 Jul 2026 11:59:38 -0700 Subject: [PATCH] add isSameOrigin to URL --- src/browser/URL.zig | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/browser/URL.zig b/src/browser/URL.zig index fe97add68..49d48e821 100644 --- a/src/browser/URL.zig +++ b/src/browser/URL.zig @@ -341,6 +341,11 @@ pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 { return raw[0..authority_end]; } +pub fn isSameOrigin(url: [:0]const u8, origin: [:0]const u8) bool { + return std.mem.eql(u8, getProtocol(url), getProtocol(origin)) and + std.mem.eql(u8, getHost(url), getHost(origin)); +} + fn getUserInfo(raw: [:0]const u8) ?[]const u8 { const auth = parseAuthority(raw) orelse return null; if (!auth.has_user_info) return null; @@ -1500,6 +1505,56 @@ test "URL: getOrigin" { } } +test "URL: isSameOrigin" { + const Case = struct { + url: [:0]const u8, + origin: [:0]const u8, + expected: bool, + }; + + const cases = [_]Case{ + // Identical origins + .{ .url = "https://example.com/path", .origin = "https://example.com", .expected = true }, + .{ .url = "https://example.com", .origin = "https://example.com", .expected = true }, + + // Different scheme + .{ .url = "http://example.com/path", .origin = "https://example.com", .expected = false }, + + // Different host + .{ .url = "https://example.org/path", .origin = "https://example.com", .expected = false }, + + // Subdomain is a different origin + .{ .url = "https://sub.example.com/path", .origin = "https://example.com", .expected = false }, + + // Fastpath false-positive guard: url's host is NOT origin's host, + // even though origin is a literal string prefix of url. + .{ .url = "https://example.com.evil.com/path", .origin = "https://example.com", .expected = false }, + + // Same host, different port + .{ .url = "https://example.com:8080/path", .origin = "https://example.com", .expected = false }, + .{ .url = "https://example.com:8080/path", .origin = "https://example.com:8080", .expected = true }, + .{ .url = "https://example.com:8080/path", .origin = "https://example.com:9090", .expected = false }, + + // origin as a full URL (not just an origin serialization) still works + .{ .url = "https://example.com/a", .origin = "https://example.com/b?x=1", .expected = true }, + + // userinfo on url must not affect the comparison + .{ .url = "https://user:pass@example.com/path", .origin = "https://example.com", .expected = true }, + + // path/query/fragment differences are irrelevant to origin + .{ .url = "https://example.com/a/b?x=1#f", .origin = "https://example.com/", .expected = true }, + + // IPv6 hosts + .{ .url = "https://[::1]:8080/path", .origin = "https://[::1]:8080", .expected = true }, + .{ .url = "https://[::1]:8080/path", .origin = "https://[::1]:9090", .expected = false }, + .{ .url = "https://[::1]/path", .origin = "https://[2001:db8::1]/", .expected = false }, + }; + + for (cases) |case| { + try testing.expectEqual(case.expected, isSameOrigin(case.url, case.origin)); + } +} + test "URL: resolve path scheme" { const Case = struct { base: [:0]const u8,