From 24ef506c918053cfd3a1264d86f7182c009c7548 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 23 Sep 2026 07:56:33 -0700 Subject: [PATCH] merge get and covers to prevent entry duplication in CorsStore --- src/network/CorsGate.zig | 25 ++++++++--------- src/network/CorsStore.zig | 58 +++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/network/CorsGate.zig b/src/network/CorsGate.zig index dfd2dcfd2..ee01c975d 100644 --- a/src/network/CorsGate.zig +++ b/src/network/CorsGate.zig @@ -219,23 +219,20 @@ pub fn check(self: *CorsGate, transfer: *Transfer) !Result { const wants_credentials = req.credentials_mode == .include; - if (try self.network.cors_store.get(.{ + const authored = try collectAuthoredHeaders(transfer, transfer.arena.allocator()); + if (try self.network.cors_store.covers(.{ .origin = origin, .target = req.url, .credentials = wants_credentials, - })) |cached| { - defer cached.deinit(self.network.cors_store.allocator); - const authored = try collectAuthoredHeaders(transfer, transfer.arena.allocator()); - if (CorsStore.covers(cached, req.method, authored.items)) { - log.debug(.cors, "cross origin", .{ - .url = req.url, - .origin = origin, - .preflight = false, - .cached = true, - }); - lp.metrics.cors_check.incr(.cached); - return .allowed; - } + }, req.method, authored.items)) { + log.debug(.cors, "cross origin", .{ + .url = req.url, + .origin = origin, + .preflight = false, + .cached = true, + }); + lp.metrics.cors_check.incr(.cached); + return .allowed; } log.debug(.cors, "cross origin", .{ diff --git a/src/network/CorsStore.zig b/src/network/CorsStore.zig index ae830ce94..82ec7f483 100644 --- a/src/network/CorsStore.zig +++ b/src/network/CorsStore.zig @@ -150,15 +150,41 @@ fn getWithExpiration(self: *CorsStore, cache_key: []const u8) ?*Entry { return entry; } -pub fn get(self: *CorsStore, key: Key) !?Entry { +fn matches(entry: Entry, method: http.Method, authored_headers: []const []const u8) bool { + if (!isSafelistedMethod(method) and !entry.methods_wildcard and !entry.methods.contains(method)) { + return false; + } + for (authored_headers) |name| { + const is_authorization = std.ascii.eqlIgnoreCase(name, "authorization"); + if (entry.headers_wildcard and !is_authorization) continue; + var found = false; + for (entry.headers) |allowed| { + if (std.ascii.eqlIgnoreCase(allowed, name)) { + found = true; + break; + } + } + if (!found) return false; + } + return true; +} + +/// Whether a cached grant for `key` covers this method/headers combination. +/// A miss (expired or absent entry) is treated as not covered. +pub fn covers( + self: *CorsStore, + key: Key, + method: http.Method, + authored_headers: []const []const u8, +) !bool { const cache_key = try key.build(self.allocator); defer self.allocator.free(cache_key); self.mutex.lockUncancelable(lp.io); defer self.mutex.unlock(lp.io); - const entry = self.getWithExpiration(cache_key) orelse return null; - return try entry.dupe(self.allocator); + const entry = self.getWithExpiration(cache_key) orelse return false; + return matches(entry.*, method, authored_headers); } /// Insert or merge a CORS grant for (origin, target). `entry` is not @@ -197,32 +223,6 @@ pub fn put(self: *CorsStore, key: Key, entry: Entry) !void { } } -pub fn covers( - entry: Entry, - method: http.Method, - authored_headers: []const []const u8, -) bool { - if (!isSafelistedMethod(method) and !entry.methods_wildcard and !entry.methods.contains(method)) { - return false; - } - - for (authored_headers) |name| { - const is_authorization = std.ascii.eqlIgnoreCase(name, "authorization"); - if (entry.headers_wildcard and !is_authorization) continue; - - var found = false; - for (entry.headers) |allowed| { - if (std.ascii.eqlIgnoreCase(allowed, name)) { - found = true; - break; - } - } - if (!found) return false; - } - - return true; -} - const testing = @import("../testing.zig"); fn freeHeaders(allocator: std.mem.Allocator, headers: []const []const u8) void {