#! /usr/bin/perl

# Maintained by Eduard Bloch <edi@ka.linux.de>
# http://www.eduard.bloch.com/edecosi/unp.en.html
#
# Originaly written by Andr Karwath, 1997
# andre.karwath@informatik.tu-chemnitz.de
# http://www.aka-online.de
#
# "unp" runs the correct unpack program depending on the file extension
# of the given parameter.
# 
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# -----------------------------------------------------------------------
# If you make changes to this script, please feel free to forward the new 
# version to eduard@bloch.com or Andre.Karwath@informatik.tu-chemnitz.de
# -----------------------------------------------------------------------
# You need some archiver and compressor programs for extracting:
# GNU tar, gzip, bzip2, ar, rpm, unrar or rar, unarj, unzip and lha

use File::Basename;

sub load_strings;
sub load_de_strings;

# localisation part (only german at the moment):
if($ENV{'LANG'}=~ "^de" || $ENV{'LC_MESSAGES'}=~ "^de"  ){
   load_de_strings;
}

%pkgmap = (
   cpio, "afio $or cpio",
   afio, "afio $or cpio",
   rpm2cpio, "rpm, afio $or cpio",
   unshar, sharutils,
   uudecode, sharutils,
   PPMd, ppmd,
   rar, "rar $or unrar $or unrar-free",
   ar, binutils,
   unarj, "unarj $or arj", 
   hexbin, macutils,
   #macunpack, macunpack,
   gunzip, gzip,
   bunzip2, bzip2,
   formail, "formail, mpack"
);

