Commit 21823d1b5d

dbubel <50341559+dbubel@users.noreply.github.com>
2025-01-27 18:58:05
std.http.Server: add Request.getHeader() function (#21625)
1 parent 2043e8a
Changed files (1)
lib
std
lib/std/http/Server.zig
@@ -299,7 +299,7 @@ pub const Request = struct {
         }
     };
 
-    pub fn iterateHeaders(r: *Request) http.HeaderIterator {
+    pub fn iterateHeaders(r: Request) http.HeaderIterator {
         return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
     }
 
@@ -363,6 +363,76 @@ pub const Request = struct {
         try testing.expectEqual(null, it.next());
     }
 
+    /// Retrieves the value of a specified HTTP header from a Request struct.
+    ///
+    /// This function searches for a header with a name matching the provided string,
+    /// ignoring case. If found, it returns the header's value. If not found, it
+    /// returns null.
+    ///
+    /// Note: If multiple headers with the same name are present, this function
+    /// will return the value of the first matching header encountered.
+    ///
+    /// For accessing duplicate headers or iterating through all headers,
+    /// use the `iterateHeaders()` method directly.
+    pub fn getHeader(r: Request, s: []const u8) ?[]const u8 {
+        var iter = r.iterateHeaders();
+        while (iter.next()) |header| {
+            if (std.ascii.eqlIgnoreCase(s, header.name)) {
+                return header.value;
+            }
+        }
+        return null;
+    }
+
+    test getHeader {
+        const request_bytes = "GET /hi HTTP/1.0\r\n" ++
+            "content-tYpe: text/plain\r\n" ++
+            "content-Length:10\r\n" ++
+            "expeCt:   100-continue \r\n" ++
+            "TRansfer-encoding:\tdeflate, chunked \r\n" ++
+            "connectioN:\t keep-alive \r\n\r\n";
+        var read_buffer: [500]u8 = undefined;
+
+        @memcpy(read_buffer[0..request_bytes.len], request_bytes);
+        var server: Server = .{
+            .connection = undefined,
+            .state = .ready,
+            .read_buffer = &read_buffer,
+            .read_buffer_len = request_bytes.len,
+            .next_request_start = 0,
+        };
+        const request: Request = .{
+            .server = &server,
+            .head_end = request_bytes.len,
+            .head = undefined,
+            .reader_state = undefined,
+        };
+
+        // Test 1: Existing header with case-insensitive match
+        try testing.expectEqualStrings("text/plain", getHeader(request, "content-type").?);
+
+        // Test 2: Existing header with exact case match
+        try testing.expectEqualStrings("10", getHeader(request, "content-Length").?);
+
+        // Test 3: Existing header with leading/trailing whitespace
+        try testing.expectEqualStrings("100-continue", getHeader(request, "expect").?);
+
+        // Test 4: Existing header with tab separator
+        try testing.expectEqualStrings("deflate, chunked", getHeader(request, "Transfer-encoding").?);
+
+        // Test 5: Non-existent header
+        try testing.expectEqual(@as(?[]const u8, null), getHeader(request, "User-Agent"));
+
+        // Test 6: Case-insensitive match for header name
+        try testing.expectEqualStrings("keep-alive", getHeader(request, "CONNECTION").?);
+
+        // Test 7: Partial header name match (should fail)
+        try testing.expectEqual(@as(?[]const u8, null), getHeader(request, "content"));
+
+        // Test 8: Empty header name (should return null)
+        try testing.expectEqual(@as(?[]const u8, null), getHeader(request, ""));
+    }
+
     pub const RespondOptions = struct {
         version: http.Version = .@"HTTP/1.1",
         status: http.Status = .ok,