diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2024-03-02 14:34:43 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-02 14:34:43 -0800 |
| commit | 671c2acf47d17551266f3760698181c83cd96090 (patch) | |
| tree | 1ed92c6eccf5916e905ba956b137e3fa30c4987d /lib/std | |
| parent | 9d500bda2d09fe67c39ee98067c1e53c58adbd5e (diff) | |
| parent | e62b0773cca027e720db1b18ce3905b7fe24ca96 (diff) | |
| download | zig-671c2acf47d17551266f3760698181c83cd96090.tar.gz zig-671c2acf47d17551266f3760698181c83cd96090.zip | |
Merge pull request #19094 from truemedian/std-http-fields
std.http: fix http field parsing
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/http/Client.zig | 102 | ||||
| -rw-r--r-- | lib/std/http/HeaderIterator.zig | 55 | ||||
| -rw-r--r-- | lib/std/http/Server.zig | 88 | ||||
| -rw-r--r-- | lib/std/http/test.zig | 4 |
4 files changed, 230 insertions, 19 deletions
diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 7eade117d0..10088e7627 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -489,9 +489,9 @@ pub const Response = struct { else => {}, } - var line_it = mem.splitSequence(u8, line, ": "); + var line_it = mem.splitScalar(u8, line, ':'); const header_name = line_it.next().?; - const header_value = line_it.rest(); + const header_value = mem.trim(u8, line_it.rest(), " \t"); if (header_name.len == 0) return error.HttpHeadersInvalid; if (std.ascii.eqlIgnoreCase(header_name, "connection")) { @@ -551,6 +551,43 @@ pub const Response = struct { return error.HttpHeadersInvalid; // missing empty line } + test parse { + const response_bytes = "HTTP/1.1 200 OK\r\n" ++ + "LOcation:url\r\n" ++ + "content-tYpe: text/plain\r\n" ++ + "content-disposition:attachment; filename=example.txt \r\n" ++ + "content-Length:10\r\n" ++ + "TRansfer-encoding:\tdeflate, chunked \r\n" ++ + "connectioN:\t keep-alive \r\n\r\n"; + + var header_buffer: [1024]u8 = undefined; + var res = Response{ + .status = undefined, + .reason = undefined, + .version = undefined, + .keep_alive = false, + .parser = proto.HeadersParser.init(&header_buffer), + }; + + @memcpy(header_buffer[0..response_bytes.len], response_bytes); + res.parser.header_bytes_len = response_bytes.len; + + try res.parse(response_bytes); + + try testing.expectEqual(.@"HTTP/1.1", res.version); + try testing.expectEqualStrings("OK", res.reason); + try testing.expectEqual(.ok, res.status); + + try testing.expectEqualStrings("url", res.location.?); + try testing.expectEqualStrings("text/plain", res.content_type.?); + try testing.expectEqualStrings("attachment; filename=example.txt", res.content_disposition.?); + + try testing.expectEqual(true, res.keep_alive); + try testing.expectEqual(10, res.content_length.?); + try testing.expectEqual(.chunked, res.transfer_encoding); + try testing.expectEqual(.deflate, res.transfer_compression); + } + inline fn int64(array: *const [8]u8) u64 { return @bitCast(array.*); } @@ -575,6 +612,67 @@ pub const Response = struct { pub fn iterateHeaders(r: Response) http.HeaderIterator { return http.HeaderIterator.init(r.parser.get()); } + + test iterateHeaders { + const response_bytes = "HTTP/1.1 200 OK\r\n" ++ + "LOcation:url\r\n" ++ + "content-tYpe: text/plain\r\n" ++ + "content-disposition:attachment; filename=example.txt \r\n" ++ + "content-Length:10\r\n" ++ + "TRansfer-encoding:\tdeflate, chunked \r\n" ++ + "connectioN:\t keep-alive \r\n\r\n"; + + var header_buffer: [1024]u8 = undefined; + var res = Response{ + .status = undefined, + .reason = undefined, + .version = undefined, + .keep_alive = false, + .parser = proto.HeadersParser.init(&header_buffer), + }; + + @memcpy(header_buffer[0..response_bytes.len], response_bytes); + res.parser.header_bytes_len = response_bytes.len; + + var it = res.iterateHeaders(); + { + const header = it.next().?; + try testing.expectEqualStrings("LOcation", header.name); + try testing.expectEqualStrings("url", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("content-tYpe", header.name); + try testing.expectEqualStrings("text/plain", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("content-disposition", header.name); + try testing.expectEqualStrings("attachment; filename=example.txt", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("content-Length", header.name); + try testing.expectEqualStrings("10", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("TRansfer-encoding", header.name); + try testing.expectEqualStrings("deflate, chunked", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("connectioN", header.name); + try testing.expectEqualStrings("keep-alive", header.value); + try testing.expect(!it.is_trailer); + } + try testing.expectEqual(null, it.next()); + } }; /// A HTTP request that has been sent. diff --git a/lib/std/http/HeaderIterator.zig b/lib/std/http/HeaderIterator.zig index 515058859d..518950a9ab 100644 --- a/lib/std/http/HeaderIterator.zig +++ b/lib/std/http/HeaderIterator.zig @@ -12,30 +12,43 @@ pub fn init(bytes: []const u8) HeaderIterator { pub fn next(it: *HeaderIterator) ?std.http.Header { const end = std.mem.indexOfPosLinear(u8, it.bytes, it.index, "\r\n").?; - var kv_it = std.mem.splitSequence(u8, it.bytes[it.index..end], ": "); - const name = kv_it.next().?; - const value = kv_it.rest(); - if (name.len == 0 and value.len == 0) { + if (it.index == end) { // found the trailer boundary (\r\n\r\n) if (it.is_trailer) return null; + const next_end = std.mem.indexOfPosLinear(u8, it.bytes, end + 2, "\r\n") orelse return null; + + var kv_it = std.mem.splitScalar(u8, it.bytes[end + 2 .. next_end], ':'); + const name = kv_it.first(); + const value = kv_it.rest(); + it.is_trailer = true; it.index = next_end + 2; - kv_it = std.mem.splitSequence(u8, it.bytes[end + 2 .. next_end], ": "); + if (name.len == 0) + return null; + + return .{ + .name = name, + .value = std.mem.trim(u8, value, " \t"), + }; + } else { // normal header + var kv_it = std.mem.splitScalar(u8, it.bytes[it.index..end], ':'); + const name = kv_it.first(); + const value = kv_it.rest(); + + it.index = end + 2; + if (name.len == 0) + return null; + return .{ - .name = kv_it.next().?, - .value = kv_it.rest(), + .name = name, + .value = std.mem.trim(u8, value, " \t"), }; } - it.index = end + 2; - return .{ - .name = name, - .value = value, - }; } test next { - var it = HeaderIterator.init("200 OK\r\na: b\r\nc: \r\nd: e\r\n\r\nf: g\r\n\r\n"); + var it = HeaderIterator.init("200 OK\r\na: b\r\nc: \r\nd:e\r\n\r\nf: g\r\n\r\n"); try std.testing.expect(!it.is_trailer); { const header = it.next().?; @@ -62,7 +75,23 @@ test next { try std.testing.expectEqualStrings("g", header.value); } try std.testing.expectEqual(null, it.next()); + + it = HeaderIterator.init("200 OK\r\n: ss\r\n\r\n"); + try std.testing.expect(!it.is_trailer); + try std.testing.expectEqual(null, it.next()); + + it = HeaderIterator.init("200 OK\r\na:b\r\n\r\n: ss\r\n\r\n"); + try std.testing.expect(!it.is_trailer); + { + const header = it.next().?; + try std.testing.expect(!it.is_trailer); + try std.testing.expectEqualStrings("a", header.name); + try std.testing.expectEqualStrings("b", header.value); + } + try std.testing.expectEqual(null, it.next()); + try std.testing.expect(it.is_trailer); } const HeaderIterator = @This(); const std = @import("../std.zig"); +const assert = std.debug.assert; diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig index bfc97157f6..5290241b6e 100644 --- a/lib/std/http/Server.zig +++ b/lib/std/http/Server.zig @@ -211,9 +211,9 @@ pub const Request = struct { else => {}, } - var line_it = mem.splitSequence(u8, line, ": "); + var line_it = mem.splitScalar(u8, line, ':'); const header_name = line_it.next().?; - const header_value = line_it.rest(); + const header_value = mem.trim(u8, line_it.rest(), " \t"); if (header_name.len == 0) return error.HttpHeadersInvalid; if (std.ascii.eqlIgnoreCase(header_name, "connection")) { @@ -271,6 +271,29 @@ pub const Request = struct { return error.MissingFinalNewline; } + test parse { + const request_bytes = "GET /hi HTTP/1.0\r\n" ++ + "content-tYpe: text/plain\r\n" ++ + "content-Length:10\r\n" ++ + "expeCt: 100-continue \r\n" ++ + "TRansfer-encoding:\tdeflate, chunked \r\n" ++ + "connectioN:\t keep-alive \r\n\r\n"; + + const req = try parse(request_bytes); + + try testing.expectEqual(.GET, req.method); + try testing.expectEqual(.@"HTTP/1.0", req.version); + try testing.expectEqualStrings("/hi", req.target); + + try testing.expectEqualStrings("text/plain", req.content_type.?); + try testing.expectEqualStrings("100-continue", req.expect.?); + + try testing.expectEqual(true, req.keep_alive); + try testing.expectEqual(10, req.content_length.?); + try testing.expectEqual(.chunked, req.transfer_encoding); + try testing.expectEqual(.deflate, req.transfer_compression); + } + inline fn int64(array: *const [8]u8) u64 { return @bitCast(array.*); } @@ -280,6 +303,66 @@ pub const Request = struct { return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]); } + test iterateHeaders { + const request_bytes = "GET /hi HTTP/1.0\r\n" ++ + "content-tYpe: text/plain\r\n" ++ + "content-Length:10\r\n" ++ + "expeCt: 100-continue \r\n" ++ + "TRansfer-encoding:\tdeflate, chunked \r\n" ++ + "connectioN:\t keep-alive \r\n\r\n"; + + var read_buffer: [500]u8 = undefined; + @memcpy(read_buffer[0..request_bytes.len], request_bytes); + + var server: Server = .{ + .connection = undefined, + .state = .ready, + .read_buffer = &read_buffer, + .read_buffer_len = request_bytes.len, + .next_request_start = 0, + }; + + var request: Request = .{ + .server = &server, + .head_end = request_bytes.len, + .head = undefined, + .reader_state = undefined, + }; + + var it = request.iterateHeaders(); + { + const header = it.next().?; + try testing.expectEqualStrings("content-tYpe", header.name); + try testing.expectEqualStrings("text/plain", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("content-Length", header.name); + try testing.expectEqualStrings("10", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("expeCt", header.name); + try testing.expectEqualStrings("100-continue", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("TRansfer-encoding", header.name); + try testing.expectEqualStrings("deflate, chunked", header.value); + try testing.expect(!it.is_trailer); + } + { + const header = it.next().?; + try testing.expectEqualStrings("connectioN", header.name); + try testing.expectEqualStrings("keep-alive", header.value); + try testing.expect(!it.is_trailer); + } + try testing.expectEqual(null, it.next()); + } + pub const RespondOptions = struct { version: http.Version = .@"HTTP/1.1", status: http.Status = .ok, @@ -1060,5 +1143,6 @@ const mem = std.mem; const net = std.net; const Uri = std.Uri; const assert = std.debug.assert; +const testing = std.testing; const Server = @This(); diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig index 15e7542d19..e2aa810d58 100644 --- a/lib/std/http/test.zig +++ b/lib/std/http/test.zig @@ -328,8 +328,8 @@ test "receiving arbitrary http headers from the client" { defer test_server.destroy(); const request_bytes = "GET /bar HTTP/1.1\r\n" ++ - "CoNneCtIoN: close\r\n" ++ - "aoeu: asdf\r\n" ++ + "CoNneCtIoN:close\r\n" ++ + "aoeu: asdf \r\n" ++ "\r\n"; const gpa = std.testing.allocator; const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port()); |
