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 8ba5a19012c55ec0eb7d111a3ad9570bfc71c6ef..04571fb3c88f0a5329544723d9202a89e69ee9db 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 9f136536476bab020438948974f0136961dff674..6c5a930285d9bf0a5ed0d0a1cfab071835ab0456 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 } } } -- 2.54.0