authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-16 16:26:25-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-17 19:16:01-05:00
log85221b4e977756bf30f7d45a7eb8636ea0d5168a
treef69fa0d5707108924e1725187c8bde843a5b6ab0
parent134294230a08d531afd0a6d823ae3046b1699b0f
signaturelock-open Commit is signed but in an unrecognized format.

std.http: curate some Server errors, fix reading chunked bodies


3 files changed, 140 insertions(+), 83 deletions(-)

lib/std/http/Client.zig+38-28
......@@ -193,7 +193,13 @@ pub const Connection = struct {
193193 };
194194 }
195195
196 pub const ReadError = error{ TlsFailure, TlsAlert, ConnectionTimedOut, ConnectionResetByPeer, UnexpectedReadFailure };
196 pub const ReadError = error{
197 TlsFailure,
198 TlsAlert,
199 ConnectionTimedOut,
200 ConnectionResetByPeer,
201 UnexpectedReadFailure,
202 };
197203
198204 pub const Reader = std.io.Reader(*Connection, ReadError, read);
199205
......@@ -518,7 +524,10 @@ pub const Request = struct {
518524 req.* = undefined;
519525 }
520526
521 pub fn start(req: *Request, uri: Uri) !void {
527 pub const StartError = BufferedConnection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
528
529 /// Send the request to the server.
530 pub fn start(req: *Request, uri: Uri) StartError!void {
522531 var buffered = std.io.bufferedWriter(req.connection.data.buffered.writer());
523532 const w = buffered.writer();
524533
......@@ -575,7 +584,7 @@ pub const Request = struct {
575584 }
576585 } else {
577586 if (has_content_length) {
578 const content_length = try std.fmt.parseInt(u64, req.headers.getFirstValue("content-length").?, 10);
587 const content_length = std.fmt.parseInt(u64, req.headers.getFirstValue("content-length").?, 10) catch return error.InvalidContentLength;
579588
580589 req.transfer_encoding = .{ .content_length = content_length };
581590 } else if (has_transfer_encoding) {
......@@ -618,7 +627,7 @@ pub const Request = struct {
618627 return index;
619628 }
620629
621 pub const DoError = RequestError || TransferReadError || proto.HeadersParser.CheckCompleteHeadError || Response.ParseError || Uri.ParseError || error{ TooManyHttpRedirects, HttpRedirectMissingLocation, CompressionInitializationFailed };
630 pub const DoError = RequestError || TransferReadError || proto.HeadersParser.CheckCompleteHeadError || Response.ParseError || Uri.ParseError || error{ TooManyHttpRedirects, HttpRedirectMissingLocation, CompressionInitializationFailed, CompressionNotSupported };
622631
623632 /// Waits for a response from the server and parses any headers that are sent.
624633 /// This function will block until the final response is received.
......@@ -739,25 +748,23 @@ pub const Request = struct {
739748
740749 /// Reads data from the response body. Must be called after `do`.
741750 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
742 while (true) {
743 const out_index = switch (req.response.compression) {
744 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
745 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
746 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
747 else => try req.transferRead(buffer),
748 };
749
750 if (out_index == 0) {
751 while (!req.response.parser.state.isContent()) { // read trailing headers
752 try req.connection.data.buffered.fill();
753
754 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
755 req.connection.data.buffered.clear(@intCast(u16, nchecked));
756 }
757 }
751 const out_index = switch (req.response.compression) {
752 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
753 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
754 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
755 else => try req.transferRead(buffer),
756 };
757
758 if (out_index == 0) {
759 while (!req.response.parser.state.isContent()) { // read trailing headers
760 try req.connection.data.buffered.fill();
758761
759 return out_index;
762 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
763 req.connection.data.buffered.clear(@intCast(u16, nchecked));
764 }
760765 }
766
767 return out_index;
761768 }
762769
763770 /// Reads data from the response body. Must be called after `do`.
......@@ -800,15 +807,19 @@ pub const Request = struct {
800807 }
801808 }
802809
810 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
811 var index: usize = 0;
812 while (index < bytes.len) {
813 index += try write(req, bytes[index..]);
814 }
815 }
816
803817 pub const FinishError = WriteError || error{MessageNotCompleted};
804818
805819 /// Finish the body of a request. This notifies the server that you have no more data to send.
806820 pub fn finish(req: *Request) FinishError!void {
807821 switch (req.transfer_encoding) {
808 .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| {
809 req.client.last_error = .{ .write = err };
810 return error.WriteFailed;
811 },
822 .chunked => try req.connection.data.conn.writeAll("0\r\n\r\n"),
812823 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
813824 .none => {},
814825 }
......@@ -923,7 +934,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
923934 }
924935}
925936
926pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || std.fmt.ParseIntError || BufferedConnection.WriteError || error{
937pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || BufferedConnection.WriteError || error{
927938 UnsupportedUrlScheme,
928939 UriMissingHost,
929940
......@@ -998,6 +1009,7 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option
9981009 .handle_redirects = options.handle_redirects,
9991010 .response = .{
10001011 .status = undefined,
1012 .reason = undefined,
10011013 .version = undefined,
10021014 .headers = undefined,
10031015 .parser = switch (options.header_strategy) {
......@@ -1011,8 +1023,6 @@ pub fn request(client: *Client, uri: Uri, headers: http.Headers, options: Option
10111023
10121024 req.arena = std.heap.ArenaAllocator.init(client.allocator);
10131025
1014 try req.start(uri);
1015
10161026 return req;
10171027}
10181028
lib/std/http/Server.zig+100-55
......@@ -23,21 +23,33 @@ pub const Connection = struct {
2323
2424 pub const Protocol = enum { plain };
2525
26 pub fn read(conn: *Connection, buffer: []u8) !usize {
27 switch (conn.protocol) {
28 .plain => return conn.stream.read(buffer),
26 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
27 return switch (conn.protocol) {
28 .plain => conn.stream.read(buffer),
2929 // .tls => return conn.tls_client.read(conn.stream, buffer),
30 }
30 } catch |err| switch (err) {
31 error.ConnectionTimedOut => return error.ConnectionTimedOut,
32 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
33 else => return error.UnexpectedReadFailure,
34 };
3135 }
3236
33 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) !usize {
34 switch (conn.protocol) {
35 .plain => return conn.stream.readAtLeast(buffer, len),
37 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
38 return switch (conn.protocol) {
39 .plain => conn.stream.readAtLeast(buffer, len),
3640 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),
37 }
41 } catch |err| switch (err) {
42 error.ConnectionTimedOut => return error.ConnectionTimedOut,
43 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
44 else => return error.UnexpectedReadFailure,
45 };
3846 }
3947
40 pub const ReadError = net.Stream.ReadError;
48 pub const ReadError = error{
49 ConnectionTimedOut,
50 ConnectionResetByPeer,
51 UnexpectedReadFailure,
52 };
4153
4254 pub const Reader = std.io.Reader(*Connection, ReadError, read);
4355
......@@ -45,21 +57,31 @@ pub const Connection = struct {
4557 return Reader{ .context = conn };
4658 }
4759
48 pub fn writeAll(conn: *Connection, buffer: []const u8) !void {
49 switch (conn.protocol) {
50 .plain => return conn.stream.writeAll(buffer),
60 pub fn writeAll(conn: *Connection, buffer: []const u8) WriteError!void {
61 return switch (conn.protocol) {
62 .plain => conn.stream.writeAll(buffer),
5163 // .tls => return conn.tls_client.writeAll(conn.stream, buffer),
52 }
64 } catch |err| switch (err) {
65 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
66 else => return error.UnexpectedWriteFailure,
67 };
5368 }
5469
55 pub fn write(conn: *Connection, buffer: []const u8) !usize {
56 switch (conn.protocol) {
57 .plain => return conn.stream.write(buffer),
70 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
71 return switch (conn.protocol) {
72 .plain => conn.stream.write(buffer),
5873 // .tls => return conn.tls_client.write(conn.stream, buffer),
59 }
74 } catch |err| switch (err) {
75 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
76 else => return error.UnexpectedWriteFailure,
77 };
6078 }
6179
62 pub const WriteError = net.Stream.WriteError || error{};
80 pub const WriteError = error{
81 ConnectionResetByPeer,
82 UnexpectedWriteFailure,
83 };
84
6385 pub const Writer = std.io.Writer(*Connection, WriteError, write);
6486
6587 pub fn writer(conn: *Connection) Writer {
......@@ -155,6 +177,25 @@ pub const BufferedConnection = struct {
155177 }
156178};
157179
180/// The mode of transport for responses.
181pub const ResponseTransfer = union(enum) {
182 content_length: u64,
183 chunked: void,
184 none: void,
185};
186
187/// The decompressor for request messages.
188pub const Compression = union(enum) {
189 pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Response.TransferReader);
190 pub const GzipDecompressor = std.compress.gzip.Decompress(Response.TransferReader);
191 pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Response.TransferReader, .{});
192
193 deflate: DeflateDecompressor,
194 gzip: GzipDecompressor,
195 zstd: ZstdDecompressor,
196 none: void,
197};
198
158199/// A HTTP request originating from a client.
159200pub const Request = struct {
160201 pub const ParseError = Allocator.Error || error{
......@@ -165,10 +206,11 @@ pub const Request = struct {
165206 HttpHeaderContinuationsUnsupported,
166207 HttpTransferEncodingUnsupported,
167208 HttpConnectionHeaderUnsupported,
168 InvalidCharacter,
209 InvalidContentLength,
210 CompressionNotSupported,
169211 };
170212
171 pub fn parse(req: *Request, bytes: []const u8) !void {
213 pub fn parse(req: *Request, bytes: []const u8) ParseError!void {
172214 var it = mem.tokenize(u8, bytes[0 .. bytes.len - 4], "\r\n");
173215
174216 const first_line = it.next() orelse return error.HttpHeadersInvalid;
......@@ -211,7 +253,7 @@ pub const Request = struct {
211253
212254 if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
213255 if (req.content_length != null) return error.HttpHeadersInvalid;
214 req.content_length = try std.fmt.parseInt(u64, header_value, 10);
256 req.content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength;
215257 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
216258 // Transfer-Encoding: second, first
217259 // Transfer-Encoding: deflate, chunked
......@@ -321,6 +363,8 @@ pub const Response = struct {
321363 }
322364 }
323365
366 pub const DoError = BufferedConnection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
367
324368 /// Send the response headers.
325369 pub fn do(res: *Response) !void {
326370 var buffered = std.io.bufferedWriter(res.connection.writer());
......@@ -356,7 +400,7 @@ pub const Response = struct {
356400 }
357401 } else {
358402 if (has_content_length) {
359 const content_length = try std.fmt.parseInt(u64, res.headers.getFirstValue("content-length").?, 10);
403 const content_length = std.fmt.parseInt(u64, res.headers.getFirstValue("content-length").?, 10) catch return error.InvalidContentLength;
360404
361405 res.transfer_encoding = .{ .content_length = content_length };
362406 } else if (has_transfer_encoding) {
......@@ -386,23 +430,23 @@ pub const Response = struct {
386430 return .{ .context = res };
387431 }
388432
389 pub fn transferRead(res: *Response, buf: []u8) TransferReadError!usize {
390 if (res.request.parser.isComplete()) return 0;
433 fn transferRead(res: *Response, buf: []u8) TransferReadError!usize {
434 if (res.request.parser.done) return 0;
391435
392436 var index: usize = 0;
393437 while (index == 0) {
394438 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
395 if (amt == 0 and res.request.parser.isComplete()) break;
439 if (amt == 0 and res.request.parser.done) break;
396440 index += amt;
397441 }
398442
399443 return index;
400444 }
401445
402 pub const WaitForCompleteHeadError = BufferedConnection.ReadError || proto.HeadersParser.WaitForCompleteHeadError || Request.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported};
446 pub const WaitError = BufferedConnection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
403447
404448 /// Wait for the client to send a complete request head.
405 pub fn wait(res: *Response) !void {
449 pub fn wait(res: *Response) WaitError!void {
406450 while (true) {
407451 try res.connection.fill();
408452
......@@ -445,10 +489,10 @@ pub const Response = struct {
445489 if (res.request.transfer_compression) |tc| switch (tc) {
446490 .compress => return error.CompressionNotSupported,
447491 .deflate => res.request.compression = .{
448 .deflate = try std.compress.zlib.zlibStream(res.server.allocator, res.transferReader()),
492 .deflate = std.compress.zlib.zlibStream(res.server.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
449493 },
450494 .gzip => res.request.compression = .{
451 .gzip = try std.compress.gzip.decompress(res.server.allocator, res.transferReader()),
495 .gzip = std.compress.gzip.decompress(res.server.allocator, res.transferReader()) catch return error.CompressionInitializationFailed,
452496 },
453497 .zstd => res.request.compression = .{
454498 .zstd = std.compress.zstd.decompressStream(res.server.allocator, res.transferReader()),
......@@ -457,7 +501,7 @@ pub const Response = struct {
457501 }
458502 }
459503
460 pub const ReadError = Compression.DeflateDecompressor.Error || Compression.GzipDecompressor.Error || Compression.ZstdDecompressor.Error || WaitForCompleteHeadError;
504 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{DecompressionFailure};
461505
462506 pub const Reader = std.io.Reader(*Response, ReadError, read);
463507
......@@ -466,12 +510,23 @@ pub const Response = struct {
466510 }
467511
468512 pub fn read(res: *Response, buffer: []u8) ReadError!usize {
469 return switch (res.request.compression) {
470 .deflate => |*deflate| try deflate.read(buffer),
471 .gzip => |*gzip| try gzip.read(buffer),
472 .zstd => |*zstd| try zstd.read(buffer),
513 const out_index = switch (res.request.compression) {
514 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
515 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
516 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
473517 else => try res.transferRead(buffer),
474518 };
519
520 if (out_index == 0) {
521 while (!res.request.parser.state.isContent()) { // read trailing headers
522 try res.connection.fill();
523
524 const nchecked = try res.request.parser.checkCompleteHead(res.server.allocator, res.connection.peek());
525 res.connection.clear(@intCast(u16, nchecked));
526 }
527 }
528
529 return out_index;
475530 }
476531
477532 pub fn readAll(res: *Response, buffer: []u8) !usize {
......@@ -513,9 +568,18 @@ pub const Response = struct {
513568 }
514569 }
515570
571 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
572 var index: usize = 0;
573 while (index < bytes.len) {
574 index += try write(req, bytes[index..]);
575 }
576 }
577
578 pub const FinishError = WriteError || error{MessageNotCompleted};
579
516580 /// Finish the body of a request. This notifies the server that you have no more data to send.
517 pub fn finish(res: *Response) !void {
518 switch (res.headers.transfer_encoding) {
581 pub fn finish(res: *Response) FinishError!void {
582 switch (res.transfer_encoding) {
519583 .chunked => try res.connection.writeAll("0\r\n\r\n"),
520584 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
521585 .none => {},
......@@ -523,25 +587,6 @@ pub const Response = struct {
523587 }
524588};
525589
526/// The mode of transport for responses.
527pub const ResponseTransfer = union(enum) {
528 content_length: u64,
529 chunked: void,
530 none: void,
531};
532
533/// The decompressor for request messages.
534pub const Compression = union(enum) {
535 pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Response.TransferReader);
536 pub const GzipDecompressor = std.compress.gzip.Decompress(Response.TransferReader);
537 pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Response.TransferReader, .{});
538
539 deflate: DeflateDecompressor,
540 gzip: GzipDecompressor,
541 zstd: ZstdDecompressor,
542 none: void,
543};
544
545590pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server {
546591 return .{
547592 .allocator = allocator,
src/Package.zig+2
......@@ -485,6 +485,8 @@ fn fetchAndUnpack(
485485 var req = try http_client.request(uri, h, .{ .method = .GET });
486486 defer req.deinit();
487487
488 try req.start();
489
488490 try req.do();
489491
490492 if (mem.endsWith(u8, uri.path, ".tar.gz")) {