authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-15 23:09:01-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log20a784f7136143e4afa4d9d1d85fc0fa6d69d777
treeb6db814d9e577b96be4f4d102d9648c85833428e
parentc872a9fd49b090efc5b6132ec0ab959d7fe8e70f

std: start converting networking stuff to new reader/writer


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,38 +155,35 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
155 }155 }
156156
157 // Read a DER-encoded integer.157 // Read a DER-encoded integer.
158 fn readDerInt(out: []u8, reader: anytype) EncodingError!void {158 // Asserts `br` has storage capacity >= 2.
159 var buf: [2]u8 = undefined;159 fn readDerInt(out: []u8, br: *std.io.BufferedReader) EncodingError!void {
160 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;160 const buf = br.take(2) catch return error.InvalidEncoding;
161 if (buf[0] != 0x02) return error.InvalidEncoding;161 if (buf[0] != 0x02) return error.InvalidEncoding;
162 var expected_len = @as(usize, buf[1]);162 var expected_len: usize = buf[1];
163 if (expected_len == 0 or expected_len > 1 + out.len) return error.InvalidEncoding;163 if (expected_len == 0 or expected_len > 1 + out.len) return error.InvalidEncoding;
164 var has_top_bit = false;164 var has_top_bit = false;
165 if (expected_len == 1 + out.len) {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 expected_len -= 1;167 expected_len -= 1;
168 has_top_bit = true;168 has_top_bit = true;
169 }169 }
170 const out_slice = out[out.len - expected_len ..];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 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;172 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;
173 }173 }
174174
175 /// Create a signature from a DER representation.175 /// Create a signature from a DER representation.
176 /// Returns InvalidEncoding if the DER encoding is invalid.176 /// Returns InvalidEncoding if the DER encoding is invalid.
177 pub fn fromDer(der: []const u8) EncodingError!Signature {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 var sig: Signature = mem.zeroInit(Signature, .{});183 var sig: Signature = mem.zeroInit(Signature, .{});
179 var fb: std.io.FixedBufferStream = .{ .buffer = der };184 try readDerInt(&sig.r, &br);
180 const reader = fb.reader();185 try readDerInt(&sig.s, &br);
181 var buf: [2]u8 = undefined;186 if (br.seek != der.len) return error.InvalidEncoding;
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
190 return sig;187 return sig;
191 }188 }
192 };189 };
lib/std/crypto/tls/Client.zig+165-254
...@@ -1,3 +1,6 @@...@@ -1,3 +1,6 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
1const std = @import("../../std.zig");4const std = @import("../../std.zig");
2const tls = std.crypto.tls;5const tls = std.crypto.tls;
3const Client = @This();6const Client = @This();
...@@ -13,18 +16,44 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;...@@ -13,18 +16,44 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;
13const int = tls.int;16const int = tls.int;
14const array = tls.array;17const array = tls.array;
1518
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.
32input: *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`.
37output: *std.io.BufferedWriter,
38/// Cleartext received from the server here.
39///
40/// Its buffer aliases the buffer of `input`.
41reader: std.io.BufferedReader,
42/// Populated under various error conditions.
43diagnostics: Diagnostics,
44
16tls_version: tls.ProtocolVersion,45tls_version: tls.ProtocolVersion,
17read_seq: u64,46read_seq: u64,
18write_seq: u64,47write_seq: u64,
19/// The starting index of cleartext bytes inside `partially_read_buffer`.48/// The starting index of cleartext bytes inside the input buffer.
20partial_cleartext_idx: u15,49partial_cleartext_idx: u15,
21/// The ending index of cleartext bytes inside `partially_read_buffer` as well50/// The ending index of cleartext bytes inside the input buffer as well
22/// as the starting index of ciphertext bytes.51/// as the starting index of ciphertext bytes.
23partial_ciphertext_idx: u15,52partial_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.
25partial_ciphertext_end: u15,54partial_ciphertext_end: u15,
26/// When this is true, the stream may still not be at the end because there55/// 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.
28received_close_notify: bool,57received_close_notify: bool,
29/// By default, reaching the end-of-stream when reading from the server will58/// By default, reaching the end-of-stream when reading from the server will
30/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify59/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
...@@ -35,24 +64,40 @@ received_close_notify: bool,...@@ -35,24 +64,40 @@ received_close_notify: bool,
35/// the amount of data expected, such as HTTP with the Content-Length header.64/// the amount of data expected, such as HTTP with the Content-Length header.
36allow_truncation_attacks: bool,65allow_truncation_attacks: bool,
37application_cipher: tls.ApplicationCipher,66application_cipher: tls.ApplicationCipher,
38/// The size is enough to contain exactly one TLSCiphertext record.67/// If non-null, ssl secrets are logged to a stream. Creating such a log file
39/// This buffer is segmented into four parts:68/// allows other programs with access to that file to decrypt all traffic over
40/// 0. unused69/// this connection.
41/// 1. cleartext70ssl_key_log: ?*SslKeyLog,
42/// 2. ciphertext71
43/// 3. unused72pub const Diagnostics = union {
44/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and73 /// Populated on `error.WriteFailure` and `error.ReadFailure`.
45/// `partial_ciphertext_end` describe the span of the segments.74 err: anyerror,
46partially_read_buffer: [tls.max_ciphertext_record_len]u8,75 /// Populated on `error.TlsAlert`.
47/// Encrypted bytes sent to the server here.76 ///
48output: *std.io.BufferedWriter,77 /// If this isn't a error alert, then it's a closure alert, which makes
49/// If non-null, ssl secrets are logged to a file. Creating such a log file allows other78 /// no sense in a handshake.
50/// programs with access to that file to decrypt all traffic over this connection.79 alert: tls.AlertDescription,
51ssl_key_log: ?struct {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
96pub const SslKeyLog = struct {
52 client_key_seq: u64,97 client_key_seq: u64,
53 server_key_seq: u64,98 server_key_seq: u64,
54 client_random: [32]u8,99 client_random: [32]u8,
55 file: std.fs.File,100 writer: *std.io.BufferedWriter,
56101
57 fn clientCounter(key_log: *@This()) u64 {102 fn clientCounter(key_log: *@This()) u64 {
58 defer key_log.client_key_seq += 1;103 defer key_log.client_key_seq += 1;
...@@ -63,31 +108,12 @@ ssl_key_log: ?struct {...@@ -63,31 +108,12 @@ ssl_key_log: ?struct {
63 defer key_log.server_key_seq += 1;108 defer key_log.server_key_seq += 1;
64 return key_log.server_key_seq;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.
75pub 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};
90112
113/// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init`
114/// each require a buffer capacity at least this amount.
115pub const min_buffer_len = tls.max_ciphertext_record_len;
116
91pub const Options = struct {117pub const Options = struct {
92 /// How to perform host verification of server certificates.118 /// How to perform host verification of server certificates.
93 host: union(enum) {119 host: union(enum) {
...@@ -109,39 +135,11 @@ pub const Options = struct {...@@ -109,39 +135,11 @@ pub const Options = struct {
109 /// Verify that the server certificate is authorized by a given ca bundle.135 /// Verify that the server certificate is authorized by a given ca bundle.
110 bundle: Certificate.Bundle,136 bundle: Certificate.Bundle,
111 },137 },
112 /// If non-null, ssl secrets are logged to this file. Creating such a log file allows138 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
113 /// other programs with access to that file to decrypt all traffic over this connection.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`.140 ssl_key_log: ?*std.io.BufferedWriter = null,
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 };
127};141};
128142
129/// TODO I wish this could be a method of Diagnostics
130fn 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
138fn 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
145const InitError = error{143const InitError = error{
146 //OutOfMemory,144 //OutOfMemory,
147 WriteFailure,145 WriteFailure,
...@@ -193,12 +191,21 @@ const InitError = error{...@@ -193,12 +191,21 @@ const InitError = error{
193 WeakPublicKey,191 WeakPublicKey,
194};192};
195193
196/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session with `input`, which194/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session.
197/// must conform to `StreamInterface`.
198///195///
199/// `host` is only borrowed during this function call.196/// `host` is only borrowed during this function call.
200pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) InitError!Client {197///
201 const diags = options.diagnostics;198/// Both `input` and `output` are asserted to have buffer capacity at least
199/// `min_buffer_len`.
200pub 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 const host = switch (options.host) {209 const host = switch (options.host) {
203 .no_verification => "",210 .no_verification => "",
204 .explicit => |host| host,211 .explicit => |host| host,
...@@ -291,7 +298,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -291,7 +298,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
291298
292 {299 {
293 var iovecs: [2][]const u8 = .{ cleartext_header, host };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 }
296303
297 var tls_version: tls.ProtocolVersion = undefined;304 var tls_version: tls.ProtocolVersion = undefined;
...@@ -343,12 +350,12 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -343,12 +350,12 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
343 var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined;350 var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined;
344 var d: tls.Decoder = .{ .buf = &handshake_buffer };351 var d: tls.Decoder = .{ .buf = &handshake_buffer };
345 fragment: while (true) {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 const record_header = d.buf[d.idx..][0..tls.record_header_len];354 const record_header = d.buf[d.idx..][0..tls.record_header_len];
348 const record_ct = d.decode(tls.ContentType);355 const record_ct = d.decode(tls.ContentType);
349 d.skip(2); // legacy_version356 d.skip(2); // legacy_version
350 const record_len = d.decode(u16);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 var record_decoder = try d.sub(record_len);359 var record_decoder = try d.sub(record_len);
353 var ctd, const ct = content: switch (cipher_state) {360 var ctd, const ct = content: switch (cipher_state) {
354 .cleartext => .{ record_decoder, record_ct },361 .cleartext => .{ record_decoder, record_ct },
...@@ -426,7 +433,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -426,7 +433,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
426 const level = ctd.decode(tls.AlertLevel);433 const level = ctd.decode(tls.AlertLevel);
427 const desc = ctd.decode(tls.AlertDescription);434 const desc = ctd.decode(tls.AlertDescription);
428 _ = level;435 _ = level;
429 if (diags) |x| x.* = .{ .alert = desc };436 diags.* = .{ .alert = desc };
430 return error.TlsAlert;437 return error.TlsAlert;
431 },438 },
432 .change_cipher_spec => {439 .change_cipher_spec => {
...@@ -768,7 +775,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -768,7 +775,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
768 &client_change_cipher_spec_msg,775 &client_change_cipher_spec_msg,
769 &client_verify_msg,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 write_seq += 1;781 write_seq += 1;
...@@ -833,7 +840,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -833,7 +840,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
833 &client_change_cipher_spec_msg,840 &client_change_cipher_spec_msg,
834 &finished_msg,841 &finished_msg,
835 };842 };
836 try wrapWrite(diags, output.writevAll(&all_msgs_vec));843 try diags.wrapWrite(output.writevAll(&all_msgs_vec));
837844
838 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);845 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
839 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);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,7 +872,10 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
865 },872 },
866 };873 };
867 const leftover = d.rest();874 const leftover = d.rest();
868 var client: Client = .{875 client.* = .{
876 .input = input,
877 .output = output,
878 .reader = undefined,
869 .tls_version = tls_version,879 .tls_version = tls_version,
870 .read_seq = switch (tls_version) {880 .read_seq = switch (tls_version) {
871 .tls_1_3 => 0,881 .tls_1_3 => 0,
...@@ -883,7 +893,6 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -883,7 +893,6 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
883 .received_close_notify = false,893 .received_close_notify = false,
884 .allow_truncation_attacks = false,894 .allow_truncation_attacks = false,
885 .application_cipher = app_cipher,895 .application_cipher = app_cipher,
886 .output = output,
887 .partially_read_buffer = undefined,896 .partially_read_buffer = undefined,
888 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{897 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{
889 .client_key_seq = key_seq,898 .client_key_seq = key_seq,
...@@ -893,7 +902,14 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In...@@ -893,7 +902,14 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
893 } else null,902 } else null,
894 };903 };
895 @memcpy(client.partially_read_buffer[0..leftover.len], leftover);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 else => return error.TlsUnexpectedMessage,914 else => return error.TlsUnexpectedMessage,
899 }915 }
...@@ -919,81 +935,45 @@ pub fn writer(c: *Client) std.io.Writer {...@@ -919,81 +935,45 @@ pub fn writer(c: *Client) std.io.Writer {
919935
920fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {936fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
921 const c: *Client = @alignCast(@ptrCast(context));937 const c: *Client = @alignCast(@ptrCast(context));
922 assert(data.len > 1 or splat > 0);938 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
923 return writeEnd(c, data[0], false);939 const output = &c.output;
924}940 const ciphertext_buf = try output.writableSlice(min_buffer_len);
925941 var total_clear: usize = 0;
926/// If `end` is true, then this function additionally sends a `close_notify`942 var ciphertext_end: usize = 0;
927/// alert, which is necessary for the server to distinguish between a properly943 for (sliced_data) |buf| {
928/// finished TLS session, or a truncation attack.944 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
929pub fn writeAllEnd(c: *Client, bytes: []const u8, end: bool) anyerror!void {945 total_clear += prepared.cleartext_len;
930 var index: usize = 0;946 ciphertext_end += prepared.ciphertext_end;
931 while (index < bytes.len) {947 if (total_clear < buf.len) break;
932 index += try c.writeEnd(bytes[index..], end);
933 }948 }
949 output.advance(ciphertext_end);
950 return total_clear;
934}951}
935952
936/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.953/// Sends a `close_notify` alert, which is necessary for the server to
937/// If `end` is true, then this function additionally sends a `close_notify` alert,954/// distinguish between a properly finished TLS session, or a truncation
938/// which is necessary for the server to distinguish between a properly finished955/// attack.
939/// TLS session, or a truncation attack.956pub fn end(c: *Client) anyerror!void {
940pub fn writeEnd(c: *Client, bytes: []const u8, end: bool) anyerror!usize {957 const output = &c.output;
941 var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;958 const ciphertext_buf = try output.writableSlice(min_buffer_len);
942 var iovecs_buf: [6][]const u8 = undefined;959 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
943 var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);960 output.advance(prepared.cleartext_len);
944 if (end) {961 return prepared.ciphertext_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 }
978}962}
979963
980fn prepareCiphertextRecord(964fn prepareCiphertextRecord(
981 c: *Client,965 c: *Client,
982 iovecs: [][]const u8,
983 ciphertext_buf: []u8,966 ciphertext_buf: []u8,
984 bytes: []const u8,967 bytes: []const u8,
985 inner_content_type: tls.ContentType,968 inner_content_type: tls.ContentType,
986) struct {969) struct {
987 iovec_end: usize,
988 ciphertext_end: usize,970 ciphertext_end: usize,
989 /// How many bytes are taken up by overhead per record.971 cleartext_len: usize,
990 overhead_len: usize,
991} {972} {
992 // Due to the trailing inner content type byte in the ciphertext, we need973 // Due to the trailing inner content type byte in the ciphertext, we need
993 // an additional buffer for storing the cleartext into before encrypting.974 // an additional buffer for storing the cleartext into before encrypting.
994 var cleartext_buf: [max_ciphertext_len]u8 = undefined;975 var cleartext_buf: [max_ciphertext_len]u8 = undefined;
995 var ciphertext_end: usize = 0;976 var ciphertext_end: usize = 0;
996 var iovec_end: usize = 0;
997 var bytes_i: usize = 0;977 var bytes_i: usize = 0;
998 switch (c.application_cipher) {978 switch (c.application_cipher) {
999 inline else => |*p| switch (c.tls_version) {979 inline else => |*p| switch (c.tls_version) {
...@@ -1001,18 +981,15 @@ fn prepareCiphertextRecord(...@@ -1001,18 +981,15 @@ fn prepareCiphertextRecord(
1001 const pv = &p.tls_1_3;981 const pv = &p.tls_1_3;
1002 const P = @TypeOf(p.*);982 const P = @TypeOf(p.*);
1003 const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1;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 while (true) {984 while (true) {
1006 const encrypted_content_len: u16 = @min(985 const encrypted_content_len: u16 = @min(
1007 bytes.len - bytes_i,986 bytes.len - bytes_i,
1008 tls.max_ciphertext_inner_record_len,987 tls.max_ciphertext_inner_record_len,
1009 ciphertext_buf.len -|988 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1010 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1011 );989 );
1012 if (encrypted_content_len == 0) return .{990 if (encrypted_content_len == 0) return .{
1013 .iovec_end = iovec_end,
1014 .ciphertext_end = ciphertext_end,991 .ciphertext_end = ciphertext_end,
1015 .overhead_len = overhead_len,992 .cleartext_len = bytes_i,
1016 };993 };
1017994
1018 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);995 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);
...@@ -1021,7 +998,6 @@ fn prepareCiphertextRecord(...@@ -1021,7 +998,6 @@ fn prepareCiphertextRecord(
1021 const ciphertext_len = encrypted_content_len + 1;998 const ciphertext_len = encrypted_content_len + 1;
1022 const cleartext = cleartext_buf[0..ciphertext_len];999 const cleartext = cleartext_buf[0..ciphertext_len];
10231000
1024 const record_start = ciphertext_end;
1025 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];1001 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
1026 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++1002 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++
1027 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++1003 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
...@@ -1039,35 +1015,27 @@ fn prepareCiphertextRecord(...@@ -1039,35 +1015,27 @@ fn prepareCiphertextRecord(
1039 };1015 };
1040 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);1016 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
1041 c.write_seq += 1; // TODO send key_update on overflow1017 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 .tls_1_2 => {1020 .tls_1_2 => {
1049 const pv = &p.tls_1_2;1021 const pv = &p.tls_1_2;
1050 const P = @TypeOf(p.*);1022 const P = @TypeOf(p.*);
1051 const overhead_len = tls.record_header_len + P.record_iv_length + P.mac_length;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 while (true) {1024 while (true) {
1054 const message_len: u16 = @min(1025 const message_len: u16 = @min(
1055 bytes.len - bytes_i,1026 bytes.len - bytes_i,
1056 tls.max_ciphertext_inner_record_len,1027 tls.max_ciphertext_inner_record_len,
1057 ciphertext_buf.len -|1028 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1058 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1059 );1029 );
1060 if (message_len == 0) return .{1030 if (message_len == 0) return .{
1061 .iovec_end = iovec_end,
1062 .ciphertext_end = ciphertext_end,1031 .ciphertext_end = ciphertext_end,
1063 .overhead_len = overhead_len,1032 .cleartext_len = bytes_i,
1064 };1033 };
10651034
1066 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);1035 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);
1067 bytes_i += message_len;1036 bytes_i += message_len;
1068 const cleartext = cleartext_buf[0..message_len];1037 const cleartext = cleartext_buf[0..message_len];
10691038
1070 const record_start = ciphertext_end;
1071 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];1039 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
1072 ciphertext_end += tls.record_header_len;1040 ciphertext_end += tls.record_header_len;
1073 record_header.* = .{@intFromEnum(inner_content_type)} ++1041 record_header.* = .{@intFromEnum(inner_content_type)} ++
...@@ -1089,10 +1057,6 @@ fn prepareCiphertextRecord(...@@ -1089,10 +1057,6 @@ fn prepareCiphertextRecord(
1089 ciphertext_end += P.mac_length;1057 ciphertext_end += P.mac_length;
1090 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);1058 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);
1091 c.write_seq += 1; // TODO send key_update on overflow1059 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 else => unreachable,1062 else => unreachable,
...@@ -1106,74 +1070,22 @@ pub fn eof(c: Client) bool {...@@ -1106,74 +1070,22 @@ pub fn eof(c: Client) bool {
1106 c.partial_ciphertext_idx >= c.partial_ciphertext_end;1070 c.partial_ciphertext_idx >= c.partial_ciphertext_end;
1107}1071}
11081072
1109/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.1073fn reader_read(
1110/// Returns the number of bytes read, calling the underlying read function the1074 context: ?*anyopaque,
1111/// minimal number of times until the buffer has at least `len` bytes filled.1075 bw: *std.io.BufferedWriter,
1112/// If the number read is less than `len` it means the stream reached the end.1076 limit: std.io.Reader.Limit,
1113/// Reaching the end of the stream is not an error condition.1077) anyerror!std.io.Reader.Status {
1114pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {1078 const buf = limit.slice(try bw.writableSlice(1));
1115 var iovecs = [1]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }};1079 const status = try reader_readv(context, &.{buf});
1116 return readvAtLeast(c, stream, &iovecs, len);1080 bw.advance(status.len);
1081 return status;
1117}1082}
11181083
1119/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.1084fn reader_readv(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
1120pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize {1085 const c: *Client = @ptrCast(@alignCast(context));
1121 return readAtLeast(c, stream, buffer, 1);1086 if (c.eof()) return .{ .end = true };
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.
1128pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
1129 return readAtLeast(c, stream, buffer, buffer.len);
1130}
11311087
1132/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.1088 var vp: VecPut = .{ .iovecs = data };
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.
1138pub 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.
1149pub 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`.
1175pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize {
1176 var vp: VecPut = .{ .iovecs = iovecs };
11771089
1178 // Give away the buffered cleartext we have, if any.1090 // Give away the buffered cleartext we have, if any.
1179 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];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,11 +1105,11 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
1193 if (c.received_close_notify) {1105 if (c.received_close_notify) {
1194 c.partial_ciphertext_end = 0;1106 c.partial_ciphertext_end = 0;
1195 assert(vp.total == amt);1107 assert(vp.total == amt);
1196 return amt;1108 return .{ .len = amt, .end = c.eof() };
1197 } else if (amt > 0) {1109 } else if (amt > 0) {
1198 // We don't need more data, so don't call read.1110 // We don't need more data, so don't call read.
1199 assert(vp.total == amt);1111 assert(vp.total == amt);
1200 return amt;1112 return .{ .len = amt, .end = c.eof() };
1201 }1113 }
1202 }1114 }
12031115
...@@ -1241,7 +1153,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove...@@ -1241,7 +1153,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
1241 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);1153 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);
1242 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;1154 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;
1243 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);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 if (actual_read_len == 0) {1157 if (actual_read_len == 0) {
1246 // This is either a truncation attack, a bug in the server, or an1158 // This is either a truncation attack, a bug in the server, or an
1247 // intentional omission of the close_notify message due to truncation1159 // 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,7 +1180,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
1268 // Perfect split.1180 // Perfect split.
1269 if (frag.ptr == frag1.ptr) {1181 if (frag.ptr == frag1.ptr) {
1270 c.partial_ciphertext_end = c.partial_ciphertext_idx;1182 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1271 return vp.total;1183 return .{ .len = vp.total, .end = c.eof() };
1272 }1184 }
1273 frag = frag1;1185 frag = frag1;
1274 in = 0;1186 in = 0;
...@@ -1310,8 +1222,8 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove...@@ -1310,8 +1222,8 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
1310 const record_len = mem.readInt(u16, frag[in..][0..2], .big);1222 const record_len = mem.readInt(u16, frag[in..][0..2], .big);
1311 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;1223 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
1312 in += 2;1224 in += 2;
1313 const end = in + record_len;1225 const the_end = in + record_len;
1314 if (end > frag.len) {1226 if (the_end > frag.len) {
1315 // We need the record header on the next iteration of the loop.1227 // We need the record header on the next iteration of the loop.
1316 in -= tls.record_header_len;1228 in -= tls.record_header_len;
13171229
...@@ -1398,17 +1310,23 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove...@@ -1398,17 +1310,23 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
1398 .alert => {1310 .alert => {
1399 if (cleartext.len != 2) return error.TlsDecodeError;1311 if (cleartext.len != 2) return error.TlsDecodeError;
1400 const level: tls.AlertLevel = @enumFromInt(cleartext[0]);1312 const level: tls.AlertLevel = @enumFromInt(cleartext[0]);
1313 _ = level;
1401 const desc: tls.AlertDescription = @enumFromInt(cleartext[1]);1314 const desc: tls.AlertDescription = @enumFromInt(cleartext[1]);
1402 if (desc == .close_notify) {1315 switch (desc) {
1403 c.received_close_notify = true;1316 .close_notify => {
1404 c.partial_ciphertext_end = c.partial_ciphertext_idx;1317 c.received_close_notify = true;
1405 return vp.total;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 .handshake => {1331 .handshake => {
1414 var ct_i: usize = 0;1332 var ct_i: usize = 0;
...@@ -1524,7 +1442,7 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi...@@ -1524,7 +1442,7 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
1524 }) catch {};1442 }) catch {};
1525}1443}
15261444
1527fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {1445fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) std.io.Reader.Status {
1528 const saved_buf = frag[in..];1446 const saved_buf = frag[in..];
1529 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {1447 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1530 // There is cleartext at the beginning already which we need to preserve.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,11 +1454,11 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
1536 c.partial_ciphertext_end = @intCast(saved_buf.len);1454 c.partial_ciphertext_end = @intCast(saved_buf.len);
1537 @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf);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}
15411459
1542/// Note that `first` usually overlaps with `c.partially_read_buffer`.1460/// Note that `first` usually overlaps with `c.partially_read_buffer`.
1543fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize {1461fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) std.io.Reader.Status {
1544 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {1462 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1545 // There is cleartext at the beginning already which we need to preserve.1463 // There is cleartext at the beginning already which we need to preserve.
1546 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len);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,7 +1473,7 @@ fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usi
1555 std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first);1473 std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first);
1556 @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1);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}
15601478
1561fn limitedOverlapCopy(frag: []u8, in: usize) void {1479fn limitedOverlapCopy(frag: []u8, in: usize) void {
...@@ -1577,9 +1495,6 @@ fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {...@@ -1577,9 +1495,6 @@ fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {
1577 }1495 }
1578}1496}
15791497
1580const builtin = @import("builtin");
1581const native_endian = builtin.cpu.arch.endian();
1582
1583inline fn big(x: anytype) @TypeOf(x) {1498inline fn big(x: anytype) @TypeOf(x) {
1584 return switch (native_endian) {1499 return switch (native_endian) {
1585 .big => x,1500 .big => x,
...@@ -1958,7 +1873,3 @@ else...@@ -1958,7 +1873,3 @@ else
1958 .AES_256_GCM_SHA384,1873 .AES_256_GCM_SHA384,
1959 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,1874 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
1960 });1875 });
1961
1962test {
1963 _ = StreamInterface;
1964}
lib/std/http/Client.zig+243-225
...@@ -24,6 +24,12 @@ allocator: Allocator,...@@ -24,6 +24,12 @@ allocator: Allocator,
2424
25ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},25ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
26ca_bundle_mutex: std.Thread.Mutex = .{},26ca_bundle_mutex: std.Thread.Mutex = .{},
27/// Used both for the reader and writer buffers.
28tls_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`.
32ssl_key_logger: ?*std.io.BufferedWriter = null,
2733
28/// When this is `true`, the next time this client performs an HTTPS request,34/// When this is `true`, the next time this client performs an HTTPS request,
29/// it will first rescan the system for root certificates.35/// it will first rescan the system for root certificates.
...@@ -31,6 +37,10 @@ next_https_rescan_certs: bool = true,...@@ -31,6 +37,10 @@ next_https_rescan_certs: bool = true,
3137
32/// The pool of connections that can be reused (and currently in use).38/// The pool of connections that can be reused (and currently in use).
33connection_pool: ConnectionPool = .{},39connection_pool: ConnectionPool = .{},
40/// Each `Connection` allocates this amount for the reader buffer.
41read_buffer_size: usize,
42/// Each `Connection` allocates this amount for the writer buffer.
43write_buffer_size: usize,
3444
35/// If populated, all http traffic travels through this third party.45/// If populated, all http traffic travels through this third party.
36/// This field cannot be modified while the client has active connections.46/// This field cannot be modified while the client has active connections.
...@@ -41,7 +51,7 @@ http_proxy: ?*Proxy = null,...@@ -41,7 +51,7 @@ http_proxy: ?*Proxy = null,
41/// Pointer to externally-owned memory.51/// Pointer to externally-owned memory.
42https_proxy: ?*Proxy = null,52https_proxy: ?*Proxy = null,
4353
44/// A set of linked lists of connections that can be reused.54/// A Least-Recently-Used cache of open connections to be reused.
45pub const ConnectionPool = struct {55pub const ConnectionPool = struct {
46 mutex: std.Thread.Mutex = .{},56 mutex: std.Thread.Mutex = .{},
47 /// Open connections that are currently in use.57 /// Open connections that are currently in use.
...@@ -58,8 +68,10 @@ pub const ConnectionPool = struct {...@@ -58,8 +68,10 @@ pub const ConnectionPool = struct {
58 protocol: Connection.Protocol,68 protocol: Connection.Protocol,
59 };69 };
6070
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 /// If no connection is found, null is returned.72 /// If no connection is found, null is returned.
73 ///
74 /// Threadsafe.
63 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {75 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
64 pool.mutex.lock();76 pool.mutex.lock();
65 defer pool.mutex.unlock();77 defer pool.mutex.unlock();
...@@ -96,21 +108,21 @@ pub const ConnectionPool = struct {...@@ -96,21 +108,21 @@ pub const ConnectionPool = struct {
96 return pool.acquireUnsafe(connection);108 return pool.acquireUnsafe(connection);
97 }109 }
98110
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 /// If the connection is marked as closing, it will be closed instead.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.114 /// `allocator` must be the same one used to create `connection`.
103 /// The allocator must be the owner of all resources associated with the connection.115 ///
116 /// Threadsafe.
104 pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void {117 pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void {
118 if (connection.closing) return connection.destroy(allocator);
119
105 pool.mutex.lock();120 pool.mutex.lock();
106 defer pool.mutex.unlock();121 defer pool.mutex.unlock();
107122
108 pool.used.remove(&connection.pool_node);123 pool.used.remove(&connection.pool_node);
109124
110 if (connection.closing or pool.free_size == 0) {125 if (pool.free_size == 0) return connection.destroy(allocator);
111 connection.close(allocator);
112 return allocator.destroy(connection);
113 }
114126
115 if (pool.free_len >= pool.free_size) {127 if (pool.free_len >= pool.free_size) {
116 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);128 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
...@@ -138,9 +150,11 @@ pub const ConnectionPool = struct {...@@ -138,9 +150,11 @@ pub const ConnectionPool = struct {
138 pool.used.append(&connection.pool_node);150 pool.used.append(&connection.pool_node);
139 }151 }
140152
141 /// Resizes the connection pool. This function is threadsafe.153 /// Resizes the connection pool.
142 ///154 ///
143 /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size.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 pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void {158 pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void {
145 pool.mutex.lock();159 pool.mutex.lock();
146 defer pool.mutex.unlock();160 defer pool.mutex.unlock();
...@@ -158,9 +172,11 @@ pub const ConnectionPool = struct {...@@ -158,9 +172,11 @@ pub const ConnectionPool = struct {
158 pool.free_size = new_size;172 pool.free_size = new_size;
159 }173 }
160174
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 /// All future operations on the connection pool will deadlock.177 /// All future operations on the connection pool will deadlock.
178 ///
179 /// Threadsafe.
164 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {180 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {
165 pool.mutex.lock();181 pool.mutex.lock();
166182
...@@ -184,160 +200,212 @@ pub const ConnectionPool = struct {...@@ -184,160 +200,212 @@ pub const ConnectionPool = struct {
184 }200 }
185};201};
186202
187/// An interface to either a plain or TLS connection.
188pub const Connection = struct {203pub const Connection = struct {
204 client: *Client,
189 stream: net.Stream,205 stream: net.Stream,
190 /// Populated when protocol is TLS; this is the writer given to the TLS206 /// HTTP protocol from client to server.
191 /// client, which writes directly to `stream`, unbuffered.207 /// This either goes directly to `stream`, or to a TLS client.
192 stream_writer: std.io.BufferedWriter,208 writer: std.io.BufferedWriter,
193 /// undefined unless protocol is tls.
194 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
195
196 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.209 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
197 pool_node: std.DoublyLinkedList.Node,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 port: u16,211 port: u16,
212 host_len: u8,
213 proxied: bool,
214 closing: bool,
215 protocol: Protocol,
207216
208 /// Whether this connection is proxied and is not directly connected.217 pub const Protocol = enum { plain, tls };
209 proxied: bool = false,
210218
211 /// Whether this connection is closing when we're done with it.219 const Plain = struct {
212 closing: bool = false,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 }
213255
214 read_start: BufferSize = 0,256 fn destroy(plain: *Plain) void {
215 read_end: BufferSize = 0,257 const c = &plain.connection;
216 read_buf: [buffer_size]u8,258 const gpa = c.client.allocator;
259 const base: [*]u8 = @ptrCast(plain);
260 gpa.free(base[0..allocLen(c.client, c.host_len)]);
261 }
217262
218 write_buffer: [buffer_size]u8,263 fn allocLen(client: *Client, host_len: usize) usize {
219 writer: std.io.BufferedWriter,264 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
265 }
220266
221 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;267 fn host(plain: *Plain) []u8 {
222 const BufferSize = std.math.IntFittingRange(0, buffer_size);268 const base: [*]u8 = @ptrCast(plain);
269 return base[@sizeOf(Plain)..][0..plain.connection.host_len];
270 }
271 };
223272
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;
225323
226 pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {324 return tls;
227 return conn.tls_client.readv(conn.stream, buffers) catch |err| {325 }
228 // https://github.com/ziglang/zig/issues/2473
229 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
230326
231 switch (err) {327 fn destroy(tls: *Tls, gpa: Allocator) void {
232 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,328 const c = &tls.connection;
233 error.ConnectionTimedOut => return error.ConnectionTimedOut,329 const base: [*]u8 = @ptrCast(tls);
234 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,330 gpa.free(base[0..allocLen(c.client, c.host_len)]);
235 else => return error.UnexpectedReadFailure,331 }
236 }
237 };
238 }
239332
240 pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {333 fn allocLen(client: *Client, host_len: usize) usize {
241 if (conn.protocol == .tls) {334 return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size;
242 if (disable_tls) unreachable;335 }
243336
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 };
246342
247 return conn.stream.readv(buffers) catch |err| switch (err) {343 fn host(c: *Connection) []u8 {
248 error.ConnectionTimedOut => return error.ConnectionTimedOut,344 return switch (c.protocol) {
249 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,345 .tls => {
250 else => return error.UnexpectedReadFailure,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 }
253356
254 /// Refills the read buffer with data from the connection.357 /// This is either data from `stream`, or `Tls.client`.
255 pub fn fill(conn: *Connection) ReadError!void {358 fn reader(c: *Connection) *std.io.BufferedReader {
256 if (conn.read_end != conn.read_start) return;359 return switch (c.protocol) {
257360 .tls => {
258 var iovecs = [1]std.posix.iovec{361 if (disable_tls) unreachable;
259 .{ .base = &conn.read_buf, .len = conn.read_buf.len },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 }
266371
267 /// Returns the current slice of buffered data.372 /// If this is called without calling `flush` or `end`, data will be
268 pub fn peek(conn: *Connection) []const u8 {373 /// dropped unsent.
269 return conn.read_buf[conn.read_start..conn.read_end];374 pub fn destroy(c: *Connection) void {
270 }375 c.stream.close();
271376 switch (c.protocol) {
272 /// Discards the given number of bytes from the read buffer.377 .tls => {
273 pub fn drop(conn: *Connection, num: BufferSize) void {378 if (disable_tls) unreachable;
274 conn.read_start += num;379 const tls: *Tls = @fieldParentPtr("connection", c);
275 }380 tls.destroy();
276381 },
277 /// Reads data from the connection into the given buffer.382 .plain => {
278 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {383 const plain: *Plain = @fieldParentPtr("connection", c);
279 const available_read = conn.read_end - conn.read_start;384 plain.destroy();
280 const available_buffer = buffer.len;385 },
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;
304 }386 }
305
306 return nread;
307 }387 }
308388
309 pub const ReadError = error{389 pub fn flush(c: *Connection) anyerror!void {
310 TlsFailure,390 try c.writer.flush();
311 TlsAlert,391 if (c.protocol == .tls) {
312 ConnectionTimedOut,392 if (disable_tls) unreachable;
313 ConnectionResetByPeer,393 const tls: *Tls = @fieldParentPtr("connection", c);
314 UnexpectedReadFailure,394 try tls.writer.flush();
315 EndOfStream,395 }
316 };
317
318 pub const Reader = std.io.Reader(*Connection, ReadError, read);
319
320 pub fn reader(conn: *Connection) Reader {
321 return .{ .context = conn };
322 }396 }
323397
324 pub const WriteError = error{398 /// If the connection is a TLS connection, sends the close_notify alert.
325 ConnectionResetByPeer,399 ///
326 UnexpectedWriteFailure,400 /// Flushes all buffers.
327 };401 pub fn end(c: *Connection) anyerror!void {
328402 try c.writer.flush();
329 pub fn close(conn: *Connection, allocator: Allocator) void {403 if (c.protocol == .tls) {
330 if (conn.protocol == .tls) {
331 if (disable_tls) unreachable;404 if (disable_tls) unreachable;
332405 const tls: *Tls = @fieldParentPtr("connection", c);
333 // try to cleanly close the TLS connection, for any server that cares.406 try tls.client.end();
334 _ = conn.tls_client.writeEnd("", true) catch {};407 try tls.writer.flush();
335 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
336 allocator.destroy(conn.tls_client);
337 }408 }
338
339 conn.stream.close();
340 allocator.free(conn.host);
341 }409 }
342};410};
343411
...@@ -350,10 +418,10 @@ pub const RequestTransfer = union(enum) {...@@ -350,10 +418,10 @@ pub const RequestTransfer = union(enum) {
350418
351/// The decompressor for response messages.419/// The decompressor for response messages.
352pub const Compression = union(enum) {420pub const Compression = union(enum) {
353 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Request.TransferReader);421 pub const DeflateDecompressor = std.compress.zlib.Decompressor;
354 pub const GzipDecompressor = std.compress.gzip.Decompressor(Request.TransferReader);422 pub const GzipDecompressor = std.compress.gzip.Decompressor;
355 // https://github.com/ziglang/zig/issues/18937423 // 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(.{});
357425
358 deflate: DeflateDecompressor,426 deflate: DeflateDecompressor,
359 gzip: GzipDecompressor,427 gzip: GzipDecompressor,
...@@ -617,9 +685,6 @@ pub const Response = struct {...@@ -617,9 +685,6 @@ pub const Response = struct {
617 }685 }
618};686};
619687
620/// A HTTP request that has been sent.
621///
622/// Order of operations: open -> send[ -> write -> finish] -> wait -> read
623pub const Request = struct {688pub const Request = struct {
624 uri: Uri,689 uri: Uri,
625 client: *Client,690 client: *Client,
...@@ -1300,24 +1365,34 @@ pub const basic_authorization = struct {...@@ -1300,24 +1365,34 @@ pub const basic_authorization = struct {
1300 }1365 }
1301};1366};
13021367
1303pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };1368pub 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};
13041380
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.1383/// Threadsafe.
1308pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection {1384pub fn connectTcp(
1385 client: *Client,
1386 host: []const u8,
1387 port: u16,
1388 protocol: Connection.Protocol,
1389) ConnectTcpError!*Connection {
1309 if (client.connection_pool.findConnection(.{1390 if (client.connection_pool.findConnection(.{
1310 .host = host,1391 .host = host,
1311 .port = port,1392 .port = port,
1312 .protocol = protocol,1393 .protocol = protocol,
1313 })) |conn| return conn;1394 })) |conn| return conn;
13141395
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 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {1396 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
1322 error.ConnectionRefused => return error.ConnectionRefused,1397 error.ConnectionRefused => return error.ConnectionRefused,
1323 error.NetworkUnreachable => return error.NetworkUnreachable,1398 error.NetworkUnreachable => return error.NetworkUnreachable,
...@@ -1331,77 +1406,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1331,77 +1406,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1331 };1406 };
1332 errdefer stream.close();1407 errdefer stream.close();
13331408
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 switch (protocol) {1409 switch (protocol) {
1352 .tls => {1410 .tls => {
1353 if (disable_tls) unreachable;1411 if (disable_tls) return error.TlsInitializationFailed;
13541412 const tc = try Connection.Tls.create(client, host, port, stream);
1355 const tls_client = try client.allocator.create(std.crypto.tls.Client);1413 client.connection_pool.addUsed(&tc.connection);
1356 errdefer client.allocator.destroy(tls_client);1414 return &tc.connection;
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;
1393 },1415 },
1394 .plain => {1416 .plain => {
1395 conn.writer = .{1417 const pc = try Connection.Plain.create(client, host, port, stream);
1396 .unbuffered_writer = stream.writer(),1418 client.connection_pool.addUsed(&pc.connection);
1397 .buffer = &conn.write_buffer,1419 return &pc.connection;
1398 };
1399 },1420 },
1400 }1421 }
1401
1402 client.connection_pool.addUsed(conn);
1403
1404 return conn;
1405}1422}
14061423
1407pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;1424pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;
...@@ -1662,16 +1679,17 @@ pub fn open(...@@ -1662,16 +1679,17 @@ pub fn open(
1662 var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer);1679 var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer);
1663 const protocol, const valid_uri = try validateUri(uri, server_header.allocator());1680 const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
16641681
1665 if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {1682 if (protocol == .tls) {
1666 if (disable_tls) unreachable;1683 if (disable_tls) unreachable;
16671684 if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1668 client.ca_bundle_mutex.lock();1685 client.ca_bundle_mutex.lock();
1669 defer client.ca_bundle_mutex.unlock();1686 defer client.ca_bundle_mutex.unlock();
16701687
1671 if (client.next_https_rescan_certs) {1688 if (client.next_https_rescan_certs) {
1672 client.ca_bundle.rescan(client.allocator) catch1689 client.ca_bundle.rescan(client.allocator) catch
1673 return error.CertificateBundleLoadFailure;1690 return error.CertificateBundleLoadFailure;
1674 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);1691 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1692 }
1675 }1693 }
1676 }1694 }
16771695
lib/std/http/Server.zig+52-78
...@@ -1,18 +1,25 @@...@@ -1,18 +1,25 @@
1//! Blocking HTTP server implementation.1//! Blocking HTTP server implementation.
2//! Handles a single connection's lifecycle.2//! Handles a single connection's lifecycle.
33
4connection: net.Server.Connection,4const std = @import("../std.zig");
5const http = std.http;
6const mem = std.mem;
7const net = std.net;
8const Uri = std.Uri;
9const assert = std.debug.assert;
10const testing = std.testing;
11
12const 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`.
16in: *std.io.BufferedReader,
17out: *std.io.BufferedWriter,
5/// Keeps track of whether the Server is ready to accept a new request on the18/// Keeps track of whether the Server is ready to accept a new request on the
6/// same connection, and makes invalid API usage cause assertion failures19/// same connection, and makes invalid API usage cause assertion failures
7/// rather than HTTP protocol violations.20/// rather than HTTP protocol violations.
8state: State,21state: State,
9/// User-provided buffer that must outlive this Server.22in_err: anyerror,
10/// Used to store the client's entire HTTP header.
11read_buffer: []u8,
12/// Amount of available data inside read_buffer.
13read_buffer_len: usize,
14/// Index into `read_buffer` of the first byte of the next HTTP request.
15next_request_start: usize,
1623
17pub const State = enum {24pub const State = enum {
18 /// The connection is available to be used for the first time, or reused.25 /// The connection is available to be used for the first time, or reused.
...@@ -31,14 +38,13 @@ pub const State = enum {...@@ -31,14 +38,13 @@ pub const State = enum {
3138
32/// Initialize an HTTP server that can respond to multiple requests on the same39/// Initialize an HTTP server that can respond to multiple requests on the same
33/// connection.40/// connection.
41///
34/// The returned `Server` is ready for `receiveHead` to be called.42/// The returned `Server` is ready for `receiveHead` to be called.
35pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server {43pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {
36 return .{44 return .{
37 .connection = connection,45 .in = in,
46 .out = out,
38 .state = .ready,47 .state = .ready,
39 .read_buffer = read_buffer,
40 .read_buffer_len = 0,
41 .next_request_start = 0,
42 };48 };
43}49}
4450
...@@ -48,78 +54,55 @@ pub const ReceiveHeadError = error{...@@ -48,78 +54,55 @@ pub const ReceiveHeadError = error{
48 /// before closing the connection.54 /// before closing the connection.
49 HttpHeadersOversize,55 HttpHeadersOversize,
50 /// Client sent headers that did not conform to the HTTP protocol.56 /// Client sent headers that did not conform to the HTTP protocol.
57 /// `in_err` is populated with a `Request.Head.ParseError`.
51 HttpHeadersInvalid,58 HttpHeadersInvalid,
52 /// A low level I/O error occurred trying to read the headers.
53 HttpHeadersUnreadable,
54 /// Partial HTTP request was received but the connection was closed before59 /// Partial HTTP request was received but the connection was closed before
55 /// fully receiving the headers.60 /// fully receiving the headers.
56 HttpRequestTruncated,61 HttpRequestTruncated,
57 /// The client sent 0 bytes of headers before closing the stream.62 /// The client sent 0 bytes of headers before closing the stream.
58 /// In other words, a keep-alive connection was finally closed.63 /// In other words, a keep-alive connection was finally closed.
59 HttpConnectionClosing,64 HttpConnectionClosing,
65 /// Error occurred reading from `in`; `in_err` is populated.
66 ReadFailure,
60};67};
6168
62/// The header bytes reference the read buffer that Server was initialized with69/// The header bytes reference the internal storage of `in`, which are
63/// and remain alive until the next call to receiveHead.70/// invalidated with the next call to `receiveHead`.
64pub fn receiveHead(s: *Server) ReceiveHeadError!Request {71pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
65 assert(s.state == .ready);72 assert(s.state == .ready);
66 s.state = .received_head;73 s.state = .received_head;
67 errdefer s.state = .receiving_head;74 errdefer s.state = .receiving_head;
6875
69 // In case of a reused connection, move the next request's bytes to the76 const in = &s.in;
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
79 var hp: http.HeadParser = .{};77 var hp: http.HeadParser = .{};
8078 var head_end: usize = 0;
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 }
8779
88 while (true) {80 while (true) {
89 const buf = s.read_buffer[s.read_buffer_len..];81 if (head_end >= in.bufferContents().len) return error.HttpHeadersOversize;
90 if (buf.len == 0)82 const buf = (in.peekGreedy(head_end + 1) catch |err| {
91 return error.HttpHeadersOversize;83 s.in_err = err;
92 const read_n = s.connection.stream.read(buf) catch84 return error.ReadFailure;
93 return error.HttpHeadersUnreadable;85 }) orelse switch (head_end) {
94 if (read_n == 0) {86 0 => return error.HttpConnectionClosing,
95 if (s.read_buffer_len > 0) {87 else => return error.HttpRequestTruncated,
96 return error.HttpRequestTruncated;88 };
97 } else {89 head_end += hp.feed(buf[head_end..]);
98 return error.HttpConnectionClosing;90 if (hp.state == .finished) return .{
99 }91 .server = s,
100 }92 .head_end = head_end,
101 s.read_buffer_len += read_n;93 .head = Request.Head.parse(buf[0..head_end]) catch |err| {
102 const bytes = buf[0..read_n];94 s.in_err = err;
103 const end = hp.feed(bytes);95 return error.HttpHeadersInvalid;
104 if (hp.state == .finished)96 },
105 return finishReceivingHead(s, s.read_buffer_len - bytes.len + end);97 .reader_state = undefined,
98 .write_error = undefined,
99 };
106 }100 }
107}101}
108102
109fn 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
120pub const Request = struct {103pub const Request = struct {
121 server: *Server,104 server: *Server,
122 /// Index into Server's read_buffer.105 /// Index into `Server.in` internal buffer.
123 head_end: usize,106 head_end: usize,
124 head: Head,107 head: Head,
125 reader_state: union {108 reader_state: union {
...@@ -299,7 +282,7 @@ pub const Request = struct {...@@ -299,7 +282,7 @@ pub const Request = struct {
299 };282 };
300283
301 pub fn iterateHeaders(r: *Request) http.HeaderIterator {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 }
304287
305 test iterateHeaders {288 test iterateHeaders {
...@@ -312,13 +295,14 @@ pub const Request = struct {...@@ -312,13 +295,14 @@ pub const Request = struct {
312295
313 var read_buffer: [500]u8 = undefined;296 var read_buffer: [500]u8 = undefined;
314 @memcpy(read_buffer[0..request_bytes.len], request_bytes);297 @memcpy(read_buffer[0..request_bytes.len], request_bytes);
298 var br: std.io.BufferedReader = undefined;
299 br.initFixed(&read_buffer);
315300
316 var server: Server = .{301 var server: Server = .{
317 .connection = undefined,302 .in = &br,
303 .out = undefined,
318 .state = .ready,304 .state = .ready,
319 .read_buffer = &read_buffer,305 .in_err = undefined,
320 .read_buffer_len = request_bytes.len,
321 .next_request_start = 0,
322 };306 };
323307
324 var request: Request = .{308 var request: Request = .{
...@@ -1158,13 +1142,3 @@ fn rebase(s: *Server, index: usize) void {...@@ -1158,13 +1142,3 @@ fn rebase(s: *Server, index: usize) void {
1158 }1142 }
1159 s.read_buffer_len = index + leftover.len;1143 s.read_buffer_len = index + leftover.len;
1160}1144}
1161
1162const std = @import("../std.zig");
1163const http = std.http;
1164const mem = std.mem;
1165const net = std.net;
1166const Uri = std.Uri;
1167const assert = std.debug.assert;
1168const testing = std.testing;
1169
1170const Server = @This();
lib/std/io/BufferedReader.zig+61-17
...@@ -94,6 +94,11 @@ pub fn storageBuffer(br: *BufferedReader) []u8 {...@@ -94,6 +94,11 @@ pub fn storageBuffer(br: *BufferedReader) []u8 {
94 return storage.buffer;94 return storage.buffer;
95}95}
9696
97pub fn bufferContents(br: *BufferedReader) []u8 {
98 const storage = &br.storage;
99 return storage.buffer[br.seek..storage.end];
100}
101
97/// Although `BufferedReader` can easily satisfy the `Reader` interface, it's102/// Although `BufferedReader` can easily satisfy the `Reader` interface, it's
98/// generally more practical to pass a `BufferedReader` instance itself around,103/// generally more practical to pass a `BufferedReader` instance itself around,
99/// since it will result in fewer calls across vtable boundaries.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,31 +164,69 @@ pub fn seekForwardBy(br: *BufferedReader, seek_by: u64) anyerror!void {
159/// is returned instead.164/// is returned instead.
160///165///
161/// See also:166/// See also:
162/// * `peekAll`167/// * `peekGreedy`
163/// * `toss`168/// * `toss`
164pub fn peek(br: *BufferedReader, n: usize) anyerror![]u8 {169pub 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}
167172
168/// Returns the next buffered bytes from `unbuffered_reader`, after filling the buffer173/// Returns the next `n` bytes from `unbuffered_reader`, filling the buffer as
169/// with at least `n` bytes.174/// necessary.
170///175///
171/// Invalidates previously returned values from `peek`.176/// Invalidates previously returned values from `peek`.
172///177///
173/// Asserts that the `BufferedReader` was initialized with a buffer capacity at178/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
174/// least as big as `n`.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`
187pub 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/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`200/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
177/// is returned instead.201/// is returned instead.
178///202///
179/// See also:203/// See also:
180/// * `peek`204/// * `peek`
181/// * `toss`205/// * `toss`
182pub fn peekAll(br: *BufferedReader, n: usize) anyerror![]u8 {206pub fn peekGreedy(br: *BufferedReader, n: usize) anyerror![]u8 {
183 const storage = &br.storage;207 assert(n <= br.storage.buffer.len);
184 assert(n <= storage.buffer.len);208 if (try br.fill(n)) return br.bufferContents();
185 try br.fill(n);209 return error.EndOfStream;
186 return storage.buffer[br.seek..storage.end];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`
226pub 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}
188231
189/// Skips the next `n` bytes from the stream, advancing the seek position. This232/// 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,17 +548,17 @@ pub fn discardDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror!vo
505/// Fills the buffer such that it contains at least `n` bytes, without548/// Fills the buffer such that it contains at least `n` bytes, without
506/// advancing the seek position.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/// Asserts buffer capacity is at least `n`.553/// Asserts buffer capacity is at least `n`.
511pub fn fill(br: *BufferedReader, n: usize) anyerror!void {554pub fn fill(br: *BufferedReader, n: usize) anyerror!bool {
512 const storage = &br.storage;555 const storage = &br.storage;
513 assert(n <= storage.buffer.len);556 assert(n <= storage.buffer.len);
514 const buffer = storage.buffer[0..storage.end];557 const buffer = storage.buffer[0..storage.end];
515 const seek = br.seek;558 const seek = br.seek;
516 if (seek + n <= buffer.len) {559 if (seek + n <= buffer.len) {
517 @branchHint(.likely);560 @branchHint(.likely);
518 return;561 return true;
519 }562 }
520 const remainder = buffer[seek..];563 const remainder = buffer[seek..];
521 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);564 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
...@@ -523,8 +566,8 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {...@@ -523,8 +566,8 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {
523 br.seek = 0;566 br.seek = 0;
524 while (true) {567 while (true) {
525 const status = try br.unbuffered_reader.read(storage, .unlimited);568 const status = try br.unbuffered_reader.read(storage, .unlimited);
526 if (n <= storage.end) return;569 if (n <= storage.end) return true;
527 if (status.end) return error.EndOfStream;570 if (status.end) return false;
528 }571 }
529}572}
530573
...@@ -535,7 +578,8 @@ pub fn takeByte(br: *BufferedReader) anyerror!u8 {...@@ -535,7 +578,8 @@ pub fn takeByte(br: *BufferedReader) anyerror!u8 {
535 const seek = br.seek;578 const seek = br.seek;
536 if (seek >= buffer.len) {579 if (seek >= buffer.len) {
537 @branchHint(.unlikely);580 @branchHint(.unlikely);
538 try br.fill(1);581 const filled = try fill(br, 1);
582 if (!filled) return error.EndOfStream;
539 }583 }
540 br.seek = seek + 1;584 br.seek = seek + 1;
541 return buffer[seek];585 return buffer[seek];
...@@ -603,7 +647,7 @@ fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) anyerror!Re...@@ -603,7 +647,7 @@ fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) anyerror!Re
603 var result: UnsignedResult = 0;647 var result: UnsignedResult = 0;
604 var fits = true;648 var fits = true;
605 while (true) {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 for (buffer, 1..) |byte, len| {651 for (buffer, 1..) |byte, len| {
608 if (remaining_bits > 0) {652 if (remaining_bits > 0) {
609 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |653 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
...@@ -639,7 +683,7 @@ test peek {...@@ -639,7 +683,7 @@ test peek {
639 return error.Unimplemented;683 return error.Unimplemented;
640}684}
641685
642test peekAll {686test peekGreedy {
643 return error.Unimplemented;687 return error.Unimplemented;
644}688}
645689