Commit bba90b8863

Ryo Ota <nwtgck@nwtgck.org>
2023-04-23 19:07:52
fix HTTP server to handle a chunked transfer coding request
1 parent 1d1255b
Changed files (2)
lib/std/http/protocol.zig
@@ -556,8 +556,12 @@ pub const HeadersParser = struct {
                     switch (r.state) {
                         .invalid => return error.HttpChunkInvalid,
                         .chunk_data => if (r.next_chunk_length == 0) {
-                            // The trailer section is formatted identically to the header section.
-                            r.state = .seen_rn;
+                            if (std.mem.eql(u8, bconn.peek(), "\r\n")) {
+                                r.state = .finished;
+                            } else {
+                                // The trailer section is formatted identically to the header section.
+                                r.state = .seen_rn;
+                            }
                             r.done = true;
 
                             return out_index;
lib/std/http/Server.zig
@@ -661,3 +661,74 @@ pub fn accept(server: *Server, options: HeaderStrategy) AcceptError!*Response {
 
     return res;
 }
+
+test "HTTP server handles a chunked transfer coding request" {
+    const builtin = @import("builtin");
+
+    // This test requires spawning threads.
+    if (builtin.single_threaded) {
+        return error.SkipZigTest;
+    }
+
+    const native_endian = comptime builtin.cpu.arch.endian();
+    if (builtin.zig_backend == .stage2_llvm and native_endian == .Big) {
+        // https://github.com/ziglang/zig/issues/13782
+        return error.SkipZigTest;
+    }
+
+    if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+    const allocator = std.testing.allocator;
+    const expect = std.testing.expect;
+
+    const max_header_size = 8192;
+    var server = std.http.Server.init(allocator, .{ .reuse_address = true });
+    defer server.deinit();
+
+    const address = try std.net.Address.parseIp("127.0.0.1", 0);
+    try server.listen(address);
+    const server_port = server.socket.listen_address.in.getPort();
+
+    const server_thread = try std.Thread.spawn(.{}, (struct {
+        fn apply(s: *std.http.Server) !void {
+            const res = try s.accept(.{ .dynamic = max_header_size });
+            defer res.deinit();
+            defer res.reset();
+            try res.wait();
+
+            try expect(res.request.transfer_encoding.? == .chunked);
+
+            const server_body: []const u8 = "message from server!\n";
+            res.transfer_encoding = .{ .content_length = server_body.len };
+            try res.headers.append("content-type", "text/plain");
+            try res.headers.append("connection", "close");
+            try res.do();
+
+            var buf: [128]u8 = undefined;
+            const n = try res.readAll(&buf);
+            try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
+            _ = try res.writer().writeAll(server_body);
+            try res.finish();
+        }
+    }).apply, .{&server});
+
+    const request_bytes =
+        "POST / HTTP/1.1\r\n" ++
+        "Content-Type: text/plain\r\n" ++
+        "Transfer-Encoding: chunked\r\n" ++
+        "\r\n" ++
+        "1\r\n" ++
+        "A\r\n" ++
+        "1\r\n" ++
+        "B\r\n" ++
+        "2\r\n" ++
+        "CD\r\n" ++
+        "0\r\n" ++
+        "\r\n";
+
+    const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);
+    defer stream.close();
+    _ = try stream.writeAll(request_bytes[0..]);
+
+    server_thread.join();
+}