File Coverage

blib/lib/IO/Compress/Zip.pm
Criterion Covered Total %
statement 359 424 84.6
branch 106 180 58.8
condition 29 63 46.0
subroutine 32 35 91.4
pod 2 19 10.5
total 528 721 73.2


line stmt bran cond sub pod time code
1             package IO::Compress::Zip ;
2              
3 47     47   120903 use strict ;
  47         111  
  47         1714  
4 47     47   249 use warnings;
  47         86  
  47         2042  
5 47     47   17908 use bytes;
  47         19941  
  47         318  
6              
7 47     47   22028 use IO::Compress::Base::Common 2.220 qw(:Status );
  47         1183  
  47         8380  
8 47     47   29989 use IO::Compress::RawDeflate 2.220 ();
  47         1505  
  47         2123  
9 47     47   310 use IO::Compress::Adapter::Deflate 2.220 ;
  47         780  
  47         10630  
10 47     47   25276 use IO::Compress::Adapter::Identity 2.220 ;
  47         1106  
  47         1915  
11 47     47   19976 use IO::Compress::Zlib::Extra 2.220 ;
  47         901  
  47         1905  
12 47     47   20664 use IO::Compress::Zip::Constants 2.220 ;
  47         956  
  47         10799  
13              
14 47     47   362 use File::Spec();
  47         98  
  47         1021  
15 47     47   198 use Config;
  47         2220  
  47         2588  
16              
17 47     47   2222 use Compress::Raw::Zlib 2.218 ();
  47         828  
  47         13366  
