#!/usr/bin/perl -w ######################################################################## # safeunrar, safeunzip, safeuntar -- Extract archives, ensuring that # # all files in each archive remain in a single subdirectory, without # # overwriting anything. # ######################################################################## # The decompression program is called once for each argument. The # # program to use is guessed, first by extension, and failing that # # by the name of this program (which, in that case, must be one of # # safeunrar, safeunzip, safeuntar). # ######################################################################## use File::Temp qw(tempdir); use File::Basename; use Cwd; my %extension_map = ( '(tar\.gz|tgz|taz)' => 'tar -zxf', '(tar\.Z|taZ)' => 'tar -Zxf', '(tar\.bz2|tz2|tbz2|tbz)' => 'tar -jxf', '(tar\.lzma|tlz)' => 'tar --use-compress-program=lzma -xf', '(zip|xpi|jar)' => 'unzip', 'rar' => 'unrar x', 'tar' => 'tar -xf', ); $cwd = getcwd or die "getcwd error: $!"; for $archive (@ARGV) { chdir $cwd or die "chdir error: $!"; my ($base, @cmd); keys %extension_map; # resets "each" while (($rx, $cmd) = each %extension_map) { $_ = basename $archive; if (m/^(.*)\.$rx$/) { @cmd = split / /, $cmd; $base = $1; last; } } unless (@cmd) { for (basename $0) { $_ eq 'safeunzip' && (@cmd = qw'unzip') || $_ eq 'safeunrar' && (@cmd = qw'unrar x') # don't use this because the current gnu tar's --auto-compress is # guaranteed to fail if the current %extension_map failed. # || $_ eq 'safeuntar' && (@cmd = qw'tar --auto-compress -xf') || $_ eq 'safeuntar' && (@cmd = qw'tar -zxf') || die; } ($base = basename $archive) =~ s/\..{2,4}$//; } $tempdir = tempdir("$base.XXXXXX", DIR => ".") or warn("tempdir error: $!; skipping $archive"), next; chmod 0777 & ~ umask, $tempdir; $ltd = "leaving temporary directory '$tempdir'"; # extract the files chdir $tempdir or warn("chdir error: $!; $ltd"), next; $archive =~ s#^(?!/)#../#; system(@cmd, $archive) == 0 or warn("$cmd[0] error: $?/$!; $ltd"), next; # count the files opendir(DIR, '.') or warn("opendir error: $!; $ltd"), next; @f = readdir(DIR) or warn("readdir error: $!; $ltd"), next; closedir(DIR) or warn("closedir error: $!; ignoring"); # if only one file, delete the dir @f = grep { ! m/^\.\.?$/ } @f; if (1 == @f) { ! -e "$cwd/$f[0]" or warn("not overwriting $f[0]; $ltd"), next; rename $f[0], "$cwd/$f[0]" or warn("rename error: $!; $ltd"), next; chdir $cwd or warn("chdir error: $!; $ltd"), next; rmdir $tempdir or warn("rmdir error: $!; $ltd"), next; # if multiple files, rename the dir } else { rename "$cwd/$tempdir", "$cwd/$base" or warn("rename error: $!; $ltd"), next; } }