authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-12 23:26:40-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-04-17 19:14:48-05:00
log2c492064fbc882fa31256209d201ade1bb20cb92
treec509a9f068a80dc26eb943fd37747693f5b1342d
parent038ed32cffbb40d87d8634470e29df31b7699359
signaturelock-open Commit is signed but in an unrecognized format.

std.http: further curate error set, remove last_error


1 files changed, 77 insertions(+), 114 deletions(-)

lib/std/http/Client.zig+77-114
...@@ -25,9 +25,6 @@ next_https_rescan_certs: bool = true,...@@ -25,9 +25,6 @@ next_https_rescan_certs: bool = true,
25/// The pool of connections that can be reused (and currently in use).25/// The pool of connections that can be reused (and currently in use).
26connection_pool: ConnectionPool = .{},26connection_pool: ConnectionPool = .{},
2727
28/// The last error that occurred on this client. This is not threadsafe, do not expect it to be completely accurate.
29last_error: ?ExtraError = null,
30
31pub const ExtraError = union(enum) {28pub const ExtraError = union(enum) {
32 pub const TcpConnectError = std.net.TcpConnectToHostError;29 pub const TcpConnectError = std.net.TcpConnectToHostError;
33 pub const TlsError = std.crypto.tls.Client.InitError(net.Stream);30 pub const TlsError = std.crypto.tls.Client.InitError(net.Stream);
...@@ -184,31 +181,33 @@ pub const Connection = struct {...@@ -184,31 +181,33 @@ pub const Connection = struct {
184181
185 pub const Protocol = enum { plain, tls };182 pub const Protocol = enum { plain, tls };
186183
187 pub fn read(conn: *Connection, buffer: []u8) !usize {184 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
188 switch (conn.protocol) {185 return switch (conn.protocol) {
189 .plain => return conn.stream.read(buffer),186 .plain => conn.stream.read(buffer),
190 .tls => return conn.tls_client.read(conn.stream, buffer),187 .tls => conn.tls_client.read(conn.stream, buffer),
191 }188 } catch |err| switch (err) {
189 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
190 error.TlsAlert => return error.TlsAlert,
191 error.ConnectionTimedOut => return error.ConnectionTimedOut,
192 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
193 else => return error.UnexpectedReadFailure,
194 };
192 }195 }
193196
194 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) !usize {197 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
195 switch (conn.protocol) {198 return switch (conn.protocol) {
196 .plain => return conn.stream.readAtLeast(buffer, len),199 .plain => conn.stream.readAtLeast(buffer, len),
197 .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),200 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
198 }201 } catch |err| switch (err) {
202 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
203 error.TlsAlert => return error.TlsAlert,
204 error.ConnectionTimedOut => return error.ConnectionTimedOut,
205 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
206 else => return error.UnexpectedReadFailure,
207 };
199 }208 }
200209
201 pub const ReadError = net.Stream.ReadError || error{210 pub const ReadError = error{ TlsFailure, TlsAlert, ConnectionTimedOut, ConnectionResetByPeer, UnexpectedReadFailure };
202 TlsConnectionTruncated,
203 TlsRecordOverflow,
204 TlsDecodeError,
205 TlsAlert,
206 TlsBadRecordMac,
207 Overflow,
208 TlsBadLength,
209 TlsIllegalParameter,
210 TlsUnexpectedMessage,
211 };
212211
213 pub const Reader = std.io.Reader(*Connection, ReadError, read);212 pub const Reader = std.io.Reader(*Connection, ReadError, read);
214213
...@@ -217,20 +216,30 @@ pub const Connection = struct {...@@ -217,20 +216,30 @@ pub const Connection = struct {
217 }216 }
218217
219 pub fn writeAll(conn: *Connection, buffer: []const u8) !void {218 pub fn writeAll(conn: *Connection, buffer: []const u8) !void {
220 switch (conn.protocol) {219 return switch (conn.protocol) {
221 .plain => return conn.stream.writeAll(buffer),220 .plain => conn.stream.writeAll(buffer),
222 .tls => return conn.tls_client.writeAll(conn.stream, buffer),221 .tls => conn.tls_client.writeAll(conn.stream, buffer),
223 }222 } catch |err| switch (err) {
223 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
224 else => return error.UnexpectedWriteFailure,
225 };
224 }226 }
225227
226 pub fn write(conn: *Connection, buffer: []const u8) !usize {228 pub fn write(conn: *Connection, buffer: []const u8) !usize {
227 switch (conn.protocol) {229 return switch (conn.protocol) {
228 .plain => return conn.stream.write(buffer),230 .plain => conn.stream.write(buffer),
229 .tls => return conn.tls_client.write(conn.stream, buffer),231 .tls => conn.tls_client.write(conn.stream, buffer),
230 }232 } catch |err| switch (err) {
233 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
234 else => return error.UnexpectedWriteFailure,
235 };
231 }236 }
232237
233 pub const WriteError = net.Stream.WriteError || error{};238 pub const WriteError = error{
239 ConnectionResetByPeer,
240 UnexpectedWriteFailure,
241 };
242
234 pub const Writer = std.io.Writer(*Connection, WriteError, write);243 pub const Writer = std.io.Writer(*Connection, WriteError, write);
235244
236 pub fn writer(conn: *Connection) Writer {245 pub fn writer(conn: *Connection) Writer {
...@@ -604,7 +613,7 @@ pub const Request = struct {...@@ -604,7 +613,7 @@ pub const Request = struct {
604 try buffered.flush();613 try buffered.flush();
605 }614 }
606615
607 pub const TransferReadError = proto.HeadersParser.ReadError || error{ReadFailed};616 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;
608617
609 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);618 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);
610619
...@@ -617,10 +626,7 @@ pub const Request = struct {...@@ -617,10 +626,7 @@ pub const Request = struct {
617626
618 var index: usize = 0;627 var index: usize = 0;
619 while (index == 0) {628 while (index == 0) {
620 const amt = req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip) catch |err| {629 const amt = try req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip);
621 req.client.last_error = .{ .read = err };
622 return error.ReadFailed;
623 };
624 if (amt == 0 and req.response.parser.done) break;630 if (amt == 0 and req.response.parser.done) break;
625 index += amt;631 index += amt;
626 }632 }
...@@ -638,10 +644,7 @@ pub const Request = struct {...@@ -638,10 +644,7 @@ pub const Request = struct {
638 pub fn do(req: *Request) DoError!void {644 pub fn do(req: *Request) DoError!void {
639 while (true) { // handle redirects645 while (true) { // handle redirects
640 while (true) { // read headers646 while (true) { // read headers
641 req.connection.data.buffered.fill() catch |err| {647 try req.connection.data.buffered.fill();
642 req.client.last_error = .{ .read = err };
643 return error.ReadFailed;
644 };
645648
646 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());649 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
647 req.connection.data.buffered.clear(@intCast(u16, nchecked));650 req.connection.data.buffered.clear(@intCast(u16, nchecked));
...@@ -712,16 +715,10 @@ pub const Request = struct {...@@ -712,16 +715,10 @@ pub const Request = struct {
712 if (req.response.headers.transfer_compression) |tc| switch (tc) {715 if (req.response.headers.transfer_compression) |tc| switch (tc) {
713 .compress => return error.CompressionNotSupported,716 .compress => return error.CompressionNotSupported,
714 .deflate => req.response.compression = .{717 .deflate => req.response.compression = .{
715 .deflate = std.compress.zlib.zlibStream(req.client.allocator, req.transferReader()) catch |err| {718 .deflate = std.compress.zlib.zlibStream(req.client.allocator, req.transferReader()) catch return error.CompressionInitializationFailed,
716 req.client.last_error = .{ .zlib_init = err };
717 return error.CompressionInitializationFailed;
718 },
719 },719 },
720 .gzip => req.response.compression = .{720 .gzip => req.response.compression = .{
721 .gzip = std.compress.gzip.decompress(req.client.allocator, req.transferReader()) catch |err| {721 .gzip = std.compress.gzip.decompress(req.client.allocator, req.transferReader()) catch return error.CompressionInitializationFailed,
722 req.client.last_error = .{ .gzip_init = err };
723 return error.CompressionInitializationFailed;
724 },
725 },722 },
726 .zstd => req.response.compression = .{723 .zstd => req.response.compression = .{
727 .zstd = std.compress.zstd.decompressStream(req.client.allocator, req.transferReader()),724 .zstd = std.compress.zstd.decompressStream(req.client.allocator, req.transferReader()),
...@@ -734,7 +731,7 @@ pub const Request = struct {...@@ -734,7 +731,7 @@ pub const Request = struct {
734 }731 }
735 }732 }
736733
737 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError;734 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{DecompressionFailure};
738735
739 pub const Reader = std.io.Reader(*Request, ReadError, read);736 pub const Reader = std.io.Reader(*Request, ReadError, read);
740737
...@@ -746,30 +743,15 @@ pub const Request = struct {...@@ -746,30 +743,15 @@ pub const Request = struct {
746 pub fn read(req: *Request, buffer: []u8) ReadError!usize {743 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
747 while (true) {744 while (true) {
748 const out_index = switch (req.response.compression) {745 const out_index = switch (req.response.compression) {
749 .deflate => |*deflate| deflate.read(buffer) catch |err| {746 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
750 req.client.last_error = .{ .decompress = err };747 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
751 err catch {};748 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
752 return error.ReadFailed;
753 },
754 .gzip => |*gzip| gzip.read(buffer) catch |err| {
755 req.client.last_error = .{ .decompress = err };
756 err catch {};
757 return error.ReadFailed;
758 },
759 .zstd => |*zstd| zstd.read(buffer) catch |err| {
760 req.client.last_error = .{ .decompress = err };
761 err catch {};
762 return error.ReadFailed;
763 },
764 else => try req.transferRead(buffer),749 else => try req.transferRead(buffer),
765 };750 };
766751
767 if (out_index == 0) {752 if (out_index == 0) {
768 while (!req.response.parser.state.isContent()) { // read trailing headers753 while (!req.response.parser.state.isContent()) { // read trailing headers
769 req.connection.data.buffered.fill() catch |err| {754 try req.connection.data.buffered.fill();
770 req.client.last_error = .{ .read = err };
771 return error.ReadFailed;
772 };
773755
774 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());756 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
775 req.connection.data.buffered.clear(@intCast(u16, nchecked));757 req.connection.data.buffered.clear(@intCast(u16, nchecked));
...@@ -784,17 +766,14 @@ pub const Request = struct {...@@ -784,17 +766,14 @@ pub const Request = struct {
784 pub fn readAll(req: *Request, buffer: []u8) !usize {766 pub fn readAll(req: *Request, buffer: []u8) !usize {
785 var index: usize = 0;767 var index: usize = 0;
786 while (index < buffer.len) {768 while (index < buffer.len) {
787 const amt = read(req, buffer[index..]) catch |err| {769 const amt = try read(req, buffer[index..]);
788 req.client.last_error = .{ .read = err };
789 return error.ReadFailed;
790 };
791 if (amt == 0) break;770 if (amt == 0) break;
792 index += amt;771 index += amt;
793 }772 }
794 return index;773 return index;
795 }774 }
796775
797 pub const WriteError = error{ WriteFailed, NotWriteable, MessageTooLong };776 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };
798777
799 pub const Writer = std.io.Writer(*Request, WriteError, write);778 pub const Writer = std.io.Writer(*Request, WriteError, write);
800779
...@@ -806,28 +785,16 @@ pub const Request = struct {...@@ -806,28 +785,16 @@ pub const Request = struct {
806 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {785 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
807 switch (req.headers.transfer_encoding) {786 switch (req.headers.transfer_encoding) {
808 .chunked => {787 .chunked => {
809 req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len}) catch |err| {788 try req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len});
810 req.client.last_error = .{ .write = err };789 try req.connection.data.conn.writeAll(bytes);
811 return error.WriteFailed;790 try req.connection.data.conn.writeAll("\r\n");
812 };
813 req.connection.data.conn.writeAll(bytes) catch |err| {
814 req.client.last_error = .{ .write = err };
815 return error.WriteFailed;
816 };
817 req.connection.data.conn.writeAll("\r\n") catch |err| {
818 req.client.last_error = .{ .write = err };
819 return error.WriteFailed;
820 };
821791
822 return bytes.len;792 return bytes.len;
823 },793 },
824 .content_length => |*len| {794 .content_length => |*len| {
825 if (len.* < bytes.len) return error.MessageTooLong;795 if (len.* < bytes.len) return error.MessageTooLong;
826796
827 const amt = req.connection.data.conn.write(bytes) catch |err| {797 const amt = try req.connection.data.conn.write(bytes);
828 req.client.last_error = .{ .write = err };
829 return error.WriteFailed;
830 };
831 len.* -= amt;798 len.* -= amt;
832 return amt;799 return amt;
833 },800 },
...@@ -835,8 +802,10 @@ pub const Request = struct {...@@ -835,8 +802,10 @@ pub const Request = struct {
835 }802 }
836 }803 }
837804
805 pub const FinishError = WriteError || error{ MessageNotCompleted };
806
838 /// Finish the body of a request. This notifies the server that you have no more data to send.807 /// Finish the body of a request. This notifies the server that you have no more data to send.
839 pub fn finish(req: *Request) !void {808 pub fn finish(req: *Request) FinishError!void {
840 switch (req.headers.transfer_encoding) {809 switch (req.headers.transfer_encoding) {
841 .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| {810 .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| {
842 req.client.last_error = .{ .write = err };811 req.client.last_error = .{ .write = err };
...@@ -857,7 +826,7 @@ pub fn deinit(client: *Client) void {...@@ -857,7 +826,7 @@ pub fn deinit(client: *Client) void {
857 client.* = undefined;826 client.* = undefined;
858}827}
859828
860pub const ConnectError = Allocator.Error || error{ ConnectionFailed, TlsInitializationFailed };829pub const ConnectError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
861830
862/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.831/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
863/// This function is threadsafe.832/// This function is threadsafe.
...@@ -873,9 +842,16 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -873,9 +842,16 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
873 errdefer client.allocator.destroy(conn);842 errdefer client.allocator.destroy(conn);
874 conn.* = .{ .data = undefined };843 conn.* = .{ .data = undefined };
875844
876 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| {845 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
877 client.last_error = .{ .connect = err };846 error.ConnectionRefused => return error.ConnectionRefused,
878 return error.ConnectionFailed;847 error.NetworkUnreachable => return error.NetworkUnreachable,
848 error.ConnectionTimedOut => return error.ConnectionTimedOut,
849 error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
850 error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure,
851 error.NameServerFailure => return error.NameServerFailure,
852 error.UnknownHostName => return error.UnknownHostName,
853 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
854 else => return error.UnexpectedConnectFailure,
879 };855 };
880 errdefer stream.close();856 errdefer stream.close();
881857
...@@ -896,10 +872,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -896,10 +872,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
896 conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client);872 conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
897 errdefer client.allocator.destroy(conn.data.buffered.conn.tls_client);873 errdefer client.allocator.destroy(conn.data.buffered.conn.tls_client);
898874
899 conn.data.buffered.conn.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch |err| {875 conn.data.buffered.conn.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
900 client.last_error = .{ .tls = err };
901 return error.TlsInitializationFailed;
902 };
903 // This is appropriate for HTTPS because the HTTP headers contain876 // This is appropriate for HTTPS because the HTTP headers contain
904 // the content length which is used to detect truncation attacks.877 // the content length which is used to detect truncation attacks.
905 conn.data.buffered.conn.tls_client.allow_truncation_attacks = true;878 conn.data.buffered.conn.tls_client.allow_truncation_attacks = true;
...@@ -911,12 +884,11 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -911,12 +884,11 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
911 return conn;884 return conn;
912}885}
913886
914pub const RequestError = ConnectError || error{887pub const RequestError = ConnectError || BufferedConnection.WriteError || error{
915 UnsupportedUrlScheme,888 UnsupportedUrlScheme,
916 UriMissingHost,889 UriMissingHost,
917890
918 CertificateAuthorityBundleFailed,891 CertificateBundleLoadFailure,
919 WriteFailed,
920};892};
921893
922pub const Options = struct {894pub const Options = struct {
...@@ -962,10 +934,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt...@@ -962,10 +934,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt
962 defer client.ca_bundle_mutex.unlock();934 defer client.ca_bundle_mutex.unlock();
963935
964 if (client.next_https_rescan_certs) {936 if (client.next_https_rescan_certs) {
965 client.ca_bundle.rescan(client.allocator) catch |err| {937 client.ca_bundle.rescan(client.allocator) catch return error.CertificateBundleLoadFailure;
966 client.last_error = .{ .ca_bundle = err };
967 return error.CertificateAuthorityBundleFailed;
968 };
969 @atomicStore(bool, &client.next_https_rescan_certs, false, .Release);938 @atomicStore(bool, &client.next_https_rescan_certs, false, .Release);
970 }939 }
971 }940 }
...@@ -989,13 +958,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt...@@ -989,13 +958,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt
989958
990 req.arena = std.heap.ArenaAllocator.init(client.allocator);959 req.arena = std.heap.ArenaAllocator.init(client.allocator);
991960
992 req.start(uri, headers) catch |err| {961 try req.start(uri, headers);
993 if (err == error.OutOfMemory) return error.OutOfMemory;
994 const err_casted = @errSetCast(BufferedConnection.WriteError, err);
995
996 client.last_error = .{ .write = err_casted };
997 return error.WriteFailed;
998 };
999962
1000 return req;963 return req;
1001}964}