Archive-Zip

 view release on metacpan or  search on metacpan

lib/Archive/Zip/Member.pm  view on Meta::CPAN


    my ($stringOrStringRef, $fileName);
    if (ref($_[0]) eq 'HASH') {
        $stringOrStringRef = $_[0]->{string};
        $fileName          = $_[0]->{zipName};
    } else {
        ($stringOrStringRef, $fileName) = @_;
    }

    my $self =
      Archive::Zip::StringMember->_newFromString($stringOrStringRef, $fileName);
    return $self;
}

sub newFromFile {
    my $class = shift;

    my ($fileName, $zipName);
    if (ref($_[0]) eq 'HASH') {
        $fileName = $_[0]->{fileName};
        $zipName  = $_[0]->{zipName};
    } else {
        ($fileName, $zipName) = @_;
    }

    my $self =
      Archive::Zip::NewFileMember->_newFromFileNamed($fileName, $zipName);
    return $self;
}

sub newDirectoryNamed {
    my $class = shift;

    my ($directoryName, $newName);
    if (ref($_[0]) eq 'HASH') {
        $directoryName = $_[0]->{directoryName};
        $newName       = $_[0]->{zipName};
    } else {
        ($directoryName, $newName) = @_;
    }

    my $self =
      Archive::Zip::DirectoryMember->_newNamed($directoryName, $newName);
    return $self;
}

sub new {
    my $class = shift;
    # Info-Zip 3.0 (I guess) seems to use the following values
    # for the version fields in local and central directory
    # headers, regardless of whether the member has an zip64
    # extended information extra field or not:
    #
    #   version made by:
    #     30
    #
    #   version needed to extract:
    #     10 for directory and stored entries
    #     20 for anything else
    my $self  = {
        'lastModFileDateTime'      => 0,
        'fileAttributeFormat'      => FA_UNIX,
        'zip64'                    => 0,
        'desiredZip64Mode'         => ZIP64_AS_NEEDED,
        'versionMadeBy'            => 20,
        'versionNeededToExtract'   => 20,
        'bitFlag'                  => ($Archive::Zip::UNICODE ? 0x0800 : 0),
        'compressionMethod'        => COMPRESSION_STORED,
        'desiredCompressionMethod' => COMPRESSION_STORED,
        'desiredCompressionLevel'  => COMPRESSION_LEVEL_NONE,
        'internalFileAttributes'   => 0,
        'externalFileAttributes'   => 0,                        # set later
        'fileName'                 => '',
        'cdExtraField'             => '',
        'localExtraField'          => '',
        'fileComment'              => '',
        'crc32'                    => 0,
        'compressedSize'           => 0,
        'uncompressedSize'         => 0,
        'password'                 => undef,    # password for encrypted data
        'crc32c'                   => -1,       # crc for decrypted data
        @_
    };
    bless($self, $class);
    $self->unixFileAttributes($self->DEFAULT_FILE_PERMISSIONS);
    return $self;
}

# Morph into given class (do whatever cleanup I need to do)
sub _become {
    return bless($_[0], $_[1]);
}

sub fileAttributeFormat {
    my $self = shift;

    if (@_) {
        $self->{fileAttributeFormat} =
          (ref($_[0]) eq 'HASH') ? $_[0]->{format} : $_[0];
    } else {
        return $self->{fileAttributeFormat};
    }
}

sub zip64 {
    shift->{'zip64'};
}

sub desiredZip64Mode {
    my $self = shift;
    my $desiredZip64Mode = $self->{'desiredZip64Mode'};
    if (@_) {
        $self->{'desiredZip64Mode'} =
          ref($_[0]) eq 'HASH' ? shift->{desiredZip64Mode} : shift;
    }
    return $desiredZip64Mode;
}

sub versionMadeBy {
    shift->{'versionMadeBy'};
}

lib/Archive/Zip/Member.pm  view on Meta::CPAN

    $self->{'password'};
}

sub compressionMethod {
    shift->{'compressionMethod'};
}

sub desiredCompressionMethod {
    my $self = shift;
    my $newDesiredCompressionMethod =
      (ref($_[0]) eq 'HASH') ? shift->{compressionMethod} : shift;
    my $oldDesiredCompressionMethod = $self->{'desiredCompressionMethod'};
    if (defined($newDesiredCompressionMethod)) {
        $self->{'desiredCompressionMethod'} = $newDesiredCompressionMethod;
        if ($newDesiredCompressionMethod == COMPRESSION_STORED) {
            $self->{'desiredCompressionLevel'} = 0;
            $self->{'bitFlag'} &= ~GPBF_HAS_DATA_DESCRIPTOR_MASK
                if $self->uncompressedSize() == 0;
        } elsif ($oldDesiredCompressionMethod == COMPRESSION_STORED) {
            $self->{'desiredCompressionLevel'} = COMPRESSION_LEVEL_DEFAULT;
        }
    }
    return $oldDesiredCompressionMethod;
}

sub desiredCompressionLevel {
    my $self = shift;
    my $newDesiredCompressionLevel =
      (ref($_[0]) eq 'HASH') ? shift->{compressionLevel} : shift;
    my $oldDesiredCompressionLevel = $self->{'desiredCompressionLevel'};
    if (defined($newDesiredCompressionLevel)) {
        $self->{'desiredCompressionLevel'}  = $newDesiredCompressionLevel;
        $self->{'desiredCompressionMethod'} = (
            $newDesiredCompressionLevel
            ? COMPRESSION_DEFLATED
            : COMPRESSION_STORED
        );
    }
    return $oldDesiredCompressionLevel;
}

sub fileName {
    my $self    = shift;
    my $newName = shift;
    if (defined $newName) {
        $newName =~ y{\\/}{/}s;    # deal with dos/windoze problems
        $self->{'fileName'} = $newName;
    }
    return $self->{'fileName'};
}

sub fileNameAsBytes {
    my $self  = shift;
    my $bytes = $self->{'fileName'};
    if($self->{'bitFlag'} & 0x800){
        $bytes = Encode::encode_utf8($bytes);
    }
    return $bytes;
}

sub lastModFileDateTime {
    my $modTime = shift->{'lastModFileDateTime'};
    $modTime =~ m/^(\d+)$/;           # untaint
    return $1;
}

sub lastModTime {
    my $self = shift;
    return _dosToUnixTime($self->lastModFileDateTime());
}

sub setLastModFileDateTimeFromUnix {
    my $self   = shift;
    my $time_t = shift;
    $self->{'lastModFileDateTime'} = _unixToDosTime($time_t);
}

sub internalFileAttributes {
    shift->{'internalFileAttributes'};
}

sub externalFileAttributes {
    shift->{'externalFileAttributes'};
}

# Convert UNIX permissions into proper value for zip file
# Usable as a function or a method
sub _mapPermissionsFromUnix {
    my $self    = shift;
    my $mode    = shift;
    my $attribs = $mode << 16;

    # Microsoft Windows Explorer needs this bit set for directories
    if ($mode & DIRECTORY_ATTRIB) {
        $attribs |= 16;
    }

    return $attribs;

    # TODO: map more MS-DOS perms
}

# Convert ZIP permissions into Unix ones
#
# This was taken from Info-ZIP group's portable UnZip
# zipfile-extraction program, version 5.50.
# http://www.info-zip.org/pub/infozip/
#
# See the mapattr() function in unix/unix.c
# See the attribute format constants in unzpriv.h
#
# XXX Note that there's one situation that is not implemented
# yet that depends on the "extra field."
sub _mapPermissionsToUnix {
    my $self = shift;

    my $format  = $self->{'fileAttributeFormat'};
    my $attribs = $self->{'externalFileAttributes'};

    my $mode = 0;

    if ($format == FA_AMIGA) {
        $attribs = $attribs >> 17 & 7;                         # Amiga RWE bits
        $mode    = $attribs << 6 | $attribs << 3 | $attribs;
        return $mode;
    }

    if ($format == FA_THEOS) {
        $attribs &= 0xF1FFFFFF;
        if (($attribs & 0xF0000000) != 0x40000000) {
            $attribs &= 0x01FFFFFF;    # not a dir, mask all ftype bits
        } else {
            $attribs &= 0x41FFFFFF;    # leave directory bit as set
        }
    }

lib/Archive/Zip/Member.pm  view on Meta::CPAN

            }
        }
        elsif ($hasDataDescriptor) {
            $crc32            = 0;
            $compressedSize   = 0;
            $uncompressedSize = 0;
        }
        else {
            $crc32            = $self->crc32();
            $compressedSize   = $self->_writeOffset();
            $uncompressedSize = $self->uncompressedSize();
        }
    }
    else {
        return _zip64NotSupported() unless ZIP64_SUPPORTED;

        $versionNeededToExtract = 45 if ($versionNeededToExtract < 45);

        my $zip64CompressedSize;
        my $zip64UncompressedSize;
        if ($refresh) {
            $crc32                 = $self->crc32();
            $compressedSize        = 0xffffffff;
            $uncompressedSize      = 0xffffffff;
            $zip64CompressedSize   = $self->_writeOffset();
            $zip64UncompressedSize = $self->uncompressedSize();
        }
        elsif ($hasDataDescriptor) {
            $crc32                 = 0;
            $compressedSize        = 0xffffffff;
            $uncompressedSize      = 0xffffffff;
            $zip64CompressedSize   = 0;
            $zip64UncompressedSize = 0;
        }
        else {
            $crc32                 = $self->crc32();
            $compressedSize        = 0xffffffff;
            $uncompressedSize      = 0xffffffff;
            $zip64CompressedSize   = $self->_writeOffset();
            $zip64UncompressedSize = $self->uncompressedSize();
        }

        $localExtraField .= pack('S< S< Q< Q<',
                                 0x0001, 16,
                                 $zip64UncompressedSize,
                                 $zip64CompressedSize);
    }

    my $fileNameLength    = length($self->fileNameAsBytes());
    my $localFieldLength  = length($localExtraField);

    my $signatureData = pack(SIGNATURE_FORMAT, LOCAL_FILE_HEADER_SIGNATURE);
    $self->_print($fh, $signatureData)
      or return _ioError("writing local header signature");

    my $header =
      pack(LOCAL_FILE_HEADER_FORMAT,
           $versionNeededToExtract,
           $self->{'bitFlag'},
           $self->desiredCompressionMethod(),
           $self->lastModFileDateTime(),
           $crc32,
           $compressedSize,
           $uncompressedSize,
           $fileNameLength,
           $localFieldLength);
    $self->_print($fh, $header)
      or return _ioError("writing local header");

    # Write these only if required
    if (! $refresh || $zip64) {
        if ($fileNameLength) {
            $self->_print($fh, $self->fileNameAsBytes())
              or return _ioError("writing local header filename");
        }
        if ($localFieldLength) {
            $self->_print($fh, $localExtraField)
              or return _ioError("writing local extra field");
        }
    }

    return
      (AZ_OK,
       LOCAL_FILE_HEADER_LENGTH +
       SIGNATURE_LENGTH +
       $fileNameLength +
       $localFieldLength);
}

# Re-writes the local file header with new crc32 and compressedSize fields.
# To be called after writing the data stream.
# Assumes that filename and extraField sizes didn't change since last written.
sub _refreshLocalFileHeader {
    my $self = shift;
    my $fh   = shift;

    my $here = $fh->tell();
    $fh->seek($self->writeLocalHeaderRelativeOffset(), IO::Seekable::SEEK_SET)
      or return _ioError("seeking to rewrite local header");

    my ($status, undef) = $self->_writeLocalFileHeader($fh, 1);
    return $status if $status != AZ_OK;

    $fh->seek($here, IO::Seekable::SEEK_SET)
      or return _ioError("seeking after rewrite of local header");

    return AZ_OK;
}

# Write central directory file header.
# Returns a pair (AZ_OK, $headerSize) on success.
sub _writeCentralDirectoryFileHeader {
    my $self   = shift;
    my $fh     = shift;
    my $adz64m = shift;         # $archiveDesiredZip64Mode

    # (Re-)Determine whether to write zip64 format.  Assume
    # {'diskNumberStart'} is always zero.
    my $zip64 = $adz64m == ZIP64_HEADERS
             || $self->desiredZip64Mode() == ZIP64_HEADERS
             || $self->_writeOffset() > 0xffffffff

lib/Archive/Zip/Member.pm  view on Meta::CPAN

    my $versionNeededToExtract    = $self->versionNeededToExtract();
    my $compressedSize            = $self->_writeOffset();
    my $uncompressedSize          = $self->uncompressedSize();
    my $localHeaderRelativeOffset = $self->writeLocalHeaderRelativeOffset();
    my $cdExtraField              = $self->cdExtraField();

    if (!$zip64) {
        # no-op
    }
    else {
        return _zip64NotSupported() unless ZIP64_SUPPORTED;

        $versionNeededToExtract = 45 if ($versionNeededToExtract < 45);

        my $extraFieldFormat = '';
        my @extraFieldValues = ();
        my $extraFieldSize   = 0;
        if ($uncompressedSize > 0xffffffff) {
            $extraFieldFormat .= 'Q< ';
            push(@extraFieldValues, $uncompressedSize);
            $extraFieldSize += 8;
            $uncompressedSize = 0xffffffff;
        }
        if ($compressedSize > 0xffffffff) {
            $extraFieldFormat .= 'Q< ';
            push(@extraFieldValues, $compressedSize);
            $extraFieldSize += 8;
            $compressedSize = 0xffffffff;
        }
        # Avoid empty zip64 extended information extra fields
        if (   $localHeaderRelativeOffset > 0xffffffff
            || @extraFieldValues == 0) {
            $extraFieldFormat .= 'Q< ';
            push(@extraFieldValues, $localHeaderRelativeOffset);
            $extraFieldSize += 8;
            $localHeaderRelativeOffset = 0xffffffff;
        }

        $cdExtraField .=
          pack("S< S< $extraFieldFormat",
               0x0001, $extraFieldSize,
               @extraFieldValues);
    }

    my $fileNameLength    = length($self->fileNameAsBytes());
    my $extraFieldLength  = length($cdExtraField);
    my $fileCommentLength = length($self->fileComment());

    my $sigData =
      pack(SIGNATURE_FORMAT, CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE);
    $self->_print($fh, $sigData)
      or return _ioError("writing central directory header signature");

    my $header = pack(
        CENTRAL_DIRECTORY_FILE_HEADER_FORMAT,
        $versionMadeBy,
        $self->fileAttributeFormat(),
        $versionNeededToExtract,
        $self->bitFlag(),
        $self->desiredCompressionMethod(),
        $self->lastModFileDateTime(),
        $self->crc32(),            # these three fields should have been updated
        $compressedSize,           # by writing the data stream out
        $uncompressedSize,         #
        $fileNameLength,
        $extraFieldLength,
        $fileCommentLength,
        0,                         # {'diskNumberStart'},
        $self->internalFileAttributes(),
        $self->externalFileAttributes(),
        $localHeaderRelativeOffset);

    $self->_print($fh, $header)
      or return _ioError("writing central directory header");

    if ($fileNameLength) {
        $self->_print($fh, $self->fileNameAsBytes())
          or return _ioError("writing central directory header signature");
    }
    if ($extraFieldLength) {
        $self->_print($fh, $cdExtraField)
          or return _ioError("writing central directory extra field");
    }
    if ($fileCommentLength) {
        $self->_print($fh, $self->fileComment())
          or return _ioError("writing central directory file comment");
    }

    # Update object members with information which might have
    # changed while writing this member.  We already did the
    # zip64 flag.  We must not update the extra fields with any
    # zip64 information, since we consider that internal.
    $self->{'versionNeededToExtract'} = $versionNeededToExtract;
    $self->{'compressedSize'}         = $self->_writeOffset();

    return
      (AZ_OK,
       CENTRAL_DIRECTORY_FILE_HEADER_LENGTH +
       SIGNATURE_LENGTH +
       $fileNameLength +
       $extraFieldLength +
       $fileCommentLength)
}

