Update for IO::Uncompress::Base
[p5sagit/p5-mst-13.2.git] / ext / Compress / IO / Zlib / lib / IO / Uncompress / Unzip.pm
index 4c9d882..ae123c9 100644 (file)
@@ -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<undef> 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</"Constructor Options"> section below.
 
 =over 5
 
-=item AutoClose =E<gt> 0|1
+=item C<< AutoClose => 0|1 >>
 
 This option applies to any input or output data streams to 
 C<unzip> that are filehandles.
@@ -796,8 +920,7 @@ completed.
 This parameter defaults to 0.
 
 
-
-=item BinModeOut =E<gt> 0|1
+=item C<< BinModeOut => 0|1 >>
 
 When writing to a file or filehandle, set C<binmode> before writing to the
 file.
@@ -808,15 +931,16 @@ Defaults to 0.
 
 
 
-=item -Append =E<gt> 0|1
+=item C<< Append => 0|1 >>
 
 TODO
 
-=item -MultiStream =E<gt> 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<gt> 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<gt> 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<gt> $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<primed> with these bytes using this
 option.
 
-=item -Transparent =E<gt> 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<gt> $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<gt> $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<gt> 0|1
+=item C<< Append => 0|1 >>
 
 This option controls what the C<read> method does with uncompressed data.
 
@@ -1014,7 +1136,7 @@ will be overwritten by the uncompressed data.
 
 Defaults to 0.
 
-=item -Strict =E<gt> 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.