18              
19             BEGIN
20             {
21 47     47   137 eval { require IO::Compress::Adapter::Bzip2 ;
  47         22924  
22 47         1186 IO::Compress::Adapter::Bzip2->VERSION( 2.218 );
23 47         25175 require IO::Compress::Bzip2 ;
24 47         1325 IO::Compress::Bzip2->VERSION( 2.218 );
25             } ;
26              
27 47         122 eval { require IO::Compress::Adapter::Lzma ;
  47         23939  
28 0         0 IO::Compress::Adapter::Lzma->VERSION( 2.217 );
29 0         0 require IO::Compress::Lzma ;
30 0         0 IO::Compress::Lzma->VERSION( 2.217 );
31             } ;
32              
33 47         21164 eval { require IO::Compress::Adapter::Xz ;
  47         22786  
34 0         0 IO::Compress::Adapter::Xz->VERSION( 2.217 );
35 0         0 require IO::Compress::Xz ;
36 0         0 IO::Compress::Xz->VERSION( 2.217 );
37             } ;
38 47         20327 eval { require IO::Compress::Adapter::Zstd ;
  47         189911  
39 0         0 IO::Compress::Adapter::Zstd->VERSION( 2.217 );
40 0         0 require IO::Compress::Zstd ;
41 0         0 IO::Compress::Zstd->VERSION( 2.217 );
42             } ;
43             }
44              
45              
46             require Exporter ;
47              
48             our ($VERSION, @ISA, @EXPORT_OK, %EXPORT_TAGS, %DEFLATE_CONSTANTS, $ZipError);
49              
50             $VERSION = '2.220';
51             $ZipError = '';
52              
53             @ISA = qw(IO::Compress::RawDeflate Exporter);
54             @EXPORT_OK = qw( $ZipError zip ) ;
55             %EXPORT_TAGS = %IO::Compress::RawDeflate::DEFLATE_CONSTANTS ;
56              
57             $EXPORT_TAGS{all} = [ defined $EXPORT_TAGS{all} ? @{ $EXPORT_TAGS{all} } : (), @EXPORT_OK ] ;
58              
59             $EXPORT_TAGS{zip_method} = [qw( ZIP_CM_STORE ZIP_CM_DEFLATE ZIP_CM_BZIP2 ZIP_CM_LZMA ZIP_CM_XZ ZIP_CM_ZSTD)];
60             push @{ $EXPORT_TAGS{all} }, @{ $EXPORT_TAGS{zip_method} };
61              
62             Exporter::export_ok_tags('all');
63              
64             sub new
65             {
66 151     151 1 916327 my $class = shift ;
67              
68 151         966 my $obj = IO::Compress::Base::Common::createSelfTiedObject($class, \$ZipError);
69 151         939 $obj->_create(undef, @_);
70              
71             }
72              
73             sub zip
74             {
75 225     225 1 9753831 my $obj = IO::Compress::Base::Common::createSelfTiedObject(undef, \$ZipError);
76 225         1427 return $obj->_def(@_);
77             }
78              
79             sub isMethodAvailable
80             {
81 3     3 0 286 my $method = shift;
82              
83             # Store & Deflate are always available
84 3 100 100     26 return 1
85             if $method == ZIP_CM_STORE || $method == ZIP_CM_DEFLATE ;
86              
87             return 1
88             if $method == ZIP_CM_BZIP2 &&
89             defined $IO::Compress::Adapter::Bzip2::VERSION &&
90 1 0 33     7 defined &{ "IO::Compress::Adapter::Bzip2::mkRawZipCompObject" };
  0   33     0  
91              
92             return 1
93             if $method == ZIP_CM_LZMA &&
94             defined $IO::Compress::Adapter::Lzma::VERSION &&
95 1 0 33     6 defined &{ "IO::Compress::Adapter::Lzma::mkRawZipCompObject" };
  0   33     0  
96              
97             return 1
98             if $method == ZIP_CM_XZ &&
99             defined $IO::Compress::Adapter::Xz::VERSION &&
100 1 0 33     5 defined &{ "IO::Compress::Adapter::Xz::mkRawZipCompObject" };
  0   33     0  
101              
102             return 1
103             if $method == ZIP_CM_ZSTD &&
104             defined $IO::Compress::Adapter::ZSTD::VERSION &&
105 1 0 33     5 defined &{ "IO::Compress::Adapter::ZSTD::mkRawZipCompObject" };
  0   33     0  
106              
107 1         6 return 0;
108             }
109              
110             sub beforePayload
111             {
112 411     411 0 827 my $self = shift ;
113              
114 411 50       1703 if (*$self->{ZipData}{Sparse} ) {
115 0         0 my $inc = 1024 * 100 ;
116 0         0 my $NULLS = ("\x00" x $inc) ;
117 0         0 my $sparse = *$self->{ZipData}{Sparse} ;
118 0         0 *$self->{CompSize}->add( $sparse );
119 0         0 *$self->{UnCompSize}->add( $sparse );
120              
121 0         0 *$self->{FH}->seek($sparse, IO::Handle::SEEK_CUR);
122              
123             *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32($NULLS, *$self->{ZipData}{CRC32})
124 0         0 for 1 .. int $sparse / $inc;
125             *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(substr($NULLS, 0, $sparse % $inc),
126             *$self->{ZipData}{CRC32})
127 0 0       0 if $sparse % $inc;
128             }
129             }
130              
131             sub mkComp
132             {
133 413     413 0 868 my $self = shift ;
134 413         804 my $got = shift ;
135              
136 413         1086 my ($obj, $errstr, $errno) ;
137              
138 413 100       2350 if (*$self->{ZipData}{Method} == ZIP_CM_STORE) {
    100          
    50          
    0          
    0          
    0          
139 36         116 ($obj, $errstr, $errno) = IO::Compress::Adapter::Identity::mkCompObject(
140             $got->getValue('level'),
141             $got->getValue('strategy')
142             );
143 36         306 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(undef);
144             }
145             elsif (*$self->{ZipData}{Method} == ZIP_CM_DEFLATE) {
146 356         1151 ($obj, $errstr, $errno) = IO::Compress::Adapter::Deflate::mkCompObject(
147             $got->getValue('crc32'),
148             $got->getValue('adler32'),
149             $got->getValue('level'),
150             $got->getValue('strategy')
151             );
152             }
153             elsif (*$self->{ZipData}{Method} == ZIP_CM_BZIP2) {
154 21         73 ($obj, $errstr, $errno) = IO::Compress::Adapter::Bzip2::mkCompObject(
155             $got->getValue('blocksize100k'),
156             $got->getValue('workfactor'),
157             $got->getValue('verbosity')
158             );
159 21         221 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(undef);
160             }
161             elsif (*$self->{ZipData}{Method} == ZIP_CM_LZMA) {
162 0         0 ($obj, $errstr, $errno) = IO::Compress::Adapter::Lzma::mkRawZipCompObject($got->getValue('preset'),
163             $got->getValue('extreme'),
164             );
165 0         0 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(undef);
166             }
167             elsif (*$self->{ZipData}{Method} == ZIP_CM_XZ) {
168 0         0 ($obj, $errstr, $errno) = IO::Compress::Adapter::Xz::mkCompObject($got->getValue('preset'),
169             $got->getValue('extreme'),
170             0
171             );
172 0         0 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(undef);
173             }
174             elsif (*$self->{ZipData}{Method} == ZIP_CM_ZSTD) {
175 0 0       0 ($obj, $errstr, $errno) = IO::Compress::Adapter::Zstd::mkCompObject(defined $got->getValue('level') ? $got->getValue('level') : 3,
176             );
177 0         0 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(undef);
178             }
179              
180 413 50       1613 return $self->saveErrorString(undef, $errstr, $errno)
181             if ! defined $obj;
182              
183 413 100       2001 if (! defined *$self->{ZipData}{SizesOffset}) {
184 342         1171 *$self->{ZipData}{SizesOffset} = 0;
185 342         3042 *$self->{ZipData}{Offset} = U64->new();
186             }
187              
188             *$self->{ZipData}{AnyZip64} = 0
189 413 100       1996 if ! defined *$self->{ZipData}{AnyZip64} ;
190              
191 413         4821 return $obj;
192             }
193              
194             sub reset
195             {
196 0     0 0 0 my $self = shift ;
197              
198 0         0 *$self->{Compress}->reset();
199 0         0 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32('');
200              
201 0         0 return STATUS_OK;
202             }
203              
204             sub filterUncompressed
205             {
206 393     393 0 747 my $self = shift ;
207              
208 393 100       1454 if (*$self->{ZipData}{Method} == ZIP_CM_DEFLATE) {
209 341         1769 *$self->{ZipData}{CRC32} = *$self->{Compress}->crc32();
210             }
211             else {
212 52         91 *$self->{ZipData}{CRC32} = Compress::Raw::Zlib::crc32(${$_[0]}, *$self->{ZipData}{CRC32});
  52         463  
213              
214             }
215             }
216              
217             sub canonicalName
218             {
219             # This sub is derived from Archive::Zip::_asZipDirName
220              
221             # Return the normalized name as used in a zip file (path
222             # separators become slashes, etc.).
223             # Will translate internal slashes in path components (i.e. on Macs) to
224             # underscores. Discards volume names.
225             # When $forceDir is set, returns paths with trailing slashes
226             #
227             # input output
228             # . '.'
229             # ./a a
230             # ./a/b a/b
231             # ./a/b/ a/b
232             # a/b/ a/b
233             # /a/b/ a/b
234             # c:\a\b\c.doc a/b/c.doc # on Windows
235             # "i/o maps:whatever" i_o maps/whatever # on Macs
236              
237 2     2 0 4 my $name = shift;
238 2         4 my $forceDir = shift ;
239              
240 2         62 my ( $volume, $directories, $file ) =
241             File::Spec->splitpath( File::Spec->canonpath($name), $forceDir );
242              
243 2         18 my @dirs = map { $_ =~ s{/}{_}g; $_ }
  6         13  
  6         12  
244             File::Spec->splitdir($directories);
245              
246 2 50       34 if ( @dirs > 0 ) { pop (@dirs) if $dirs[-1] eq '' } # remove empty component
  2 50       10  
247 2 50       10 push @dirs, defined($file) ? $file : '' ;
248              
249 2         9 my $normalised_path = join '/', @dirs;
250              
251             # Leading directory separators should not be stored in zip archives.
252             # Example:
253             # C:\a\b\c\ a/b/c
254             # C:\a\b\c.txt a/b/c.txt
255             # /a/b/c/ a/b/c
256             # /a/b/c.txt a/b/c.txt
257 2         9 $normalised_path =~ s{^/}{}; # remove leading separator
258              
259 2         9 return $normalised_path;
260             }
261              
262              
263             sub mkHeader
264             {
265 412     412 0 1243 my $self = shift;
266 412         907 my $param = shift ;
267              
268 412         2212 *$self->{ZipData}{LocalHdrOffset} = U64::clone(*$self->{ZipData}{Offset});
269              
270 412         1531 my $comment = '';
271 412         1936 $comment = $param->valueOrDefault('comment') ;
272              
273 412         919 my $filename = '';
274 412         1046 $filename = $param->valueOrDefault('name') ;
275              
276 412 100 100     1746 $filename = canonicalName($filename)
277             if length $filename && $param->getValue('canonicalname') ;
278              
279 412 100       1482 if (defined *$self->{ZipData}{FilterName} ) {
280 3         12 local *_ = \$filename ;
281 3         6 &{ *$self->{ZipData}{FilterName} }() ;
  3         16  
282             }
283              
284 412 100 66     1471 if ( $param->getValue('efs') && $] >= 5.008004) {
285 5 50       11 if (length $filename) {
286 5 100       333 utf8::downgrade($filename, 1)
287             or Carp::croak "Wide character in zip filename";
288             }
289              
290 4 50       6 if (length $comment) {
291 0 0       0 utf8::downgrade($comment, 1)
292             or Carp::croak "Wide character in zip comment";
293             }
294             }
295              
296 411         857 my $hdr = '';
297              
298 411         1251 my $time = _unixToDosTime($param->getValue('time'));
299              
300 411         843 my $extra = '';
301 411         1057 my $ctlExtra = '';
302 411         868 my $empty = 0;
303 411         1319 my $osCode = $param->getValue('os_code') ;
304 411         883 my $extFileAttr = 0 ;
305              
306             # This code assumes Unix.
307             # TODO - revisit this
308 411 50       1537 $extFileAttr = 0100644 << 16
309             if $osCode == ZIP_OS_CODE_UNIX ;
310              
311 411 100       1827 if (*$self->{ZipData}{Zip64}) {
312 26         111 $empty = IO::Compress::Base::Common::MAX32;
313              
314 26         89 my $x = '';
315 26         57 $x .= pack "V V", 0, 0 ; # uncompressedLength
316 26         63 $x .= pack "V V", 0, 0 ; # compressedLength
317              
318             # Zip64 needs to be first in extra field to workaround a Windows Explorer Bug
319             # See http://www.info-zip.org/phpBB3/viewtopic.php?f=3&t=440 for details
320 26         134 $extra .= IO::Compress::Zlib::Extra::mkSubField(ZIP_EXTRA_ID_ZIP64, $x);
321             }
322              
323 411 50       1233 if (! $param->getValue('minimal')) {
324 411 100       1180 if ($param->parsed('mtime'))
325             {
326 113         317 $extra .= mkExtendedTime($param->getValue('mtime'),
327             $param->getValue('atime'),
328             $param->getValue('ctime'));
329              
330 113         415 $ctlExtra .= mkExtendedTime($param->getValue('mtime'));
331             }
332              
333 411 50       1417 if ( $osCode == ZIP_OS_CODE_UNIX )
334             {
335 411 100       1284 if ( $param->getValue('want_exunixn') )
336             {
337 113         246 my $ux3 = mkUnixNExtra( @{ $param->getValue('want_exunixn') });
  113         287  
338 113         246 $extra .= $ux3;
339 113         364 $ctlExtra .= $ux3;
340             }
341              
342 411 50       1251 if ( $param->getValue('exunix2') )
343             {
344 0         0 $extra .= mkUnix2Extra( @{ $param->getValue('exunix2') });
  0         0  
345 0         0 $ctlExtra .= mkUnix2Extra();
346             }
347             }
348              
349 411 50       1201 $extFileAttr = $param->getValue('extattr')
350             if defined $param->getValue('extattr') ;
351              
352 411 50       1135 $extra .= $param->getValue('extrafieldlocal')
353             if defined $param->getValue('extrafieldlocal');
354              
355 411 50       1064 $ctlExtra .= $param->getValue('extrafieldcentral')
356             if defined $param->getValue('extrafieldcentral');
357             }
358              
359 411         1452 my $method = *$self->{ZipData}{Method} ;
360 411         749 my $gpFlag = 0 ;
361             $gpFlag |= ZIP_GP_FLAG_STREAMING_MASK
362 411 100       1523 if *$self->{ZipData}{Stream} ;
363              
364 411 50       1321 $gpFlag |= ZIP_GP_FLAG_LZMA_EOS_PRESENT
365             if $method == ZIP_CM_LZMA ;
366              
367 411 50 33     1141 $gpFlag |= ZIP_GP_FLAG_LANGUAGE_ENCODING
      66        
