# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- package CPAN::Tarzip; use strict; use vars qw($VERSION @ISA $BUGHUNTING); use CPAN::Debug; use File::Basename qw(basename); $VERSION = "5.5011"; # module is internal to CPAN.pm @ISA = qw(CPAN::Debug); ## no critic $BUGHUNTING ||= 0; # released code must have turned off # it's ok if file doesn't exist, it just matters if it is .gz or .bz2 sub new { my($class,$file) = @_; $CPAN::Frontend->mydie("CPAN::Tarzip->new called without arg") unless defined $file; my $me = { FILE => $file }; if ($file =~ /\.(bz2|gz|zip|tbz|tgz)$/i) { $me->{ISCOMPRESSED} = 1; } else { $me->{ISCOMPRESSED} = 0; } if (0) { } elsif ($file =~ /\.(?:bz2|tbz)$/i) { unless ($me->{UNGZIPPRG} = $CPAN::Config->{bzip2}) { my $bzip2 = _my_which("bzip2"); if ($bzip2) { $me->{UNGZIPPRG} = $bzip2; } else { $CPAN::Frontend->mydie(qq{ CPAN.pm needs the external program bzip2 in order to handle '$file'. Please install it now and run 'o conf init bzip2' from the CPAN shell prompt to register it as external program. }); } } } else { $me->{UNGZIPPRG} = _my_which("gzip"); } $me->{TARPRG} = _my_which("tar") || _my_which("gtar"); bless $me, $class; } sub _my_which { my($what) = @_; if ($CPAN::Config->{$what}) { return $CPAN::Config->{$what}; } if ($CPAN::META->has_inst("File::Which")) { return File::Which::which($what); } my @cand = MM->maybe_command($what); return $cand[0] if @cand; require File::Spec; my $component; PATH_COMPONENT: foreach $component (File::Spec->path()) { next unless defined($component) && $component; my($abs) = File::Spec->catfile($component,$what); if (MM->maybe_command($abs)) { return $abs; } } return; } sub gzip { my($self,$read) = @_; my $write = $self->{FILE}; if ($CPAN::META->has_inst("Compress::Zlib")) { my($buffer,$fhw); $fhw = FileHandle->new($read) or $CPAN::Frontend->mydie("Could not open $read: $!"); my $cwd = `pwd`; my $gz = Compress::Zlib::gzopen($write, "wb") or $CPAN::Frontend->mydie("Cannot gzopen $write: $! (pwd is $cwd)\n"); $gz->gzwrite($buffer) while read($fhw,$buffer,4096) > 0 ; $gz->gzclose() ; $fhw->close; return 1; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); system(qq{$command -c "$read" > "$write"})==0; } } sub gunzip { my($self,$write) = @_; my $read = $self->{FILE}; if ($CPAN::META->has_inst("Compress::Zlib")) { my($buffer,$fhw); $fhw = FileHandle->new(">$write") or $CPAN::Frontend->mydie("Could not open >$write: $!"); my $gz = Compress::Zlib::gzopen($read, "rb") or $CPAN::Frontend->mydie("Cannot gzopen $read: $!\n"); $fhw->print($buffer) while $gz->gzread($buffer) > 0 ; $CPAN::Frontend->mydie("Error reading from $read: $!\n") if $gz->gzerror != Compress::Zlib::Z_STREAM_END(); $gz->gzclose() ; $fhw->close; return 1; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); system(qq{$command -dc "$read" > "$write"})==0; } } sub gtest { my($self) = @_; return $self->{GTEST} if exists $self->{GTEST}; defined $self->{FILE} or $CPAN::Frontend->mydie("gtest called but no FILE specified"); my $read = $self->{FILE}; my $success; if ($read=~/\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { my($buffer,$len); $len = 0; my $gz = Compress::Bzip2::bzopen($read, "rb") or $CPAN::Frontend->mydie(sprintf("Cannot gzopen %s: %s\n", $read, $Compress::Bzip2::bzerrno)); while ($gz->bzread($buffer) > 0 ) { $len += length($buffer); $buffer = ""; } my $err = $gz->bzerror; $success = ! $err || $err == Compress::Bzip2::BZ_STREAM_END(); if ($len == -s $read) { $success = 0; CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; } $gz->gzclose(); CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; } elsif ( $read=~/\.(?:gz|tgz)$/ && $CPAN::META->has_inst("Compress::Zlib") ) { # After I had reread the documentation in zlib.h, I discovered that # uncompressed files do not lead to an gzerror (anymore?). my($buffer,$len); $len = 0; my $gz = Compress::Zlib::gzopen($read, "rb") or $CPAN::Frontend->mydie(sprintf("Cannot gzopen %s: %s\n", $read, $Compress::Zlib::gzerrno)); while ($gz->gzread($buffer) > 0 ) { $len += length($buffer); $buffer = ""; } my $err = $gz->gzerror; $success = ! $err || $err == Compress::Zlib::Z_STREAM_END(); if ($len == -s $read) { $success = 0; CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; } $gz->gzclose(); CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; } elsif (!$self->{ISCOMPRESSED}) { $success = 0; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); $success = 0==system(qq{$command -qdt "$read"}); } return $self->{GTEST} = $success; } sub TIEHANDLE { my($class,$file) = @_; my $ret; $class->debug("file[$file]"); my $self = $class->new($file); if (0) { } elsif (!$self->gtest) { my $fh = FileHandle->new($file) or $CPAN::Frontend->mydie("Could not open file[$file]: $!"); binmode $fh; $self->{FH} = $fh; $class->debug("via uncompressed FH"); } elsif ($file =~ /\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { my $gz = Compress::Bzip2::bzopen($file,"rb") or $CPAN::Frontend->mydie("Could not bzopen $file"); $self->{GZ} = $gz; $class->debug("via Compress::Bzip2"); } elsif ($file =~/\.(?:gz|tgz)$/ && $CPAN::META->has_inst("Compress::Zlib")) { my $gz = Compress::Zlib::gzopen($file,"rb") or $CPAN::Frontend->mydie("Could not gzopen $file"); $self->{GZ} = $gz; $class->debug("via Compress::Zlib"); } else { my $gzip = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); my $pipe = "$gzip -dc $file |"; my $fh = FileHandle->new($pipe) or $CPAN::Frontend->mydie("Could not pipe[$pipe]: $!"); binmode $fh; $self->{FH} = $fh; $class->debug("via external $gzip"); } $self; } sub READLINE { my($self) = @_; if (exists $self->{GZ}) { my $gz = $self->{GZ}; my($line,$bytesread); $bytesread = $gz->gzreadline($line); return undef if $bytesread <= 0; return $line; } else { my $fh = $self->{FH}; return scalar <$fh>; } } sub READ { my($self,$ref,$length,$offset) = @_; $CPAN::Frontend->mydie("read with offset not implemented") if defined $offset; if (exists $self->{GZ}) { my $gz = $self->{GZ}; my $byteread = $gz->gzread($$ref,$length);# 30eaf79e8b446ef52464b5422da328a8 return $byteread; } else { my $fh = $self->{FH}; return read($fh,$$ref,$length); } } sub DESTROY { my($self) = @_; if (exists $self->{GZ}) { my $gz = $self->{GZ}; $gz->gzclose() if defined $gz; # hard to say if it is allowed # to be undef ever. AK, 2000-09 } else { my $fh = $self->{FH}; $fh->close if defined $fh; } undef $self; } sub untar { my($self) = @_; my $file = $self->{FILE}; my($prefer) = 0; my $exttar = $self->{TARPRG} || ""; $exttar = "" if $exttar =~ /^\s+$/; # user refuses to use it my $extgzip = $self->{UNGZIPPRG} || ""; $extgzip = "" if $extgzip =~ /^\s+$/; # user refuses to use it if (0) { # makes changing order easier } elsif ($BUGHUNTING) { $prefer=2; } elsif ($CPAN::Config->{prefer_external_tar}) { $prefer = 1; } elsif ( $CPAN::META->has_usable("Archive::Tar") && $CPAN::META->has_inst("Compress::Zlib") ) { my $prefer_external_tar = $CPAN::Config->{prefer_external_tar}; unless (defined $prefer_external_tar) { if ($^O =~ /(MSWin32|solaris)/) { $prefer_external_tar = 0; } else { $prefer_external_tar = 1; } } $prefer = $prefer_external_tar ? 1 : 2; } elsif ($exttar && $extgzip) { # no modules and not bz2 $prefer = 1; # but solaris binary tar is a problem if ($^O eq 'solaris' && qx($exttar --version 2>/dev/null) !~ /gnu/i) { $CPAN::Frontend->mywarn(<< 'END_WARN'); WARNING: Many CPAN distributions were archived with GNU tar and some of them may be incompatible with Solaris tar. We respectfully suggest you configure CPAN to use a GNU tar instead ("o conf init tar") or install a recent Archive::Tar instead; END_WARN } } else { my $foundtar = $exttar ? "'$exttar'" : "nothing"; my $foundzip = $extgzip ? "'$extgzip'" : $foundtar ? "nothing" : "also nothing"; my $foundAT; if ($CPAN::META->has_usable("Archive::Tar")) { $foundAT = sprintf "'%s'", "Archive::Tar::"->VERSION; } else { $foundAT = "nothing"; } my $foundCZ; if ($CPAN::META->has_inst("Compress::Zlib")) { $foundCZ = sprintf "'%s'", "Compress::Zlib::"->VERSION; } elsif ($foundAT) { $foundCZ = "nothing"; } else { $foundCZ = "also nothing"; } $CPAN::Frontend->mydie(qq{ CPAN.pm needs either the external programs tar and gzip -or- both modules Archive::Tar and Compress::Zlib installed. For tar I found $foundtar, for gzip $foundzip. For Archive::Tar I found $foundAT, for Compress::Zlib $foundCZ; Can't continue cutting file '$file'. }); } my $tar_verb = "v"; if (defined $CPAN::Config->{tar_verbosity}) { $tar_verb = $CPAN::Config->{tar_verbosity} eq "none" ? "" : $CPAN::Config->{tar_verbosity}; } if ($prefer==1) { # 1 => external gzip+tar my($system); my $is_compressed = $self->gtest(); my $tarcommand = CPAN::HandleConfig->safe_quote($exttar); if ($is_compressed) { my $command = CPAN::HandleConfig->safe_quote($extgzip); $system = qq{$command -dc }. qq{< "$file" | $tarcommand x${tar_verb}f -}; } else { $system = qq{$tarcommand x${tar_verb}f "$file"}; } if (system($system) != 0) { # people find the most curious tar binaries that cannot handle # pipes if ($is_compressed) { (my $ungzf = $file) =~ s/\.gz(?!\n)\Z//; $ungzf = basename $ungzf; my $ct = CPAN::Tarzip->new($file); if ($ct->gunzip($ungzf)) { $CPAN::Frontend->myprint(qq{Uncompressed $file successfully\n}); } else { $CPAN::Frontend->mydie(qq{Couldn\'t uncompress $file\n}); } $file = $ungzf; } $system = qq{$tarcommand x${tar_verb}f "$file"}; $CPAN::Frontend->myprint(qq{Using Tar:$system:\n}); if (system($system)==0) { $CPAN::Frontend->myprint(qq{Untarred $file successfully\n}); } else { $CPAN::Frontend->mydie(qq{Couldn\'t untar $file\n}); } return 1; } else { return 1; } } elsif ($prefer==2) { # 2 => modules unless ($CPAN::META->has_usable("Archive::Tar")) { $CPAN::Frontend->mydie("Archive::Tar not installed, please install it to continue"); } # Make sure AT does not use uid/gid/permissions in the archive # This leaves it to the user's umask instead local $Archive::Tar::CHMOD = 1; local $Archive::Tar::SAME_PERMISSIONS = 0; # Make sure AT leaves current user as owner local $Archive::Tar::CHOWN = 0; my $tar = Archive::Tar->new($file,1); my $af; # archive file my @af; if ($BUGHUNTING) { # RCS 1.337 had this code, it turned out unacceptable slow but # it revealed a bug in Archive::Tar. Code is only here to hunt # the bug again. It should never be enabled in published code. # GDGraph3d-0.53 was an interesting case according to Larry # Virden. warn(">>>Bughunting code enabled<<< " x 20); for $af ($tar->list_files) { if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } $CPAN::Frontend->myprint("$af\n"); $tar->extract($af); # slow but effective for finding the bug return if $CPAN::Signal; } } else { for $af ($tar->list_files) { if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } if ($tar_verb eq "v" || $tar_verb eq "vv") { $CPAN::Frontend->myprint("$af\n"); } push @af, $af; return if $CPAN::Signal; } $tar->extract(@af) or $CPAN::Frontend->mydie("Could not untar with Archive::Tar."); } Mac::BuildTools::convert_files([$tar->list_files], 1) if ($^O eq 'MacOS'); return 1; } } sub unzip { my($self) = @_; my $file = $self->{FILE}; if ($CPAN::META->has_inst("Archive::Zip")) { # blueprint of the code from Archive::Zip::Tree::extractTree(); my $zip = Archive::Zip->new(); my $status; $status = $zip->read($file); $CPAN::Frontend->mydie("Read of file[$file] failed\n") if $status != Archive::Zip::AZ_OK(); $CPAN::META->debug("Successfully read file[$file]") if $CPAN::DEBUG; my @members = $zip->members(); for my $member ( @members ) { my $af = $member->fileName(); if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } $status = $member->extractToFileNamed( $af ); $CPAN::META->debug("af[$af]status[$status]") if $CPAN::DEBUG; $CPAN::Frontend->mydie("Extracting of file[$af] from zipfile[$file] failed\n") if $status != Archive::Zip::AZ_OK(); return if $CPAN::Signal; } return 1; } elsif ( my $unzip = $CPAN::Config->{unzip} ) { my @system = ($unzip, $file); return system(@system) == 0; } else { $CPAN::Frontend->mydie(<<"END"); Can't unzip '$file': You have not configured an 'unzip' program and do not have Archive::Zip installed. Please either install Archive::Zip or else configure 'unzip' by running the command 'o conf init unzip' from the CPAN shell prompt. END } } 1; __END__ =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut