authorgravatar for iamlazynic@gmail.coml1yefeng <iamlazynic@gmail.com> 2026-06-13 00:30:11+02:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-06-13 00:30:11+02:00
log783a9df0b8f159062710a94bf5ee9a54002f59ed
tree7edb649e7a9972a4a0a723bf732daa96ff6e5272
parentc11d6e716d651975d019ec85be40b56b109bf99e

Zip.Entry.extractTo (#35232)

`Entry.extractTo` writes output using a given writer instead of to a destination dir. `extract` now calls `extractTo`. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35232

1 files changed, 61 insertions(+), 43 deletions(-)

lib/std/zip.zig+61-43
...@@ -413,15 +413,7 @@ pub const Iterator = struct {...@@ -413,15 +413,7 @@ pub const Iterator = struct {
413 uncompressed_size: u64,413 uncompressed_size: u64,
414 file_offset: u64,414 file_offset: u64,
415415
416 pub fn extract(416 pub fn getFilename(self: Entry, stream: *File.Reader, filename_buf: []u8, options: ExtractOptions) ![]u8 {
417 self: Entry,
418 stream: *File.Reader,
419 options: ExtractOptions,
420 filename_buf: []u8,
421 dest: Io.Dir,
422 ) !void {
423 const io = stream.io;
424
425 if (filename_buf.len < self.filename_len)417 if (filename_buf.len < self.filename_len)
426 return error.ZipInsufficientBuffer;418 return error.ZipInsufficientBuffer;
427 switch (self.compression_method) {419 switch (self.compression_method) {
...@@ -434,6 +426,25 @@ pub const Iterator = struct {...@@ -434,6 +426,25 @@ pub const Iterator = struct {
434 try stream.interface.readSliceAll(filename);426 try stream.interface.readSliceAll(filename);
435 }427 }
436428
429 if (options.allow_backslashes) {
430 std.mem.replaceScalar(u8, filename, '\\', '/');
431 } else {
432 if (std.mem.findScalar(u8, filename, '\\')) |_|
433 return error.ZipFilenameHasBackslash;
434 }
435
436 if (isBadFilename(filename))
437 return error.ZipBadFilename;
438
439 return filename;
440 }
441
442 pub fn extractTo(self: Entry, stream: *File.Reader, w: *Writer) !void {
443 switch (self.compression_method) {
444 .store, .deflate => {},
445 else => return error.UnsupportedCompressionMethod,
446 }
447
437 const local_data_header_offset: u64 = local_data_header_offset: {448 const local_data_header_offset: u64 = local_data_header_offset: {
438 const local_header = blk: {449 const local_header = blk: {
439 try stream.seekTo(self.file_offset);450 try stream.seekTo(self.file_offset);
...@@ -496,15 +507,45 @@ pub const Iterator = struct {...@@ -496,15 +507,45 @@ pub const Iterator = struct {
496 @as(u64, local_header.extra_len);507 @as(u64, local_header.extra_len);
497 };508 };
498509
499 if (options.allow_backslashes) {510 const local_data_file_offset: u64 =
500 std.mem.replaceScalar(u8, filename, '\\', '/');511 @as(u64, self.file_offset) +
501 } else {512 @as(u64, @sizeOf(LocalFileHeader)) +
502 if (std.mem.findScalar(u8, filename, '\\')) |_|513 local_data_header_offset;
503 return error.ZipFilenameHasBackslash;514 try stream.seekTo(local_data_file_offset);
515
516 // TODO limit based on self.compressed_size
517
518 switch (self.compression_method) {
519 .store => {
520 stream.interface.streamExact64(w, self.uncompressed_size) catch |err| switch (err) {
521 error.ReadFailed => |e| return stream.err orelse e,
522 error.WriteFailed => |e| return e,
523 error.EndOfStream => return error.ZipDecompressTruncated,
524 };
525 },
526 .deflate => {
527 var flate_buffer: [flate.max_window_len]u8 = undefined;
528 var decompress: flate.Decompress = .init(&stream.interface, .raw, &flate_buffer);
529 decompress.reader.streamExact64(w, self.uncompressed_size) catch |err| switch (err) {
530 error.ReadFailed => |e| return decompress.err orelse (stream.err orelse e),
531 error.WriteFailed => |e| return e,
532 error.EndOfStream => return error.ZipDecompressTruncated,
533 };
534 },
535 else => return error.UnsupportedCompressionMethod,
504 }536 }
537 }
505538
506 if (isBadFilename(filename))539 pub fn extract(
507 return error.ZipBadFilename;540 self: Entry,
541 stream: *File.Reader,
542 options: ExtractOptions,
543 filename_buf: []u8,
544 dest: Io.Dir,
545 ) !void {
546 const io = stream.io;
547
548 const filename = try self.getFilename(stream, filename_buf, options);
508549
509 // All entries that end in '/' are directories550 // All entries that end in '/' are directories
510 if (filename[filename.len - 1] == '/') {551 if (filename[filename.len - 1] == '/') {
...@@ -527,33 +568,10 @@ pub const Iterator = struct {...@@ -527,33 +568,10 @@ pub const Iterator = struct {
527 defer out_file.close(io);568 defer out_file.close(io);
528 var out_file_buffer: [1024]u8 = undefined;569 var out_file_buffer: [1024]u8 = undefined;
529 var file_writer = out_file.writer(io, &out_file_buffer);570 var file_writer = out_file.writer(io, &out_file_buffer);
530 const local_data_file_offset: u64 =571 self.extractTo(stream, &file_writer.interface) catch |err| switch (err) {
531 @as(u64, self.file_offset) +572 error.WriteFailed => |e| return file_writer.err orelse e,
532 @as(u64, @sizeOf(LocalFileHeader)) +573 else => return err,
533 local_data_header_offset;574 };
534 try stream.seekTo(local_data_file_offset);
535
536 // TODO limit based on self.compressed_size
537
538 switch (self.compression_method) {
539 .store => {
540 stream.interface.streamExact64(&file_writer.interface, self.uncompressed_size) catch |err| switch (err) {
541 error.ReadFailed => return stream.err.?,
542 error.WriteFailed => return file_writer.err.?,
543 error.EndOfStream => return error.ZipDecompressTruncated,
544 };
545 },
546 .deflate => {
547 var flate_buffer: [flate.max_window_len]u8 = undefined;
548 var decompress: flate.Decompress = .init(&stream.interface, .raw, &flate_buffer);
549 decompress.reader.streamExact64(&file_writer.interface, self.uncompressed_size) catch |err| switch (err) {
550 error.ReadFailed => return stream.err.?,
551 error.WriteFailed => return file_writer.err orelse decompress.err.?,
552 error.EndOfStream => return error.ZipDecompressTruncated,
553 };
554 },
555 else => return error.UnsupportedCompressionMethod,
556 }
557 try file_writer.end();575 try file_writer.end();
558 }576 }
559 };577 };