| author | |
| committer | |
| log | a07218cc431701d13f169f4896a440d87a5a47c1 |
| tree | 8a70a10a34fca753a8b2a223a62c608f0b660b66 |
| parent | 9727931fda50ae412c47bfd40ad1d1dcd06aada0 |
4 files changed, 92 insertions(+), 19 deletions(-)
lib/std/http/Client.zig+4-2| ... | ... | @@ -488,7 +488,7 @@ pub const Response = struct { |
| 488 | 488 | var line_it = mem.splitSequence(u8, line, ": "); |
| 489 | 489 | const header_name = line_it.next().?; |
| 490 | 490 | const header_value = line_it.rest(); |
| 491 | if (header_value.len == 0) return error.HttpHeadersInvalid; | |
| 491 | if (header_name.len == 0) return error.HttpHeadersInvalid; | |
| 492 | 492 | |
| 493 | 493 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { |
| 494 | 494 | res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); |
| ... | ... | @@ -774,7 +774,7 @@ pub const Request = struct { |
| 774 | 774 | } |
| 775 | 775 | |
| 776 | 776 | for (req.extra_headers) |header| { |
| 777 | assert(header.value.len != 0); | |
| 777 | assert(header.name.len != 0); | |
| 778 | 778 | |
| 779 | 779 | try w.writeAll(header.name); |
| 780 | 780 | try w.writeAll(": "); |
| ... | ... | @@ -1515,11 +1515,13 @@ pub fn open( |
| 1515 | 1515 | ) RequestError!Request { |
| 1516 | 1516 | if (std.debug.runtime_safety) { |
| 1517 | 1517 | for (options.extra_headers) |header| { |
| 1518 | assert(header.name.len != 0); | |
| 1518 | 1519 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); |
| 1519 | 1520 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 1520 | 1521 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| 1521 | 1522 | } |
| 1522 | 1523 | for (options.privileged_headers) |header| { |
| 1524 | assert(header.name.len != 0); | |
| 1523 | 1525 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 1524 | 1526 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| 1525 | 1527 | } |
lib/std/http/HeaderIterator.zig+11-5| ... | ... | @@ -15,7 +15,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header { |
| 15 | 15 | var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": "); |
| 16 | 16 | const name = kv_it.next().?; |
| 17 | 17 | const value = kv_it.rest(); |
| 18 | if (value.len == 0) { | |
| 18 | if (name.len == 0 and value.len == 0) { | |
| 19 | 19 | if (it.is_trailer) return null; |
| 20 | 20 | const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse |
| 21 | 21 | return null; |
| ... | ... | @@ -35,7 +35,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header { |
| 35 | 35 | } |
| 36 | 36 | |
| 37 | 37 | test next { |
| 38 | var it = HeaderIterator.init("200 OK\r\na: b\r\nc: d\r\n\r\ne: f\r\n\r\n"); | |
| 38 | var it = HeaderIterator.init("200 OK\r\na: b\r\nc: \r\nd: e\r\n\r\nf: g\r\n\r\n"); | |
| 39 | 39 | try std.testing.expect(!it.is_trailer); |
| 40 | 40 | { |
| 41 | 41 | const header = it.next().?; |
| ... | ... | @@ -47,13 +47,19 @@ test next { |
| 47 | 47 | const header = it.next().?; |
| 48 | 48 | try std.testing.expect(!it.is_trailer); |
| 49 | 49 | try std.testing.expectEqualStrings("c", header.name); |
| 50 | try std.testing.expectEqualStrings("d", header.value); | |
| 50 | try std.testing.expectEqualStrings("", header.value); | |
| 51 | } | |
| 52 | { | |
| 53 | const header = it.next().?; | |
| 54 | try std.testing.expect(!it.is_trailer); | |
| 55 | try std.testing.expectEqualStrings("d", header.name); | |
| 56 | try std.testing.expectEqualStrings("e", header.value); | |
| 51 | 57 | } |
| 52 | 58 | { |
| 53 | 59 | const header = it.next().?; |
| 54 | 60 | try std.testing.expect(it.is_trailer); |
| 55 | try std.testing.expectEqualStrings("e", header.name); | |
| 56 | try std.testing.expectEqualStrings("f", header.value); | |
| 61 | try std.testing.expectEqualStrings("f", header.name); | |
| 62 | try std.testing.expectEqualStrings("g", header.value); | |
| 57 | 63 | } |
| 58 | 64 | try std.testing.expectEqual(null, it.next()); |
| 59 | 65 | } |
lib/std/http/Server.zig+17-11| ... | ... | @@ -211,7 +211,7 @@ pub const Request = struct { |
| 211 | 211 | var line_it = mem.splitSequence(u8, line, ": "); |
| 212 | 212 | const header_name = line_it.next().?; |
| 213 | 213 | const header_value = line_it.rest(); |
| 214 | if (header_value.len == 0) return error.HttpHeadersInvalid; | |
| 214 | if (header_name.len == 0) return error.HttpHeadersInvalid; | |
| 215 | 215 | |
| 216 | 216 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { |
| 217 | 217 | head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); |
| ... | ... | @@ -311,6 +311,7 @@ pub const Request = struct { |
| 311 | 311 | assert(options.extra_headers.len <= max_extra_headers); |
| 312 | 312 | if (std.debug.runtime_safety) { |
| 313 | 313 | for (options.extra_headers) |header| { |
| 314 | assert(header.name.len != 0); | |
| 314 | 315 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); |
| 315 | 316 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 316 | 317 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| ... | ... | @@ -370,11 +371,13 @@ pub const Request = struct { |
| 370 | 371 | }; |
| 371 | 372 | iovecs_len += 1; |
| 372 | 373 | |
| 373 | iovecs[iovecs_len] = .{ | |
| 374 | .iov_base = header.value.ptr, | |
| 375 | .iov_len = header.value.len, | |
| 376 | }; | |
| 377 | iovecs_len += 1; | |
| 374 | if (header.value.len != 0) { | |
| 375 | iovecs[iovecs_len] = .{ | |
| 376 | .iov_base = header.value.ptr, | |
| 377 | .iov_len = header.value.len, | |
| 378 | }; | |
| 379 | iovecs_len += 1; | |
| 380 | } | |
| 378 | 381 | |
| 379 | 382 | iovecs[iovecs_len] = .{ |
| 380 | 383 | .iov_base = "\r\n", |
| ... | ... | @@ -496,6 +499,7 @@ pub const Request = struct { |
| 496 | 499 | } |
| 497 | 500 | |
| 498 | 501 | for (o.extra_headers) |header| { |
| 502 | assert(header.name.len != 0); | |
| 499 | 503 | h.appendSliceAssumeCapacity(header.name); |
| 500 | 504 | h.appendSliceAssumeCapacity(": "); |
| 501 | 505 | h.appendSliceAssumeCapacity(header.value); |
| ... | ... | @@ -986,11 +990,13 @@ pub const Response = struct { |
| 986 | 990 | }; |
| 987 | 991 | iovecs_len += 1; |
| 988 | 992 | |
| 989 | iovecs[iovecs_len] = .{ | |
| 990 | .iov_base = trailer.value.ptr, | |
| 991 | .iov_len = trailer.value.len, | |
| 992 | }; | |
| 993 | iovecs_len += 1; | |
| 993 | if (trailer.value.len != 0) { | |
| 994 | iovecs[iovecs_len] = .{ | |
| 995 | .iov_base = trailer.value.ptr, | |
| 996 | .iov_len = trailer.value.len, | |
| 997 | }; | |
| 998 | iovecs_len += 1; | |
| 999 | } | |
| 994 | 1000 | |
| 995 | 1001 | iovecs[iovecs_len] = .{ |
| 996 | 1002 | .iov_base = "\r\n", |
lib/std/http/test.zig+60-1| ... | ... | @@ -479,6 +479,12 @@ test "general client/server API coverage" { |
| 479 | 479 | .{ .name = "location", .value = location }, |
| 480 | 480 | }, |
| 481 | 481 | }); |
| 482 | } else if (mem.eql(u8, request.head.target, "/empty")) { | |
| 483 | try request.respond("", .{ | |
| 484 | .extra_headers = &.{ | |
| 485 | .{ .name = "empty", .value = "" }, | |
| 486 | }, | |
| 487 | }); | |
| 482 | 488 | } else { |
| 483 | 489 | try request.respond("", .{ .status = .not_found }); |
| 484 | 490 | } |
| ... | ... | @@ -491,7 +497,10 @@ test "general client/server API coverage" { |
| 491 | 497 | return s.listen_address.in.getPort(); |
| 492 | 498 | } |
| 493 | 499 | }); |
| 494 | defer test_server.destroy(); | |
| 500 | defer { | |
| 501 | global.handle_new_requests = false; | |
| 502 | test_server.destroy(); | |
| 503 | } | |
| 495 | 504 | |
| 496 | 505 | const log = std.log.scoped(.client); |
| 497 | 506 | |
| ... | ... | @@ -654,6 +663,56 @@ test "general client/server API coverage" { |
| 654 | 663 | // connection has been closed |
| 655 | 664 | try expect(client.connection_pool.free_len == 0); |
| 656 | 665 | |
| 666 | { // handle empty header field value | |
| 667 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/empty", .{port}); | |
| 668 | defer gpa.free(location); | |
| 669 | const uri = try std.Uri.parse(location); | |
| 670 | ||
| 671 | log.info("{s}", .{location}); | |
| 672 | var server_header_buffer: [1024]u8 = undefined; | |
| 673 | var req = try client.open(.GET, uri, .{ | |
| 674 | .server_header_buffer = &server_header_buffer, | |
| 675 | .extra_headers = &.{ | |
| 676 | .{ .name = "empty", .value = "" }, | |
| 677 | }, | |
| 678 | }); | |
| 679 | defer req.deinit(); | |
| 680 | ||
| 681 | try req.send(.{}); | |
| 682 | try req.wait(); | |
| 683 | ||
| 684 | try std.testing.expectEqual(.ok, req.response.status); | |
| 685 | ||
| 686 | const body = try req.reader().readAllAlloc(gpa, 8192); | |
| 687 | defer gpa.free(body); | |
| 688 | ||
| 689 | try expectEqualStrings("", body); | |
| 690 | ||
| 691 | var it = req.response.iterateHeaders(); | |
| 692 | { | |
| 693 | const header = it.next().?; | |
| 694 | try expect(!it.is_trailer); | |
| 695 | try expectEqualStrings("connection", header.name); | |
| 696 | try expectEqualStrings("keep-alive", header.value); | |
| 697 | } | |
| 698 | { | |
| 699 | const header = it.next().?; | |
| 700 | try expect(!it.is_trailer); | |
| 701 | try expectEqualStrings("content-length", header.name); | |
| 702 | try expectEqualStrings("0", header.value); | |
| 703 | } | |
| 704 | { | |
| 705 | const header = it.next().?; | |
| 706 | try expect(!it.is_trailer); | |
| 707 | try expectEqualStrings("empty", header.name); | |
| 708 | try expectEqualStrings("", header.value); | |
| 709 | } | |
| 710 | try expectEqual(null, it.next()); | |
| 711 | } | |
| 712 | ||
| 713 | // connection has been kept alive | |
| 714 | try expect(client.http_proxy != null or client.connection_pool.free_len == 1); | |
| 715 | ||
| 657 | 716 | { // relative redirect |
| 658 | 717 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port}); |
| 659 | 718 | defer gpa.free(location); |