File Coverage

blib/lib/Archive/Zip/Archive.pm
Criterion Covered Total %
statement 494 654 75.5
branch 200 390 51.2
condition 47 105 44.7
subroutine 54 65 83.0
pod 41 42 97.6
total 836 1256 66.5


line stmt bran cond sub pod time code
1             package Archive::Zip::Archive;
2              
3             # Represents a generic ZIP archive
4              
5 28     28   196 use strict;
  28         54  
  28         838  
6 28     28   136 use File::Path;
  28         52  
  28         1613  
7 28     28   166 use File::Find ();
  28         51  
  28         321  
8 28     28   114 use File::Spec ();
  28         46  
  28         295  
9 28     28   13503 use File::Copy ();
  28         61093  
  28         666  
10 28     28   172 use File::Basename;
  28         57  
  28         2632  
11 28     28   186 use Cwd;
  28         54  
  28         1410  
12 28     28   14813 use Encode qw(encode_utf8 decode_utf8);
  28         251381  
  28         1937  
13              
14 28     28   207 use vars qw( $VERSION @ISA );
  28         53  
  28         1601  
15              
16             BEGIN {
17 28     28   88 $VERSION = '1.68';
18 28         1174 @ISA = qw( Archive::Zip );
19             }
20              
21 28         203767 use Archive::Zip qw(
22             :CONSTANTS
23             :ERROR_CODES
24             :PKZIP_CONSTANTS
25             :UTILITY_METHODS
26 28     28   181 );
  28         55  
