Blame lib/Archive/Zip/ZipFileMember.pm

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