authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-08 12:33:53-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-08-08 12:33:53-07:00
log1ba6838bc3919f2e1306b01a11fd3d5dd01fefe1
tree15eb64ba704c52c178f49db30d3fe50a0a27a8b5
parentd2f77920398976670aa25cb5fd278435d08eba25
parent8da645c883c3477ef21c72603434f130c1c43e65
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24740 from ziglang/http-plus-fixes

fetch, tls, and http fixes

7 files changed, 172 insertions(+), 155 deletions(-)

lib/std/Io/Reader.zig+1-3
...@@ -25,9 +25,7 @@ pub const VTable = struct {...@@ -25,9 +25,7 @@ pub const VTable = struct {
25 ///25 ///
26 /// Returns the number of bytes written, which will be at minimum `0` and26 /// Returns the number of bytes written, which will be at minimum `0` and
27 /// at most `limit`. The number returned, including zero, does not indicate27 /// at most `limit`. The number returned, including zero, does not indicate
28 /// end of stream. `limit` is guaranteed to be at least as large as the28 /// end of stream.
29 /// buffer capacity of `w`, a value whose minimum size is determined by the
30 /// stream implementation.
31 ///29 ///
32 /// The reader's internal logical seek position moves forward in accordance30 /// The reader's internal logical seek position moves forward in accordance
33 /// with the number of bytes returned from this function.31 /// with the number of bytes returned from this function.
lib/std/crypto/tls/Client.zig+45-23
...@@ -61,9 +61,6 @@ pub const ReadError = error{...@@ -61,9 +61,6 @@ pub const ReadError = error{
61 TlsUnexpectedMessage,61 TlsUnexpectedMessage,
62 TlsIllegalParameter,62 TlsIllegalParameter,
63 TlsSequenceOverflow,63 TlsSequenceOverflow,
64 /// The buffer provided to the read function was not at least
65 /// `min_buffer_len`.
66 OutputBufferUndersize,
67};64};
6865
69pub const SslKeyLog = struct {66pub const SslKeyLog = struct {
...@@ -372,7 +369,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -372,7 +369,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
372 };369 };
373 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, pv.server_handshake_key) catch370 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, pv.server_handshake_key) catch
374 return error.TlsBadRecordMac;371 return error.TlsBadRecordMac;
375 cleartext_fragment_end += std.mem.trimEnd(u8, cleartext, "\x00").len;372 // TODO use scalar, non-slice version
373 cleartext_fragment_end += mem.trimEnd(u8, cleartext, "\x00").len;
376 },374 },
377 }375 }
378 read_seq += 1;376 read_seq += 1;
...@@ -395,9 +393,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -395,9 +393,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
395 const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..];393 const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..];
396 if (message_len > cleartext_fragment_buf.len) return error.TlsRecordOverflow;394 if (message_len > cleartext_fragment_buf.len) return error.TlsRecordOverflow;
397 const cleartext = cleartext_fragment_buf[0..message_len];395 const cleartext = cleartext_fragment_buf[0..message_len];
398 const ad = std.mem.toBytes(big(read_seq)) ++396 const ad = mem.toBytes(big(read_seq)) ++
399 record_header[0 .. 1 + 2] ++397 record_header[0 .. 1 + 2] ++
400 std.mem.toBytes(big(message_len));398 mem.toBytes(big(message_len));
401 const record_iv = record_decoder.array(P.record_iv_length).*;399 const record_iv = record_decoder.array(P.record_iv_length).*;
402 const masked_read_seq = read_seq &400 const masked_read_seq = read_seq &
403 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);401 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
...@@ -738,7 +736,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -738,7 +736,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
738 &.{ "server finished", &p.transcript_hash.finalResult() },736 &.{ "server finished", &p.transcript_hash.finalResult() },
739 P.verify_data_length,737 P.verify_data_length,
740 ),738 ),
741 .app_cipher = std.mem.bytesToValue(P.Tls_1_2, &key_block),739 .app_cipher = mem.bytesToValue(P.Tls_1_2, &key_block),
742 } };740 } };
743 const pv = &p.version.tls_1_2;741 const pv = &p.version.tls_1_2;
744 const nonce: [P.AEAD.nonce_length]u8 = nonce: {742 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
...@@ -756,7 +754,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -756,7 +754,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
756 client_verify_cleartext.len ..][0..client_verify_cleartext.len],754 client_verify_cleartext.len ..][0..client_verify_cleartext.len],
757 client_verify_msg[client_verify_msg.len - P.mac_length ..][0..P.mac_length],755 client_verify_msg[client_verify_msg.len - P.mac_length ..][0..P.mac_length],
758 &client_verify_cleartext,756 &client_verify_cleartext,
759 std.mem.toBytes(big(write_seq)) ++ client_verify_msg[0 .. 1 + 2] ++ int(u16, client_verify_cleartext.len),757 mem.toBytes(big(write_seq)) ++ client_verify_msg[0 .. 1 + 2] ++ int(u16, client_verify_cleartext.len),
760 nonce,758 nonce,
761 pv.app_cipher.client_write_key,759 pv.app_cipher.client_write_key,
762 );760 );
...@@ -873,7 +871,10 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -873,7 +871,10 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
873 .input = input,871 .input = input,
874 .reader = .{872 .reader = .{
875 .buffer = options.read_buffer,873 .buffer = options.read_buffer,
876 .vtable = &.{ .stream = stream },874 .vtable = &.{
875 .stream = stream,
876 .readVec = readVec,
877 },
877 .seek = 0,878 .seek = 0,
878 .end = 0,879 .end = 0,
879 },880 },
...@@ -1017,7 +1018,7 @@ fn prepareCiphertextRecord(...@@ -1017,7 +1018,7 @@ fn prepareCiphertextRecord(
1017 const nonce = nonce: {1018 const nonce = nonce: {
1018 const V = @Vector(P.AEAD.nonce_length, u8);1019 const V = @Vector(P.AEAD.nonce_length, u8);
1019 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);1020 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1020 const operand: V = pad ++ std.mem.toBytes(big(c.write_seq));1021 const operand: V = pad ++ mem.toBytes(big(c.write_seq));
1021 break :nonce @as(V, pv.client_iv) ^ operand;1022 break :nonce @as(V, pv.client_iv) ^ operand;
1022 };1023 };
1023 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);1024 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
...@@ -1048,7 +1049,7 @@ fn prepareCiphertextRecord(...@@ -1048,7 +1049,7 @@ fn prepareCiphertextRecord(
1048 record_header.* = .{@intFromEnum(inner_content_type)} ++1049 record_header.* = .{@intFromEnum(inner_content_type)} ++
1049 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++1050 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
1050 int(u16, P.record_iv_length + message_len + P.mac_length);1051 int(u16, P.record_iv_length + message_len + P.mac_length);
1051 const ad = std.mem.toBytes(big(c.write_seq)) ++ record_header[0 .. 1 + 2] ++ int(u16, message_len);1052 const ad = mem.toBytes(big(c.write_seq)) ++ record_header[0 .. 1 + 2] ++ int(u16, message_len);
1052 const record_iv = ciphertext_buf[ciphertext_end..][0..P.record_iv_length];1053 const record_iv = ciphertext_buf[ciphertext_end..][0..P.record_iv_length];
1053 ciphertext_end += P.record_iv_length;1054 ciphertext_end += P.record_iv_length;
1054 const nonce: [P.AEAD.nonce_length]u8 = nonce: {1055 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
...@@ -1076,7 +1077,22 @@ pub fn eof(c: Client) bool {...@@ -1076,7 +1077,22 @@ pub fn eof(c: Client) bool {
1076}1077}
10771078
1078fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {1079fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
1080 // This function writes exclusively to the buffer.
1081 _ = w;
1082 _ = limit;
1083 const c: *Client = @alignCast(@fieldParentPtr("reader", r));
1084 return readIndirect(c);
1085}
1086
1087fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize {
1088 // This function writes exclusively to the buffer.
1089 _ = data;
1079 const c: *Client = @alignCast(@fieldParentPtr("reader", r));1090 const c: *Client = @alignCast(@fieldParentPtr("reader", r));
1091 return readIndirect(c);
1092}
1093
1094fn readIndirect(c: *Client) Reader.Error!usize {
1095 const r = &c.reader;
1080 if (c.eof()) return error.EndOfStream;1096 if (c.eof()) return error.EndOfStream;
1081 const input = c.input;1097 const input = c.input;
1082 // If at least one full encrypted record is not buffered, read once.1098 // If at least one full encrypted record is not buffered, read once.
...@@ -1108,8 +1124,13 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1108,8 +1124,13 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1108 if (record_end > input.buffered().len) return 0;1124 if (record_end > input.buffered().len) return 0;
1109 }1125 }
11101126
1111 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;1127 if (r.seek == r.end) {
1112 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {1128 r.seek = 0;
1129 r.end = 0;
1130 }
1131 const cleartext_buffer = r.buffer[r.end..];
1132
1133 const cleartext_len, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1113 inline else => |*p| switch (c.tls_version) {1134 inline else => |*p| switch (c.tls_version) {
1114 .tls_1_3 => {1135 .tls_1_3 => {
1115 const pv = &p.tls_1_3;1136 const pv = &p.tls_1_3;
...@@ -1121,23 +1142,24 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1121,23 +1142,24 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1121 const nonce = nonce: {1142 const nonce = nonce: {
1122 const V = @Vector(P.AEAD.nonce_length, u8);1143 const V = @Vector(P.AEAD.nonce_length, u8);
1123 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);1144 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1124 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));1145 const operand: V = pad ++ mem.toBytes(big(c.read_seq));
1125 break :nonce @as(V, pv.server_iv) ^ operand;1146 break :nonce @as(V, pv.server_iv) ^ operand;
1126 };1147 };
1127 const cleartext = cleartext_stack_buffer[0..ciphertext.len];1148 const cleartext = cleartext_buffer[0..ciphertext.len];
1128 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch1149 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1129 return failRead(c, error.TlsBadRecordMac);1150 return failRead(c, error.TlsBadRecordMac);
1151 // TODO use scalar, non-slice version
1130 const msg = mem.trimRight(u8, cleartext, "\x00");1152 const msg = mem.trimRight(u8, cleartext, "\x00");
1131 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };1153 break :cleartext .{ msg.len - 1, @enumFromInt(msg[msg.len - 1]) };
1132 },1154 },
1133 .tls_1_2 => {1155 .tls_1_2 => {
1134 const pv = &p.tls_1_2;1156 const pv = &p.tls_1_2;
1135 const P = @TypeOf(p.*);1157 const P = @TypeOf(p.*);
1136 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;1158 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1137 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked1159 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1138 const ad = std.mem.toBytes(big(c.read_seq)) ++1160 const ad = mem.toBytes(big(c.read_seq)) ++
1139 ad_header[0 .. 1 + 2] ++1161 ad_header[0 .. 1 + 2] ++
1140 std.mem.toBytes(big(message_len));1162 mem.toBytes(big(message_len));
1141 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked1163 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
1142 const masked_read_seq = c.read_seq &1164 const masked_read_seq = c.read_seq &
1143 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);1165 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
...@@ -1149,14 +1171,15 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1149,14 +1171,15 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1149 };1171 };
1150 const ciphertext = input.take(message_len) catch unreachable; // already peeked1172 const ciphertext = input.take(message_len) catch unreachable; // already peeked
1151 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked1173 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
1152 const cleartext = cleartext_stack_buffer[0..ciphertext.len];1174 const cleartext = cleartext_buffer[0..ciphertext.len];
1153 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch1175 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1154 return failRead(c, error.TlsBadRecordMac);1176 return failRead(c, error.TlsBadRecordMac);
1155 break :cleartext .{ cleartext, ct };1177 break :cleartext .{ cleartext.len, ct };
1156 },1178 },
1157 else => unreachable,1179 else => unreachable,
1158 },1180 },
1159 };1181 };
1182 const cleartext = cleartext_buffer[0..cleartext_len];
1160 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);1183 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1161 switch (inner_ct) {1184 switch (inner_ct) {
1162 .alert => {1185 .alert => {
...@@ -1245,9 +1268,8 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1245,9 +1268,8 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1245 return 0;1268 return 0;
1246 },1269 },
1247 .application_data => {1270 .application_data => {
1248 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);1271 r.end += cleartext.len;
1249 try w.writeAll(cleartext);1272 return 0;
1250 return cleartext.len;
1251 },1273 },
1252 else => return failRead(c, error.TlsUnexpectedMessage),1274 else => return failRead(c, error.TlsUnexpectedMessage),
1253 }1275 }
lib/std/http.zig+35-23
...@@ -292,6 +292,14 @@ pub const ContentEncoding = enum {...@@ -292,6 +292,14 @@ pub const ContentEncoding = enum {
292 });292 });
293 return map.get(s);293 return map.get(s);
294 }294 }
295
296 pub fn minBufferCapacity(ce: ContentEncoding) usize {
297 return switch (ce) {
298 .zstd => std.compress.zstd.default_window_len,
299 .gzip, .deflate => std.compress.flate.max_window_len,
300 .compress, .identity => 0,
301 };
302 }
295};303};
296304
297pub const Connection = enum {305pub const Connection = enum {
...@@ -412,7 +420,7 @@ pub const Reader = struct {...@@ -412,7 +420,7 @@ pub const Reader = struct {
412 /// * `interfaceDecompressing`420 /// * `interfaceDecompressing`
413 pub fn bodyReader(421 pub fn bodyReader(
414 reader: *Reader,422 reader: *Reader,
415 buffer: []u8,423 transfer_buffer: []u8,
416 transfer_encoding: TransferEncoding,424 transfer_encoding: TransferEncoding,
417 content_length: ?u64,425 content_length: ?u64,
418 ) *std.Io.Reader {426 ) *std.Io.Reader {
...@@ -421,7 +429,7 @@ pub const Reader = struct {...@@ -421,7 +429,7 @@ pub const Reader = struct {
421 .chunked => {429 .chunked => {
422 reader.state = .{ .body_remaining_chunk_len = .head };430 reader.state = .{ .body_remaining_chunk_len = .head };
423 reader.interface = .{431 reader.interface = .{
424 .buffer = buffer,432 .buffer = transfer_buffer,
425 .seek = 0,433 .seek = 0,
426 .end = 0,434 .end = 0,
427 .vtable = &.{435 .vtable = &.{
...@@ -435,7 +443,7 @@ pub const Reader = struct {...@@ -435,7 +443,7 @@ pub const Reader = struct {
435 if (content_length) |len| {443 if (content_length) |len| {
436 reader.state = .{ .body_remaining_content_length = len };444 reader.state = .{ .body_remaining_content_length = len };
437 reader.interface = .{445 reader.interface = .{
438 .buffer = buffer,446 .buffer = transfer_buffer,
439 .seek = 0,447 .seek = 0,
440 .end = 0,448 .end = 0,
441 .vtable = &.{449 .vtable = &.{
...@@ -460,11 +468,12 @@ pub const Reader = struct {...@@ -460,11 +468,12 @@ pub const Reader = struct {
460 /// * `interface`468 /// * `interface`
461 pub fn bodyReaderDecompressing(469 pub fn bodyReaderDecompressing(
462 reader: *Reader,470 reader: *Reader,
471 transfer_buffer: []u8,
463 transfer_encoding: TransferEncoding,472 transfer_encoding: TransferEncoding,
464 content_length: ?u64,473 content_length: ?u64,
465 content_encoding: ContentEncoding,474 content_encoding: ContentEncoding,
466 decompressor: *Decompressor,475 decompress: *Decompress,
467 decompression_buffer: []u8,476 decompress_buffer: []u8,
468 ) *std.Io.Reader {477 ) *std.Io.Reader {
469 if (transfer_encoding == .none and content_length == null) {478 if (transfer_encoding == .none and content_length == null) {
470 assert(reader.state == .received_head);479 assert(reader.state == .received_head);
...@@ -474,22 +483,22 @@ pub const Reader = struct {...@@ -474,22 +483,22 @@ pub const Reader = struct {
474 return reader.in;483 return reader.in;
475 },484 },
476 .deflate => {485 .deflate => {
477 decompressor.* = .{ .flate = .init(reader.in, .zlib, decompression_buffer) };486 decompress.* = .{ .flate = .init(reader.in, .zlib, decompress_buffer) };
478 return &decompressor.flate.reader;487 return &decompress.flate.reader;
479 },488 },
480 .gzip => {489 .gzip => {
481 decompressor.* = .{ .flate = .init(reader.in, .gzip, decompression_buffer) };490 decompress.* = .{ .flate = .init(reader.in, .gzip, decompress_buffer) };
482 return &decompressor.flate.reader;491 return &decompress.flate.reader;
483 },492 },
484 .zstd => {493 .zstd => {
485 decompressor.* = .{ .zstd = .init(reader.in, decompression_buffer, .{ .verify_checksum = false }) };494 decompress.* = .{ .zstd = .init(reader.in, decompress_buffer, .{ .verify_checksum = false }) };
486 return &decompressor.zstd.reader;495 return &decompress.zstd.reader;
487 },496 },
488 .compress => unreachable,497 .compress => unreachable,
489 }498 }
490 }499 }
491 const transfer_reader = bodyReader(reader, &.{}, transfer_encoding, content_length);500 const transfer_reader = bodyReader(reader, transfer_buffer, transfer_encoding, content_length);
492 return decompressor.init(transfer_reader, decompression_buffer, content_encoding);501 return decompress.init(transfer_reader, decompress_buffer, content_encoding);
493 }502 }
494503
495 fn contentLengthStream(504 fn contentLengthStream(
...@@ -691,33 +700,33 @@ pub const Reader = struct {...@@ -691,33 +700,33 @@ pub const Reader = struct {
691 }700 }
692};701};
693702
694pub const Decompressor = union(enum) {703pub const Decompress = union(enum) {
695 flate: std.compress.flate.Decompress,704 flate: std.compress.flate.Decompress,
696 zstd: std.compress.zstd.Decompress,705 zstd: std.compress.zstd.Decompress,
697 none: *std.Io.Reader,706 none: *std.Io.Reader,
698707
699 pub fn init(708 pub fn init(
700 decompressor: *Decompressor,709 decompress: *Decompress,
701 transfer_reader: *std.Io.Reader,710 transfer_reader: *std.Io.Reader,
702 buffer: []u8,711 buffer: []u8,
703 content_encoding: ContentEncoding,712 content_encoding: ContentEncoding,
704 ) *std.Io.Reader {713 ) *std.Io.Reader {
705 switch (content_encoding) {714 switch (content_encoding) {
706 .identity => {715 .identity => {
707 decompressor.* = .{ .none = transfer_reader };716 decompress.* = .{ .none = transfer_reader };
708 return transfer_reader;717 return transfer_reader;
709 },718 },
710 .deflate => {719 .deflate => {
711 decompressor.* = .{ .flate = .init(transfer_reader, .zlib, buffer) };720 decompress.* = .{ .flate = .init(transfer_reader, .zlib, buffer) };
712 return &decompressor.flate.reader;721 return &decompress.flate.reader;
713 },722 },
714 .gzip => {723 .gzip => {
715 decompressor.* = .{ .flate = .init(transfer_reader, .gzip, buffer) };724 decompress.* = .{ .flate = .init(transfer_reader, .gzip, buffer) };
716 return &decompressor.flate.reader;725 return &decompress.flate.reader;
717 },726 },
718 .zstd => {727 .zstd => {
719 decompressor.* = .{ .zstd = .init(transfer_reader, buffer, .{ .verify_checksum = false }) };728 decompress.* = .{ .zstd = .init(transfer_reader, buffer, .{ .verify_checksum = false }) };
720 return &decompressor.zstd.reader;729 return &decompress.zstd.reader;
721 },730 },
722 .compress => unreachable,731 .compress => unreachable,
723 }732 }
...@@ -794,7 +803,7 @@ pub const BodyWriter = struct {...@@ -794,7 +803,7 @@ pub const BodyWriter = struct {
794 }803 }
795804
796 /// When using content-length, asserts that the amount of data sent matches805 /// When using content-length, asserts that the amount of data sent matches
797 /// the value sent in the header, then flushes.806 /// the value sent in the header, then flushes `http_protocol_output`.
798 ///807 ///
799 /// When using transfer-encoding: chunked, writes the end-of-stream message808 /// When using transfer-encoding: chunked, writes the end-of-stream message
800 /// with empty trailers, then flushes the stream to the system. Asserts any809 /// with empty trailers, then flushes the stream to the system. Asserts any
...@@ -818,10 +827,13 @@ pub const BodyWriter = struct {...@@ -818,10 +827,13 @@ pub const BodyWriter = struct {
818 ///827 ///
819 /// Respects the value of `isEliding` to omit all data after the headers.828 /// Respects the value of `isEliding` to omit all data after the headers.
820 ///829 ///
830 /// Does not flush `http_protocol_output`, but does flush `writer`.
831 ///
821 /// See also:832 /// See also:
822 /// * `end`833 /// * `end`
823 /// * `endChunked`834 /// * `endChunked`
824 pub fn endUnflushed(w: *BodyWriter) Error!void {835 pub fn endUnflushed(w: *BodyWriter) Error!void {
836 try w.writer.flush();
825 switch (w.state) {837 switch (w.state) {
826 .end => unreachable,838 .end => unreachable,
827 .content_length => |len| {839 .content_length => |len| {
lib/std/http/Client.zig+22-27
...@@ -13,8 +13,8 @@ const net = std.net;...@@ -13,8 +13,8 @@ const net = std.net;
13const Uri = std.Uri;13const Uri = std.Uri;
14const Allocator = mem.Allocator;14const Allocator = mem.Allocator;
15const assert = std.debug.assert;15const assert = std.debug.assert;
16const Writer = std.io.Writer;16const Writer = std.Io.Writer;
17const Reader = std.io.Reader;17const Reader = std.Io.Reader;
1818
19const Client = @This();19const Client = @This();
2020
...@@ -704,12 +704,12 @@ pub const Response = struct {...@@ -704,12 +704,12 @@ pub const Response = struct {
704 ///704 ///
705 /// See also:705 /// See also:
706 /// * `readerDecompressing`706 /// * `readerDecompressing`
707 pub fn reader(response: *Response, buffer: []u8) *Reader {707 pub fn reader(response: *Response, transfer_buffer: []u8) *Reader {
708 response.head.invalidateStrings();708 response.head.invalidateStrings();
709 const req = response.request;709 const req = response.request;
710 if (!req.method.responseHasBody()) return .ending;710 if (!req.method.responseHasBody()) return .ending;
711 const head = &response.head;711 const head = &response.head;
712 return req.reader.bodyReader(buffer, head.transfer_encoding, head.content_length);712 return req.reader.bodyReader(transfer_buffer, head.transfer_encoding, head.content_length);
713 }713 }
714714
715 /// If compressed body has been negotiated this will return decompressed bytes.715 /// If compressed body has been negotiated this will return decompressed bytes.
...@@ -723,17 +723,19 @@ pub const Response = struct {...@@ -723,17 +723,19 @@ pub const Response = struct {
723 /// * `reader`723 /// * `reader`
724 pub fn readerDecompressing(724 pub fn readerDecompressing(
725 response: *Response,725 response: *Response,
726 decompressor: *http.Decompressor,726 transfer_buffer: []u8,
727 decompression_buffer: []u8,727 decompress: *http.Decompress,
728 decompress_buffer: []u8,
728 ) *Reader {729 ) *Reader {
729 response.head.invalidateStrings();730 response.head.invalidateStrings();
730 const head = &response.head;731 const head = &response.head;
731 return response.request.reader.bodyReaderDecompressing(732 return response.request.reader.bodyReaderDecompressing(
733 transfer_buffer,
732 head.transfer_encoding,734 head.transfer_encoding,
733 head.content_length,735 head.content_length,
734 head.content_encoding,736 head.content_encoding,
735 decompressor,737 decompress,
736 decompression_buffer,738 decompress_buffer,
737 );739 );
738 }740 }
739741
...@@ -1322,7 +1324,7 @@ pub const basic_authorization = struct {...@@ -1322,7 +1324,7 @@ pub const basic_authorization = struct {
1322 const user: Uri.Component = uri.user orelse .empty;1324 const user: Uri.Component = uri.user orelse .empty;
1323 const password: Uri.Component = uri.password orelse .empty;1325 const password: Uri.Component = uri.password orelse .empty;
13241326
1325 var dw: std.io.Writer.Discarding = .init(&.{});1327 var dw: Writer.Discarding = .init(&.{});
1326 user.formatUser(&dw.writer) catch unreachable; // discarding1328 user.formatUser(&dw.writer) catch unreachable; // discarding
1327 const user_len = dw.count + dw.writer.end;1329 const user_len = dw.count + dw.writer.end;
13281330
...@@ -1696,8 +1698,8 @@ pub const FetchOptions = struct {...@@ -1696,8 +1698,8 @@ pub const FetchOptions = struct {
1696 /// `null` means it will be heap-allocated.1698 /// `null` means it will be heap-allocated.
1697 decompress_buffer: ?[]u8 = null,1699 decompress_buffer: ?[]u8 = null,
1698 redirect_behavior: ?Request.RedirectBehavior = null,1700 redirect_behavior: ?Request.RedirectBehavior = null,
1699 /// If the server sends a body, it will be stored here.1701 /// If the server sends a body, it will be written here.
1700 response_storage: ?ResponseStorage = null,1702 response_writer: ?*Writer = null,
17011703
1702 location: Location,1704 location: Location,
1703 method: ?http.Method = null,1705 method: ?http.Method = null,
...@@ -1725,7 +1727,7 @@ pub const FetchOptions = struct {...@@ -1725,7 +1727,7 @@ pub const FetchOptions = struct {
1725 list: *std.ArrayListUnmanaged(u8),1727 list: *std.ArrayListUnmanaged(u8),
1726 /// If null then only the existing capacity will be used.1728 /// If null then only the existing capacity will be used.
1727 allocator: ?Allocator = null,1729 allocator: ?Allocator = null,
1728 append_limit: std.io.Limit = .unlimited,1730 append_limit: std.Io.Limit = .unlimited,
1729 };1731 };
1730};1732};
17311733
...@@ -1778,7 +1780,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1778,7 +1780,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
17781780
1779 var response = try req.receiveHead(redirect_buffer);1781 var response = try req.receiveHead(redirect_buffer);
17801782
1781 const storage = options.response_storage orelse {1783 const response_writer = options.response_writer orelse {
1782 const reader = response.reader(&.{});1784 const reader = response.reader(&.{});
1783 _ = reader.discardRemaining() catch |err| switch (err) {1785 _ = reader.discardRemaining() catch |err| switch (err) {
1784 error.ReadFailed => return response.bodyErr().?,1786 error.ReadFailed => return response.bodyErr().?,
...@@ -1794,21 +1796,14 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1794,21 +1796,14 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
1794 };1796 };
1795 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);1797 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);
17961798
1797 var decompressor: http.Decompressor = undefined;1799 var transfer_buffer: [64]u8 = undefined;
1798 const reader = response.readerDecompressing(&decompressor, decompress_buffer);1800 var decompress: http.Decompress = undefined;
1799 const list = storage.list;1801 const reader = response.readerDecompressing(&transfer_buffer, &decompress, decompress_buffer);
18001802
1801 if (storage.allocator) |allocator| {1803 _ = reader.streamRemaining(response_writer) catch |err| switch (err) {
1802 reader.appendRemaining(allocator, null, list, storage.append_limit) catch |err| switch (err) {1804 error.ReadFailed => return response.bodyErr().?,
1803 error.ReadFailed => return response.bodyErr().?,1805 else => |e| return e,
1804 else => |e| return e,1806 };
1805 };
1806 } else {
1807 const buf = storage.append_limit.slice(list.unusedCapacitySlice());
1808 list.items.len += reader.readSliceShort(buf) catch |err| switch (err) {
1809 error.ReadFailed => return response.bodyErr().?,
1810 };
1811 }
18121807
1813 return .{ .status = response.head.status };1808 return .{ .status = response.head.status };
1814}1809}
lib/std/http/test.zig+5-4
...@@ -1006,8 +1006,9 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1006,8 +1006,9 @@ fn echoTests(client: *http.Client, port: u16) !void {
1006 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port});1006 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port});
1007 defer gpa.free(location);1007 defer gpa.free(location);
10081008
1009 var body: std.ArrayListUnmanaged(u8) = .empty;1009 var body: std.Io.Writer.Allocating = .init(gpa);
1010 defer body.deinit(gpa);1010 defer body.deinit();
1011 try body.ensureUnusedCapacity(64);
10111012
1012 const res = try client.fetch(.{1013 const res = try client.fetch(.{
1013 .location = .{ .url = location },1014 .location = .{ .url = location },
...@@ -1016,10 +1017,10 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1016,10 +1017,10 @@ fn echoTests(client: *http.Client, port: u16) !void {
1016 .extra_headers = &.{1017 .extra_headers = &.{
1017 .{ .name = "content-type", .value = "text/plain" },1018 .{ .name = "content-type", .value = "text/plain" },
1018 },1019 },
1019 .response_storage = .{ .allocator = gpa, .list = &body },1020 .response_writer = &body.writer,
1020 });1021 });
1021 try expectEqual(.ok, res.status);1022 try expectEqual(.ok, res.status);
1022 try expectEqualStrings("Hello, World!\n", body.items);1023 try expectEqualStrings("Hello, World!\n", body.getWritten());
1023 }1024 }
10241025
1025 { // expect: 100-continue1026 { // expect: 100-continue
src/Package/Fetch.zig+24-18
...@@ -883,7 +883,9 @@ const Resource = union(enum) {...@@ -883,7 +883,9 @@ const Resource = union(enum) {
883 const HttpRequest = struct {883 const HttpRequest = struct {
884 request: std.http.Client.Request,884 request: std.http.Client.Request,
885 response: std.http.Client.Response,885 response: std.http.Client.Response,
886 buffer: []u8,886 transfer_buffer: []u8,
887 decompress: std.http.Decompress,
888 decompress_buffer: []u8,
887 };889 };
888890
889 fn deinit(resource: *Resource) void {891 fn deinit(resource: *Resource) void {
...@@ -892,7 +894,6 @@ const Resource = union(enum) {...@@ -892,7 +894,6 @@ const Resource = union(enum) {
892 .http_request => |*http_request| http_request.request.deinit(),894 .http_request => |*http_request| http_request.request.deinit(),
893 .git => |*git_resource| {895 .git => |*git_resource| {
894 git_resource.fetch_stream.deinit();896 git_resource.fetch_stream.deinit();
895 git_resource.session.deinit();
896 },897 },
897 .dir => |*dir| dir.close(),898 .dir => |*dir| dir.close(),
898 }899 }
...@@ -902,7 +903,11 @@ const Resource = union(enum) {...@@ -902,7 +903,11 @@ const Resource = union(enum) {
902 fn reader(resource: *Resource) *std.Io.Reader {903 fn reader(resource: *Resource) *std.Io.Reader {
903 return switch (resource.*) {904 return switch (resource.*) {
904 .file => |*file_reader| return &file_reader.interface,905 .file => |*file_reader| return &file_reader.interface,
905 .http_request => |*http_request| return http_request.response.reader(http_request.buffer),906 .http_request => |*http_request| return http_request.response.readerDecompressing(
907 http_request.transfer_buffer,
908 &http_request.decompress,
909 http_request.decompress_buffer,
910 ),
906 .git => |*g| return &g.fetch_stream.reader,911 .git => |*g| return &g.fetch_stream.reader,
907 .dir => unreachable,912 .dir => unreachable,
908 };913 };
...@@ -971,7 +976,6 @@ const FileType = enum {...@@ -971,7 +976,6 @@ const FileType = enum {
971const init_resource_buffer_size = git.Packet.max_data_length;976const init_resource_buffer_size = git.Packet.max_data_length;
972977
973fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {978fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {
974 const gpa = f.arena.child_allocator;
975 const arena = f.arena.allocator();979 const arena = f.arena.allocator();
976 const eb = &f.error_bundle;980 const eb = &f.error_bundle;
977981
...@@ -995,7 +999,9 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -995,7 +999,9 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
995 .request = http_client.request(.GET, uri, .{}) catch |err|999 .request = http_client.request(.GET, uri, .{}) catch |err|
996 return f.fail(f.location_tok, try eb.printString("unable to connect to server: {t}", .{err})),1000 return f.fail(f.location_tok, try eb.printString("unable to connect to server: {t}", .{err})),
997 .response = undefined,1001 .response = undefined,
998 .buffer = reader_buffer,1002 .transfer_buffer = reader_buffer,
1003 .decompress_buffer = &.{},
1004 .decompress = undefined,
999 } };1005 } };
1000 const request = &resource.http_request.request;1006 const request = &resource.http_request.request;
1001 errdefer request.deinit();1007 errdefer request.deinit();
...@@ -1019,6 +1025,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -1019,6 +1025,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
1019 .{ response.head.status, response.head.status.phrase() orelse "" },1025 .{ response.head.status, response.head.status.phrase() orelse "" },
1020 ));1026 ));
10211027
1028 resource.http_request.decompress_buffer = try arena.alloc(u8, response.head.content_encoding.minBufferCapacity());
1022 return;1029 return;
1023 }1030 }
10241031
...@@ -1027,13 +1034,12 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -1027,13 +1034,12 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
1027 {1034 {
1028 var transport_uri = uri;1035 var transport_uri = uri;
1029 transport_uri.scheme = uri.scheme["git+".len..];1036 transport_uri.scheme = uri.scheme["git+".len..];
1030 var session = git.Session.init(gpa, http_client, transport_uri, reader_buffer) catch |err| {1037 var session = git.Session.init(arena, http_client, transport_uri, reader_buffer) catch |err| {
1031 return f.fail(f.location_tok, try eb.printString(1038 return f.fail(
1032 "unable to discover remote git server capabilities: {s}",1039 f.location_tok,
1033 .{@errorName(err)},1040 try eb.printString("unable to discover remote git server capabilities: {t}", .{err}),
1034 ));1041 );
1035 };1042 };
1036 errdefer session.deinit();
10371043
1038 const want_oid = want_oid: {1044 const want_oid = want_oid: {
1039 const want_ref =1045 const want_ref =
...@@ -1086,17 +1092,17 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -1086,17 +1092,17 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
10861092
1087 var want_oid_buf: [git.Oid.max_formatted_length]u8 = undefined;1093 var want_oid_buf: [git.Oid.max_formatted_length]u8 = undefined;
1088 _ = std.fmt.bufPrint(&want_oid_buf, "{f}", .{want_oid}) catch unreachable;1094 _ = std.fmt.bufPrint(&want_oid_buf, "{f}", .{want_oid}) catch unreachable;
1089 var fetch_stream: git.Session.FetchStream = undefined;
1090 session.fetch(&fetch_stream, &.{&want_oid_buf}, reader_buffer) catch |err| {
1091 return f.fail(f.location_tok, try eb.printString("unable to create fetch stream: {t}", .{err}));
1092 };
1093 errdefer fetch_stream.deinit();
1094
1095 resource.* = .{ .git = .{1095 resource.* = .{ .git = .{
1096 .session = session,1096 .session = session,
1097 .fetch_stream = fetch_stream,1097 .fetch_stream = undefined,
1098 .want_oid = want_oid,1098 .want_oid = want_oid,
1099 } };1099 } };
1100 const fetch_stream = &resource.git.fetch_stream;
1101 session.fetch(fetch_stream, &.{&want_oid_buf}, reader_buffer) catch |err| {
1102 return f.fail(f.location_tok, try eb.printString("unable to create fetch stream: {t}", .{err}));
1103 };
1104 errdefer fetch_stream.deinit(fetch_stream);
1105
1100 return;1106 return;
1101 }1107 }
11021108
src/Package/Fetch/git.zig+40-57
...@@ -644,7 +644,7 @@ pub const Session = struct {...@@ -644,7 +644,7 @@ pub const Session = struct {
644 supports_agent: bool,644 supports_agent: bool,
645 supports_shallow: bool,645 supports_shallow: bool,
646 object_format: Oid.Format,646 object_format: Oid.Format,
647 allocator: Allocator,647 arena: Allocator,
648648
649 const agent = "zig/" ++ @import("builtin").zig_version_string;649 const agent = "zig/" ++ @import("builtin").zig_version_string;
650 const agent_capability = std.fmt.comptimePrint("agent={s}\n", .{agent});650 const agent_capability = std.fmt.comptimePrint("agent={s}\n", .{agent});
...@@ -652,7 +652,7 @@ pub const Session = struct {...@@ -652,7 +652,7 @@ pub const Session = struct {
652 /// Initializes a client session and discovers the capabilities of the652 /// Initializes a client session and discovers the capabilities of the
653 /// server for optimal transport.653 /// server for optimal transport.
654 pub fn init(654 pub fn init(
655 allocator: Allocator,655 arena: Allocator,
656 transport: *std.http.Client,656 transport: *std.http.Client,
657 uri: std.Uri,657 uri: std.Uri,
658 /// Asserted to be at least `Packet.max_data_length`658 /// Asserted to be at least `Packet.max_data_length`
...@@ -661,13 +661,12 @@ pub const Session = struct {...@@ -661,13 +661,12 @@ pub const Session = struct {
661 assert(response_buffer.len >= Packet.max_data_length);661 assert(response_buffer.len >= Packet.max_data_length);
662 var session: Session = .{662 var session: Session = .{
663 .transport = transport,663 .transport = transport,
664 .location = try .init(allocator, uri),664 .location = try .init(arena, uri),
665 .supports_agent = false,665 .supports_agent = false,
666 .supports_shallow = false,666 .supports_shallow = false,
667 .object_format = .sha1,667 .object_format = .sha1,
668 .allocator = allocator,668 .arena = arena,
669 };669 };
670 errdefer session.deinit();
671 var capability_iterator: CapabilityIterator = undefined;670 var capability_iterator: CapabilityIterator = undefined;
672 try session.getCapabilities(&capability_iterator, response_buffer);671 try session.getCapabilities(&capability_iterator, response_buffer);
673 defer capability_iterator.deinit();672 defer capability_iterator.deinit();
...@@ -690,34 +689,24 @@ pub const Session = struct {...@@ -690,34 +689,24 @@ pub const Session = struct {
690 return session;689 return session;
691 }690 }
692691
693 pub fn deinit(session: *Session) void {
694 session.location.deinit(session.allocator);
695 session.* = undefined;
696 }
697
698 /// An owned `std.Uri` representing the location of the server (base URI).692 /// An owned `std.Uri` representing the location of the server (base URI).
699 const Location = struct {693 const Location = struct {
700 uri: std.Uri,694 uri: std.Uri,
701695
702 fn init(allocator: Allocator, uri: std.Uri) !Location {696 fn init(arena: Allocator, uri: std.Uri) !Location {
703 const scheme = try allocator.dupe(u8, uri.scheme);697 const scheme = try arena.dupe(u8, uri.scheme);
704 errdefer allocator.free(scheme);698 const user = if (uri.user) |user| try std.fmt.allocPrint(arena, "{f}", .{
705 const user = if (uri.user) |user| try std.fmt.allocPrint(allocator, "{f}", .{
706 std.fmt.alt(user, .formatUser),699 std.fmt.alt(user, .formatUser),
707 }) else null;700 }) else null;
708 errdefer if (user) |s| allocator.free(s);701 const password = if (uri.password) |password| try std.fmt.allocPrint(arena, "{f}", .{
709 const password = if (uri.password) |password| try std.fmt.allocPrint(allocator, "{f}", .{
710 std.fmt.alt(password, .formatPassword),702 std.fmt.alt(password, .formatPassword),
711 }) else null;703 }) else null;
712 errdefer if (password) |s| allocator.free(s);704 const host = if (uri.host) |host| try std.fmt.allocPrint(arena, "{f}", .{
713 const host = if (uri.host) |host| try std.fmt.allocPrint(allocator, "{f}", .{
714 std.fmt.alt(host, .formatHost),705 std.fmt.alt(host, .formatHost),
715 }) else null;706 }) else null;
716 errdefer if (host) |s| allocator.free(s);707 const path = try std.fmt.allocPrint(arena, "{f}", .{
717 const path = try std.fmt.allocPrint(allocator, "{f}", .{
718 std.fmt.alt(uri.path, .formatPath),708 std.fmt.alt(uri.path, .formatPath),
719 });709 });
720 errdefer allocator.free(path);
721 // The query and fragment are not used as part of the base server URI.710 // The query and fragment are not used as part of the base server URI.
722 return .{711 return .{
723 .uri = .{712 .uri = .{
...@@ -730,14 +719,6 @@ pub const Session = struct {...@@ -730,14 +719,6 @@ pub const Session = struct {
730 },719 },
731 };720 };
732 }721 }
733
734 fn deinit(loc: *Location, allocator: Allocator) void {
735 allocator.free(loc.uri.scheme);
736 if (loc.uri.user) |user| allocator.free(user.percent_encoded);
737 if (loc.uri.password) |password| allocator.free(password.percent_encoded);
738 if (loc.uri.host) |host| allocator.free(host.percent_encoded);
739 allocator.free(loc.uri.path.percent_encoded);
740 }
741 };722 };
742723
743 /// Returns an iterator over capabilities supported by the server.724 /// Returns an iterator over capabilities supported by the server.
...@@ -745,16 +726,17 @@ pub const Session = struct {...@@ -745,16 +726,17 @@ pub const Session = struct {
745 /// The `session.location` is updated if the server returns a redirect, so726 /// The `session.location` is updated if the server returns a redirect, so
746 /// that subsequent session functions do not need to handle redirects.727 /// that subsequent session functions do not need to handle redirects.
747 fn getCapabilities(session: *Session, it: *CapabilityIterator, response_buffer: []u8) !void {728 fn getCapabilities(session: *Session, it: *CapabilityIterator, response_buffer: []u8) !void {
729 const arena = session.arena;
748 assert(response_buffer.len >= Packet.max_data_length);730 assert(response_buffer.len >= Packet.max_data_length);
749 var info_refs_uri = session.location.uri;731 var info_refs_uri = session.location.uri;
750 {732 {
751 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{733 const session_uri_path = try std.fmt.allocPrint(arena, "{f}", .{
752 std.fmt.alt(session.location.uri.path, .formatPath),734 std.fmt.alt(session.location.uri.path, .formatPath),
753 });735 });
754 defer session.allocator.free(session_uri_path);736 info_refs_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(arena, &.{
755 info_refs_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "info/refs" }) };737 "/", session_uri_path, "info/refs",
738 }) };
756 }739 }
757 defer session.allocator.free(info_refs_uri.path.percent_encoded);
758 info_refs_uri.query = .{ .percent_encoded = "service=git-upload-pack" };740 info_refs_uri.query = .{ .percent_encoded = "service=git-upload-pack" };
759 info_refs_uri.fragment = null;741 info_refs_uri.fragment = null;
760742
...@@ -767,6 +749,7 @@ pub const Session = struct {...@@ -767,6 +749,7 @@ pub const Session = struct {
767 },749 },
768 }),750 }),
769 .reader = undefined,751 .reader = undefined,
752 .decompress = undefined,
770 };753 };
771 errdefer it.deinit();754 errdefer it.deinit();
772 const request = &it.request;755 const request = &it.request;
...@@ -777,19 +760,17 @@ pub const Session = struct {...@@ -777,19 +760,17 @@ pub const Session = struct {
777 if (response.head.status != .ok) return error.ProtocolError;760 if (response.head.status != .ok) return error.ProtocolError;
778 const any_redirects_occurred = request.redirect_behavior.remaining() < max_redirects;761 const any_redirects_occurred = request.redirect_behavior.remaining() < max_redirects;
779 if (any_redirects_occurred) {762 if (any_redirects_occurred) {
780 const request_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{763 const request_uri_path = try std.fmt.allocPrint(arena, "{f}", .{
781 std.fmt.alt(request.uri.path, .formatPath),764 std.fmt.alt(request.uri.path, .formatPath),
782 });765 });
783 defer session.allocator.free(request_uri_path);
784 if (!mem.endsWith(u8, request_uri_path, "/info/refs")) return error.UnparseableRedirect;766 if (!mem.endsWith(u8, request_uri_path, "/info/refs")) return error.UnparseableRedirect;
785 var new_uri = request.uri;767 var new_uri = request.uri;
786 new_uri.path = .{ .percent_encoded = request_uri_path[0 .. request_uri_path.len - "/info/refs".len] };768 new_uri.path = .{ .percent_encoded = request_uri_path[0 .. request_uri_path.len - "/info/refs".len] };
787 const new_location: Location = try .init(session.allocator, new_uri);769 session.location = try .init(arena, new_uri);
788 session.location.deinit(session.allocator);
789 session.location = new_location;
790 }770 }
791771
792 it.reader = response.reader(response_buffer);772 const decompress_buffer = try arena.alloc(u8, response.head.content_encoding.minBufferCapacity());
773 it.reader = response.readerDecompressing(response_buffer, &it.decompress, decompress_buffer);
793 var state: enum { response_start, response_content } = .response_start;774 var state: enum { response_start, response_content } = .response_start;
794 while (true) {775 while (true) {
795 // Some Git servers (at least GitHub) include an additional776 // Some Git servers (at least GitHub) include an additional
...@@ -821,6 +802,7 @@ pub const Session = struct {...@@ -821,6 +802,7 @@ pub const Session = struct {
821 const CapabilityIterator = struct {802 const CapabilityIterator = struct {
822 request: std.http.Client.Request,803 request: std.http.Client.Request,
823 reader: *std.Io.Reader,804 reader: *std.Io.Reader,
805 decompress: std.http.Decompress,
824806
825 const Capability = struct {807 const Capability = struct {
826 key: []const u8,808 key: []const u8,
...@@ -864,16 +846,15 @@ pub const Session = struct {...@@ -864,16 +846,15 @@ pub const Session = struct {
864846
865 /// Returns an iterator over refs known to the server.847 /// Returns an iterator over refs known to the server.
866 pub fn listRefs(session: Session, it: *RefIterator, options: ListRefsOptions) !void {848 pub fn listRefs(session: Session, it: *RefIterator, options: ListRefsOptions) !void {
849 const arena = session.arena;
867 assert(options.buffer.len >= Packet.max_data_length);850 assert(options.buffer.len >= Packet.max_data_length);
868 var upload_pack_uri = session.location.uri;851 var upload_pack_uri = session.location.uri;
869 {852 {
870 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{853 const session_uri_path = try std.fmt.allocPrint(arena, "{f}", .{
871 std.fmt.alt(session.location.uri.path, .formatPath),854 std.fmt.alt(session.location.uri.path, .formatPath),
872 });855 });
873 defer session.allocator.free(session_uri_path);856 upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(arena, &.{ "/", session_uri_path, "git-upload-pack" }) };
874 upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "git-upload-pack" }) };
875 }857 }
876 defer session.allocator.free(upload_pack_uri.path.percent_encoded);
877 upload_pack_uri.query = null;858 upload_pack_uri.query = null;
878 upload_pack_uri.fragment = null;859 upload_pack_uri.fragment = null;
879860
...@@ -883,16 +864,14 @@ pub const Session = struct {...@@ -883,16 +864,14 @@ pub const Session = struct {
883 try Packet.write(.{ .data = agent_capability }, &body);864 try Packet.write(.{ .data = agent_capability }, &body);
884 }865 }
885 {866 {
886 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={t}\n", .{867 const object_format_packet = try std.fmt.allocPrint(arena, "object-format={t}\n", .{
887 session.object_format,868 session.object_format,
888 });869 });
889 defer session.allocator.free(object_format_packet);
890 try Packet.write(.{ .data = object_format_packet }, &body);870 try Packet.write(.{ .data = object_format_packet }, &body);
891 }871 }
892 try Packet.write(.delimiter, &body);872 try Packet.write(.delimiter, &body);
893 for (options.ref_prefixes) |ref_prefix| {873 for (options.ref_prefixes) |ref_prefix| {
894 const ref_prefix_packet = try std.fmt.allocPrint(session.allocator, "ref-prefix {s}\n", .{ref_prefix});874 const ref_prefix_packet = try std.fmt.allocPrint(arena, "ref-prefix {s}\n", .{ref_prefix});
895 defer session.allocator.free(ref_prefix_packet);
896 try Packet.write(.{ .data = ref_prefix_packet }, &body);875 try Packet.write(.{ .data = ref_prefix_packet }, &body);
897 }876 }
898 if (options.include_symrefs) {877 if (options.include_symrefs) {
...@@ -913,6 +892,7 @@ pub const Session = struct {...@@ -913,6 +892,7 @@ pub const Session = struct {
913 }),892 }),
914 .reader = undefined,893 .reader = undefined,
915 .format = session.object_format,894 .format = session.object_format,
895 .decompress = undefined,
916 };896 };
917 const request = &it.request;897 const request = &it.request;
918 errdefer request.deinit();898 errdefer request.deinit();
...@@ -920,13 +900,15 @@ pub const Session = struct {...@@ -920,13 +900,15 @@ pub const Session = struct {
920900
921 var response = try request.receiveHead(options.buffer);901 var response = try request.receiveHead(options.buffer);
922 if (response.head.status != .ok) return error.ProtocolError;902 if (response.head.status != .ok) return error.ProtocolError;
923 it.reader = response.reader(options.buffer);903 const decompress_buffer = try arena.alloc(u8, response.head.content_encoding.minBufferCapacity());
904 it.reader = response.readerDecompressing(options.buffer, &it.decompress, decompress_buffer);
924 }905 }
925906
926 pub const RefIterator = struct {907 pub const RefIterator = struct {
927 format: Oid.Format,908 format: Oid.Format,
928 request: std.http.Client.Request,909 request: std.http.Client.Request,
929 reader: *std.Io.Reader,910 reader: *std.Io.Reader,
911 decompress: std.http.Decompress,
930912
931 pub const Ref = struct {913 pub const Ref = struct {
932 oid: Oid,914 oid: Oid,
...@@ -981,16 +963,15 @@ pub const Session = struct {...@@ -981,16 +963,15 @@ pub const Session = struct {
981 /// Asserted to be at least `Packet.max_data_length`.963 /// Asserted to be at least `Packet.max_data_length`.
982 response_buffer: []u8,964 response_buffer: []u8,
983 ) !void {965 ) !void {
966 const arena = session.arena;
984 assert(response_buffer.len >= Packet.max_data_length);967 assert(response_buffer.len >= Packet.max_data_length);
985 var upload_pack_uri = session.location.uri;968 var upload_pack_uri = session.location.uri;
986 {969 {
987 const session_uri_path = try std.fmt.allocPrint(session.allocator, "{f}", .{970 const session_uri_path = try std.fmt.allocPrint(arena, "{f}", .{
988 std.fmt.alt(session.location.uri.path, .formatPath),971 std.fmt.alt(session.location.uri.path, .formatPath),
989 });972 });
990 defer session.allocator.free(session_uri_path);973 upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(arena, &.{ "/", session_uri_path, "git-upload-pack" }) };
991 upload_pack_uri.path = .{ .percent_encoded = try std.fs.path.resolvePosix(session.allocator, &.{ "/", session_uri_path, "git-upload-pack" }) };
992 }974 }
993 defer session.allocator.free(upload_pack_uri.path.percent_encoded);
994 upload_pack_uri.query = null;975 upload_pack_uri.query = null;
995 upload_pack_uri.fragment = null;976 upload_pack_uri.fragment = null;
996977
...@@ -1000,8 +981,7 @@ pub const Session = struct {...@@ -1000,8 +981,7 @@ pub const Session = struct {
1000 try Packet.write(.{ .data = agent_capability }, &body);981 try Packet.write(.{ .data = agent_capability }, &body);
1001 }982 }
1002 {983 {
1003 const object_format_packet = try std.fmt.allocPrint(session.allocator, "object-format={s}\n", .{@tagName(session.object_format)});984 const object_format_packet = try std.fmt.allocPrint(arena, "object-format={s}\n", .{@tagName(session.object_format)});
1004 defer session.allocator.free(object_format_packet);
1005 try Packet.write(.{ .data = object_format_packet }, &body);985 try Packet.write(.{ .data = object_format_packet }, &body);
1006 }986 }
1007 try Packet.write(.delimiter, &body);987 try Packet.write(.delimiter, &body);
...@@ -1031,6 +1011,7 @@ pub const Session = struct {...@@ -1031,6 +1011,7 @@ pub const Session = struct {
1031 .input = undefined,1011 .input = undefined,
1032 .reader = undefined,1012 .reader = undefined,
1033 .remaining_len = undefined,1013 .remaining_len = undefined,
1014 .decompress = undefined,
1034 };1015 };
1035 const request = &fs.request;1016 const request = &fs.request;
1036 errdefer request.deinit();1017 errdefer request.deinit();
...@@ -1040,7 +1021,8 @@ pub const Session = struct {...@@ -1040,7 +1021,8 @@ pub const Session = struct {
1040 var response = try request.receiveHead(&.{});1021 var response = try request.receiveHead(&.{});
1041 if (response.head.status != .ok) return error.ProtocolError;1022 if (response.head.status != .ok) return error.ProtocolError;
10421023
1043 const reader = response.reader(response_buffer);1024 const decompress_buffer = try arena.alloc(u8, response.head.content_encoding.minBufferCapacity());
1025 const reader = response.readerDecompressing(response_buffer, &fs.decompress, decompress_buffer);
1044 // We are not interested in any of the sections of the returned fetch1026 // We are not interested in any of the sections of the returned fetch
1045 // data other than the packfile section, since we aren't doing anything1027 // data other than the packfile section, since we aren't doing anything
1046 // complex like ref negotiation (this is a fresh clone).1028 // complex like ref negotiation (this is a fresh clone).
...@@ -1079,6 +1061,7 @@ pub const Session = struct {...@@ -1079,6 +1061,7 @@ pub const Session = struct {
1079 reader: std.Io.Reader,1061 reader: std.Io.Reader,
1080 err: ?Error = null,1062 err: ?Error = null,
1081 remaining_len: usize,1063 remaining_len: usize,
1064 decompress: std.http.Decompress,
10821065
1083 pub fn deinit(fs: *FetchStream) void {1066 pub fn deinit(fs: *FetchStream) void {
1084 fs.request.deinit();1067 fs.request.deinit();
...@@ -1131,8 +1114,8 @@ pub const Session = struct {...@@ -1131,8 +1114,8 @@ pub const Session = struct {
1131 }1114 }
1132 const buf = limit.slice(try w.writableSliceGreedy(1));1115 const buf = limit.slice(try w.writableSliceGreedy(1));
1133 const n = @min(buf.len, fs.remaining_len);1116 const n = @min(buf.len, fs.remaining_len);
1134 @memcpy(buf[0..n], input.buffered()[0..n]);1117 try input.readSliceAll(buf[0..n]);
1135 input.toss(n);1118 w.advance(n);
1136 fs.remaining_len -= n;1119 fs.remaining_len -= n;
1137 return n;1120 return n;
1138 }1121 }