| ... | ... | @@ -25,9 +25,6 @@ next_https_rescan_certs: bool = true, |
| 25 | 25 | /// The pool of connections that can be reused (and currently in use). |
| 26 | 26 | connection_pool: ConnectionPool = .{}, |
| 27 | 27 | |
| 28 | | /// The last error that occurred on this client. This is not threadsafe, do not expect it to be completely accurate. |
| 29 | | last_error: ?ExtraError = null, |
| 30 | | |
| 31 | 28 | pub const ExtraError = union(enum) { |
| 32 | 29 | pub const TcpConnectError = std.net.TcpConnectToHostError; |
| 33 | 30 | pub const TlsError = std.crypto.tls.Client.InitError(net.Stream); |
| ... | ... | @@ -184,31 +181,33 @@ pub const Connection = struct { |
| 184 | 181 | |
| 185 | 182 | pub const Protocol = enum { plain, tls }; |
| 186 | 183 | |
| 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 | }; |
| 192 | 195 | } |
| 193 | 196 | |
| 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 | }; |
| 199 | 208 | } |
| 200 | 209 | |
| 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 }; |
| 212 | 211 | |
| 213 | 212 | pub const Reader = std.io.Reader(*Connection, ReadError, read); |
| 214 | 213 | |
| ... | ... | @@ -217,20 +216,30 @@ pub const Connection = struct { |
| 217 | 216 | } |
| 218 | 217 | |
| 219 | 218 | 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 | }; |
| 224 | 226 | } |
| 225 | 227 | |
| 226 | 228 | 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 | }; |
| 231 | 236 | } |
| 232 | 237 | |
| 233 | | pub const WriteError = net.Stream.WriteError || error{}; |
| 238 | pub const WriteError = error{ |
| 239 | ConnectionResetByPeer, |
| 240 | UnexpectedWriteFailure, |
| 241 | }; |
| 242 | |
| 234 | 243 | pub const Writer = std.io.Writer(*Connection, WriteError, write); |
| 235 | 244 | |
| 236 | 245 | pub fn writer(conn: *Connection) Writer { |
| ... | ... | @@ -604,7 +613,7 @@ pub const Request = struct { |
| 604 | 613 | try buffered.flush(); |
| 605 | 614 | } |
| 606 | 615 | |
| 607 | | pub const TransferReadError = proto.HeadersParser.ReadError || error{ReadFailed}; |
| 616 | pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError; |
| 608 | 617 | |
| 609 | 618 | pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead); |
| 610 | 619 | |
| ... | ... | @@ -617,10 +626,7 @@ pub const Request = struct { |
| 617 | 626 | |
| 618 | 627 | var index: usize = 0; |
| 619 | 628 | 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); |
| 624 | 630 | if (amt == 0 and req.response.parser.done) break; |
| 625 | 631 | index += amt; |
| 626 | 632 | } |
| ... | ... | @@ -638,10 +644,7 @@ pub const Request = struct { |
| 638 | 644 | pub fn do(req: *Request) DoError!void { |
| 639 | 645 | while (true) { // handle redirects |
| 640 | 646 | 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(); |
| 645 | 648 | |
| 646 | 649 | const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek()); |
| 647 | 650 | req.connection.data.buffered.clear(@intCast(u16, nchecked)); |
| ... | ... | @@ -712,16 +715,10 @@ pub const Request = struct { |
| 712 | 715 | if (req.response.headers.transfer_compression) |tc| switch (tc) { |
| 713 | 716 | .compress => return error.CompressionNotSupported, |
| 714 | 717 | .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, |
| 719 | 719 | }, |
| 720 | 720 | .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, |
| 725 | 722 | }, |
| 726 | 723 | .zstd => req.response.compression = .{ |
| 727 | 724 | .zstd = std.compress.zstd.decompressStream(req.client.allocator, req.transferReader()), |
| ... | ... | @@ -734,7 +731,7 @@ pub const Request = struct { |
| 734 | 731 | } |
| 735 | 732 | } |
| 736 | 733 | |
| 737 | | pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError; |
| 734 | pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{DecompressionFailure}; |
| 738 | 735 | |
| 739 | 736 | pub const Reader = std.io.Reader(*Request, ReadError, read); |
| 740 | 737 | |
| ... | ... | @@ -746,30 +743,15 @@ pub const Request = struct { |
| 746 | 743 | pub fn read(req: *Request, buffer: []u8) ReadError!usize { |
| 747 | 744 | while (true) { |
| 748 | 745 | 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, |
| 764 | 749 | else => try req.transferRead(buffer), |
| 765 | 750 | }; |
| 766 | 751 | |
| 767 | 752 | if (out_index == 0) { |
| 768 | 753 | 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(); |
| 773 | 755 | |
| 774 | 756 | const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek()); |
| 775 | 757 | req.connection.data.buffered.clear(@intCast(u16, nchecked)); |
| ... | ... | @@ -784,17 +766,14 @@ pub const Request = struct { |
| 784 | 766 | pub fn readAll(req: *Request, buffer: []u8) !usize { |
| 785 | 767 | var index: usize = 0; |
| 786 | 768 | 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..]); |
| 791 | 770 | if (amt == 0) break; |
| 792 | 771 | index += amt; |
| 793 | 772 | } |
| 794 | 773 | return index; |
| 795 | 774 | } |
| 796 | 775 | |
| 797 | | pub const WriteError = error{ WriteFailed, NotWriteable, MessageTooLong }; |
| 776 | pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong }; |
| 798 | 777 | |
| 799 | 778 | pub const Writer = std.io.Writer(*Request, WriteError, write); |
| 800 | 779 | |
| ... | ... | @@ -806,28 +785,16 @@ pub const Request = struct { |
| 806 | 785 | pub fn write(req: *Request, bytes: []const u8) WriteError!usize { |
| 807 | 786 | switch (req.headers.transfer_encoding) { |
| 808 | 787 | .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"); |
| 821 | 791 | |
| 822 | 792 | return bytes.len; |
| 823 | 793 | }, |
| 824 | 794 | .content_length => |*len| { |
| 825 | 795 | if (len.* < bytes.len) return error.MessageTooLong; |
| 826 | 796 | |
| 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); |
| 831 | 798 | len.* -= amt; |
| 832 | 799 | return amt; |
| 833 | 800 | }, |
| ... | ... | @@ -835,8 +802,10 @@ pub const Request = struct { |
| 835 | 802 | } |
| 836 | 803 | } |
| 837 | 804 | |
| 805 | pub const FinishError = WriteError || error{ MessageNotCompleted }; |
| 806 | |
| 838 | 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 | 809 | switch (req.headers.transfer_encoding) { |
| 841 | 810 | .chunked => req.connection.data.conn.writeAll("0\r\n\r\n") catch |err| { |
| 842 | 811 | req.client.last_error = .{ .write = err }; |
| ... | ... | @@ -857,7 +826,7 @@ pub fn deinit(client: *Client) void { |
| 857 | 826 | client.* = undefined; |
| 858 | 827 | } |
| 859 | 828 | |
| 860 | | pub const ConnectError = Allocator.Error || error{ ConnectionFailed, TlsInitializationFailed }; |
| 829 | pub const ConnectError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; |
| 861 | 830 | |
| 862 | 831 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. |
| 863 | 832 | /// This function is threadsafe. |
| ... | ... | @@ -873,9 +842,16 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 873 | 842 | errdefer client.allocator.destroy(conn); |
| 874 | 843 | conn.* = .{ .data = undefined }; |
| 875 | 844 | |
| 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, |
| 879 | 855 | }; |
| 880 | 856 | errdefer stream.close(); |
| 881 | 857 | |
| ... | ... | @@ -896,10 +872,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 896 | 872 | conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client); |
| 897 | 873 | errdefer client.allocator.destroy(conn.data.buffered.conn.tls_client); |
| 898 | 874 | |
| 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; |
| 903 | 876 | // This is appropriate for HTTPS because the HTTP headers contain |
| 904 | 877 | // the content length which is used to detect truncation attacks. |
| 905 | 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 | 884 | return conn; |
| 912 | 885 | } |
| 913 | 886 | |
| 914 | | pub const RequestError = ConnectError || error{ |
| 887 | pub const RequestError = ConnectError || BufferedConnection.WriteError || error{ |
| 915 | 888 | UnsupportedUrlScheme, |
| 916 | 889 | UriMissingHost, |
| 917 | 890 | |
| 918 | | CertificateAuthorityBundleFailed, |
| 919 | | WriteFailed, |
| 891 | CertificateBundleLoadFailure, |
| 920 | 892 | }; |
| 921 | 893 | |
| 922 | 894 | pub const Options = struct { |
| ... | ... | @@ -962,10 +934,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt |
| 962 | 934 | defer client.ca_bundle_mutex.unlock(); |
| 963 | 935 | |
| 964 | 936 | 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; |
| 969 | 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 | 958 | |
| 990 | 959 | req.arena = std.heap.ArenaAllocator.init(client.allocator); |
| 991 | 960 | |
| 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); |
| 999 | 962 | |
| 1000 | 963 | return req; |
| 1001 | 964 | } |