From 035c1b65229083e01bc5ec69f2ad529ba0c56062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Thu, 11 Apr 2024 16:32:07 +0200 Subject: [PATCH] std.tar: add strip components error to diagnostics This was the only kind of error which was raised in pipeToFileSystem and not added to Diagnostics. Shell tar silently ignores paths which are stripped out when used with `--strip-components` switch. This enables that same behavior, errors will be collected in diagnostics but caller is free to ignore that type of diagnostics errors. Enables use case where caller knows structure of the tar file and want to extract only some deeply nested folders ignoring upper files/folders. Fixes: #17620 by giving caller options: - not provide diagnostic and get errors - provide diagnostics and analyze errors - provide diagnostics and ignore errors --- lib/std/tar.zig | 41 ++++++++++++++++++++++++++++++++++++++--- src/Package/Fetch.zig | 1 + 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 8ba5a19012..04571fb3c8 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -46,6 +46,9 @@ pub const Diagnostics = struct { file_name: []const u8, file_type: Header.Kind, }, + components_outside_stripped_prefix: struct { + file_name: []const u8, + }, }; fn findRoot(d: *Diagnostics, path: []const u8) !void { @@ -97,6 +100,9 @@ pub const Diagnostics = struct { .unsupported_file_type => |info| { d.allocator.free(info.file_name); }, + .components_outside_stripped_prefix => |info| { + d.allocator.free(info.file_name); + }, } } d.errors.deinit(d.allocator); @@ -623,18 +629,24 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) while (try iter.next()) |file| { const file_name = stripComponents(file.name, options.strip_components); + if (file_name.len == 0 and file.kind != .directory) { + const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix; + try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{ + .file_name = try d.allocator.dupe(u8, file.name), + } }); + continue; + } if (options.diagnostics) |d| { try d.findRoot(file_name); } switch (file.kind) { .directory => { - if (file_name.len != 0 and !options.exclude_empty_directories) { + if (file_name.len > 0 and !options.exclude_empty_directories) { try dir.makePath(file_name); } }, .file => { - if (file_name.len == 0) return error.BadFileName; if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| { defer fs_file.close(); try file.writeAll(fs_file); @@ -647,7 +659,6 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: PipeOptions) } }, .sym_link => { - if (file_name.len == 0) return error.BadFileName; const link_name = file.link_name; createDirAndSymlink(dir, link_name, file_name) catch |err| { const d = options.diagnostics orelse return error.UnableToCreateSymLink; @@ -1096,6 +1107,30 @@ test "findRoot without explicit root dir" { try testing.expectEqualStrings("root", diagnostics.root_dir); } +test "pipeToFileSystem strip_components" { + const data = @embedFile("tar/testdata/example.tar"); + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + var tmp = testing.tmpDir(.{ .no_follow = true }); + defer tmp.cleanup(); + var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; + defer diagnostics.deinit(); + + pipeToFileSystem(tmp.dir, reader, .{ + .strip_components = 3, + .diagnostics = &diagnostics, + }) catch |err| { + // Skip on platform which don't support symlinks + if (err == error.UnableToCreateSymLink) return error.SkipZigTest; + return err; + }; + + try testing.expectEqual(2, diagnostics.errors.items.len); + try testing.expectEqualStrings("example/b/symlink", diagnostics.errors.items[0].components_outside_stripped_prefix.file_name); + try testing.expectEqualStrings("example/a/file", diagnostics.errors.items[1].components_outside_stripped_prefix.file_name); +} + fn normalizePath(bytes: []u8) []u8 { const canonical_sep = std.fs.path.sep_posix; if (std.fs.path.sep == canonical_sep) return bytes; diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 9f13653647..6c5a930285 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1189,6 +1189,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes .unable_to_create_file => |i| res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code), .unable_to_create_sym_link => |i| res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code), .unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)), + .components_outside_stripped_prefix => {}, // impossible with strip_components = 0 } } }