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 {...@@ -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
...@@ -490,6 +490,12 @@ test "general client/server API coverage" {...@@ -490,6 +490,12 @@ test "general client/server API coverage" {
490 .{ .name = "location", .value = location },490 .{ .name = "location", .value = location },
491 },491 },
492 });492 });
493 } else if (mem.eql(u8, request.head.target, "/empty")) {
494 try request.respond("", .{
495 .extra_headers = &.{
496 .{ .name = "empty", .value = "" },
497 },
498 });
493 } else {499 } else {
494 try request.respond("", .{ .status = .not_found });500 try request.respond("", .{ .status = .not_found });
495 }501 }
...@@ -502,7 +508,10 @@ test "general client/server API coverage" {...@@ -502,7 +508,10 @@ test "general client/server API coverage" {
502 return s.listen_address.in.getPort();508 return s.listen_address.in.getPort();
503 }509 }
504 });510 });
505 defer test_server.destroy();511 defer {
512 global.handle_new_requests = false;
513 test_server.destroy();
514 }
506515
507 const log = std.log.scoped(.client);516 const log = std.log.scoped(.client);
508517
...@@ -665,6 +674,56 @@ test "general client/server API coverage" {...@@ -665,6 +674,56 @@ test "general client/server API coverage" {
665 // connection has been closed674 // connection has been closed
666 try expect(client.connection_pool.free_len == 0);675 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
668 { // relative redirect727 { // relative redirect
669 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});728 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/redirect/1", .{port});
670 defer gpa.free(location);729 defer gpa.free(location);
lib/std/mem.zig+21
...@@ -1346,6 +1346,7 @@ pub fn lastIndexOfLinear(comptime T: type, haystack: []const T, needle: []const...@@ -1346,6 +1346,7 @@ pub fn lastIndexOfLinear(comptime T: type, haystack: []const T, needle: []const
1346/// Consider using `indexOfPos` instead of this, which will automatically use a1346/// Consider using `indexOfPos` instead of this, which will automatically use a
1347/// more sophisticated algorithm on larger inputs.1347/// more sophisticated algorithm on larger inputs.
1348pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize {1348pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize {
1349 if (needle.len > haystack.len) return null;
1349 var i: usize = start_index;1350 var i: usize = start_index;
1350 const end = haystack.len - needle.len;1351 const end = haystack.len - needle.len;
1351 while (i <= end) : (i += 1) {1352 while (i <= end) : (i += 1) {
...@@ -1354,6 +1355,26 @@ pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usiz...@@ -1354,6 +1355,26 @@ pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usiz
1354 return null;1355 return null;
1355}1356}
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
1357fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void {1378fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void {
1358 for (table) |*c| {1379 for (table) |*c| {
1359 c.* = pattern.len;1380 c.* = pattern.len;