Archive-Zip
view release on metacpan or search on metacpan
lib/Archive/Zip/Archive.pm view on Meta::CPAN
}
$self->{'zipfileComment'} = $zipfileComment;
}
if (! $self->{'zip64'}) {
return
wantarray
? (AZ_OK, $eocdPosition)
: AZ_OK;
}
else {
return
wantarray
? (AZ_OK, $zip64EOCDPosition)
: AZ_OK;
}
}
# Seek in my file to the end, then read backwards until we find the
# signature of the central directory record. Leave the file positioned right
# before the signature. Returns AZ_OK if success.
sub _findEndOfCentralDirectory {
my $self = shift;
my $fh = shift;
my $data = '';
$fh->seek(0, IO::Seekable::SEEK_END)
or return _ioError("seeking to end");
my $fileLength = $fh->tell();
if ($fileLength < END_OF_CENTRAL_DIRECTORY_LENGTH + 4) {
return _formatError("file is too short");
}
my $seekOffset = 0;
my $pos = -1;
for (; ;) {
$seekOffset += 512;
$seekOffset = $fileLength if ($seekOffset > $fileLength);
$fh->seek(-$seekOffset, IO::Seekable::SEEK_END)
or return _ioError("seek failed");
my $bytesRead = $fh->read($data, $seekOffset);
if ($bytesRead != $seekOffset) {
return _ioError("read failed");
}
$pos = rindex($data, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING);
last
if ( $pos >= 0
or $seekOffset == $fileLength
or $seekOffset >= $Archive::Zip::ChunkSize);
}
if ($pos >= 0) {
$fh->seek($pos - $seekOffset, IO::Seekable::SEEK_CUR)
or return _ioError("seeking to EOCD");
return AZ_OK;
} else {
return _formatError("can't find EOCD signature");
}
}
# Used to avoid taint problems when chdir'ing.
# Not intended to increase security in any way; just intended to shut up the -T
# complaints. If your Cwd module is giving you unreliable returns from cwd()
# you have bigger problems than this.
sub _untaintDir {
my $dir = shift;
$dir =~ m/$UNTAINT/s;
return $1;
}
sub addTree {
my $self = shift;
my ($root, $dest, $pred, $compressionLevel);
if (ref($_[0]) eq 'HASH') {
$root = $_[0]->{root};
$dest = $_[0]->{zipName};
$pred = $_[0]->{select};
$compressionLevel = $_[0]->{compressionLevel};
} else {
($root, $dest, $pred, $compressionLevel) = @_;
}
return _error("root arg missing in call to addTree()")
unless defined($root);
$dest = '' unless defined($dest);
$pred = sub { -r }
unless defined($pred);
my @files;
my $startDir = _untaintDir(cwd());
return _error('undef returned by _untaintDir on cwd ', cwd())
unless $startDir;
# This avoids chdir'ing in Find, in a way compatible with older
# versions of File::Find.
my $wanted = sub {
local $main::_ = $File::Find::name;
my $dir = _untaintDir($File::Find::dir);
chdir($startDir);
if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
push(@files, Win32::GetANSIPathName($File::Find::name)) if (&$pred);
$dir = Win32::GetANSIPathName($dir);
} else {
push(@files, $File::Find::name) if (&$pred);
}
chdir($dir);
};
if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
$root = Win32::GetANSIPathName($root);
}
# File::Find will not untaint unless you explicitly pass the flag and regex pattern.
File::Find::find({ wanted => $wanted, untaint => 1, untaint_pattern => $UNTAINT }, $root);
my $rootZipName = _asZipDirName($root, 1); # with trailing slash
my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
$dest = _asZipDirName($dest, 1); # with trailing slash
foreach my $fileName (@files) {
my $isDir;
if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
$isDir = -d Win32::GetANSIPathName($fileName);
} else {
$isDir = -d $fileName;
}
# normalize, remove leading ./
my $archiveName = _asZipDirName($fileName, $isDir);
if ($archiveName eq $rootZipName) { $archiveName = $dest }
else { $archiveName =~ s{$pattern}{$dest} }
next if $archiveName =~ m{^\.?/?$}; # skip current dir
my $member =
$isDir
? $self->addDirectory($fileName, $archiveName)
: $self->addFile($fileName, $archiveName);
$member->desiredCompressionLevel($compressionLevel);
return _error("add $fileName failed in addTree()") if !$member;
}
return AZ_OK;
}
sub addTreeMatching {
my $self = shift;
my ($root, $dest, $pattern, $pred, $compressionLevel);
if (ref($_[0]) eq 'HASH') {
$root = $_[0]->{root};
$dest = $_[0]->{zipName};
$pattern = $_[0]->{pattern};
$pred = $_[0]->{select};
$compressionLevel = $_[0]->{compressionLevel};
} else {
($root, $dest, $pattern, $pred, $compressionLevel) = @_;
}
return _error("root arg missing in call to addTreeMatching()")
unless defined($root);
$dest = '' unless defined($dest);
return _error("pattern missing in call to addTreeMatching()")
unless defined($pattern);
my $matcher =
$pred ? sub { m{$pattern} && &$pred } : sub { m{$pattern} && -r };
return $self->addTree($root, $dest, $matcher, $compressionLevel);
}
# Check if one of the components of a path to the file or the file name
# itself is an already existing symbolic link. If yes then return an
# error. Continuing and writing to a file traversing a link posseses
# a security threat, especially if the link was extracted from an
# attacker-supplied archive. This would allow writing to an arbitrary
# file. The same applies when using ".." to escape from a working
lib/Archive/Zip/Archive.pm view on Meta::CPAN
&& $oldMember->isDirectory() == $isDir
&& ($isDir || ($oldMember->uncompressedSize() == $newStat[7]))) {
# create the new member
my $newMember =
$isDir
? Archive::Zip::Member->newDirectoryNamed($fileName, $memberName)
: Archive::Zip::Member->newFromFile($fileName, $memberName);
unless (defined($newMember)) {
_error("creation of member $fileName failed in updateMember()");
return undef;
}
# replace old member or append new one
if (defined($oldMember)) {
$self->replaceMember($oldMember, $newMember);
} else {
$self->addMember($newMember);
}
return $newMember;
}
return $oldMember;
}
# $zip->updateTree( $root, [ $dest, [ $pred [, $mirror]]] );
#
# This takes the same arguments as addTree, but first checks to see
# whether the file or directory already exists in the zip file.
#
# If the fourth argument $mirror is true, then delete all my members
# if corresponding files were not found.
sub updateTree {
my $self = shift;
my ($root, $dest, $pred, $mirror, $compressionLevel);
if (ref($_[0]) eq 'HASH') {
$root = $_[0]->{root};
$dest = $_[0]->{zipName};
$pred = $_[0]->{select};
$mirror = $_[0]->{mirror};
$compressionLevel = $_[0]->{compressionLevel};
} else {
($root, $dest, $pred, $mirror, $compressionLevel) = @_;
}
return _error("root arg missing in call to updateTree()")
unless defined($root);
$dest = '' unless defined($dest);
$pred = sub { -r }
unless defined($pred);
$dest = _asZipDirName($dest, 1);
my $rootZipName = _asZipDirName($root, 1); # with trailing slash
my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
my @files;
my $startDir = _untaintDir(cwd());
return _error('undef returned by _untaintDir on cwd ', cwd())
unless $startDir;
# This avoids chdir'ing in Find, in a way compatible with older
# versions of File::Find.
my $wanted = sub {
local $main::_ = $File::Find::name;
my $dir = _untaintDir($File::Find::dir);
chdir($startDir);
push(@files, $File::Find::name) if (&$pred);
chdir($dir);
};
File::Find::find($wanted, $root);
# Now @files has all the files that I could potentially be adding to
# the zip. Only add the ones that are necessary.
# For each file (updated or not), add its member name to @done.
my %done;
foreach my $fileName (@files) {
my @newStat = stat($fileName);
my $isDir = -d _;
# normalize, remove leading ./
my $memberName = _asZipDirName($fileName, $isDir);
if ($memberName eq $rootZipName) { $memberName = $dest }
else { $memberName =~ s{$pattern}{$dest} }
next if $memberName =~ m{^\.?/?$}; # skip current dir
$done{$memberName} = 1;
my $changedMember = $self->updateMember($memberName, $fileName);
$changedMember->desiredCompressionLevel($compressionLevel);
return _error("updateTree failed to update $fileName")
unless ref($changedMember);
}
# @done now has the archive names corresponding to all the found files.
# If we're mirroring, delete all those members that aren't in @done.
if ($mirror) {
foreach my $member ($self->members()) {
$self->removeMember($member)
unless $done{$member->fileName()};
}
}
return AZ_OK;
}
1;
( run in 0.783 second using v1.01-cache-2.11-cpan-39bf76dae61 )