authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-25 13:08:18-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:30-07:00
logd4545f216a6ea86caec208cabc724004b7ef320a
tree75a826b42293794f3ab9341703a11d8c6cbd926c
parent7d6b5ed5103e6a06d1bd8be884f6c2a7ecb0e09a

std.io: start removing context from Reader/Writer

rely on the field parent pointer pattern

7 files changed, 221 insertions(+), 189 deletions(-)

lib/std/crypto/tls/Client.zig+51-51
......@@ -21,11 +21,15 @@ const array = tls.array;
2121/// here via `reader`.
2222///
2323/// The buffer is asserted to have capacity at least `min_buffer_len`.
24input: *std.io.Reader,
24input: *Reader,
25/// Decrypted stream from the server to the client.
26reader: Reader,
2527
2628/// The encrypted stream from the client to the server. Bytes are pushed here
2729/// via `writer`.
2830output: *Writer,
31/// The plaintext stream from the client to the server.
32writer: Writer,
2933
3034/// Populated when `error.TlsAlert` is returned.
3135alert: ?tls.Alert = null,
......@@ -36,14 +40,6 @@ write_seq: u64,
3640/// When this is true, the stream may still not be at the end because there
3741/// may be data in the input buffer.
3842received_close_notify: bool,
39/// By default, reaching the end-of-stream when reading from the server will
40/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
41/// message has been received. By setting this flag to `true`, instead, the
42/// end-of-stream will be forwarded to the application layer above TLS.
43///
44/// This makes the application vulnerable to truncation attacks unless the
45/// application layer itself verifies that the amount of data received equals
46/// the amount of data expected, such as HTTP with the Content-Length header.
4743allow_truncation_attacks: bool,
4844application_cipher: tls.ApplicationCipher,
4945
......@@ -85,7 +81,7 @@ pub const SslKeyLog = struct {
8581 }
8682};
8783
88/// The `std.io.Reader` supplied to `init` requires a buffer capacity
84/// The `Reader` supplied to `init` requires a buffer capacity
8985/// at least this amount.
9086pub const min_buffer_len = tls.max_ciphertext_record_len;
9187
......@@ -116,6 +112,20 @@ pub const Options = struct {
116112 /// Only the `writer` field is observed during the handshake (`init`).
117113 /// After that, the other fields are populated.
118114 ssl_key_log: ?*SslKeyLog = null,
115 /// By default, reaching the end-of-stream when reading from the server will
116 /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
117 /// message has been received. By setting this flag to `true`, instead, the
118 /// end-of-stream will be forwarded to the application layer above TLS.
119 ///
120 /// This makes the application vulnerable to truncation attacks unless the
121 /// application layer itself verifies that the amount of data received equals
122 /// the amount of data expected, such as HTTP with the Content-Length header.
123 allow_truncation_attacks: bool = false,
124 write_buffer: []u8,
125 /// Asserted to have capacity at least `min_buffer_len`.
126 read_buffer: []u8,
127 /// Populated when `error.TlsAlert` is returned from `init`.
128 alert: ?*tls.Alert = null,
119129};
120130
121131const InitError = error{
......@@ -173,14 +183,8 @@ const InitError = error{
173183/// `host` is only borrowed during this function call.
174184///
175185/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
176pub fn init(
177 client: *Client,
178 input: *std.io.Reader,
179 output: *Writer,
180 options: Options,
181) InitError!void {
186pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client {
182187 assert(input.buffer.len >= min_buffer_len);
183 client.alert = null;
184188 const host = switch (options.host) {
185189 .no_verification => "",
186190 .explicit => |host| host,
......@@ -411,7 +415,7 @@ pub fn init(
411415 switch (ct) {
412416 .alert => {
413417 ctd.ensure(2) catch continue :fragment;
414 client.alert = .{
418 if (options.alert) |a| a.* = .{
415419 .level = ctd.decode(tls.Alert.Level),
416420 .description = ctd.decode(tls.Alert.Description),
417421 };
......@@ -852,9 +856,28 @@ pub fn init(
852856 else => unreachable,
853857 },
854858 };
855 client.* = .{
859 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
860 .client_key_seq = key_seq,
861 .server_key_seq = key_seq,
862 .client_random = client_hello_rand,
863 .writer = ssl_key_log.writer,
864 };
865 return .{
856866 .input = input,
867 .reader = .{
868 .buffer = options.read_buffer,
869 .vtable = &.{ .stream = stream },
870 .seek = 0,
871 .end = 0,
872 },
857873 .output = output,
874 .writer = .{
875 .buffer = options.write_buffer,
876 .vtable = &.{
877 .drain = drain,
878 .sendFile = Writer.unimplementedSendFile,
879 },
880 },
858881 .tls_version = tls_version,
859882 .read_seq = switch (tls_version) {
860883 .tls_1_3 => 0,
......@@ -867,17 +890,10 @@ pub fn init(
867890 else => unreachable,
868891 },
869892 .received_close_notify = false,
870 .allow_truncation_attacks = false,
893 .allow_truncation_attacks = options.allow_truncation_attacks,
871894 .application_cipher = app_cipher,
872895 .ssl_key_log = options.ssl_key_log,
873896 };
874 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
875 .client_key_seq = key_seq,
876 .server_key_seq = key_seq,
877 .client_random = client_hello_rand,
878 .writer = ssl_key_log.writer,
879 };
880 return;
881897 },
882898 else => return error.TlsUnexpectedMessage,
883899 }
......@@ -891,25 +907,9 @@ pub fn init(
891907 }
892908}
893909
894pub fn reader(c: *Client) Reader {
895 return .{
896 .context = c,
897 .vtable = &.{ .read = read },
898 };
899}
900
901pub fn writer(c: *Client) Writer {
902 return .{
903 .context = c,
904 .vtable = &.{
905 .writeSplat = writeSplat,
906 .writeFile = Writer.unimplementedWriteFile,
907 },
908 };
909}
910
911fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize {
912 const c: *Client = @alignCast(@ptrCast(context));
910fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
911 const c: *Client = @fieldParentPtr("writer", w);
912 if (true) @panic("update to use the buffer and flush");
913913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
914914 const output = c.output;
915915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
......@@ -1043,8 +1043,8 @@ pub fn eof(c: Client) bool {
10431043 return c.received_close_notify;
10441044}
10451045
1046fn read(context: ?*anyopaque, bw: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
1047 const c: *Client = @ptrCast(@alignCast(context));
1046fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
1047 const c: *Client = @fieldParentPtr("reader", r);
10481048 if (c.eof()) return error.EndOfStream;
10491049 const input = c.input;
10501050 // If at least one full encrypted record is not buffered, read once.
......@@ -1214,7 +1214,7 @@ fn read(context: ?*anyopaque, bw: *Writer, limit: std.io.Limit) Reader.StreamErr
12141214 },
12151215 .application_data => {
12161216 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);
1217 try bw.writeAll(cleartext);
1217 try w.writeAll(cleartext);
12181218 return cleartext.len;
12191219 },
12201220 else => return failRead(c, error.TlsUnexpectedMessage),
......@@ -1226,8 +1226,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
12261226 return error.ReadFailed;
12271227}
12281228
1229fn logSecrets(bw: *Writer, context: anytype, secrets: anytype) void {
1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++
1229fn logSecrets(w: *Writer, context: anytype, secrets: anytype) void {
1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++
12311231 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
12321232 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
12331233 context.client_random,
lib/std/fs/File.zig-2
......@@ -943,7 +943,6 @@ pub const Reader = struct {
943943
944944 pub fn initInterface(buffer: []u8) std.io.Reader {
945945 return .{
946 .context = undefined,
947946 .vtable = &.{
948947 .stream = Reader.stream,
949948 .discard = Reader.discard,
......@@ -1291,7 +1290,6 @@ pub const Writer = struct {
12911290
12921291 pub fn initInterface(buffer: []u8) std.io.Writer {
12931292 return .{
1294 .context = undefined,
12951293 .vtable = &.{
12961294 .drain = drain,
12971295 .sendFile = sendFile,
lib/std/http.zig+45-62
......@@ -328,6 +328,7 @@ pub const Header = struct {
328328
329329pub const Reader = struct {
330330 in: *std.io.Reader,
331 interface: std.io.Reader,
331332 /// Keeps track of whether the stream is ready to accept a new request,
332333 /// making invalid API usage cause assertion failures rather than HTTP
333334 /// protocol violations.
......@@ -438,37 +439,41 @@ pub const Reader = struct {
438439 buffer: []u8,
439440 transfer_encoding: TransferEncoding,
440441 content_length: ?u64,
441 ) std.io.Reader {
442 ) *std.io.Reader {
442443 assert(reader.state == .received_head);
443 return switch (transfer_encoding) {
444 switch (transfer_encoding) {
444445 .chunked => {
445446 reader.state = .{ .body_remaining_chunk_len = .head };
446 return .{
447 reader.interface = .{
447448 .buffer = buffer,
448 .context = reader,
449 .seek = 0,
450 .end = 0,
449451 .vtable = &.{
450 .read = chunkedRead,
452 .stream = chunkedStream,
451453 .discard = chunkedDiscard,
452454 },
453455 };
456 return &reader.interface;
454457 },
455458 .none => {
456459 if (content_length) |len| {
457460 reader.state = .{ .body_remaining_content_length = len };
458 return .{
461 reader.interface = .{
459462 .buffer = buffer,
460 .context = reader,
463 .seek = 0,
464 .end = 0,
461465 .vtable = &.{
462 .read = contentLengthRead,
466 .stream = contentLengthStream,
463467 .discard = contentLengthDiscard,
464468 },
465469 };
470 return &reader.interface;
466471 } else {
467472 reader.state = .body_none;
468 return reader.in.reader();
473 return reader.in;
469474 }
470475 },
471 };
476 }
472477 }
473478
474479 /// If compressed body has been negotiated this will return decompressed bytes.
......@@ -511,25 +516,25 @@ pub const Reader = struct {
511516 return decompressor.reader(transfer_reader, decompression_buffer, content_encoding);
512517 }
513518
514 fn contentLengthRead(
515 ctx: ?*anyopaque,
516 bw: *Writer,
519 fn contentLengthStream(
520 io_r: *std.io.Reader,
521 w: *Writer,
517522 limit: std.io.Limit,
518523 ) std.io.Reader.StreamError!usize {
519 const reader: *Reader = @alignCast(@ptrCast(ctx));
524 const reader: *Reader = @fieldParentPtr("interface", io_r);
520525 const remaining_content_length = &reader.state.body_remaining_content_length;
521526 const remaining = remaining_content_length.*;
522527 if (remaining == 0) {
523528 reader.state = .ready;
524529 return error.EndOfStream;
525530 }
526 const n = try reader.in.read(bw, limit.min(.limited(remaining)));
531 const n = try reader.in.stream(w, limit.min(.limited(remaining)));
527532 remaining_content_length.* = remaining - n;
528533 return n;
529534 }
530535
531 fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
532 const reader: *Reader = @alignCast(@ptrCast(ctx));
536 fn contentLengthDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {
537 const reader: *Reader = @fieldParentPtr("interface", io_r);
533538 const remaining_content_length = &reader.state.body_remaining_content_length;
534539 const remaining = remaining_content_length.*;
535540 if (remaining == 0) {
......@@ -541,18 +546,14 @@ pub const Reader = struct {
541546 return n;
542547 }
543548
544 fn chunkedRead(
545 ctx: ?*anyopaque,
546 bw: *Writer,
547 limit: std.io.Limit,
548 ) std.io.Reader.StreamError!usize {
549 const reader: *Reader = @alignCast(@ptrCast(ctx));
549 fn chunkedStream(io_r: *std.io.Reader, w: *Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize {
550 const reader: *Reader = @fieldParentPtr("interface", io_r);
550551 const chunk_len_ptr = switch (reader.state) {
551552 .ready => return error.EndOfStream,
552553 .body_remaining_chunk_len => |*x| x,
553554 else => unreachable,
554555 };
555 return chunkedReadEndless(reader, bw, limit, chunk_len_ptr) catch |err| switch (err) {
556 return chunkedReadEndless(reader, w, limit, chunk_len_ptr) catch |err| switch (err) {
556557 error.ReadFailed => return error.ReadFailed,
557558 error.WriteFailed => return error.WriteFailed,
558559 error.EndOfStream => {
......@@ -568,7 +569,7 @@ pub const Reader = struct {
568569
569570 fn chunkedReadEndless(
570571 reader: *Reader,
571 bw: *Writer,
572 w: *Writer,
572573 limit: std.io.Limit,
573574 chunk_len_ptr: *RemainingChunkLen,
574575 ) (BodyError || std.io.Reader.StreamError)!usize {
......@@ -592,7 +593,7 @@ pub const Reader = struct {
592593 }
593594 }
594595 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
595 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));
596 const n = try in.stream(w, limit.min(.limited(cp.chunk_len)));
596597 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
597598 return n;
598599 },
......@@ -608,15 +609,15 @@ pub const Reader = struct {
608609 continue :len .head;
609610 },
610611 else => |remaining_chunk_len| {
611 const n = try in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));
612 const n = try in.stream(w, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));
612613 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
613614 return n;
614615 },
615616 }
616617 }
617618
618 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
619 const reader: *Reader = @alignCast(@ptrCast(ctx));
619 fn chunkedDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {
620 const reader: *Reader = @fieldParentPtr("interface", io_r);
620621 const chunk_len_ptr = switch (reader.state) {
621622 .ready => return error.EndOfStream,
622623 .body_remaining_chunk_len => |*x| x,
......@@ -758,7 +759,6 @@ pub const BodyWriter = struct {
758759 /// state of this other than via methods of `BodyWriter`.
759760 http_protocol_output: *Writer,
760761 state: State,
761 elide: bool,
762762 interface: Writer,
763763
764764 pub const Error = Writer.Error;
......@@ -796,6 +796,10 @@ pub const BodyWriter = struct {
796796 };
797797 };
798798
799 pub fn isEliding(w: *const BodyWriter) bool {
800 return w.interface.vtable.drain == Writer.discardingDrain;
801 }
802
799803 /// Sends all buffered data across `BodyWriter.http_protocol_output`.
800804 pub fn flush(w: *BodyWriter) Error!void {
801805 const out = w.http_protocol_output;
......@@ -825,7 +829,7 @@ pub const BodyWriter = struct {
825829 /// with empty trailers, then flushes the stream to the system. Asserts any
826830 /// started chunk has been completely finished.
827831 ///
828 /// Respects the value of `elide` to omit all data after the headers.
832 /// Respects the value of `isEliding` to omit all data after the headers.
829833 ///
830834 /// See also:
831835 /// * `endUnflushed`
......@@ -841,7 +845,7 @@ pub const BodyWriter = struct {
841845 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
842846 /// end-of-stream message with empty trailers.
843847 ///
844 /// Respects the value of `elide` to omit all data after the headers.
848 /// Respects the value of `isEliding` to omit all data after the headers.
845849 ///
846850 /// See also:
847851 /// * `end`
......@@ -867,7 +871,7 @@ pub const BodyWriter = struct {
867871 ///
868872 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
869873 ///
870 /// Respects the value of `elide` to omit all data after the headers.
874 /// Respects the value of `isEliding` to omit all data after the headers.
871875 ///
872876 /// See also:
873877 /// * `endChunkedUnflushed`
......@@ -883,7 +887,7 @@ pub const BodyWriter = struct {
883887 ///
884888 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
885889 ///
886 /// Respects the value of `elide` to omit all data after the headers.
890 /// Respects the value of `isEliding` to omit all data after the headers.
887891 ///
888892 /// See also:
889893 /// * `endChunked`
......@@ -891,7 +895,7 @@ pub const BodyWriter = struct {
891895 /// * `end`
892896 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void {
893897 const chunked = &w.state.chunked;
894 if (w.elide) {
898 if (w.isEliding()) {
895899 w.state = .end;
896900 return;
897901 }
......@@ -922,7 +926,7 @@ pub const BodyWriter = struct {
922926
923927 fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
924928 const bw: *BodyWriter = @fieldParentPtr("interface", w);
925 assert(!bw.elide);
929 assert(!bw.isEliding());
926930 const out = w.http_protocol_output;
927931 const n = try w.drainTo(out, data, splat);
928932 w.state.content_length -= n;
......@@ -931,7 +935,7 @@ pub const BodyWriter = struct {
931935
932936 fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
933937 const bw: *BodyWriter = @fieldParentPtr("interface", w);
934 assert(!bw.elide);
938 assert(!bw.isEliding());
935939 const out = w.http_protocol_output;
936940 return try w.drainTo(out, data, splat);
937941 }
......@@ -939,13 +943,13 @@ pub const BodyWriter = struct {
939943 /// Returns `null` if size cannot be computed without making any syscalls.
940944 fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
941945 const bw: *BodyWriter = @fieldParentPtr("interface", w);
942 assert(!bw.elide);
946 assert(!bw.isEliding());
943947 return w.sendFileTo(bw.http_protocol_output, file_reader, limit);
944948 }
945949
946950 fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
947951 const bw: *BodyWriter = @fieldParentPtr("interface", w);
948 assert(!bw.elide);
952 assert(!bw.isEliding());
949953 const n = try w.sendFileTo(bw.http_protocol_output, file_reader, limit);
950954 bw.state.content_length -= n;
951955 return n;
......@@ -953,7 +957,7 @@ pub const BodyWriter = struct {
953957
954958 fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
955959 const bw: *BodyWriter = @fieldParentPtr("interface", w);
956 assert(!bw.elide);
960 assert(!bw.isEliding());
957961 const data_len = w.countSendFileUpperBound(file_reader, limit) orelse {
958962 // If the file size is unknown, we cannot lower to a `writeFile` since we would
959963 // have to flush the chunk header before knowing the chunk length.
......@@ -1001,7 +1005,7 @@ pub const BodyWriter = struct {
10011005
10021006 fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
10031007 const bw: *BodyWriter = @fieldParentPtr("interface", w);
1004 assert(!bw.elide);
1008 assert(!bw.isEliding());
10051009 const out = w.http_protocol_output;
10061010 const data_len = Writer.countSplat(w.end, data, splat);
10071011 const chunked = &bw.state.chunked;
......@@ -1059,27 +1063,6 @@ pub const BodyWriter = struct {
10591063 a /= base;
10601064 }
10611065 }
1062
1063 pub fn writer(w: *BodyWriter) Writer {
1064 return if (w.elide) .discarding else .{
1065 .context = w,
1066 .vtable = switch (w.state) {
1067 .none => &.{
1068 .drain = noneDrain,
1069 .sendFile = noneSendFile,
1070 },
1071 .content_length => &.{
1072 .drain = contentLengthDrain,
1073 .sendFile = contentLengthSendFile,
1074 },
1075 .chunked => &.{
1076 .drain = chunkedDrain,
1077 .sendFile = chunkedSendFile,
1078 },
1079 .end => unreachable,
1080 },
1081 };
1082 }
10831066};
10841067
10851068test {
lib/std/http/Client.zig+92-50
......@@ -14,6 +14,7 @@ const Uri = std.Uri;
1414const Allocator = mem.Allocator;
1515const assert = std.debug.assert;
1616const Writer = std.io.Writer;
17const Reader = std.io.Reader;
1718
1819const Client = @This();
1920
......@@ -228,12 +229,6 @@ pub const Connection = struct {
228229 client: *Client,
229230 stream_writer: net.Stream.Writer,
230231 stream_reader: net.Stream.Reader,
231 /// HTTP protocol from client to server.
232 /// This either goes directly to `stream_writer`, or to a TLS client.
233 writer: Writer,
234 /// HTTP protocol from server to client.
235 /// This either comes directly from `stream_reader`, or from a TLS client.
236 reader: std.io.Reader,
237232 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
238233 pool_node: std.DoublyLinkedList.Node,
239234 port: u16,
......@@ -264,10 +259,8 @@ pub const Connection = struct {
264259 plain.* = .{
265260 .connection = .{
266261 .client = client,
267 .stream_writer = stream.writer(),
268 .stream_reader = stream.reader(),
269 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),
270 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
262 .stream_writer = stream.writer(socket_write_buffer),
263 .stream_reader = stream.reader(socket_read_buffer),
271264 .pool_node = .{},
272265 .port = port,
273266 .host_len = @intCast(remote_host.len),
......@@ -297,10 +290,6 @@ pub const Connection = struct {
297290 };
298291
299292 const Tls = struct {
300 /// Data from `client` to `Connection.stream`.
301 writer: Writer,
302 /// Data from `Connection.stream` to `client`.
303 reader: std.io.Reader,
304293 client: std.crypto.tls.Client,
305294 connection: Connection,
306295
......@@ -324,10 +313,8 @@ pub const Connection = struct {
324313 tls.* = .{
325314 .connection = .{
326315 .client = client,
327 .stream_writer = stream.writer(),
328 .stream_reader = stream.reader(),
329 .writer = tls.client.writer().buffered(socket_write_buffer),
330 .reader = tls.client.reader().unbuffered(),
316 .stream_writer = stream.writer(socket_write_buffer),
317 .stream_reader = stream.reader(&.{}),
331318 .pool_node = .{},
332319 .port = port,
333320 .host_len = @intCast(remote_host.len),
......@@ -335,20 +322,22 @@ pub const Connection = struct {
335322 .closing = false,
336323 .protocol = .tls,
337324 },
338 .writer = tls.connection.stream_writer.interface().buffered(tls_write_buffer),
339 .reader = tls.connection.stream_reader.interface().buffered(tls_read_buffer),
340 .client = undefined,
325 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
326 .client = std.crypto.tls.Client.init(
327 tls.connection.stream_reader.interface(),
328 &tls.connection.stream_writer.interface,
329 .{
330 .host = .{ .explicit = remote_host },
331 .ca = .{ .bundle = client.ca_bundle },
332 .ssl_key_log = client.ssl_key_log,
333 .read_buffer = tls_read_buffer,
334 .write_buffer = tls_write_buffer,
335 // This is appropriate for HTTPS because the HTTP headers contain
336 // the content length which is used to detect truncation attacks.
337 .allow_truncation_attacks = true,
338 },
339 ) catch return error.TlsInitializationFailed,
341340 };
342 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
343 tls.client.init(&tls.reader, &tls.writer, .{
344 .host = .{ .explicit = remote_host },
345 .ca = .{ .bundle = client.ca_bundle },
346 .ssl_key_log = client.ssl_key_log,
347 }) catch return error.TlsInitializationFailed;
348 // This is appropriate for HTTPS because the HTTP headers contain
349 // the content length which is used to detect truncation attacks.
350 tls.client.allow_truncation_attacks = true;
351
352341 return tls;
353342 }
354343
......@@ -404,26 +393,52 @@ pub const Connection = struct {
404393 }
405394 }
406395
396 /// HTTP protocol from client to server.
397 /// This either goes directly to `stream_writer`, or to a TLS client.
398 pub fn writer(c: *Connection) *Writer {
399 return switch (c.protocol) {
400 .tls => {
401 if (disable_tls) unreachable;
402 const tls: *Tls = @fieldParentPtr("connection", c);
403 return &tls.client.writer;
404 },
405 .plain => &c.stream_writer.interface,
406 };
407 }
408
409 /// HTTP protocol from server to client.
410 /// This either comes directly from `stream_reader`, or from a TLS client.
411 pub fn reader(c: *const Connection) *Reader {
412 return switch (c.protocol) {
413 .tls => {
414 if (disable_tls) unreachable;
415 const tls: *Tls = @fieldParentPtr("connection", c);
416 return &tls.client.reader;
417 },
418 .plain => c.stream_reader.interface(),
419 };
420 }
421
407422 pub fn flush(c: *Connection) Writer.Error!void {
408 try c.writer.flush();
409423 if (c.protocol == .tls) {
410424 if (disable_tls) unreachable;
411425 const tls: *Tls = @fieldParentPtr("connection", c);
412 try tls.writer.flush();
426 try tls.client.writer.flush();
413427 }
428 try c.stream_writer.interface.flush();
414429 }
415430
416431 /// If the connection is a TLS connection, sends the close_notify alert.
417432 ///
418433 /// Flushes all buffers.
419434 pub fn end(c: *Connection) Writer.Error!void {
420 try c.writer.flush();
421435 if (c.protocol == .tls) {
422436 if (disable_tls) unreachable;
423437 const tls: *Tls = @fieldParentPtr("connection", c);
424438 try tls.client.end();
425 try tls.writer.flush();
439 try tls.client.writer.flush();
426440 }
441 try c.stream_writer.interface.flush();
427442 }
428443};
429444
......@@ -660,14 +675,14 @@ pub const Response = struct {
660675
661676 /// If compressed body has been negotiated this will return compressed bytes.
662677 ///
663 /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is
678 /// If the returned `Reader` returns `error.ReadFailed` the error is
664679 /// available via `bodyErr`.
665680 ///
666681 /// Asserts that this function is only called once.
667682 ///
668683 /// See also:
669684 /// * `readerDecompressing`
670 pub fn reader(response: *Response, buffer: []u8) std.io.Reader {
685 pub fn reader(response: *Response, buffer: []u8) Reader {
671686 const req = response.request;
672687 if (!req.method.responseHasBody()) return .ending;
673688 const head = &response.head;
......@@ -676,7 +691,7 @@ pub const Response = struct {
676691
677692 /// If compressed body has been negotiated this will return decompressed bytes.
678693 ///
679 /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is
694 /// If the returned `Reader` returns `error.ReadFailed` the error is
680695 /// available via `bodyErr`.
681696 ///
682697 /// Asserts that this function is only called once.
......@@ -687,7 +702,7 @@ pub const Response = struct {
687702 response: *Response,
688703 decompressor: *http.Decompressor,
689704 decompression_buffer: []u8,
690 ) std.io.Reader {
705 ) Reader {
691706 const head = &response.head;
692707 return response.request.reader.bodyReaderDecompressing(
693708 head.transfer_encoding,
......@@ -698,7 +713,7 @@ pub const Response = struct {
698713 );
699714 }
700715
701 /// After receiving `error.ReadFailed` from the `std.io.Reader` returned by
716 /// After receiving `error.ReadFailed` from the `Reader` returned by
702717 /// `reader` or `readerDecompressing`, this function accesses the
703718 /// more specific error code.
704719 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {
......@@ -835,8 +850,8 @@ pub const Request = struct {
835850 ///
836851 /// See also:
837852 /// * `sendBodyUnflushed`
838 pub fn sendBody(r: *Request) Writer.Error!http.BodyWriter {
839 const result = try sendBodyUnflushed(r);
853 pub fn sendBody(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
854 const result = try sendBodyUnflushed(r, buffer);
840855 try r.connection.?.flush();
841856 return result;
842857 }
......@@ -846,17 +861,44 @@ pub const Request = struct {
846861 ///
847862 /// See also:
848863 /// * `sendBody`
849 pub fn sendBodyUnflushed(r: *Request) Writer.Error!http.BodyWriter {
864 pub fn sendBodyUnflushed(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
850865 assert(r.method.requestHasBody());
851866 try sendHead(r);
852 return .{
853 .http_protocol_output = &r.connection.?.writer,
854 .state = switch (r.transfer_encoding) {
855 .chunked => .{ .chunked = .init },
856 .content_length => |len| .{ .content_length = len },
857 .none => .none,
867 const http_protocol_output = &r.connection.?.writer;
868 return switch (r.transfer_encoding) {
869 .chunked => .{
870 .http_protocol_output = http_protocol_output,
871 .state = .{ .chunked = .init },
872 .interface = .{
873 .buffer = buffer,
874 .interface = &.{
875 .drain = http.BodyWriter.chunkedDrain,
876 .sendFile = http.BodyWriter.chunkedSendFile,
877 },
878 },
879 },
880 .content_length => |len| .{
881 .http_protocol_output = http_protocol_output,
882 .state = .{ .content_length = len },
883 .interface = .{
884 .buffer = buffer,
885 .interface = &.{
886 .drain = http.BodyWriter.contentLengthDrain,
887 .sendFile = http.BodyWriter.contentLengthSendFile,
888 },
889 },
890 },
891 .none => .{
892 .http_protocol_output = http_protocol_output,
893 .state = .none,
894 .interface = .{
895 .buffer = buffer,
896 .interface = &.{
897 .drain = http.BodyWriter.noneDrain,
898 .sendFile = http.BodyWriter.noneSendFile,
899 },
900 },
858901 },
859 .elide = false,
860902 };
861903 }
862904
lib/std/http/Server.zig+33-9
......@@ -381,6 +381,8 @@ pub const Request = struct {
381381 content_length: ?u64 = null,
382382 /// Options that are shared with the `respond` method.
383383 respond_options: RespondOptions = .{},
384 /// Used by `http.BodyWriter`.
385 buffer: []u8,
384386 };
385387
386388 /// The header is not guaranteed to be sent until `BodyWriter.flush` or
......@@ -436,16 +438,38 @@ pub const Request = struct {
436438
437439 try out.writeAll("\r\n");
438440 const elide_body = request.head.method == .HEAD;
439
440 return .{
441 const state: http.BodyWriter.State = if (o.transfer_encoding) |te| switch (te) {
442 .chunked => .{ .chunked = .init },
443 .none => .none,
444 } else if (options.content_length) |len| .{
445 .content_length = len,
446 } else .{ .chunked = .init };
447
448 return if (elide_body) .{
449 .http_protocol_output = request.server.out,
450 .state = state,
451 .interface = .discarding(options.buffer),
452 } else .{
441453 .http_protocol_output = request.server.out,
442 .state = if (o.transfer_encoding) |te| switch (te) {
443 .chunked => .{ .chunked = .init },
444 .none => .none,
445 } else if (options.content_length) |len| .{
446 .content_length = len,
447 } else .{ .chunked = .init },
448 .elide = elide_body,
454 .state = state,
455 .interface = .{
456 .buffer = options.buffer,
457 .vtable = switch (state) {
458 .none => &.{
459 .drain = http.BodyWriter.noneDrain,
460 .sendFile = http.BodyWriter.noneSendFile,
461 },
462 .content_length => &.{
463 .drain = http.BodyWriter.contentLengthDrain,
464 .sendFile = http.BodyWriter.contentLengthSendFile,
465 },
466 .chunked => &.{
467 .drain = http.BodyWriter.chunkedDrain,
468 .sendFile = http.BodyWriter.chunkedSendFile,
469 },
470 .end => unreachable,
471 },
472 },
449473 };
450474 }
451475
lib/std/io/Reader.zig-9
......@@ -13,7 +13,6 @@ const Limit = std.io.Limit;
1313
1414pub const Limited = @import("Reader/Limited.zig");
1515
16context: ?*anyopaque = null,
1716vtable: *const VTable,
1817buffer: []u8,
1918/// Number of bytes which have been consumed from `buffer`.
......@@ -88,7 +87,6 @@ pub const ShortError = error{
8887};
8988
9089pub const failing: Reader = .{
91 .context = undefined,
9290 .vtable = &.{
9391 .read = failingStream,
9492 .discard = failingDiscard,
......@@ -107,7 +105,6 @@ pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
107105/// Constructs a `Reader` such that it will read from `buffer` and then end.
108106pub fn fixed(buffer: []const u8) Reader {
109107 return .{
110 .context = undefined,
111108 .vtable = &.{
112109 .stream = endingStream,
113110 .discard = endingDiscard,
......@@ -1402,12 +1399,6 @@ test "readAlloc when the backing reader provides one byte at a time" {
14021399 self.curr += 1;
14031400 return 1;
14041401 }
1405
1406 fn reader(self: *@This()) std.io.Reader {
1407 return .{
1408 .context = self,
1409 };
1410 }
14111402 };
14121403
14131404 const str = "This is a test";
lib/std/io/Writer.zig-6
......@@ -9,12 +9,6 @@ const File = std.fs.File;
99const testing = std.testing;
1010const Allocator = std.mem.Allocator;
1111
12/// There are two strategies for obtaining context; one can use this field, or
13/// embed the `Writer` and use `@fieldParentPtr`. This field must be either set
14/// to a valid pointer or left as `null` because the interface will sometimes
15/// check if this pointer value is a known special value, for example to make
16/// `writableVector` work.
17context: ?*anyopaque = null,
1812vtable: *const VTable,
1913/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.
2014buffer: []u8,