diff --git a/src/browser/URL.zig b/src/browser/URL.zig
index a94f9db4e..01c305bdb 100644
--- a/src/browser/URL.zig
+++ b/src/browser/URL.zig
@@ -224,6 +224,27 @@ pub fn isSecure(raw: [:0]const u8) bool {
return std.mem.startsWith(u8, raw, "https:") or std.mem.startsWith(u8, raw, "wss:");
}
+/// Cryptographic scheme or loopback host. Browsers let such origins use
+/// secure-only features (Secure cookies, prefixed cookie names) so that
+/// plain-http local development behaves like production.
+pub fn isPotentiallyTrustworthy(raw: [:0]const u8) bool {
+ return isSecure(raw) or isLoopbackHost(getHostname(raw));
+}
+
+/// Chromium's net::IsLocalhost. Takes a hostname as returned by
+/// `getHostname`: no port, IPv6 literals still bracketed.
+pub fn isLoopbackHost(hostname: []const u8) bool {
+ const host = std.mem.trimEnd(u8, hostname, ".");
+ if (std.ascii.eqlIgnoreCase(host, "localhost") or std.ascii.endsWithIgnoreCase(host, ".localhost")) {
+ return true;
+ }
+ const address = std.Io.net.IpAddress.parseLiteral(host) catch return false;
+ return switch (address) {
+ .ip4 => |ip4| ip4.bytes[0] == 127,
+ .ip6 => |ip6| std.mem.eql(u8, &ip6.bytes, &([_]u8{0} ** 15 ++ [_]u8{1})),
+ };
+}
+
pub fn getHostname(raw: []const u8) []const u8 {
const host = getHost(raw);
const port_sep = findPortSeparator(host) orelse return host;
@@ -1457,6 +1478,34 @@ test "URL: getHostname" {
try testing.expectEqualSlices(u8, "[2001:db8::1]", getHostname("https://[2001:db8::1]/"));
}
+test "URL: isPotentiallyTrustworthy" {
+ for ([_][:0]const u8{
+ "https://example.com/",
+ "http://localhost/",
+ "http://LOCALHOST:3000/x",
+ "http://localhost./",
+ "http://app.localhost/",
+ "http://127.0.0.1:8080/",
+ "http://127.255.255.254/",
+ "http://[::1]:9/",
+ }) |url| {
+ try testing.expect(isPotentiallyTrustworthy(url));
+ }
+
+ for ([_][:0]const u8{
+ "http://example.com/",
+ "http://notlocalhost/",
+ "http://localhost.evil.com/",
+ "http://127.0.0.1.evil.com/",
+ "http://128.0.0.1/",
+ "http://[::2]/",
+ "http://[::ffff:127.0.0.1]/",
+ "about:blank",
+ }) |url| {
+ try testing.expect(!isPotentiallyTrustworthy(url));
+ }
+}
+
test "URL: getPort" {
// Regular hosts
try testing.expectEqualSlices(u8, "8080", getPort("https://example.com:8080/path"));
diff --git a/src/browser/tests/cookie_store.html b/src/browser/tests/cookie_store.html
index c933f67f1..90159b5c5 100644
--- a/src/browser/tests/cookie_store.html
+++ b/src/browser/tests/cookie_store.html
@@ -74,6 +74,24 @@
});
+
+