| author | |
| committer | |
| log | aa39e98d9024ab68246ae45b2d67c7eda46f28ab |
| tree | dfb15ea6fa26c5559c4624a2d7002657cc5efd8f |
| parent | b2374c4d75d16ac0eb7b7b5e53411a80e9f4413d |
| parent | a07218cc431701d13f169f4896a440d87a5a47c1 |
| signature |
http: check for empty header name instead of value5 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; |
| 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| ... | @@ -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 | } | ||
| 506 | 515 | ||
| 507 | const log = std.log.scoped(.client); | 516 | const log = std.log.scoped(.client); |
| 508 | 517 | ||
| ... | @@ -665,6 +674,56 @@ test "general client/server API coverage" { | ... | @@ -665,6 +674,56 @@ test "general client/server API coverage" { |
| 665 | // connection has been closed | 674 | // connection has been closed |
| 666 | try expect(client.connection_pool.free_len == 0); | 675 | try expect(client.connection_pool.free_len == 0); |
| 667 | 676 | ||
| 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 redirect | 727 | { // 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 a | 1346 | /// 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. |
| 1348 | pub fn indexOfPosLinear(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize { | 1348 | pub 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 | } |
| 1356 | 1357 | ||
| 1358 | test 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 | |||
| 1357 | fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void { | 1378 | fn boyerMooreHorspoolPreprocessReverse(pattern: []const u8, table: *[256]usize) void { |
| 1358 | for (table) |*c| { | 1379 | for (table) |*c| { |
| 1359 | c.* = pattern.len; | 1380 | c.* = pattern.len; |