Skip to content

implement strip_components option of std.tar in std.zip as per #20257 #20263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 40 additions & 12 deletions lib/std/zip.zig
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,13 @@ pub fn Iterator(comptime SeekableStream: type) type {
) !u32 {
if (filename_buf.len < self.filename_len)
return error.ZipInsufficientBuffer;
const filename = filename_buf[0..self.filename_len];
const orig_filename = filename_buf[0..self.filename_len];

try stream.seekTo(self.header_zip_offset + @sizeOf(CentralDirectoryFileHeader));

{
const len = try stream.context.reader().readAll(filename);
if (len != filename.len)
const len = try stream.context.reader().readAll(orig_filename);
if (len != orig_filename.len)
return error.ZipBadFileOffset;
}

Expand Down Expand Up @@ -479,33 +479,36 @@ pub fn Iterator(comptime SeekableStream: type) type {
@as(u64, local_header.extra_len);
};

if (isBadFilename(filename))
if (isBadFilename(orig_filename))
return error.ZipBadFilename;

if (options.allow_backslashes) {
std.mem.replaceScalar(u8, filename, '\\', '/');
std.mem.replaceScalar(u8, orig_filename, '\\', '/');
} else {
if (std.mem.indexOfScalar(u8, filename, '\\')) |_|
if (std.mem.indexOfScalar(u8, orig_filename, '\\')) |_|
return error.ZipFilenameHasBackslash;
}

const strip_filename = stripComponents(orig_filename, options.strip_components);

// All entries that end in '/' are directories
if (filename[filename.len - 1] == '/') {
if (orig_filename[orig_filename.len - 1] == '/') {
if (self.uncompressed_size != 0)
return error.ZipBadDirectorySize;
try dest.makePath(filename[0 .. filename.len - 1]);
try dest.makePath(strip_filename);
return std.hash.Crc32.hash(&.{});
}

const out_file = blk: {
if (std.fs.path.dirname(filename)) |dirname| {
const out_file = try blk: {
if (std.fs.path.dirname(strip_filename)) |dirname| {
var parent_dir = try dest.makeOpenPath(dirname, .{});
defer parent_dir.close();

const basename = std.fs.path.basename(filename);
const basename = std.fs.path.basename(strip_filename);
break :blk try parent_dir.createFile(basename, .{ .exclusive = true });
}
break :blk try dest.createFile(filename, .{ .exclusive = true });
if (strip_filename.len == 0) break :blk error.BadFileName;
break :blk try dest.createFile(strip_filename, .{ .exclusive = true });
};
defer out_file.close();
const local_data_file_offset: u64 =
Expand Down Expand Up @@ -571,6 +574,8 @@ pub const ExtractOptions = struct {
/// Allow filenames within the zip to use backslashes. Back slashes are normalized
/// to forward slashes before forwarding them to platform APIs.
allow_backslashes: bool = false,
/// Number of directory levels to skip when extracting files.
strip_components: u32 = 0,

diagnostics: ?*Diagnostics = null,
};
Expand All @@ -594,6 +599,29 @@ pub fn extract(dest: std.fs.Dir, seekable_stream: anytype, options: ExtractOptio
}
}

fn stripComponents(path: []const u8, count: u32) []const u8 {
var i: usize = 0;
var c = count;
while (c > 0) : (c -= 1) {
if (std.mem.indexOfScalarPos(u8, path, i, '/')) |pos| {
i = pos + 1;
} else {
i = path.len;
break;
}
}
return path[i..];
}

test stripComponents {
const expectEqualStrings = testing.expectEqualStrings;
try expectEqualStrings("a/b/c", stripComponents("a/b/c", 0));
try expectEqualStrings("b/c", stripComponents("a/b/c", 1));
try expectEqualStrings("c", stripComponents("a/b/c", 2));
try expectEqualStrings("", stripComponents("a/b/c", 3));
try expectEqualStrings("", stripComponents("a/b/c", 4));
}

fn testZip(options: ExtractOptions, comptime files: []const File, write_opt: testutil.WriteZipOptions) !void {
var store: [files.len]FileStore = undefined;
try testZipWithStore(options, files, write_opt, &store);
Expand Down
Loading