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;...@@ -21,11 +21,15 @@ const array = tls.array;
21/// here via `reader`.21/// here via `reader`.
22///22///
23/// The buffer is asserted to have capacity at least `min_buffer_len`.23/// 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
26/// The encrypted stream from the client to the server. Bytes are pushed here28/// The encrypted stream from the client to the server. Bytes are pushed here
27/// via `writer`.29/// via `writer`.
28output: *Writer,30output: *Writer,
31/// The plaintext stream from the client to the server.
32writer: Writer,
2933
30/// Populated when `error.TlsAlert` is returned.34/// Populated when `error.TlsAlert` is returned.
31alert: ?tls.Alert = null,35alert: ?tls.Alert = null,
...@@ -36,14 +40,6 @@ write_seq: u64,...@@ -36,14 +40,6 @@ write_seq: u64,
36/// When this is true, the stream may still not be at the end because there40/// When this is true, the stream may still not be at the end because there
37/// may be data in the input buffer.41/// may be data in the input buffer.
38received_close_notify: bool,42received_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.
47allow_truncation_attacks: bool,43allow_truncation_attacks: bool,
48application_cipher: tls.ApplicationCipher,44application_cipher: tls.ApplicationCipher,
4945
...@@ -85,7 +81,7 @@ pub const SslKeyLog = struct {...@@ -85,7 +81,7 @@ pub const SslKeyLog = struct {
85 }81 }
86};82};
8783
88/// The `std.io.Reader` supplied to `init` requires a buffer capacity84/// The `Reader` supplied to `init` requires a buffer capacity
89/// at least this amount.85/// at least this amount.
90pub const min_buffer_len = tls.max_ciphertext_record_len;86pub const min_buffer_len = tls.max_ciphertext_record_len;
9187
...@@ -116,6 +112,20 @@ pub const Options = struct {...@@ -116,6 +112,20 @@ pub const Options = struct {
116 /// Only the `writer` field is observed during the handshake (`init`).112 /// Only the `writer` field is observed during the handshake (`init`).
117 /// After that, the other fields are populated.113 /// After that, the other fields are populated.
118 ssl_key_log: ?*SslKeyLog = null,114 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,
119};129};
120130
121const InitError = error{131const InitError = error{
...@@ -173,14 +183,8 @@ const InitError = error{...@@ -173,14 +183,8 @@ const InitError = error{
173/// `host` is only borrowed during this function call.183/// `host` is only borrowed during this function call.
174///184///
175/// `input` is asserted to have buffer capacity at least `min_buffer_len`.185/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
176pub fn init(186pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client {
177 client: *Client,
178 input: *std.io.Reader,
179 output: *Writer,
180 options: Options,
181) InitError!void {
182 assert(input.buffer.len >= min_buffer_len);187 assert(input.buffer.len >= min_buffer_len);
183 client.alert = null;
184 const host = switch (options.host) {188 const host = switch (options.host) {
185 .no_verification => "",189 .no_verification => "",
186 .explicit => |host| host,190 .explicit => |host| host,
...@@ -411,7 +415,7 @@ pub fn init(...@@ -411,7 +415,7 @@ pub fn init(
411 switch (ct) {415 switch (ct) {
412 .alert => {416 .alert => {
413 ctd.ensure(2) catch continue :fragment;417 ctd.ensure(2) catch continue :fragment;
414 client.alert = .{418 if (options.alert) |a| a.* = .{
415 .level = ctd.decode(tls.Alert.Level),419 .level = ctd.decode(tls.Alert.Level),
416 .description = ctd.decode(tls.Alert.Description),420 .description = ctd.decode(tls.Alert.Description),
417 };421 };
...@@ -852,9 +856,28 @@ pub fn init(...@@ -852,9 +856,28 @@ pub fn init(
852 else => unreachable,856 else => unreachable,
853 },857 },
854 };858 };
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 .{
856 .input = input,866 .input = input,
867 .reader = .{
868 .buffer = options.read_buffer,
869 .vtable = &.{ .stream = stream },
870 .seek = 0,
871 .end = 0,
872 },
857 .output = output,873 .output = output,
874 .writer = .{
875 .buffer = options.write_buffer,
876 .vtable = &.{
877 .drain = drain,
878 .sendFile = Writer.unimplementedSendFile,
879 },
880 },
858 .tls_version = tls_version,881 .tls_version = tls_version,
859 .read_seq = switch (tls_version) {882 .read_seq = switch (tls_version) {
860 .tls_1_3 => 0,883 .tls_1_3 => 0,
...@@ -867,17 +890,10 @@ pub fn init(...@@ -867,17 +890,10 @@ pub fn init(
867 else => unreachable,890 else => unreachable,
868 },891 },
869 .received_close_notify = false,892 .received_close_notify = false,
870 .allow_truncation_attacks = false,893 .allow_truncation_attacks = options.allow_truncation_attacks,
871 .application_cipher = app_cipher,894 .application_cipher = app_cipher,
872 .ssl_key_log = options.ssl_key_log,895 .ssl_key_log = options.ssl_key_log,
873 };896 };
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;
881 },897 },
882 else => return error.TlsUnexpectedMessage,898 else => return error.TlsUnexpectedMessage,
883 }899 }
...@@ -891,25 +907,9 @@ pub fn init(...@@ -891,25 +907,9 @@ pub fn init(
891 }907 }
892}908}
893909
894pub fn reader(c: *Client) Reader {910fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
895 return .{911 const c: *Client = @fieldParentPtr("writer", w);
896 .context = c,912 if (true) @panic("update to use the buffer and flush");
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));
913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
914 const output = c.output;914 const output = c.output;
915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
...@@ -1043,8 +1043,8 @@ pub fn eof(c: Client) bool {...@@ -1043,8 +1043,8 @@ pub fn eof(c: Client) bool {
1043 return c.received_close_notify;1043 return c.received_close_notify;
1044}1044}
10451045
1046fn read(context: ?*anyopaque, bw: *Writer, limit: std.io.Limit) Reader.StreamError!usize {1046fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
1047 const c: *Client = @ptrCast(@alignCast(context));1047 const c: *Client = @fieldParentPtr("reader", r);
1048 if (c.eof()) return error.EndOfStream;1048 if (c.eof()) return error.EndOfStream;
1049 const input = c.input;1049 const input = c.input;
1050 // If at least one full encrypted record is not buffered, read once.1050 // 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...@@ -1214,7 +1214,7 @@ fn read(context: ?*anyopaque, bw: *Writer, limit: std.io.Limit) Reader.StreamErr
1214 },1214 },
1215 .application_data => {1215 .application_data => {
1216 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);1216 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);
1217 try bw.writeAll(cleartext);1217 try w.writeAll(cleartext);
1218 return cleartext.len;1218 return cleartext.len;
1219 },1219 },
1220 else => return failRead(c, error.TlsUnexpectedMessage),1220 else => return failRead(c, error.TlsUnexpectedMessage),
...@@ -1226,8 +1226,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {...@@ -1226,8 +1226,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
1226 return error.ReadFailed;1226 return error.ReadFailed;
1227}1227}
12281228
1229fn logSecrets(bw: *Writer, context: anytype, secrets: anytype) void {1229fn logSecrets(w: *Writer, context: anytype, secrets: anytype) void {
1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++
1231 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++1231 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
1232 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{1232 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
1233 context.client_random,1233 context.client_random,
lib/std/fs/File.zig-2
...@@ -943,7 +943,6 @@ pub const Reader = struct {...@@ -943,7 +943,6 @@ pub const Reader = struct {
943943
944 pub fn initInterface(buffer: []u8) std.io.Reader {944 pub fn initInterface(buffer: []u8) std.io.Reader {
945 return .{945 return .{
946 .context = undefined,
947 .vtable = &.{946 .vtable = &.{
948 .stream = Reader.stream,947 .stream = Reader.stream,
949 .discard = Reader.discard,948 .discard = Reader.discard,
...@@ -1291,7 +1290,6 @@ pub const Writer = struct {...@@ -1291,7 +1290,6 @@ pub const Writer = struct {
12911290
1292 pub fn initInterface(buffer: []u8) std.io.Writer {1291 pub fn initInterface(buffer: []u8) std.io.Writer {
1293 return .{1292 return .{
1294 .context = undefined,
1295 .vtable = &.{1293 .vtable = &.{
1296 .drain = drain,1294 .drain = drain,
1297 .sendFile = sendFile,1295 .sendFile = sendFile,
lib/std/http.zig+45-62
...@@ -328,6 +328,7 @@ pub const Header = struct {...@@ -328,6 +328,7 @@ pub const Header = struct {
328328
329pub const Reader = struct {329pub const Reader = struct {
330 in: *std.io.Reader,330 in: *std.io.Reader,
331 interface: std.io.Reader,
331 /// Keeps track of whether the stream is ready to accept a new request,332 /// Keeps track of whether the stream is ready to accept a new request,
332 /// making invalid API usage cause assertion failures rather than HTTP333 /// making invalid API usage cause assertion failures rather than HTTP
333 /// protocol violations.334 /// protocol violations.
...@@ -438,37 +439,41 @@ pub const Reader = struct {...@@ -438,37 +439,41 @@ pub const Reader = struct {
438 buffer: []u8,439 buffer: []u8,
439 transfer_encoding: TransferEncoding,440 transfer_encoding: TransferEncoding,
440 content_length: ?u64,441 content_length: ?u64,
441 ) std.io.Reader {442 ) *std.io.Reader {
442 assert(reader.state == .received_head);443 assert(reader.state == .received_head);
443 return switch (transfer_encoding) {444 switch (transfer_encoding) {
444 .chunked => {445 .chunked => {
445 reader.state = .{ .body_remaining_chunk_len = .head };446 reader.state = .{ .body_remaining_chunk_len = .head };
446 return .{447 reader.interface = .{
447 .buffer = buffer,448 .buffer = buffer,
448 .context = reader,449 .seek = 0,
450 .end = 0,
449 .vtable = &.{451 .vtable = &.{
450 .read = chunkedRead,452 .stream = chunkedStream,
451 .discard = chunkedDiscard,453 .discard = chunkedDiscard,
452 },454 },
453 };455 };
456 return &reader.interface;
454 },457 },
455 .none => {458 .none => {
456 if (content_length) |len| {459 if (content_length) |len| {
457 reader.state = .{ .body_remaining_content_length = len };460 reader.state = .{ .body_remaining_content_length = len };
458 return .{461 reader.interface = .{
459 .buffer = buffer,462 .buffer = buffer,
460 .context = reader,463 .seek = 0,
464 .end = 0,
461 .vtable = &.{465 .vtable = &.{
462 .read = contentLengthRead,466 .stream = contentLengthStream,
463 .discard = contentLengthDiscard,467 .discard = contentLengthDiscard,
464 },468 },
465 };469 };
470 return &reader.interface;
466 } else {471 } else {
467 reader.state = .body_none;472 reader.state = .body_none;
468 return reader.in.reader();473 return reader.in;
469 }474 }
470 },475 },
471 };476 }
472 }477 }
473478
474 /// If compressed body has been negotiated this will return decompressed bytes.479 /// If compressed body has been negotiated this will return decompressed bytes.
...@@ -511,25 +516,25 @@ pub const Reader = struct {...@@ -511,25 +516,25 @@ pub const Reader = struct {
511 return decompressor.reader(transfer_reader, decompression_buffer, content_encoding);516 return decompressor.reader(transfer_reader, decompression_buffer, content_encoding);
512 }517 }
513518
514 fn contentLengthRead(519 fn contentLengthStream(
515 ctx: ?*anyopaque,520 io_r: *std.io.Reader,
516 bw: *Writer,521 w: *Writer,
517 limit: std.io.Limit,522 limit: std.io.Limit,
518 ) std.io.Reader.StreamError!usize {523 ) std.io.Reader.StreamError!usize {
519 const reader: *Reader = @alignCast(@ptrCast(ctx));524 const reader: *Reader = @fieldParentPtr("interface", io_r);
520 const remaining_content_length = &reader.state.body_remaining_content_length;525 const remaining_content_length = &reader.state.body_remaining_content_length;
521 const remaining = remaining_content_length.*;526 const remaining = remaining_content_length.*;
522 if (remaining == 0) {527 if (remaining == 0) {
523 reader.state = .ready;528 reader.state = .ready;
524 return error.EndOfStream;529 return error.EndOfStream;
525 }530 }
526 const n = try reader.in.read(bw, limit.min(.limited(remaining)));531 const n = try reader.in.stream(w, limit.min(.limited(remaining)));
527 remaining_content_length.* = remaining - n;532 remaining_content_length.* = remaining - n;
528 return n;533 return n;
529 }534 }
530535
531 fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {536 fn contentLengthDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {
532 const reader: *Reader = @alignCast(@ptrCast(ctx));537 const reader: *Reader = @fieldParentPtr("interface", io_r);
533 const remaining_content_length = &reader.state.body_remaining_content_length;538 const remaining_content_length = &reader.state.body_remaining_content_length;
534 const remaining = remaining_content_length.*;539 const remaining = remaining_content_length.*;
535 if (remaining == 0) {540 if (remaining == 0) {
...@@ -541,18 +546,14 @@ pub const Reader = struct {...@@ -541,18 +546,14 @@ pub const Reader = struct {
541 return n;546 return n;
542 }547 }
543548
544 fn chunkedRead(549 fn chunkedStream(io_r: *std.io.Reader, w: *Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize {
545 ctx: ?*anyopaque,550 const reader: *Reader = @fieldParentPtr("interface", io_r);
546 bw: *Writer,
547 limit: std.io.Limit,
548 ) std.io.Reader.StreamError!usize {
549 const reader: *Reader = @alignCast(@ptrCast(ctx));
550 const chunk_len_ptr = switch (reader.state) {551 const chunk_len_ptr = switch (reader.state) {
551 .ready => return error.EndOfStream,552 .ready => return error.EndOfStream,
552 .body_remaining_chunk_len => |*x| x,553 .body_remaining_chunk_len => |*x| x,
553 else => unreachable,554 else => unreachable,
554 };555 };
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) {
556 error.ReadFailed => return error.ReadFailed,557 error.ReadFailed => return error.ReadFailed,
557 error.WriteFailed => return error.WriteFailed,558 error.WriteFailed => return error.WriteFailed,
558 error.EndOfStream => {559 error.EndOfStream => {
...@@ -568,7 +569,7 @@ pub const Reader = struct {...@@ -568,7 +569,7 @@ pub const Reader = struct {
568569
569 fn chunkedReadEndless(570 fn chunkedReadEndless(
570 reader: *Reader,571 reader: *Reader,
571 bw: *Writer,572 w: *Writer,
572 limit: std.io.Limit,573 limit: std.io.Limit,
573 chunk_len_ptr: *RemainingChunkLen,574 chunk_len_ptr: *RemainingChunkLen,
574 ) (BodyError || std.io.Reader.StreamError)!usize {575 ) (BodyError || std.io.Reader.StreamError)!usize {
...@@ -592,7 +593,7 @@ pub const Reader = struct {...@@ -592,7 +593,7 @@ pub const Reader = struct {
592 }593 }
593 }594 }
594 if (cp.chunk_len == 0) return parseTrailers(reader, 0);595 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)));
596 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);597 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
597 return n;598 return n;
598 },599 },
...@@ -608,15 +609,15 @@ pub const Reader = struct {...@@ -608,15 +609,15 @@ pub const Reader = struct {
608 continue :len .head;609 continue :len .head;
609 },610 },
610 else => |remaining_chunk_len| {611 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)));
612 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);613 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
613 return n;614 return n;
614 },615 },
615 }616 }
616 }617 }
617618
618 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {619 fn chunkedDiscard(io_r: *std.io.Reader, limit: std.io.Limit) std.io.Reader.Error!usize {
619 const reader: *Reader = @alignCast(@ptrCast(ctx));620 const reader: *Reader = @fieldParentPtr("interface", io_r);
620 const chunk_len_ptr = switch (reader.state) {621 const chunk_len_ptr = switch (reader.state) {
621 .ready => return error.EndOfStream,622 .ready => return error.EndOfStream,
622 .body_remaining_chunk_len => |*x| x,623 .body_remaining_chunk_len => |*x| x,
...@@ -758,7 +759,6 @@ pub const BodyWriter = struct {...@@ -758,7 +759,6 @@ pub const BodyWriter = struct {
758 /// state of this other than via methods of `BodyWriter`.759 /// state of this other than via methods of `BodyWriter`.
759 http_protocol_output: *Writer,760 http_protocol_output: *Writer,
760 state: State,761 state: State,
761 elide: bool,
762 interface: Writer,762 interface: Writer,
763763
764 pub const Error = Writer.Error;764 pub const Error = Writer.Error;
...@@ -796,6 +796,10 @@ pub const BodyWriter = struct {...@@ -796,6 +796,10 @@ pub const BodyWriter = struct {
796 };796 };
797 };797 };
798798
799 pub fn isEliding(w: *const BodyWriter) bool {
800 return w.interface.vtable.drain == Writer.discardingDrain;
801 }
802
799 /// Sends all buffered data across `BodyWriter.http_protocol_output`.803 /// Sends all buffered data across `BodyWriter.http_protocol_output`.
800 pub fn flush(w: *BodyWriter) Error!void {804 pub fn flush(w: *BodyWriter) Error!void {
801 const out = w.http_protocol_output;805 const out = w.http_protocol_output;
...@@ -825,7 +829,7 @@ pub const BodyWriter = struct {...@@ -825,7 +829,7 @@ pub const BodyWriter = struct {
825 /// with empty trailers, then flushes the stream to the system. Asserts any829 /// with empty trailers, then flushes the stream to the system. Asserts any
826 /// started chunk has been completely finished.830 /// started chunk has been completely finished.
827 ///831 ///
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.
829 ///833 ///
830 /// See also:834 /// See also:
831 /// * `endUnflushed`835 /// * `endUnflushed`
...@@ -841,7 +845,7 @@ pub const BodyWriter = struct {...@@ -841,7 +845,7 @@ pub const BodyWriter = struct {
841 /// Otherwise, transfer-encoding: chunked is being used, and it writes the845 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
842 /// end-of-stream message with empty trailers.846 /// end-of-stream message with empty trailers.
843 ///847 ///
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.
845 ///849 ///
846 /// See also:850 /// See also:
847 /// * `end`851 /// * `end`
...@@ -867,7 +871,7 @@ pub const BodyWriter = struct {...@@ -867,7 +871,7 @@ pub const BodyWriter = struct {
867 ///871 ///
868 /// Asserts that the BodyWriter is using transfer-encoding: chunked.872 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
869 ///873 ///
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.
871 ///875 ///
872 /// See also:876 /// See also:
873 /// * `endChunkedUnflushed`877 /// * `endChunkedUnflushed`
...@@ -883,7 +887,7 @@ pub const BodyWriter = struct {...@@ -883,7 +887,7 @@ pub const BodyWriter = struct {
883 ///887 ///
884 /// Asserts that the BodyWriter is using transfer-encoding: chunked.888 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
885 ///889 ///
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.
887 ///891 ///
888 /// See also:892 /// See also:
889 /// * `endChunked`893 /// * `endChunked`
...@@ -891,7 +895,7 @@ pub const BodyWriter = struct {...@@ -891,7 +895,7 @@ pub const BodyWriter = struct {
891 /// * `end`895 /// * `end`
892 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void {896 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void {
893 const chunked = &w.state.chunked;897 const chunked = &w.state.chunked;
894 if (w.elide) {898 if (w.isEliding()) {
895 w.state = .end;899 w.state = .end;
896 return;900 return;
897 }901 }
...@@ -922,7 +926,7 @@ pub const BodyWriter = struct {...@@ -922,7 +926,7 @@ pub const BodyWriter = struct {
922926
923 fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {927 fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
924 const bw: *BodyWriter = @fieldParentPtr("interface", w);928 const bw: *BodyWriter = @fieldParentPtr("interface", w);
925 assert(!bw.elide);929 assert(!bw.isEliding());
926 const out = w.http_protocol_output;930 const out = w.http_protocol_output;
927 const n = try w.drainTo(out, data, splat);931 const n = try w.drainTo(out, data, splat);
928 w.state.content_length -= n;932 w.state.content_length -= n;
...@@ -931,7 +935,7 @@ pub const BodyWriter = struct {...@@ -931,7 +935,7 @@ pub const BodyWriter = struct {
931935
932 fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {936 fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
933 const bw: *BodyWriter = @fieldParentPtr("interface", w);937 const bw: *BodyWriter = @fieldParentPtr("interface", w);
934 assert(!bw.elide);938 assert(!bw.isEliding());
935 const out = w.http_protocol_output;939 const out = w.http_protocol_output;
936 return try w.drainTo(out, data, splat);940 return try w.drainTo(out, data, splat);
937 }941 }
...@@ -939,13 +943,13 @@ pub const BodyWriter = struct {...@@ -939,13 +943,13 @@ pub const BodyWriter = struct {
939 /// Returns `null` if size cannot be computed without making any syscalls.943 /// Returns `null` if size cannot be computed without making any syscalls.
940 fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {944 fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
941 const bw: *BodyWriter = @fieldParentPtr("interface", w);945 const bw: *BodyWriter = @fieldParentPtr("interface", w);
942 assert(!bw.elide);946 assert(!bw.isEliding());
943 return w.sendFileTo(bw.http_protocol_output, file_reader, limit);947 return w.sendFileTo(bw.http_protocol_output, file_reader, limit);
944 }948 }
945949
946 fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {950 fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
947 const bw: *BodyWriter = @fieldParentPtr("interface", w);951 const bw: *BodyWriter = @fieldParentPtr("interface", w);
948 assert(!bw.elide);952 assert(!bw.isEliding());
949 const n = try w.sendFileTo(bw.http_protocol_output, file_reader, limit);953 const n = try w.sendFileTo(bw.http_protocol_output, file_reader, limit);
950 bw.state.content_length -= n;954 bw.state.content_length -= n;
951 return n;955 return n;
...@@ -953,7 +957,7 @@ pub const BodyWriter = struct {...@@ -953,7 +957,7 @@ pub const BodyWriter = struct {
953957
954 fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {958 fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
955 const bw: *BodyWriter = @fieldParentPtr("interface", w);959 const bw: *BodyWriter = @fieldParentPtr("interface", w);
956 assert(!bw.elide);960 assert(!bw.isEliding());
957 const data_len = w.countSendFileUpperBound(file_reader, limit) orelse {961 const data_len = w.countSendFileUpperBound(file_reader, limit) orelse {
958 // If the file size is unknown, we cannot lower to a `writeFile` since we would962 // If the file size is unknown, we cannot lower to a `writeFile` since we would
959 // have to flush the chunk header before knowing the chunk length.963 // have to flush the chunk header before knowing the chunk length.
...@@ -1001,7 +1005,7 @@ pub const BodyWriter = struct {...@@ -1001,7 +1005,7 @@ pub const BodyWriter = struct {
10011005
1002 fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {1006 fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
1003 const bw: *BodyWriter = @fieldParentPtr("interface", w);1007 const bw: *BodyWriter = @fieldParentPtr("interface", w);
1004 assert(!bw.elide);1008 assert(!bw.isEliding());
1005 const out = w.http_protocol_output;1009 const out = w.http_protocol_output;
1006 const data_len = Writer.countSplat(w.end, data, splat);1010 const data_len = Writer.countSplat(w.end, data, splat);
1007 const chunked = &bw.state.chunked;1011 const chunked = &bw.state.chunked;
...@@ -1059,27 +1063,6 @@ pub const BodyWriter = struct {...@@ -1059,27 +1063,6 @@ pub const BodyWriter = struct {
1059 a /= base;1063 a /= base;
1060 }1064 }
1061 }1065 }
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 }
1083};1066};
10841067
1085test {1068test {
lib/std/http/Client.zig+92-50
...@@ -14,6 +14,7 @@ const Uri = std.Uri;...@@ -14,6 +14,7 @@ const Uri = std.Uri;
14const Allocator = mem.Allocator;14const Allocator = mem.Allocator;
15const assert = std.debug.assert;15const assert = std.debug.assert;
16const Writer = std.io.Writer;16const Writer = std.io.Writer;
17const Reader = std.io.Reader;
1718
18const Client = @This();19const Client = @This();
1920
...@@ -228,12 +229,6 @@ pub const Connection = struct {...@@ -228,12 +229,6 @@ pub const Connection = struct {
228 client: *Client,229 client: *Client,
229 stream_writer: net.Stream.Writer,230 stream_writer: net.Stream.Writer,
230 stream_reader: net.Stream.Reader,231 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,
237 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.232 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
238 pool_node: std.DoublyLinkedList.Node,233 pool_node: std.DoublyLinkedList.Node,
239 port: u16,234 port: u16,
...@@ -264,10 +259,8 @@ pub const Connection = struct {...@@ -264,10 +259,8 @@ pub const Connection = struct {
264 plain.* = .{259 plain.* = .{
265 .connection = .{260 .connection = .{
266 .client = client,261 .client = client,
267 .stream_writer = stream.writer(),262 .stream_writer = stream.writer(socket_write_buffer),
268 .stream_reader = stream.reader(),263 .stream_reader = stream.reader(socket_read_buffer),
269 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),
270 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
271 .pool_node = .{},264 .pool_node = .{},
272 .port = port,265 .port = port,
273 .host_len = @intCast(remote_host.len),266 .host_len = @intCast(remote_host.len),
...@@ -297,10 +290,6 @@ pub const Connection = struct {...@@ -297,10 +290,6 @@ pub const Connection = struct {
297 };290 };
298291
299 const Tls = struct {292 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,
304 client: std.crypto.tls.Client,293 client: std.crypto.tls.Client,
305 connection: Connection,294 connection: Connection,
306295
...@@ -324,10 +313,8 @@ pub const Connection = struct {...@@ -324,10 +313,8 @@ pub const Connection = struct {
324 tls.* = .{313 tls.* = .{
325 .connection = .{314 .connection = .{
326 .client = client,315 .client = client,
327 .stream_writer = stream.writer(),316 .stream_writer = stream.writer(socket_write_buffer),
328 .stream_reader = stream.reader(),317 .stream_reader = stream.reader(&.{}),
329 .writer = tls.client.writer().buffered(socket_write_buffer),
330 .reader = tls.client.reader().unbuffered(),
331 .pool_node = .{},318 .pool_node = .{},
332 .port = port,319 .port = port,
333 .host_len = @intCast(remote_host.len),320 .host_len = @intCast(remote_host.len),
...@@ -335,20 +322,22 @@ pub const Connection = struct {...@@ -335,20 +322,22 @@ pub const Connection = struct {
335 .closing = false,322 .closing = false,
336 .protocol = .tls,323 .protocol = .tls,
337 },324 },
338 .writer = tls.connection.stream_writer.interface().buffered(tls_write_buffer),325 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
339 .reader = tls.connection.stream_reader.interface().buffered(tls_read_buffer),326 .client = std.crypto.tls.Client.init(
340 .client = undefined,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,
341 };340 };
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
352 return tls;341 return tls;
353 }342 }
354343
...@@ -404,26 +393,52 @@ pub const Connection = struct {...@@ -404,26 +393,52 @@ pub const Connection = struct {
404 }393 }
405 }394 }
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
407 pub fn flush(c: *Connection) Writer.Error!void {422 pub fn flush(c: *Connection) Writer.Error!void {
408 try c.writer.flush();
409 if (c.protocol == .tls) {423 if (c.protocol == .tls) {
410 if (disable_tls) unreachable;424 if (disable_tls) unreachable;
411 const tls: *Tls = @fieldParentPtr("connection", c);425 const tls: *Tls = @fieldParentPtr("connection", c);
412 try tls.writer.flush();426 try tls.client.writer.flush();
413 }427 }
428 try c.stream_writer.interface.flush();
414 }429 }
415430
416 /// If the connection is a TLS connection, sends the close_notify alert.431 /// If the connection is a TLS connection, sends the close_notify alert.
417 ///432 ///
418 /// Flushes all buffers.433 /// Flushes all buffers.
419 pub fn end(c: *Connection) Writer.Error!void {434 pub fn end(c: *Connection) Writer.Error!void {
420 try c.writer.flush();
421 if (c.protocol == .tls) {435 if (c.protocol == .tls) {
422 if (disable_tls) unreachable;436 if (disable_tls) unreachable;
423 const tls: *Tls = @fieldParentPtr("connection", c);437 const tls: *Tls = @fieldParentPtr("connection", c);
424 try tls.client.end();438 try tls.client.end();
425 try tls.writer.flush();439 try tls.client.writer.flush();
426 }440 }
441 try c.stream_writer.interface.flush();
427 }442 }
428};443};
429444
...@@ -660,14 +675,14 @@ pub const Response = struct {...@@ -660,14 +675,14 @@ pub const Response = struct {
660675
661 /// If compressed body has been negotiated this will return compressed bytes.676 /// If compressed body has been negotiated this will return compressed bytes.
662 ///677 ///
663 /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is678 /// If the returned `Reader` returns `error.ReadFailed` the error is
664 /// available via `bodyErr`.679 /// available via `bodyErr`.
665 ///680 ///
666 /// Asserts that this function is only called once.681 /// Asserts that this function is only called once.
667 ///682 ///
668 /// See also:683 /// See also:
669 /// * `readerDecompressing`684 /// * `readerDecompressing`
670 pub fn reader(response: *Response, buffer: []u8) std.io.Reader {685 pub fn reader(response: *Response, buffer: []u8) Reader {
671 const req = response.request;686 const req = response.request;
672 if (!req.method.responseHasBody()) return .ending;687 if (!req.method.responseHasBody()) return .ending;
673 const head = &response.head;688 const head = &response.head;
...@@ -676,7 +691,7 @@ pub const Response = struct {...@@ -676,7 +691,7 @@ pub const Response = struct {
676691
677 /// If compressed body has been negotiated this will return decompressed bytes.692 /// If compressed body has been negotiated this will return decompressed bytes.
678 ///693 ///
679 /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is694 /// If the returned `Reader` returns `error.ReadFailed` the error is
680 /// available via `bodyErr`.695 /// available via `bodyErr`.
681 ///696 ///
682 /// Asserts that this function is only called once.697 /// Asserts that this function is only called once.
...@@ -687,7 +702,7 @@ pub const Response = struct {...@@ -687,7 +702,7 @@ pub const Response = struct {
687 response: *Response,702 response: *Response,
688 decompressor: *http.Decompressor,703 decompressor: *http.Decompressor,
689 decompression_buffer: []u8,704 decompression_buffer: []u8,
690 ) std.io.Reader {705 ) Reader {
691 const head = &response.head;706 const head = &response.head;
692 return response.request.reader.bodyReaderDecompressing(707 return response.request.reader.bodyReaderDecompressing(
693 head.transfer_encoding,708 head.transfer_encoding,
...@@ -698,7 +713,7 @@ pub const Response = struct {...@@ -698,7 +713,7 @@ pub const Response = struct {
698 );713 );
699 }714 }
700715
701 /// After receiving `error.ReadFailed` from the `std.io.Reader` returned by716 /// After receiving `error.ReadFailed` from the `Reader` returned by
702 /// `reader` or `readerDecompressing`, this function accesses the717 /// `reader` or `readerDecompressing`, this function accesses the
703 /// more specific error code.718 /// more specific error code.
704 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {719 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {
...@@ -835,8 +850,8 @@ pub const Request = struct {...@@ -835,8 +850,8 @@ pub const Request = struct {
835 ///850 ///
836 /// See also:851 /// See also:
837 /// * `sendBodyUnflushed`852 /// * `sendBodyUnflushed`
838 pub fn sendBody(r: *Request) Writer.Error!http.BodyWriter {853 pub fn sendBody(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
839 const result = try sendBodyUnflushed(r);854 const result = try sendBodyUnflushed(r, buffer);
840 try r.connection.?.flush();855 try r.connection.?.flush();
841 return result;856 return result;
842 }857 }
...@@ -846,17 +861,44 @@ pub const Request = struct {...@@ -846,17 +861,44 @@ pub const Request = struct {
846 ///861 ///
847 /// See also:862 /// See also:
848 /// * `sendBody`863 /// * `sendBody`
849 pub fn sendBodyUnflushed(r: *Request) Writer.Error!http.BodyWriter {864 pub fn sendBodyUnflushed(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
850 assert(r.method.requestHasBody());865 assert(r.method.requestHasBody());
851 try sendHead(r);866 try sendHead(r);
852 return .{867 const http_protocol_output = &r.connection.?.writer;
853 .http_protocol_output = &r.connection.?.writer,868 return switch (r.transfer_encoding) {
854 .state = switch (r.transfer_encoding) {869 .chunked => .{
855 .chunked => .{ .chunked = .init },870 .http_protocol_output = http_protocol_output,
856 .content_length => |len| .{ .content_length = len },871 .state = .{ .chunked = .init },
857 .none => .none,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 },
858 },901 },
859 .elide = false,
860 };902 };
861 }903 }
862904
lib/std/http/Server.zig+33-9
...@@ -381,6 +381,8 @@ pub const Request = struct {...@@ -381,6 +381,8 @@ pub const Request = struct {
381 content_length: ?u64 = null,381 content_length: ?u64 = null,
382 /// Options that are shared with the `respond` method.382 /// Options that are shared with the `respond` method.
383 respond_options: RespondOptions = .{},383 respond_options: RespondOptions = .{},
384 /// Used by `http.BodyWriter`.
385 buffer: []u8,
384 };386 };
385387
386 /// The header is not guaranteed to be sent until `BodyWriter.flush` or388 /// The header is not guaranteed to be sent until `BodyWriter.flush` or
...@@ -436,16 +438,38 @@ pub const Request = struct {...@@ -436,16 +438,38 @@ pub const Request = struct {
436438
437 try out.writeAll("\r\n");439 try out.writeAll("\r\n");
438 const elide_body = request.head.method == .HEAD;440 const elide_body = request.head.method == .HEAD;
439441 const state: http.BodyWriter.State = if (o.transfer_encoding) |te| switch (te) {
440 return .{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 .{
441 .http_protocol_output = request.server.out,453 .http_protocol_output = request.server.out,
442 .state = if (o.transfer_encoding) |te| switch (te) {454 .state = state,
443 .chunked => .{ .chunked = .init },455 .interface = .{
444 .none => .none,456 .buffer = options.buffer,
445 } else if (options.content_length) |len| .{457 .vtable = switch (state) {
446 .content_length = len,458 .none => &.{
447 } else .{ .chunked = .init },459 .drain = http.BodyWriter.noneDrain,
448 .elide = elide_body,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 },
449 };473 };
450 }474 }
451475
lib/std/io/Reader.zig-9
...@@ -13,7 +13,6 @@ const Limit = std.io.Limit;...@@ -13,7 +13,6 @@ const Limit = std.io.Limit;
1313
14pub const Limited = @import("Reader/Limited.zig");14pub const Limited = @import("Reader/Limited.zig");
1515
16context: ?*anyopaque = null,
17vtable: *const VTable,16vtable: *const VTable,
18buffer: []u8,17buffer: []u8,
19/// Number of bytes which have been consumed from `buffer`.18/// Number of bytes which have been consumed from `buffer`.
...@@ -88,7 +87,6 @@ pub const ShortError = error{...@@ -88,7 +87,6 @@ pub const ShortError = error{
88};87};
8988
90pub const failing: Reader = .{89pub const failing: Reader = .{
91 .context = undefined,
92 .vtable = &.{90 .vtable = &.{
93 .read = failingStream,91 .read = failingStream,
94 .discard = failingDiscard,92 .discard = failingDiscard,
...@@ -107,7 +105,6 @@ pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {...@@ -107,7 +105,6 @@ pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
107/// Constructs a `Reader` such that it will read from `buffer` and then end.105/// Constructs a `Reader` such that it will read from `buffer` and then end.
108pub fn fixed(buffer: []const u8) Reader {106pub fn fixed(buffer: []const u8) Reader {
109 return .{107 return .{
110 .context = undefined,
111 .vtable = &.{108 .vtable = &.{
112 .stream = endingStream,109 .stream = endingStream,
113 .discard = endingDiscard,110 .discard = endingDiscard,
...@@ -1402,12 +1399,6 @@ test "readAlloc when the backing reader provides one byte at a time" {...@@ -1402,12 +1399,6 @@ test "readAlloc when the backing reader provides one byte at a time" {
1402 self.curr += 1;1399 self.curr += 1;
1403 return 1;1400 return 1;
1404 }1401 }
1405
1406 fn reader(self: *@This()) std.io.Reader {
1407 return .{
1408 .context = self,
1409 };
1410 }
1411 };1402 };
14121403
1413 const str = "This is a test";1404 const str = "This is a test";
lib/std/io/Writer.zig-6
...@@ -9,12 +9,6 @@ const File = std.fs.File;...@@ -9,12 +9,6 @@ const File = std.fs.File;
9const testing = std.testing;9const testing = std.testing;
10const Allocator = std.mem.Allocator;10const 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,
18vtable: *const VTable,12vtable: *const VTable,
19/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.13/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.
20buffer: []u8,14buffer: []u8,