27              
28             our $UNICODE;
29             our $UNTAINT = qr/\A(.+)\z/;
30              
31             # Note that this returns undef on read errors, else new zip object.
32              
33             sub new {
34 70     70 1 219 my $class = shift;
35             # Info-Zip 3.0 (I guess) seems to use the following values
36             # for the version fields in the zip64 EOCD record:
37             #
38             # version made by:
39             # 30 (plus upper byte indicating host system)
40             #
41             # version needed to extract:
42             # 45
43 70         887 my $self = bless(
44             {
45             'zip64' => 0,
46             'desiredZip64Mode' => ZIP64_AS_NEEDED,
47             'versionMadeBy' => 0,
48             'versionNeededToExtract' => 0,
49             'diskNumber' => 0,
50             'diskNumberWithStartOfCentralDirectory' =>
51             0,
52             'numberOfCentralDirectoriesOnThisDisk' =>
53             0, # should be # of members
54             'numberOfCentralDirectories' => 0, # should be # of members
55             'centralDirectorySize' => 0, # must re-compute on write
56             'centralDirectoryOffsetWRTStartingDiskNumber' =>
57             0, # must re-compute
58             'writeEOCDOffset' => 0,
59             'writeCentralDirectoryOffset' => 0,
60             'zipfileComment' => '',
61             'eocdOffset' => 0,
62             'fileName' => ''
63             },
64             $class
65             );
66 70         396 $self->{'members'} = [];
67 70 50       419 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{filename} : shift;
68 70 100       233 if ($fileName) {
69 4         23 my $status = $self->read($fileName);
70 4 50       25 return $status == AZ_OK ? $self : undef;
71             }
72 66         363 return $self;
73             }
74              
75             sub storeSymbolicLink {
76 0     0 0 0 my $self = shift;
77 0         0 $self->{'storeSymbolicLink'} = shift;
78             }
79              
80             sub members {
81 1031     1031 1 3445 @{shift->{'members'}};
  1031         3953  
82             }
83              
84             sub numberOfMembers {
85 256     256 1 13159 scalar(shift->members());
86             }
87              
88             sub memberNames {
89 22     22 1 7266 my $self = shift;
90 22         99 return map { $_->fileName() } $self->members();
  147         306  
91             }
92              
93             # return ref to member with given name or undef
94             sub memberNamed {
95 246     246 1 6196 my $self = shift;
96 246 50       548 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{zipName} : shift;
97 246         605 foreach my $member ($self->members()) {
98 3981 100       7680 return $member if $member->fileName() eq $fileName;
99             }
100 80         342 return undef;
101             }
102              
103             sub membersMatching {
104 16     16 1 3605 my $self = shift;
105 16 50       89 my $pattern = (ref($_[0]) eq 'HASH') ? shift->{regex} : shift;
106 16         61 return grep { $_->fileName() =~ /$pattern/ } $self->members();
  115         290  
107             }
108              
109             sub zip64 {
110 55     55 1 575 shift->{'zip64'};
111             }
112              
113             sub desiredZip64Mode {
114 915     915 1 2824 my $self = shift;
115 915         1351 my $desiredZip64Mode = $self->{'desiredZip64Mode'};
116 915 100       1899 if (@_) {
117             $self->{'desiredZip64Mode'} =
118 4 50       37 ref($_[0]) eq 'HASH' ? shift->{desiredZip64Mode} : shift;
119             }
120 915         3715 return $desiredZip64Mode;
121             }
122              
123             sub versionMadeBy {
124 116     116 1 216 shift->{'versionMadeBy'};
125             }
126              
127             sub versionNeededToExtract {
128 116     116 1 240 shift->{'versionNeededToExtract'};
129             }
130              
131             sub diskNumber {
132 0     0 1 0 shift->{'diskNumber'};
133             }
134              
135             sub diskNumberWithStartOfCentralDirectory {
136 0     0 1 0 shift->{'diskNumberWithStartOfCentralDirectory'};
137             }
138              
139             sub numberOfCentralDirectoriesOnThisDisk {
140 0     0 1 0 shift->{'numberOfCentralDirectoriesOnThisDisk'};
141             }
142              
143             sub numberOfCentralDirectories {
144 0     0 1 0 shift->{'numberOfCentralDirectories'};
145             }
146              
147             sub centralDirectorySize {
148 94     94 1 317 shift->{'centralDirectorySize'};
149             }
150              
151             sub centralDirectoryOffsetWRTStartingDiskNumber {
152 47     47 1 119 shift->{'centralDirectoryOffsetWRTStartingDiskNumber'};
153             }
154              
155             sub zipfileComment {
156 116     116 1 193 my $self = shift;
157 116         359 my $comment = $self->{'zipfileComment'};
158 116 50       358 if (@_) {
159 0 0       0 my $new_comment = (ref($_[0]) eq 'HASH') ? shift->{comment} : shift;
160 0         0 $self->{'zipfileComment'} = pack('C0a*', $new_comment); # avoid Unicode
161             }
162 116         273 return $comment;
163             }
164              
165             sub eocdOffset {
166 117     117 1 766 shift->{'eocdOffset'};
167             }
168              
169             # Return the name of the file last read.
170             sub fileName {
171 0     0 1 0 shift->{'fileName'};
172             }
173              
174             sub removeMember {
175 10     10 1 6021 my $self = shift;
176 10 50       51 my $member = (ref($_[0]) eq 'HASH') ? shift->{memberOrZipName} : shift;
177 10 50       47 $member = $self->memberNamed($member) unless ref($member);
178 10 50       41 return undef unless $member;
179 10         37 my @newMembers = grep { $_ != $member } $self->members();
  88         194  
180 10         46 $self->{'members'} = \@newMembers;
181 10         93 return $member;
182             }
183              
184             sub replaceMember {
185 9     9 1 49 my $self = shift;
186              
187 9         38 my ($oldMember, $newMember);
188 9 50       57 if (ref($_[0]) eq 'HASH') {
189 0         0 $oldMember = $_[0]->{memberOrZipName};
190 0         0 $newMember = $_[0]->{newMember};
191             } else {
192 9         26 ($oldMember, $newMember) = @_;
193             }
194              
195 9 50       34 $oldMember = $self->memberNamed($oldMember) unless ref($oldMember);
196 9 50       41 return undef unless $oldMember;
197 9 50       39 return undef unless $newMember;
198             my @newMembers =
199 9 100       36 map { ($_ == $oldMember) ? $newMember : $_ } $self->members();
  244         522  
200 9         64 $self->{'members'} = \@newMembers;
201 9         60 return $oldMember;
202             }
203              
204             sub extractMember {
205 51     51 1 4624 my $self = shift;
206              
207 51         98 my ($member, $name);
208 51 50       158 if (ref($_[0]) eq 'HASH') {
209 0         0 $member = $_[0]->{memberOrZipName};
210 0         0 $name = $_[0]->{name};
211             } else {
212 51         138 ($member, $name) = @_;
213             }
214              
215 51 100       188 $member = $self->memberNamed($member) unless ref($member);
216 51 50       126 return _error('member not found') unless $member;
217 51         161 my $originalSize = $member->compressedSize();
218 51         104 my ($volumeName, $dirName, $fileName);
219 51 100       105 if (defined($name)) {
220 11         285 ($volumeName, $dirName, $fileName) = File::Spec->splitpath($name);
221 11         144 $dirName = File::Spec->catpath($volumeName, $dirName, '');
222             } else {
223 40         103 $name = $member->fileName();
224 40 100       175 if ((my $ret = _extractionNameIsSafe($name))
225 1         4 != AZ_OK) { return $ret; }
226 39         438 ($dirName = $name) =~ s{[^/]*$}{};
227 39         184 $dirName = Archive::Zip::_asLocalName($dirName);
228 39         95 $name = Archive::Zip::_asLocalName($name);
229             }
230 50 100 66     1008 if ($dirName && !-d $dirName) {
231 6         936 mkpath($dirName);
232 6 50       113 return _ioError("can't create dir $dirName") if (!-d $dirName);
233             }
234 50         349 my $rc = $member->extractToFileNamed($name, @_);
235              
236             # TODO refactor this fix into extractToFileNamed()
237 50         137 $member->{'compressedSize'} = $originalSize;
238 50         297 return $rc;
239             }
240              
241             sub extractMemberWithoutPaths {
242 0     0 1 0 my $self = shift;
243              
244 0         0 my ($member, $name);
245 0 0       0 if (ref($_[0]) eq 'HASH') {
246 0         0 $member = $_[0]->{memberOrZipName};
247 0         0 $name = $_[0]->{name};
248             } else {
249 0         0 ($member, $name) = @_;
250             }
251              
252 0 0       0 $member = $self->memberNamed($member) unless ref($member);
253 0 0       0 return _error('member not found') unless $member;
254 0         0 my $originalSize = $member->compressedSize();
255 0 0       0 return AZ_OK if $member->isDirectory();
256 0 0       0 unless ($name) {
257 0         0 $name = $member->fileName();
258 0         0 $name =~ s{.*/}{}; # strip off directories, if any
259 0 0       0 if ((my $ret = _extractionNameIsSafe($name))
260 0         0 != AZ_OK) { return $ret; }
261 0         0 $name = Archive::Zip::_asLocalName($name);
262             }
263 0         0 my $rc = $member->extractToFileNamed($name, @_);
264 0         0 $member->{'compressedSize'} = $originalSize;
265 0         0 return $rc;
266             }
267              
268             sub addMember {
269 233     233 1 2820 my $self = shift;
270 233 50       639 my $newMember = (ref($_[0]) eq 'HASH') ? shift->{member} : shift;
271 233 50       516 push(@{$self->{'members'}}, $newMember) if $newMember;
  233         935  
272 233 100 66     1167 if($newMember && ($newMember->{bitFlag} & 0x800)
      100        
273             && !utf8::is_utf8($newMember->{fileName})){
274 2         18 $newMember->{fileName} = Encode::decode_utf8($newMember->{fileName});
275             }
276 233         565 return $newMember;
277             }
278              
279             sub addFile {
280 163     163 1 4918 my $self = shift;
281              
282 163         301 my ($fileName, $newName, $compressionLevel);
283 163 50       347 if (ref($_[0]) eq 'HASH') {
284 0         0 $fileName = $_[0]->{filename};
285 0         0 $newName = $_[0]->{zipName};
286 0         0 $compressionLevel = $_[0]->{compressionLevel};
287             } else {
288 163         364 ($fileName, $newName, $compressionLevel) = @_;
289             }
290              
291 163 0 33     487 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
292 0         0 $fileName = Win32::GetANSIPathName($fileName);
293             }
294              
295 163         666 my $newMember = Archive::Zip::Member->newFromFile($fileName, $newName);
296 163         506 $newMember->desiredCompressionLevel($compressionLevel);
297 163 50 33     471 if ($self->{'storeSymbolicLink'} && -l $fileName) {
298 0         0 my $newMember =
299             Archive::Zip::Member->newFromString(readlink $fileName, $newName);
300              
301             # For symbolic links, External File Attribute is set to 0xA1FF0000 by Info-ZIP
302 0         0 $newMember->{'externalFileAttributes'} = 0xA1FF0000;
303 0         0 $self->addMember($newMember);
304             } else {
305 163         418 $self->addMember($newMember);
306             }
307              
308 163         320 return $newMember;
309             }
310              
311             sub addString {
312 13     13 1 2912 my $self = shift;
313              
314 13         41 my ($stringOrStringRef, $name, $compressionLevel);
315 13 50       56 if (ref($_[0]) eq 'HASH') {
316 0         0 $stringOrStringRef = $_[0]->{string};
317 0         0 $name = $_[0]->{zipName};
318 0         0 $compressionLevel = $_[0]->{compressionLevel};
319             } else {
320 13         59 ($stringOrStringRef, $name, $compressionLevel) = @_;
321             }
322              
323 13         167 my $newMember =
324             Archive::Zip::Member->newFromString($stringOrStringRef, $name);
325 13         90 $newMember->desiredCompressionLevel($compressionLevel);
326 13         44 return $self->addMember($newMember);
327             }
328              
329             sub addDirectory {
330 11     11 1 1076 my $self = shift;
331              
332 11         40 my ($name, $newName);
333 11 50       74 if (ref($_[0]) eq 'HASH') {
334 0         0 $name = $_[0]->{directoryName};
335 0         0 $newName = $_[0]->{zipName};
336             } else {
337 11         51 ($name, $newName) = @_;
338             }
339              
340 11 0 33     118 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
341 0         0 $name = Win32::GetANSIPathName($name);
342             }
343              
344 11         188 my $newMember = Archive::Zip::Member->newDirectoryNamed($name, $newName);
345 11 50 33     75 if ($self->{'storeSymbolicLink'} && -l $name) {
346 0         0 my $link = readlink $name;
347 0 0       0 ($newName =~ s{/$}{}) if $newName; # Strip trailing /
348 0         0 my $newMember = Archive::Zip::Member->newFromString($link, $newName);
349              
350             # For symbolic links, External File Attribute is set to 0xA1FF0000 by Info-ZIP
351 0         0 $newMember->{'externalFileAttributes'} = 0xA1FF0000;
352 0         0 $self->addMember($newMember);
353             } else {
354 11         60 $self->addMember($newMember);
355             }
356              
357 11         34 return $newMember;
358             }
359              
360             # add either a file or a directory.
361              
362             sub addFileOrDirectory {
363 2     2 1 645 my $self = shift;
364              
365 2         10 my ($name, $newName, $compressionLevel);
366 2 50       11 if (ref($_[0]) eq 'HASH') {
367 0         0 $name = $_[0]->{name};
368 0         0 $newName = $_[0]->{zipName};
369 0         0 $compressionLevel = $_[0]->{compressionLevel};
370             } else {
371 2         10 ($name, $newName, $compressionLevel) = @_;
372             }
373              
374 2 0 33     26 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
375 0         0 $name = Win32::GetANSIPathName($name);
376             }
377              
378 2         20 $name =~ s{/$}{};
379 2 50       16 if ($newName) {
380 2         13 $newName =~ s{/$}{};
381             } else {
382 0         0 $newName = $name;
383             }
384 2 100       52 if (-f $name) {
    50          
385 1         17 return $self->addFile($name, $newName, $compressionLevel);
386             } elsif (-d $name) {
387 1         16 return $self->addDirectory($name, $newName);
388             } else {
389 0         0 return _error("$name is neither a file nor a directory");
390             }
391             }
392              
393             sub contents {
394 3     3 1 2349 my $self = shift;
395              
396 3         19 my ($member, $newContents);
397 3 50       20 if (ref($_[0]) eq 'HASH') {
398 0         0 $member = $_[0]->{memberOrZipName};
399 0         0 $newContents = $_[0]->{contents};
400             } else {
401 3         21 ($member, $newContents) = @_;
402             }
403              
404 3         16 my ($contents, $status) = (undef, AZ_OK);
405 3 50       34 if ($status == AZ_OK) {
406 3 50       18 $status = _error('No member name given') unless defined($member);
407             }
408 3 50 33     54 if ($status == AZ_OK && ! ref($member)) {
409 0         0 my $memberName = $member;
410 0         0 $member = $self->memberNamed($memberName);
411 0 0       0 $status = _error('No member named $memberName') unless defined($member);
412             }
413 3 50       13 if ($status == AZ_OK) {
414 3         54 ($contents, $status) = $member->contents($newContents);
415             }
416              
417             return
418             wantarray
419 3 50       19 ? ($contents, $status)
420             : $contents;
421             }
422              
423             sub writeToFileNamed {
424 76     76 1 92357 my $self = shift;
425             my $fileName =
426 76 50       311 (ref($_[0]) eq 'HASH') ? shift->{filename} : shift; # local FS format
427 76         246 foreach my $member ($self->members()) {
428 218 50       836 if ($member->_usesFileNamed($fileName)) {
429 0         0 return _error("$fileName is needed by member "
430             . $member->fileName()
431             . "; consider using overwrite() or overwriteAs() instead.");
432             }
433             }
434 76         290 my ($status, $fh) = _newFileHandle($fileName, 'w');
435 76 50       306 return _ioError("Can't open $fileName for write") unless $status;
436 76         287 $status = $self->writeToFileHandle($fh, 1);
437 76         289 $fh->close();
438 76         7109 $fh = undef;
439              
440 76         472 return $status;
441             }
442              
443             # It is possible to write data to the FH before calling this,
444             # perhaps to make a self-extracting archive.
445             sub writeToFileHandle {
446 116     116 1 157674 my $self = shift;
447              
448 116         436 my ($fh, $fhIsSeekable);
449 116 50       752 if (ref($_[0]) eq 'HASH') {
450 0         0 $fh = $_[0]->{fileHandle};
451             $fhIsSeekable =
452 0 0       0 exists($_[0]->{seek}) ? $_[0]->{seek} : _isSeekable($fh);
453             } else {
454 116         272 $fh = shift;
455 116 100       1055 $fhIsSeekable = @_ ? shift : _isSeekable($fh);
456             }
457              
458 116 50       383 return _error('No filehandle given') unless $fh;
459 116 50       1101 return _ioError('filehandle not open') unless $fh->opened();
460 116         1184 _binmode($fh);
461              
462             # Find out where the current position is.
463 116 100       1376 my $offset = $fhIsSeekable ? $fh->tell() : 0;
464 116 50       648 $offset = 0 if $offset < 0;
465              
466             # (Re-)set the "was-successfully-written" flag so that the
467             # contract advertised in the documentation ("that member and
468             # *all following it* will return false from wasWritten()")
469             # also holds for members written more than once.
470             #
471             # Not sure whether that mechanism works, anyway. If method
472             # $member->_writeToFileHandle fails with an error below and
473             # user continues with calling $zip->writeCentralDirectory
474             # manually, we should end up with the following picture
475             # unless the user seeks back to writeCentralDirectoryOffset:
476             #
477             # ...
478             # [last successfully written member]
479             # <- writeCentralDirectoryOffset points here
480             # [half-written member junk with unknown size]
481             # [central directory entry 0]
482             # ...
483 116         711 foreach my $member ($self->members()) {
484 409         1452 $member->{'wasWritten'} = 0;
485             }
486              
487 116         312 foreach my $member ($self->members()) {
488              
489             # (Re-)set object member zip64 flag. Here is what
490             # happens next to that flag:
491             #
492             # $member->_writeToFileHandle
493             # Determines a local flag value depending on
494             # necessity and user desire and ors it to
495             # the object member
496             # $member->_writeLocalFileHeader
497             # Queries the object member to write appropriate
498             # local header
499             # $member->_writeDataDescriptor
500             # Queries the object member to write appropriate
501             # data descriptor
502             # $member->_writeCentralDirectoryFileHeader
503             # Determines a local flag value depending on
504             # necessity and user desire. Writes a central
505             # directory header appropriate to the local flag.
506             # Ors the local flag to the object member.
507 409         963 $member->{'zip64'} = 0;
508              
509 409         1178 my ($status, $memberSize) =
510             $member->_writeToFileHandle($fh, $fhIsSeekable, $offset,
511             $self->desiredZip64Mode());
512 409         1365 $member->endRead();
513 409 50       746 return $status if $status != AZ_OK;
514              
515 409         632 $offset += $memberSize;
516              
517             # Change this so it reflects write status and last
518             # successful position
519 409         830 $member->{'wasWritten'} = 1;
520 409         996 $self->{'writeCentralDirectoryOffset'} = $offset;
521             }
522              
523 116         790 return $self->writeCentralDirectory($fh);
524             }
525              
526             # Write zip back to the original file,
527             # as safely as possible.
528             # Returns AZ_OK if successful.
529             sub overwrite {
530 0     0 1 0 my $self = shift;
531 0         0 return $self->overwriteAs($self->{'fileName'});
532             }
533              
534             # Write zip to the specified file,
535             # as safely as possible.
536             # Returns AZ_OK if successful.
537             sub overwriteAs {
538 0     0 1 0 my $self = shift;
539 0 0       0 my $zipName = (ref($_[0]) eq 'HASH') ? $_[0]->{filename} : shift;
540 0 0       0 return _error("no filename in overwriteAs()") unless defined($zipName);
541              
542 0         0 my ($fh, $tempName) = Archive::Zip::tempFile();
543 0 0       0 return _error("Can't open temp file", $!) unless $fh;
544              
545 0         0 (my $backupName = $zipName) =~ s{(\.[^.]*)?$}{.zbk};
546              
547 0         0 my $status = $self->writeToFileHandle($fh);
548 0         0 $fh->close();
549 0         0 $fh = undef;
550              
551 0 0       0 if ($status != AZ_OK) {
552 0         0 unlink($tempName);
553 0         0 _printError("Can't write to $tempName");
554 0         0 return $status;
555             }
556              
557 0         0 my $err;
558              
559             # rename the zip
560 0 0 0     0 if (-f $zipName && !rename($zipName, $backupName)) {
561 0         0 $err = $!;
562 0         0 unlink($tempName);
563 0         0 return _error("Can't rename $zipName as $backupName", $err);
564             }
565              
566             # move the temp to the original name (possibly copying)
567 0 0 0     0 unless (File::Copy::move($tempName, $zipName)
568             || File::Copy::copy($tempName, $zipName)) {
569 0         0 $err = $!;
570 0         0 rename($backupName, $zipName);
571 0         0 unlink($tempName);
572 0         0 return _error("Can't move $tempName to $zipName", $err);
573             }
574              
575             # unlink the backup
576 0 0 0     0 if (-f $backupName && !unlink($backupName)) {
577 0         0 $err = $!;
578 0         0 return _error("Can't unlink $backupName", $err);
579             }
580              
581 0         0 return AZ_OK;
582             }
583              
584             # Used only during writing
585             sub _writeCentralDirectoryOffset {
586 348     348   654 shift->{'writeCentralDirectoryOffset'};
587             }
588              
589             sub _writeEOCDOffset {
590 165     165   621 shift->{'writeEOCDOffset'};
591             }
592              
593             # Expects to have _writeEOCDOffset() set
594             sub _writeEndOfCentralDirectory {
595 116     116   300 my ($self, $fh, $membersZip64) = @_;
596              
597 116         230 my $zip64 = 0;
598 116         279 my $versionMadeBy = $self->versionMadeBy();
599 116         279 my $versionNeededToExtract = $self->versionNeededToExtract();
600 116         187 my $diskNumber = 0;
601 116         168 my $diskNumberWithStartOfCentralDirectory = 0;
602 116         479 my $numberOfCentralDirectoriesOnThisDisk = $self->numberOfMembers();
603 116         278 my $numberOfCentralDirectories = $self->numberOfMembers();
604 116         407 my $centralDirectorySize =
605             $self->_writeEOCDOffset() - $self->_writeCentralDirectoryOffset();
606 116         247 my $centralDirectoryOffsetWRTStartingDiskNumber =
607             $self->_writeCentralDirectoryOffset();
608 116         340 my $zipfileCommentLength = length($self->zipfileComment());
609              
610 116         193 my $eocdDataZip64 = 0;
611 116   33     745 $eocdDataZip64 ||= $numberOfCentralDirectoriesOnThisDisk > 0xffff;
612 116   33     765 $eocdDataZip64 ||= $numberOfCentralDirectories > 0xffff;
613 116   33     407 $eocdDataZip64 ||= $centralDirectorySize > 0xffffffff;
614 116   33     624 $eocdDataZip64 ||= $centralDirectoryOffsetWRTStartingDiskNumber > 0xffffffff;
615              
616 116 100 66     535 if ( $membersZip64
      100        
617             || $eocdDataZip64
618             || $self->desiredZip64Mode() == ZIP64_EOCD) {
619 49         169 return _zip64NotSupported() unless ZIP64_SUPPORTED;
620              
621 49         138 $zip64 = 1;
622 49 100       237 $versionMadeBy = 45 if ($versionMadeBy == 0);
623 49 100       179 $versionNeededToExtract = 45 if ($versionNeededToExtract < 45);
624              
625 49 50       288 $self->_print($fh, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE_STRING)
626             or return _ioError('writing zip64 EOCD record signature');
627              
628 49         584 my $record = pack(
629             ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_FORMAT,
630             ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_LENGTH +
631             SIGNATURE_LENGTH - 12,
632             $versionMadeBy,
633             $versionNeededToExtract,
634             $diskNumber,
635             $diskNumberWithStartOfCentralDirectory,
636             $numberOfCentralDirectoriesOnThisDisk,
637             $numberOfCentralDirectories,
638             $centralDirectorySize,
639             $centralDirectoryOffsetWRTStartingDiskNumber
640             );
641 49 50       136 $self->_print($fh, $record)
642             or return _ioError('writing zip64 EOCD record');
643              
644 49 50       598 $self->_print($fh, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE_STRING)
645             or return _ioError('writing zip64 EOCD locator signature');
646              
647 49         482 my $locator = pack(
648             ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_FORMAT,
649             0,
650             $self->_writeEOCDOffset(),
651             1
652             );
653 49 50       177 $self->_print($fh, $locator)
654             or return _ioError('writing zip64 EOCD locator');
655             }
656              
657 116 50       900 $self->_print($fh, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING)
658             or return _ioError('writing EOCD Signature');
659              
660 116 50       1622 my $header = pack(
    50          
    50          
    50          
661             END_OF_CENTRAL_DIRECTORY_FORMAT,
662             $diskNumber,
663             $diskNumberWithStartOfCentralDirectory,
664             $numberOfCentralDirectoriesOnThisDisk > 0xffff
665             ? 0xffff : $numberOfCentralDirectoriesOnThisDisk,
666             $numberOfCentralDirectories > 0xffff
667             ? 0xffff : $numberOfCentralDirectories,
668             $centralDirectorySize > 0xffffffff
669             ? 0xffffffff : $centralDirectorySize,
670             $centralDirectoryOffsetWRTStartingDiskNumber > 0xffffffff
671             ? 0xffffffff : $centralDirectoryOffsetWRTStartingDiskNumber,
672             $zipfileCommentLength
673             );
674 116 50       342 $self->_print($fh, $header)
675             or return _ioError('writing EOCD header');
676 116 50       929 if ($zipfileCommentLength) {
677 0 0       0 $self->_print($fh, $self->zipfileComment())
678             or return _ioError('writing zipfile comment');
679             }
680              
681             # Adjust object members related to zip64 format
682 116         233 $self->{'zip64'} = $zip64;
683 116         194 $self->{'versionMadeBy'} = $versionMadeBy;
684 116         169 $self->{'versionNeededToExtract'} = $versionNeededToExtract;
685              
686 116         754 return AZ_OK;
687             }
688              
689             # $offset can be specified to truncate a zip file.
690             sub writeCentralDirectory {
691 116     116 1 254 my $self = shift;
692              
693 116         246 my ($fh, $offset);
694 116 50       459 if (ref($_[0]) eq 'HASH') {
695 0         0 $fh = $_[0]->{fileHandle};
696 0         0 $offset = $_[0]->{offset};
697             } else {
698 116         276 ($fh, $offset) = @_;
699             }
700              
701 116 50       259 if (defined($offset)) {
702 0         0 $self->{'writeCentralDirectoryOffset'} = $offset;
703 0 0       0 $fh->seek($offset, IO::Seekable::SEEK_SET)
704             or return _ioError('seeking to write central directory');
705             } else {
706 116         547 $offset = $self->_writeCentralDirectoryOffset();
707             }
708              
709 116         215 my $membersZip64 = 0;
710 116         369 foreach my $member ($self->members()) {
711 409         902 my ($status, $headerSize) =
712             $member->_writeCentralDirectoryFileHeader($fh, $self->desiredZip64Mode());
713 409 50       931 return $status if $status != AZ_OK;
714 409   100     1505 $membersZip64 ||= $member->zip64();
715 409         614 $offset += $headerSize;
716 409         831 $self->{'writeEOCDOffset'} = $offset;
717             }
718              
719 116         374 return $self->_writeEndOfCentralDirectory($fh, $membersZip64);
720             }
721              
722             sub read {
723 47     47 1 23308 my $self = shift;
724 47 50       171 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{filename} : shift;
725 47 50       156 return _error('No filename given') unless $fileName;
726 47         201 my ($status, $fh) = _newFileHandle($fileName, 'r');
727 47 50       190 return _ioError("opening $fileName for read") unless $status;
728              
729 47         281 $status = $self->readFromFileHandle($fh, $fileName);
730 47 50       119 return $status if $status != AZ_OK;
731              
732 47         228 $fh->close();
733 47         1000 $self->{'fileName'} = $fileName;
734 47         335 return AZ_OK;
735             }
736              
737             sub readFromFileHandle {
738 47     47 1 111 my $self = shift;
739              
740 47         108 my ($fh, $fileName);
741 47 50       172 if (ref($_[0]) eq 'HASH') {
742 0         0 $fh = $_[0]->{fileHandle};
743 0         0 $fileName = $_[0]->{filename};
744             } else {
745 47         112 ($fh, $fileName) = @_;
746             }
747              
748 47 50       197 $fileName = $fh unless defined($fileName);
749 47 50       130 return _error('No filehandle given') unless $fh;
750 47 50       213 return _ioError('filehandle not open') unless $fh->opened();
751              
752 47         438 _binmode($fh);
753 47         471 $self->{'fileName'} = "$fh";
754              
755             # TODO: how to support non-seekable zips?
756 47 50       159 return _error('file not seekable')
757             unless _isSeekable($fh);
758              
759 47         244 $fh->seek(0, 0); # rewind the file
760              
761 47         835 my $status = $self->_findEndOfCentralDirectory($fh);
762 47 50       147 return $status if $status != AZ_OK;
763              
764 47         75 my $eocdPosition;
765 47         231 ($status, $eocdPosition) = $self->_readEndOfCentralDirectory($fh, $fileName);
766 47 50       128 return $status if $status != AZ_OK;
767              
768 47         199 my $zip64 = $self->zip64();
769              
770 47 50       154 $fh->seek($eocdPosition - $self->centralDirectorySize(),
771             IO::Seekable::SEEK_SET)
772             or return _ioError("Can't seek $fileName");
773              
774             # Try to detect garbage at beginning of archives
775             # This should be 0
776 47         661 $self->{'eocdOffset'} = $eocdPosition - $self->centralDirectorySize() # here
777             - $self->centralDirectoryOffsetWRTStartingDiskNumber();
778              
779 47         90 for (; ;) {
780 117         330 my $newMember =
781             Archive::Zip::Member->_newFromZipFile($fh, $fileName, $zip64,
782             $self->eocdOffset());
783 117         158 my $signature;
784 117         308 ($status, $signature) = _readSignature($fh, $fileName);
785 117 50       284 return $status if $status != AZ_OK;
786 117 100       233 if (! $zip64) {
787 95 100       398 last if $signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE;
788             }
789             else {
790 22 100       83 last if $signature == ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE;
791             }
792 70         324 $status = $newMember->_readCentralDirectoryFileHeader();
793 70 50       173 return $status if $status != AZ_OK;
794 70         250 $status = $newMember->endRead();
795 70 50       159 return $status if $status != AZ_OK;
796              
797 70 100       195 if ($newMember->isDirectory()) {
798 13         76 $newMember->_become('Archive::Zip::DirectoryMember');
799             # Ensure above call suceeded to avoid future trouble
800 13 50       61 $newMember->_ISA('Archive::Zip::DirectoryMember') or
801             return $self->_error('becoming Archive::Zip::DirectoryMember');
802             }
803              
804 70 100 66     318 if(($newMember->{bitFlag} & 0x800) && !utf8::is_utf8($newMember->{fileName})){
805 10         37 $newMember->{fileName} = Encode::decode_utf8($newMember->{fileName});
806             }
807              
808 70         407 push(@{$self->{'members'}}, $newMember);
  70         212  
809             }
810              
811 47         150 return AZ_OK;
812             }
813              
814             # Read EOCD, starting from position before signature.
815             # Checks for a zip64 EOCD record and uses that if present.
816             #
817             # Return AZ_OK (in scalar context) or a pair (AZ_OK,
818             # $eocdPosition) (in list context) on success:
819             # ( $status, $eocdPosition ) = $zip->_readEndOfCentralDirectory( $fh, $fileName );
820             # where the returned EOCD position either points to the beginning
821             # of the EOCD or to the beginning of the zip64 EOCD record.
822             #
823             # APPNOTE.TXT as of version 6.3.6 is a bit vague on the
824             # "ZIP64(tm) format". It has a lot of conditions like "if an
825             # archive is in ZIP64 format", but never explicitly mentions
826             # *when* an archive is in that format. (Or at least I haven't
827             # found it.)
828             #
829             # So I decided that an archive is in ZIP64 format if zip64 EOCD
830             # locator and zip64 EOCD record are present before the EOCD with
831             # the format given in the specification.
832             sub _readEndOfCentralDirectory {
833 47     47   97 my $self = shift;
834 47         88 my $fh = shift;
835 47         98 my $fileName = shift;
836              
837             # Remember current position, which is just before the EOCD
838             # signature
839 47         178 my $eocdPosition = $fh->tell();
840              
841             # Reset the zip64 format flag
842 47         258 $self->{'zip64'} = 0;
843 47         90 my $zip64EOCDPosition;
844              
845             # Check for zip64 EOCD locator and zip64 EOCD record. Be
846             # extra careful here to not interpret any random data as
847             # zip64 data structures. If in doubt, silently continue
848             # reading the regular EOCD.
849             NOZIP64:
850             {
851             # Do not even start looking for any zip64 structures if
852             # that would not be supported.
853 47         76 if (! ZIP64_SUPPORTED) {
  47         69  
854             last NOZIP64;
855             }
856              
857 47 50       126 if ($eocdPosition < ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH + SIGNATURE_LENGTH) {
858 0         0 last NOZIP64;
859             }
860              
861             # Skip to before potential zip64 EOCD locator
862 47 50       139 $fh->seek(-(ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH) - SIGNATURE_LENGTH,
863             IO::Seekable::SEEK_CUR)
864             or return _ioError("seeking to before zip 64 EOCD locator");
865 47         619 my $zip64EOCDLocatorPosition =
866             $eocdPosition - ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH - SIGNATURE_LENGTH;
867              
868 47         86 my $status;
869             my $bytesRead;
870              
871             # Read potential zip64 EOCD locator signature
872 47         241 $status =
873             _readSignature($fh, $fileName,
874             ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE, 1);
875 47 50       160 return $status if $status == AZ_IO_ERROR;
876 47 50       112 if ($status == AZ_FORMAT_ERROR) {
877 0 0       0 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
878             or return _ioError("seeking to EOCD");
879 0         0 last NOZIP64;
880             }
881              
882             # Read potential zip64 EOCD locator and verify it
883 47         145 my $locator = '';
884 47         173 $bytesRead = $fh->read($locator, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH);
885 47 50       309 if ($bytesRead != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH) {
886 0         0 return _ioError("reading zip64 EOCD locator");
887             }
888 47         196 (undef, $zip64EOCDPosition, undef) =
889             unpack(ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_FORMAT, $locator);
890 47 100       211 if ($zip64EOCDPosition >
891             ($zip64EOCDLocatorPosition - ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_LENGTH - SIGNATURE_LENGTH)) {
892             # No need to seek to EOCD since we're already there
893 24         89 last NOZIP64;
894             }
895              
896             # Skip to potential zip64 EOCD record
897 23 50       80 $fh->seek($zip64EOCDPosition, IO::Seekable::SEEK_SET)
898             or return _ioError("seeking to zip64 EOCD record");
899              
900             # Read potential zip64 EOCD record signature
901 23         387 $status =
902             _readSignature($fh, $fileName,
903             ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE, 1);
904 23 50       75 return $status if $status == AZ_IO_ERROR;
905 23 50       57 if ($status == AZ_FORMAT_ERROR) {
906 0 0       0 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
907             or return _ioError("seeking to EOCD");
908 0         0 last NOZIP64;
909             }
910              
911             # Read potential zip64 EOCD record. Ignore the zip64
912             # extensible data sector.
913 23         41 my $record = '';
914 23         66 $bytesRead = $fh->read($record, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_LENGTH);
915 23 50       148 if ($bytesRead != ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_LENGTH) {
916 0         0 return _ioError("reading zip64 EOCD record");
917             }
918              
919             # Perform one final check, hoping that all implementors
920             # follow the recommendation of the specification
921             # regarding the size of the zip64 EOCD record
922 23         67 my ($zip64EODCRecordSize) = unpack("Q<", $record);
923 23 100       92 if ($zip64EOCDPosition + 12 + $zip64EODCRecordSize != $zip64EOCDLocatorPosition) {
924 16 50       34 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
925             or return _ioError("seeking to EOCD");
926 16         230 last NOZIP64;
927             }
928              
929 7         18 $self->{'zip64'} = 1;
930             (
931             undef,
932             $self->{'versionMadeBy'},
933             $self->{'versionNeededToExtract'},
934             $self->{'diskNumber'},
935             $self->{'diskNumberWithStartOfCentralDirectory'},
936             $self->{'numberOfCentralDirectoriesOnThisDisk'},
937             $self->{'numberOfCentralDirectories'},
938             $self->{'centralDirectorySize'},
939 7         63 $self->{'centralDirectoryOffsetWRTStartingDiskNumber'}
940             ) = unpack(ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_FORMAT, $record);
941              
942             # Don't just happily bail out, we still need to read the
943             # zip file comment!
944 7 50       35 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
945             or return _ioError("seeking to EOCD");
946             }
947              
948             # Skip past signature
949 47 50       257 $fh->seek(SIGNATURE_LENGTH, IO::Seekable::SEEK_CUR)
950             or return _ioError("seeking past EOCD signature");
951              
952 47         685 my $header = '';
953 47         217 my $bytesRead = $fh->read($header, END_OF_CENTRAL_DIRECTORY_LENGTH);
954 47 50       587 if ($bytesRead != END_OF_CENTRAL_DIRECTORY_LENGTH) {
955 0         0 return _ioError("reading end of central directory");
956             }
957              
958 47         86 my $zipfileCommentLength;
959 47 100       153 if (! $self->{'zip64'}) {
960             (
961             $self->{'diskNumber'},
962             $self->{'diskNumberWithStartOfCentralDirectory'},
963             $self->{'numberOfCentralDirectoriesOnThisDisk'},
964             $self->{'numberOfCentralDirectories'},
965             $self->{'centralDirectorySize'},
966 40         227 $self->{'centralDirectoryOffsetWRTStartingDiskNumber'},
967             $zipfileCommentLength
968             ) = unpack(END_OF_CENTRAL_DIRECTORY_FORMAT, $header);
969              
970 40 50 33     692 if ( $self->{'diskNumber'} == 0xffff
      33        
      33        
      33        
      33        
971             || $self->{'diskNumberWithStartOfCentralDirectory'} == 0xffff
972             || $self->{'numberOfCentralDirectoriesOnThisDisk'} == 0xffff
973             || $self->{'numberOfCentralDirectories'} == 0xffff
974             || $self->{'centralDirectorySize'} == 0xffffffff
975             || $self->{'centralDirectoryOffsetWRTStartingDiskNumber'} == 0xffffffff) {
976 0         0 if (ZIP64_SUPPORTED) {
977 0         0 return _formatError("unexpected zip64 marker values in EOCD");
978             }
979             else {
980             return _zip64NotSupported();
981             }
982             }
983             }
984             else {
985             (
986             undef,
987             undef,
988             undef,
989             undef,
990             undef,
991             undef,
992 7         63 $zipfileCommentLength
993             ) = unpack(END_OF_CENTRAL_DIRECTORY_FORMAT, $header);
994             }
995              
996 47 50       139 if ($zipfileCommentLength) {
997 0         0 my $zipfileComment = '';
998 0         0 $bytesRead = $fh->read($zipfileComment, $zipfileCommentLength);
999 0 0       0 if ($bytesRead != $zipfileCommentLength) {
1000 0         0 return _ioError("reading zipfile comment");
1001             }
1002 0         0 $self->{'zipfileComment'} = $zipfileComment;
1003             }
1004              
1005 47 100       121 if (! $self->{'zip64'}) {
1006             return
1007             wantarray
1008 40 50       173 ? (AZ_OK, $eocdPosition)
1009             : AZ_OK;
1010             }
1011             else {
1012             return
1013             wantarray
1014 7 50       52 ? (AZ_OK, $zip64EOCDPosition)
1015             : AZ_OK;
1016             }
1017             }
1018              
1019             # Seek in my file to the end, then read backwards until we find the
1020             # signature of the central directory record. Leave the file positioned right
1021             # before the signature. Returns AZ_OK if success.
1022             sub _findEndOfCentralDirectory {
1023 47     47   131 my $self = shift;
1024 47         99 my $fh = shift;
1025 47         137 my $data = '';
1026 47 50       189 $fh->seek(0, IO::Seekable::SEEK_END)
1027             or return _ioError("seeking to end");
1028              
1029 47         659 my $fileLength = $fh->tell();
1030 47 50       338 if ($fileLength < END_OF_CENTRAL_DIRECTORY_LENGTH + 4) {
1031 0         0 return _formatError("file is too short");
1032             }
1033              
1034 47         117 my $seekOffset = 0;
1035 47         109 my $pos = -1;
1036 47         78 for (; ;) {
1037 47         79 $seekOffset += 512;
1038 47 100       122 $seekOffset = $fileLength if ($seekOffset > $fileLength);
1039 47 50       150 $fh->seek(-$seekOffset, IO::Seekable::SEEK_END)
1040             or return _ioError("seek failed");
1041 47         682 my $bytesRead = $fh->read($data, $seekOffset);
1042 47 50       1042 if ($bytesRead != $seekOffset) {
1043 0         0 return _ioError("read failed");
1044             }
1045 47         136 $pos = rindex($data, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING);
1046             last
1047 47 0 33     217 if ( $pos >= 0
      33        
1048             or $seekOffset == $fileLength
1049             or $seekOffset >= $Archive::Zip::ChunkSize);
1050             }
1051              
1052 47 50       179 if ($pos >= 0) {
1053 47 50       167 $fh->seek($pos - $seekOffset, IO::Seekable::SEEK_CUR)
1054             or return _ioError("seeking to EOCD");
1055 47         672 return AZ_OK;
1056             } else {
1057 0         0 return _formatError("can't find EOCD signature");
1058             }
1059             }
1060              
1061             # Used to avoid taint problems when chdir'ing.
1062             # Not intended to increase security in any way; just intended to shut up the -T
1063             # complaints. If your Cwd module is giving you unreliable returns from cwd()
1064             # you have bigger problems than this.
1065             sub _untaintDir {
1066 818     818   1472 my $dir = shift;
1067 818         4789 $dir =~ m/$UNTAINT/s;
1068 818         2497 return $1;
1069             }
1070              
1071             sub addTree {
1072 7     7 1 1727 my $self = shift;
1073              
1074 7         81 my ($root, $dest, $pred, $compressionLevel);
1075 7 50       31 if (ref($_[0]) eq 'HASH') {
1076 0         0 $root = $_[0]->{root};
1077 0         0 $dest = $_[0]->{zipName};
1078 0         0 $pred = $_[0]->{select};
1079 0         0 $compressionLevel = $_[0]->{compressionLevel};
1080             } else {
1081 7         25 ($root, $dest, $pred, $compressionLevel) = @_;
1082             }
1083              
1084 7 50       26 return _error("root arg missing in call to addTree()")
1085             unless defined($root);
1086 7 50       23 $dest = '' unless defined($dest);
1087 5     5   94 $pred = sub { -r }
1088 7 100       44 unless defined($pred);
1089              
1090 7         28 my @files;
1091 7         21660 my $startDir = _untaintDir(cwd());
1092              
1093 7 50       107 return _error('undef returned by _untaintDir on cwd ', cwd())
1094             unless $startDir;
1095              
1096             # This avoids chdir'ing in Find, in a way compatible with older
1097             # versions of File::Find.
1098             my $wanted = sub {
1099 650     650   1684 local $main::_ = $File::Find::name;
1100 650         1402 my $dir = _untaintDir($File::Find::dir);
1101 650         5900 chdir($startDir);
1102 650 50 33     3013 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
1103 0 0       0 push(@files, Win32::GetANSIPathName($File::Find::name)) if (&$pred);
1104 0         0 $dir = Win32::GetANSIPathName($dir);
1105             } else {
1106 650 100       1616 push(@files, $File::Find::name) if (&$pred);
1107             }
1108 650         110787 chdir($dir);
1109 7         213 };
1110              
1111 7 0 33     159 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
1112 0         0 $root = Win32::GetANSIPathName($root);
1113             }
1114             # File::Find will not untaint unless you explicitly pass the flag and regex pattern.
1115 7         1874 File::Find::find({ wanted => $wanted, untaint => 1, untaint_pattern => $UNTAINT }, $root);
1116              
1117 7         161 my $rootZipName = _asZipDirName($root, 1); # with trailing slash
1118 7 100       55 my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
1119              
1120 7         46 $dest = _asZipDirName($dest, 1); # with trailing slash
1121              
1122 7         59 foreach my $fileName (@files) {
1123 156         233 my $isDir;
1124 156 50 33     572 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
1125 0         0 $isDir = -d Win32::GetANSIPathName($fileName);
1126             } else {
1127 156         2252 $isDir = -d $fileName;
1128             }
1129              
1130             # normalize, remove leading ./
1131 156         707 my $archiveName = _asZipDirName($fileName, $isDir);
1132 156 100       323 if ($archiveName eq $rootZipName) { $archiveName = $dest }
  3         26  
