authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-03 13:36:07-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-03 13:36:07-07:00
log77b40d6ecb8d04bd9d8b95b04b8ba6ce3a6ea604
tree6713ba27d34e65c9645573ace7fe0c5bf7a8369c
parent9461ed5037de8f3e4f03021c27d7458aa3d1a432
parent23ccff9cce2a5264fc84998bd2c897682ac266ea
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15927 from truemedian/http-bugs

std.http: fix infinite read loop, deduplicate connection code, add TlsAlert errors

6 files changed, 331 insertions(+), 374 deletions(-)

lib/std/crypto/tls.zig+62
......@@ -138,6 +138,35 @@ pub const AlertLevel = enum(u8) {
138138};
139139
140140pub const AlertDescription = enum(u8) {
141 pub const Error = error{
142 TlsAlertUnexpectedMessage,
143 TlsAlertBadRecordMac,
144 TlsAlertRecordOverflow,
145 TlsAlertHandshakeFailure,
146 TlsAlertBadCertificate,
147 TlsAlertUnsupportedCertificate,
148 TlsAlertCertificateRevoked,
149 TlsAlertCertificateExpired,
150 TlsAlertCertificateUnknown,
151 TlsAlertIllegalParameter,
152 TlsAlertUnknownCa,
153 TlsAlertAccessDenied,
154 TlsAlertDecodeError,
155 TlsAlertDecryptError,
156 TlsAlertProtocolVersion,
157 TlsAlertInsufficientSecurity,
158 TlsAlertInternalError,
159 TlsAlertInappropriateFallback,
160 TlsAlertMissingExtension,
161 TlsAlertUnsupportedExtension,
162 TlsAlertUnrecognizedName,
163 TlsAlertBadCertificateStatusResponse,
164 TlsAlertUnknownPskIdentity,
165 TlsAlertCertificateRequired,
166 TlsAlertNoApplicationProtocol,
167 TlsAlertUnknown,
168 };
169
141170 close_notify = 0,
142171 unexpected_message = 10,
143172 bad_record_mac = 20,
......@@ -166,6 +195,39 @@ pub const AlertDescription = enum(u8) {
166195 certificate_required = 116,
167196 no_application_protocol = 120,
168197 _,
198
199 pub fn toError(alert: AlertDescription) Error!void {
200 return switch (alert) {
201 .close_notify => {}, // not an error
202 .unexpected_message => error.TlsAlertUnexpectedMessage,
203 .bad_record_mac => error.TlsAlertBadRecordMac,
204 .record_overflow => error.TlsAlertRecordOverflow,
205 .handshake_failure => error.TlsAlertHandshakeFailure,
206 .bad_certificate => error.TlsAlertBadCertificate,
207 .unsupported_certificate => error.TlsAlertUnsupportedCertificate,
208 .certificate_revoked => error.TlsAlertCertificateRevoked,
209 .certificate_expired => error.TlsAlertCertificateExpired,
210 .certificate_unknown => error.TlsAlertCertificateUnknown,
211 .illegal_parameter => error.TlsAlertIllegalParameter,
212 .unknown_ca => error.TlsAlertUnknownCa,
213 .access_denied => error.TlsAlertAccessDenied,
214 .decode_error => error.TlsAlertDecodeError,
215 .decrypt_error => error.TlsAlertDecryptError,
216 .protocol_version => error.TlsAlertProtocolVersion,
217 .insufficient_security => error.TlsAlertInsufficientSecurity,
218 .internal_error => error.TlsAlertInternalError,
219 .inappropriate_fallback => error.TlsAlertInappropriateFallback,
220 .user_canceled => {}, // not an error
221 .missing_extension => error.TlsAlertMissingExtension,
222 .unsupported_extension => error.TlsAlertUnsupportedExtension,
223 .unrecognized_name => error.TlsAlertUnrecognizedName,
224 .bad_certificate_status_response => error.TlsAlertBadCertificateStatusResponse,
225 .unknown_psk_identity => error.TlsAlertUnknownPskIdentity,
226 .certificate_required => error.TlsAlertCertificateRequired,
227 .no_application_protocol => error.TlsAlertNoApplicationProtocol,
228 _ => error.TlsAlertUnknown,
229 };
230 }
169231};
170232
171233pub const SignatureScheme = enum(u16) {
lib/std/crypto/tls/Client.zig+14-7
......@@ -89,12 +89,11 @@ pub const StreamInterface = struct {
8989};
9090
9191pub fn InitError(comptime Stream: type) type {
92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error{
92 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || tls.AlertDescription.Error || error{
9393 InsufficientEntropy,
9494 DiskQuota,
9595 LockViolation,
9696 NotOpenForWriting,
97 TlsAlert,
9897 TlsUnexpectedMessage,
9998 TlsIllegalParameter,
10099 TlsDecryptFailure,
......@@ -251,8 +250,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
251250 const level = ptd.decode(tls.AlertLevel);
252251 const desc = ptd.decode(tls.AlertDescription);
253252 _ = level;
254 _ = desc;
255 return error.TlsAlert;
253
254 // if this isn't a error alert, then it's a closure alert, which makes no sense in a handshake
255 try desc.toError();
256 // TODO: handle server-side closures
257 return error.TlsUnexpectedMessage;
256258 },
257259 .handshake => {
258260 try ptd.ensure(4);
......@@ -1071,8 +1073,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
10711073 const level = @intToEnum(tls.AlertLevel, frag[in]);
10721074 const desc = @intToEnum(tls.AlertDescription, frag[in + 1]);
10731075 _ = level;
1074 _ = desc;
1075 return error.TlsAlert;
1076
1077 try desc.toError();
1078 // TODO: handle server-side closures
1079 return error.TlsUnexpectedMessage;
10761080 },
10771081 .application_data => {
10781082 const cleartext = switch (c.application_cipher) {
......@@ -1112,7 +1116,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
11121116 return vp.total;
11131117 }
11141118 _ = level;
1115 return error.TlsAlert;
1119
1120 try desc.toError();
1121 // TODO: handle server-side closures
1122 return error.TlsUnexpectedMessage;
11161123 },
11171124 .handshake => {
11181125 var ct_i: usize = 0;
lib/std/http/Client.zig+112-169
......@@ -36,21 +36,7 @@ pub const ConnectionPool = struct {
3636 is_tls: bool,
3737 };
3838
39 pub const StoredConnection = struct {
40 buffered: BufferedConnection,
41 host: []u8,
42 port: u16,
43
44 proxied: bool = false,
45 closing: bool = false,
46
47 pub fn deinit(self: *StoredConnection, client: *Client) void {
48 self.buffered.close(client);
49 client.allocator.free(self.host);
50 }
51 };
52
53 const Queue = std.TailQueue(StoredConnection);
39 const Queue = std.TailQueue(Connection);
5440 pub const Node = Queue.Node;
5541
5642 mutex: std.Thread.Mutex = .{},
......@@ -69,7 +55,7 @@ pub const ConnectionPool = struct {
6955
7056 var next = pool.free.last;
7157 while (next) |node| : (next = node.prev) {
72 if ((node.data.buffered.conn.protocol == .tls) != criteria.is_tls) continue;
58 if ((node.data.protocol == .tls) != criteria.is_tls) continue;
7359 if (node.data.port != criteria.port) continue;
7460 if (!mem.eql(u8, node.data.host, criteria.host)) continue;
7561
......@@ -160,45 +146,105 @@ pub const ConnectionPool = struct {
160146
161147/// An interface to either a plain or TLS connection.
162148pub const Connection = struct {
149 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
150 pub const Protocol = enum { plain, tls };
151
163152 stream: net.Stream,
164153 /// undefined unless protocol is tls.
165154 tls_client: *std.crypto.tls.Client,
155
166156 protocol: Protocol,
157 host: []u8,
158 port: u16,
167159
168 pub const Protocol = enum { plain, tls };
160 proxied: bool = false,
161 closing: bool = false,
169162
170 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
171 return switch (conn.protocol) {
172 .plain => conn.stream.read(buffer),
173 .tls => conn.tls_client.read(conn.stream, buffer),
174 } catch |err| switch (err) {
175 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
176 error.TlsAlert => return error.TlsAlert,
177 error.ConnectionTimedOut => return error.ConnectionTimedOut,
178 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
179 else => return error.UnexpectedReadFailure,
180 };
181 }
163 read_start: u16 = 0,
164 read_end: u16 = 0,
165 read_buf: [buffer_size]u8 = undefined,
182166
183 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
167 pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
184168 return switch (conn.protocol) {
185169 .plain => conn.stream.readAtLeast(buffer, len),
186170 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
187 } catch |err| switch (err) {
188 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
189 error.TlsAlert => return error.TlsAlert,
190 error.ConnectionTimedOut => return error.ConnectionTimedOut,
191 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
192 else => return error.UnexpectedReadFailure,
171 } catch |err| {
172 // TODO: https://github.com/ziglang/zig/issues/2473
173 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
174
175 switch (err) {
176 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
177 error.ConnectionTimedOut => return error.ConnectionTimedOut,
178 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
179 else => return error.UnexpectedReadFailure,
180 }
193181 };
194182 }
195183
184 pub fn fill(conn: *Connection) ReadError!void {
185 if (conn.read_end != conn.read_start) return;
186
187 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
188 if (nread == 0) return error.EndOfStream;
189 conn.read_start = 0;
190 conn.read_end = @intCast(u16, nread);
191 }
192
193 pub fn peek(conn: *Connection) []const u8 {
194 return conn.read_buf[conn.read_start..conn.read_end];
195 }
196
197 pub fn drop(conn: *Connection, num: u16) void {
198 conn.read_start += num;
199 }
200
201 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
202 assert(len <= buffer.len);
203
204 var out_index: u16 = 0;
205 while (out_index < len) {
206 const available_read = conn.read_end - conn.read_start;
207 const available_buffer = buffer.len - out_index;
208
209 if (available_read > available_buffer) { // partially read buffered data
210 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
211 out_index += @intCast(u16, available_buffer);
212 conn.read_start += @intCast(u16, available_buffer);
213
214 break;
215 } else if (available_read > 0) { // fully read buffered data
216 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
217 out_index += available_read;
218 conn.read_start += available_read;
219
220 if (out_index >= len) break;
221 }
222
223 const leftover_buffer = available_buffer - available_read;
224 const leftover_len = len - out_index;
225
226 if (leftover_buffer > conn.read_buf.len) {
227 // skip the buffer if the output is large enough
228 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
229 }
230
231 try conn.fill();
232 }
233
234 return out_index;
235 }
236
237 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
238 return conn.readAtLeast(buffer, 1);
239 }
240
196241 pub const ReadError = error{
197242 TlsFailure,
198243 TlsAlert,
199244 ConnectionTimedOut,
200245 ConnectionResetByPeer,
201246 UnexpectedReadFailure,
247 EndOfStream,
202248 };
203249
204250 pub const Reader = std.io.Reader(*Connection, ReadError, read);
......@@ -247,111 +293,10 @@ pub const Connection = struct {
247293
248294 conn.stream.close();
249295 }
250};
251
252/// A buffered (and peekable) Connection.
253pub const BufferedConnection = struct {
254 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
255
256 conn: Connection,
257 read_buf: [buffer_size]u8 = undefined,
258 read_start: u16 = 0,
259 read_end: u16 = 0,
260
261 write_buf: [buffer_size]u8 = undefined,
262 write_end: u16 = 0,
263
264 pub fn fill(bconn: *BufferedConnection) ReadError!void {
265 if (bconn.read_end != bconn.read_start) return;
266
267 const nread = try bconn.conn.read(bconn.read_buf[0..]);
268 if (nread == 0) return error.EndOfStream;
269 bconn.read_start = 0;
270 bconn.read_end = @intCast(u16, nread);
271 }
272
273 pub fn peek(bconn: *BufferedConnection) []const u8 {
274 return bconn.read_buf[bconn.read_start..bconn.read_end];
275 }
276
277 pub fn clear(bconn: *BufferedConnection, num: u16) void {
278 bconn.read_start += num;
279 }
280
281 pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize {
282 var out_index: u16 = 0;
283 while (out_index < len) {
284 const available = bconn.read_end - bconn.read_start;
285 const left = buffer.len - out_index;
286
287 if (available > 0) {
288 const can_read = @intCast(u16, @min(available, left));
289
290 @memcpy(buffer[out_index..][0..can_read], bconn.read_buf[bconn.read_start..][0..can_read]);
291 out_index += can_read;
292 bconn.read_start += can_read;
293
294 continue;
295 }
296
297 if (left > bconn.read_buf.len) {
298 // skip the buffer if the output is large enough
299 return bconn.conn.read(buffer[out_index..]);
300 }
301296
302 try bconn.fill();
303 }
304
305 return out_index;
306 }
307
308 pub fn read(bconn: *BufferedConnection, buffer: []u8) ReadError!usize {
309 return bconn.readAtLeast(buffer, 1);
310 }
311
312 pub const ReadError = Connection.ReadError || error{EndOfStream};
313 pub const Reader = std.io.Reader(*BufferedConnection, ReadError, read);
314
315 pub fn reader(bconn: *BufferedConnection) Reader {
316 return Reader{ .context = bconn };
317 }
318
319 pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void {
320 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
321 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
322 bconn.write_end += @intCast(u16, buffer.len);
323 } else {
324 try bconn.flush();
325 try bconn.conn.writeAll(buffer);
326 }
327 }
328
329 pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize {
330 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
331 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
332 bconn.write_end += @intCast(u16, buffer.len);
333
334 return buffer.len;
335 } else {
336 try bconn.flush();
337 return try bconn.conn.write(buffer);
338 }
339 }
340
341 pub fn flush(bconn: *BufferedConnection) WriteError!void {
342 defer bconn.write_end = 0;
343 return bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
344 }
345
346 pub const WriteError = Connection.WriteError;
347 pub const Writer = std.io.Writer(*BufferedConnection, WriteError, write);
348
349 pub fn writer(bconn: *BufferedConnection) Writer {
350 return Writer{ .context = bconn };
351 }
352
353 pub fn close(bconn: *BufferedConnection, client: *const Client) void {
354 bconn.conn.close(client);
297 pub fn deinit(conn: *Connection, client: *const Client) void {
298 conn.close(client);
299 client.allocator.free(conn.host);
355300 }
356301};
357302
......@@ -585,11 +530,12 @@ pub const Request = struct {
585530 };
586531 }
587532
588 pub const StartError = BufferedConnection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
533 pub const StartError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
589534
590535 /// Send the request to the server.
591536 pub fn start(req: *Request) StartError!void {
592 const w = req.connection.data.buffered.writer();
537 var buffered = std.io.bufferedWriter(req.connection.data.writer());
538 const w = buffered.writer();
593539
594540 try w.writeAll(@tagName(req.method));
595541 try w.writeByte(' ');
......@@ -663,10 +609,10 @@ pub const Request = struct {
663609
664610 try w.writeAll("\r\n");
665611
666 try req.connection.data.buffered.flush();
612 try buffered.flush();
667613 }
668614
669 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;
615 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
670616
671617 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);
672618
......@@ -679,7 +625,7 @@ pub const Request = struct {
679625
680626 var index: usize = 0;
681627 while (index == 0) {
682 const amt = try req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip);
628 const amt = try req.response.parser.read(&req.connection.data, buf[index..], req.response.skip);
683629 if (amt == 0 and req.response.parser.done) break;
684630 index += amt;
685631 }
......@@ -697,10 +643,10 @@ pub const Request = struct {
697643 pub fn wait(req: *Request) WaitError!void {
698644 while (true) { // handle redirects
699645 while (true) { // read headers
700 try req.connection.data.buffered.fill();
646 try req.connection.data.fill();
701647
702 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
703 req.connection.data.buffered.clear(@intCast(u16, nchecked));
648 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.peek());
649 req.connection.data.drop(@intCast(u16, nchecked));
704650
705651 if (req.response.parser.state.isContent()) break;
706652 }
......@@ -816,10 +762,10 @@ pub const Request = struct {
816762 const has_trail = !req.response.parser.state.isContent();
817763
818764 while (!req.response.parser.state.isContent()) { // read trailing headers
819 try req.connection.data.buffered.fill();
765 try req.connection.data.fill();
820766
821 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek());
822 req.connection.data.buffered.clear(@intCast(u16, nchecked));
767 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.peek());
768 req.connection.data.drop(@intCast(u16, nchecked));
823769 }
824770
825771 if (has_trail) {
......@@ -845,7 +791,7 @@ pub const Request = struct {
845791 return index;
846792 }
847793
848 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };
794 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
849795
850796 pub const Writer = std.io.Writer(*Request, WriteError, write);
851797
......@@ -857,16 +803,16 @@ pub const Request = struct {
857803 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
858804 switch (req.transfer_encoding) {
859805 .chunked => {
860 try req.connection.data.buffered.writer().print("{x}\r\n", .{bytes.len});
861 try req.connection.data.buffered.writeAll(bytes);
862 try req.connection.data.buffered.writeAll("\r\n");
806 try req.connection.data.writer().print("{x}\r\n", .{bytes.len});
807 try req.connection.data.writeAll(bytes);
808 try req.connection.data.writeAll("\r\n");
863809
864810 return bytes.len;
865811 },
866812 .content_length => |*len| {
867813 if (len.* < bytes.len) return error.MessageTooLong;
868814
869 const amt = try req.connection.data.buffered.write(bytes);
815 const amt = try req.connection.data.write(bytes);
870816 len.* -= amt;
871817 return amt;
872818 },
......@@ -886,12 +832,10 @@ pub const Request = struct {
886832 /// Finish the body of a request. This notifies the server that you have no more data to send.
887833 pub fn finish(req: *Request) FinishError!void {
888834 switch (req.transfer_encoding) {
889 .chunked => try req.connection.data.buffered.writeAll("0\r\n\r\n"),
835 .chunked => try req.connection.data.writeAll("0\r\n\r\n"),
890836 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
891837 .none => {},
892838 }
893
894 try req.connection.data.buffered.flush();
895839 }
896840};
897841
......@@ -948,11 +892,10 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:
948892 errdefer stream.close();
949893
950894 conn.data = .{
951 .buffered = .{ .conn = .{
952 .stream = stream,
953 .tls_client = undefined,
954 .protocol = protocol,
955 } },
895 .stream = stream,
896 .tls_client = undefined,
897 .protocol = protocol,
898
956899 .host = try client.allocator.dupe(u8, host),
957900 .port = port,
958901 };
......@@ -961,13 +904,13 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:
961904 switch (protocol) {
962905 .plain => {},
963906 .tls => {
964 conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
965 errdefer client.allocator.destroy(conn.data.buffered.conn.tls_client);
907 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
908 errdefer client.allocator.destroy(conn.data.tls_client);
966909
967 conn.data.buffered.conn.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
910 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
968911 // This is appropriate for HTTPS because the HTTP headers contain
969912 // the content length which is used to detect truncation attacks.
970 conn.data.buffered.conn.tls_client.allow_truncation_attacks = true;
913 conn.data.tls_client.allow_truncation_attacks = true;
971914 },
972915 }
973916
......@@ -1003,7 +946,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
1003946 }
1004947}
1005948
1006pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || BufferedConnection.WriteError || error{
949pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || Connection.WriteError || error{
1007950 UnsupportedUrlScheme,
1008951 UriMissingHost,
1009952
lib/std/http/Server.zig+85-139
......@@ -16,39 +16,92 @@ socket: net.StreamServer,
1616
1717/// An interface to either a plain or TLS connection.
1818pub const Connection = struct {
19 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
20 pub const Protocol = enum { plain };
21
1922 stream: net.Stream,
2023 protocol: Protocol,
2124
2225 closing: bool = true,
2326
24 pub const Protocol = enum { plain };
27 read_buf: [buffer_size]u8 = undefined,
28 read_start: u16 = 0,
29 read_end: u16 = 0,
2530
26 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
31 pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
2732 return switch (conn.protocol) {
28 .plain => conn.stream.read(buffer),
29 // .tls => return conn.tls_client.read(conn.stream, buffer),
30 } catch |err| switch (err) {
31 error.ConnectionTimedOut => return error.ConnectionTimedOut,
32 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
33 else => return error.UnexpectedReadFailure,
33 .plain => conn.stream.readAtLeast(buffer, len),
34 // .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
35 } catch |err| {
36 switch (err) {
37 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
38 else => return error.UnexpectedReadFailure,
39 }
3440 };
3541 }
3642
43 pub fn fill(conn: *Connection) ReadError!void {
44 if (conn.read_end != conn.read_start) return;
45
46 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
47 if (nread == 0) return error.EndOfStream;
48 conn.read_start = 0;
49 conn.read_end = @intCast(u16, nread);
50 }
51
52 pub fn peek(conn: *Connection) []const u8 {
53 return conn.read_buf[conn.read_start..conn.read_end];
54 }
55
56 pub fn drop(conn: *Connection, num: u16) void {
57 conn.read_start += num;
58 }
59
3760 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
38 return switch (conn.protocol) {
39 .plain => conn.stream.readAtLeast(buffer, len),
40 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),
41 } catch |err| switch (err) {
42 error.ConnectionTimedOut => return error.ConnectionTimedOut,
43 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
44 else => return error.UnexpectedReadFailure,
45 };
61 assert(len <= buffer.len);
62
63 var out_index: u16 = 0;
64 while (out_index < len) {
65 const available_read = conn.read_end - conn.read_start;
66 const available_buffer = buffer.len - out_index;
67
68 if (available_read > available_buffer) { // partially read buffered data
69 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
70 out_index += @intCast(u16, available_buffer);
71 conn.read_start += @intCast(u16, available_buffer);
72
73 break;
74 } else if (available_read > 0) { // fully read buffered data
75 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
76 out_index += available_read;
77 conn.read_start += available_read;
78
79 if (out_index >= len) break;
80 }
81
82 const leftover_buffer = available_buffer - available_read;
83 const leftover_len = len - out_index;
84
85 if (leftover_buffer > conn.read_buf.len) {
86 // skip the buffer if the output is large enough
87 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
88 }
89
90 try conn.fill();
91 }
92
93 return out_index;
94 }
95
96 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
97 return conn.readAtLeast(buffer, 1);
4698 }
4799
48100 pub const ReadError = error{
49101 ConnectionTimedOut,
50102 ConnectionResetByPeer,
51103 UnexpectedReadFailure,
104 EndOfStream,
52105 };
53106
54107 pub const Reader = std.io.Reader(*Connection, ReadError, read);
......@@ -93,112 +146,6 @@ pub const Connection = struct {
93146 }
94147};
95148
96/// A buffered (and peekable) Connection.
97pub const BufferedConnection = struct {
98 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
99
100 conn: Connection,
101 read_buf: [buffer_size]u8 = undefined,
102 read_start: u16 = 0,
103 read_end: u16 = 0,
104
105 write_buf: [buffer_size]u8 = undefined,
106 write_end: u16 = 0,
107
108 pub fn fill(bconn: *BufferedConnection) ReadError!void {
109 if (bconn.read_end != bconn.read_start) return;
110
111 const nread = try bconn.conn.read(bconn.read_buf[0..]);
112 if (nread == 0) return error.EndOfStream;
113 bconn.read_start = 0;
114 bconn.read_end = @intCast(u16, nread);
115 }
116
117 pub fn peek(bconn: *BufferedConnection) []const u8 {
118 return bconn.read_buf[bconn.read_start..bconn.read_end];
119 }
120
121 pub fn clear(bconn: *BufferedConnection, num: u16) void {
122 bconn.read_start += num;
123 }
124
125 pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize {
126 var out_index: u16 = 0;
127 while (out_index < len) {
128 const available = bconn.read_end - bconn.read_start;
129 const left = buffer.len - out_index;
130
131 if (available > 0) {
132 const can_read = @intCast(u16, @min(available, left));
133
134 @memcpy(buffer[out_index..][0..can_read], bconn.read_buf[bconn.read_start..][0..can_read]);
135 out_index += can_read;
136 bconn.read_start += can_read;
137
138 continue;
139 }
140
141 if (left > bconn.read_buf.len) {
142 // skip the buffer if the output is large enough
143 return bconn.conn.read(buffer[out_index..]);
144 }
145
146 try bconn.fill();
147 }
148
149 return out_index;
150 }
151
152 pub fn read(bconn: *BufferedConnection, buffer: []u8) ReadError!usize {
153 return bconn.readAtLeast(buffer, 1);
154 }
155
156 pub const ReadError = Connection.ReadError || error{EndOfStream};
157 pub const Reader = std.io.Reader(*BufferedConnection, ReadError, read);
158
159 pub fn reader(bconn: *BufferedConnection) Reader {
160 return Reader{ .context = bconn };
161 }
162
163 pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void {
164 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
165 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
166 bconn.write_end += @intCast(u16, buffer.len);
167 } else {
168 try bconn.flush();
169 try bconn.conn.writeAll(buffer);
170 }
171 }
172
173 pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize {
174 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
175 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
176 bconn.write_end += @intCast(u16, buffer.len);
177
178 return buffer.len;
179 } else {
180 try bconn.flush();
181 return try bconn.conn.write(buffer);
182 }
183 }
184
185 pub fn flush(bconn: *BufferedConnection) WriteError!void {
186 defer bconn.write_end = 0;
187 return bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
188 }
189
190 pub const WriteError = Connection.WriteError;
191 pub const Writer = std.io.Writer(*BufferedConnection, WriteError, write);
192
193 pub fn writer(bconn: *BufferedConnection) Writer {
194 return Writer{ .context = bconn };
195 }
196
197 pub fn close(bconn: *BufferedConnection) void {
198 bconn.conn.close();
199 }
200};
201
202149/// The mode of transport for responses.
203150pub const ResponseTransfer = union(enum) {
204151 content_length: u64,
......@@ -351,7 +298,7 @@ pub const Response = struct {
351298
352299 allocator: Allocator,
353300 address: net.Address,
354 connection: BufferedConnection,
301 connection: Connection,
355302
356303 headers: http.Headers,
357304 request: Request,
......@@ -388,7 +335,7 @@ pub const Response = struct {
388335
389336 if (!res.request.parser.done) {
390337 // If the response wasn't fully read, then we need to close the connection.
391 res.connection.conn.closing = true;
338 res.connection.closing = true;
392339 return .closing;
393340 }
394341
......@@ -402,9 +349,9 @@ pub const Response = struct {
402349 const req_connection = res.request.headers.getFirstValue("connection");
403350 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);
404351 if (req_keepalive and (res_keepalive or res_connection == null)) {
405 res.connection.conn.closing = false;
352 res.connection.closing = false;
406353 } else {
407 res.connection.conn.closing = true;
354 res.connection.closing = true;
408355 }
409356
410357 switch (res.request.compression) {
......@@ -434,14 +381,14 @@ pub const Response = struct {
434381 .parser = res.request.parser,
435382 };
436383
437 if (res.connection.conn.closing) {
384 if (res.connection.closing) {
438385 return .closing;
439386 } else {
440387 return .reset;
441388 }
442389 }
443390
444 pub const DoError = BufferedConnection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
391 pub const DoError = Connection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
445392
446393 /// Send the response headers.
447394 pub fn do(res: *Response) !void {
......@@ -450,7 +397,8 @@ pub const Response = struct {
450397 .first, .start, .responded, .finished => unreachable,
451398 }
452399
453 const w = res.connection.writer();
400 var buffered = std.io.bufferedWriter(res.connection.writer());
401 const w = buffered.writer();
454402
455403 try w.writeAll(@tagName(res.version));
456404 try w.writeByte(' ');
......@@ -508,10 +456,10 @@ pub const Response = struct {
508456
509457 try w.writeAll("\r\n");
510458
511 try res.connection.flush();
459 try buffered.flush();
512460 }
513461
514 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;
462 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
515463
516464 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);
517465
......@@ -532,7 +480,7 @@ pub const Response = struct {
532480 return index;
533481 }
534482
535 pub const WaitError = BufferedConnection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
483 pub const WaitError = Connection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
536484
537485 /// Wait for the client to send a complete request head.
538486 pub fn wait(res: *Response) WaitError!void {
......@@ -545,7 +493,7 @@ pub const Response = struct {
545493 try res.connection.fill();
546494
547495 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());
548 res.connection.clear(@intCast(u16, nchecked));
496 res.connection.drop(@intCast(u16, nchecked));
549497
550498 if (res.request.parser.state.isContent()) break;
551499 }
......@@ -612,7 +560,7 @@ pub const Response = struct {
612560 try res.connection.fill();
613561
614562 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());
615 res.connection.clear(@intCast(u16, nchecked));
563 res.connection.drop(@intCast(u16, nchecked));
616564 }
617565
618566 if (has_trail) {
......@@ -637,7 +585,7 @@ pub const Response = struct {
637585 return index;
638586 }
639587
640 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };
588 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
641589
642590 pub const Writer = std.io.Writer(*Response, WriteError, write);
643591
......@@ -692,8 +640,6 @@ pub const Response = struct {
692640 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
693641 .none => {},
694642 }
695
696 try res.connection.flush();
697643 }
698644};
699645
......@@ -742,10 +688,10 @@ pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {
742688 return Response{
743689 .allocator = options.allocator,
744690 .address = in.address,
745 .connection = .{ .conn = .{
691 .connection = .{
746692 .stream = in.stream,
747693 .protocol = .plain,
748 } },
694 },
749695 .headers = .{ .allocator = options.allocator },
750696 .request = .{
751697 .version = undefined,
lib/std/http/protocol.zig+58-58
......@@ -513,8 +513,8 @@ pub const HeadersParser = struct {
513513 ///
514514 /// If `skip` is true, the buffer will be unused and the body will be skipped.
515515 ///
516 /// See `std.http.Client.BufferedConnection for an example of `bconn`.
517 pub fn read(r: *HeadersParser, bconn: anytype, buffer: []u8, skip: bool) !usize {
516 /// See `std.http.Client.BufferedConnection for an example of `conn`.
517 pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
518518 assert(r.state.isContent());
519519 if (r.done) return 0;
520520
......@@ -526,10 +526,10 @@ pub const HeadersParser = struct {
526526 const data_avail = r.next_chunk_length;
527527
528528 if (skip) {
529 try bconn.fill();
529 try conn.fill();
530530
531 const nread = @min(bconn.peek().len, data_avail);
532 bconn.clear(@intCast(u16, nread));
531 const nread = @min(conn.peek().len, data_avail);
532 conn.drop(@intCast(u16, nread));
533533 r.next_chunk_length -= nread;
534534
535535 if (r.next_chunk_length == 0) r.done = true;
......@@ -539,7 +539,7 @@ pub const HeadersParser = struct {
539539 const out_avail = buffer.len;
540540
541541 const can_read = @intCast(usize, @min(data_avail, out_avail));
542 const nread = try bconn.read(buffer[0..can_read]);
542 const nread = try conn.read(buffer[0..can_read]);
543543 r.next_chunk_length -= nread;
544544
545545 if (r.next_chunk_length == 0) r.done = true;
......@@ -548,15 +548,15 @@ pub const HeadersParser = struct {
548548 }
549549 },
550550 .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
551 try bconn.fill();
551 try conn.fill();
552552
553 const i = r.findChunkedLen(bconn.peek());
554 bconn.clear(@intCast(u16, i));
553 const i = r.findChunkedLen(conn.peek());
554 conn.drop(@intCast(u16, i));
555555
556556 switch (r.state) {
557557 .invalid => return error.HttpChunkInvalid,
558558 .chunk_data => if (r.next_chunk_length == 0) {
559 if (std.mem.eql(u8, bconn.peek(), "\r\n")) {
559 if (std.mem.eql(u8, conn.peek(), "\r\n")) {
560560 r.state = .finished;
561561 } else {
562562 // The trailer section is formatted identically to the header section.
......@@ -576,14 +576,14 @@ pub const HeadersParser = struct {
576576 const out_avail = buffer.len - out_index;
577577
578578 if (skip) {
579 try bconn.fill();
579 try conn.fill();
580580
581 const nread = @min(bconn.peek().len, data_avail);
582 bconn.clear(@intCast(u16, nread));
581 const nread = @min(conn.peek().len, data_avail);
582 conn.drop(@intCast(u16, nread));
583583 r.next_chunk_length -= nread;
584584 } else {
585585 const can_read = @intCast(usize, @min(data_avail, out_avail));
586 const nread = try bconn.read(buffer[out_index..][0..can_read]);
586 const nread = try conn.read(buffer[out_index..][0..can_read]);
587587 r.next_chunk_length -= nread;
588588 out_index += nread;
589589 }
......@@ -628,74 +628,74 @@ const MockBufferedConnection = struct {
628628 start: u16 = 0,
629629 end: u16 = 0,
630630
631 pub fn fill(bconn: *MockBufferedConnection) ReadError!void {
632 if (bconn.end != bconn.start) return;
631 pub fn fill(conn: *MockBufferedConnection) ReadError!void {
632 if (conn.end != conn.start) return;
633633
634 const nread = try bconn.conn.read(bconn.buf[0..]);
634 const nread = try conn.conn.read(conn.buf[0..]);
635635 if (nread == 0) return error.EndOfStream;
636 bconn.start = 0;
637 bconn.end = @truncate(u16, nread);
636 conn.start = 0;
637 conn.end = @truncate(u16, nread);
638638 }
639639
640 pub fn peek(bconn: *MockBufferedConnection) []const u8 {
641 return bconn.buf[bconn.start..bconn.end];
640 pub fn peek(conn: *MockBufferedConnection) []const u8 {
641 return conn.buf[conn.start..conn.end];
642642 }
643643
644 pub fn clear(bconn: *MockBufferedConnection, num: u16) void {
645 bconn.start += num;
644 pub fn drop(conn: *MockBufferedConnection, num: u16) void {
645 conn.start += num;
646646 }
647647
648 pub fn readAtLeast(bconn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize {
648 pub fn readAtLeast(conn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize {
649649 var out_index: u16 = 0;
650650 while (out_index < len) {
651 const available = bconn.end - bconn.start;
651 const available = conn.end - conn.start;
652652 const left = buffer.len - out_index;
653653
654654 if (available > 0) {
655655 const can_read = @truncate(u16, @min(available, left));
656656
657 @memcpy(buffer[out_index..][0..can_read], bconn.buf[bconn.start..][0..can_read]);
657 @memcpy(buffer[out_index..][0..can_read], conn.buf[conn.start..][0..can_read]);
658658 out_index += can_read;
659 bconn.start += can_read;
659 conn.start += can_read;
660660
661661 continue;
662662 }
663663
664 if (left > bconn.buf.len) {
664 if (left > conn.buf.len) {
665665 // skip the buffer if the output is large enough
666 return bconn.conn.read(buffer[out_index..]);
666 return conn.conn.read(buffer[out_index..]);
667667 }
668668
669 try bconn.fill();
669 try conn.fill();
670670 }
671671
672672 return out_index;
673673 }
674674
675 pub fn read(bconn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
676 return bconn.readAtLeast(buffer, 1);
675 pub fn read(conn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
676 return conn.readAtLeast(buffer, 1);
677677 }
678678
679679 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};
680680 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);
681681
682 pub fn reader(bconn: *MockBufferedConnection) Reader {
683 return Reader{ .context = bconn };
682 pub fn reader(conn: *MockBufferedConnection) Reader {
683 return Reader{ .context = conn };
684684 }
685685
686 pub fn writeAll(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
687 return bconn.conn.writeAll(buffer);
686 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
687 return conn.conn.writeAll(buffer);
688688 }
689689
690 pub fn write(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
691 return bconn.conn.write(buffer);
690 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
691 return conn.conn.write(buffer);
692692 }
693693
694694 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
695695 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);
696696
697 pub fn writer(bconn: *MockBufferedConnection) Writer {
698 return Writer{ .context = bconn };
697 pub fn writer(conn: *MockBufferedConnection) Writer {
698 return Writer{ .context = conn };
699699 }
700700};
701701
......@@ -753,15 +753,15 @@ test "HeadersParser.read length" {
753753 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
754754 var fbs = std.io.fixedBufferStream(data);
755755
756 var bconn = MockBufferedConnection{
756 var conn = MockBufferedConnection{
757757 .conn = fbs,
758758 };
759759
760760 while (true) { // read headers
761 try bconn.fill();
761 try conn.fill();
762762
763 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());
764 bconn.clear(@intCast(u16, nchecked));
763 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
764 conn.drop(@intCast(u16, nchecked));
765765
766766 if (r.state.isContent()) break;
767767 }
......@@ -769,7 +769,7 @@ test "HeadersParser.read length" {
769769 var buf: [8]u8 = undefined;
770770
771771 r.next_chunk_length = 5;
772 const len = try r.read(&bconn, &buf, false);
772 const len = try r.read(&conn, &buf, false);
773773 try std.testing.expectEqual(@as(usize, 5), len);
774774 try std.testing.expectEqualStrings("Hello", buf[0..len]);
775775
......@@ -784,22 +784,22 @@ test "HeadersParser.read chunked" {
784784 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";
785785 var fbs = std.io.fixedBufferStream(data);
786786
787 var bconn = MockBufferedConnection{
787 var conn = MockBufferedConnection{
788788 .conn = fbs,
789789 };
790790
791791 while (true) { // read headers
792 try bconn.fill();
792 try conn.fill();
793793
794 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());
795 bconn.clear(@intCast(u16, nchecked));
794 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
795 conn.drop(@intCast(u16, nchecked));
796796
797797 if (r.state.isContent()) break;
798798 }
799799 var buf: [8]u8 = undefined;
800800
801801 r.state = .chunk_head_size;
802 const len = try r.read(&bconn, &buf, false);
802 const len = try r.read(&conn, &buf, false);
803803 try std.testing.expectEqual(@as(usize, 5), len);
804804 try std.testing.expectEqualStrings("Hello", buf[0..len]);
805805
......@@ -814,30 +814,30 @@ test "HeadersParser.read chunked trailer" {
814814 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";
815815 var fbs = std.io.fixedBufferStream(data);
816816
817 var bconn = MockBufferedConnection{
817 var conn = MockBufferedConnection{
818818 .conn = fbs,
819819 };
820820
821821 while (true) { // read headers
822 try bconn.fill();
822 try conn.fill();
823823
824 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());
825 bconn.clear(@intCast(u16, nchecked));
824 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
825 conn.drop(@intCast(u16, nchecked));
826826
827827 if (r.state.isContent()) break;
828828 }
829829 var buf: [8]u8 = undefined;
830830
831831 r.state = .chunk_head_size;
832 const len = try r.read(&bconn, &buf, false);
832 const len = try r.read(&conn, &buf, false);
833833 try std.testing.expectEqual(@as(usize, 5), len);
834834 try std.testing.expectEqualStrings("Hello", buf[0..len]);
835835
836836 while (true) { // read headers
837 try bconn.fill();
837 try conn.fill();
838838
839 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());
840 bconn.clear(@intCast(u16, nchecked));
839 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
840 conn.drop(@intCast(u16, nchecked));
841841
842842 if (r.state.isContent()) break;
843843 }
test/standalone/http.zig-1
......@@ -86,7 +86,6 @@ fn handleRequest(res: *Server.Response) !void {
8686 try res.writeAll("World!\n");
8787 // try res.finish();
8888 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
89 try res.connection.flush();
9089 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {
9190 res.transfer_encoding = .chunked;
9291