authorgravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2024-02-25 12:01:21+01:00
committergravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2024-02-25 12:07:13+01:00
loga07218cc431701d13f169f4896a440d87a5a47c1
tree8a70a10a34fca753a8b2a223a62c608f0b660b66
parent9727931fda50ae412c47bfd40ad1d1dcd06aada0

http: handle header fields with empty value


4 files changed, 92 insertions(+), 19 deletions(-)

lib/std/http/Client.zig+4-2
......@@ -488,7 +488,7 @@ pub const Response = struct {
488488 var line_it = mem.splitSequence(u8, line, ": ");
489489 const header_name = line_it.next().?;
490490 const header_value = line_it.rest();
491 if (header_value.len == 0) return error.HttpHeadersInvalid;
491 if (header_name.len == 0) return error.HttpHeadersInvalid;
492492
493493 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
494494 res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
......@@ -774,7 +774,7 @@ pub const Request = struct {
774774 }
775775
776776 for (req.extra_headers) |header| {
777 assert(header.value.len != 0);
777 assert(header.name.len != 0);
778778
779779 try w.writeAll(header.name);
780780 try w.writeAll(": ");
......@@ -1515,11 +1515,13 @@ pub fn open(
15151515) RequestError!Request {
15161516 if (std.debug.runtime_safety) {
15171517 for (options.extra_headers) |header| {
1518 assert(header.name.len != 0);
15181519 assert(std.mem.indexOfScalar(u8, header.name, ':') == null);
15191520 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
15201521 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
15211522 }
15221523 for (options.privileged_headers) |header| {
1524 assert(header.name.len != 0);
15231525 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
15241526 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
15251527 }
lib/std/http/HeaderIterator.zig+11-5
......@@ -15,7 +15,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header {
1515 var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": ");
1616 const name = kv_it.next().?;
1717 const value = kv_it.rest();
18 if (value.len == 0) {
18 if (name.len == 0 and value.len == 0) {
1919 if (it.is_trailer) return null;
2020 const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse
2121 return null;
......@@ -35,7 +35,7 @@ pub fn next(it: *HeaderIterator) ?std.http.Header {
3535}
3636
3737test 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");
3939 try std.testing.expect(!it.is_trailer);
4040 {
4141 const header = it.next().?;
......@@ -47,13 +47,19 @@ test next {
4747 const header = it.next().?;
4848 try std.testing.expect(!it.is_trailer);
4949 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);
5157 }
5258 {
5359 const header = it.next().?;
5460 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);
5763 }
5864 try std.testing.expectEqual(null, it.next());
5965}
lib/std/http/Server.zig+17-11
......@@ -211,7 +211,7 @@ pub const Request = struct {
211211 var line_it = mem.splitSequence(u8, line, ": ");
212212 const header_name = line_it.next().?;
213213 const header_value = line_it.rest();
214 if (header_value.len == 0) return error.HttpHeadersInvalid;
214 if (header_name.len == 0) return error.HttpHeadersInvalid;
215215
216216 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
217217 head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
......@@ -311,6 +311,7 @@ pub const Request = struct {
311311 assert(options.extra_headers.len <= max_extra_headers);
312312 if (std.debug.runtime_safety) {
313313 for (options.extra_headers) |header| {
314 assert(header.name.len != 0);
314315 assert(std.mem.indexOfScalar(u8, header.name, ':') == null);
315316 assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
316317 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
......@@ -370,11 +371,13 @@ pub const Request = struct {
370371 };
371372 iovecs_len += 1;
372373
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 }
378381
379382 iovecs[iovecs_len] = .{
380383 .iov_base = "\r\n",
......@@ -496,6 +499,7 @@ pub const Request = struct {
496499 }
497500
498501 for (o.extra_headers) |header| {
502 assert(header.name.len != 0);
499503 h.appendSliceAssumeCapacity(header.name);
500504 h.appendSliceAssumeCapacity(": ");
501505 h.appendSliceAssumeCapacity(header.value);
......@@ -986,11 +990,13 @@ pub const Response = struct {
986990 };
987991 iovecs_len += 1;
988992
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 }
9941000
9951001 iovecs[iovecs_len] = .{
9961002 .iov_base = "\r\n",
lib/std/http/test.zig+60-1
......@@ -479,6 +479,12 @@ test "general client/server API coverage" {
479479 .{ .name = "location", .value = location },
480480 },
481481 });
482 } else if (mem.eql(u8, request.head.target, "/empty")) {
483 try request.respond("", .{
484 .extra_headers = &.{
485 .{ .name = "empty", .value = "" },
486 },
487 });
482488 } else {
483489 try request.respond("", .{ .status = .not_found });
484490 }
......@@ -491,7 +497,10 @@ test "general client/server API coverage" {
491497 return s.listen_address.in.getPort();
492498 }
493499 });
494 defer test_server.destroy();
500 defer {
501 global.handle_new_requests = false;
502 test_server.destroy();
503 }
495504
496505 const log = std.log.scoped(.client);
497506
......@@ -654,6 +663,56 @@ test "general client/server API coverage" {
654663 // connection has been closed
655664 try expect(client.connection_pool.free_len == 0);
656665
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
657716 { // relative redirect
658717 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
659718 defer gpa.free(location);