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 {...@@ -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;
492492
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 }
775775
776 for (req.extra_headers) |header| {776 for (req.extra_headers) |header| {
777 assert(header.value.len != 0);777 assert(header.name.len != 0);
778778
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") orelse20 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}
3636
37test next {37test 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;
215215
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;
372373
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 }
378381
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 }
497500
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;
988992
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 }
9941000
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 }
495504
496 const log = std.log.scoped(.client);505 const log = std.log.scoped(.client);
497506
...@@ -654,6 +663,56 @@ test "general client/server API coverage" {...@@ -654,6 +663,56 @@ test "general client/server API coverage" {
654 // connection has been closed663 // connection has been closed
655 try expect(client.connection_pool.free_len == 0);664 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
657 { // relative redirect716 { // 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);