1 package IO::Uncompress::Unzip;
10 use IO::Uncompress::RawInflate ;
11 use Compress::Zlib::Common qw(createSelfTiedObject);
12 use UncompressPlugin::Identity;
16 our ($VERSION, @ISA, @EXPORT_OK, %EXPORT_TAGS, $UnzipError);
18 $VERSION = '2.000_05';
21 @ISA = qw(Exporter IO::Uncompress::RawInflate);
22 @EXPORT_OK = qw( $UnzipError unzip );
23 %EXPORT_TAGS = %IO::Uncompress::RawInflate::EXPORT_TAGS ;
24 push @{ $EXPORT_TAGS{all} }, @EXPORT_OK ;
25 Exporter::export_ok_tags('all');
31 my $obj = createSelfTiedObject($class, \$UnzipError);
32 $obj->_create(undef, 0, @_);
37 my $obj = createSelfTiedObject(undef, \$UnzipError);
38 return $obj->_inf(@_) ;
43 use Compress::Zlib::ParseParameters;
48 'Name' => [1, 1, Parse_any, undef],
50 # 'Streaming' => [1, 1, Parse_boolean, 1],
59 # unzip always needs crc32
60 $got->value('CRC32' => 1);
62 *$self->{UnzipData}{Name} = $got->value('Name');
73 $self->smartReadExact(\$magic, 4);
75 *$self->{HeaderPending} = $magic ;
77 return $self->HeaderError("Minimum header size is " .
79 if length $magic != 4 ;
81 return $self->HeaderError("Bad Magic")
82 if ! _isZipMagic($magic) ;
84 *$self->{Type} = 'zip';
96 my $name = *$self->{UnzipData}{Name} ;
97 my $status = $self->_readZipHeader($magic) ;
99 while (defined $status)
101 if (! defined $name || $status->{Name} eq $name)
107 my $c = $status->{CompressedLength};
109 $self->smartReadExact(\$buffer, $c)
110 or return $self->saveErrorString(undef, "Truncated file");
113 $c = $status->{TrailerLength};
114 $self->smartReadExact(\$buffer, $c)
115 or return $self->saveErrorString(undef, "Truncated file");
117 $self->chkTrailer($buffer)
118 or return $self->saveErrorString(undef, "Truncated file");
120 $status = $self->_readFullZipHeader();
122 return $self->saveErrorString(undef, "Cannot find '$name'")
123 if $self->smartEof();
134 my ($sig, $CRC32, $cSize, $uSize) ;
135 if (*$self->{ZipData}{Streaming}) {
136 ($sig, $CRC32, $cSize, $uSize) = unpack("V V V V", $trailer) ;
137 return $self->TrailerError("Data Descriptor signature")
138 if $sig != 0x08074b50;
141 ($CRC32, $cSize, $uSize) =
142 (*$self->{ZipData}{Crc32},
143 *$self->{ZipData}{CompressedLen},
144 *$self->{ZipData}{UnCompressedLen});
147 if (*$self->{Strict}) {
148 #return $self->TrailerError("CRC mismatch")
149 # if $CRC32 != *$self->{Uncomp}->crc32() ;
151 my $exp_isize = *$self->{Uncomp}->compressedBytes();
152 return $self->TrailerError("CSIZE mismatch. Got $cSize"
153 . ", expected $exp_isize")
154 if $cSize != $exp_isize ;
156 $exp_isize = *$self->{Uncomp}->uncompressedBytes();
157 return $self->TrailerError("USIZE mismatch. Got $uSize"
158 . ", expected $exp_isize")
159 if $uSize != $exp_isize ;
162 # check for central directory or end of central directory
166 $self->smartReadExact(\$magic, 4);
167 my $sig = unpack("V", $magic) ;
169 if ($sig == 0x02014b50)
171 $self->skipCentralDirectory($magic);
173 elsif ($sig == 0x06054b50)
175 $self->skipEndCentralDirectory($magic);
181 $self->pushBack($magic) ;
189 sub skipCentralDirectory
195 $self->smartReadExact(\$buffer, 46 - 4)
196 or return $self->HeaderError("Minimum header size is " .
199 my $keep = $magic . $buffer ;
200 *$self->{HeaderPending} = $keep ;
202 #my $versionMadeBy = unpack ("v", substr($buffer, 4-4, 2));
203 #my $extractVersion = unpack ("v", substr($buffer, 6-4, 2));
204 #my $gpFlag = unpack ("v", substr($buffer, 8-4, 2));
205 #my $compressedMethod = unpack ("v", substr($buffer, 10-4, 2));
206 #my $lastModTime = unpack ("V", substr($buffer, 12-4, 4));
207 #my $crc32 = unpack ("V", substr($buffer, 16-4, 4));
208 #my $compressedLength = unpack ("V", substr($buffer, 20-4, 4));
209 #my $uncompressedLength = unpack ("V", substr($buffer, 24-4, 4));
210 my $filename_length = unpack ("v", substr($buffer, 28-4, 2));
211 my $extra_length = unpack ("v", substr($buffer, 30-4, 2));
212 my $comment_length = unpack ("v", substr($buffer, 32-4, 2));
213 #my $disk_start = unpack ("v", substr($buffer, 34-4, 2));
214 #my $int_file_attrib = unpack ("v", substr($buffer, 36-4, 2));
215 #my $ext_file_attrib = unpack ("V", substr($buffer, 38-4, 2));
216 #my $lcl_hdr_offset = unpack ("V", substr($buffer, 42-4, 2));
222 if ($filename_length)
224 $self->smartReadExact(\$filename, $filename_length)
225 or return $self->HeaderError("xxx");
231 $self->smartReadExact(\$extraField, $extra_length)
232 or return $self->HeaderError("xxx");
233 $keep .= $extraField ;
238 $self->smartReadExact(\$comment, $comment_length)
239 or return $self->HeaderError("xxx");
246 sub skipEndCentralDirectory
252 $self->smartReadExact(\$buffer, 22 - 4)
253 or return $self->HeaderError("Minimum header size is " .
256 my $keep = $magic . $buffer ;
257 *$self->{HeaderPending} = $keep ;
259 #my $diskNumber = unpack ("v", substr($buffer, 4-4, 2));
260 #my $cntrlDirDiskNo = unpack ("v", substr($buffer, 6-4, 2));
261 #my $entriesInThisCD = unpack ("v", substr($buffer, 8-4, 2));
262 #my $entriesInCD = unpack ("v", substr($buffer, 10-4, 2));
263 #my $sizeOfCD = unpack ("V", substr($buffer, 12-4, 2));
264 #my $offsetToCD = unpack ("V", substr($buffer, 16-4, 2));
265 my $comment_length = unpack ("v", substr($buffer, 20-4, 2));
271 $self->smartReadExact(\$comment, $comment_length)
272 or return $self->HeaderError("xxx");
285 return 0 if length $buffer < 4 ;
286 my $sig = unpack("V", $buffer) ;
287 return $sig == 0x04034b50 ;
291 sub _readFullZipHeader($)
296 $self->smartReadExact(\$magic, 4);
298 *$self->{HeaderPending} = $magic ;
300 return $self->HeaderError("Minimum header size is " .
302 if length $magic != 4 ;
305 return $self->HeaderError("Bad Magic")
306 if ! _isZipMagic($magic) ;
308 my $status = $self->_readZipHeader($magic);
309 delete *$self->{Transparent} if ! defined $status ;
313 sub _readZipHeader($)
315 my ($self, $magic) = @_ ;
319 $self->smartReadExact(\$buffer, 30 - 4)
320 or return $self->HeaderError("Minimum header size is " .
323 my $keep = $magic . $buffer ;
324 *$self->{HeaderPending} = $keep ;
326 my $extractVersion = unpack ("v", substr($buffer, 4-4, 2));
327 my $gpFlag = unpack ("v", substr($buffer, 6-4, 2));
328 my $compressedMethod = unpack ("v", substr($buffer, 8-4, 2));
329 my $lastModTime = unpack ("V", substr($buffer, 10-4, 4));
330 my $crc32 = unpack ("V", substr($buffer, 14-4, 4));
331 my $compressedLength = unpack ("V", substr($buffer, 18-4, 4));
332 my $uncompressedLength = unpack ("V", substr($buffer, 22-4, 4));
333 my $filename_length = unpack ("v", substr($buffer, 26-4, 2));
334 my $extra_length = unpack ("v", substr($buffer, 28-4, 2));
338 my $streamingMode = ($gpFlag & 0x08) ? 1 : 0 ;
340 return $self->HeaderError("Streamed Stored content not supported")
341 if $streamingMode && $compressedMethod == 0 ;
343 *$self->{ZipData}{Streaming} = $streamingMode;
345 if (! $streamingMode) {
346 *$self->{ZipData}{Streaming} = 0;
347 *$self->{ZipData}{Crc32} = $crc32;
348 *$self->{ZipData}{CompressedLen} = $compressedLength;
349 *$self->{ZipData}{UnCompressedLen} = $uncompressedLength;
352 if ($filename_length)
354 $self->smartReadExact(\$filename, $filename_length)
355 or return $self->HeaderError("xxx");
361 $self->smartReadExact(\$extraField, $extra_length)
362 or return $self->HeaderError("xxx");
363 $keep .= $extraField ;
366 *$self->{CompressedInputLengthRemaining} =
367 *$self->{CompressedInputLength} = $compressedLength;
369 if ($compressedMethod == 8)
371 *$self->{Type} = 'zip';
373 elsif ($compressedMethod == 0)
375 # TODO -- add support for reading uncompressed
377 *$self->{Type} = 'zipStored';
379 my $obj = UncompressPlugin::Identity::mkUncompObject(# $got->value('CRC32'),
380 # $got->value('ADLER32'),
383 *$self->{Uncomp} = $obj;
388 return $self->HeaderError("Unsupported Compression format $compressedMethod");
393 'FingerprintLength' => 2,
394 #'HeaderLength' => $compressedMethod == 8 ? length $keep : 0,
395 'HeaderLength' => length $keep,
396 'TrailerLength' => $streamingMode ? 16 : 0,
398 'CompressedLength' => $compressedLength ,
399 'UncompressedLength' => $uncompressedLength ,
402 'Time' => _dosToUnixTime($lastModTime),
403 'Stream' => $streamingMode,
405 'MethodID' => $compressedMethod,
406 'MethodName' => $compressedMethod == 8
408 : $compressedMethod == 0
412 # 'TextFlag' => $flag & GZIP_FLG_FTEXT ? 1 : 0,
413 # 'HeaderCRCFlag' => $flag & GZIP_FLG_FHCRC ? 1 : 0,
414 # 'NameFlag' => $flag & GZIP_FLG_FNAME ? 1 : 0,
415 # 'CommentFlag' => $flag & GZIP_FLG_FCOMMENT ? 1 : 0,
416 # 'ExtraFlag' => $flag & GZIP_FLG_FEXTRA ? 1 : 0,
417 # 'Comment' => $comment,
419 # 'OsName' => defined $GZIP_OS_Names{$os}
420 # ? $GZIP_OS_Names{$os} : "Unknown",
421 # 'HeaderCRC' => $HeaderCRC,
423 # 'ExtraFlags' => $xfl,
424 # 'ExtraFieldRaw' => $EXTRA,
425 # 'ExtraField' => [ @EXTRA ],
434 #use Time::Local 'timelocal_nocheck';
435 use Time::Local 'timelocal';
439 my $year = ( ( $dt >> 25 ) & 0x7f ) + 80;
440 my $mon = ( ( $dt >> 21 ) & 0x0f ) - 1;
441 my $mday = ( ( $dt >> 16 ) & 0x1f );
443 my $hour = ( ( $dt >> 11 ) & 0x1f );
444 my $min = ( ( $dt >> 5 ) & 0x3f );
445 my $sec = ( ( $dt << 1 ) & 0x3e );
449 eval { timelocal( $sec, $min, $hour, $mday, $mon, $year ); };