From dd54d55423374f42f42c7fe0a4d8d847bb3e7aaf Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 10 Mar 2024 11:31:39 +0000 Subject: [PATCH] Use content-type header if present to detect format Detect request format (HTML or JSON) from `content-type` header. Previous: - path extension (.json, .html) - "Accept" header - default (.UNKNOWN => .HTML) Current: - path extension (.json, .html) - "Accept" header - "Content-Type" header - default (.UNKNOWN => .HTML) Adjust `http.Headers` to do case-insensitive matching to conform to HTTP spec: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names --- src/jetzig/http/Headers.zig | 16 ++++++++++++++-- src/jetzig/http/Request.zig | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/jetzig/http/Headers.zig b/src/jetzig/http/Headers.zig index e3510e7..864e166 100644 --- a/src/jetzig/http/Headers.zig +++ b/src/jetzig/http/Headers.zig @@ -18,10 +18,14 @@ pub fn deinit(self: *Self) void { self.headers.deinit(self.allocator); } -// Gets the first value for a given header identified by `name`. +// Gets the first value for a given header identified by `name`. Case-insensitive string comparison. pub fn getFirstValue(self: *Self, name: []const u8) ?[]const u8 { for (self.headers.items) |header| { - if (std.mem.eql(u8, header.name, name)) return header.value; + if (name.len != header.name.len) continue; + for (name, header.name) |expected, actual| { + if (std.ascii.toLower(expected) != std.ascii.toLower(actual)) continue; + } + return header.value; } return null; } @@ -78,6 +82,14 @@ test "append" { try std.testing.expectEqualStrings(headers.getFirstValue("foo").?, "bar"); } +test "case-insensitive matching" { + const allocator = std.testing.allocator; + var headers = Self.init(allocator); + defer headers.deinit(); + try headers.append("Content-Type", "bar"); + try std.testing.expectEqualStrings(headers.getFirstValue("content-type").?, "bar"); +} + test "iterator" { const allocator = std.testing.allocator; var headers = Self.init(allocator); diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index ab50e07..0bf76ab 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -151,7 +151,10 @@ pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jet } pub fn requestFormat(self: *Self) jetzig.http.Request.Format { - return self.extensionFormat() orelse self.acceptHeaderFormat() orelse .UNKNOWN; + return self.extensionFormat() orelse + self.acceptHeaderFormat() orelse + self.contentTypeHeaderFormat() orelse + .UNKNOWN; } pub fn getHeader(self: *Self, key: []const u8) ?[]const u8 { @@ -233,6 +236,17 @@ pub fn acceptHeaderFormat(self: *Self) ?jetzig.http.Request.Format { return null; } +pub fn contentTypeHeaderFormat(self: *Self) ?jetzig.http.Request.Format { + const acceptHeader = self.getHeader("content-type"); + + if (acceptHeader) |item| { + if (std.mem.eql(u8, item, "text/html")) return .HTML; + if (std.mem.eql(u8, item, "application/json")) return .JSON; + } + + return null; +} + pub fn hash(self: *Self) ![]const u8 { return try std.fmt.allocPrint( self.allocator,