X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=ext%2FCompress%2FIO%2FZlib%2Flib%2FIO%2FUncompress%2FUnzip.pm;h=ae123c9085a9aa50ab5fcdd0e29fc5e9251d314d;hb=6ecef415672954d64bc0df6bdb809cebe10f89d4;hp=4c9d882545f2a92e6f55923d4ef9fb4d089e40c7;hpb=2b4e0969009806e4e03a23b007570fa5279be8e0;p=p5sagit%2Fp5-mst-13.2.git diff --git a/ext/Compress/IO/Zlib/lib/IO/Uncompress/Unzip.pm b/ext/Compress/IO/Zlib/lib/IO/Uncompress/Unzip.pm index 4c9d882..ae123c9 100644 --- a/ext/Compress/IO/Zlib/lib/IO/Uncompress/Unzip.pm +++ b/ext/Compress/IO/Zlib/lib/IO/Uncompress/Unzip.pm @@ -11,20 +11,23 @@ use bytes; use IO::Uncompress::RawInflate ; use IO::Compress::Base::Common qw(:Status createSelfTiedObject); use IO::Uncompress::Adapter::Identity; +use IO::Compress::Zlib::Extra; +use IO::Compress::Zip::Constants; use Compress::Raw::Zlib qw(crc32) ; + BEGIN { - eval { require IO::Uncompress::Adapter::Bunzip2 ; - import IO::Uncompress::Adapter::Bunzip2 } ; + eval { require IO::Uncompress::Adapter::Bunzip2 ; + import IO::Uncompress::Adapter::Bunzip2 } ; } require Exporter ; -our ($VERSION, @ISA, @EXPORT_OK, %EXPORT_TAGS, $UnzipError); +our ($VERSION, @ISA, @EXPORT_OK, %EXPORT_TAGS, $UnzipError, %headerLookup); -$VERSION = '2.000_11'; +$VERSION = '2.000_13'; $UnzipError = ''; @ISA = qw(Exporter IO::Uncompress::RawInflate); @@ -33,6 +36,14 @@ $UnzipError = ''; push @{ $EXPORT_TAGS{all} }, @EXPORT_OK ; Exporter::export_ok_tags('all'); +%headerLookup = ( + ZIP_CENTRAL_HDR_SIG, \&skipCentralDirectory, + ZIP_END_CENTRAL_HDR_SIG, \&skipEndCentralDirectory, + ZIP64_END_CENTRAL_REC_HDR_SIG, \&skipCentralDirectory64Rec, + ZIP64_END_CENTRAL_LOC_HDR_SIG, \&skipCentralDirectory64Loc, + ZIP64_ARCHIVE_EXTRA_SIG, \&skipArchiveExtra, + ZIP64_DIGITAL_SIGNATURE_SIG, \&skipDigitalSignature, + ); sub new { @@ -143,7 +154,7 @@ sub readHeader or return $self->saveErrorString(undef, "Truncated file"); } else { - my $c = $hdr->{CompressedLength}; + my $c = $hdr->{CompressedLength}->get32bit(); $self->smartReadExact(\$buffer, $c) or return $self->saveErrorString(undef, "Truncated file"); $buffer = ''; @@ -167,10 +178,22 @@ sub chkTrailer my $trailer = shift; my ($sig, $CRC32, $cSize, $uSize) ; + my ($cSizeHi, $uSizeHi) = (0, 0); if (*$self->{ZipData}{Streaming}) { - ($sig, $CRC32, $cSize, $uSize) = unpack("V V V V", $trailer) ; + $sig = unpack ("V", substr($trailer, 0, 4)); + $CRC32 = unpack ("V", substr($trailer, 4, 4)); + + if (*$self->{ZipData}{Zip64} ) { + $cSize = U64::newUnpack_V64 substr($trailer, 8, 8); + $uSize = U64::newUnpack_V64 substr($trailer, 16, 8); + } + else { + $cSize = U64::newUnpack_V32 substr($trailer, 8, 4); + $uSize = U64::newUnpack_V32 substr($trailer, 12, 4); + } + return $self->TrailerError("Data Descriptor signature, got $sig") - if $sig != 0x08074b50; + if $sig != ZIP_DATA_HDR_SIG; } else { ($CRC32, $cSize, $uSize) = @@ -183,15 +206,11 @@ sub chkTrailer return $self->TrailerError("CRC mismatch") if $CRC32 != *$self->{ZipData}{CRC32} ; - my $exp_isize = *$self->{Uncomp}->compressedBytes(); - return $self->TrailerError("CSIZE mismatch. Got $cSize" - . ", expected $exp_isize") - if $cSize != $exp_isize ; + return $self->TrailerError("CSIZE mismatch.") + if ! $cSize->equal(*$self->{CompSize}); - $exp_isize = *$self->{Uncomp}->uncompressedBytes(); - return $self->TrailerError("USIZE mismatch. Got $uSize" - . ", expected $exp_isize") - if $uSize != $exp_isize ; + return $self->TrailerError("USIZE mismatch.") + if ! $uSize->equal(*$self->{UnCompSize}); } my $reachedEnd = STATUS_ERROR ; @@ -217,9 +236,9 @@ sub chkTrailer my $sig = unpack("V", $magic) ; - if ($sig == 0x02014b50) + if ($headerLookup{$sig}) { - if ($self->skipCentralDirectory($magic) != STATUS_OK ) { + if ($headerLookup{$sig}($self, $magic) != STATUS_OK ) { if (*$self->{Strict}) { return STATUS_ERROR ; } @@ -228,23 +247,14 @@ sub chkTrailer return STATUS_OK ; } } - } - elsif ($sig == 0x06054b50) - { - if ($self->skipEndCentralDirectory($magic) != STATUS_OK) { - if (*$self->{Strict}) { - return STATUS_ERROR ; - } - else { - $self->clearError(); - return STATUS_OK ; - } + + if ($sig == ZIP_END_CENTRAL_HDR_SIG) + { + return STATUS_OK ; + last; } - # $reachedEnd = STATUS_OK ; - return STATUS_OK ; - last; } - elsif ($sig == 0x04034b50) + elsif ($sig == ZIP_LOCAL_HDR_SIG) { $self->pushBack($magic) ; return STATUS_OK ; @@ -279,8 +289,8 @@ sub skipCentralDirectory #my $compressedMethod = unpack ("v", substr($buffer, 10-4, 2)); #my $lastModTime = unpack ("V", substr($buffer, 12-4, 4)); #my $crc32 = unpack ("V", substr($buffer, 16-4, 4)); - #my $compressedLength = unpack ("V", substr($buffer, 20-4, 4)); - #my $uncompressedLength = unpack ("V", substr($buffer, 24-4, 4)); + my $compressedLength = unpack ("V", substr($buffer, 20-4, 4)); + my $uncompressedLength = unpack ("V", substr($buffer, 24-4, 4)); my $filename_length = unpack ("v", substr($buffer, 28-4, 2)); my $extra_length = unpack ("v", substr($buffer, 30-4, 2)); my $comment_length = unpack ("v", substr($buffer, 32-4, 2)); @@ -317,6 +327,85 @@ sub skipCentralDirectory return STATUS_OK ; } +sub skipArchiveExtra +{ + my $self = shift; + my $magic = shift ; + + my $buffer; + $self->smartReadExact(\$buffer, 4) + or return $self->TrailerError("Minimum header size is " . + 4 . " bytes") ; + + my $keep = $magic . $buffer ; + + my $size = unpack ("V", $buffer); + + $self->smartReadExact(\$buffer, $size) + or return $self->TrailerError("Minimum header size is " . + $size . " bytes") ; + + $keep .= $buffer ; + *$self->{HeaderPending} = $keep ; + + return STATUS_OK ; +} + + +sub skipCentralDirectory64Rec +{ + my $self = shift; + my $magic = shift ; + + my $buffer; + $self->smartReadExact(\$buffer, 8) + or return $self->TrailerError("Minimum header size is " . + 8 . " bytes") ; + + my $keep = $magic . $buffer ; + + my ($sizeLo, $sizeHi) = unpack ("V V", $buffer); + + # TODO - take SizeHi into account + $self->smartReadExact(\$buffer, $sizeLo) + or return $self->TrailerError("Minimum header size is " . + $sizeLo . " bytes") ; + + $keep .= $buffer ; + *$self->{HeaderPending} = $keep ; + + #my $versionMadeBy = unpack ("v", substr($buffer, 0, 2)); + #my $extractVersion = unpack ("v", substr($buffer, 2, 2)); + #my $diskNumber = unpack ("V", substr($buffer, 4, 4)); + #my $cntrlDirDiskNo = unpack ("V", substr($buffer, 8, 4)); + #my $entriesInThisCD = unpack ("V V", substr($buffer, 12, 8)); + #my $entriesInCD = unpack ("V V", substr($buffer, 20, 8)); + #my $sizeOfCD = unpack ("V V", substr($buffer, 28, 8)); + #my $offsetToCD = unpack ("V V", substr($buffer, 36, 8)); + + return STATUS_OK ; +} + +sub skipCentralDirectory64Loc +{ + my $self = shift; + my $magic = shift ; + + my $buffer; + $self->smartReadExact(\$buffer, 20 - 4) + or return $self->TrailerError("Minimum header size is " . + 20 . " bytes") ; + + my $keep = $magic . $buffer ; + *$self->{HeaderPending} = $keep ; + + #my $startCdDisk = unpack ("V", substr($buffer, 4-4, 4)); + #my $offsetToCD = unpack ("V V", substr($buffer, 8-4, 8)); + #my $diskCount = unpack ("V", substr($buffer, 16-4, 4)); + + return STATUS_OK ; +} + sub skipEndCentralDirectory { my $self = shift; @@ -351,14 +440,12 @@ sub skipEndCentralDirectory } - - sub _isZipMagic { my $buffer = shift ; return 0 if length $buffer < 4 ; my $sig = unpack("V", $buffer) ; - return $sig == 0x04034b50 ; + return $sig == ZIP_LOCAL_HDR_SIG ; } @@ -402,50 +489,79 @@ sub _readZipHeader($) my $compressedMethod = unpack ("v", substr($buffer, 8-4, 2)); my $lastModTime = unpack ("V", substr($buffer, 10-4, 4)); my $crc32 = unpack ("V", substr($buffer, 14-4, 4)); - my $compressedLength = unpack ("V", substr($buffer, 18-4, 4)); - my $uncompressedLength = unpack ("V", substr($buffer, 22-4, 4)); + my $compressedLength = new U64 unpack ("V", substr($buffer, 18-4, 4)); + my $uncompressedLength = new U64 unpack ("V", substr($buffer, 22-4, 4)); my $filename_length = unpack ("v", substr($buffer, 26-4, 2)); my $extra_length = unpack ("v", substr($buffer, 28-4, 2)); my $filename; my $extraField; - my $streamingMode = ($gpFlag & 0x08) ? 1 : 0 ; + my @EXTRA = (); + my $streamingMode = ($gpFlag & ZIP_GP_FLAG_STREAMING_MASK) ? 1 : 0 ; return $self->HeaderError("Streamed Stored content not supported") if $streamingMode && $compressedMethod == 0 ; *$self->{ZipData}{Streaming} = $streamingMode; - if (! $streamingMode) { - *$self->{ZipData}{Streaming} = 0; - *$self->{ZipData}{Crc32} = $crc32; - *$self->{ZipData}{CompressedLen} = $compressedLength; - *$self->{ZipData}{UnCompressedLen} = $uncompressedLength; - *$self->{CompressedInputLengthRemaining} = - *$self->{CompressedInputLength} = $compressedLength; - } - if ($filename_length) { $self->smartReadExact(\$filename, $filename_length) - or return $self->HeaderError("xxx"); + or return $self->TruncatedHeader("Filename"); $keep .= $filename ; } + my $zip64 = 0 ; + if ($extra_length) { $self->smartReadExact(\$extraField, $extra_length) - or return $self->HeaderError("xxx"); + or return $self->TruncatedHeader("Extra Field"); + + my $bad = IO::Compress::Zlib::Extra::parseRawExtra($extraField, + \@EXTRA, 1, 0); + return $self->HeaderError($bad) + if defined $bad; + $keep .= $extraField ; + + my %Extra ; + for (@EXTRA) + { + $Extra{$_->[0]} = \$_->[1]; + } + + if (defined $Extra{ZIP_EXTRA_ID_ZIP64()}) + { + $zip64 = 1 ; + + my $buff = ${ $Extra{ZIP_EXTRA_ID_ZIP64()} }; + + $uncompressedLength = U64::newUnpack_V64 substr($buff, 0, 8); + $compressedLength = U64::newUnpack_V64 substr($buff, 8, 8); + #my $cheaderOffset = U64::newUnpack_V64 substr($buff, 16, 8); + #my $diskNumber = unpack ("V", substr($buff, 24, 4)); + } + } + + *$self->{ZipData}{Zip64} = $zip64; + + if (! $streamingMode) { + *$self->{ZipData}{Streaming} = 0; + *$self->{ZipData}{Crc32} = $crc32; + *$self->{ZipData}{CompressedLen} = $compressedLength; + *$self->{ZipData}{UnCompressedLen} = $uncompressedLength; + *$self->{CompressedInputLengthRemaining} = + *$self->{CompressedInputLength} = $compressedLength->get32bit(); } *$self->{ZipData}{Method} = $compressedMethod; - if ($compressedMethod == 8) + if ($compressedMethod == ZIP_CM_DEFLATE) { *$self->{Type} = 'zip-deflate'; } - elsif ($compressedMethod == 12) + elsif ($compressedMethod == ZIP_CM_BZIP2) { #if (! defined $IO::Uncompress::Adapter::Bunzip2::VERSION) @@ -458,7 +574,7 @@ sub _readZipHeader($) *$self->{ZipData}{CRC32} = crc32(undef); } - elsif ($compressedMethod == 0) + elsif ($compressedMethod == ZIP_CM_STORE) { # TODO -- add support for reading uncompressed @@ -481,7 +597,8 @@ sub _readZipHeader($) 'FingerprintLength' => 4, #'HeaderLength' => $compressedMethod == 8 ? length $keep : 0, 'HeaderLength' => length $keep, - 'TrailerLength' => $streamingMode ? 16 : 0, + 'Zip64' => $zip64, + 'TrailerLength' => ! $streamingMode ? 0 : $zip64 ? 24 : 16, 'Header' => $keep, 'CompressedLength' => $compressedLength , 'UncompressedLength' => $uncompressedLength , @@ -491,11 +608,13 @@ sub _readZipHeader($) 'Stream' => $streamingMode, 'MethodID' => $compressedMethod, - 'MethodName' => $compressedMethod == 8 + 'MethodName' => $compressedMethod == ZIP_CM_DEFLATE ? "Deflated" - : $compressedMethod == 0 - ? "Stored" - : "Unknown" , + : $compressedMethod == ZIP_CM_BZIP2 + ? "Bzip2" + : $compressedMethod == ZIP_CM_STORE + ? "Stored" + : "Unknown" , # 'TextFlag' => $flag & GZIP_FLG_FTEXT ? 1 : 0, # 'HeaderCRCFlag' => $flag & GZIP_FLG_FHCRC ? 1 : 0, @@ -509,8 +628,8 @@ sub _readZipHeader($) # 'HeaderCRC' => $HeaderCRC, # 'Flags' => $flag, # 'ExtraFlags' => $xfl, -# 'ExtraFieldRaw' => $EXTRA, -# 'ExtraField' => [ @EXTRA ], + 'ExtraFieldRaw' => $extraField, + 'ExtraField' => [ @EXTRA ], } @@ -587,7 +706,8 @@ IO::Uncompress::Unzip - Read zip files/buffers $status = $z->inflateSync() - $z->trailingData() + $data = $z->trailingData() + $status = $z->nextStream() $data = $z->getHeaderInfo() $z->tell() $z->seek($position, $whence) @@ -770,9 +890,13 @@ If the C<$output> parameter is any other type, C will be returned. =head2 Notes -When C<$input> maps to multiple files/buffers and C<$output> is a single -file/buffer the uncompressed input files/buffers will all be stored -in C<$output> as a single uncompressed stream. + +When C<$input> maps to multiple compressed files/buffers and C<$output> is +a single file/buffer, after uncompression C<$output> will contain a +concatenation of all the uncompressed data from each of the input +files/buffers. + + @@ -784,7 +908,7 @@ L section below. =over 5 -=item AutoClose =E 0|1 +=item C<< AutoClose => 0|1 >> This option applies to any input or output data streams to C that are filehandles. @@ -796,8 +920,7 @@ completed. This parameter defaults to 0. - -=item BinModeOut =E 0|1 +=item C<< BinModeOut => 0|1 >> When writing to a file or filehandle, set C before writing to the file. @@ -808,15 +931,16 @@ Defaults to 0. -=item -Append =E 0|1 +=item C<< Append => 0|1 >> TODO -=item -MultiStream =E 0|1 +=item C<< MultiStream => 0|1 >> -Creates a new stream after each file. +If the input file/buffer contains multiple compressed data streams, this +option will uncompress the whole lot as a single data stream. -Defaults to 1. +Defaults to 0. @@ -938,7 +1062,7 @@ OPTS is a combination of the following options: =over 5 -=item -AutoClose =E 0|1 +=item C<< AutoClose => 0|1 >> This option is only valid when the C<$input> parameter is a filehandle. If specified, and the value is true, it will result in the file being closed once @@ -947,21 +1071,19 @@ destroyed. This parameter defaults to 0. -=item -MultiStream =E 0|1 +=item C<< MultiStream => 0|1 >> -Allows multiple concatenated compressed streams to be treated as a single -compressed stream. Decompression will stop once either the end of the -file/buffer is reached, an error is encountered (premature eof, corrupt -compressed data) or the end of a stream is not immediately followed by the -start of another stream. +Treats the complete zip file/buffer as a single compressed data +stream. When reading in multi-stream mode each member of the zip +file/buffer will be uncompressed in turn until the end of the file/buffer +is encountered. This parameter defaults to 0. - -=item -Prime =E $string +=item C<< Prime => $string >> This option will uncompress the contents of C<$string> before processing the input file/buffer. @@ -972,21 +1094,21 @@ data begins without having to read the first few bytes. If this is the case, the uncompression can be I with these bytes using this option. -=item -Transparent =E 0|1 +=item C<< Transparent => 0|1 >> If this option is set and the input file or buffer is not compressed data, the module will allow reading of it anyway. This option defaults to 1. -=item -BlockSize =E $num +=item C<< BlockSize => $num >> When reading the compressed input data, IO::Uncompress::Unzip will read it in blocks of C<$num> bytes. This option defaults to 4096. -=item -InputLength =E $size +=item C<< InputLength => $size >> When present this option will limit the number of compressed bytes read from the input file/buffer to C<$size>. This option can be used in the @@ -1002,7 +1124,7 @@ compressed data stream. This option defaults to off. -=item -Append =E 0|1 +=item C<< Append => 0|1 >> This option controls what the C method does with uncompressed data. @@ -1014,7 +1136,7 @@ will be overwritten by the uncompressed data. Defaults to 0. -=item -Strict =E 0|1 +=item C<< Strict => 0|1 >> @@ -1266,6 +1388,27 @@ underlying file will also be closed. +=head2 nextStream + +Usage is + + my $status = $z->nextStream(); + +Skips to the next compressed data stream in the input file/buffer. If a new +compressed data stream is found, the eof marker will be cleared, C<$.> will +be reset to 0. + +Returns 1 if a new stream was found, 0 if none was found, and -1 if an +error was encountered. + +=head2 trailingData + +Usage is + + my $data = $z->trailingData(); + +Returns any data that + =head1 Importing No symbolic constants are required by this IO::Uncompress::Unzip at present.