| author | |
| committer | |
| log | 20a784f7136143e4afa4d9d1d85fc0fa6d69d777 |
| tree | b6db814d9e577b96be4f4d102d9648c85833428e |
| parent | c872a9fd49b090efc5b6132ec0ab959d7fe8e70f |
5 files changed, 535 insertions(+), 591 deletions(-)
lib/std/crypto/ecdsa.zig+14-17| ... | ... | @@ -155,38 +155,35 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { |
| 155 | 155 | } |
| 156 | 156 | |
| 157 | 157 | // Read a DER-encoded integer. |
| 158 | fn readDerInt(out: []u8, reader: anytype) EncodingError!void { | |
| 159 | var buf: [2]u8 = undefined; | |
| 160 | _ = reader.readNoEof(&buf) catch return error.InvalidEncoding; | |
| 158 | // Asserts `br` has storage capacity >= 2. | |
| 159 | fn readDerInt(out: []u8, br: *std.io.BufferedReader) EncodingError!void { | |
| 160 | const buf = br.take(2) catch return error.InvalidEncoding; | |
| 161 | 161 | if (buf[0] != 0x02) return error.InvalidEncoding; |
| 162 | var expected_len = @as(usize, buf[1]); | |
| 162 | var expected_len: usize = buf[1]; | |
| 163 | 163 | if (expected_len == 0 or expected_len > 1 + out.len) return error.InvalidEncoding; |
| 164 | 164 | var has_top_bit = false; |
| 165 | 165 | if (expected_len == 1 + out.len) { |
| 166 | if ((reader.readByte() catch return error.InvalidEncoding) != 0) return error.InvalidEncoding; | |
| 166 | if ((br.takeByte() catch return error.InvalidEncoding) != 0) return error.InvalidEncoding; | |
| 167 | 167 | expected_len -= 1; |
| 168 | 168 | has_top_bit = true; |
| 169 | 169 | } |
| 170 | 170 | const out_slice = out[out.len - expected_len ..]; |
| 171 | reader.readNoEof(out_slice) catch return error.InvalidEncoding; | |
| 171 | br.read(out_slice) catch return error.InvalidEncoding; | |
| 172 | 172 | if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding; |
| 173 | 173 | } |
| 174 | 174 | |
| 175 | 175 | /// Create a signature from a DER representation. |
| 176 | 176 | /// Returns InvalidEncoding if the DER encoding is invalid. |
| 177 | 177 | pub fn fromDer(der: []const u8) EncodingError!Signature { |
| 178 | if (der.len < 2) return error.InvalidEncoding; | |
| 179 | var br: std.io.BufferedReader = undefined; | |
| 180 | br.initFixed(der); | |
| 181 | const buf = br.take(2) catch return error.InvalidEncoding; | |
| 182 | if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) return error.InvalidEncoding; | |
| 178 | 183 | var sig: Signature = mem.zeroInit(Signature, .{}); |
| 179 | var fb: std.io.FixedBufferStream = .{ .buffer = der }; | |
| 180 | const reader = fb.reader(); | |
| 181 | var buf: [2]u8 = undefined; | |
| 182 | _ = reader.readNoEof(&buf) catch return error.InvalidEncoding; | |
| 183 | if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) { | |
| 184 | return error.InvalidEncoding; | |
| 185 | } | |
| 186 | try readDerInt(&sig.r, reader); | |
| 187 | try readDerInt(&sig.s, reader); | |
| 188 | if (fb.getPos() catch unreachable != der.len) return error.InvalidEncoding; | |
| 189 | ||
| 184 | try readDerInt(&sig.r, &br); | |
| 185 | try readDerInt(&sig.s, &br); | |
| 186 | if (br.seek != der.len) return error.InvalidEncoding; | |
| 190 | 187 | return sig; |
| 191 | 188 | } |
| 192 | 189 | }; |
lib/std/crypto/tls/Client.zig+165-254| ... | ... | @@ -1,3 +1,6 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const native_endian = builtin.cpu.arch.endian(); | |
| 3 | ||
| 1 | 4 | const std = @import("../../std.zig"); |
| 2 | 5 | const tls = std.crypto.tls; |
| 3 | 6 | const Client = @This(); |
| ... | ... | @@ -13,18 +16,44 @@ const hkdfExpandLabel = tls.hkdfExpandLabel; |
| 13 | 16 | const int = tls.int; |
| 14 | 17 | const array = tls.array; |
| 15 | 18 | |
| 19 | /// The encrypted stream from the server to the client. Bytes are pulled from | |
| 20 | /// here via `reader`. | |
| 21 | /// | |
| 22 | /// The buffer is asserted to have capacity at least `min_buffer_len`. | |
| 23 | /// | |
| 24 | /// The size is enough to contain exactly one TLSCiphertext record. | |
| 25 | /// This buffer is segmented into four parts: | |
| 26 | /// 0. unused | |
| 27 | /// 1. cleartext | |
| 28 | /// 2. ciphertext | |
| 29 | /// 3. unused | |
| 30 | /// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and | |
| 31 | /// `partial_ciphertext_end` describe the span of the segments. | |
| 32 | input: *std.io.BufferedReader, | |
| 33 | /// The encrypted stream from the client to the server. Bytes are pushed here | |
| 34 | /// via `writer`. | |
| 35 | /// | |
| 36 | /// The buffer is asserted to have capacity at least `min_buffer_len`. | |
| 37 | output: *std.io.BufferedWriter, | |
| 38 | /// Cleartext received from the server here. | |
| 39 | /// | |
| 40 | /// Its buffer aliases the buffer of `input`. | |
| 41 | reader: std.io.BufferedReader, | |
| 42 | /// Populated under various error conditions. | |
| 43 | diagnostics: Diagnostics, | |
| 44 | ||
| 16 | 45 | tls_version: tls.ProtocolVersion, |
| 17 | 46 | read_seq: u64, |
| 18 | 47 | write_seq: u64, |
| 19 | /// The starting index of cleartext bytes inside `partially_read_buffer`. | |
| 48 | /// The starting index of cleartext bytes inside the input buffer. | |
| 20 | 49 | partial_cleartext_idx: u15, |
| 21 | /// The ending index of cleartext bytes inside `partially_read_buffer` as well | |
| 50 | /// The ending index of cleartext bytes inside the input buffer as well | |
| 22 | 51 | /// as the starting index of ciphertext bytes. |
| 23 | 52 | partial_ciphertext_idx: u15, |
| 24 | /// The ending index of ciphertext bytes inside `partially_read_buffer`. | |
| 53 | /// The ending index of ciphertext bytes inside the input buffer. | |
| 25 | 54 | partial_ciphertext_end: u15, |
| 26 | 55 | /// When this is true, the stream may still not be at the end because there |
| 27 | /// may be data in `partially_read_buffer`. | |
| 56 | /// may be data in the input buffer. | |
| 28 | 57 | received_close_notify: bool, |
| 29 | 58 | /// By default, reaching the end-of-stream when reading from the server will |
| 30 | 59 | /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify |
| ... | ... | @@ -35,24 +64,40 @@ received_close_notify: bool, |
| 35 | 64 | /// the amount of data expected, such as HTTP with the Content-Length header. |
| 36 | 65 | allow_truncation_attacks: bool, |
| 37 | 66 | application_cipher: tls.ApplicationCipher, |
| 38 | /// The size is enough to contain exactly one TLSCiphertext record. | |
| 39 | /// This buffer is segmented into four parts: | |
| 40 | /// 0. unused | |
| 41 | /// 1. cleartext | |
| 42 | /// 2. ciphertext | |
| 43 | /// 3. unused | |
| 44 | /// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and | |
| 45 | /// `partial_ciphertext_end` describe the span of the segments. | |
| 46 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, | |
| 47 | /// Encrypted bytes sent to the server here. | |
| 48 | output: *std.io.BufferedWriter, | |
| 49 | /// If non-null, ssl secrets are logged to a file. Creating such a log file allows other | |
| 50 | /// programs with access to that file to decrypt all traffic over this connection. | |
| 51 | ssl_key_log: ?struct { | |
| 67 | /// If non-null, ssl secrets are logged to a stream. Creating such a log file | |
| 68 | /// allows other programs with access to that file to decrypt all traffic over | |
| 69 | /// this connection. | |
| 70 | ssl_key_log: ?*SslKeyLog, | |
| 71 | ||
| 72 | pub const Diagnostics = union { | |
| 73 | /// Populated on `error.WriteFailure` and `error.ReadFailure`. | |
| 74 | err: anyerror, | |
| 75 | /// Populated on `error.TlsAlert`. | |
| 76 | /// | |
| 77 | /// If this isn't a error alert, then it's a closure alert, which makes | |
| 78 | /// no sense in a handshake. | |
| 79 | alert: tls.AlertDescription, | |
| 80 | ||
| 81 | fn wrapWrite(d: *Diagnostics, returned: anyerror!void) error{WriteFailure}!void { | |
| 82 | returned catch |err| { | |
| 83 | d.* = .{ .err = err }; | |
| 84 | return error.WriteFailure; | |
| 85 | }; | |
| 86 | } | |
| 87 | ||
| 88 | fn wrapRead(d: *Diagnostics, returned: anyerror!void) error{ReadFailure}!void { | |
| 89 | returned catch |err| { | |
| 90 | d.* = .{ .err = err }; | |
| 91 | return error.ReadFailure; | |
| 92 | }; | |
| 93 | } | |
| 94 | }; | |
| 95 | ||
| 96 | pub const SslKeyLog = struct { | |
| 52 | 97 | client_key_seq: u64, |
| 53 | 98 | server_key_seq: u64, |
| 54 | 99 | client_random: [32]u8, |
| 55 | file: std.fs.File, | |
| 100 | writer: *std.io.BufferedWriter, | |
| 56 | 101 | |
| 57 | 102 | fn clientCounter(key_log: *@This()) u64 { |
| 58 | 103 | defer key_log.client_key_seq += 1; |
| ... | ... | @@ -63,31 +108,12 @@ ssl_key_log: ?struct { |
| 63 | 108 | defer key_log.server_key_seq += 1; |
| 64 | 109 | return key_log.server_key_seq; |
| 65 | 110 | } |
| 66 | }, | |
| 67 | ||
| 68 | /// This is an example of the type that is needed by the read and write | |
| 69 | /// functions. It can have any fields but it must at least have these | |
| 70 | /// functions. | |
| 71 | /// | |
| 72 | /// Note that `std.net.Stream` conforms to this interface. | |
| 73 | /// | |
| 74 | /// This declaration serves as documentation only. | |
| 75 | pub const StreamInterface = struct { | |
| 76 | /// Can be any error set. | |
| 77 | pub const ReadError = error{}; | |
| 78 | ||
| 79 | /// Returns the number of bytes read. The number read may be less than the | |
| 80 | /// buffer space provided. End-of-stream is indicated by a return value of 0. | |
| 81 | /// | |
| 82 | /// The `iovecs` parameter is mutable because so that function may to | |
| 83 | /// mutate the fields in order to handle partial reads from the underlying | |
| 84 | /// stream layer. | |
| 85 | pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize { | |
| 86 | _ = .{ this, iovecs }; | |
| 87 | @panic("unimplemented"); | |
| 88 | } | |
| 89 | 111 | }; |
| 90 | 112 | |
| 113 | /// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init` | |
| 114 | /// each require a buffer capacity at least this amount. | |
| 115 | pub const min_buffer_len = tls.max_ciphertext_record_len; | |
| 116 | ||
| 91 | 117 | pub const Options = struct { |
| 92 | 118 | /// How to perform host verification of server certificates. |
| 93 | 119 | host: union(enum) { |
| ... | ... | @@ -109,39 +135,11 @@ pub const Options = struct { |
| 109 | 135 | /// Verify that the server certificate is authorized by a given ca bundle. |
| 110 | 136 | bundle: Certificate.Bundle, |
| 111 | 137 | }, |
| 112 | /// If non-null, ssl secrets are logged to this file. Creating such a log file allows | |
| 138 | /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows | |
| 113 | 139 | /// other programs with access to that file to decrypt all traffic over this connection. |
| 114 | /// TODO `std.crypto` should have no dependencies on `std.fs`. | |
| 115 | ssl_key_log_file: ?std.fs.File = null, | |
| 116 | diagnostics: ?*Diagnostics = null, | |
| 117 | ||
| 118 | pub const Diagnostics = union { | |
| 119 | /// Populated on `error.WriteFailure` and `error.ReadFailure`. | |
| 120 | err: anyerror, | |
| 121 | /// Populated on `error.TlsAlert`. | |
| 122 | /// | |
| 123 | /// If this isn't a error alert, then it's a closure alert, which makes | |
| 124 | /// no sense in a handshake. | |
| 125 | alert: tls.AlertDescription, | |
| 126 | }; | |
| 140 | ssl_key_log: ?*std.io.BufferedWriter = null, | |
| 127 | 141 | }; |
| 128 | 142 | |
| 129 | /// TODO I wish this could be a method of Diagnostics | |
| 130 | fn wrapWrite(opt_diags: ?*Options.Diagnostics, returned: anyerror!void) error{WriteFailure}!void { | |
| 131 | returned catch |err| { | |
| 132 | if (opt_diags) |diags| diags.* = .{ .err = err }; | |
| 133 | return error.WriteFailure; | |
| 134 | }; | |
| 135 | } | |
| 136 | ||
| 137 | /// TODO I wish this could be a method of Diagnostics | |
| 138 | fn wrapRead(opt_diags: ?*Options.Diagnostics, returned: anyerror!void) error{ReadFailure}!void { | |
| 139 | returned catch |err| { | |
| 140 | if (opt_diags) |diags| diags.* = .{ .err = err }; | |
| 141 | return error.ReadFailure; | |
| 142 | }; | |
| 143 | } | |
| 144 | ||
| 145 | 143 | const InitError = error{ |
| 146 | 144 | //OutOfMemory, |
| 147 | 145 | WriteFailure, |
| ... | ... | @@ -193,12 +191,21 @@ const InitError = error{ |
| 193 | 191 | WeakPublicKey, |
| 194 | 192 | }; |
| 195 | 193 | |
| 196 | /// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session with `input`, which | |
| 197 | /// must conform to `StreamInterface`. | |
| 194 | /// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session. | |
| 198 | 195 | /// |
| 199 | 196 | /// `host` is only borrowed during this function call. |
| 200 | pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) InitError!Client { | |
| 201 | const diags = options.diagnostics; | |
| 197 | /// | |
| 198 | /// Both `input` and `output` are asserted to have buffer capacity at least | |
| 199 | /// `min_buffer_len`. | |
| 200 | pub fn init( | |
| 201 | client: *Client, | |
| 202 | input: *std.io.BufferedReader, | |
| 203 | output: *std.io.BufferedWriter, | |
| 204 | options: Options, | |
| 205 | ) InitError!void { | |
| 206 | assert(input.storage.buffer.len >= min_buffer_len); | |
| 207 | assert(output.buffer.len >= min_buffer_len); | |
| 208 | const diags = &client.diagnostics; | |
| 202 | 209 | const host = switch (options.host) { |
| 203 | 210 | .no_verification => "", |
| 204 | 211 | .explicit => |host| host, |
| ... | ... | @@ -291,7 +298,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 291 | 298 | |
| 292 | 299 | { |
| 293 | 300 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; |
| 294 | try wrapWrite(diags, output.writevAll(iovecs[0..if (host.len == 0) 1 else 2])); | |
| 301 | try diags.wrapWrite(output.writevAll(iovecs[0..if (host.len == 0) 1 else 2])); | |
| 295 | 302 | } |
| 296 | 303 | |
| 297 | 304 | var tls_version: tls.ProtocolVersion = undefined; |
| ... | ... | @@ -343,12 +350,12 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 343 | 350 | var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined; |
| 344 | 351 | var d: tls.Decoder = .{ .buf = &handshake_buffer }; |
| 345 | 352 | fragment: while (true) { |
| 346 | try wrapRead(diags, d.readAtLeastOurAmt(input, tls.record_header_len)); | |
| 353 | try diags.wrapRead(d.readAtLeastOurAmt(input, tls.record_header_len)); | |
| 347 | 354 | const record_header = d.buf[d.idx..][0..tls.record_header_len]; |
| 348 | 355 | const record_ct = d.decode(tls.ContentType); |
| 349 | 356 | d.skip(2); // legacy_version |
| 350 | 357 | const record_len = d.decode(u16); |
| 351 | try wrapRead(diags, d.readAtLeast(input, record_len)); | |
| 358 | try diags.wrapRead(d.readAtLeast(input, record_len)); | |
| 352 | 359 | var record_decoder = try d.sub(record_len); |
| 353 | 360 | var ctd, const ct = content: switch (cipher_state) { |
| 354 | 361 | .cleartext => .{ record_decoder, record_ct }, |
| ... | ... | @@ -426,7 +433,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 426 | 433 | const level = ctd.decode(tls.AlertLevel); |
| 427 | 434 | const desc = ctd.decode(tls.AlertDescription); |
| 428 | 435 | _ = level; |
| 429 | if (diags) |x| x.* = .{ .alert = desc }; | |
| 436 | diags.* = .{ .alert = desc }; | |
| 430 | 437 | return error.TlsAlert; |
| 431 | 438 | }, |
| 432 | 439 | .change_cipher_spec => { |
| ... | ... | @@ -768,7 +775,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 768 | 775 | &client_change_cipher_spec_msg, |
| 769 | 776 | &client_verify_msg, |
| 770 | 777 | }; |
| 771 | try wrapWrite(diags, output.writevAll(&all_msgs_vec)); | |
| 778 | try diags.wrapWrite(output.writevAll(&all_msgs_vec)); | |
| 772 | 779 | }, |
| 773 | 780 | } |
| 774 | 781 | write_seq += 1; |
| ... | ... | @@ -833,7 +840,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 833 | 840 | &client_change_cipher_spec_msg, |
| 834 | 841 | &finished_msg, |
| 835 | 842 | }; |
| 836 | try wrapWrite(diags, output.writevAll(&all_msgs_vec)); | |
| 843 | try diags.wrapWrite(output.writevAll(&all_msgs_vec)); | |
| 837 | 844 | |
| 838 | 845 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 839 | 846 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| ... | ... | @@ -865,7 +872,10 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 865 | 872 | }, |
| 866 | 873 | }; |
| 867 | 874 | const leftover = d.rest(); |
| 868 | var client: Client = .{ | |
| 875 | client.* = .{ | |
| 876 | .input = input, | |
| 877 | .output = output, | |
| 878 | .reader = undefined, | |
| 869 | 879 | .tls_version = tls_version, |
| 870 | 880 | .read_seq = switch (tls_version) { |
| 871 | 881 | .tls_1_3 => 0, |
| ... | ... | @@ -883,7 +893,6 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 883 | 893 | .received_close_notify = false, |
| 884 | 894 | .allow_truncation_attacks = false, |
| 885 | 895 | .application_cipher = app_cipher, |
| 886 | .output = output, | |
| 887 | 896 | .partially_read_buffer = undefined, |
| 888 | 897 | .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{ |
| 889 | 898 | .client_key_seq = key_seq, |
| ... | ... | @@ -893,7 +902,14 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In |
| 893 | 902 | } else null, |
| 894 | 903 | }; |
| 895 | 904 | @memcpy(client.partially_read_buffer[0..leftover.len], leftover); |
| 896 | return client; | |
| 905 | client.reader.init(.{ | |
| 906 | .context = client, | |
| 907 | .vtable = &.{ | |
| 908 | .read = reader_read, | |
| 909 | .readv = reader_readv, | |
| 910 | }, | |
| 911 | }, input.storage.buffer[0..0]); | |
| 912 | return; | |
| 897 | 913 | }, |
| 898 | 914 | else => return error.TlsUnexpectedMessage, |
| 899 | 915 | } |
| ... | ... | @@ -919,81 +935,45 @@ pub fn writer(c: *Client) std.io.Writer { |
| 919 | 935 | |
| 920 | 936 | fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { |
| 921 | 937 | const c: *Client = @alignCast(@ptrCast(context)); |
| 922 | assert(data.len > 1 or splat > 0); | |
| 923 | return writeEnd(c, data[0], false); | |
| 924 | } | |
| 925 | ||
| 926 | /// If `end` is true, then this function additionally sends a `close_notify` | |
| 927 | /// alert, which is necessary for the server to distinguish between a properly | |
| 928 | /// finished TLS session, or a truncation attack. | |
| 929 | pub fn writeAllEnd(c: *Client, bytes: []const u8, end: bool) anyerror!void { | |
| 930 | var index: usize = 0; | |
| 931 | while (index < bytes.len) { | |
| 932 | index += try c.writeEnd(bytes[index..], end); | |
| 938 | const sliced_data = if (splat == 0) data[0..data.len -| 1] else data; | |
| 939 | const output = &c.output; | |
| 940 | const ciphertext_buf = try output.writableSlice(min_buffer_len); | |
| 941 | var total_clear: usize = 0; | |
| 942 | var ciphertext_end: usize = 0; | |
| 943 | for (sliced_data) |buf| { | |
| 944 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); | |
| 945 | total_clear += prepared.cleartext_len; | |
| 946 | ciphertext_end += prepared.ciphertext_end; | |
| 947 | if (total_clear < buf.len) break; | |
| 933 | 948 | } |
| 949 | output.advance(ciphertext_end); | |
| 950 | return total_clear; | |
| 934 | 951 | } |
| 935 | 952 | |
| 936 | /// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`. | |
| 937 | /// If `end` is true, then this function additionally sends a `close_notify` alert, | |
| 938 | /// which is necessary for the server to distinguish between a properly finished | |
| 939 | /// TLS session, or a truncation attack. | |
| 940 | pub fn writeEnd(c: *Client, bytes: []const u8, end: bool) anyerror!usize { | |
| 941 | var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined; | |
| 942 | var iovecs_buf: [6][]const u8 = undefined; | |
| 943 | var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data); | |
| 944 | if (end) { | |
| 945 | prepared.iovec_end += prepareCiphertextRecord( | |
| 946 | c, | |
| 947 | iovecs_buf[prepared.iovec_end..], | |
| 948 | ciphertext_buf[prepared.ciphertext_end..], | |
| 949 | &tls.close_notify_alert, | |
| 950 | .alert, | |
| 951 | ).iovec_end; | |
| 952 | } | |
| 953 | ||
| 954 | const iovec_end = prepared.iovec_end; | |
| 955 | const overhead_len = prepared.overhead_len; | |
| 956 | ||
| 957 | // Ideally we would call writev exactly once here, however, we must ensure | |
| 958 | // that we don't return with a record partially written. | |
| 959 | var i: usize = 0; | |
| 960 | var total_amt: usize = 0; | |
| 961 | while (true) { | |
| 962 | var amt = try c.output.writev(iovecs_buf[i..iovec_end]); | |
| 963 | while (amt >= iovecs_buf[i].len) { | |
| 964 | const encrypted_amt = iovecs_buf[i].len; | |
| 965 | total_amt += encrypted_amt - overhead_len; | |
| 966 | amt -= encrypted_amt; | |
| 967 | i += 1; | |
| 968 | // Rely on the property that iovecs delineate records, meaning that | |
| 969 | // if amt equals zero here, we have fortunately found ourselves | |
| 970 | // with a short read that aligns at the record boundary. | |
| 971 | if (i >= iovec_end) return total_amt; | |
| 972 | // We also cannot return on a vector boundary if the final close_notify is | |
| 973 | // not sent; otherwise the caller would not know to retry the call. | |
| 974 | if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt; | |
| 975 | } | |
| 976 | iovecs_buf[i] = iovecs_buf[i][amt..]; | |
| 977 | } | |
| 953 | /// Sends a `close_notify` alert, which is necessary for the server to | |
| 954 | /// distinguish between a properly finished TLS session, or a truncation | |
| 955 | /// attack. | |
| 956 | pub fn end(c: *Client) anyerror!void { | |
| 957 | const output = &c.output; | |
| 958 | const ciphertext_buf = try output.writableSlice(min_buffer_len); | |
| 959 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); | |
| 960 | output.advance(prepared.cleartext_len); | |
| 961 | return prepared.ciphertext_end; | |
| 978 | 962 | } |
| 979 | 963 | |
| 980 | 964 | fn prepareCiphertextRecord( |
| 981 | 965 | c: *Client, |
| 982 | iovecs: [][]const u8, | |
| 983 | 966 | ciphertext_buf: []u8, |
| 984 | 967 | bytes: []const u8, |
| 985 | 968 | inner_content_type: tls.ContentType, |
| 986 | 969 | ) struct { |
| 987 | iovec_end: usize, | |
| 988 | 970 | ciphertext_end: usize, |
| 989 | /// How many bytes are taken up by overhead per record. | |
| 990 | overhead_len: usize, | |
| 971 | cleartext_len: usize, | |
| 991 | 972 | } { |
| 992 | 973 | // Due to the trailing inner content type byte in the ciphertext, we need |
| 993 | 974 | // an additional buffer for storing the cleartext into before encrypting. |
| 994 | 975 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; |
| 995 | 976 | var ciphertext_end: usize = 0; |
| 996 | var iovec_end: usize = 0; | |
| 997 | 977 | var bytes_i: usize = 0; |
| 998 | 978 | switch (c.application_cipher) { |
| 999 | 979 | inline else => |*p| switch (c.tls_version) { |
| ... | ... | @@ -1001,18 +981,15 @@ fn prepareCiphertextRecord( |
| 1001 | 981 | const pv = &p.tls_1_3; |
| 1002 | 982 | const P = @TypeOf(p.*); |
| 1003 | 983 | const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1; |
| 1004 | const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len; | |
| 1005 | 984 | while (true) { |
| 1006 | 985 | const encrypted_content_len: u16 = @min( |
| 1007 | 986 | bytes.len - bytes_i, |
| 1008 | 987 | tls.max_ciphertext_inner_record_len, |
| 1009 | ciphertext_buf.len -| | |
| 1010 | (close_notify_alert_reserved + overhead_len + ciphertext_end), | |
| 988 | ciphertext_buf.len -| (overhead_len + ciphertext_end), | |
| 1011 | 989 | ); |
| 1012 | 990 | if (encrypted_content_len == 0) return .{ |
| 1013 | .iovec_end = iovec_end, | |
| 1014 | 991 | .ciphertext_end = ciphertext_end, |
| 1015 | .overhead_len = overhead_len, | |
| 992 | .cleartext_len = bytes_i, | |
| 1016 | 993 | }; |
| 1017 | 994 | |
| 1018 | 995 | @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]); |
| ... | ... | @@ -1021,7 +998,6 @@ fn prepareCiphertextRecord( |
| 1021 | 998 | const ciphertext_len = encrypted_content_len + 1; |
| 1022 | 999 | const cleartext = cleartext_buf[0..ciphertext_len]; |
| 1023 | 1000 | |
| 1024 | const record_start = ciphertext_end; | |
| 1025 | 1001 | const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len]; |
| 1026 | 1002 | ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++ |
| 1027 | 1003 | int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ |
| ... | ... | @@ -1039,35 +1015,27 @@ fn prepareCiphertextRecord( |
| 1039 | 1015 | }; |
| 1040 | 1016 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key); |
| 1041 | 1017 | c.write_seq += 1; // TODO send key_update on overflow |
| 1042 | ||
| 1043 | const record = ciphertext_buf[record_start..ciphertext_end]; | |
| 1044 | iovecs[iovec_end] = record; | |
| 1045 | iovec_end += 1; | |
| 1046 | 1018 | } |
| 1047 | 1019 | }, |
| 1048 | 1020 | .tls_1_2 => { |
| 1049 | 1021 | const pv = &p.tls_1_2; |
| 1050 | 1022 | const P = @TypeOf(p.*); |
| 1051 | 1023 | const overhead_len = tls.record_header_len + P.record_iv_length + P.mac_length; |
| 1052 | const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len; | |
| 1053 | 1024 | while (true) { |
| 1054 | 1025 | const message_len: u16 = @min( |
| 1055 | 1026 | bytes.len - bytes_i, |
| 1056 | 1027 | tls.max_ciphertext_inner_record_len, |
| 1057 | ciphertext_buf.len -| | |
| 1058 | (close_notify_alert_reserved + overhead_len + ciphertext_end), | |
| 1028 | ciphertext_buf.len -| (overhead_len + ciphertext_end), | |
| 1059 | 1029 | ); |
| 1060 | 1030 | if (message_len == 0) return .{ |
| 1061 | .iovec_end = iovec_end, | |
| 1062 | 1031 | .ciphertext_end = ciphertext_end, |
| 1063 | .overhead_len = overhead_len, | |
| 1032 | .cleartext_len = bytes_i, | |
| 1064 | 1033 | }; |
| 1065 | 1034 | |
| 1066 | 1035 | @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]); |
| 1067 | 1036 | bytes_i += message_len; |
| 1068 | 1037 | const cleartext = cleartext_buf[0..message_len]; |
| 1069 | 1038 | |
| 1070 | const record_start = ciphertext_end; | |
| 1071 | 1039 | const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len]; |
| 1072 | 1040 | ciphertext_end += tls.record_header_len; |
| 1073 | 1041 | record_header.* = .{@intFromEnum(inner_content_type)} ++ |
| ... | ... | @@ -1089,10 +1057,6 @@ fn prepareCiphertextRecord( |
| 1089 | 1057 | ciphertext_end += P.mac_length; |
| 1090 | 1058 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key); |
| 1091 | 1059 | c.write_seq += 1; // TODO send key_update on overflow |
| 1092 | ||
| 1093 | const record = ciphertext_buf[record_start..ciphertext_end]; | |
| 1094 | iovecs[iovec_end] = record; | |
| 1095 | iovec_end += 1; | |
| 1096 | 1060 | } |
| 1097 | 1061 | }, |
| 1098 | 1062 | else => unreachable, |
| ... | ... | @@ -1106,74 +1070,22 @@ pub fn eof(c: Client) bool { |
| 1106 | 1070 | c.partial_ciphertext_idx >= c.partial_ciphertext_end; |
| 1107 | 1071 | } |
| 1108 | 1072 | |
| 1109 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1110 | /// Returns the number of bytes read, calling the underlying read function the | |
| 1111 | /// minimal number of times until the buffer has at least `len` bytes filled. | |
| 1112 | /// If the number read is less than `len` it means the stream reached the end. | |
| 1113 | /// Reaching the end of the stream is not an error condition. | |
| 1114 | pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { | |
| 1115 | var iovecs = [1]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }}; | |
| 1116 | return readvAtLeast(c, stream, &iovecs, len); | |
| 1073 | fn reader_read( | |
| 1074 | context: ?*anyopaque, | |
| 1075 | bw: *std.io.BufferedWriter, | |
| 1076 | limit: std.io.Reader.Limit, | |
| 1077 | ) anyerror!std.io.Reader.Status { | |
| 1078 | const buf = limit.slice(try bw.writableSlice(1)); | |
| 1079 | const status = try reader_readv(context, &.{buf}); | |
| 1080 | bw.advance(status.len); | |
| 1081 | return status; | |
| 1117 | 1082 | } |
| 1118 | 1083 | |
| 1119 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1120 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { | |
| 1121 | return readAtLeast(c, stream, buffer, 1); | |
| 1122 | } | |
| 1123 | ||
| 1124 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1125 | /// Returns the number of bytes read. If the number read is smaller than | |
| 1126 | /// `buffer.len`, it means the stream reached the end. Reaching the end of the | |
| 1127 | /// stream is not an error condition. | |
| 1128 | pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { | |
| 1129 | return readAtLeast(c, stream, buffer, buffer.len); | |
| 1130 | } | |
| 1084 | fn reader_readv(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status { | |
| 1085 | const c: *Client = @ptrCast(@alignCast(context)); | |
| 1086 | if (c.eof()) return .{ .end = true }; | |
| 1131 | 1087 | |
| 1132 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1133 | /// Returns the number of bytes read. If the number read is less than the space | |
| 1134 | /// provided it means the stream reached the end. Reaching the end of the | |
| 1135 | /// stream is not an error condition. | |
| 1136 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | |
| 1137 | /// order to handle partial reads from the underlying stream layer. | |
| 1138 | pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize { | |
| 1139 | return readvAtLeast(c, stream, iovecs, 1); | |
| 1140 | } | |
| 1141 | ||
| 1142 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1143 | /// Returns the number of bytes read, calling the underlying read function the | |
| 1144 | /// minimal number of times until the iovecs have at least `len` bytes filled. | |
| 1145 | /// If the number read is less than `len` it means the stream reached the end. | |
| 1146 | /// Reaching the end of the stream is not an error condition. | |
| 1147 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | |
| 1148 | /// order to handle partial reads from the underlying stream layer. | |
| 1149 | pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize { | |
| 1150 | if (c.eof()) return 0; | |
| 1151 | ||
| 1152 | var off_i: usize = 0; | |
| 1153 | var vec_i: usize = 0; | |
| 1154 | while (true) { | |
| 1155 | var amt = try c.readvAdvanced(stream, iovecs[vec_i..]); | |
| 1156 | off_i += amt; | |
| 1157 | if (c.eof() or off_i >= len) return off_i; | |
| 1158 | while (amt >= iovecs[vec_i].len) { | |
| 1159 | amt -= iovecs[vec_i].len; | |
| 1160 | vec_i += 1; | |
| 1161 | } | |
| 1162 | iovecs[vec_i].base += amt; | |
| 1163 | iovecs[vec_i].len -= amt; | |
| 1164 | } | |
| 1165 | } | |
| 1166 | ||
| 1167 | /// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`. | |
| 1168 | /// Returns number of bytes that have been read, populated inside `iovecs`. A | |
| 1169 | /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` | |
| 1170 | /// for the end of stream. The `eof()` may be true after any call to | |
| 1171 | /// `read`, including when greater than zero bytes are returned, and this | |
| 1172 | /// function asserts that `eof()` is `false`. | |
| 1173 | /// See `readv` for a higher level function that has the same, familiar API as | |
| 1174 | /// other read functions, such as `std.fs.File.read`. | |
| 1175 | pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize { | |
| 1176 | var vp: VecPut = .{ .iovecs = iovecs }; | |
| 1088 | var vp: VecPut = .{ .iovecs = data }; | |
| 1177 | 1089 | |
| 1178 | 1090 | // Give away the buffered cleartext we have, if any. |
| 1179 | 1091 | const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx]; |
| ... | ... | @@ -1193,11 +1105,11 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove |
| 1193 | 1105 | if (c.received_close_notify) { |
| 1194 | 1106 | c.partial_ciphertext_end = 0; |
| 1195 | 1107 | assert(vp.total == amt); |
| 1196 | return amt; | |
| 1108 | return .{ .len = amt, .end = c.eof() }; | |
| 1197 | 1109 | } else if (amt > 0) { |
| 1198 | 1110 | // We don't need more data, so don't call read. |
| 1199 | 1111 | assert(vp.total == amt); |
| 1200 | return amt; | |
| 1112 | return .{ .len = amt, .end = c.eof() }; | |
| 1201 | 1113 | } |
| 1202 | 1114 | } |
| 1203 | 1115 | |
| ... | ... | @@ -1241,7 +1153,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove |
| 1241 | 1153 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len); |
| 1242 | 1154 | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end; |
| 1243 | 1155 | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); |
| 1244 | const actual_read_len = try stream.readv(ask_iovecs); | |
| 1156 | const actual_read_len = try c.input.readv(ask_iovecs); | |
| 1245 | 1157 | if (actual_read_len == 0) { |
| 1246 | 1158 | // This is either a truncation attack, a bug in the server, or an |
| 1247 | 1159 | // intentional omission of the close_notify message due to truncation |
| ... | ... | @@ -1268,7 +1180,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove |
| 1268 | 1180 | // Perfect split. |
| 1269 | 1181 | if (frag.ptr == frag1.ptr) { |
| 1270 | 1182 | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| 1271 | return vp.total; | |
| 1183 | return .{ .len = vp.total, .end = c.eof() }; | |
| 1272 | 1184 | } |
| 1273 | 1185 | frag = frag1; |
| 1274 | 1186 | in = 0; |
| ... | ... | @@ -1310,8 +1222,8 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove |
| 1310 | 1222 | const record_len = mem.readInt(u16, frag[in..][0..2], .big); |
| 1311 | 1223 | if (record_len > max_ciphertext_len) return error.TlsRecordOverflow; |
| 1312 | 1224 | in += 2; |
| 1313 | const end = in + record_len; | |
| 1314 | if (end > frag.len) { | |
| 1225 | const the_end = in + record_len; | |
| 1226 | if (the_end > frag.len) { | |
| 1315 | 1227 | // We need the record header on the next iteration of the loop. |
| 1316 | 1228 | in -= tls.record_header_len; |
| 1317 | 1229 | |
| ... | ... | @@ -1398,17 +1310,23 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove |
| 1398 | 1310 | .alert => { |
| 1399 | 1311 | if (cleartext.len != 2) return error.TlsDecodeError; |
| 1400 | 1312 | const level: tls.AlertLevel = @enumFromInt(cleartext[0]); |
| 1313 | _ = level; | |
| 1401 | 1314 | const desc: tls.AlertDescription = @enumFromInt(cleartext[1]); |
| 1402 | if (desc == .close_notify) { | |
| 1403 | c.received_close_notify = true; | |
| 1404 | c.partial_ciphertext_end = c.partial_ciphertext_idx; | |
| 1405 | return vp.total; | |
| 1315 | switch (desc) { | |
| 1316 | .close_notify => { | |
| 1317 | c.received_close_notify = true; | |
| 1318 | c.partial_ciphertext_end = c.partial_ciphertext_idx; | |
| 1319 | return .{ .len = vp.total, .end = c.eof() }; | |
| 1320 | }, | |
| 1321 | .user_canceled => { | |
| 1322 | // TODO: handle server-side closures | |
| 1323 | return error.TlsUnexpectedMessage; | |
| 1324 | }, | |
| 1325 | else => { | |
| 1326 | c.diagnostics = .{ .alert = desc }; | |
| 1327 | return error.TlsAlert; | |
| 1328 | }, | |
| 1406 | 1329 | } |
| 1407 | _ = level; | |
| 1408 | ||
| 1409 | try desc.toError(); | |
| 1410 | // TODO: handle server-side closures | |
| 1411 | return error.TlsUnexpectedMessage; | |
| 1412 | 1330 | }, |
| 1413 | 1331 | .handshake => { |
| 1414 | 1332 | var ct_i: usize = 0; |
| ... | ... | @@ -1524,7 +1442,7 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi |
| 1524 | 1442 | }) catch {}; |
| 1525 | 1443 | } |
| 1526 | 1444 | |
| 1527 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { | |
| 1445 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) std.io.Reader.Status { | |
| 1528 | 1446 | const saved_buf = frag[in..]; |
| 1529 | 1447 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1530 | 1448 | // There is cleartext at the beginning already which we need to preserve. |
| ... | ... | @@ -1536,11 +1454,11 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { |
| 1536 | 1454 | c.partial_ciphertext_end = @intCast(saved_buf.len); |
| 1537 | 1455 | @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf); |
| 1538 | 1456 | } |
| 1539 | return out; | |
| 1457 | return .{ .len = out, .end = c.eof() }; | |
| 1540 | 1458 | } |
| 1541 | 1459 | |
| 1542 | 1460 | /// Note that `first` usually overlaps with `c.partially_read_buffer`. |
| 1543 | fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize { | |
| 1461 | fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) std.io.Reader.Status { | |
| 1544 | 1462 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1545 | 1463 | // There is cleartext at the beginning already which we need to preserve. |
| 1546 | 1464 | c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len); |
| ... | ... | @@ -1555,7 +1473,7 @@ fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usi |
| 1555 | 1473 | std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first); |
| 1556 | 1474 | @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1); |
| 1557 | 1475 | } |
| 1558 | return out; | |
| 1476 | return .{ .len = out, .end = c.eof() }; | |
| 1559 | 1477 | } |
| 1560 | 1478 | |
| 1561 | 1479 | fn limitedOverlapCopy(frag: []u8, in: usize) void { |
| ... | ... | @@ -1577,9 +1495,6 @@ fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 { |
| 1577 | 1495 | } |
| 1578 | 1496 | } |
| 1579 | 1497 | |
| 1580 | const builtin = @import("builtin"); | |
| 1581 | const native_endian = builtin.cpu.arch.endian(); | |
| 1582 | ||
| 1583 | 1498 | inline fn big(x: anytype) @TypeOf(x) { |
| 1584 | 1499 | return switch (native_endian) { |
| 1585 | 1500 | .big => x, |
| ... | ... | @@ -1958,7 +1873,3 @@ else |
| 1958 | 1873 | .AES_256_GCM_SHA384, |
| 1959 | 1874 | .ECDHE_RSA_WITH_AES_256_GCM_SHA384, |
| 1960 | 1875 | }); |
| 1961 | ||
| 1962 | test { | |
| 1963 | _ = StreamInterface; | |
| 1964 | } |
lib/std/http/Client.zig+243-225| ... | ... | @@ -24,6 +24,12 @@ allocator: Allocator, |
| 24 | 24 | |
| 25 | 25 | ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{}, |
| 26 | 26 | ca_bundle_mutex: std.Thread.Mutex = .{}, |
| 27 | /// Used both for the reader and writer buffers. | |
| 28 | tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len, | |
| 29 | /// If non-null, ssl secrets are logged to a stream. Creating such a stream | |
| 30 | /// allows other processes with access to that stream to decrypt all | |
| 31 | /// traffic over connections created with this `Client`. | |
| 32 | ssl_key_logger: ?*std.io.BufferedWriter = null, | |
| 27 | 33 | |
| 28 | 34 | /// When this is `true`, the next time this client performs an HTTPS request, |
| 29 | 35 | /// it will first rescan the system for root certificates. |
| ... | ... | @@ -31,6 +37,10 @@ next_https_rescan_certs: bool = true, |
| 31 | 37 | |
| 32 | 38 | /// The pool of connections that can be reused (and currently in use). |
| 33 | 39 | connection_pool: ConnectionPool = .{}, |
| 40 | /// Each `Connection` allocates this amount for the reader buffer. | |
| 41 | read_buffer_size: usize, | |
| 42 | /// Each `Connection` allocates this amount for the writer buffer. | |
| 43 | write_buffer_size: usize, | |
| 34 | 44 | |
| 35 | 45 | /// If populated, all http traffic travels through this third party. |
| 36 | 46 | /// This field cannot be modified while the client has active connections. |
| ... | ... | @@ -41,7 +51,7 @@ http_proxy: ?*Proxy = null, |
| 41 | 51 | /// Pointer to externally-owned memory. |
| 42 | 52 | https_proxy: ?*Proxy = null, |
| 43 | 53 | |
| 44 | /// A set of linked lists of connections that can be reused. | |
| 54 | /// A Least-Recently-Used cache of open connections to be reused. | |
| 45 | 55 | pub const ConnectionPool = struct { |
| 46 | 56 | mutex: std.Thread.Mutex = .{}, |
| 47 | 57 | /// Open connections that are currently in use. |
| ... | ... | @@ -58,8 +68,10 @@ pub const ConnectionPool = struct { |
| 58 | 68 | protocol: Connection.Protocol, |
| 59 | 69 | }; |
| 60 | 70 | |
| 61 | /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe. | |
| 71 | /// Finds and acquires a connection from the connection pool matching the criteria. | |
| 62 | 72 | /// If no connection is found, null is returned. |
| 73 | /// | |
| 74 | /// Threadsafe. | |
| 63 | 75 | pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection { |
| 64 | 76 | pool.mutex.lock(); |
| 65 | 77 | defer pool.mutex.unlock(); |
| ... | ... | @@ -96,21 +108,21 @@ pub const ConnectionPool = struct { |
| 96 | 108 | return pool.acquireUnsafe(connection); |
| 97 | 109 | } |
| 98 | 110 | |
| 99 | /// Tries to release a connection back to the connection pool. This function is threadsafe. | |
| 111 | /// Tries to release a connection back to the connection pool. | |
| 100 | 112 | /// If the connection is marked as closing, it will be closed instead. |
| 101 | 113 | /// |
| 102 | /// The allocator must be the owner of all nodes in this pool. | |
| 103 | /// The allocator must be the owner of all resources associated with the connection. | |
| 114 | /// `allocator` must be the same one used to create `connection`. | |
| 115 | /// | |
| 116 | /// Threadsafe. | |
| 104 | 117 | pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void { |
| 118 | if (connection.closing) return connection.destroy(allocator); | |
| 119 | ||
| 105 | 120 | pool.mutex.lock(); |
| 106 | 121 | defer pool.mutex.unlock(); |
| 107 | 122 | |
| 108 | 123 | pool.used.remove(&connection.pool_node); |
| 109 | 124 | |
| 110 | if (connection.closing or pool.free_size == 0) { | |
| 111 | connection.close(allocator); | |
| 112 | return allocator.destroy(connection); | |
| 113 | } | |
| 125 | if (pool.free_size == 0) return connection.destroy(allocator); | |
| 114 | 126 | |
| 115 | 127 | if (pool.free_len >= pool.free_size) { |
| 116 | 128 | const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?); |
| ... | ... | @@ -138,9 +150,11 @@ pub const ConnectionPool = struct { |
| 138 | 150 | pool.used.append(&connection.pool_node); |
| 139 | 151 | } |
| 140 | 152 | |
| 141 | /// Resizes the connection pool. This function is threadsafe. | |
| 153 | /// Resizes the connection pool. | |
| 142 | 154 | /// |
| 143 | 155 | /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size. |
| 156 | /// | |
| 157 | /// Threadsafe. | |
| 144 | 158 | pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void { |
| 145 | 159 | pool.mutex.lock(); |
| 146 | 160 | defer pool.mutex.unlock(); |
| ... | ... | @@ -158,9 +172,11 @@ pub const ConnectionPool = struct { |
| 158 | 172 | pool.free_size = new_size; |
| 159 | 173 | } |
| 160 | 174 | |
| 161 | /// Frees the connection pool and closes all connections within. This function is threadsafe. | |
| 175 | /// Frees the connection pool and closes all connections within. | |
| 162 | 176 | /// |
| 163 | 177 | /// All future operations on the connection pool will deadlock. |
| 178 | /// | |
| 179 | /// Threadsafe. | |
| 164 | 180 | pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void { |
| 165 | 181 | pool.mutex.lock(); |
| 166 | 182 | |
| ... | ... | @@ -184,160 +200,212 @@ pub const ConnectionPool = struct { |
| 184 | 200 | } |
| 185 | 201 | }; |
| 186 | 202 | |
| 187 | /// An interface to either a plain or TLS connection. | |
| 188 | 203 | pub const Connection = struct { |
| 204 | client: *Client, | |
| 189 | 205 | stream: net.Stream, |
| 190 | /// Populated when protocol is TLS; this is the writer given to the TLS | |
| 191 | /// client, which writes directly to `stream`, unbuffered. | |
| 192 | stream_writer: std.io.BufferedWriter, | |
| 193 | /// undefined unless protocol is tls. | |
| 194 | tls_client: if (!disable_tls) *std.crypto.tls.Client else void, | |
| 195 | ||
| 206 | /// HTTP protocol from client to server. | |
| 207 | /// This either goes directly to `stream`, or to a TLS client. | |
| 208 | writer: std.io.BufferedWriter, | |
| 196 | 209 | /// Entry in `ConnectionPool.used` or `ConnectionPool.free`. |
| 197 | 210 | pool_node: std.DoublyLinkedList.Node, |
| 198 | ||
| 199 | /// The protocol that this connection is using. | |
| 200 | protocol: Protocol, | |
| 201 | ||
| 202 | /// The host that this connection is connected to. | |
| 203 | host: []u8, | |
| 204 | ||
| 205 | /// The port that this connection is connected to. | |
| 206 | 211 | port: u16, |
| 212 | host_len: u8, | |
| 213 | proxied: bool, | |
| 214 | closing: bool, | |
| 215 | protocol: Protocol, | |
| 207 | 216 | |
| 208 | /// Whether this connection is proxied and is not directly connected. | |
| 209 | proxied: bool = false, | |
| 217 | pub const Protocol = enum { plain, tls }; | |
| 210 | 218 | |
| 211 | /// Whether this connection is closing when we're done with it. | |
| 212 | closing: bool = false, | |
| 219 | const Plain = struct { | |
| 220 | /// Data from `Connection.stream`. | |
| 221 | reader: std.io.BufferedReader, | |
| 222 | connection: Connection, | |
| 223 | ||
| 224 | fn create( | |
| 225 | client: *Client, | |
| 226 | remote_host: []const u8, | |
| 227 | port: u16, | |
| 228 | stream: net.Stream, | |
| 229 | ) error{OutOfMemory}!*Connection { | |
| 230 | const gpa = client.allocator; | |
| 231 | const alloc_len = allocLen(client, remote_host.len); | |
| 232 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); | |
| 233 | errdefer gpa.free(base); | |
| 234 | const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len]; | |
| 235 | const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size]; | |
| 236 | const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size]; | |
| 237 | assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len); | |
| 238 | @memcpy(host_buffer, remote_host); | |
| 239 | const plain: *Plain = @ptrCast(base); | |
| 240 | plain.* = .{ | |
| 241 | .connection = .{ | |
| 242 | .client = client, | |
| 243 | .stream = stream, | |
| 244 | .writer = stream.writer().buffered(socket_write_buffer), | |
| 245 | .pool_node = .{}, | |
| 246 | .port = port, | |
| 247 | .proxied = false, | |
| 248 | .closing = false, | |
| 249 | .protocol = .plain, | |
| 250 | }, | |
| 251 | .reader = undefined, | |
| 252 | }; | |
| 253 | plain.reader.init(stream.reader(), socket_read_buffer); | |
| 254 | } | |
| 213 | 255 | |
| 214 | read_start: BufferSize = 0, | |
| 215 | read_end: BufferSize = 0, | |
| 216 | read_buf: [buffer_size]u8, | |
| 256 | fn destroy(plain: *Plain) void { | |
| 257 | const c = &plain.connection; | |
| 258 | const gpa = c.client.allocator; | |
| 259 | const base: [*]u8 = @ptrCast(plain); | |
| 260 | gpa.free(base[0..allocLen(c.client, c.host_len)]); | |
| 261 | } | |
| 217 | 262 | |
| 218 | write_buffer: [buffer_size]u8, | |
| 219 | writer: std.io.BufferedWriter, | |
| 263 | fn allocLen(client: *Client, host_len: usize) usize { | |
| 264 | return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size; | |
| 265 | } | |
| 220 | 266 | |
| 221 | pub const buffer_size = std.crypto.tls.max_ciphertext_record_len; | |
| 222 | const BufferSize = std.math.IntFittingRange(0, buffer_size); | |
| 267 | fn host(plain: *Plain) []u8 { | |
| 268 | const base: [*]u8 = @ptrCast(plain); | |
| 269 | return base[@sizeOf(Plain)..][0..plain.connection.host_len]; | |
| 270 | } | |
| 271 | }; | |
| 223 | 272 | |
| 224 | pub const Protocol = enum { plain, tls }; | |
| 273 | const Tls = struct { | |
| 274 | /// Data from `client` to `Connection.stream`. | |
| 275 | writer: std.io.BufferedWriter, | |
| 276 | /// Data from `Connection.stream` to `client`. | |
| 277 | reader: std.io.BufferedReader, | |
| 278 | client: std.crypto.tls.Client, | |
| 279 | connection: Connection, | |
| 280 | ||
| 281 | fn create( | |
| 282 | client: *Client, | |
| 283 | remote_host: []const u8, | |
| 284 | port: u16, | |
| 285 | stream: net.Stream, | |
| 286 | ) error{ OutOfMemory, TlsInitializationFailed }!*Tls { | |
| 287 | const gpa = client.allocator; | |
| 288 | const alloc_len = allocLen(client, remote_host.len); | |
| 289 | const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len); | |
| 290 | errdefer gpa.free(base); | |
| 291 | const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len]; | |
| 292 | const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size]; | |
| 293 | const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size]; | |
| 294 | const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; | |
| 295 | assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len); | |
| 296 | @memcpy(host_buffer, remote_host); | |
| 297 | const tls: *Tls = @ptrCast(base); | |
| 298 | tls.* = .{ | |
| 299 | .connection = .{ | |
| 300 | .client = client, | |
| 301 | .stream = stream, | |
| 302 | .writer = tls.client.writer().buffered(socket_write_buffer), | |
| 303 | .pool_node = .{}, | |
| 304 | .port = port, | |
| 305 | .proxied = false, | |
| 306 | .closing = false, | |
| 307 | .protocol = .tls, | |
| 308 | }, | |
| 309 | .writer = stream.writer().buffered(tls_write_buffer), | |
| 310 | .reader = undefined, | |
| 311 | .client = undefined, | |
| 312 | }; | |
| 313 | tls.reader.init(stream.reader(), tls_read_buffer); | |
| 314 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true | |
| 315 | tls.client.init(&tls.reader, &tls.writer, .{ | |
| 316 | .host = .{ .explicit = remote_host }, | |
| 317 | .ca = .{ .bundle = client.ca_bundle }, | |
| 318 | .ssl_key_logger = client.ssl_key_logger, | |
| 319 | }) catch return error.TlsInitializationFailed; | |
| 320 | // This is appropriate for HTTPS because the HTTP headers contain | |
| 321 | // the content length which is used to detect truncation attacks. | |
| 322 | tls.client.allow_truncation_attacks = true; | |
| 225 | 323 | |
| 226 | pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { | |
| 227 | return conn.tls_client.readv(conn.stream, buffers) catch |err| { | |
| 228 | // https://github.com/ziglang/zig/issues/2473 | |
| 229 | if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; | |
| 324 | return tls; | |
| 325 | } | |
| 230 | 326 | |
| 231 | switch (err) { | |
| 232 | error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure, | |
| 233 | error.ConnectionTimedOut => return error.ConnectionTimedOut, | |
| 234 | error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer, | |
| 235 | else => return error.UnexpectedReadFailure, | |
| 236 | } | |
| 237 | }; | |
| 238 | } | |
| 327 | fn destroy(tls: *Tls, gpa: Allocator) void { | |
| 328 | const c = &tls.connection; | |
| 329 | const base: [*]u8 = @ptrCast(tls); | |
| 330 | gpa.free(base[0..allocLen(c.client, c.host_len)]); | |
| 331 | } | |
| 239 | 332 | |
| 240 | pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { | |
| 241 | if (conn.protocol == .tls) { | |
| 242 | if (disable_tls) unreachable; | |
| 333 | fn allocLen(client: *Client, host_len: usize) usize { | |
| 334 | return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size; | |
| 335 | } | |
| 243 | 336 | |
| 244 | return conn.readvDirectTls(buffers); | |
| 337 | fn host(tls: *Tls) []u8 { | |
| 338 | const base: [*]u8 = @ptrCast(tls); | |
| 339 | return base[@sizeOf(Tls)..][0..tls.connection.host_len]; | |
| 245 | 340 | } |
| 341 | }; | |
| 246 | 342 | |
| 247 | return conn.stream.readv(buffers) catch |err| switch (err) { | |
| 248 | error.ConnectionTimedOut => return error.ConnectionTimedOut, | |
| 249 | error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer, | |
| 250 | else => return error.UnexpectedReadFailure, | |
| 343 | fn host(c: *Connection) []u8 { | |
| 344 | return switch (c.protocol) { | |
| 345 | .tls => { | |
| 346 | if (disable_tls) unreachable; | |
| 347 | const tls: *Tls = @fieldParentPtr("connection", c); | |
| 348 | return tls.host(); | |
| 349 | }, | |
| 350 | .plain => { | |
| 351 | const plain: *Plain = @fieldParentPtr("connection", c); | |
| 352 | return plain.host(); | |
| 353 | }, | |
| 251 | 354 | }; |
| 252 | 355 | } |
| 253 | 356 | |
| 254 | /// Refills the read buffer with data from the connection. | |
| 255 | pub fn fill(conn: *Connection) ReadError!void { | |
| 256 | if (conn.read_end != conn.read_start) return; | |
| 257 | ||
| 258 | var iovecs = [1]std.posix.iovec{ | |
| 259 | .{ .base = &conn.read_buf, .len = conn.read_buf.len }, | |
| 357 | /// This is either data from `stream`, or `Tls.client`. | |
| 358 | fn reader(c: *Connection) *std.io.BufferedReader { | |
| 359 | return switch (c.protocol) { | |
| 360 | .tls => { | |
| 361 | if (disable_tls) unreachable; | |
| 362 | const tls: *Tls = @fieldParentPtr("connection", c); | |
| 363 | return &tls.client.reader; | |
| 364 | }, | |
| 365 | .plain => { | |
| 366 | const plain: *Plain = @fieldParentPtr("connection", c); | |
| 367 | return &plain.reader; | |
| 368 | }, | |
| 260 | 369 | }; |
| 261 | const nread = try conn.readvDirect(&iovecs); | |
| 262 | if (nread == 0) return error.EndOfStream; | |
| 263 | conn.read_start = 0; | |
| 264 | conn.read_end = @intCast(nread); | |
| 265 | 370 | } |
| 266 | 371 | |
| 267 | /// Returns the current slice of buffered data. | |
| 268 | pub fn peek(conn: *Connection) []const u8 { | |
| 269 | return conn.read_buf[conn.read_start..conn.read_end]; | |
| 270 | } | |
| 271 | ||
| 272 | /// Discards the given number of bytes from the read buffer. | |
| 273 | pub fn drop(conn: *Connection, num: BufferSize) void { | |
| 274 | conn.read_start += num; | |
| 275 | } | |
| 276 | ||
| 277 | /// Reads data from the connection into the given buffer. | |
| 278 | pub fn read(conn: *Connection, buffer: []u8) ReadError!usize { | |
| 279 | const available_read = conn.read_end - conn.read_start; | |
| 280 | const available_buffer = buffer.len; | |
| 281 | ||
| 282 | if (available_read > available_buffer) { // partially read buffered data | |
| 283 | @memcpy(buffer[0..available_buffer], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]); | |
| 284 | conn.read_start += @intCast(available_buffer); | |
| 285 | ||
| 286 | return available_buffer; | |
| 287 | } else if (available_read > 0) { // fully read buffered data | |
| 288 | @memcpy(buffer[0..available_read], conn.read_buf[conn.read_start..conn.read_end]); | |
| 289 | conn.read_start += available_read; | |
| 290 | ||
| 291 | return available_read; | |
| 292 | } | |
| 293 | ||
| 294 | var iovecs = [2]std.posix.iovec{ | |
| 295 | .{ .base = buffer.ptr, .len = buffer.len }, | |
| 296 | .{ .base = &conn.read_buf, .len = conn.read_buf.len }, | |
| 297 | }; | |
| 298 | const nread = try conn.readvDirect(&iovecs); | |
| 299 | ||
| 300 | if (nread > buffer.len) { | |
| 301 | conn.read_start = 0; | |
| 302 | conn.read_end = @intCast(nread - buffer.len); | |
| 303 | return buffer.len; | |
| 372 | /// If this is called without calling `flush` or `end`, data will be | |
| 373 | /// dropped unsent. | |
| 374 | pub fn destroy(c: *Connection) void { | |
| 375 | c.stream.close(); | |
| 376 | switch (c.protocol) { | |
| 377 | .tls => { | |
| 378 | if (disable_tls) unreachable; | |
| 379 | const tls: *Tls = @fieldParentPtr("connection", c); | |
| 380 | tls.destroy(); | |
| 381 | }, | |
| 382 | .plain => { | |
| 383 | const plain: *Plain = @fieldParentPtr("connection", c); | |
| 384 | plain.destroy(); | |
| 385 | }, | |
| 304 | 386 | } |
| 305 | ||
| 306 | return nread; | |
| 307 | 387 | } |
| 308 | 388 | |
| 309 | pub const ReadError = error{ | |
| 310 | TlsFailure, | |
| 311 | TlsAlert, | |
| 312 | ConnectionTimedOut, | |
| 313 | ConnectionResetByPeer, | |
| 314 | UnexpectedReadFailure, | |
| 315 | EndOfStream, | |
| 316 | }; | |
| 317 | ||
| 318 | pub const Reader = std.io.Reader(*Connection, ReadError, read); | |
| 319 | ||
| 320 | pub fn reader(conn: *Connection) Reader { | |
| 321 | return .{ .context = conn }; | |
| 389 | pub fn flush(c: *Connection) anyerror!void { | |
| 390 | try c.writer.flush(); | |
| 391 | if (c.protocol == .tls) { | |
| 392 | if (disable_tls) unreachable; | |
| 393 | const tls: *Tls = @fieldParentPtr("connection", c); | |
| 394 | try tls.writer.flush(); | |
| 395 | } | |
| 322 | 396 | } |
| 323 | 397 | |
| 324 | pub const WriteError = error{ | |
| 325 | ConnectionResetByPeer, | |
| 326 | UnexpectedWriteFailure, | |
| 327 | }; | |
| 328 | ||
| 329 | pub fn close(conn: *Connection, allocator: Allocator) void { | |
| 330 | if (conn.protocol == .tls) { | |
| 398 | /// If the connection is a TLS connection, sends the close_notify alert. | |
| 399 | /// | |
| 400 | /// Flushes all buffers. | |
| 401 | pub fn end(c: *Connection) anyerror!void { | |
| 402 | try c.writer.flush(); | |
| 403 | if (c.protocol == .tls) { | |
| 331 | 404 | if (disable_tls) unreachable; |
| 332 | ||
| 333 | // try to cleanly close the TLS connection, for any server that cares. | |
| 334 | _ = conn.tls_client.writeEnd("", true) catch {}; | |
| 335 | if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close(); | |
| 336 | allocator.destroy(conn.tls_client); | |
| 405 | const tls: *Tls = @fieldParentPtr("connection", c); | |
| 406 | try tls.client.end(); | |
| 407 | try tls.writer.flush(); | |
| 337 | 408 | } |
| 338 | ||
| 339 | conn.stream.close(); | |
| 340 | allocator.free(conn.host); | |
| 341 | 409 | } |
| 342 | 410 | }; |
| 343 | 411 | |
| ... | ... | @@ -350,10 +418,10 @@ pub const RequestTransfer = union(enum) { |
| 350 | 418 | |
| 351 | 419 | /// The decompressor for response messages. |
| 352 | 420 | pub const Compression = union(enum) { |
| 353 | pub const DeflateDecompressor = std.compress.zlib.Decompressor(Request.TransferReader); | |
| 354 | pub const GzipDecompressor = std.compress.gzip.Decompressor(Request.TransferReader); | |
| 421 | pub const DeflateDecompressor = std.compress.zlib.Decompressor; | |
| 422 | pub const GzipDecompressor = std.compress.gzip.Decompressor; | |
| 355 | 423 | // https://github.com/ziglang/zig/issues/18937 |
| 356 | //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Request.TransferReader, .{}); | |
| 424 | //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(.{}); | |
| 357 | 425 | |
| 358 | 426 | deflate: DeflateDecompressor, |
| 359 | 427 | gzip: GzipDecompressor, |
| ... | ... | @@ -617,9 +685,6 @@ pub const Response = struct { |
| 617 | 685 | } |
| 618 | 686 | }; |
| 619 | 687 | |
| 620 | /// A HTTP request that has been sent. | |
| 621 | /// | |
| 622 | /// Order of operations: open -> send[ -> write -> finish] -> wait -> read | |
| 623 | 688 | pub const Request = struct { |
| 624 | 689 | uri: Uri, |
| 625 | 690 | client: *Client, |
| ... | ... | @@ -1300,24 +1365,34 @@ pub const basic_authorization = struct { |
| 1300 | 1365 | } |
| 1301 | 1366 | }; |
| 1302 | 1367 | |
| 1303 | pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; | |
| 1368 | pub const ConnectTcpError = Allocator.Error || error{ | |
| 1369 | ConnectionRefused, | |
| 1370 | NetworkUnreachable, | |
| 1371 | ConnectionTimedOut, | |
| 1372 | ConnectionResetByPeer, | |
| 1373 | TemporaryNameServerFailure, | |
| 1374 | NameServerFailure, | |
| 1375 | UnknownHostName, | |
| 1376 | HostLacksNetworkAddresses, | |
| 1377 | UnexpectedConnectFailure, | |
| 1378 | TlsInitializationFailed, | |
| 1379 | }; | |
| 1304 | 1380 | |
| 1305 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. | |
| 1381 | /// Reuses a `Connection` if one matching `host` and `port` is already open. | |
| 1306 | 1382 | /// |
| 1307 | /// This function is threadsafe. | |
| 1308 | pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection { | |
| 1383 | /// Threadsafe. | |
| 1384 | pub fn connectTcp( | |
| 1385 | client: *Client, | |
| 1386 | host: []const u8, | |
| 1387 | port: u16, | |
| 1388 | protocol: Connection.Protocol, | |
| 1389 | ) ConnectTcpError!*Connection { | |
| 1309 | 1390 | if (client.connection_pool.findConnection(.{ |
| 1310 | 1391 | .host = host, |
| 1311 | 1392 | .port = port, |
| 1312 | 1393 | .protocol = protocol, |
| 1313 | 1394 | })) |conn| return conn; |
| 1314 | 1395 | |
| 1315 | if (disable_tls and protocol == .tls) | |
| 1316 | return error.TlsInitializationFailed; | |
| 1317 | ||
| 1318 | const conn = try client.allocator.create(Connection); | |
| 1319 | errdefer client.allocator.destroy(conn); | |
| 1320 | ||
| 1321 | 1396 | const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) { |
| 1322 | 1397 | error.ConnectionRefused => return error.ConnectionRefused, |
| 1323 | 1398 | error.NetworkUnreachable => return error.NetworkUnreachable, |
| ... | ... | @@ -1331,77 +1406,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec |
| 1331 | 1406 | }; |
| 1332 | 1407 | errdefer stream.close(); |
| 1333 | 1408 | |
| 1334 | conn.* = .{ | |
| 1335 | .stream = stream, | |
| 1336 | .stream_writer = undefined, | |
| 1337 | .tls_client = undefined, | |
| 1338 | .read_buf = undefined, | |
| 1339 | ||
| 1340 | .write_buffer = undefined, | |
| 1341 | .writer = undefined, // populated below | |
| 1342 | ||
| 1343 | .protocol = protocol, | |
| 1344 | .host = try client.allocator.dupe(u8, host), | |
| 1345 | .port = port, | |
| 1346 | ||
| 1347 | .pool_node = .{}, | |
| 1348 | }; | |
| 1349 | errdefer client.allocator.free(conn.host); | |
| 1350 | ||
| 1351 | 1409 | switch (protocol) { |
| 1352 | 1410 | .tls => { |
| 1353 | if (disable_tls) unreachable; | |
| 1354 | ||
| 1355 | const tls_client = try client.allocator.create(std.crypto.tls.Client); | |
| 1356 | errdefer client.allocator.destroy(tls_client); | |
| 1357 | ||
| 1358 | const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: { | |
| 1359 | const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) { | |
| 1360 | error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null, | |
| 1361 | error.OutOfMemory => return error.OutOfMemory, | |
| 1362 | }; | |
| 1363 | defer client.allocator.free(ssl_key_log_path); | |
| 1364 | break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{ | |
| 1365 | .truncate = false, | |
| 1366 | .mode = switch (builtin.os.tag) { | |
| 1367 | .windows, .wasi => 0, | |
| 1368 | else => 0o600, | |
| 1369 | }, | |
| 1370 | }) catch null; | |
| 1371 | } else null; | |
| 1372 | errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close(); | |
| 1373 | ||
| 1374 | conn.stream_writer = .{ | |
| 1375 | .unbuffered_writer = stream.writer(), | |
| 1376 | .buffer = &.{}, | |
| 1377 | }; | |
| 1378 | ||
| 1379 | tls_client.* = std.crypto.tls.Client.init(stream, &conn.stream_writer, .{ | |
| 1380 | .host = .{ .explicit = host }, | |
| 1381 | .ca = .{ .bundle = client.ca_bundle }, | |
| 1382 | .ssl_key_log_file = ssl_key_log_file, | |
| 1383 | }) catch return error.TlsInitializationFailed; | |
| 1384 | // This is appropriate for HTTPS because the HTTP headers contain | |
| 1385 | // the content length which is used to detect truncation attacks. | |
| 1386 | tls_client.allow_truncation_attacks = true; | |
| 1387 | ||
| 1388 | conn.writer = .{ | |
| 1389 | .unbuffered_writer = tls_client.writer(), | |
| 1390 | .buffer = &conn.write_buffer, | |
| 1391 | }; | |
| 1392 | conn.tls_client = tls_client; | |
| 1411 | if (disable_tls) return error.TlsInitializationFailed; | |
| 1412 | const tc = try Connection.Tls.create(client, host, port, stream); | |
| 1413 | client.connection_pool.addUsed(&tc.connection); | |
| 1414 | return &tc.connection; | |
| 1393 | 1415 | }, |
| 1394 | 1416 | .plain => { |
| 1395 | conn.writer = .{ | |
| 1396 | .unbuffered_writer = stream.writer(), | |
| 1397 | .buffer = &conn.write_buffer, | |
| 1398 | }; | |
| 1417 | const pc = try Connection.Plain.create(client, host, port, stream); | |
| 1418 | client.connection_pool.addUsed(&pc.connection); | |
| 1419 | return &pc.connection; | |
| 1399 | 1420 | }, |
| 1400 | 1421 | } |
| 1401 | ||
| 1402 | client.connection_pool.addUsed(conn); | |
| 1403 | ||
| 1404 | return conn; | |
| 1405 | 1422 | } |
| 1406 | 1423 | |
| 1407 | 1424 | pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError; |
| ... | ... | @@ -1662,16 +1679,17 @@ pub fn open( |
| 1662 | 1679 | var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer); |
| 1663 | 1680 | const protocol, const valid_uri = try validateUri(uri, server_header.allocator()); |
| 1664 | 1681 | |
| 1665 | if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) { | |
| 1682 | if (protocol == .tls) { | |
| 1666 | 1683 | if (disable_tls) unreachable; |
| 1667 | ||
| 1668 | client.ca_bundle_mutex.lock(); | |
| 1669 | defer client.ca_bundle_mutex.unlock(); | |
| 1670 | ||
| 1671 | if (client.next_https_rescan_certs) { | |
| 1672 | client.ca_bundle.rescan(client.allocator) catch | |
| 1673 | return error.CertificateBundleLoadFailure; | |
| 1674 | @atomicStore(bool, &client.next_https_rescan_certs, false, .release); | |
| 1684 | if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) { | |
| 1685 | client.ca_bundle_mutex.lock(); | |
| 1686 | defer client.ca_bundle_mutex.unlock(); | |
| 1687 | ||
| 1688 | if (client.next_https_rescan_certs) { | |
| 1689 | client.ca_bundle.rescan(client.allocator) catch | |
| 1690 | return error.CertificateBundleLoadFailure; | |
| 1691 | @atomicStore(bool, &client.next_https_rescan_certs, false, .release); | |
| 1692 | } | |
| 1675 | 1693 | } |
| 1676 | 1694 | } |
| 1677 | 1695 |
lib/std/http/Server.zig+52-78| ... | ... | @@ -1,18 +1,25 @@ |
| 1 | 1 | //! Blocking HTTP server implementation. |
| 2 | 2 | //! Handles a single connection's lifecycle. |
| 3 | 3 | |
| 4 | connection: net.Server.Connection, | |
| 4 | const std = @import("../std.zig"); | |
| 5 | const http = std.http; | |
| 6 | const mem = std.mem; | |
| 7 | const net = std.net; | |
| 8 | const Uri = std.Uri; | |
| 9 | const assert = std.debug.assert; | |
| 10 | const testing = std.testing; | |
| 11 | ||
| 12 | const Server = @This(); | |
| 13 | ||
| 14 | /// The reader's buffer must be large enough to store the client's entire HTTP | |
| 15 | /// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`. | |
| 16 | in: *std.io.BufferedReader, | |
| 17 | out: *std.io.BufferedWriter, | |
| 5 | 18 | /// Keeps track of whether the Server is ready to accept a new request on the |
| 6 | 19 | /// same connection, and makes invalid API usage cause assertion failures |
| 7 | 20 | /// rather than HTTP protocol violations. |
| 8 | 21 | state: State, |
| 9 | /// User-provided buffer that must outlive this Server. | |
| 10 | /// Used to store the client's entire HTTP header. | |
| 11 | read_buffer: []u8, | |
| 12 | /// Amount of available data inside read_buffer. | |
| 13 | read_buffer_len: usize, | |
| 14 | /// Index into `read_buffer` of the first byte of the next HTTP request. | |
| 15 | next_request_start: usize, | |
| 22 | in_err: anyerror, | |
| 16 | 23 | |
| 17 | 24 | pub const State = enum { |
| 18 | 25 | /// The connection is available to be used for the first time, or reused. |
| ... | ... | @@ -31,14 +38,13 @@ pub const State = enum { |
| 31 | 38 | |
| 32 | 39 | /// Initialize an HTTP server that can respond to multiple requests on the same |
| 33 | 40 | /// connection. |
| 41 | /// | |
| 34 | 42 | /// The returned `Server` is ready for `receiveHead` to be called. |
| 35 | pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server { | |
| 43 | pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server { | |
| 36 | 44 | return .{ |
| 37 | .connection = connection, | |
| 45 | .in = in, | |
| 46 | .out = out, | |
| 38 | 47 | .state = .ready, |
| 39 | .read_buffer = read_buffer, | |
| 40 | .read_buffer_len = 0, | |
| 41 | .next_request_start = 0, | |
| 42 | 48 | }; |
| 43 | 49 | } |
| 44 | 50 | |
| ... | ... | @@ -48,78 +54,55 @@ pub const ReceiveHeadError = error{ |
| 48 | 54 | /// before closing the connection. |
| 49 | 55 | HttpHeadersOversize, |
| 50 | 56 | /// Client sent headers that did not conform to the HTTP protocol. |
| 57 | /// `in_err` is populated with a `Request.Head.ParseError`. | |
| 51 | 58 | HttpHeadersInvalid, |
| 52 | /// A low level I/O error occurred trying to read the headers. | |
| 53 | HttpHeadersUnreadable, | |
| 54 | 59 | /// Partial HTTP request was received but the connection was closed before |
| 55 | 60 | /// fully receiving the headers. |
| 56 | 61 | HttpRequestTruncated, |
| 57 | 62 | /// The client sent 0 bytes of headers before closing the stream. |
| 58 | 63 | /// In other words, a keep-alive connection was finally closed. |
| 59 | 64 | HttpConnectionClosing, |
| 65 | /// Error occurred reading from `in`; `in_err` is populated. | |
| 66 | ReadFailure, | |
| 60 | 67 | }; |
| 61 | 68 | |
| 62 | /// The header bytes reference the read buffer that Server was initialized with | |
| 63 | /// and remain alive until the next call to receiveHead. | |
| 69 | /// The header bytes reference the internal storage of `in`, which are | |
| 70 | /// invalidated with the next call to `receiveHead`. | |
| 64 | 71 | pub fn receiveHead(s: *Server) ReceiveHeadError!Request { |
| 65 | 72 | assert(s.state == .ready); |
| 66 | 73 | s.state = .received_head; |
| 67 | 74 | errdefer s.state = .receiving_head; |
| 68 | 75 | |
| 69 | // In case of a reused connection, move the next request's bytes to the | |
| 70 | // beginning of the buffer. | |
| 71 | if (s.next_request_start > 0) { | |
| 72 | if (s.read_buffer_len > s.next_request_start) { | |
| 73 | rebase(s, 0); | |
| 74 | } else { | |
| 75 | s.read_buffer_len = 0; | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 76 | const in = &s.in; | |
| 79 | 77 | var hp: http.HeadParser = .{}; |
| 80 | ||
| 81 | if (s.read_buffer_len > 0) { | |
| 82 | const bytes = s.read_buffer[0..s.read_buffer_len]; | |
| 83 | const end = hp.feed(bytes); | |
| 84 | if (hp.state == .finished) | |
| 85 | return finishReceivingHead(s, end); | |
| 86 | } | |
| 78 | var head_end: usize = 0; | |
| 87 | 79 | |
| 88 | 80 | while (true) { |
| 89 | const buf = s.read_buffer[s.read_buffer_len..]; | |
| 90 | if (buf.len == 0) | |
| 91 | return error.HttpHeadersOversize; | |
| 92 | const read_n = s.connection.stream.read(buf) catch | |
| 93 | return error.HttpHeadersUnreadable; | |
| 94 | if (read_n == 0) { | |
| 95 | if (s.read_buffer_len > 0) { | |
| 96 | return error.HttpRequestTruncated; | |
| 97 | } else { | |
| 98 | return error.HttpConnectionClosing; | |
| 99 | } | |
| 100 | } | |
| 101 | s.read_buffer_len += read_n; | |
| 102 | const bytes = buf[0..read_n]; | |
| 103 | const end = hp.feed(bytes); | |
| 104 | if (hp.state == .finished) | |
| 105 | return finishReceivingHead(s, s.read_buffer_len - bytes.len + end); | |
| 81 | if (head_end >= in.bufferContents().len) return error.HttpHeadersOversize; | |
| 82 | const buf = (in.peekGreedy(head_end + 1) catch |err| { | |
| 83 | s.in_err = err; | |
| 84 | return error.ReadFailure; | |
| 85 | }) orelse switch (head_end) { | |
| 86 | 0 => return error.HttpConnectionClosing, | |
| 87 | else => return error.HttpRequestTruncated, | |
| 88 | }; | |
| 89 | head_end += hp.feed(buf[head_end..]); | |
| 90 | if (hp.state == .finished) return .{ | |
| 91 | .server = s, | |
| 92 | .head_end = head_end, | |
| 93 | .head = Request.Head.parse(buf[0..head_end]) catch |err| { | |
| 94 | s.in_err = err; | |
| 95 | return error.HttpHeadersInvalid; | |
| 96 | }, | |
| 97 | .reader_state = undefined, | |
| 98 | .write_error = undefined, | |
| 99 | }; | |
| 106 | 100 | } |
| 107 | 101 | } |
| 108 | 102 | |
| 109 | fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request { | |
| 110 | return .{ | |
| 111 | .server = s, | |
| 112 | .head_end = head_end, | |
| 113 | .head = Request.Head.parse(s.read_buffer[0..head_end]) catch | |
| 114 | return error.HttpHeadersInvalid, | |
| 115 | .reader_state = undefined, | |
| 116 | .write_error = undefined, | |
| 117 | }; | |
| 118 | } | |
| 119 | ||
| 120 | 103 | pub const Request = struct { |
| 121 | 104 | server: *Server, |
| 122 | /// Index into Server's read_buffer. | |
| 105 | /// Index into `Server.in` internal buffer. | |
| 123 | 106 | head_end: usize, |
| 124 | 107 | head: Head, |
| 125 | 108 | reader_state: union { |
| ... | ... | @@ -299,7 +282,7 @@ pub const Request = struct { |
| 299 | 282 | }; |
| 300 | 283 | |
| 301 | 284 | pub fn iterateHeaders(r: *Request) http.HeaderIterator { |
| 302 | return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]); | |
| 285 | return http.HeaderIterator.init(r.in.bufferContents()[0..r.head_end]); | |
| 303 | 286 | } |
| 304 | 287 | |
| 305 | 288 | test iterateHeaders { |
| ... | ... | @@ -312,13 +295,14 @@ pub const Request = struct { |
| 312 | 295 | |
| 313 | 296 | var read_buffer: [500]u8 = undefined; |
| 314 | 297 | @memcpy(read_buffer[0..request_bytes.len], request_bytes); |
| 298 | var br: std.io.BufferedReader = undefined; | |
| 299 | br.initFixed(&read_buffer); | |
| 315 | 300 | |
| 316 | 301 | var server: Server = .{ |
| 317 | .connection = undefined, | |
| 302 | .in = &br, | |
| 303 | .out = undefined, | |
| 318 | 304 | .state = .ready, |
| 319 | .read_buffer = &read_buffer, | |
| 320 | .read_buffer_len = request_bytes.len, | |
| 321 | .next_request_start = 0, | |
| 305 | .in_err = undefined, | |
| 322 | 306 | }; |
| 323 | 307 | |
| 324 | 308 | var request: Request = .{ |
| ... | ... | @@ -1158,13 +1142,3 @@ fn rebase(s: *Server, index: usize) void { |
| 1158 | 1142 | } |
| 1159 | 1143 | s.read_buffer_len = index + leftover.len; |
| 1160 | 1144 | } |
| 1161 | ||
| 1162 | const std = @import("../std.zig"); | |
| 1163 | const http = std.http; | |
| 1164 | const mem = std.mem; | |
| 1165 | const net = std.net; | |
| 1166 | const Uri = std.Uri; | |
| 1167 | const assert = std.debug.assert; | |
| 1168 | const testing = std.testing; | |
| 1169 | ||
| 1170 | const Server = @This(); |
lib/std/io/BufferedReader.zig+61-17| ... | ... | @@ -94,6 +94,11 @@ pub fn storageBuffer(br: *BufferedReader) []u8 { |
| 94 | 94 | return storage.buffer; |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | pub fn bufferContents(br: *BufferedReader) []u8 { | |
| 98 | const storage = &br.storage; | |
| 99 | return storage.buffer[br.seek..storage.end]; | |
| 100 | } | |
| 101 | ||
| 97 | 102 | /// Although `BufferedReader` can easily satisfy the `Reader` interface, it's |
| 98 | 103 | /// generally more practical to pass a `BufferedReader` instance itself around, |
| 99 | 104 | /// since it will result in fewer calls across vtable boundaries. |
| ... | ... | @@ -159,31 +164,69 @@ pub fn seekForwardBy(br: *BufferedReader, seek_by: u64) anyerror!void { |
| 159 | 164 | /// is returned instead. |
| 160 | 165 | /// |
| 161 | 166 | /// See also: |
| 162 | /// * `peekAll` | |
| 167 | /// * `peekGreedy` | |
| 163 | 168 | /// * `toss` |
| 164 | 169 | pub fn peek(br: *BufferedReader, n: usize) anyerror![]u8 { |
| 165 | return (try br.peekAll(n))[0..n]; | |
| 170 | return (try br.peekGreedy(n))[0..n]; | |
| 166 | 171 | } |
| 167 | 172 | |
| 168 | /// Returns the next buffered bytes from `unbuffered_reader`, after filling the buffer | |
| 169 | /// with at least `n` bytes. | |
| 173 | /// Returns the next `n` bytes from `unbuffered_reader`, filling the buffer as | |
| 174 | /// necessary. | |
| 170 | 175 | /// |
| 171 | 176 | /// Invalidates previously returned values from `peek`. |
| 172 | 177 | /// |
| 173 | 178 | /// Asserts that the `BufferedReader` was initialized with a buffer capacity at |
| 174 | 179 | /// least as big as `n`. |
| 175 | 180 | /// |
| 181 | /// If there are fewer than `n` bytes left in the stream, `null` is returned | |
| 182 | /// instead. | |
| 183 | /// | |
| 184 | /// See also: | |
| 185 | /// * `peekGreedy` | |
| 186 | /// * `toss` | |
| 187 | pub fn peek2(br: *BufferedReader, n: usize) anyerror!?[]u8 { | |
| 188 | if (try br.peekGreedy(n)) |buf| return buf[0..n]; | |
| 189 | return null; | |
| 190 | } | |
| 191 | ||
| 192 | /// Returns all the next buffered bytes from `unbuffered_reader`, after filling | |
| 193 | /// the buffer to ensure it contains at least `n` bytes. | |
| 194 | /// | |
| 195 | /// Invalidates previously returned values from `peek` and `peekGreedy`. | |
| 196 | /// | |
| 197 | /// Asserts that the `BufferedReader` was initialized with a buffer capacity at | |
| 198 | /// least as big as `n`. | |
| 199 | /// | |
| 176 | 200 | /// If there are fewer than `n` bytes left in the stream, `error.EndOfStream` |
| 177 | 201 | /// is returned instead. |
| 178 | 202 | /// |
| 179 | 203 | /// See also: |
| 180 | 204 | /// * `peek` |
| 181 | 205 | /// * `toss` |
| 182 | pub fn peekAll(br: *BufferedReader, n: usize) anyerror![]u8 { | |
| 183 | const storage = &br.storage; | |
| 184 | assert(n <= storage.buffer.len); | |
| 185 | try br.fill(n); | |
| 186 | return storage.buffer[br.seek..storage.end]; | |
| 206 | pub fn peekGreedy(br: *BufferedReader, n: usize) anyerror![]u8 { | |
| 207 | assert(n <= br.storage.buffer.len); | |
| 208 | if (try br.fill(n)) return br.bufferContents(); | |
| 209 | return error.EndOfStream; | |
| 210 | } | |
| 211 | ||
| 212 | /// Returns all the next buffered bytes from `unbuffered_reader`, after filling | |
| 213 | /// the buffer to ensure it contains at least `n` bytes. | |
| 214 | /// | |
| 215 | /// Invalidates previously returned values from `peek` and `peekGreedy`. | |
| 216 | /// | |
| 217 | /// Asserts that the `BufferedReader` was initialized with a buffer capacity at | |
| 218 | /// least as big as `n`. | |
| 219 | /// | |
| 220 | /// If there are fewer than `n` bytes left in the stream, `null` is returned | |
| 221 | /// instead. | |
| 222 | /// | |
| 223 | /// See also: | |
| 224 | /// * `peek` | |
| 225 | /// * `toss` | |
| 226 | pub fn peekGreedy2(br: *BufferedReader, n: usize) anyerror!?[]u8 { | |
| 227 | assert(n <= br.storage.buffer.len); | |
| 228 | if (try br.fill(n)) return br.bufferContents(); | |
| 229 | return null; | |
| 187 | 230 | } |
| 188 | 231 | |
| 189 | 232 | /// Skips the next `n` bytes from the stream, advancing the seek position. This |
| ... | ... | @@ -505,17 +548,17 @@ pub fn discardDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror!vo |
| 505 | 548 | /// Fills the buffer such that it contains at least `n` bytes, without |
| 506 | 549 | /// advancing the seek position. |
| 507 | 550 | /// |
| 508 | /// Returns `error.EndOfStream` if there are fewer than `n` bytes remaining. | |
| 551 | /// Returns `false` if and only if there are fewer than `n` bytes remaining. | |
| 509 | 552 | /// |
| 510 | 553 | /// Asserts buffer capacity is at least `n`. |
| 511 | pub fn fill(br: *BufferedReader, n: usize) anyerror!void { | |
| 554 | pub fn fill(br: *BufferedReader, n: usize) anyerror!bool { | |
| 512 | 555 | const storage = &br.storage; |
| 513 | 556 | assert(n <= storage.buffer.len); |
| 514 | 557 | const buffer = storage.buffer[0..storage.end]; |
| 515 | 558 | const seek = br.seek; |
| 516 | 559 | if (seek + n <= buffer.len) { |
| 517 | 560 | @branchHint(.likely); |
| 518 | return; | |
| 561 | return true; | |
| 519 | 562 | } |
| 520 | 563 | const remainder = buffer[seek..]; |
| 521 | 564 | std.mem.copyForwards(u8, buffer[0..remainder.len], remainder); |
| ... | ... | @@ -523,8 +566,8 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void { |
| 523 | 566 | br.seek = 0; |
| 524 | 567 | while (true) { |
| 525 | 568 | const status = try br.unbuffered_reader.read(storage, .unlimited); |
| 526 | if (n <= storage.end) return; | |
| 527 | if (status.end) return error.EndOfStream; | |
| 569 | if (n <= storage.end) return true; | |
| 570 | if (status.end) return false; | |
| 528 | 571 | } |
| 529 | 572 | } |
| 530 | 573 | |
| ... | ... | @@ -535,7 +578,8 @@ pub fn takeByte(br: *BufferedReader) anyerror!u8 { |
| 535 | 578 | const seek = br.seek; |
| 536 | 579 | if (seek >= buffer.len) { |
| 537 | 580 | @branchHint(.unlikely); |
| 538 | try br.fill(1); | |
| 581 | const filled = try fill(br, 1); | |
| 582 | if (!filled) return error.EndOfStream; | |
| 539 | 583 | } |
| 540 | 584 | br.seek = seek + 1; |
| 541 | 585 | return buffer[seek]; |
| ... | ... | @@ -603,7 +647,7 @@ fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) anyerror!Re |
| 603 | 647 | var result: UnsignedResult = 0; |
| 604 | 648 | var fits = true; |
| 605 | 649 | while (true) { |
| 606 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try br.peekAll(1)); | |
| 650 | const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try br.peekGreedy(1)); | |
| 607 | 651 | for (buffer, 1..) |byte, len| { |
| 608 | 652 | if (remaining_bits > 0) { |
| 609 | 653 | result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) | |
| ... | ... | @@ -639,7 +683,7 @@ test peek { |
| 639 | 683 | return error.Unimplemented; |
| 640 | 684 | } |
| 641 | 685 | |
| 642 | test peekAll { | |
| 686 | test peekGreedy { | |
| 643 | 687 | return error.Unimplemented; |
| 644 | 688 | } |
| 645 | 689 |