authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2025-09-01 16:42:45-04:00
committergravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2025-09-04 17:26:49-04:00
log58dda3b10b8aacf377447d36dc97efc3a3f2e21a
treeebf0235cc301958e30c0dab1f15687c3b45d493d
parent7687b916fdb99605820bbbd515644707cc918703

fix sendFile implementations bypassing interface buffer

Also removes `File.Reader.read` since it is otherwise unused and is a footgun.

2 files changed, 43 insertions(+), 19 deletions(-)

lib/std/Io/Writer.zig+40-9
...@@ -921,7 +921,8 @@ pub fn sendFileHeader(...@@ -921,7 +921,8 @@ pub fn sendFileHeader(
921/// Asserts nonzero buffer capacity.921/// Asserts nonzero buffer capacity.
922pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {922pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
923 const dest = limit.slice(try w.writableSliceGreedy(1));923 const dest = limit.slice(try w.writableSliceGreedy(1));
924 const n = try file_reader.read(dest);924 const n = try file_reader.interface.readSliceShort(dest);
925 if (n == 0) return error.EndOfStream;
925 w.advance(n);926 w.advance(n);
926 return n;927 return n;
927}928}
...@@ -2276,6 +2277,12 @@ pub const Discarding = struct {...@@ -2276,6 +2277,12 @@ pub const Discarding = struct {
2276 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));2277 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2277 d.count += w.end;2278 d.count += w.end;
2278 w.end = 0;2279 w.end = 0;
2280 const buffered_n = limit.minInt64(file_reader.interface.bufferedLen());
2281 if (buffered_n != 0) {
2282 file_reader.interface.toss(buffered_n);
2283 d.count += buffered_n;
2284 return buffered_n;
2285 }
2279 if (limit == .nothing) return 0;2286 if (limit == .nothing) return 0;
2280 if (file_reader.getSize()) |size| {2287 if (file_reader.getSize()) |size| {
2281 const n = limit.minInt64(size - file_reader.pos);2288 const n = limit.minInt64(size - file_reader.pos);
...@@ -2767,7 +2774,9 @@ pub const Allocating = struct {...@@ -2767,7 +2774,9 @@ pub const Allocating = struct {
2767 if (additional == 0) return error.EndOfStream;2774 if (additional == 0) return error.EndOfStream;
2768 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;2775 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
2769 const dest = limit.slice(a.writer.buffer[a.writer.end..]);2776 const dest = limit.slice(a.writer.buffer[a.writer.end..]);
2770 const n = try file_reader.read(dest);2777 const n = try file_reader.interface.readSliceShort(dest);
2778 // If it was a short read, then EOF has been reached and `file_reader.size`
2779 // has been set and the EOF case will be hit on subsequent calls.
2771 a.writer.end += n;2780 a.writer.end += n;
2772 return n;2781 return n;
2773 }2782 }
...@@ -2818,18 +2827,18 @@ test "discarding sendFile" {...@@ -2818,18 +2827,18 @@ test "discarding sendFile" {
28182827
2819 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2828 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2820 defer file.close();2829 defer file.close();
2821 var r_buffer: [256]u8 = undefined;2830 var r_buffer: [2]u8 = undefined;
2822 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);2831 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2823 try file_writer.interface.writeByte('h');2832 try file_writer.interface.writeAll("abcd");
2824 try file_writer.interface.flush();2833 try file_writer.interface.flush();
28252834
2826 var file_reader = file_writer.moveToReader();2835 var file_reader = file_writer.moveToReader();
2827 try file_reader.seekTo(0);2836 try file_reader.seekTo(0);
2837 try file_reader.interface.fill(2);
28282838
2829 var w_buffer: [256]u8 = undefined;2839 var w_buffer: [256]u8 = undefined;
2830 var discarding: Writer.Discarding = .init(&w_buffer);2840 var discarding: Writer.Discarding = .init(&w_buffer);
28312841 try testing.expectEqual(4, discarding.writer.sendFileAll(&file_reader, .unlimited));
2832 _ = try file_reader.interface.streamRemaining(&discarding.writer);
2833}2842}
28342843
2835test "allocating sendFile" {2844test "allocating sendFile" {
...@@ -2838,18 +2847,40 @@ test "allocating sendFile" {...@@ -2838,18 +2847,40 @@ test "allocating sendFile" {
28382847
2839 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2848 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2840 defer file.close();2849 defer file.close();
2841 var r_buffer: [256]u8 = undefined;2850 var r_buffer: [2]u8 = undefined;
2842 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);2851 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2843 try file_writer.interface.writeByte('h');2852 try file_writer.interface.writeAll("abcd");
2844 try file_writer.interface.flush();2853 try file_writer.interface.flush();
28452854
2846 var file_reader = file_writer.moveToReader();2855 var file_reader = file_writer.moveToReader();
2847 try file_reader.seekTo(0);2856 try file_reader.seekTo(0);
2857 try file_reader.interface.fill(2);
28482858
2849 var allocating: Writer.Allocating = .init(testing.allocator);2859 var allocating: Writer.Allocating = .init(testing.allocator);
2850 defer allocating.deinit();2860 defer allocating.deinit();
2861 try allocating.ensureUnusedCapacity(1);
2862 try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited));
2863 try testing.expectEqualStrings("abcd", allocating.writer.buffered());
2864}
2865
2866test sendFileReading {
2867 var tmp_dir = testing.tmpDir(.{});
2868 defer tmp_dir.cleanup();
28512869
2852 _ = try file_reader.interface.streamRemaining(&allocating.writer);2870 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2871 defer file.close();
2872 var r_buffer: [2]u8 = undefined;
2873 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2874 try file_writer.interface.writeAll("abcd");
2875 try file_writer.interface.flush();
2876
2877 var file_reader = file_writer.moveToReader();
2878 try file_reader.seekTo(0);
2879 try file_reader.interface.fill(2);
2880
2881 var w_buffer: [1]u8 = undefined;
2882 var discarding: Writer.Discarding = .init(&w_buffer);
2883 try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited));
2853}2884}
28542885
2855test writeStruct {2886test writeStruct {
lib/std/fs/File.zig+3-10
...@@ -1154,6 +1154,7 @@ pub const Reader = struct {...@@ -1154,6 +1154,7 @@ pub const Reader = struct {
1154 };1154 };
1155 }1155 }
11561156
1157 /// If `error.EndOfStream` has been hit, this cannot fail.
1157 pub fn getSize(r: *Reader) SizeError!u64 {1158 pub fn getSize(r: *Reader) SizeError!u64 {
1158 return r.size orelse {1159 return r.size orelse {
1159 if (r.size_err) |err| return err;1160 if (r.size_err) |err| return err;
...@@ -1440,7 +1441,7 @@ pub const Reader = struct {...@@ -1440,7 +1441,7 @@ pub const Reader = struct {
1440 }1441 }
1441 }1442 }
14421443
1443 pub fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {1444 fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1444 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {1445 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
1445 error.Unseekable => {1446 error.Unseekable => {
1446 r.mode = r.mode.toStreaming();1447 r.mode = r.mode.toStreaming();
...@@ -1467,7 +1468,7 @@ pub const Reader = struct {...@@ -1467,7 +1468,7 @@ pub const Reader = struct {
1467 return n;1468 return n;
1468 }1469 }
14691470
1470 pub fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {1471 fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1471 const n = r.file.read(dest) catch |err| {1472 const n = r.file.read(dest) catch |err| {
1472 r.err = err;1473 r.err = err;
1473 return error.ReadFailed;1474 return error.ReadFailed;
...@@ -1480,14 +1481,6 @@ pub const Reader = struct {...@@ -1480,14 +1481,6 @@ pub const Reader = struct {
1480 return n;1481 return n;
1481 }1482 }
14821483
1483 pub fn read(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1484 switch (r.mode) {
1485 .positional, .positional_reading => return readPositional(r, dest),
1486 .streaming, .streaming_reading => return readStreaming(r, dest),
1487 .failure => return error.ReadFailed,
1488 }
1489 }
1490
1491 pub fn atEnd(r: *Reader) bool {1484 pub fn atEnd(r: *Reader) bool {
1492 // Even if stat fails, size is set when end is encountered.1485 // Even if stat fails, size is set when end is encountered.
1493 const size = r.size orelse return false;1486 const size = r.size orelse return false;