Blame | Last modification | View Log | RSS feed
package Archive::Zip::ZipFileMember;use strict;use vars qw( $VERSION @ISA );BEGIN {$VERSION = '1.57';@ISA = qw ( Archive::Zip::FileMember );}use Archive::Zip qw(:CONSTANTS:ERROR_CODES:PKZIP_CONSTANTS:UTILITY_METHODS);# Create a new Archive::Zip::ZipFileMember# given a filename and optional open file handle#sub _newFromZipFile {my $class = shift;my $fh = shift;my $externalFileName = shift;my $possibleEocdOffset = shift; # normally 0my $self = $class->new('crc32' => 0,'diskNumberStart' => 0,'localHeaderRelativeOffset' => 0,'dataOffset' => 0, # localHeaderRelativeOffset + header length@_);$self->{'externalFileName'} = $externalFileName;$self->{'fh'} = $fh;$self->{'possibleEocdOffset'} = $possibleEocdOffset;return $self;}sub isDirectory {my $self = shift;return (substr($self->fileName, -1, 1) eq '/'and $self->uncompressedSize == 0);}# Seek to the beginning of the local header, just past the signature.# Verify that the local header signature is in fact correct.# Update the localHeaderRelativeOffset if necessary by adding the possibleEocdOffset.# Returns status.sub _seekToLocalHeader {my $self = shift;my $where = shift; # optionalmy $previousWhere = shift; # optional$where = $self->localHeaderRelativeOffset() unless defined($where);# avoid loop on certain corrupt files (from Julian Field)return _formatError("corrupt zip file")if defined($previousWhere) && $where == $previousWhere;my $status;my $signature;$status = $self->fh()->seek($where, IO::Seekable::SEEK_SET);return _ioError("seeking to local header") unless $status;($status, $signature) =_readSignature($self->fh(), $self->externalFileName(),LOCAL_FILE_HEADER_SIGNATURE);return $status if $status == AZ_IO_ERROR;# retry with EOCD offset if any was given.if ($status == AZ_FORMAT_ERROR && $self->{'possibleEocdOffset'}) {$status = $self->_seekToLocalHeader($self->localHeaderRelativeOffset() + $self->{'possibleEocdOffset'},$where);if ($status == AZ_OK) {$self->{'localHeaderRelativeOffset'} +=$self->{'possibleEocdOffset'};$self->{'possibleEocdOffset'} = 0;}}return $status;}# Because I'm going to delete the file handle, read the local file# header if the file handle is seekable. If it is not, I assume that# I've already read the local header.# Return ( $status, $self )sub _become {my $self = shift;my $newClass = shift;return $self if ref($self) eq $newClass;my $status = AZ_OK;if (_isSeekable($self->fh())) {my $here = $self->fh()->tell();$status = $self->_seekToLocalHeader();$status = $self->_readLocalFileHeader() if $status == AZ_OK;$self->fh()->seek($here, IO::Seekable::SEEK_SET);return $status unless $status == AZ_OK;}delete($self->{'eocdCrc32'});delete($self->{'diskNumberStart'});delete($self->{'localHeaderRelativeOffset'});delete($self->{'dataOffset'});return $self->SUPER::_become($newClass);}sub diskNumberStart {shift->{'diskNumberStart'};}sub localHeaderRelativeOffset {shift->{'localHeaderRelativeOffset'};}sub dataOffset {shift->{'dataOffset'};}# Skip local file header, updating only extra field stuff.# Assumes that fh is positioned before signature.sub _skipLocalFileHeader {my $self = shift;my $header;my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {return _ioError("reading local file header");}my $fileNameLength;my $extraFieldLength;my $bitFlag;(undef, # $self->{'versionNeededToExtract'},$bitFlag,undef, # $self->{'compressionMethod'},undef, # $self->{'lastModFileDateTime'},undef, # $crc32,undef, # $compressedSize,undef, # $uncompressedSize,$fileNameLength,$extraFieldLength) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);if ($fileNameLength) {$self->fh()->seek($fileNameLength, IO::Seekable::SEEK_CUR)or return _ioError("skipping local file name");}if ($extraFieldLength) {$bytesRead =$self->fh()->read($self->{'localExtraField'}, $extraFieldLength);if ($bytesRead != $extraFieldLength) {return _ioError("reading local extra field");}}$self->{'dataOffset'} = $self->fh()->tell();if ($bitFlag & GPBF_HAS_DATA_DESCRIPTOR_MASK) {# Read the crc32, compressedSize, and uncompressedSize from the# extended data descriptor, which directly follows the compressed data.## Skip over the compressed file data (assumes that EOCD compressedSize# was correct)$self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)or return _ioError("seeking to extended local header");# these values should be set correctly from before.my $oldCrc32 = $self->{'eocdCrc32'};my $oldCompressedSize = $self->{'compressedSize'};my $oldUncompressedSize = $self->{'uncompressedSize'};my $status = $self->_readDataDescriptor();return $status unless $status == AZ_OK;# The buffer withe encrypted data is prefixed with a new# encrypted 12 byte header. The size only changes when# the buffer is also compressed$self->isEncrypted && $oldUncompressedSize > $self->{uncompressedSize}and $oldUncompressedSize -= DATA_DESCRIPTOR_LENGTH;return _formatError("CRC or size mismatch while skipping data descriptor")if ( $oldCrc32 != $self->{'crc32'}|| $oldUncompressedSize != $self->{'uncompressedSize'});$self->{'crc32'} = 0if $self->compressionMethod() == COMPRESSION_STORED ;}return AZ_OK;}# Read from a local file header into myself. Returns AZ_OK if successful.# Assumes that fh is positioned after signature.# Note that crc32, compressedSize, and uncompressedSize will be 0 if# GPBF_HAS_DATA_DESCRIPTOR_MASK is set in the bitFlag.sub _readLocalFileHeader {my $self = shift;my $header;my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {return _ioError("reading local file header");}my $fileNameLength;my $crc32;my $compressedSize;my $uncompressedSize;my $extraFieldLength;($self->{'versionNeededToExtract'}, $self->{'bitFlag'},$self->{'compressionMethod'}, $self->{'lastModFileDateTime'},$crc32, $compressedSize,$uncompressedSize, $fileNameLength,$extraFieldLength) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);if ($fileNameLength) {my $fileName;$bytesRead = $self->fh()->read($fileName, $fileNameLength);if ($bytesRead != $fileNameLength) {return _ioError("reading local file name");}$self->fileName($fileName);}if ($extraFieldLength) {$bytesRead =$self->fh()->read($self->{'localExtraField'}, $extraFieldLength);if ($bytesRead != $extraFieldLength) {return _ioError("reading local extra field");}}$self->{'dataOffset'} = $self->fh()->tell();if ($self->hasDataDescriptor()) {# Read the crc32, compressedSize, and uncompressedSize from the# extended data descriptor.# Skip over the compressed file data (assumes that EOCD compressedSize# was correct)$self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)or return _ioError("seeking to extended local header");my $status = $self->_readDataDescriptor();return $status unless $status == AZ_OK;} else {return _formatError("CRC or size mismatch after reading data descriptor")if ( $self->{'crc32'} != $crc32|| $self->{'uncompressedSize'} != $uncompressedSize);}return AZ_OK;}# This will read the data descriptor, which is after the end of compressed file# data in members that have GPBF_HAS_DATA_DESCRIPTOR_MASK set in their bitFlag.# The only reliable way to find these is to rely on the EOCD compressedSize.# Assumes that file is positioned immediately after the compressed data.# Returns status; sets crc32, compressedSize, and uncompressedSize.sub _readDataDescriptor {my $self = shift;my $signatureData;my $header;my $crc32;my $compressedSize;my $uncompressedSize;my $bytesRead = $self->fh()->read($signatureData, SIGNATURE_LENGTH);return _ioError("reading header signature")if $bytesRead != SIGNATURE_LENGTH;my $signature = unpack(SIGNATURE_FORMAT, $signatureData);# unfortunately, the signature appears to be optional.if ($signature == DATA_DESCRIPTOR_SIGNATURE&& ($signature != $self->{'crc32'})) {$bytesRead = $self->fh()->read($header, DATA_DESCRIPTOR_LENGTH);return _ioError("reading data descriptor")if $bytesRead != DATA_DESCRIPTOR_LENGTH;($crc32, $compressedSize, $uncompressedSize) =unpack(DATA_DESCRIPTOR_FORMAT, $header);} else {$bytesRead = $self->fh()->read($header, DATA_DESCRIPTOR_LENGTH_NO_SIG);return _ioError("reading data descriptor")if $bytesRead != DATA_DESCRIPTOR_LENGTH_NO_SIG;$crc32 = $signature;($compressedSize, $uncompressedSize) =unpack(DATA_DESCRIPTOR_FORMAT_NO_SIG, $header);}$self->{'eocdCrc32'} = $self->{'crc32'}unless defined($self->{'eocdCrc32'});$self->{'crc32'} = $crc32;$self->{'compressedSize'} = $compressedSize;$self->{'uncompressedSize'} = $uncompressedSize;return AZ_OK;}# Read a Central Directory header. Return AZ_OK on success.# Assumes that fh is positioned right after the signature.sub _readCentralDirectoryFileHeader {my $self = shift;my $fh = $self->fh();my $header = '';my $bytesRead = $fh->read($header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH);if ($bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH) {return _ioError("reading central dir header");}my ($fileNameLength, $extraFieldLength, $fileCommentLength);($self->{'versionMadeBy'},$self->{'fileAttributeFormat'},$self->{'versionNeededToExtract'},$self->{'bitFlag'},$self->{'compressionMethod'},$self->{'lastModFileDateTime'},$self->{'crc32'},$self->{'compressedSize'},$self->{'uncompressedSize'},$fileNameLength,$extraFieldLength,$fileCommentLength,$self->{'diskNumberStart'},$self->{'internalFileAttributes'},$self->{'externalFileAttributes'},$self->{'localHeaderRelativeOffset'}) = unpack(CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header);$self->{'eocdCrc32'} = $self->{'crc32'};if ($fileNameLength) {$bytesRead = $fh->read($self->{'fileName'}, $fileNameLength);if ($bytesRead != $fileNameLength) {_ioError("reading central dir filename");}}if ($extraFieldLength) {$bytesRead = $fh->read($self->{'cdExtraField'}, $extraFieldLength);if ($bytesRead != $extraFieldLength) {return _ioError("reading central dir extra field");}}if ($fileCommentLength) {$bytesRead = $fh->read($self->{'fileComment'}, $fileCommentLength);if ($bytesRead != $fileCommentLength) {return _ioError("reading central dir file comment");}}# NK 10/21/04: added to avoid problems with manipulated headersif ( $self->{'uncompressedSize'} != $self->{'compressedSize'}and $self->{'compressionMethod'} == COMPRESSION_STORED) {$self->{'uncompressedSize'} = $self->{'compressedSize'};}$self->desiredCompressionMethod($self->compressionMethod());return AZ_OK;}sub rewindData {my $self = shift;my $status = $self->SUPER::rewindData(@_);return $status unless $status == AZ_OK;return AZ_IO_ERROR unless $self->fh();$self->fh()->clearerr();# Seek to local file header.# The only reason that I'm doing this this way is that the extraField# length seems to be different between the CD header and the LF header.$status = $self->_seekToLocalHeader();return $status unless $status == AZ_OK;# skip local file header$status = $self->_skipLocalFileHeader();return $status unless $status == AZ_OK;# Seek to beginning of file data$self->fh()->seek($self->dataOffset(), IO::Seekable::SEEK_SET)or return _ioError("seeking to beginning of file data");return AZ_OK;}# Return bytes read. Note that first parameter is a ref to a buffer.# my $data;# my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize );sub _readRawChunk {my ($self, $dataRef, $chunkSize) = @_;return (0, AZ_OK) unless $chunkSize;my $bytesRead = $self->fh()->read($$dataRef, $chunkSize)or return (0, _ioError("reading data"));return ($bytesRead, AZ_OK);}1;