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
signature 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,
2525/// The pool of connections that can be reused (and currently in use).
2626connection_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
3128pub const ExtraError = union(enum) {
3229 pub const TcpConnectError = std.net.TcpConnectToHostError;
3330 pub const TlsError = std.crypto.tls.Client.InitError(net.Stream);
......@@ -184,31 +181,33 @@ pub const Connection = struct {
184181
185182 pub const Protocol = enum { plain, tls };
186183
187 pub fn read(conn: *Connection, buffer: []u8) !usize {
188 switch (conn.protocol) {
189 .plain => return conn.stream.read(buffer),
190 .tls => return conn.tls_client.read(conn.stream, buffer),
191 }
184 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
185 return switch (conn.protocol) {
186 .plain => conn.stream.read(buffer),
187 .tls => conn.tls_client.read(conn.stream, buffer),
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 };
192195 }
193196
194 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) !usize {
195 switch (conn.protocol) {
196 .plain => return conn.stream.readAtLeast(buffer, len),
197 .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),
198 }
197 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
198 return switch (conn.protocol) {
199 .plain => conn.stream.readAtLeast(buffer, len),
200 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
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 };
199208 }
200209
201 pub const ReadError = net.Stream.ReadError || error{
202 TlsConnectionTruncated,
203 TlsRecordOverflow,
204 TlsDecodeError,
205 TlsAlert,
206 TlsBadRecordMac,
207 Overflow,
208 TlsBadLength,
209 TlsIllegalParameter,
210 TlsUnexpectedMessage,
211 };
210 pub const ReadError = error{ TlsFailure, TlsAlert, ConnectionTimedOut, ConnectionResetByPeer, UnexpectedReadFailure };
212211
213212 pub const Reader = std.io.Reader(*Connection, ReadError, read);
214213
......@@ -217,20 +216,30 @@ pub const Connection = struct {
217216 }
218217
219218 pub fn writeAll(conn: *Connection, buffer: []const u8) !void {
220 switch (conn.protocol) {
221 .plain => return conn.stream.writeAll(buffer),
222 .tls => return conn.tls_client.writeAll(conn.stream, buffer),
223 }
219 return switch (conn.protocol) {
220 .plain => conn.stream.writeAll(buffer),
221 .tls => conn.tls_client.writeAll(conn.stream, buffer),
222 } catch |err| switch (err) {
223 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
224 else => return error.UnexpectedWriteFailure,
225 };
224226 }
225227
226228 pub fn write(conn: *Connection, buffer: []const u8) !usize {
227 switch (conn.protocol) {
228 .plain => return conn.stream.write(buffer),
229 .tls => return conn.tls_client.write(conn.stream, buffer),
230 }
229 return switch (conn.protocol) {
230 .plain => conn.stream.write(buffer),
231 .tls => conn.tls_client.write(conn.stream, buffer),
232 } catch |err| switch (err) {
233 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
234 else => return error.UnexpectedWriteFailure,
235 };
231236 }
232237
233 pub const WriteError = net.Stream.WriteError || error{};
238 pub const WriteError = error{
239 ConnectionResetByPeer,
240 UnexpectedWriteFailure,
241 };
242
234243 pub const Writer = std.io.Writer(*Connection, WriteError, write);
235244
236245 pub fn writer(conn: *Connection) Writer {
......@@ -604,7 +613,7 @@ pub const Request = struct {
604613 try buffered.flush();
605614 }
606615
607 pub const TransferReadError = proto.HeadersParser.ReadError || error{ReadFailed};
616 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;
608617
609618 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);
610619
......@@ -617,10 +626,7 @@ pub const Request = struct {
617626
618627 var index: usize = 0;
619628 while (index == 0) {
620 const amt = req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip) catch |err| {
621 req.client.last_error = .{ .read = err };
622 return error.ReadFailed;
623 };
629 const amt = try req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip);
624630 if (amt == 0 and req.response.parser.done) break;
625631 index += amt;
626632 }
......@@ -638,10 +644,7 @@ pub const Request = struct {
638644 pub fn do(req: *Request) DoError!void {
639645 while (true) { // handle redirects
640646 while (true) { // read headers
641 req.connection.data.buffered.fill() catch |err| {
642 req.client.last_error = .{ .read = err };
643 return error.ReadFailed;
644 };
647 try req.connection.data.buffered.fill();
645648
646649 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
647650 req.connection.data.buffered.clear(@intCast(u16, nchecked));
......@@ -712,16 +715,10 @@ pub const Request = struct {
712715 if (req.response.headers.transfer_compression) |tc| switch (tc) {
713716 .compress => return error.CompressionNotSupported,
714717 .deflate => req.response.compression = .{
715 .deflate = std.compress.zlib.zlibStream(req.client.allocator, req.transferReader()) catch |err| {
716 req.client.last_error = .{ .zlib_init = err };
717 return error.CompressionInitializationFailed;
718 },
718 .deflate = std.compress.zlib.zlibStream(req.client.allocator, req.transferReader()) catch return error.CompressionInitializationFailed,
719719 },
720720 .gzip => req.response.compression = .{
721 .gzip = std.compress.gzip.decompress(req.client.allocator, req.transferReader()) catch |err| {
722 req.client.last_error = .{ .gzip_init = err };
723 return error.CompressionInitializationFailed;
724 },
721 .gzip = std.compress.gzip.decompress(req.client.allocator, req.transferReader()) catch return error.CompressionInitializationFailed,
725722 },
726723 .zstd => req.response.compression = .{
727724 .zstd = std.compress.zstd.decompressStream(req.client.allocator, req.transferReader()),
......@@ -734,7 +731,7 @@ pub const Request = struct {
734731 }
735732 }
736733
737 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError;
734 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{DecompressionFailure};
738735
739736 pub const Reader = std.io.Reader(*Request, ReadError, read);
740737
......@@ -746,30 +743,15 @@ pub const Request = struct {
746743 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
747744 while (true) {
748745 const out_index = switch (req.response.compression) {
749 .deflate => |*deflate| deflate.read(buffer) catch |err| {
750 req.client.last_error = .{ .decompress = err };
751 err catch {};
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 },
746 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
747 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
748 .zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
764749 else => try req.transferRead(buffer),
765750 };
766751
767752 if (out_index == 0) {
768753 while (!req.response.parser.state.isContent()) { // read trailing headers
769 req.connection.data.buffered.fill() catch |err| {
770 req.client.last_error = .{ .read = err };
771 return error.ReadFailed;
772 };
754 try req.connection.data.buffered.fill();
773755
774756 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
775757 req.connection.data.buffered.clear(@intCast(u16, nchecked));
......@@ -784,17 +766,14 @@ pub const Request = struct {
784766 pub fn readAll(req: *Request, buffer: []u8) !usize {
785767 var index: usize = 0;
786768 while (index < buffer.len) {
787 const amt = read(req, buffer[index..]) catch |err| {
788 req.client.last_error = .{ .read = err };
789 return error.ReadFailed;
790 };
769 const amt = try read(req, buffer[index..]);
791770 if (amt == 0) break;
792771 index += amt;
793772 }
794773 return index;
795774 }
796775
797 pub const WriteError = error{ WriteFailed, NotWriteable, MessageTooLong };
776 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };
798777
799778 pub const Writer = std.io.Writer(*Request, WriteError, write);
800779
......@@ -806,28 +785,16 @@ pub const Request = struct {
806785 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
807786 switch (req.headers.transfer_encoding) {
808787 .chunked => {
809 req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len}) catch |err| {
810 req.client.last_error = .{ .write = err };
811 return error.WriteFailed;
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 };
788 try req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len});
789 try req.connection.data.conn.writeAll(bytes);
790 try req.connection.data.conn.writeAll("\r\n");
821791
822792 return bytes.len;
823793 },
824794 .content_length => |*len| {
825795 if (len.* < bytes.len) return error.MessageTooLong;
826796
827 const amt = req.connection.data.conn.write(bytes) catch |err| {
828 req.client.last_error = .{ .write = err };
829 return error.WriteFailed;
830 };
797 const amt = try req.connection.data.conn.write(bytes);
831798 len.* -= amt;
832799 return amt;
833800 },
......@@ -835,8 +802,10 @@ pub const Request = struct {
835802 }
836803 }
837804
805 pub const FinishError = WriteError || error{ MessageNotCompleted };
806
838807 /// 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 {
840809 switch (req.headers.transfer_encoding) {
841810 .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| {
842811 req.client.last_error = .{ .write = err };
......@@ -857,7 +826,7 @@ pub fn deinit(client: *Client) void {
857826 client.* = undefined;
858827}
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
862831/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
863832/// This function is threadsafe.
......@@ -873,9 +842,16 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
873842 errdefer client.allocator.destroy(conn);
874843 conn.* = .{ .data = undefined };
875844
876 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| {
877 client.last_error = .{ .connect = err };
878 return error.ConnectionFailed;
845 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
846 error.ConnectionRefused => return error.ConnectionRefused,
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,
879855 };
880856 errdefer stream.close();
881857
......@@ -896,10 +872,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
896872 conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
897873 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| {
900 client.last_error = .{ .tls = err };
901 return error.TlsInitializationFailed;
902 };
875 conn.data.buffered.conn.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
903876 // This is appropriate for HTTPS because the HTTP headers contain
904877 // the content length which is used to detect truncation attacks.
905878 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
911884 return conn;
912885}
913886
914pub const RequestError = ConnectError || error{
887pub const RequestError = ConnectError || BufferedConnection.WriteError || error{
915888 UnsupportedUrlScheme,
916889 UriMissingHost,
917890
918 CertificateAuthorityBundleFailed,
919 WriteFailed,
891 CertificateBundleLoadFailure,
920892};
921893
922894pub const Options = struct {
......@@ -962,10 +934,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt
962934 defer client.ca_bundle_mutex.unlock();
963935
964936 if (client.next_https_rescan_certs) {
965 client.ca_bundle.rescan(client.allocator) catch |err| {
966 client.last_error = .{ .ca_bundle = err };
967 return error.CertificateAuthorityBundleFailed;
968 };
937 client.ca_bundle.rescan(client.allocator) catch return error.CertificateBundleLoadFailure;
969938 @atomicStore(bool, &client.next_https_rescan_certs, false, .Release);
970939 }
971940 }
......@@ -989,13 +958,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt
989958
990959 req.arena = std.heap.ArenaAllocator.init(client.allocator);
991960
992 req.start(uri, headers) catch |err| {
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 };
961 try req.start(uri, headers);
999962
1000963 return req;
1001964}