authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-21 17:42:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
logc0d8ac83eb0a8d80690db1691024fdda1668f364
tree532b8e1084ecbce9ab87d9c17316f785fae05a7b
parent51451341bf06f949fd3b4cec0c5c302cecf2aa8c

std.http.Server: fix handling of HEAD + chunked


1 files changed, 32 insertions(+), 20 deletions(-)

lib/std/http/Server.zig+32-20
...@@ -309,14 +309,13 @@ pub const Request = struct {...@@ -309,14 +309,13 @@ pub const Request = struct {
309309
310 var first_buffer: [500]u8 = undefined;310 var first_buffer: [500]u8 = undefined;
311 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);311 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
312 h.writerAssumeCapacity().print("{s} {d} {s}\r\n", .{312 h.fixedWriter().print("{s} {d} {s}\r\n", .{
313 @tagName(options.version), @intFromEnum(options.status), phrase,313 @tagName(options.version), @intFromEnum(options.status), phrase,
314 }) catch |err| switch (err) {};314 }) catch unreachable;
315 if (keep_alive)315 if (keep_alive)
316 h.appendSliceAssumeCapacity("connection: keep-alive\r\n");316 h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
317 if (content.len > 0)317 if (content.len > 0)
318 h.writerAssumeCapacity().print("content-length: {d}\r\n", .{content.len}) catch |err|318 h.fixedWriter().print("content-length: {d}\r\n", .{content.len}) catch unreachable;
319 switch (err) {};
320319
321 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;320 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
322 var iovecs_len: usize = 0;321 var iovecs_len: usize = 0;
...@@ -407,13 +406,13 @@ pub const Request = struct {...@@ -407,13 +406,13 @@ pub const Request = struct {
407406
408 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);407 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);
409408
410 h.writerAssumeCapacity().print("{s} {d} {s}\r\n", .{409 h.fixedWriter().print("{s} {d} {s}\r\n", .{
411 @tagName(o.version), @intFromEnum(o.status), phrase,410 @tagName(o.version), @intFromEnum(o.status), phrase,
412 }) catch |err| switch (err) {};411 }) catch unreachable;
413 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");412 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
414413
415 if (options.content_length) |len| {414 if (options.content_length) |len| {
416 h.writerAssumeCapacity().print("content-length: {d}\r\n", .{len}) catch |err| switch (err) {};415 h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable;
417 } else {416 } else {
418 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");417 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
419 }418 }
...@@ -443,12 +442,12 @@ pub const Request = struct {...@@ -443,12 +442,12 @@ pub const Request = struct {
443 fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {442 fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {
444 const request: *Request = @constCast(@alignCast(@ptrCast(context)));443 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
445 const s = request.server;444 const s = request.server;
446 assert(s.state == .receiving_body);
447 const remaining_content_length = &request.reader_state.remaining_content_length;445 const remaining_content_length = &request.reader_state.remaining_content_length;
448 if (remaining_content_length.* == 0) {446 if (remaining_content_length.* == 0) {
449 s.state = .ready;447 s.state = .ready;
450 return 0;448 return 0;
451 }449 }
450 assert(s.state == .receiving_body);
452 const available = try fill(s, request.head_end);451 const available = try fill(s, request.head_end);
453 const len = @min(remaining_content_length.*, available.len, buffer.len);452 const len = @min(remaining_content_length.*, available.len, buffer.len);
454 @memcpy(buffer[0..len], available[0..len]);453 @memcpy(buffer[0..len], available[0..len]);
...@@ -470,8 +469,6 @@ pub const Request = struct {...@@ -470,8 +469,6 @@ pub const Request = struct {
470 fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {469 fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {
471 const request: *Request = @constCast(@alignCast(@ptrCast(context)));470 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
472 const s = request.server;471 const s = request.server;
473 assert(s.state == .receiving_body);
474
475 const cp = &request.reader_state.chunk_parser;472 const cp = &request.reader_state.chunk_parser;
476 const head_end = request.head_end;473 const head_end = request.head_end;
477474
...@@ -481,6 +478,7 @@ pub const Request = struct {...@@ -481,6 +478,7 @@ pub const Request = struct {
481 switch (cp.state) {478 switch (cp.state) {
482 .invalid => return 0,479 .invalid => return 0,
483 .data => {480 .data => {
481 assert(s.state == .receiving_body);
484 const available = try fill(s, head_end);482 const available = try fill(s, head_end);
485 const len = @min(cp.chunk_len, available.len, buffer.len);483 const len = @min(cp.chunk_len, available.len, buffer.len);
486 @memcpy(buffer[0..len], available[0..len]);484 @memcpy(buffer[0..len], available[0..len]);
...@@ -492,6 +490,7 @@ pub const Request = struct {...@@ -492,6 +490,7 @@ pub const Request = struct {
492 continue;490 continue;
493 },491 },
494 else => {492 else => {
493 assert(s.state == .receiving_body);
495 const available = try fill(s, head_end);494 const available = try fill(s, head_end);
496 const n = cp.feed(available);495 const n = cp.feed(available);
497 switch (cp.state) {496 switch (cp.state) {
...@@ -514,6 +513,8 @@ pub const Request = struct {...@@ -514,6 +513,8 @@ pub const Request = struct {
514 const bytes = s.read_buffer[head_end..s.read_buffer_len];513 const bytes = s.read_buffer[head_end..s.read_buffer_len];
515 const end = hp.feed(bytes);514 const end = hp.feed(bytes);
516 if (hp.state == .finished) {515 if (hp.state == .finished) {
516 cp.state = .invalid;
517 s.state = .ready;
517 s.next_request_start = s.read_buffer_len - bytes.len + end;518 s.next_request_start = s.read_buffer_len - bytes.len + end;
518 return out_end;519 return out_end;
519 }520 }
...@@ -527,6 +528,8 @@ pub const Request = struct {...@@ -527,6 +528,8 @@ pub const Request = struct {
527 const bytes = buf[0..read_n];528 const bytes = buf[0..read_n];
528 const end = hp.feed(bytes);529 const end = hp.feed(bytes);
529 if (hp.state == .finished) {530 if (hp.state == .finished) {
531 cp.state = .invalid;
532 s.state = .ready;
530 s.next_request_start = s.read_buffer_len - bytes.len + end;533 s.next_request_start = s.read_buffer_len - bytes.len + end;
531 return out_end;534 return out_end;
532 }535 }
...@@ -625,14 +628,13 @@ pub const Response = struct {...@@ -625,14 +628,13 @@ pub const Response = struct {
625 /// the value sent in the header, then calls `flush`.628 /// the value sent in the header, then calls `flush`.
626 /// Otherwise, transfer-encoding: chunked is being used, and it writes the629 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
627 /// end-of-stream message, then flushes the stream to the system.630 /// end-of-stream message, then flushes the stream to the system.
628 /// When request method is HEAD, does not write anything to the stream.631 /// Respects the value of `elide_body` to omit all data after the headers.
629 pub fn end(r: *Response) WriteError!void {632 pub fn end(r: *Response) WriteError!void {
630 if (r.content_length) |len| {633 if (r.content_length) |len| {
631 assert(len == 0); // Trips when end() called before all bytes written.634 assert(len == 0); // Trips when end() called before all bytes written.
632 return flush_cl(r);635 try flush_cl(r);
633 }636 } else {
634 if (!r.elide_body) {637 try flush_chunked(r, &.{});
635 return flush_chunked(r, &.{});
636 }638 }
637 r.* = undefined;639 r.* = undefined;
638 }640 }
...@@ -644,11 +646,10 @@ pub const Response = struct {...@@ -644,11 +646,10 @@ pub const Response = struct {
644 /// Asserts that the Response is using transfer-encoding: chunked.646 /// Asserts that the Response is using transfer-encoding: chunked.
645 /// Writes the end-of-stream message and any optional trailers, then647 /// Writes the end-of-stream message and any optional trailers, then
646 /// flushes the stream to the system.648 /// flushes the stream to the system.
647 /// When request method is HEAD, does not write anything to the stream.649 /// Respects the value of `elide_body` to omit all data after the headers.
648 /// Asserts there are at most 25 trailers.650 /// Asserts there are at most 25 trailers.
649 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {651 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
650 assert(r.content_length == null);652 assert(r.content_length == null);
651 if (r.elide_body) return;
652 try flush_chunked(r, options.trailers);653 try flush_chunked(r, options.trailers);
653 r.* = undefined;654 r.* = undefined;
654 }655 }
...@@ -771,6 +772,7 @@ pub const Response = struct {...@@ -771,6 +772,7 @@ pub const Response = struct {
771772
772 /// Sends all buffered data to the client.773 /// Sends all buffered data to the client.
773 /// This is redundant after calling `end`.774 /// This is redundant after calling `end`.
775 /// Respects the value of `elide_body` to omit all data after the headers.
774 pub fn flush(r: *Response) WriteError!void {776 pub fn flush(r: *Response) WriteError!void {
775 if (r.content_length != null) {777 if (r.content_length != null) {
776 return flush_cl(r);778 return flush_cl(r);
...@@ -790,7 +792,17 @@ pub const Response = struct {...@@ -790,7 +792,17 @@ pub const Response = struct {
790 const max_trailers = 25;792 const max_trailers = 25;
791 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);793 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
792 assert(r.content_length == null);794 assert(r.content_length == null);
793 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;795
796 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];
797
798 if (r.elide_body) {
799 try r.stream.writeAll(http_headers);
800 r.send_buffer_start = 0;
801 r.send_buffer_end = 0;
802 r.chunk_len = 0;
803 return;
804 }
805
794 var header_buf: [18]u8 = undefined;806 var header_buf: [18]u8 = undefined;
795 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;807 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
796808
...@@ -798,8 +810,8 @@ pub const Response = struct {...@@ -798,8 +810,8 @@ pub const Response = struct {
798 var iovecs_len: usize = 0;810 var iovecs_len: usize = 0;
799811
800 iovecs[iovecs_len] = .{812 iovecs[iovecs_len] = .{
801 .iov_base = r.send_buffer.ptr + r.send_buffer_start,813 .iov_base = http_headers.ptr,
802 .iov_len = send_buffer_len - r.chunk_len,814 .iov_len = http_headers.len,
803 };815 };
804 iovecs_len += 1;816 iovecs_len += 1;
805817