1133 153         958 else { $archiveName =~ s{$pattern}{$dest} }
1134 156 50       666 next if $archiveName =~ m{^\.?/?$}; # skip current dir
1135 156 100       604 my $member =
1136             $isDir
1137             ? $self->addDirectory($fileName, $archiveName)
1138             : $self->addFile($fileName, $archiveName);
1139 156         417 $member->desiredCompressionLevel($compressionLevel);
1140              
1141 156 50       466 return _error("add $fileName failed in addTree()") if !$member;
1142             }
1143 7         155 return AZ_OK;
1144             }
1145              
1146             sub addTreeMatching {
1147 0     0 1 0 my $self = shift;
1148              
1149 0         0 my ($root, $dest, $pattern, $pred, $compressionLevel);
1150 0 0       0 if (ref($_[0]) eq 'HASH') {
1151 0         0 $root = $_[0]->{root};
1152 0         0 $dest = $_[0]->{zipName};
1153 0         0 $pattern = $_[0]->{pattern};
1154 0         0 $pred = $_[0]->{select};
1155 0         0 $compressionLevel = $_[0]->{compressionLevel};
1156             } else {
1157 0         0 ($root, $dest, $pattern, $pred, $compressionLevel) = @_;
1158             }
1159              
1160 0 0       0 return _error("root arg missing in call to addTreeMatching()")
1161             unless defined($root);
1162 0 0       0 $dest = '' unless defined($dest);
1163 0 0       0 return _error("pattern missing in call to addTreeMatching()")
1164             unless defined($pattern);
1165             my $matcher =
1166 0 0   0   0 $pred ? sub { m{$pattern} && &$pred } : sub { m{$pattern} && -r };
  0 0       0  
  0 0       0  
1167 0         0 return $self->addTree($root, $dest, $matcher, $compressionLevel);
1168             }
1169              
1170             # Check if one of the components of a path to the file or the file name
1171             # itself is an already existing symbolic link. If yes then return an
1172             # error. Continuing and writing to a file traversing a link posseses
1173             # a security threat, especially if the link was extracted from an
1174             # attacker-supplied archive. This would allow writing to an arbitrary
1175             # file. The same applies when using ".." to escape from a working
1176             # directory.
1177             sub _extractionNameIsSafe {
1178 83     83   160 my $name = shift;
1179 83         668 my ($volume, $directories) = File::Spec->splitpath($name, 1);
1180 83         523 my @directories = File::Spec->splitdir($directories);
1181 83 100       368 if (grep '..' eq $_, @directories) {
1182 2         16 return _error(
1183             "Could not extract $name safely: a parent directory is used");
1184             }
1185 81         160 my @path;
1186             my $path;
1187 81         224 for my $directory (@directories) {
1188 479         1328 push @path, $directory;
1189 479         4200 $path = File::Spec->catpath($volume, File::Spec->catdir(@path), '');
1190 479 50       6785 if (-l $path) {
1191 0         0 return _error(
1192             "Could not extract $name safely: $path is an existing symbolic link");
1193             }
1194 479 100       5434 if (!-e $path) {
1195 69         173 last;
1196             }
1197             }
1198 81         457 return AZ_OK;
1199             }
1200              
1201             # $zip->extractTree( $root, $dest [, $volume] );
1202             #
1203             # $root and $dest are Unix-style.
1204             # $volume is in local FS format.
1205             #
1206             sub extractTree {
1207 4     4 1 92 my $self = shift;
1208              
1209 4         15 my ($root, $dest, $volume);
1210 4 50       19 if (ref($_[0]) eq 'HASH') {
1211 0         0 $root = $_[0]->{root};
1212 0         0 $dest = $_[0]->{zipName};
1213 0         0 $volume = $_[0]->{volume};
1214             } else {
1215 4         13 ($root, $dest, $volume) = @_;
1216             }
1217              
1218 4 100       17 $root = '' unless defined($root);
1219 4 100       13 if (defined $dest) {
1220 3 50       27 if ($dest !~ m{/$}) {
1221 3         12 $dest .= '/';
1222             }
1223             } else {
1224 1         1 $dest = './';
1225             }
1226              
1227 4         15 my $pattern = "^\Q$root";
1228 4         40 my @members = $self->membersMatching($pattern);
1229              
1230 4         26 foreach my $member (@members) {
1231 43         212 my $fileName = $member->fileName(); # in Unix format
1232 43         359 $fileName =~ s{$pattern}{$dest}; # in Unix format
1233             # convert to platform format:
1234 43         160 $fileName = Archive::Zip::_asLocalName($fileName, $volume);
1235 43 100       114 if ((my $ret = _extractionNameIsSafe($fileName))
1236 1         4 != AZ_OK) { return $ret; }
1237 42         176 my $status = $member->extractToFileNamed($fileName);
1238 42 50       160 return $status if $status != AZ_OK;
1239             }
1240 3         21 return AZ_OK;
1241             }
1242              
1243             # $zip->updateMember( $memberOrName, $fileName );
1244             # Returns (possibly updated) member, if any; undef on errors.
1245              
1246             sub updateMember {
1247 157     157 1 228 my $self = shift;
1248              
1249 157         239 my ($oldMember, $fileName);
1250 157 50       332 if (ref($_[0]) eq 'HASH') {
1251 0         0 $oldMember = $_[0]->{memberOrZipName};
1252 0         0 $fileName = $_[0]->{name};
1253             } else {
1254 157         299 ($oldMember, $fileName) = @_;
1255             }
1256              
1257 157 50       322 if (!defined($fileName)) {
1258 0         0 _error("updateMember(): missing fileName argument");
1259 0         0 return undef;
1260             }
1261              
1262 157         2557 my @newStat = stat($fileName);
1263 157 50       539 if (!@newStat) {
1264 0         0 _ioError("Can't stat $fileName");
1265 0         0 return undef;
1266             }
1267              
1268 157         335 my $isDir = -d _;
1269              
1270 157         243 my $memberName;
1271              
1272 157 50       367 if (ref($oldMember)) {
1273 0         0 $memberName = $oldMember->fileName();
1274             } else {
1275 157   66     533 $oldMember = $self->memberNamed($memberName = $oldMember)
1276             || $self->memberNamed($memberName =
1277             _asZipDirName($oldMember, $isDir));
1278             }
1279              
1280 157 50 100     623 unless (defined($oldMember)
      66        
      33        
      66        
1281             && $oldMember->lastModTime() == $newStat[9]
1282             && $oldMember->isDirectory() == $isDir
1283             && ($isDir || ($oldMember->uncompressedSize() == $newStat[7]))) {
1284              
1285             # create the new member
1286 46 100       253 my $newMember =
1287             $isDir
1288             ? Archive::Zip::Member->newDirectoryNamed($fileName, $memberName)
1289             : Archive::Zip::Member->newFromFile($fileName, $memberName);
1290              
1291 46 50       118 unless (defined($newMember)) {
1292 0         0 _error("creation of member $fileName failed in updateMember()");
1293 0         0 return undef;
1294             }
1295              
1296             # replace old member or append new one
1297 46 100       86 if (defined($oldMember)) {
1298 6         40 $self->replaceMember($oldMember, $newMember);
1299             } else {
1300 40         122 $self->addMember($newMember);
1301             }
1302              
1303 46         241 return $newMember;
1304             }
1305              
1306 111         338 return $oldMember;
1307             }
1308              
1309             # $zip->updateTree( $root, [ $dest, [ $pred [, $mirror]]] );
1310             #
1311             # This takes the same arguments as addTree, but first checks to see
1312             # whether the file or directory already exists in the zip file.
1313             #
1314             # If the fourth argument $mirror is true, then delete all my members
1315             # if corresponding files were not found.
1316              
1317             sub updateTree {
1318 4     4 1 5342 my $self = shift;
1319              
1320 4         14 my ($root, $dest, $pred, $mirror, $compressionLevel);
1321 4 50       27 if (ref($_[0]) eq 'HASH') {
1322 0         0 $root = $_[0]->{root};
1323 0         0 $dest = $_[0]->{zipName};
1324 0         0 $pred = $_[0]->{select};
1325 0         0 $mirror = $_[0]->{mirror};
1326 0         0 $compressionLevel = $_[0]->{compressionLevel};
1327             } else {
1328 4         18 ($root, $dest, $pred, $mirror, $compressionLevel) = @_;
1329             }
1330              
1331 4 50       14 return _error("root arg missing in call to updateTree()")
1332             unless defined($root);
1333 4 50       11 $dest = '' unless defined($dest);
1334 157     157   2500 $pred = sub { -r }
1335 4 50       33 unless defined($pred);
1336              
1337 4         20 $dest = _asZipDirName($dest, 1);
1338 4         18 my $rootZipName = _asZipDirName($root, 1); # with trailing slash
1339 4 50       21 my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
1340              
1341 4         10 my @files;
1342 4         10824 my $startDir = _untaintDir(cwd());
1343              
1344 4 50       48 return _error('undef returned by _untaintDir on cwd ', cwd())
1345             unless $startDir;
1346              
1347             # This avoids chdir'ing in Find, in a way compatible with older
1348             # versions of File::Find.
1349             my $wanted = sub {
1350 157     157   406 local $main::_ = $File::Find::name;
1351 157         307 my $dir = _untaintDir($File::Find::dir);
1352 157         1455 chdir($startDir);
1353 157 50       428 push(@files, $File::Find::name) if (&$pred);
1354 157         3979 chdir($dir);
1355 4         170 };
1356              
1357 4         871 File::Find::find($wanted, $root);
1358              
1359             # Now @files has all the files that I could potentially be adding to
1360             # the zip. Only add the ones that are necessary.
1361             # For each file (updated or not), add its member name to @done.
1362 4         15 my %done;
1363 4         47 foreach my $fileName (@files) {
1364 157         2528 my @newStat = stat($fileName);
1365 157         548 my $isDir = -d _;
1366              
1367             # normalize, remove leading ./
1368 157         579 my $memberName = _asZipDirName($fileName, $isDir);
1369 157 100       362 if ($memberName eq $rootZipName) { $memberName = $dest }
  4         24  
1370 153         1007 else { $memberName =~ s{$pattern}{$dest} }
1371 157 50       634 next if $memberName =~ m{^\.?/?$}; # skip current dir
1372              
1373 157         650 $done{$memberName} = 1;
1374 157         413 my $changedMember = $self->updateMember($memberName, $fileName);
1375 157         502 $changedMember->desiredCompressionLevel($compressionLevel);
1376 157 50       585 return _error("updateTree failed to update $fileName")
1377             unless ref($changedMember);
1378             }
1379              
1380             # @done now has the archive names corresponding to all the found files.
1381             # If we're mirroring, delete all those members that aren't in @done.
1382 4 100       16 if ($mirror) {
1383 1         21 foreach my $member ($self->members()) {
1384             $self->removeMember($member)
1385 40 100       97 unless $done{$member->fileName()};
1386             }
1387             }
1388              
1389 4         309 return AZ_OK;
1390             }
1391              
1392             1;