📄 archive.pm
字号:
or return _ioError('seeking to write central directory'); } else { $offset = $self->_writeCentralDirectoryOffset(); } foreach my $member ( $self->members() ) { my $status = $member->_writeCentralDirectoryFileHeader($fh); return $status if $status != AZ_OK; $offset += $member->_centralDirectoryHeaderSize(); $self->{'writeEOCDOffset'} = $offset; } return $self->_writeEndOfCentralDirectory($fh);}sub read { my $self = shift; my $fileName = shift; return _error('No filename given') unless $fileName; my ( $status, $fh ) = _newFileHandle( $fileName, 'r' ); return _ioError("opening $fileName for read") unless $status; $status = $self->readFromFileHandle( $fh, $fileName ); return $status if $status != AZ_OK; $fh->close(); $self->{'fileName'} = $fileName; return AZ_OK;}sub readFromFileHandle { my $self = shift; my $fh = shift; my $fileName = shift; $fileName = $fh unless defined($fileName); return _error('No filehandle given') unless $fh; return _ioError('filehandle not open') unless $fh->opened(); _binmode($fh); $self->{'fileName'} = "$fh"; # TODO: how to support non-seekable zips? return _error('file not seekable') unless _isSeekable($fh); $fh->seek( 0, 0 ); # rewind the file my $status = $self->_findEndOfCentralDirectory($fh); return $status if $status != AZ_OK; my $eocdPosition = $fh->tell(); $status = $self->_readEndOfCentralDirectory($fh); return $status if $status != AZ_OK; $fh->seek( $eocdPosition - $self->centralDirectorySize(), IO::Seekable::SEEK_SET ) or return _ioError("Can't seek $fileName"); # Try to detect garbage at beginning of archives # This should be 0 $self->{'eocdOffset'} = $eocdPosition - $self->centralDirectorySize() # here - $self->centralDirectoryOffsetWRTStartingDiskNumber(); for ( ; ; ) { my $newMember = $self->ZIPMEMBERCLASS->_newFromZipFile( $fh, $fileName, $self->eocdOffset() ); my $signature; ( $status, $signature ) = _readSignature( $fh, $fileName ); return $status if $status != AZ_OK; last if $signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE; $status = $newMember->_readCentralDirectoryFileHeader(); return $status if $status != AZ_OK; $status = $newMember->endRead(); return $status if $status != AZ_OK; $newMember->_becomeDirectoryIfNecessary(); push( @{ $self->{'members'} }, $newMember ); } return AZ_OK;}# Read EOCD, starting from position before signature.# Return AZ_OK on success.sub _readEndOfCentralDirectory { my $self = shift; my $fh = shift; # Skip past signature $fh->seek( SIGNATURE_LENGTH, IO::Seekable::SEEK_CUR ) or return _ioError("Can't seek past EOCD signature"); my $header = ''; my $bytesRead = $fh->read( $header, END_OF_CENTRAL_DIRECTORY_LENGTH ); if ( $bytesRead != END_OF_CENTRAL_DIRECTORY_LENGTH ) { return _ioError("reading end of central directory"); } my $zipfileCommentLength; ( $self->{'diskNumber'}, $self->{'diskNumberWithStartOfCentralDirectory'}, $self->{'numberOfCentralDirectoriesOnThisDisk'}, $self->{'numberOfCentralDirectories'}, $self->{'centralDirectorySize'}, $self->{'centralDirectoryOffsetWRTStartingDiskNumber'}, $zipfileCommentLength ) = unpack( END_OF_CENTRAL_DIRECTORY_FORMAT, $header ); if ($zipfileCommentLength) { my $zipfileComment = ''; $bytesRead = $fh->read( $zipfileComment, $zipfileCommentLength ); if ( $bytesRead != $zipfileCommentLength ) { return _ioError("reading zipfile comment"); } $self->{'zipfileComment'} = $zipfileComment; } return 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/\A(.+)\z/s; return $1;}sub addTree { my $self = shift; my $root = shift or return _error("root arg missing in call to addTree()"); my $dest = shift; $dest = '' unless defined($dest); my $pred = shift || sub { -r }; 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 ); 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 = -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 ); return _error("add $fileName failed in addTree()") if !$member; } return AZ_OK;}sub addTreeMatching { my $self = shift; my $root = shift or return _error("root arg missing in call to addTreeMatching()"); my $dest = shift; $dest = '' unless defined($dest); my $pattern = shift or return _error("pattern missing in call to addTreeMatching()"); my $pred = shift; my $matcher = $pred ? sub { m{$pattern} && &$pred } : sub { m{$pattern} && -r }; return $self->addTree( $root, $dest, $matcher );}# $zip->extractTree( $root, $dest [, $volume] );## $root and $dest are Unix-style.# $volume is in local FS format.#sub extractTree { my $self = shift; my $root = shift; # Zip format $root = '' unless defined($root); my $dest = shift; # Zip format $dest = './' unless defined($dest); my $volume = shift; # optional my $pattern = "^\Q$root"; my @members = $self->membersMatching($pattern); foreach my $member (@members) { my $fileName = $member->fileName(); # in Unix format $fileName =~ s{$pattern}{$dest}; # in Unix format # convert to platform format: $fileName = Archive::Zip::_asLocalName( $fileName, $volume ); my $status = $member->extractToFileNamed($fileName); return $status if $status != AZ_OK; } return AZ_OK;}# $zip->updateMember( $memberOrName, $fileName );# Returns (possibly updated) member, if any; undef on errors.sub updateMember { my $self = shift; my $oldMember = shift; my $fileName = shift; if ( !defined($fileName) ) { _error("updateMember(): missing fileName argument"); return undef; } my @newStat = stat($fileName); if ( !@newStat ) { _ioError("Can't stat $fileName"); return undef; } my $isDir = -d _; my $memberName; if ( ref($oldMember) ) { $memberName = $oldMember->fileName(); } else { $oldMember = $self->memberNamed( $memberName = $oldMember ) || $self->memberNamed( $memberName = _asZipDirName( $oldMember, $isDir ) ); } unless ( defined($oldMember) && $oldMember->lastModTime() == $newStat[9] && $oldMember->isDirectory() == $isDir && ( $isDir || ( $oldMember->uncompressedSize() == $newStat[7] ) ) ) { # create the new member my $newMember = $isDir ? $self->ZIPMEMBERCLASS->newDirectoryNamed( $fileName, $memberName ) : $self->ZIPMEMBERCLASS->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 weren't found.sub updateTree { my $self = shift; my $root = shift or return _error("root arg missing in call to updateTree()"); my $dest = shift; $dest = '' unless defined($dest); $dest = _asZipDirName( $dest, 1 ); my $pred = shift || sub { -r }; my $mirror = shift; 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 ); 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -