| 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,7 +488,7 @@ pub const Response = struct { |
| 488 | var line_it = mem.splitSequence(u8, line, ": "); | 488 | var line_it = mem.splitSequence(u8, line, ": "); |
| 489 | const header_name = line_it.next().?; | 489 | const header_name = line_it.next().?; |
| 490 | const header_value = line_it.rest(); | 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 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { | 493 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { |
| 494 | res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); | 494 | res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); |
| ... | @@ -774,7 +774,7 @@ pub const Request = struct { | ... | @@ -774,7 +774,7 @@ pub const Request = struct { |
| 774 | } | 774 | } |
| 775 | 775 | ||
| 776 | for (req.extra_headers) |header| { | 776 | for (req.extra_headers) |header| { |
| 777 | assert(header.value.len != 0); | 777 | assert(header.name.len != 0); |
| 778 | 778 | ||
| 779 | try w.writeAll(header.name); | 779 | try w.writeAll(header.name); |
| 780 | try w.writeAll(": "); | 780 | try w.writeAll(": "); |
| ... | @@ -1515,11 +1515,13 @@ pub fn open( | ... | @@ -1515,11 +1515,13 @@ pub fn open( |
| 1515 | ) RequestError!Request { | 1515 | ) RequestError!Request { |
| 1516 | if (std.debug.runtime_safety) { | 1516 | if (std.debug.runtime_safety) { |
| 1517 | for (options.extra_headers) |header| { | 1517 | for (options.extra_headers) |header| { |
| 1518 | assert(header.name.len != 0); | ||
| 1518 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); | 1519 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); |
| 1519 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); | 1520 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 1520 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); | 1521 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| 1521 | } | 1522 | } |
| 1522 | for (options.privileged_headers) |header| { | 1523 | for (options.privileged_headers) |header| { |
| 1524 | assert(header.name.len != 0); | ||
| 1523 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); | 1525 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 1524 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); | 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,7 +15,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header { |
| 15 | var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": "); | 15 | var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": "); |
| 16 | const name = kv_it.next().?; | 16 | const name = kv_it.next().?; |
| 17 | const value = kv_it.rest(); | 17 | const value = kv_it.rest(); |
| 18 | if (value.len == 0) { | 18 | if (name.len == 0 and value.len == 0) { |
| 19 | if (it.is_trailer) return null; | 19 | if (it.is_trailer) return null; |
| 20 | const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse | 20 | const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse |
| 21 | return null; | 21 | return null; |
| ... | @@ -35,7 +35,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header { | ... | @@ -35,7 +35,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header { |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | test next { | 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 | try std.testing.expect(!it.is_trailer); | 39 | try std.testing.expect(!it.is_trailer); |
| 40 | { | 40 | { |
| 41 | const header = it.next().?; | 41 | const header = it.next().?; |
| ... | @@ -47,13 +47,19 @@ test next { | ... | @@ -47,13 +47,19 @@ test next { |
| 47 | const header = it.next().?; | 47 | const header = it.next().?; |
| 48 | try std.testing.expect(!it.is_trailer); | 48 | try std.testing.expect(!it.is_trailer); |
| 49 | try std.testing.expectEqualStrings("c", header.name); | 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 | const header = it.next().?; | 59 | const header = it.next().?; |
| 54 | try std.testing.expect(it.is_trailer); | 60 | try std.testing.expect(it.is_trailer); |
| 55 | try std.testing.expectEqualStrings("e", header.name); | 61 | try std.testing.expectEqualStrings("f", header.name); |
| 56 | try std.testing.expectEqualStrings("f", header.value); | 62 | try std.testing.expectEqualStrings("g", header.value); |
| 57 | } | 63 | } |
| 58 | try std.testing.expectEqual(null, it.next()); | 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,7 +211,7 @@ pub const Request = struct { |
| 211 | var line_it = mem.splitSequence(u8, line, ": "); | 211 | var line_it = mem.splitSequence(u8, line, ": "); |
| 212 | const header_name = line_it.next().?; | 212 | const header_name = line_it.next().?; |
| 213 | const header_value = line_it.rest(); | 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 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { | 216 | if (std.ascii.eqlIgnoreCase(header_name, "connection")) { |
| 217 | head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); | 217 | head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); |
| ... | @@ -311,6 +311,7 @@ pub const Request = struct { | ... | @@ -311,6 +311,7 @@ pub const Request = struct { |
| 311 | assert(options.extra_headers.len <= max_extra_headers); | 311 | assert(options.extra_headers.len <= max_extra_headers); |
| 312 | if (std.debug.runtime_safety) { | 312 | if (std.debug.runtime_safety) { |
| 313 | for (options.extra_headers) |header| { | 313 | for (options.extra_headers) |header| { |
| 314 | assert(header.name.len != 0); | ||
| 314 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); | 315 | assert(std.mem.indexOfScalar(u8, header.name, ':') == null); |
| 315 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); | 316 | assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null); |
| 316 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); | 317 | assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null); |
| ... | @@ -370,11 +371,13 @@ pub const Request = struct { | ... | @@ -370,11 +371,13 @@ pub const Request = struct { |
| 370 | }; | 371 | }; |
| 371 | iovecs_len += 1; | 372 | iovecs_len += 1; |
| 372 | 373 | ||
| 373 | iovecs[iovecs_len] = .{ | 374 | if (header.value.len != 0) { |
| 374 | .iov_base = header.value.ptr, | 375 | iovecs[iovecs_len] = .{ |
| 375 | .iov_len = header.value.len, | 376 | .iov_base = header.value.ptr, |
| 376 | }; | 377 | .iov_len = header.value.len, |
| 377 | iovecs_len += 1; | 378 | }; |
| 379 | iovecs_len += 1; | ||
| 380 | } | ||
| 378 | 381 | ||
| 379 | iovecs[iovecs_len] = .{ | 382 | iovecs[iovecs_len] = .{ |
| 380 | .iov_base = "\r\n", | 383 | .iov_base = "\r\n", |
| ... | @@ -496,6 +499,7 @@ pub const Request = struct { | ... | @@ -496,6 +499,7 @@ pub const Request = struct { |
| 496 | } | 499 | } |
| 497 | 500 | ||
| 498 | for (o.extra_headers) |header| { | 501 | for (o.extra_headers) |header| { |
| 502 | assert(header.name.len != 0); | ||
| 499 | h.appendSliceAssumeCapacity(header.name); | 503 | h.appendSliceAssumeCapacity(header.name); |
| 500 | h.appendSliceAssumeCapacity(": "); | 504 | h.appendSliceAssumeCapacity(": "); |
| 501 | h.appendSliceAssumeCapacity(header.value); | 505 | h.appendSliceAssumeCapacity(header.value); |
| ... | @@ -986,11 +990,13 @@ pub const Response = struct { | ... | @@ -986,11 +990,13 @@ pub const Response = struct { |
| 986 | }; | 990 | }; |
| 987 | iovecs_len += 1; | 991 | iovecs_len += 1; |
| 988 | 992 | ||
| 989 | iovecs[iovecs_len] = .{ | 993 | if (trailer.value.len != 0) { |
| 990 | .iov_base = trailer.value.ptr, | 994 | iovecs[iovecs_len] = .{ |
| 991 | .iov_len = trailer.value.len, | 995 | .iov_base = trailer.value.ptr, |
| 992 | }; | 996 | .iov_len = trailer.value.len, |
| 993 | iovecs_len += 1; | 997 | }; |
| 998 | iovecs_len += 1; | ||
| 999 | } | ||
| 994 | 1000 | ||
| 995 | iovecs[iovecs_len] = .{ | 1001 | iovecs[iovecs_len] = .{ |
| 996 | .iov_base = "\r\n", | 1002 | .iov_base = "\r\n", |
lib/std/http/test.zig+60-1| ... | @@ -479,6 +479,12 @@ test "general client/server API coverage" { | ... | @@ -479,6 +479,12 @@ test "general client/server API coverage" { |
| 479 | .{ .name = "location", .value = location }, | 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 | } else { | 488 | } else { |
| 483 | try request.respond("", .{ .status = .not_found }); | 489 | try request.respond("", .{ .status = .not_found }); |
| 484 | } | 490 | } |
| ... | @@ -491,7 +497,10 @@ test "general client/server API coverage" { | ... | @@ -491,7 +497,10 @@ test "general client/server API coverage" { |
| 491 | return s.listen_address.in.getPort(); | 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 | const log = std.log.scoped(.client); | 505 | const log = std.log.scoped(.client); |
| 497 | 506 | ||
| ... | @@ -654,6 +663,56 @@ test "general client/server API coverage" { | ... | @@ -654,6 +663,56 @@ test "general client/server API coverage" { |
| 654 | // connection has been closed | 663 | // connection has been closed |
| 655 | try expect(client.connection_pool.free_len == 0); | 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 | { // relative redirect | 716 | { // relative redirect |
| 658 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port}); | 717 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port}); |
| 659 | defer gpa.free(location); | 718 | defer gpa.free(location); |