# This writes a data descriptor to the given file handle.
# Assumes that crc32, writeOffset, and uncompressedSize are
# set correctly (they should be after a write).
# Returns a pair (AZ_OK, $dataDescriptorSize) on success.
# Further, the local file header should have the
# GPBF_HAS_DATA_DESCRIPTOR_MASK bit set.
sub _writeDataDescriptor {
    my $self   = shift;
    my $fh     = shift;

    my $descriptor;
    if (! $self->zip64()) {
        $descriptor =
          pack(SIGNATURE_FORMAT . DATA_DESCRIPTOR_FORMAT,
               DATA_DESCRIPTOR_SIGNATURE,
               $self->crc32(),
               $self->_writeOffset(),   # compressed size

lib/Archive/Zip/Member.pm  view on Meta::CPAN

        my $c = $n;
        $c = $c & 1 ? $xor ^ ($c >> 1) : $c >> 1 for 1 .. 8;
        $crc[$n] = _revbe($c);
    }

    # generate crc for each value followed by one, two, and three zeros */
    foreach my $n (0 .. 255) {
        my $c = ($crc[($crc[$n] >> 24) ^ 0] ^ ($crc[$n] << 8)) & 0xffffffff;
        $crc[$_ * 256 + $n] = $c for 1 .. 3;
    }
    map { _revbe($crc[$_]) } 0 .. 1023;
};

sub _crc32 {
    my ($c, $b) = @_;
    return ($crct[($c ^ $b) & 0xff] ^ ($c >> 8));
}    # _crc32

sub _revbe {
    my $w = shift;
    return (($w >> 24) +
          (($w >> 8) & 0xff00) +
          (($w & 0xff00) << 8) +
          (($w & 0xff) << 24));
}    # _revbe

sub _update_keys {
    use integer;
    my $c = shift;    # signed int
    $keys[0] = _crc32($keys[0], $c);
    $keys[1] = (($keys[1] + ($keys[0] & 0xff)) * 0x08088405 + 1) & 0xffffffff;
    my $keyshift = $keys[1] >> 24;
    $keys[2] = _crc32($keys[2], $keyshift);
}    # _update_keys

sub _zdecode ($) {
    my $c = shift;
    my $t = ($keys[2] & 0xffff) | 2;
    _update_keys($c ^= ((($t * ($t ^ 1)) >> 8) & 0xff));
    return $c;
}    # _zdecode

sub _decode {
    my $self = shift;
    my $buff = shift;

    $self->isEncrypted or return $buff;

    my $pass = $self->password;
    defined $pass or return "";

    @keys = (0x12345678, 0x23456789, 0x34567890);
    _update_keys($_) for unpack "C*", $pass;

    # DDumper { uk => [ @keys ] };

    my $head = substr $buff, 0, 12, "";
    my @head = map { _zdecode($_) } unpack "C*", $head;
    my $x =
      $self->{externalFileAttributes}
      ? ($self->{lastModFileDateTime} >> 8) & 0xff
      : $self->{crc32} >> 24;
    $head[-1] == $x or return "";    # Password fail

    # Worth checking ...
    $self->{crc32c} = (unpack LOCAL_FILE_HEADER_FORMAT, pack "C*", @head)[3];

    # DHexDump ($buff);
    $buff = pack "C*" => map { _zdecode($_) } unpack "C*" => $buff;

    # DHexDump ($buff);
    return $buff;
}    # _decode

1;



( run in 0.496 second using v1.01-cache-2.11-cpan-39bf76dae61 )