diff --git a/lib/std/tar.zig b/lib/std/tar.zig index 8ba5a19012c5..04571fb3c88f 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 9f136536476b..f6e5c2e2223a 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 => unreachable, // unreachable with strip_components = 0 } } }