| author | |
| committer | |
| log | d4545f216a6ea86caec208cabc724004b7ef320a |
| tree | 75a826b42293794f3ab9341703a11d8c6cbd926c |
| parent | 7d6b5ed5103e6a06d1bd8be884f6c2a7ecb0e09a |
rely on the field parent pointer pattern7 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`. |
| 24 | input: *std.io.Reader, | 24 | input: *Reader, |
| 25 | /// Decrypted stream from the server to the client. | ||
| 26 | reader: Reader, | ||
| 25 | 27 | ||
| 26 | /// The encrypted stream from the client to the server. Bytes are pushed here | 28 | /// The encrypted stream from the client to the server. Bytes are pushed here |
| 27 | /// via `writer`. | 29 | /// via `writer`. |
| 28 | output: *Writer, | 30 | output: *Writer, |
| 31 | /// The plaintext stream from the client to the server. | ||
| 32 | writer: Writer, | ||
| 29 | 33 | ||
| 30 | /// Populated when `error.TlsAlert` is returned. | 34 | /// Populated when `error.TlsAlert` is returned. |
| 31 | alert: ?tls.Alert = null, | 35 | alert: ?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 there | 40 | /// 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. |
| 38 | received_close_notify: bool, | 42 | received_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. | ||
| 47 | allow_truncation_attacks: bool, | 43 | allow_truncation_attacks: bool, |
| 48 | application_cipher: tls.ApplicationCipher, | 44 | application_cipher: tls.ApplicationCipher, |
| 49 | 45 | ||
| ... | @@ -85,7 +81,7 @@ pub const SslKeyLog = struct { | ... | @@ -85,7 +81,7 @@ pub const SslKeyLog = struct { |
| 85 | } | 81 | } |
| 86 | }; | 82 | }; |
| 87 | 83 | ||
| 88 | /// The `std.io.Reader` supplied to `init` requires a buffer capacity | 84 | /// The `Reader` supplied to `init` requires a buffer capacity |
| 89 | /// at least this amount. | 85 | /// at least this amount. |
| 90 | pub const min_buffer_len = tls.max_ciphertext_record_len; | 86 | pub const min_buffer_len = tls.max_ciphertext_record_len; |
| 91 | 87 | ||
| ... | @@ -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 | }; |
| 120 | 130 | ||
| 121 | const InitError = error{ | 131 | const 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`. |
| 176 | pub fn init( | 186 | pub 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 | } |
| 893 | 909 | ||
| 894 | pub fn reader(c: *Client) Reader { | 910 | fn 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 | |||
| 901 | pub fn writer(c: *Client) Writer { | ||
| 902 | return .{ | ||
| 903 | .context = c, | ||
| 904 | .vtable = &.{ | ||
| 905 | .writeSplat = writeSplat, | ||
| 906 | .writeFile = Writer.unimplementedWriteFile, | ||
| 907 | }, | ||
| 908 | }; | ||
| 909 | } | ||
| 910 | |||
| 911 | fn 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 | } |
| 1045 | 1045 | ||
| 1046 | fn read(context: ?*anyopaque, bw: *Writer, limit: std.io.Limit) Reader.StreamError!usize { | 1046 | fn 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 | } |
| 1228 | 1228 | ||
| 1229 | fn logSecrets(bw: *Writer, context: anytype, secrets: anytype) void { | 1229 | fn 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 { |
| 943 | 943 | ||
| 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 { |
| 1291 | 1290 | ||
| 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 { |
| 328 | 328 | ||
| 329 | pub const Reader = struct { | 329 | pub 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 HTTP | 333 | /// 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 | } |
| 473 | 478 | ||
| 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 | } |
| 513 | 518 | ||
| 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 | } |
| 530 | 535 | ||
| 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 | } |
| 543 | 548 | ||
| 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 { |
| 568 | 569 | ||
| 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 | } |
| 617 | 618 | ||
| 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, |
| 763 | 763 | ||
| 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 | }; |
| 798 | 798 | ||
| 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 any | 829 | /// 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 the | 845 | /// 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 { |
| 922 | 926 | ||
| 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 { |
| 931 | 935 | ||
| 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 | } |
| 945 | 949 | ||
| 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 { |
| 953 | 957 | ||
| 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 would | 962 | // 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 { |
| 1001 | 1005 | ||
| 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 | }; |
| 1084 | 1067 | ||
| 1085 | test { | 1068 | test { |
lib/std/http/Client.zig+92-50| ... | @@ -14,6 +14,7 @@ const Uri = std.Uri; | ... | @@ -14,6 +14,7 @@ const Uri = std.Uri; |
| 14 | const Allocator = mem.Allocator; | 14 | const Allocator = mem.Allocator; |
| 15 | const assert = std.debug.assert; | 15 | const assert = std.debug.assert; |
| 16 | const Writer = std.io.Writer; | 16 | const Writer = std.io.Writer; |
| 17 | const Reader = std.io.Reader; | ||
| 17 | 18 | ||
| 18 | const Client = @This(); | 19 | const Client = @This(); |
| 19 | 20 | ||
| ... | @@ -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 | }; |
| 298 | 291 | ||
| 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, |
| 306 | 295 | ||
| ... | @@ -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 | } |
| 354 | 343 | ||
| ... | @@ -404,26 +393,52 @@ pub const Connection = struct { | ... | @@ -404,26 +393,52 @@ pub const Connection = struct { |
| 404 | } | 393 | } |
| 405 | } | 394 | } |
| 406 | 395 | ||
| 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 | } |
| 415 | 430 | ||
| 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 | }; |
| 429 | 444 | ||
| ... | @@ -660,14 +675,14 @@ pub const Response = struct { | ... | @@ -660,14 +675,14 @@ pub const Response = struct { |
| 660 | 675 | ||
| 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 is | 678 | /// 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 { |
| 676 | 691 | ||
| 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 is | 694 | /// 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 | } |
| 700 | 715 | ||
| 701 | /// After receiving `error.ReadFailed` from the `std.io.Reader` returned by | 716 | /// After receiving `error.ReadFailed` from the `Reader` returned by |
| 702 | /// `reader` or `readerDecompressing`, this function accesses the | 717 | /// `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 | } |
| 862 | 904 |
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 | }; |
| 385 | 387 | ||
| 386 | /// The header is not guaranteed to be sent until `BodyWriter.flush` or | 388 | /// 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 { |
| 436 | 438 | ||
| 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; |
| 439 | 441 | 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 | } |
| 451 | 475 |
lib/std/io/Reader.zig-9| ... | @@ -13,7 +13,6 @@ const Limit = std.io.Limit; | ... | @@ -13,7 +13,6 @@ const Limit = std.io.Limit; |
| 13 | 13 | ||
| 14 | pub const Limited = @import("Reader/Limited.zig"); | 14 | pub const Limited = @import("Reader/Limited.zig"); |
| 15 | 15 | ||
| 16 | context: ?*anyopaque = null, | ||
| 17 | vtable: *const VTable, | 16 | vtable: *const VTable, |
| 18 | buffer: []u8, | 17 | buffer: []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 | }; |
| 89 | 88 | ||
| 90 | pub const failing: Reader = .{ | 89 | pub 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. |
| 108 | pub fn fixed(buffer: []const u8) Reader { | 106 | pub 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 | }; |
| 1412 | 1403 | ||
| 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; |
| 9 | const testing = std.testing; | 9 | const testing = std.testing; |
| 10 | const Allocator = std.mem.Allocator; | 10 | const Allocator = std.mem.Allocator; |
| 11 | 11 | ||
| 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. | ||
| 17 | context: ?*anyopaque = null, | ||
| 18 | vtable: *const VTable, | 12 | vtable: *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. |
| 20 | buffer: []u8, | 14 | buffer: []u8, |