&print_usage if ($#ARGV<0 || $ARGV[0] eq "-h");

my $command;
my $force;
my $tempfile;

sub set_command {
   #print "CMD: @_\n";

   my %cand;
   my $lastcmd;
   
   # test passed alternatives until an unstalled tool has been found
   for $cmd (@_) {
      $cmd =~ /^(\S+)(\s|$)/;
      my $prog=$1;
      # HACKS
      set_command("mpack") if($prog eq "formail");
      for(split(/:/,$ENV{"PATH"})) {
         if(-x "$_/$prog") {
            $command=$cmd;
            goto FIX_VARS;
         }
         $lastcmd=$cmd;
         $cand{$prog}=$prog;
      }
   }

   # no hit in $PATH, print installation hints to the user
   # override with custom package names where needed
   for(keys %cand) { $cand{$_}=$pkgmap{$_} if( defined($pkgmap{$_}) ); }
   print "$nopack ".join(" $or ", keys %cand)."\n$plzinst".join(" $or ", values %cand)."\n";

   # abort unless forced...
   $force || exit 1;
   $command=$lastcmd;

   FIX_VARS:
   $ENV{"UNP_FILE"}=$UNP_FILE;
   $ENV{"UNP_TARGET"}=$UNP_TARGET;
   $command=~s/\$UNP_FILE/"\$UNP_FILE\"/g;
   $command=~s/\$UNP_TARGET/"\$UNP_TARGET"/g;
   $command=~s/\$UNP_ARGS/"\$\@"/g;

}
   
sub testfile {

   return if($tested{$UNP_FILE});
   $tested{$UNP_FILE}=1;

   print $UNP_FILE.$tryfile;
   my $uncompcmd;
   my $UNP_FILEstr;
   if(open(fd, "-|", "file", $UNP_FILE))
   {
      while(<fd>)
      {
         if(/(gzip)/si) { $uncompcmd='zcat'; }
         elsif (/(bzip2)/si) { $uncompcmd='bzcat'; }
         elsif (/(lzop)/si) { $uncompcmd='lzop -d <'; }
         else
         {
            $UNP_FILEstr.=$_;
            next;
         }
         last;
      }
      close(fd);
   }
   if($uncompcmd)
   {
      $UNP_TARGET=basename($UNP_FILE).".unp";
      set_command $uncompcmd.' $UNP_FILE > $UNP_TARGET'; # preliminary, check for tar contents also

      $ENV{"UNP_FILE"}=$UNP_FILE;
      if(open(fd, "-|", "/bin/sh", "-c", $uncompcmd.' "$UNP_FILE" | file -'))
      {
         while(<fd>)
         {
            if(/tar/i)
            {
               set_command $uncompcmd.' $UNP_FILE | tar -x -v -f - $UNP_ARGS';
               last;
            }
         }
         close(fd);
      }
   }
   
   $UNP_FILEstr=~s/^.*?://;
   if ($UNP_FILEstr =~ /RAR.*archive/i) { set_command('rar x $UNP_ARGS $UNP_FILE || rar x -av- $UNP_ARGS $UNP_FILE','unrar x $UNP_ARGS $UNP_FILE || unrar x -av- $UNP_ARGS $UNP_FILE'); }
   if ($UNP_FILEstr =~ /tar.*archive/i) { set_command 'tar -xvf $UNP_FILE $UNP_ARGS'; }
   if ($UNP_FILEstr =~ /(Debian binary package|\ ar.*archive)/i) { set_command 'ar -x -v $UNP_FILE $UNP_ARGS'; }
   if ($UNP_FILEstr =~ /LHa.*archive/i) { set_command 'lha x $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /ARJ.*archive/i) { set_command('unarj x $UNP_FILE','arj x $UNP_FILE '); }
   if ($UNP_FILEstr =~ /CAB file/i) { set_command 'cabextract $UNP_FILE'; }
   if ($UNP_FILEstr =~ /cpio/i) { set_command('afio -Z -v -i $UNP_FILE','cpio -i -d	--verbose  $UNP_ARGS < $UNP_FILE'); }
   if ($UNP_FILEstr =~ /Zip.*archive/i) { set_command 'unzip $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /Zoo.*archive/i) { set_command 'unzoo -x $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /shell.*archive/i) { set_command 'unshar $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /Transport Neutral Encapsulation Format/i) { set_command 'tnef -v $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /uuencoded/i) { set_command 'uudecode $UNP_ARGS $UNP_FILE'; }
   if ($UNP_FILEstr =~ /(mail text)|news/i) { set_command 'formail -s munpack < $UNP_FILE'; }
   if ($UNP_FILEstr =~ /RPM/) { set_command 'rpm2cpio < $UNP_FILE | cpio -i -d	--verbose $UNP_ARGS';}
   # RAR can also create executables
   if ($UNP_FILEstr =~ /executable/i){set_command 'orange $UNP_FILE || unzip $UNP_FILE || unrar x -av- $UNP_FILE || rar x -av- $UNP_FILE || unarj x $UNP_FILE || lha x $UNP_FILE';}
   #if ($UNP_FILEstr =~ /BinHex/i) {set_command 'a=$(hexbin -v $UNP_FILE 2>&1); bla=$(echo "$a" | sed -e \'s/.* name=\\(.*\\).*/\\1/\'); macunpack -v "$bla.bin"'}
   #if ($UNP_FILEstr =~ /BinHex/i) {set_command 'macunpack $(hexbin -v $UNP_FILE 2>&1 |grep ^name|sed -e \'s/name="\\(.*\\)\".*/\\1/').bin;' }

   # if still nothing could be found, print an error message
   if ($command eq "") {
      print $UNP_FILE.$unsup;
      next LOOP;
   }
}

arglabel: foreach $arg (@ARGV){
   die "Suspicious file name, aborting...\n" if($arg=~/UNP_/);
	if("$arg" eq "-u"){$dataunp=1 ; next arglabel};
	if("$arg" eq "-f"){$force=1 ; next arglabel};
	if("$arg" eq "--"){$argvalue=1; next arglabel};
	if($argvalue){
		push(@ARGS, $arg);
	}else{
		push(@FILES,$arg);
	}
}

LOOP: foreach $UNP_FILE (@FILES) {
  if (!-e $UNP_FILE) {
     print $UNP_FILE.$not_found;
     if ($UNP_FILE == "-") 
       {$stdin=1}
     else
       {next LOOP;}
  }

  if ((!-r $UNP_FILE) && (!$stdin)) {
     print $UNP_FILE.$not_read;
     next LOOP;
  }
  
	if (-d $UNP_FILE) {
     print $UNP_FILE.$is_dir;
     next LOOP;
  }
	undef $command;

  # not just gunzip, create new file with uncompressed data in the current
	# directory, same for bz2
	if ($UNP_FILE =~ /([^\/]*)\.(gz|Z)$/i) {if (-f $1){ print $1.$skip; next LOOP;};
			set_command "gunzip < \$UNP_FILE > \"$1\""; }
  if ($UNP_FILE =~ /([^\/]*)\.(bz2$)/i) {if (-f $1){ print $1.$skip; next LOOP;};
			set_command "bunzip2 < \$UNP_FILE > \"$1\""; }
	if ($UNP_FILE =~ /([^\/]*)\.lzo$/i) {if (-f $1){ print $1.$skip; next LOOP;};
			set_command "lzop -v -d \$UNP_FILE"; }
	
	# check also for _tar, because of broken filenames
  if ($UNP_FILE =~ /(\.|_)tar$/i) { set_command 'tar -xvf $UNP_FILE $UNP_ARGS'; }
	if ($UNP_FILE =~ /(\.|_)rpm$/i) { set_command 'rpm2cpio < $UNP_FILE | cpio -i -d	--verbose $UNP_ARGS';}
	if ($UNP_FILE =~ /(\.|_)tar\.gz$/i) { set_command 'tar -xvzf $UNP_FILE $UNP_ARGS'; }
  if ($UNP_FILE =~ /(\.|_)tar\.bz2$/i) { set_command 'bunzip2 -c $UNP_FILE | tar -xvf - $UNP_ARGS'; }
	
	if ($UNP_FILE =~ /\.tgz$/i) { set_command 'tar -xvzf $UNP_FILE $UNP_ARGS'; }
	if ($UNP_FILE =~ /\.(tzo|tar\.lzop)$/i) { set_command 'lzop -v -d $UNP_FILE | tar -xv $UNP_ARGS'; }
  if ($UNP_FILE =~ /\.rar$/i) { set_command('rar x $UNP_ARGS $UNP_FILE || rar x -av- $UNP_ARGS $UNP_FILE','unrar x $UNP_ARGS $UNP_FILE || unrar x -av- $UNP_ARGS $UNP_FILE'); }
  if ($UNP_FILE =~ /\.(ar|deb)$/i) { set_command 'ar -x -v $UNP_FILE $UNP_ARGS'; }
  if ($UNP_FILE =~ /\.l(ha|zh)$/i) { set_command 'lha x $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.arj$/i) { set_command ('arj x $UNP_FILE','unarj x $UNP_FILE'); }
  if ($UNP_FILE =~ /\.zip$/i) { set_command 'unzip $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.zoo$/i) { set_command 'unzoo -x $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.cab$/i) { set_command 'cabextract $UNP_FILE'; }
  if ($UNP_FILE =~ /\.ace$/i) { set_command 'unace e $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.dat$/i) { set_command 'tnef -v $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.pmd$/i) { set_command 'PPMd x $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.7z$/i) { set_command  '7z x $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.lzx$/i) { set_command 'unlzx $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.chm$/i) { 
     my $outname=$UNP_FILE; 
     $outname=~s#.*/##;
     $outname=~s#\W#_#g;
     set_command 'archmage $ARGS $UNP_FILE '.$outname; 
  }
  if ($UNP_FILE =~ /\.(cbz|cbr|jar|war|ear|xpi|adf)$/i) { set_command 'unzip $ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.dms$/i) { set_command 'xdms x $ARGS $UNP_FILE'; }
 
  if ($UNP_FILE =~ /\.(sea|sea\.bin)$/i) { set_command 'macutils -v $UNP_ARGS $UNP_FILE'; }
  if ($UNP_FILE =~ /\.uu$/i) { set_command 'uudecode $UNP_ARGS $UNP_FILE'; }
	
	# assume that exe is just an arcive with executable header and try
	# some programs
	if ($UNP_FILE =~ /\.exe$/i) { set_command 'orange $UNP_FILE || unzip $UNP_FILE || unrar x -av- $UNP_FILE || rar x -av- $UNP_FILE || unarj x $UNP_FILE || lha x $UNP_FILE';}

  if ($command eq "") {testfile};
	if ($dataunp){
#print "binda\n$command\n$1";
     if($UNP_FILE =~ /([^\/]*)\.deb$/i) {
        my $debbase=$1;
        &doit;
        system ("tar", "zxvf", "data.tar.gz");
        mkdir "control";
        mkdir "control/$debbase";
        system("tar", "zxvf", "control.tar.gz", "-C", "control/$debbase");
     }
     else {
      # Wo sind wir gerade? (Achtung newline muss weg!!)
      chomp($pwd = `pwd`);
      # Pfad fixen
      $0 =~ s!^./!$pwd/!;
      $UNP_FILE =~ /([^\/]+)\.(\w)+$/i;
      $newdir=$1;
      if($UNP_FILE !~ m/^\//) { # wenn der pfad nicht absolut ist
        $UNP_FILE = "$pwd/$UNP_FILE";
      }
      mkdir $newdir;
      chdir $newdir;
      system ($0, $UNP_FILE);
      chdir $pwd;
   }
	}
  else {
    &doit;
  }
}

sub doit {
   #print "Running command: $command\n";
   if(0!=system("/bin/sh", "-c", $command, @ARGS))
   {
      # if execution failed, try file way
      testfile;
      #print "Running command: $command\n";
      system("/bin/sh", "-c", $command, @ARGS);
   };
}

# -----------------------------------------------------------------------------
sub print_usage {
  print $usage;
	die "\n";
}
# -----------------------------------------------------------------------------

sub END {
   unlink $tempfile if($tempfile && -e $tempfile);
}



sub load_strings {
   $not_found=": not found\n";
   $not_read=": not readable\n";
   $unsup=": unsupported format\n";
   $formats="
   tar[.gz,.bz2], gz, bz2, Z, ar/deb, rpm, shar, rar, arj, zip, LHa, cab, ace,
   tnef, uu (mail, news), mime, hqx, sea, zoo, pmd, cpio, afio, lzop, 
   7z, lzx, jar, war, ear, xpi, cbz, cbr, adf, dms
   ";
   $usage="
   usage: unp file [file]...
   file: compressed file(s) to expand

   Use -- to pass arguments to external programs, eg. some tar options:
   unp fastgl.tgz xmnt.tgz -- -C /tmp

   Special option:
   -f  Continue even if program availability checks fail
   -u  For Debian packages:
   - extract data.tar.gz after each operation
   - extract control.tar.gz in control/<filename>/
   For other archives:
   - create directory <filename without suffix>/
   - extract contents there

   currently supported extensions and formats are".$formats;
   $tryfile=" - unknown extension, checking with file\n";
   $testbz2=" contains bzip2-compressed data, determining data type...\n";
   $testgz=" contains gzip-compressed data, determining data type...\n";
   $testlz=" contains lzop-compressed data, determining data type...\n";
   $skip=" exists allready, skipping...\n";
   $is_dir=" is a directory, skipping...\n";
   $nopack="Warning, the following unarchive program(s) could not be found:
   ";
   $plzinst="Please install the package(s): ";
   $or="or";
}

sub load_de_strings {
   $not_found=" wurde nicht gefunden!\n";
   $not_read=" konnte nicht gelesen werden!\n";
   $unsup=": dieses Format wird nicht unterstuetzt!\n";
   $usage="
   Aufruf: unp Datei [Datei] ...
   Datei: eine oder mehrere komprimierte Datei(en) zum entpacken

   Optionen:
   -f  Weitermachen, auch wenn Checks der Entpacker-Verfgbarkeit fehlschlagen
   -u  Fuer Debian-Pakete:
   - Nach jeder Operation data.tar.gz entpacken
   - control.tar.gz nach control/<Achiv-Name>/
   Fuer andere Archive:
   - Verzeichniss <Archivename ohne Endung>/ erstellen
   - Archiv dort entpacken

   Optionen nach -- werden an externe Programme uebergeben, z.B. tar-Optionen:
   unp fastgl.tgz xmnt.tgz -- -C /tmp

   Derzeit unterstuetzte Erweiterungen und Formate:".$formats;
   $tryfile=" - Endung unbekannt, ueberpruefe mit file...
   ";
   $testbz2=" enthlt bzip2-komprimierte Daten, ueberpruefe den Datentyp...\n";
   $testgz=" enthlt gzip-komprimierte Daten, ueberpruefe den Datentyp...\n";
   $testlz=" enthlt lzop-komprimierte Daten, ueberpruefe den Datentyp...\n";
   $skip=" existiert bereits, ueberspringe...\n";
   $is_dir=" ist ein Verzeichnis\n";
   $nopack="Warnung, folgende(s) Entpacker-Programm(e) wurde(n) nicht gefunden:
   ";
   $plzinst="Bitte installieren Sie folgend(e) Paket(e): ";
   $or="oder";
}
