std.http.Client: fix handling of \r\n before next chunk size

This commit is contained in:
Andrew Kelley 2023-01-06 17:53:06 -07:00
parent 24b4e643f4
commit 3806091a10

View File

@ -1,7 +1,3 @@
//! This API is a barely-touched, barely-functional http client, just the
//! absolute minimum thing I needed in order to test `std.crypto.tls`. Bear
//! with me and I promise the API will become useful and streamlined.
//!
//! TODO: send connection: keep-alive and LRU cache a configurable number of
//! open connections to skip DNS and TLS handshake for subsequent requests.
@ -178,6 +174,8 @@ pub const Request = struct {
seen_rnr,
finished,
/// Begin transfer-encoding: chunked parsing states.
chunk_size_prefix_r,
chunk_size_prefix_n,
chunk_size,
chunk_r,
chunk_data,
@ -382,6 +380,8 @@ pub const Request = struct {
continue :state;
},
},
.chunk_size_prefix_r => unreachable,
.chunk_size_prefix_n => unreachable,
.chunk_size => unreachable,
.chunk_r => unreachable,
.chunk_data => unreachable,
@ -449,18 +449,6 @@ pub const Request = struct {
try expectEqual(@as(u10, 999), parseInt3("999".*));
}
inline fn int16(array: *const [2]u8) u16 {
return @bitCast(u16, array.*);
}
inline fn int32(array: *const [4]u8) u32 {
return @bitCast(u32, array.*);
}
inline fn int64(array: *const [8]u8) u64 {
return @bitCast(u64, array.*);
}
test "find headers end basic" {
var buffer: [1]u8 = undefined;
var r = Response.initStatic(&buffer);
@ -480,6 +468,29 @@ pub const Request = struct {
"\r\ncontent";
try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example));
}
test "find headers end bug" {
var buffer: [1]u8 = undefined;
var r = Response.initStatic(&buffer);
const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const example =
"HTTP/1.1 200 OK\r\n" ++
"Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++
"content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++
"Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++
"Content-Type: application/x-gzip\r\n" ++
"ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++
"Strict-Transport-Security: max-age=31536000\r\n" ++
"Vary: Authorization,Accept-Encoding,Origin\r\n" ++
"X-Content-Type-Options: nosniff\r\n" ++
"X-Frame-Options: deny\r\n" ++
"X-XSS-Protection: 1; mode=block\r\n" ++
"Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++
"Transfer-Encoding: chunked\r\n" ++
"X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++
"connection: close\r\n\r\n" ++ trail;
try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example));
}
};
pub const Headers = struct {
@ -536,8 +547,7 @@ pub const Request = struct {
/// This one can return 0 without meaning EOF.
/// TODO change to readvAdvanced
pub fn readAdvanced(req: *Request, buffer: []u8) !usize {
const amt = try req.connection.read(buffer);
var in = buffer[0..amt];
var in = buffer[0..try req.connection.read(buffer)];
var out_index: usize = 0;
while (true) {
switch (req.response.state) {
@ -571,7 +581,8 @@ pub const Request = struct {
req.deinit();
req.* = new_req;
assert(out_index == 0);
return readAdvanced(req, buffer);
in = buffer[0..try req.connection.read(buffer)];
continue;
}
if (req.response.headers.transfer_encoding) |transfer_encoding| {
@ -598,8 +609,50 @@ pub const Request = struct {
return 0;
},
.finished => {
mem.copy(u8, buffer[out_index..], in);
return out_index + in.len;
if (in.ptr == buffer.ptr) {
return in.len;
} else {
mem.copy(u8, buffer[out_index..], in);
return out_index + in.len;
}
},
.chunk_size_prefix_r => switch (in.len) {
0 => return out_index,
1 => switch (in[0]) {
'\r' => {
req.response.state = .chunk_size_prefix_n;
return out_index;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
else => switch (int16(in[0..2])) {
int16("\r\n") => {
in = in[2..];
req.response.state = .chunk_size;
continue;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
},
.chunk_size_prefix_n => switch (in.len) {
0 => return out_index,
else => switch (in[0]) {
'\n' => {
in = in[1..];
req.response.state = .chunk_size;
continue;
},
else => {
req.response.state = .invalid;
return error.HttpHeadersInvalid;
},
},
},
.chunk_size, .chunk_r => {
const i = req.response.findChunkedLen(in);
@ -619,20 +672,38 @@ pub const Request = struct {
},
.chunk_data => {
const sub_amt = @min(req.response.next_chunk_length, in.len);
req.response.next_chunk_length -= sub_amt;
if (req.response.next_chunk_length > 0) {
if (in.ptr == buffer.ptr) {
return sub_amt;
} else {
mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
out_index += sub_amt;
return out_index;
}
}
mem.copy(u8, buffer[out_index..], in[0..sub_amt]);
out_index += sub_amt;
req.response.next_chunk_length -= sub_amt;
if (req.response.next_chunk_length == 0) {
req.response.state = .chunk_size;
in = in[sub_amt..];
continue;
}
return out_index;
req.response.state = .chunk_size_prefix_r;
in = in[sub_amt..];
continue;
},
}
}
}
inline fn int16(array: *const [2]u8) u16 {
return @bitCast(u16, array.*);
}
inline fn int32(array: *const [4]u8) u32 {
return @bitCast(u32, array.*);
}
inline fn int64(array: *const [8]u8) u64 {
return @bitCast(u64, array.*);
}
test {
_ = Response;
}