authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-25 15:27:12-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-25 15:27:12-08:00
logaa39e98d9024ab68246ae45b2d67c7eda46f28ab
treedfb15ea6fa26c5559c4624a2d7002657cc5efd8f
parentb2374c4d75d16ac0eb7b7b5e53411a80e9f4413d
parenta07218cc431701d13f169f4896a440d87a5a47c1
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19077 from Techatrix/http-header-parse

http: check for empty header name instead of value

5 files changed, 113 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
......@@ -490,6 +490,12 @@ test "general client/server API coverage" {
490490 .{ .name = "location", .value = location },
491491 },
492492 });
493 } else if (mem.eql(u8, request.head.target, "/empty")) {
494 try request.respond("", .{
495 .extra_headers = &.{
496 .{ .name = "empty", .value = "" },
497 },
498 });
493499 } else {
494500 try request.respond("", .{ .status = .not_found });
495501 }
......@@ -502,7 +508,10 @@ test "general client/server API coverage" {
502508 return s.listen_address.in.getPort();
503509 }
504510 });
505 defer test_server.destroy();
511 defer {
512 global.handle_new_requests = false;
513 test_server.destroy();
514 }
506515
507516 const log = std.log.scoped(.client);
508517
......@@ -665,6 +674,56 @@ test "general client/server API coverage" {
665674 // connection has been closed
666675 try expect(client.connection_pool.free_len == 0);
667676
677 { // handle empty header field value
678 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/empty", .{port});
679 defer gpa.free(location);
680 const uri = try std.Uri.parse(location);
681
682 log.info("{s}", .{location});
683 var server_header_buffer: [1024]u8 = undefined;
684 var req = try client.open(.GET, uri, .{
685 .server_header_buffer = &server_header_buffer,
686 .extra_headers = &.{
687 .{ .name = "empty", .value = "" },
688 },
689 });
690 defer req.deinit();
691
692 try req.send(.{});
693 try req.wait();
694
695 try std.testing.expectEqual(.ok, req.response.status);
696
697 const body = try req.reader().readAllAlloc(gpa, 8192);
698 defer gpa.free(body);
699
700 try expectEqualStrings("", body);
701
702 var it = req.response.iterateHeaders();
703 {
704 const header = it.next().?;
705 try expect(!it.is_trailer);
706 try expectEqualStrings("connection", header.name);
707 try expectEqualStrings("keep-alive", header.value);
708 }
709 {
710 const header = it.next().?;
711 try expect(!it.is_trailer);
712 try expectEqualStrings("content-length", header.name);
713 try expectEqualStrings("0", header.value);
714 }
715 {
716 const header = it.next().?;
717 try expect(!it.is_trailer);
718 try expectEqualStrings("empty", header.name);
719 try expectEqualStrings("", header.value);
720 }
721 try expectEqual(null, it.next());
722 }
723
724 // connection has been kept alive
725 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
726
668727 { // relative redirect
669728 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
670729 defer gpa.free(location);
lib/std/mem.zig+21
......@@ -1346,6 +1346,7 @@ pub fn lastIndexOfLinear(comptime T: type, haystack: []const T, needle: []const
13461346/// Consider using `indexOfPos` instead of this, which will automatically use a
13471347/// more sophisticated algorithm on larger inputs.
13481348pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize {
1349 if (needle.len > haystack.len) return null;
13491350 var i: usize = start_index;
13501351 const end = haystack.len - needle.len;
13511352 while (i <= end) : (i += 1) {
......@@ -1354,6 +1355,26 @@ pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usiz
13541355 return null;
13551356}
13561357
1358test indexOfPosLinear {
1359 try testing.expectEqual(0, indexOfPosLinear(u8, "", 0, ""));
1360 try testing.expectEqual(0, indexOfPosLinear(u8, "123", 0, ""));
1361
1362 try testing.expectEqual(null, indexOfPosLinear(u8, "", 0, "1"));
1363 try testing.expectEqual(0, indexOfPosLinear(u8, "1", 0, "1"));
1364 try testing.expectEqual(null, indexOfPosLinear(u8, "2", 0, "1"));
1365 try testing.expectEqual(1, indexOfPosLinear(u8, "21", 0, "1"));
1366 try testing.expectEqual(null, indexOfPosLinear(u8, "222", 0, "1"));
1367
1368 try testing.expectEqual(null, indexOfPosLinear(u8, "", 0, "12"));
1369 try testing.expectEqual(null, indexOfPosLinear(u8, "1", 0, "12"));
1370 try testing.expectEqual(null, indexOfPosLinear(u8, "2", 0, "12"));
1371 try testing.expectEqual(0, indexOfPosLinear(u8, "12", 0, "12"));
1372 try testing.expectEqual(null, indexOfPosLinear(u8, "21", 0, "12"));
1373 try testing.expectEqual(1, indexOfPosLinear(u8, "212", 0, "12"));
1374 try testing.expectEqual(0, indexOfPosLinear(u8, "122", 0, "12"));
1375 try testing.expectEqual(1, indexOfPosLinear(u8, "212112", 0, "12"));
1376}
1377
13571378fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void {
13581379 for (table) |*c| {
13591380 c.* = pattern.len;