368             if $param->getValue('efs') && (length($filename) || length($comment));
369              
370 411         1277 my $version = $ZIP_CM_MIN_VERSIONS{$method};
371             $version = ZIP64_MIN_VERSION
372 411 100 100     2449 if ZIP64_MIN_VERSION > $version && *$self->{ZipData}{Zip64};
373              
374 411         1295 my $madeBy = ($param->getValue('os_code') << 8) + $version;
375 411         755 my $extract = $version;
376              
377 411         1322 *$self->{ZipData}{Version} = $version;
378 411         1253 *$self->{ZipData}{MadeBy} = $madeBy;
379              
380 411         866 my $ifa = 0;
381 411 100       1137 $ifa |= ZIP_IFA_TEXT_MASK
382             if $param->getValue('textflag');
383              
384 411         1171 $hdr .= pack "V", ZIP_LOCAL_HDR_SIG ; # signature
385 411         1793 $hdr .= pack 'v', $extract ; # extract Version & OS
386 411         935 $hdr .= pack 'v', $gpFlag ; # general purpose flag (set streaming mode)
387 411         973 $hdr .= pack 'v', $method ; # compression method (deflate)
388 411         1095 $hdr .= pack 'V', $time ; # last mod date/time
389 411         1131 $hdr .= pack 'V', 0 ; # crc32 - 0 when streaming
390 411         1022 $hdr .= pack 'V', $empty ; # compressed length - 0 when streaming
391 411         1002 $hdr .= pack 'V', $empty ; # uncompressed length - 0 when streaming
392 411         1107 $hdr .= pack 'v', length $filename ; # filename length
393 411         1142 $hdr .= pack 'v', length $extra ; # extra length
394              
395 411         979 $hdr .= $filename ;
396              
397             # Remember the offset for the compressed & uncompressed lengths in the
398             # local header.
399 411 100       1470 if (*$self->{ZipData}{Zip64}) {
400             *$self->{ZipData}{SizesOffset} = *$self->{ZipData}{Offset}->get64bit()
401 26         129 + length($hdr) + 4 ;
402             }
403             else {
404             *$self->{ZipData}{SizesOffset} = *$self->{ZipData}{Offset}->get64bit()
405 385         1828 + 18;
406             }
407              
408 411         1142 $hdr .= $extra ;
409              
410              
411 411         1021 my $ctl = '';
412              
413 411         946 $ctl .= pack "V", ZIP_CENTRAL_HDR_SIG ; # signature
414 411         1150 $ctl .= pack 'v', $madeBy ; # version made by
415 411         1096 $ctl .= pack 'v', $extract ; # extract Version
416 411         964 $ctl .= pack 'v', $gpFlag ; # general purpose flag (streaming mode)
417 411         920 $ctl .= pack 'v', $method ; # compression method (deflate)
418 411         2498 $ctl .= pack 'V', $time ; # last mod date/time
419 411         913 $ctl .= pack 'V', 0 ; # crc32
420 411         1096 $ctl .= pack 'V', $empty ; # compressed length
421 411         1019 $ctl .= pack 'V', $empty ; # uncompressed length
422 411         993 $ctl .= pack 'v', length $filename ; # filename length
423              
424 411         1249 *$self->{ZipData}{ExtraOffset} = length $ctl;
425 411         1205 *$self->{ZipData}{ExtraSize} = length $ctlExtra ;
426              
427 411         1202 $ctl .= pack 'v', length $ctlExtra ; # extra length
428 411         1129 $ctl .= pack 'v', length $comment ; # file comment length
429 411         825 $ctl .= pack 'v', 0 ; # disk number start
430 411         941 $ctl .= pack 'v', $ifa ; # internal file attributes
431 411         928 $ctl .= pack 'V', $extFileAttr ; # external file attributes
432              
433             # offset to local hdr
434 411 50       1952 if (*$self->{ZipData}{LocalHdrOffset}->is64bit() ) {
435 0         0 $ctl .= pack 'V', IO::Compress::Base::Common::MAX32 ;
436             }
437             else {
438 411         1456 $ctl .= *$self->{ZipData}{LocalHdrOffset}->getPacked_V32() ;
439             }
440              
441 411         1001 $ctl .= $filename ;
442              
443 411         1931 *$self->{ZipData}{Offset}->add32(length $hdr) ;
444              
445 411         1838 *$self->{ZipData}{CentralHeader} = [ $ctl, $ctlExtra, $comment];
446              
447 411         2078 return $hdr;
448             }
449              
450             sub mkTrailer
451             {
452 411     411 0 734 my $self = shift ;
453              
454 411         755 my $crc32 ;
455 411 100       1559 if (*$self->{ZipData}{Method} == ZIP_CM_DEFLATE) {
456 354         1558 $crc32 = pack "V", *$self->{Compress}->crc32();
457             }
458             else {
459 57         255 $crc32 = pack "V", *$self->{ZipData}{CRC32};
460             }
461              
462 411         837 my ($ctl, $ctlExtra, $comment) = @{ *$self->{ZipData}{CentralHeader} };
  411         1696  
463              
464 411         826 my $sizes ;
465 411 100       1380 if (! *$self->{ZipData}{Zip64}) {
466 385         1378 $sizes .= *$self->{CompSize}->getPacked_V32() ; # Compressed size
467 385         1337 $sizes .= *$self->{UnCompSize}->getPacked_V32() ; # Uncompressed size
468             }
469             else {
470 26         118 $sizes .= *$self->{CompSize}->getPacked_V64() ; # Compressed size
471 26         99 $sizes .= *$self->{UnCompSize}->getPacked_V64() ; # Uncompressed size
472             }
473              
474 411         1045 my $data = $crc32 . $sizes ;
475              
476 411         1505 my $xtrasize = *$self->{UnCompSize}->getPacked_V64() ; # Uncompressed size
477 411         1171 $xtrasize .= *$self->{CompSize}->getPacked_V64() ; # Compressed size
478              
479 411         922 my $hdr = '';
480              
481 411 100       1310 if (*$self->{ZipData}{Stream}) {
482 346         756 $hdr = pack "V", ZIP_DATA_HDR_SIG ; # signature
483 346         831 $hdr .= $data ;
484             }
485             else {
486 65 50       240 $self->writeAt(*$self->{ZipData}{LocalHdrOffset}->get64bit() + 14, $crc32)
487             or return undef;
488             $self->writeAt(*$self->{ZipData}{SizesOffset},
489 65 100       430 *$self->{ZipData}{Zip64} ? $xtrasize : $sizes)
    50          
490             or return undef;
491             }
492              
493             # Central Header Record/Zip64 extended field
494              
495 411         1357 substr($ctl, 16, length $crc32) = $crc32 ;
496              
497 411         865 my $zip64Payload = '';
498              
499             # uncompressed length - only set zip64 if needed
500 411 50       1606 if (*$self->{UnCompSize}->isAlmost64bit()) { # || *$self->{ZipData}{Zip64}) {
501 0         0 $zip64Payload .= *$self->{UnCompSize}->getPacked_V64() ;
502             } else {
503 411         1291 substr($ctl, 24, 4) = *$self->{UnCompSize}->getPacked_V32() ;
504             }
505              
506             # compressed length - only set zip64 if needed
507 411 50       1384 if (*$self->{CompSize}->isAlmost64bit()) { # || *$self->{ZipData}{Zip64}) {
508 0         0 $zip64Payload .= *$self->{CompSize}->getPacked_V64() ;
509             } else {
510 411         1164 substr($ctl, 20, 4) = *$self->{CompSize}->getPacked_V32() ;
511             }
512              
513             # Local Header offset
514             $zip64Payload .= *$self->{ZipData}{LocalHdrOffset}->getPacked_V64()
515 411 50       1575 if *$self->{ZipData}{LocalHdrOffset}->is64bit() ;
516              
517             # disk no - always zero, so don't need to include it.
518             #$zip64Payload .= pack "V", 0 ;
519              
520 411         1056 my $zip64Xtra = '';
521              
522 411 50       1228 if (length $zip64Payload) {
523 0         0 $zip64Xtra = IO::Compress::Zlib::Extra::mkSubField(ZIP_EXTRA_ID_ZIP64, $zip64Payload);
524              
525             substr($ctl, *$self->{ZipData}{ExtraOffset}, 2) =
526 0         0 pack 'v', *$self->{ZipData}{ExtraSize} + length $zip64Xtra;
527              
528 0         0 *$self->{ZipData}{AnyZip64} = 1;
529             }
530              
531             # Zip64 needs to be first in extra field to workaround a Windows Explorer Bug
532             # See http://www.info-zip.org/phpBB3/viewtopic.php?f=3&t=440 for details
533 411         1127 $ctl .= $zip64Xtra . $ctlExtra . $comment;
534              
535 411         1881 *$self->{ZipData}{Offset}->add32(length($hdr));
536 411         1759 *$self->{ZipData}{Offset}->add( *$self->{CompSize} );
537 411         792 push @{ *$self->{ZipData}{CentralDir} }, $ctl ;
  411         1971  
538              
539 411         1582 return $hdr;
540             }
541              
542             sub mkFinalTrailer
543             {
544 345     345 0 627 my $self = shift ;
545              
546 345         784 my $comment = '';
547 345         1229 $comment = *$self->{ZipData}{ZipComment} ;
548              
549 345         2464 my $cd_offset = *$self->{ZipData}{Offset}->get32bit() ; # offset to start central dir
550              
551 345         865 my $entries = @{ *$self->{ZipData}{CentralDir} };
  345         984  
552              
553             *$self->{ZipData}{AnyZip64} = 1
554 345 50 33     1326 if *$self->{ZipData}{Offset}->is64bit || $entries >= 0xFFFF ;
555              
556 345         771 my $cd = join '', @{ *$self->{ZipData}{CentralDir} };
  345         1399  
557 345         701 my $cd_len = length $cd ;
558              
559 345         782 my $z64e = '';
560              
561 345 100       1253 if ( *$self->{ZipData}{AnyZip64} ) {
562              
563 21         72 my $v = *$self->{ZipData}{Version} ;
564 21         61 my $mb = *$self->{ZipData}{MadeBy} ;
565 21         70 $z64e .= pack 'v', $mb ; # Version made by
566 21         54 $z64e .= pack 'v', $v ; # Version to extract
567 21         71 $z64e .= pack 'V', 0 ; # number of disk
568 21         46 $z64e .= pack 'V', 0 ; # number of disk with central dir
569 21         81 $z64e .= U64::pack_V64 $entries ; # entries in central dir on this disk
570 21         70 $z64e .= U64::pack_V64 $entries ; # entries in central dir
571 21         68 $z64e .= U64::pack_V64 $cd_len ; # size of central dir
572 21         98 $z64e .= *$self->{ZipData}{Offset}->getPacked_V64() ; # offset to start central dir
573             $z64e .= *$self->{ZipData}{extrafieldzip64} # optional extra field
574 21 50       142 if defined *$self->{ZipData}{extrafieldzip64} ;
575              
576 21         70 $z64e = pack("V", ZIP64_END_CENTRAL_REC_HDR_SIG) # signature
577             . U64::pack_V64(length $z64e)
578             . $z64e ;
579              
580 21         144 *$self->{ZipData}{Offset}->add32(length $cd) ;
581              
582 21         38 $z64e .= pack "V", ZIP64_END_CENTRAL_LOC_HDR_SIG; # signature
583 21         65 $z64e .= pack 'V', 0 ; # number of disk with central dir
584 21         73 $z64e .= *$self->{ZipData}{Offset}->getPacked_V64() ; # offset to end zip64 central dir
585 21         51 $z64e .= pack 'V', 1 ; # Total number of disks
586              
587 21         43 $cd_offset = IO::Compress::Base::Common::MAX32 ;
588 21 50       66 $cd_len = IO::Compress::Base::Common::MAX32 if IO::Compress::Base::Common::isGeMax32 $cd_len ;
589 21 50       71 $entries = 0xFFFF if $entries >= 0xFFFF ;
590             }
591              
592 345         718 my $ecd = '';
593 345         702 $ecd .= pack "V", ZIP_END_CENTRAL_HDR_SIG ; # signature
594 345         699 $ecd .= pack 'v', 0 ; # number of disk
595 345         574 $ecd .= pack 'v', 0 ; # number of disk with central dir
596 345         1109 $ecd .= pack 'v', $entries ; # entries in central dir on this disk
597 345         805 $ecd .= pack 'v', $entries ; # entries in central dir
598 345         887 $ecd .= pack 'V', $cd_len ; # size of central dir
599 345         805 $ecd .= pack 'V', $cd_offset ; # offset to start central dir
600 345         784 $ecd .= pack 'v', length $comment ; # zipfile comment length
601 345         677 $ecd .= $comment;
602              
603 345         1751 return $cd . $z64e . $ecd ;
604             }
605              
606             sub ckParams
607             {
608 414     414 0 842 my $self = shift ;
609 414         888 my $got = shift;
610              
611 414         1743 $got->setValue('crc32' => 1);
612              
613 414 100       1188 if (! $got->parsed('time') ) {
614             # Modification time defaults to now.
615 325         1105 $got->setValue('time' => time) ;
616             }
617              
618 414 50       1255 if ($got->parsed('extime') ) {
619 0         0 my $timeRef = $got->getValue('extime');
620 0 0       0 if ( defined $timeRef) {
621 0 0 0     0 return $self->saveErrorString(undef, "exTime not a 3-element array ref")
622             if ref $timeRef ne 'ARRAY' || @$timeRef != 3;
623             }
624              
625 0         0 $got->setValue("mtime", $timeRef->[1]);
626 0         0 $got->setValue("atime", $timeRef->[0]);
627 0         0 $got->setValue("ctime", $timeRef->[2]);
628             }
629              
630             # Unix2/3 Extended Attribute
631 414         1206 for my $name (qw(exunix2 exunixn))
632             {
633 828 50       1933 if ($got->parsed($name) ) {
634 0         0 my $idRef = $got->getValue($name);
635 0 0       0 if ( defined $idRef) {
636 0 0 0     0 return $self->saveErrorString(undef, "$name not a 2-element array ref")
637             if ref $idRef ne 'ARRAY' || @$idRef != 2;
638             }
639              
640 0         0 $got->setValue("uid", $idRef->[0]);
641 0         0 $got->setValue("gid", $idRef->[1]);
642 0         0 $got->setValue("want_$name", $idRef);
643             }
644             }
645              
646 414 100 66     1206 *$self->{ZipData}{AnyZip64} = 1
647             if $got->getValue('zip64') || $got->getValue('extrafieldzip64') ;
648 414         1260 *$self->{ZipData}{Zip64} = $got->getValue('zip64');
649 414         1253 *$self->{ZipData}{Stream} = $got->getValue('stream');
650              
651 414         1368 my $method = $got->getValue('method');
652             return $self->saveErrorString(undef, "Unknown Method '$method'")
653 414 50       1795 if ! defined $ZIP_CM_MIN_VERSIONS{$method};
654              
655 414 50 66     1573 return $self->saveErrorString(undef, "Bzip2 not available")
656             if $method == ZIP_CM_BZIP2 and
657             ! defined $IO::Compress::Adapter::Bzip2::VERSION;
658              
659 414 50 33     1474 return $self->saveErrorString(undef, "Lzma not available")
660             if $method == ZIP_CM_LZMA
661             and ! defined $IO::Compress::Adapter::Lzma::VERSION;
662              
663 414         1330 *$self->{ZipData}{Method} = $method;
664              
665 414         1165 *$self->{ZipData}{ZipComment} = $got->getValue('zipcomment') ;
666              
667 414         1078 for my $name (qw( extrafieldlocal extrafieldcentral extrafieldzip64))
668             {
669 1242         2572 my $data = $got->getValue($name) ;
670 1242 50       3067 if (defined $data) {
671 0         0 my $bad = IO::Compress::Zlib::Extra::parseExtraField($data, 1, 0) ;
672 0 0       0 return $self->saveErrorString(undef, "Error with $name Parameter: $bad")
673             if $bad ;
674              
675 0         0 $got->setValue($name, $data) ;
676 0         0 *$self->{ZipData}{$name} = $data;
677             }
678             }
679              
680             return undef
681 414 50 33     3299 if defined $IO::Compress::Bzip2::VERSION
682             and ! IO::Compress::Bzip2::ckParams($self, $got);
683              
684 414 50       1124 if ($got->parsed('sparse') ) {
685 0         0 *$self->{ZipData}{Sparse} = $got->getValue('sparse') ;
686 0         0 *$self->{ZipData}{Method} = ZIP_CM_STORE;
687             }
688              
689 414 100       1079 if ($got->parsed('filtername')) {
690 3         9 my $v = $got->getValue('filtername') ;
691 3 50       22 *$self->{ZipData}{FilterName} = $v
692             if ref $v eq 'CODE' ;
693             }
694              
695 414         1518 return 1 ;
696             }
697              
698             sub outputPayload
699             {
700 399     399 0 707 my $self = shift ;
701 399 50       1414 return 1 if *$self->{ZipData}{Sparse} ;
702 399         1875 return $self->output(@_);
703             }
704              
705              
706             #sub newHeader
707             #{
708             # my $self = shift ;
709             #
710             # return $self->mkHeader(*$self->{Got});
711             #}
712              
713              
714             our %PARAMS = (
715             'stream' => [IO::Compress::Base::Common::Parse_boolean, 1],
716             #'store' => [IO::Compress::Base::Common::Parse_boolean, 0],
717             'method' => [IO::Compress::Base::Common::Parse_unsigned, ZIP_CM_DEFLATE],
718              
719             # # Zip header fields
720             'minimal' => [IO::Compress::Base::Common::Parse_boolean, 0],
721             'zip64' => [IO::Compress::Base::Common::Parse_boolean, 0],
722             'comment' => [IO::Compress::Base::Common::Parse_any, ''],
723             'zipcomment'=> [IO::Compress::Base::Common::Parse_any, ''],
724             'name' => [IO::Compress::Base::Common::Parse_any, ''],
725             'filtername'=> [IO::Compress::Base::Common::Parse_code, undef],
726             'canonicalname'=> [IO::Compress::Base::Common::Parse_boolean, 0],
727             'efs' => [IO::Compress::Base::Common::Parse_boolean, 0],
728             'time' => [IO::Compress::Base::Common::Parse_any, undef],
729             'extime' => [IO::Compress::Base::Common::Parse_any, undef],
730             'exunix2' => [IO::Compress::Base::Common::Parse_any, undef],
731             'exunixn' => [IO::Compress::Base::Common::Parse_any, undef],
732             'extattr' => [IO::Compress::Base::Common::Parse_any,
733             $Compress::Raw::Zlib::gzip_os_code == 3
734             ? 0100644 << 16
735             : 0],
736             'os_code' => [IO::Compress::Base::Common::Parse_unsigned, $Compress::Raw::Zlib::gzip_os_code],
737              
738             'textflag' => [IO::Compress::Base::Common::Parse_boolean, 0],
739             'extrafieldlocal' => [IO::Compress::Base::Common::Parse_any, undef],
740             'extrafieldcentral'=> [IO::Compress::Base::Common::Parse_any, undef],
741             'extrafieldzip64' => [IO::Compress::Base::Common::Parse_any, undef],
742              
743             # Lzma
744             'preset' => [IO::Compress::Base::Common::Parse_unsigned, 6],
745             'extreme' => [IO::Compress::Base::Common::Parse_boolean, 0],
746              
747             # For internal use only
748             'sparse' => [IO::Compress::Base::Common::Parse_unsigned, 0],
749              
750             IO::Compress::RawDeflate::getZlibParams(),
751             defined $IO::Compress::Bzip2::VERSION
752             ? IO::Compress::Bzip2::getExtraParams()
753             : ()
754              
755              
756             );
757              
758             sub getExtraParams
759             {
760 414     414 0 10532 return %PARAMS ;
761             }
762              
763             sub getInverseClass
764             {
765 47     47   35176 no warnings 'once';
  47         111  
  47         15464  
766 0     0 0 0 return ('IO::Uncompress::Unzip',
767             \$IO::Uncompress::Unzip::UnzipError);
768             }
769              
770             sub getFileInfo
771             {
772 163     163 0 415 my $self = shift ;
773 163         310 my $params = shift;
774 163         367 my $filename = shift ;
775              
776 163 100       533 if (IO::Compress::Base::Common::isaScalar($filename))
777             {
778             $params->setValue(zip64 => 1)
779 50 50       114 if IO::Compress::Base::Common::isGeMax32 length (${ $filename }) ;
  50         245  
780              
781 50         226 return ;
782             }
783              
784 113         307 my ($mode, $uid, $gid, $size, $atime, $mtime, $ctime) ;
785 113 50       481 if ( $params->parsed('storelinks') )
786             {
787 0         0 ($mode, $uid, $gid, $size, $atime, $mtime, $ctime)
788             = (lstat($filename))[2, 4,5,7, 8,9,10] ;
789             }
790             else
791             {
792 113         2973 ($mode, $uid, $gid, $size, $atime, $mtime, $ctime)
793             = (stat($filename))[2, 4,5,7, 8,9,10] ;
794             }
795              
796 113 100       618 $params->setValue(textflag => -T $filename )
797             if ! $params->parsed('textflag');
798              
799 113 50       546 $params->setValue(zip64 => 1)
800             if IO::Compress::Base::Common::isGeMax32 $size ;
801              
802 113 100       360 $params->setValue('name' => $filename)
803             if ! $params->parsed('name') ;
804              
805 113 100       341 $params->setValue('time' => $mtime)
806             if ! $params->parsed('time') ;
807              
808 113 50       316 if ( ! $params->parsed('extime'))
809             {
810 113         404 $params->setValue('mtime' => $mtime) ;
811 113         318 $params->setValue('atime' => $atime) ;
812 113         302 $params->setValue('ctime' => undef) ; # No Creation time
813             # TODO - see if can fill out creation time on non-Unix
814             }
815              
816             # NOTE - Unix specific code alert
817 113 100       314 if (! $params->parsed('extattr'))
818             {
819 47     47   365 use Fcntl qw(:mode) ;
  47         87  
  47         39325  
820 108         300 my $attr = $mode << 16;
821 108 50       370 $attr |= ZIP_A_RONLY if ($mode & S_IWRITE) == 0 ;
822 108 50       549 $attr |= ZIP_A_DIR if ($mode & S_IFMT ) == S_IFDIR ;
823              
824 108         336 $params->setValue('extattr' => $attr);
825             }
826              
827 113         468 $params->setValue('want_exunixn', [$uid, $gid]);
828 113         408 $params->setValue('uid' => $uid) ;
829 113         264 $params->setValue('gid' => $gid) ;
830              
831             }
832              
833             sub mkExtendedTime
834             {
835             # order expected is m, a, c
836              
837 226     226 0 431 my $times = '';
838 226         405 my $bit = 1 ;
839 226         347 my $flags = 0;
840              
841 226         526 for my $time (@_)
842             {
843 452 100       949 if (defined $time)
844             {
845 339         544 $flags |= $bit;
846 339         843 $times .= pack("V", $time);
847             }
848              
849 452         828 $bit <<= 1 ;
850             }
851              
852 226         1195 return IO::Compress::Zlib::Extra::mkSubField(ZIP_EXTRA_ID_EXT_TIMESTAMP,
853             pack("C", $flags) . $times);
854             }
855              
856             sub mkUnix2Extra
857             {
858 0     0 0 0 my $ids = '';
859 0         0 for my $id (@_)
860             {
861 0         0 $ids .= pack("v", $id);
862             }
863              
864 0         0 return IO::Compress::Zlib::Extra::mkSubField(ZIP_EXTRA_ID_INFO_ZIP_UNIX2,
865             $ids);
866             }
867              
868             sub mkUnixNExtra
869             {
870 113     113 0 221 my $uid = shift;
871 113         189 my $gid = shift;
872              
873             # Assumes UID/GID are 32-bit
874 113         199 my $ids ;
875 113         270 $ids .= pack "C", 1; # version
876 113         1987 $ids .= pack "C", $Config{uidsize};
877 113         434 $ids .= pack "V", $uid;
878 113         809 $ids .= pack "C", $Config{gidsize};
879 113         372 $ids .= pack "V", $gid;
880              
881 113         375 return IO::Compress::Zlib::Extra::mkSubField(ZIP_EXTRA_ID_INFO_ZIP_UNIXN,
882             $ids);
883             }
884              
885              
886             # from Archive::Zip
887             sub _unixToDosTime # Archive::Zip::Member
888             {
889 411     411   917 my $time_t = shift;
890              
891             # TODO - add something to cope with unix time < 1980
892 411         12839 my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time_t);
893 411         1122 my $dt = 0;
894 411         998 $dt += ( $sec >> 1 );
895 411         971 $dt += ( $min << 5 );
896 411         812 $dt += ( $hour << 11 );
897 411         790 $dt += ( $mday << 16 );
898 411         799 $dt += ( ( $mon + 1 ) << 21 );
899 411         1275 $dt += ( ( $year - 80 ) << 25 );
900 411         1054 return $dt;
901             }
902              
903             1;
904              
905             __END__