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) {...@@ -138,6 +138,35 @@ pub const AlertLevel = enum(u8) {
138};138};
139139
140pub const AlertDescription = enum(u8) {140pub 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
141 close_notify = 0,170 close_notify = 0,
142 unexpected_message = 10,171 unexpected_message = 10,
143 bad_record_mac = 20,172 bad_record_mac = 20,
...@@ -166,6 +195,39 @@ pub const AlertDescription = enum(u8) {...@@ -166,6 +195,39 @@ pub const AlertDescription = enum(u8) {
166 certificate_required = 116,195 certificate_required = 116,
167 no_application_protocol = 120,196 no_application_protocol = 120,
168 _,197 _,
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 }
169};231};
170232
171pub const SignatureScheme = enum(u16) {233pub const SignatureScheme = enum(u16) {
lib/std/crypto/tls/Client.zig+14-7
...@@ -89,12 +89,11 @@ pub const StreamInterface = struct {...@@ -89,12 +89,11 @@ pub const StreamInterface = struct {
89};89};
9090
91pub fn InitError(comptime Stream: type) type {91pub 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{
93 InsufficientEntropy,93 InsufficientEntropy,
94 DiskQuota,94 DiskQuota,
95 LockViolation,95 LockViolation,
96 NotOpenForWriting,96 NotOpenForWriting,
97 TlsAlert,
98 TlsUnexpectedMessage,97 TlsUnexpectedMessage,
99 TlsIllegalParameter,98 TlsIllegalParameter,
100 TlsDecryptFailure,99 TlsDecryptFailure,
...@@ -251,8 +250,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In...@@ -251,8 +250,11 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
251 const level = ptd.decode(tls.AlertLevel);250 const level = ptd.decode(tls.AlertLevel);
252 const desc = ptd.decode(tls.AlertDescription);251 const desc = ptd.decode(tls.AlertDescription);
253 _ = level;252 _ = level;
254 _ = desc;253
255 return error.TlsAlert;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;
256 },258 },
257 .handshake => {259 .handshake => {
258 try ptd.ensure(4);260 try ptd.ensure(4);
...@@ -1071,8 +1073,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)...@@ -1071,8 +1073,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
1071 const level = @intToEnum(tls.AlertLevel, frag[in]);1073 const level = @intToEnum(tls.AlertLevel, frag[in]);
1072 const desc = @intToEnum(tls.AlertDescription, frag[in + 1]);1074 const desc = @intToEnum(tls.AlertDescription, frag[in + 1]);
1073 _ = level;1075 _ = level;
1074 _ = desc;1076
1075 return error.TlsAlert;1077 try desc.toError();
1078 // TODO: handle server-side closures
1079 return error.TlsUnexpectedMessage;
1076 },1080 },
1077 .application_data => {1081 .application_data => {
1078 const cleartext = switch (c.application_cipher) {1082 const cleartext = switch (c.application_cipher) {
...@@ -1112,7 +1116,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)...@@ -1112,7 +1116,10 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
1112 return vp.total;1116 return vp.total;
1113 }1117 }
1114 _ = level;1118 _ = level;
1115 return error.TlsAlert;1119
1120 try desc.toError();
1121 // TODO: handle server-side closures
1122 return error.TlsUnexpectedMessage;
1116 },1123 },
1117 .handshake => {1124 .handshake => {
1118 var ct_i: usize = 0;1125 var ct_i: usize = 0;
lib/std/http/Client.zig+112-169
...@@ -36,21 +36,7 @@ pub const ConnectionPool = struct {...@@ -36,21 +36,7 @@ pub const ConnectionPool = struct {
36 is_tls: bool,36 is_tls: bool,
37 };37 };
3838
39 pub const StoredConnection = struct {39 const Queue = std.TailQueue(Connection);
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);
54 pub const Node = Queue.Node;40 pub const Node = Queue.Node;
5541
56 mutex: std.Thread.Mutex = .{},42 mutex: std.Thread.Mutex = .{},
...@@ -69,7 +55,7 @@ pub const ConnectionPool = struct {...@@ -69,7 +55,7 @@ pub const ConnectionPool = struct {
6955
70 var next = pool.free.last;56 var next = pool.free.last;
71 while (next) |node| : (next = node.prev) {57 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;
73 if (node.data.port != criteria.port) continue;59 if (node.data.port != criteria.port) continue;
74 if (!mem.eql(u8, node.data.host, criteria.host)) continue;60 if (!mem.eql(u8, node.data.host, criteria.host)) continue;
7561
...@@ -160,45 +146,105 @@ pub const ConnectionPool = struct {...@@ -160,45 +146,105 @@ pub const ConnectionPool = struct {
160146
161/// An interface to either a plain or TLS connection.147/// An interface to either a plain or TLS connection.
162pub const Connection = struct {148pub const Connection = struct {
149 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
150 pub const Protocol = enum { plain, tls };
151
163 stream: net.Stream,152 stream: net.Stream,
164 /// undefined unless protocol is tls.153 /// undefined unless protocol is tls.
165 tls_client: *std.crypto.tls.Client,154 tls_client: *std.crypto.tls.Client,
155
166 protocol: Protocol,156 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 {163 read_start: u16 = 0,
171 return switch (conn.protocol) {164 read_end: u16 = 0,
172 .plain => conn.stream.read(buffer),165 read_buf: [buffer_size]u8 = undefined,
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 }
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 {
184 return switch (conn.protocol) {168 return switch (conn.protocol) {
185 .plain => conn.stream.readAtLeast(buffer, len),169 .plain => conn.stream.readAtLeast(buffer, len),
186 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),170 .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
187 } catch |err| switch (err) {171 } catch |err| {
188 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,172 // TODO: https://github.com/ziglang/zig/issues/2473
189 error.TlsAlert => return error.TlsAlert,173 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
190 error.ConnectionTimedOut => return error.ConnectionTimedOut,174
191 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,175 switch (err) {
192 else => return error.UnexpectedReadFailure,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 }
193 };181 };
194 }182 }
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
196 pub const ReadError = error{241 pub const ReadError = error{
197 TlsFailure,242 TlsFailure,
198 TlsAlert,243 TlsAlert,
199 ConnectionTimedOut,244 ConnectionTimedOut,
200 ConnectionResetByPeer,245 ConnectionResetByPeer,
201 UnexpectedReadFailure,246 UnexpectedReadFailure,
247 EndOfStream,
202 };248 };
203249
204 pub const Reader = std.io.Reader(*Connection, ReadError, read);250 pub const Reader = std.io.Reader(*Connection, ReadError, read);
...@@ -247,111 +293,10 @@ pub const Connection = struct {...@@ -247,111 +293,10 @@ pub const Connection = struct {
247293
248 conn.stream.close();294 conn.stream.close();
249 }295 }
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();297 pub fn deinit(conn: *Connection, client: *const Client) void {
303 }298 conn.close(client);
304299 client.allocator.free(conn.host);
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);
355 }300 }
356};301};
357302
...@@ -585,11 +530,12 @@ pub const Request = struct {...@@ -585,11 +530,12 @@ pub const Request = struct {
585 };530 };
586 }531 }
587532
588 pub const StartError = BufferedConnection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };533 pub const StartError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
589534
590 /// Send the request to the server.535 /// Send the request to the server.
591 pub fn start(req: *Request) StartError!void {536 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
594 try w.writeAll(@tagName(req.method));540 try w.writeAll(@tagName(req.method));
595 try w.writeByte(' ');541 try w.writeByte(' ');
...@@ -663,10 +609,10 @@ pub const Request = struct {...@@ -663,10 +609,10 @@ pub const Request = struct {
663609
664 try w.writeAll("\r\n");610 try w.writeAll("\r\n");
665611
666 try req.connection.data.buffered.flush();612 try buffered.flush();
667 }613 }
668614
669 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;615 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
670616
671 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);617 pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead);
672618
...@@ -679,7 +625,7 @@ pub const Request = struct {...@@ -679,7 +625,7 @@ pub const Request = struct {
679625
680 var index: usize = 0;626 var index: usize = 0;
681 while (index == 0) {627 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);
683 if (amt == 0 and req.response.parser.done) break;629 if (amt == 0 and req.response.parser.done) break;
684 index += amt;630 index += amt;
685 }631 }
...@@ -697,10 +643,10 @@ pub const Request = struct {...@@ -697,10 +643,10 @@ pub const Request = struct {
697 pub fn wait(req: *Request) WaitError!void {643 pub fn wait(req: *Request) WaitError!void {
698 while (true) { // handle redirects644 while (true) { // handle redirects
699 while (true) { // read headers645 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());648 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.peek());
703 req.connection.data.buffered.clear(@intCast(u16, nchecked));649 req.connection.data.drop(@intCast(u16, nchecked));
704650
705 if (req.response.parser.state.isContent()) break;651 if (req.response.parser.state.isContent()) break;
706 }652 }
...@@ -816,10 +762,10 @@ pub const Request = struct {...@@ -816,10 +762,10 @@ pub const Request = struct {
816 const has_trail = !req.response.parser.state.isContent();762 const has_trail = !req.response.parser.state.isContent();
817763
818 while (!req.response.parser.state.isContent()) { // read trailing headers764 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());767 const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.peek());
822 req.connection.data.buffered.clear(@intCast(u16, nchecked));768 req.connection.data.drop(@intCast(u16, nchecked));
823 }769 }
824770
825 if (has_trail) {771 if (has_trail) {
...@@ -845,7 +791,7 @@ pub const Request = struct {...@@ -845,7 +791,7 @@ pub const Request = struct {
845 return index;791 return index;
846 }792 }
847793
848 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };794 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
849795
850 pub const Writer = std.io.Writer(*Request, WriteError, write);796 pub const Writer = std.io.Writer(*Request, WriteError, write);
851797
...@@ -857,16 +803,16 @@ pub const Request = struct {...@@ -857,16 +803,16 @@ pub const Request = struct {
857 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {803 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
858 switch (req.transfer_encoding) {804 switch (req.transfer_encoding) {
859 .chunked => {805 .chunked => {
860 try req.connection.data.buffered.writer().print("{x}\r\n", .{bytes.len});806 try req.connection.data.writer().print("{x}\r\n", .{bytes.len});
861 try req.connection.data.buffered.writeAll(bytes);807 try req.connection.data.writeAll(bytes);
862 try req.connection.data.buffered.writeAll("\r\n");808 try req.connection.data.writeAll("\r\n");
863809
864 return bytes.len;810 return bytes.len;
865 },811 },
866 .content_length => |*len| {812 .content_length => |*len| {
867 if (len.* < bytes.len) return error.MessageTooLong;813 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);
870 len.* -= amt;816 len.* -= amt;
871 return amt;817 return amt;
872 },818 },
...@@ -886,12 +832,10 @@ pub const Request = struct {...@@ -886,12 +832,10 @@ pub const Request = struct {
886 /// Finish the body of a request. This notifies the server that you have no more data to send.832 /// Finish the body of a request. This notifies the server that you have no more data to send.
887 pub fn finish(req: *Request) FinishError!void {833 pub fn finish(req: *Request) FinishError!void {
888 switch (req.transfer_encoding) {834 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"),
890 .content_length => |len| if (len != 0) return error.MessageNotCompleted,836 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
891 .none => {},837 .none => {},
892 }838 }
893
894 try req.connection.data.buffered.flush();
895 }839 }
896};840};
897841
...@@ -948,11 +892,10 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:...@@ -948,11 +892,10 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:
948 errdefer stream.close();892 errdefer stream.close();
949893
950 conn.data = .{894 conn.data = .{
951 .buffered = .{ .conn = .{895 .stream = stream,
952 .stream = stream,896 .tls_client = undefined,
953 .tls_client = undefined,897 .protocol = protocol,
954 .protocol = protocol,898
955 } },
956 .host = try client.allocator.dupe(u8, host),899 .host = try client.allocator.dupe(u8, host),
957 .port = port,900 .port = port,
958 };901 };
...@@ -961,13 +904,13 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:...@@ -961,13 +904,13 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol:
961 switch (protocol) {904 switch (protocol) {
962 .plain => {},905 .plain => {},
963 .tls => {906 .tls => {
964 conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client);907 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
965 errdefer client.allocator.destroy(conn.data.buffered.conn.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;
968 // This is appropriate for HTTPS because the HTTP headers contain911 // This is appropriate for HTTPS because the HTTP headers contain
969 // the content length which is used to detect truncation attacks.912 // 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;
971 },914 },
972 }915 }
973916
...@@ -1003,7 +946,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio...@@ -1003,7 +946,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio
1003 }946 }
1004}947}
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{
1007 UnsupportedUrlScheme,950 UnsupportedUrlScheme,
1008 UriMissingHost,951 UriMissingHost,
1009952
lib/std/http/Server.zig+85-139
...@@ -16,39 +16,92 @@ socket: net.StreamServer,...@@ -16,39 +16,92 @@ socket: net.StreamServer,
1616
17/// An interface to either a plain or TLS connection.17/// An interface to either a plain or TLS connection.
18pub const Connection = struct {18pub const Connection = struct {
19 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
20 pub const Protocol = enum { plain };
21
19 stream: net.Stream,22 stream: net.Stream,
20 protocol: Protocol,23 protocol: Protocol,
2124
22 closing: bool = true,25 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 {
27 return switch (conn.protocol) {32 return switch (conn.protocol) {
28 .plain => conn.stream.read(buffer),33 .plain => conn.stream.readAtLeast(buffer, len),
29 // .tls => return conn.tls_client.read(conn.stream, buffer),34 // .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
30 } catch |err| switch (err) {35 } catch |err| {
31 error.ConnectionTimedOut => return error.ConnectionTimedOut,36 switch (err) {
32 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,37 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
33 else => return error.UnexpectedReadFailure,38 else => return error.UnexpectedReadFailure,
39 }
34 };40 };
35 }41 }
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
37 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {60 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
38 return switch (conn.protocol) {61 assert(len <= buffer.len);
39 .plain => conn.stream.readAtLeast(buffer, len),62
40 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),63 var out_index: u16 = 0;
41 } catch |err| switch (err) {64 while (out_index < len) {
42 error.ConnectionTimedOut => return error.ConnectionTimedOut,65 const available_read = conn.read_end - conn.read_start;
43 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,66 const available_buffer = buffer.len - out_index;
44 else => return error.UnexpectedReadFailure,67
45 };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);
46 }98 }
4799
48 pub const ReadError = error{100 pub const ReadError = error{
49 ConnectionTimedOut,101 ConnectionTimedOut,
50 ConnectionResetByPeer,102 ConnectionResetByPeer,
51 UnexpectedReadFailure,103 UnexpectedReadFailure,
104 EndOfStream,
52 };105 };
53106
54 pub const Reader = std.io.Reader(*Connection, ReadError, read);107 pub const Reader = std.io.Reader(*Connection, ReadError, read);
...@@ -93,112 +146,6 @@ pub const Connection = struct {...@@ -93,112 +146,6 @@ pub const Connection = struct {
93 }146 }
94};147};
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
202/// The mode of transport for responses.149/// The mode of transport for responses.
203pub const ResponseTransfer = union(enum) {150pub const ResponseTransfer = union(enum) {
204 content_length: u64,151 content_length: u64,
...@@ -351,7 +298,7 @@ pub const Response = struct {...@@ -351,7 +298,7 @@ pub const Response = struct {
351298
352 allocator: Allocator,299 allocator: Allocator,
353 address: net.Address,300 address: net.Address,
354 connection: BufferedConnection,301 connection: Connection,
355302
356 headers: http.Headers,303 headers: http.Headers,
357 request: Request,304 request: Request,
...@@ -388,7 +335,7 @@ pub const Response = struct {...@@ -388,7 +335,7 @@ pub const Response = struct {
388335
389 if (!res.request.parser.done) {336 if (!res.request.parser.done) {
390 // If the response wasn't fully read, then we need to close the connection.337 // 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;
392 return .closing;339 return .closing;
393 }340 }
394341
...@@ -402,9 +349,9 @@ pub const Response = struct {...@@ -402,9 +349,9 @@ pub const Response = struct {
402 const req_connection = res.request.headers.getFirstValue("connection");349 const req_connection = res.request.headers.getFirstValue("connection");
403 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);350 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);
404 if (req_keepalive and (res_keepalive or res_connection == null)) {351 if (req_keepalive and (res_keepalive or res_connection == null)) {
405 res.connection.conn.closing = false;352 res.connection.closing = false;
406 } else {353 } else {
407 res.connection.conn.closing = true;354 res.connection.closing = true;
408 }355 }
409356
410 switch (res.request.compression) {357 switch (res.request.compression) {
...@@ -434,14 +381,14 @@ pub const Response = struct {...@@ -434,14 +381,14 @@ pub const Response = struct {
434 .parser = res.request.parser,381 .parser = res.request.parser,
435 };382 };
436383
437 if (res.connection.conn.closing) {384 if (res.connection.closing) {
438 return .closing;385 return .closing;
439 } else {386 } else {
440 return .reset;387 return .reset;
441 }388 }
442 }389 }
443390
444 pub const DoError = BufferedConnection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };391 pub const DoError = Connection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
445392
446 /// Send the response headers.393 /// Send the response headers.
447 pub fn do(res: *Response) !void {394 pub fn do(res: *Response) !void {
...@@ -450,7 +397,8 @@ pub const Response = struct {...@@ -450,7 +397,8 @@ pub const Response = struct {
450 .first, .start, .responded, .finished => unreachable,397 .first, .start, .responded, .finished => unreachable,
451 }398 }
452399
453 const w = res.connection.writer();400 var buffered = std.io.bufferedWriter(res.connection.writer());
401 const w = buffered.writer();
454402
455 try w.writeAll(@tagName(res.version));403 try w.writeAll(@tagName(res.version));
456 try w.writeByte(' ');404 try w.writeByte(' ');
...@@ -508,10 +456,10 @@ pub const Response = struct {...@@ -508,10 +456,10 @@ pub const Response = struct {
508456
509 try w.writeAll("\r\n");457 try w.writeAll("\r\n");
510458
511 try res.connection.flush();459 try buffered.flush();
512 }460 }
513461
514 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;462 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
515463
516 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);464 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);
517465
...@@ -532,7 +480,7 @@ pub const Response = struct {...@@ -532,7 +480,7 @@ pub const Response = struct {
532 return index;480 return index;
533 }481 }
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
537 /// Wait for the client to send a complete request head.485 /// Wait for the client to send a complete request head.
538 pub fn wait(res: *Response) WaitError!void {486 pub fn wait(res: *Response) WaitError!void {
...@@ -545,7 +493,7 @@ pub const Response = struct {...@@ -545,7 +493,7 @@ pub const Response = struct {
545 try res.connection.fill();493 try res.connection.fill();
546494
547 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());495 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
550 if (res.request.parser.state.isContent()) break;498 if (res.request.parser.state.isContent()) break;
551 }499 }
...@@ -612,7 +560,7 @@ pub const Response = struct {...@@ -612,7 +560,7 @@ pub const Response = struct {
612 try res.connection.fill();560 try res.connection.fill();
613561
614 const nchecked = try res.request.parser.checkCompleteHead(res.allocator, res.connection.peek());562 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));
616 }564 }
617565
618 if (has_trail) {566 if (has_trail) {
...@@ -637,7 +585,7 @@ pub const Response = struct {...@@ -637,7 +585,7 @@ pub const Response = struct {
637 return index;585 return index;
638 }586 }
639587
640 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };588 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
641589
642 pub const Writer = std.io.Writer(*Response, WriteError, write);590 pub const Writer = std.io.Writer(*Response, WriteError, write);
643591
...@@ -692,8 +640,6 @@ pub const Response = struct {...@@ -692,8 +640,6 @@ pub const Response = struct {
692 .content_length => |len| if (len != 0) return error.MessageNotCompleted,640 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
693 .none => {},641 .none => {},
694 }642 }
695
696 try res.connection.flush();
697 }643 }
698};644};
699645
...@@ -742,10 +688,10 @@ pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {...@@ -742,10 +688,10 @@ pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {
742 return Response{688 return Response{
743 .allocator = options.allocator,689 .allocator = options.allocator,
744 .address = in.address,690 .address = in.address,
745 .connection = .{ .conn = .{691 .connection = .{
746 .stream = in.stream,692 .stream = in.stream,
747 .protocol = .plain,693 .protocol = .plain,
748 } },694 },
749 .headers = .{ .allocator = options.allocator },695 .headers = .{ .allocator = options.allocator },
750 .request = .{696 .request = .{
751 .version = undefined,697 .version = undefined,
lib/std/http/protocol.zig+58-58
...@@ -513,8 +513,8 @@ pub const HeadersParser = struct {...@@ -513,8 +513,8 @@ pub const HeadersParser = struct {
513 ///513 ///
514 /// If `skip` is true, the buffer will be unused and the body will be skipped.514 /// If `skip` is true, the buffer will be unused and the body will be skipped.
515 ///515 ///
516 /// See `std.http.Client.BufferedConnection for an example of `bconn`.516 /// See `std.http.Client.BufferedConnection for an example of `conn`.
517 pub fn read(r: *HeadersParser, bconn: anytype, buffer: []u8, skip: bool) !usize {517 pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
518 assert(r.state.isContent());518 assert(r.state.isContent());
519 if (r.done) return 0;519 if (r.done) return 0;
520520
...@@ -526,10 +526,10 @@ pub const HeadersParser = struct {...@@ -526,10 +526,10 @@ pub const HeadersParser = struct {
526 const data_avail = r.next_chunk_length;526 const data_avail = r.next_chunk_length;
527527
528 if (skip) {528 if (skip) {
529 try bconn.fill();529 try conn.fill();
530530
531 const nread = @min(bconn.peek().len, data_avail);531 const nread = @min(conn.peek().len, data_avail);
532 bconn.clear(@intCast(u16, nread));532 conn.drop(@intCast(u16, nread));
533 r.next_chunk_length -= nread;533 r.next_chunk_length -= nread;
534534
535 if (r.next_chunk_length == 0) r.done = true;535 if (r.next_chunk_length == 0) r.done = true;
...@@ -539,7 +539,7 @@ pub const HeadersParser = struct {...@@ -539,7 +539,7 @@ pub const HeadersParser = struct {
539 const out_avail = buffer.len;539 const out_avail = buffer.len;
540540
541 const can_read = @intCast(usize, @min(data_avail, out_avail));541 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]);
543 r.next_chunk_length -= nread;543 r.next_chunk_length -= nread;
544544
545 if (r.next_chunk_length == 0) r.done = true;545 if (r.next_chunk_length == 0) r.done = true;
...@@ -548,15 +548,15 @@ pub const HeadersParser = struct {...@@ -548,15 +548,15 @@ pub const HeadersParser = struct {
548 }548 }
549 },549 },
550 .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {550 .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());553 const i = r.findChunkedLen(conn.peek());
554 bconn.clear(@intCast(u16, i));554 conn.drop(@intCast(u16, i));
555555
556 switch (r.state) {556 switch (r.state) {
557 .invalid => return error.HttpChunkInvalid,557 .invalid => return error.HttpChunkInvalid,
558 .chunk_data => if (r.next_chunk_length == 0) {558 .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")) {
560 r.state = .finished;560 r.state = .finished;
561 } else {561 } else {
562 // The trailer section is formatted identically to the header section.562 // The trailer section is formatted identically to the header section.
...@@ -576,14 +576,14 @@ pub const HeadersParser = struct {...@@ -576,14 +576,14 @@ pub const HeadersParser = struct {
576 const out_avail = buffer.len - out_index;576 const out_avail = buffer.len - out_index;
577577
578 if (skip) {578 if (skip) {
579 try bconn.fill();579 try conn.fill();
580580
581 const nread = @min(bconn.peek().len, data_avail);581 const nread = @min(conn.peek().len, data_avail);
582 bconn.clear(@intCast(u16, nread));582 conn.drop(@intCast(u16, nread));
583 r.next_chunk_length -= nread;583 r.next_chunk_length -= nread;
584 } else {584 } else {
585 const can_read = @intCast(usize, @min(data_avail, out_avail));585 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]);
587 r.next_chunk_length -= nread;587 r.next_chunk_length -= nread;
588 out_index += nread;588 out_index += nread;
589 }589 }
...@@ -628,74 +628,74 @@ const MockBufferedConnection = struct {...@@ -628,74 +628,74 @@ const MockBufferedConnection = struct {
628 start: u16 = 0,628 start: u16 = 0,
629 end: u16 = 0,629 end: u16 = 0,
630630
631 pub fn fill(bconn: *MockBufferedConnection) ReadError!void {631 pub fn fill(conn: *MockBufferedConnection) ReadError!void {
632 if (bconn.end != bconn.start) return;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..]);
635 if (nread == 0) return error.EndOfStream;635 if (nread == 0) return error.EndOfStream;
636 bconn.start = 0;636 conn.start = 0;
637 bconn.end = @truncate(u16, nread);637 conn.end = @truncate(u16, nread);
638 }638 }
639639
640 pub fn peek(bconn: *MockBufferedConnection) []const u8 {640 pub fn peek(conn: *MockBufferedConnection) []const u8 {
641 return bconn.buf[bconn.start..bconn.end];641 return conn.buf[conn.start..conn.end];
642 }642 }
643643
644 pub fn clear(bconn: *MockBufferedConnection, num: u16) void {644 pub fn drop(conn: *MockBufferedConnection, num: u16) void {
645 bconn.start += num;645 conn.start += num;
646 }646 }
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 {
649 var out_index: u16 = 0;649 var out_index: u16 = 0;
650 while (out_index < len) {650 while (out_index < len) {
651 const available = bconn.end - bconn.start;651 const available = conn.end - conn.start;
652 const left = buffer.len - out_index;652 const left = buffer.len - out_index;
653653
654 if (available > 0) {654 if (available > 0) {
655 const can_read = @truncate(u16, @min(available, left));655 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]);
658 out_index += can_read;658 out_index += can_read;
659 bconn.start += can_read;659 conn.start += can_read;
660660
661 continue;661 continue;
662 }662 }
663663
664 if (left > bconn.buf.len) {664 if (left > conn.buf.len) {
665 // skip the buffer if the output is large enough665 // 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..]);
667 }667 }
668668
669 try bconn.fill();669 try conn.fill();
670 }670 }
671671
672 return out_index;672 return out_index;
673 }673 }
674674
675 pub fn read(bconn: *MockBufferedConnection, buffer: []u8) ReadError!usize {675 pub fn read(conn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
676 return bconn.readAtLeast(buffer, 1);676 return conn.readAtLeast(buffer, 1);
677 }677 }
678678
679 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};679 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};
680 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);680 pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read);
681681
682 pub fn reader(bconn: *MockBufferedConnection) Reader {682 pub fn reader(conn: *MockBufferedConnection) Reader {
683 return Reader{ .context = bconn };683 return Reader{ .context = conn };
684 }684 }
685685
686 pub fn writeAll(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!void {686 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
687 return bconn.conn.writeAll(buffer);687 return conn.conn.writeAll(buffer);
688 }688 }
689689
690 pub fn write(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {690 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
691 return bconn.conn.write(buffer);691 return conn.conn.write(buffer);
692 }692 }
693693
694 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;694 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
695 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);695 pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write);
696696
697 pub fn writer(bconn: *MockBufferedConnection) Writer {697 pub fn writer(conn: *MockBufferedConnection) Writer {
698 return Writer{ .context = bconn };698 return Writer{ .context = conn };
699 }699 }
700};700};
701701
...@@ -753,15 +753,15 @@ test "HeadersParser.read length" {...@@ -753,15 +753,15 @@ test "HeadersParser.read length" {
753 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";753 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
754 var fbs = std.io.fixedBufferStream(data);754 var fbs = std.io.fixedBufferStream(data);
755755
756 var bconn = MockBufferedConnection{756 var conn = MockBufferedConnection{
757 .conn = fbs,757 .conn = fbs,
758 };758 };
759759
760 while (true) { // read headers760 while (true) { // read headers
761 try bconn.fill();761 try conn.fill();
762762
763 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());763 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
764 bconn.clear(@intCast(u16, nchecked));764 conn.drop(@intCast(u16, nchecked));
765765
766 if (r.state.isContent()) break;766 if (r.state.isContent()) break;
767 }767 }
...@@ -769,7 +769,7 @@ test "HeadersParser.read length" {...@@ -769,7 +769,7 @@ test "HeadersParser.read length" {
769 var buf: [8]u8 = undefined;769 var buf: [8]u8 = undefined;
770770
771 r.next_chunk_length = 5;771 r.next_chunk_length = 5;
772 const len = try r.read(&bconn, &buf, false);772 const len = try r.read(&conn, &buf, false);
773 try std.testing.expectEqual(@as(usize, 5), len);773 try std.testing.expectEqual(@as(usize, 5), len);
774 try std.testing.expectEqualStrings("Hello", buf[0..len]);774 try std.testing.expectEqualStrings("Hello", buf[0..len]);
775775
...@@ -784,22 +784,22 @@ test "HeadersParser.read chunked" {...@@ -784,22 +784,22 @@ test "HeadersParser.read chunked" {
784 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";784 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";
785 var fbs = std.io.fixedBufferStream(data);785 var fbs = std.io.fixedBufferStream(data);
786786
787 var bconn = MockBufferedConnection{787 var conn = MockBufferedConnection{
788 .conn = fbs,788 .conn = fbs,
789 };789 };
790790
791 while (true) { // read headers791 while (true) { // read headers
792 try bconn.fill();792 try conn.fill();
793793
794 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());794 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
795 bconn.clear(@intCast(u16, nchecked));795 conn.drop(@intCast(u16, nchecked));
796796
797 if (r.state.isContent()) break;797 if (r.state.isContent()) break;
798 }798 }
799 var buf: [8]u8 = undefined;799 var buf: [8]u8 = undefined;
800800
801 r.state = .chunk_head_size;801 r.state = .chunk_head_size;
802 const len = try r.read(&bconn, &buf, false);802 const len = try r.read(&conn, &buf, false);
803 try std.testing.expectEqual(@as(usize, 5), len);803 try std.testing.expectEqual(@as(usize, 5), len);
804 try std.testing.expectEqualStrings("Hello", buf[0..len]);804 try std.testing.expectEqualStrings("Hello", buf[0..len]);
805805
...@@ -814,30 +814,30 @@ test "HeadersParser.read chunked trailer" {...@@ -814,30 +814,30 @@ test "HeadersParser.read chunked trailer" {
814 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";814 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";
815 var fbs = std.io.fixedBufferStream(data);815 var fbs = std.io.fixedBufferStream(data);
816816
817 var bconn = MockBufferedConnection{817 var conn = MockBufferedConnection{
818 .conn = fbs,818 .conn = fbs,
819 };819 };
820820
821 while (true) { // read headers821 while (true) { // read headers
822 try bconn.fill();822 try conn.fill();
823823
824 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());824 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
825 bconn.clear(@intCast(u16, nchecked));825 conn.drop(@intCast(u16, nchecked));
826826
827 if (r.state.isContent()) break;827 if (r.state.isContent()) break;
828 }828 }
829 var buf: [8]u8 = undefined;829 var buf: [8]u8 = undefined;
830830
831 r.state = .chunk_head_size;831 r.state = .chunk_head_size;
832 const len = try r.read(&bconn, &buf, false);832 const len = try r.read(&conn, &buf, false);
833 try std.testing.expectEqual(@as(usize, 5), len);833 try std.testing.expectEqual(@as(usize, 5), len);
834 try std.testing.expectEqualStrings("Hello", buf[0..len]);834 try std.testing.expectEqualStrings("Hello", buf[0..len]);
835835
836 while (true) { // read headers836 while (true) { // read headers
837 try bconn.fill();837 try conn.fill();
838838
839 const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek());839 const nchecked = try r.checkCompleteHead(std.testing.allocator, conn.peek());
840 bconn.clear(@intCast(u16, nchecked));840 conn.drop(@intCast(u16, nchecked));
841841
842 if (r.state.isContent()) break;842 if (r.state.isContent()) break;
843 }843 }
test/standalone/http.zig-1
...@@ -86,7 +86,6 @@ fn handleRequest(res: *Server.Response) !void {...@@ -86,7 +86,6 @@ fn handleRequest(res: *Server.Response) !void {
86 try res.writeAll("World!\n");86 try res.writeAll("World!\n");
87 // try res.finish();87 // try res.finish();
88 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");88 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
89 try res.connection.flush();
90 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {89 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {
91 res.transfer_encoding = .chunked;90 res.transfer_encoding = .chunked;
9291