File Coverage

blib/lib/Archive/Zip/Archive.pm
Criterion Covered Total %
statement 511 654 78.1
branch 209 390 53.5
condition 48 105 45.7
subroutine 55 65 84.6
pod 41 42 97.6
total 864 1256 68.7


line stmt bran cond sub pod time code
1             package Archive::Zip::Archive;
2              
3             # Represents a generic ZIP archive
4              
5 28     28   166 use strict;
  28         48  
  28         724  
6 28     28   115 use File::Path;
  28         47  
  28         1419  
7 28     28   148 use File::Find ();
  28         46  
  28         296  
8 28     28   98 use File::Spec ();
  28         49  
  28         309  
9 28     28   11297 use File::Copy ();
  28         54544  
  28         585  
10 28     28   157 use File::Basename;
  28         51  
  28         2235  
11 28     28   155 use Cwd;
  28         49  
  28         1250  
12 28     28   12364 use Encode qw(encode_utf8 decode_utf8);
  28         322033  
  28         2111  
13              
14 28     28   186 use vars qw( $VERSION @ISA );
  28         51  
  28         1516  
15              
16             BEGIN {
17 28     28   85 $VERSION = '1.67';
18 28         1050 @ISA = qw( Archive::Zip );
19             }
20              
21 28         179627 use Archive::Zip qw(
22             :CONSTANTS
23             :ERROR_CODES
24             :PKZIP_CONSTANTS
25             :UTILITY_METHODS
26 28     28   267 );
  28         52  
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 69     69 1 154 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 69         818 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 69         391 $self->{'members'} = [];
67 69 50       291 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{filename} : shift;
68 69 100       208 if ($fileName) {
69 4         18 my $status = $self->read($fileName);
70 4 50       16 return $status == AZ_OK ? $self : undef;
71             }
72 65         273 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 1141     1141 1 12896 @{shift->{'members'}};
  1141         3864  
82             }
83              
84             sub numberOfMembers {
85 180     180 1 12982 scalar(shift->members());
86             }
87              
88             sub memberNames {
89 21     21 1 5634 my $self = shift;
90 21         69 return map { $_->fileName() } $self->members();
  117         203  
91             }
92              
93             # return ref to member with given name or undef
94             sub memberNamed {
95 368     368 1 6420 my $self = shift;
96 368 50       678 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{zipName} : shift;
97 368         671 foreach my $member ($self->members()) {
98 10165 100       14388 return $member if $member->fileName() eq $fileName;
99             }
100 128         418 return undef;
101             }
102              
103             sub membersMatching {
104 17     17 1 19916 my $self = shift;
105 17 50       56 my $pattern = (ref($_[0]) eq 'HASH') ? shift->{regex} : shift;
106 17         50 return grep { $_->fileName() =~ /$pattern/ } $self->members();
  82         189  
107             }
108              
109             sub zip64 {
110 56     56 1 2137 shift->{'zip64'};
111             }
112              
113             sub desiredZip64Mode {
114 554     554 1 1866 my $self = shift;
115 554         718 my $desiredZip64Mode = $self->{'desiredZip64Mode'};
116 554 100       987 if (@_) {
117             $self->{'desiredZip64Mode'} =
118 4 50       51 ref($_[0]) eq 'HASH' ? shift->{desiredZip64Mode} : shift;
119             }
120 554         2071 return $desiredZip64Mode;
121             }
122              
123             sub versionMadeBy {
124 78     78 1 150 shift->{'versionMadeBy'};
125             }
126              
127             sub versionNeededToExtract {
128 78     78 1 132 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 96     96 1 292 shift->{'centralDirectorySize'};
149             }
150              
151             sub centralDirectoryOffsetWRTStartingDiskNumber {
152 48     48 1 119 shift->{'centralDirectoryOffsetWRTStartingDiskNumber'};
153             }
154              
155             sub zipfileComment {
156 78     78 1 118 my $self = shift;
157 78         151 my $comment = $self->{'zipfileComment'};
158 78 50       186 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 78         127 return $comment;
163             }
164              
165             sub eocdOffset {
166 120     120 1 635 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 5975 my $self = shift;
176 10 50       47 my $member = (ref($_[0]) eq 'HASH') ? shift->{memberOrZipName} : shift;
177 10 50       44 $member = $self->memberNamed($member) unless ref($member);
178 10 50       31 return undef unless $member;
179 10         52 my @newMembers = grep { $_ != $member } $self->members();
  112         179  
180 10         36 $self->{'members'} = \@newMembers;
181 10         45 return $member;
182             }
183              
184             sub replaceMember {
185 183     183 1 231 my $self = shift;
186              
187 183         221 my ($oldMember, $newMember);
188 183 50       354 if (ref($_[0]) eq 'HASH') {
189 0         0 $oldMember = $_[0]->{memberOrZipName};
190 0         0 $newMember = $_[0]->{newMember};
191             } else {
192 183         295 ($oldMember, $newMember) = @_;
193             }
194              
195 183 50       300 $oldMember = $self->memberNamed($oldMember) unless ref($oldMember);
196 183 50       318 return undef unless $oldMember;
197 183 50       383 return undef unless $newMember;
198             my @newMembers =
199 183 100       338 map { ($_ == $oldMember) ? $newMember : $_ } $self->members();
  11519         16047  
200 183         733 $self->{'members'} = \@newMembers;
201 183         322 return $oldMember;
202             }
203              
204             sub extractMember {
205 55     55 1 21543 my $self = shift;
206              
207 55         161 my ($member, $name);
208 55 50       146 if (ref($_[0]) eq 'HASH') {
209 0         0 $member = $_[0]->{memberOrZipName};
210 0         0 $name = $_[0]->{name};
211             } else {
212 55         121 ($member, $name) = @_;
213             }
214              
215 55 100       185 $member = $self->memberNamed($member) unless ref($member);
216 55 50       141 return _error('member not found') unless $member;
217 55         160 my $originalSize = $member->compressedSize();
218 55         97 my ($volumeName, $dirName, $fileName);
219 55 100       126 if (defined($name)) {
220 13         221 ($volumeName, $dirName, $fileName) = File::Spec->splitpath($name);
221 13         119 $dirName = File::Spec->catpath($volumeName, $dirName, '');
222             } else {
223 42         99 $name = $member->fileName();
224 42 100       110 if ((my $ret = _extractionNameIsSafe($name))
225 3         9 != AZ_OK) { return $ret; }
226 39         363 ($dirName = $name) =~ s{[^/]*$}{};
227 39         127 $dirName = Archive::Zip::_asLocalName($dirName);
228 39         71 $name = Archive::Zip::_asLocalName($name);
229             }
230 52 100 100     762 if ($dirName && !-d $dirName) {
231 6         801 mkpath($dirName);
232 6 50       239 return _ioError("can't create dir $dirName") if (!-d $dirName);
233             }
234 52         293 my $rc = $member->extractToFileNamed($name, @_);
235              
236             # TODO refactor this fix into extractToFileNamed()
237 52         124 $member->{'compressedSize'} = $originalSize;
238 52         208 return $rc;
239             }
240              
241             sub extractMemberWithoutPaths {
242 2     2 1 2422 my $self = shift;
243              
244 2         4 my ($member, $name);
245 2 50       7 if (ref($_[0]) eq 'HASH') {
246 0         0 $member = $_[0]->{memberOrZipName};
247 0         0 $name = $_[0]->{name};
248             } else {
249 2         5 ($member, $name) = @_;
250             }
251              
252 2 50       6 $member = $self->memberNamed($member) unless ref($member);
253 2 50       4 return _error('member not found') unless $member;
254 2         5 my $originalSize = $member->compressedSize();
255 2 50       7 return AZ_OK if $member->isDirectory();
256 2 100       5 unless ($name) {
257 1         3 $name = $member->fileName();
258 1         8 $name =~ s{.*/}{}; # strip off directories, if any
259 1 50       3 if ((my $ret = _extractionNameIsSafe($name))
260 1         3 != AZ_OK) { return $ret; }
261 0         0 $name = Archive::Zip::_asLocalName($name);
262             }
263 1         5 my $rc = $member->extractToFileNamed($name, @_);
264 1         3 $member->{'compressedSize'} = $originalSize;
265 1         4 return $rc;
266             }
267              
268             sub addMember {
269 190     190 1 3515 my $self = shift;
270 190 50       435 my $newMember = (ref($_[0]) eq 'HASH') ? shift->{member} : shift;
271 190 50       362 push(@{$self->{'members'}}, $newMember) if $newMember;
  190         420  
272 190 100 66     783 if($newMember && ($newMember->{bitFlag} & 0x800)
      100        
273             && !utf8::is_utf8($newMember->{fileName})){
274 2         52 $newMember->{fileName} = Encode::decode_utf8($newMember->{fileName});
275             }
276 190         335 return $newMember;
277             }
278              
279             sub addFile {
280 98     98 1 3770 my $self = shift;
281              
282 98         149 my ($fileName, $newName, $compressionLevel);
283 98 50       191 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 98         179 ($fileName, $newName, $compressionLevel) = @_;
289             }
290              
291 98 0 33     262 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
292 0         0 $fileName = Win32::GetANSIPathName($fileName);
293             }
294              
295 98         373 my $newMember = Archive::Zip::Member->newFromFile($fileName, $newName);
296 98         249 $newMember->desiredCompressionLevel($compressionLevel);
297 98 50 33     236 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 98         187 $self->addMember($newMember);
306             }
307            
308 98         158 return $newMember;
309             }
310              
311             sub addString {
312 13     13 1 12114 my $self = shift;
313              
314 13         34 my ($stringOrStringRef, $name, $compressionLevel);
315 13 50       54 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         101 ($stringOrStringRef, $name, $compressionLevel) = @_;
321             }
322              
323 13         175 my $newMember =
324             Archive::Zip::Member->newFromString($stringOrStringRef, $name);
325 13         73 $newMember->desiredCompressionLevel($compressionLevel);
326 13         78 return $self->addMember($newMember);
327             }
328              
329             sub addDirectory {
330 9     9 1 4113 my $self = shift;
331              
332 9         30 my ($name, $newName);
333 9 50       47 if (ref($_[0]) eq 'HASH') {
334 0         0 $name = $_[0]->{directoryName};
335 0         0 $newName = $_[0]->{zipName};
336             } else {
337 9         29 ($name, $newName) = @_;
338             }
339              
340 9 0 33     68 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
341 0         0 $name = Win32::GetANSIPathName($name);
342             }
343              
344 9         115 my $newMember = Archive::Zip::Member->newDirectoryNamed($name, $newName);
345 9 50 33     39 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 9         56 $self->addMember($newMember);
355             }
356            
357 9         25 return $newMember;
358             }
359              
360             # add either a file or a directory.
361              
362             sub addFileOrDirectory {
363 2     2 1 25 my $self = shift;
364              
365 2         4 my ($name, $newName, $compressionLevel);
366 2 50       6 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         5 ($name, $newName, $compressionLevel) = @_;
372             }
373              
374 2 0 33     8 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
375 0         0 $name = Win32::GetANSIPathName($name);
376             }
377              
378 2         10 $name =~ s{/$}{};
379 2 50       6 if ($newName) {
380 2         9 $newName =~ s{/$}{};
381             } else {
382 0         0 $newName = $name;
383             }
384 2 100       38 if (-f $name) {
    50          
385 1         7 return $self->addFile($name, $newName, $compressionLevel);
386             } elsif (-d $name) {
387 1         5 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 1410 my $self = shift;
395              
396 3         8 my ($member, $newContents);
397 3 50       10 if (ref($_[0]) eq 'HASH') {
398 0         0 $member = $_[0]->{memberOrZipName};
399 0         0 $newContents = $_[0]->{contents};
400             } else {
401 3         9 ($member, $newContents) = @_;
402             }
403              
404 3         62 my ($contents, $status) = (undef, AZ_OK);
405 3 50       9 if ($status == AZ_OK) {
406 3 50       9 $status = _error('No member name given') unless defined($member);
407             }
408 3 50 33     16 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       8 if ($status == AZ_OK) {
414 3         16 ($contents, $status) = $member->contents($newContents);
415             }
416              
417             return
418             wantarray
419 3 50       15 ? ($contents, $status)
420             : $contents;
421             }
422              
423             sub writeToFileNamed {
424 75     75 1 23187 my $self = shift;
425             my $fileName =
426 75 50       306 (ref($_[0]) eq 'HASH') ? shift->{filename} : shift; # local FS format
427 75         254 foreach my $member ($self->members()) {
428 216 50       797 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 75         281 my ($status, $fh) = _newFileHandle($fileName, 'w');
435 75 50       495 return _ioError("Can't open $fileName for write") unless $status;
436 75         325 $status = $self->writeToFileHandle($fh, 1);
437 75         262 $fh->close();
438 75         6675 $fh = undef;
439              
440 75         397 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 78     78 1 27889 my $self = shift;
447              
448 78         166 my ($fh, $fhIsSeekable);
449 78 50       336 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 78         126 $fh = shift;
455 78 100       253 $fhIsSeekable = @_ ? shift : _isSeekable($fh);
456             }
457              
458 78 50       224 return _error('No filehandle given') unless $fh;
459 78 50       1081 return _ioError('filehandle not open') unless $fh->opened();
460 78         996 _binmode($fh);
461              
462             # Find out where the current position is.
463 78 100       923 my $offset = $fhIsSeekable ? $fh->tell() : 0;
464 78 50       483 $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 78         229 foreach my $member ($self->members()) {
484 243         524 $member->{'wasWritten'} = 0;
485             }
486              
487 78         183 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 243         448 $member->{'zip64'} = 0;
508              
509 243         605 my ($status, $memberSize) =
510             $member->_writeToFileHandle($fh, $fhIsSeekable, $offset,
511             $self->desiredZip64Mode());
512 243         677 $member->endRead();
513 243 50       467 return $status if $status != AZ_OK;
514              
515 243         353 $offset += $memberSize;
516              
517             # Change this so it reflects write status and last
518             # successful position
519 243         339 $member->{'wasWritten'} = 1;
520 243         458 $self->{'writeCentralDirectoryOffset'} = $offset;
521             }
522              
523 78         339 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 234     234   426 shift->{'writeCentralDirectoryOffset'};
587             }
588              
589             sub _writeEOCDOffset {
590 109     109   247 shift->{'writeEOCDOffset'};
591             }
592              
593             # Expects to have _writeEOCDOffset() set
594             sub _writeEndOfCentralDirectory {
595 78     78   186 my ($self, $fh, $membersZip64) = @_;
596              
597 78         127 my $zip64 = 0;
598 78         236 my $versionMadeBy = $self->versionMadeBy();
599 78         191 my $versionNeededToExtract = $self->versionNeededToExtract();
600 78         121 my $diskNumber = 0;
601 78         119 my $diskNumberWithStartOfCentralDirectory = 0;
602 78         199 my $numberOfCentralDirectoriesOnThisDisk = $self->numberOfMembers();
603 78         164 my $numberOfCentralDirectories = $self->numberOfMembers();
604 78         204 my $centralDirectorySize =
605             $self->_writeEOCDOffset() - $self->_writeCentralDirectoryOffset();
606 78         170 my $centralDirectoryOffsetWRTStartingDiskNumber =
607             $self->_writeCentralDirectoryOffset();
608 78         235 my $zipfileCommentLength = length($self->zipfileComment());
609              
610 78         141 my $eocdDataZip64 = 0;
611 78   33     355 $eocdDataZip64 ||= $numberOfCentralDirectoriesOnThisDisk > 0xffff;
612 78   33     402 $eocdDataZip64 ||= $numberOfCentralDirectories > 0xffff;
613 78   33     282 $eocdDataZip64 ||= $centralDirectorySize > 0xffffffff;
614 78   33     320 $eocdDataZip64 ||= $centralDirectoryOffsetWRTStartingDiskNumber > 0xffffffff;
615              
616 78 100 66     364 if ( $membersZip64
      100        
617             || $eocdDataZip64
618             || $self->desiredZip64Mode() == ZIP64_EOCD) {
619 31         41 return _zip64NotSupported() unless ZIP64_SUPPORTED;
620              
621 31         77 $zip64 = 1;
622 31 100       112 $versionMadeBy = 45 if ($versionMadeBy == 0);
623 31 100       76 $versionNeededToExtract = 45 if ($versionNeededToExtract < 45);
624              
625 31 50       116 $self->_print($fh, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE_STRING)
626             or return _ioError('writing zip64 EOCD record signature');
627              
628 31         331 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 31 50       88 $self->_print($fh, $record)
642             or return _ioError('writing zip64 EOCD record');
643              
644 31 50       1384 $self->_print($fh, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE_STRING)
645             or return _ioError('writing zip64 EOCD locator signature');
646              
647 31         222 my $locator = pack(
648             ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_FORMAT,
649             0,
650             $self->_writeEOCDOffset(),
651             1
652             );
653 31 50       76 $self->_print($fh, $locator)
654             or return _ioError('writing zip64 EOCD locator');
655             }
656              
657 78 50       444 $self->_print($fh, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING)
658             or return _ioError('writing EOCD Signature');
659              
660 78 50       1196 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 78 50       200 $self->_print($fh, $header)
675             or return _ioError('writing EOCD header');
676 78 50       529 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 78         141 $self->{'zip64'} = $zip64;
683 78         118 $self->{'versionMadeBy'} = $versionMadeBy;
684 78         128 $self->{'versionNeededToExtract'} = $versionNeededToExtract;
685              
686 78         192 return AZ_OK;
687             }
688              
689             # $offset can be specified to truncate a zip file.
690             sub writeCentralDirectory {
691 78     78 1 128 my $self = shift;
692              
693 78         130 my ($fh, $offset);
694 78 50       326 if (ref($_[0]) eq 'HASH') {
695 0         0 $fh = $_[0]->{fileHandle};
696 0         0 $offset = $_[0]->{offset};
697             } else {
698 78         171 ($fh, $offset) = @_;
699             }
700              
701 78 50       167 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 78         227 $offset = $self->_writeCentralDirectoryOffset();
707             }
708              
709 78         145 my $membersZip64 = 0;
710 78         312 foreach my $member ($self->members()) {
711 243         481 my ($status, $headerSize) =
712             $member->_writeCentralDirectoryFileHeader($fh, $self->desiredZip64Mode());
713 243 50       472 return $status if $status != AZ_OK;
714 243   100     804 $membersZip64 ||= $member->zip64();
715 243         321 $offset += $headerSize;
716 243         528 $self->{'writeEOCDOffset'} = $offset;
717             }
718              
719 78         242 return $self->_writeEndOfCentralDirectory($fh, $membersZip64);
720             }
721              
722             sub read {
723 48     48 1 10935 my $self = shift;
724 48 50       153 my $fileName = (ref($_[0]) eq 'HASH') ? shift->{filename} : shift;
725 48 50       125 return _error('No filename given') unless $fileName;
726 48         185 my ($status, $fh) = _newFileHandle($fileName, 'r');
727 48 50       180 return _ioError("opening $fileName for read") unless $status;
728              
729 48         231 $status = $self->readFromFileHandle($fh, $fileName);
730 48 50       110 return $status if $status != AZ_OK;
731              
732 48         197 $fh->close();
733 48         934 $self->{'fileName'} = $fileName;
734 48         285 return AZ_OK;
735             }
736              
737             sub readFromFileHandle {
738 48     48 1 95 my $self = shift;
739              
740 48         101 my ($fh, $fileName);
741 48 50       159 if (ref($_[0]) eq 'HASH') {
742 0         0 $fh = $_[0]->{fileHandle};
743 0         0 $fileName = $_[0]->{filename};
744             } else {
745 48         105 ($fh, $fileName) = @_;
746             }
747              
748 48 50       172 $fileName = $fh unless defined($fileName);
749 48 50       118 return _error('No filehandle given') unless $fh;
750 48 50       315 return _ioError('filehandle not open') unless $fh->opened();
751              
752 48         408 _binmode($fh);
753 48         462 $self->{'fileName'} = "$fh";
754              
755             # TODO: how to support non-seekable zips?
756 48 50       146 return _error('file not seekable')
757             unless _isSeekable($fh);
758              
759 48         278 $fh->seek(0, 0); # rewind the file
760              
761 48         666 my $status = $self->_findEndOfCentralDirectory($fh);
762 48 50       133 return $status if $status != AZ_OK;
763              
764 48         75 my $eocdPosition;
765 48         170 ($status, $eocdPosition) = $self->_readEndOfCentralDirectory($fh, $fileName);
766 48 50       111 return $status if $status != AZ_OK;
767              
768 48         176 my $zip64 = $self->zip64();
769              
770 48 50       160 $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 48         623 $self->{'eocdOffset'} = $eocdPosition - $self->centralDirectorySize() # here
777             - $self->centralDirectoryOffsetWRTStartingDiskNumber();
778              
779 48         83 for (; ;) {
780 120         605 my $newMember =
781             Archive::Zip::Member->_newFromZipFile($fh, $fileName, $zip64,
782             $self->eocdOffset());
783 120         159 my $signature;
784 120         301 ($status, $signature) = _readSignature($fh, $fileName);
785 120 50       244 return $status if $status != AZ_OK;
786 120 100       206 if (! $zip64) {
787 98 100       415 last if $signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE;
788             }
789             else {
790 22 100       69 last if $signature == ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE;
791             }
792 72         251 $status = $newMember->_readCentralDirectoryFileHeader();
793 72 50       161 return $status if $status != AZ_OK;
794 72         210 $status = $newMember->endRead();
795 72 50       155 return $status if $status != AZ_OK;
796              
797 72 100       158 if ($newMember->isDirectory()) {
798 12         51 $newMember->_become('Archive::Zip::DirectoryMember');
799             # Ensure above call suceeded to avoid future trouble
800 12 50       54 $newMember->_ISA('Archive::Zip::DirectoryMember') or
801             return $self->_error('becoming Archive::Zip::DirectoryMember');
802             }
803              
804 72 100 66     247 if(($newMember->{bitFlag} & 0x800) && !utf8::is_utf8($newMember->{fileName})){
805 8         76 $newMember->{fileName} = Encode::decode_utf8($newMember->{fileName});
806             }
807              
808 72         149 push(@{$self->{'members'}}, $newMember);
  72         167  
809             }
810              
811 48         153 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 48     48   79 my $self = shift;
834 48         72 my $fh = shift;
835 48         71 my $fileName = shift;
836              
837             # Remember current position, which is just before the EOCD
838             # signature
839 48         115 my $eocdPosition = $fh->tell();
840              
841             # Reset the zip64 format flag
842 48         228 $self->{'zip64'} = 0;
843 48         71 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 48         70 if (! ZIP64_SUPPORTED) {
  48         57  
854             last NOZIP64;
855             }
856              
857 48 50       125 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 48 50       114 $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 48         518 my $zip64EOCDLocatorPosition =
866             $eocdPosition - ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH - SIGNATURE_LENGTH;
867              
868 48         90 my $status;
869             my $bytesRead;
870              
871             # Read potential zip64 EOCD locator signature
872 48         188 $status =
873             _readSignature($fh, $fileName,
874             ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE, 1);
875 48 50       122 return $status if $status == AZ_IO_ERROR;
876 48 50       110 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 48         90 my $locator = '';
884 48         136 $bytesRead = $fh->read($locator, ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH);
885 48 50       301 if ($bytesRead != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_LENGTH) {
886 0         0 return _ioError("reading zip64 EOCD locator");
887             }
888 48         193 (undef, $zip64EOCDPosition, undef) =
889             unpack(ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_FORMAT, $locator);
890 48 100       481 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 25         59 last NOZIP64;
894             }
895              
896             # Skip to potential zip64 EOCD record
897 23 50       66 $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         319 $status =
902             _readSignature($fh, $fileName,
903             ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE, 1);
904 23 50       51 return $status if $status == AZ_IO_ERROR;
905 23 50       46 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         54 my $record = '';
914 23         60 $bytesRead = $fh->read($record, ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_LENGTH);
915 23 50       123 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         50 my ($zip64EODCRecordSize) = unpack("Q<", $record);
923 23 100       60 if ($zip64EOCDPosition + 12 + $zip64EODCRecordSize != $zip64EOCDLocatorPosition) {
924 16 50       30 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
925             or return _ioError("seeking to EOCD");
926 16         252 last NOZIP64;
927             }
928              
929 7         15 $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         44 $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       24 $fh->seek($eocdPosition, IO::Seekable::SEEK_SET)
945             or return _ioError("seeking to EOCD");
946             }
947              
948             # Skip past signature
949 48 50       238 $fh->seek(SIGNATURE_LENGTH, IO::Seekable::SEEK_CUR)
950             or return _ioError("seeking past EOCD signature");
951              
952 48         595 my $header = '';
953 48         166 my $bytesRead = $fh->read($header, END_OF_CENTRAL_DIRECTORY_LENGTH);
954 48 50       512 if ($bytesRead != END_OF_CENTRAL_DIRECTORY_LENGTH) {
955 0         0 return _ioError("reading end of central directory");
956             }
957              
958 48         82 my $zipfileCommentLength;
959 48 100       135 if (! $self->{'zip64'}) {
960             (
961             $self->{'diskNumber'},
962             $self->{'diskNumberWithStartOfCentralDirectory'},
963             $self->{'numberOfCentralDirectoriesOnThisDisk'},
964             $self->{'numberOfCentralDirectories'},
965             $self->{'centralDirectorySize'},
966 41         198 $self->{'centralDirectoryOffsetWRTStartingDiskNumber'},
967             $zipfileCommentLength
968             ) = unpack(END_OF_CENTRAL_DIRECTORY_FORMAT, $header);
969              
970 41 50 33     519 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         31 $zipfileCommentLength
993             ) = unpack(END_OF_CENTRAL_DIRECTORY_FORMAT, $header);
994             }
995              
996 48 50       122 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 48 100       132 if (! $self->{'zip64'}) {
1006             return
1007             wantarray
1008 41 50       153 ? (AZ_OK, $eocdPosition)
1009             : AZ_OK;
1010             }
1011             else {
1012             return
1013             wantarray
1014 7 50       28 ? (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 48     48   94 my $self = shift;
1024 48         75 my $fh = shift;
1025 48         141 my $data = '';
1026 48 50       153 $fh->seek(0, IO::Seekable::SEEK_END)
1027             or return _ioError("seeking to end");
1028              
1029 48         620 my $fileLength = $fh->tell();
1030 48 50       319 if ($fileLength < END_OF_CENTRAL_DIRECTORY_LENGTH + 4) {
1031 0         0 return _formatError("file is too short");
1032             }
1033              
1034 48         73 my $seekOffset = 0;
1035 48         76 my $pos = -1;
1036 48         71 for (; ;) {
1037 48         75 $seekOffset += 512;
1038 48 100       106 $seekOffset = $fileLength if ($seekOffset > $fileLength);
1039 48 50       124 $fh->seek(-$seekOffset, IO::Seekable::SEEK_END)
1040             or return _ioError("seek failed");
1041 48         720 my $bytesRead = $fh->read($data, $seekOffset);
1042 48 50       1031 if ($bytesRead != $seekOffset) {
1043 0         0 return _ioError("read failed");
1044             }
1045 48         111 $pos = rindex($data, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING);
1046             last
1047 48 0 33     224 if ( $pos >= 0
      33        
1048             or $seekOffset == $fileLength
1049             or $seekOffset >= $Archive::Zip::ChunkSize);
1050             }
1051              
1052 48 50       148 if ($pos >= 0) {
1053 48 50       154 $fh->seek($pos - $seekOffset, IO::Seekable::SEEK_CUR)
1054             or return _ioError("seeking to EOCD");
1055 48         586 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 793     793   1157 my $dir = shift;
1067 793         3124 $dir =~ m/$UNTAINT/s;
1068 793         2596 return $1;
1069             }
1070              
1071             sub addTree {
1072 5     5 1 56 my $self = shift;
1073              
1074 5         12 my ($root, $dest, $pred, $compressionLevel);
1075 5 50       19 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 5         13 ($root, $dest, $pred, $compressionLevel) = @_;
1082             }
1083              
1084 5 50       16 return _error("root arg missing in call to addTree()")
1085             unless defined($root);
1086 5 50       21 $dest = '' unless defined($dest);
1087 5     5   81 $pred = sub { -r }
1088 5 100       32 unless defined($pred);
1089              
1090 5         14 my @files;
1091 5         13454 my $startDir = _untaintDir(cwd());
1092              
1093 5 50       77 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 527     527   1110 local $main::_ = $File::Find::name;
1100 527         836 my $dir = _untaintDir($File::Find::dir);
1101 527         3747 chdir($startDir);
1102 527 50 33     1896 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 527 100       1011 push(@files, $File::Find::name) if (&$pred);
1107             }
1108 527         19012 chdir($dir);
1109 5         118 };
1110              
1111 5 0 33     93 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 5         1213 File::Find::find({ wanted => $wanted, untaint => 1, untaint_pattern => $UNTAINT }, $root);
1116              
1117 5         97 my $rootZipName = _asZipDirName($root, 1); # with trailing slash
1118 5 100       42 my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
1119              
1120 5         21 $dest = _asZipDirName($dest, 1); # with trailing slash
1121              
1122 5         35 foreach my $fileName (@files) {
1123 89         136 my $isDir;
1124 89 50 33     267 if ($^O eq 'MSWin32' && $Archive::Zip::UNICODE) {
1125 0         0 $isDir = -d Win32::GetANSIPathName($fileName);
1126             } else {
1127 89         1017 $isDir = -d $fileName;
1128             }
1129              
1130             # normalize, remove leading ./
1131 89         311 my $archiveName = _asZipDirName($fileName, $isDir);
1132 89 100       177 if ($archiveName eq $rootZipName) { $archiveName = $dest }
  2         10  
1133 87         452 else { $archiveName =~ s{$pattern}{$dest} }
1134 89 50       314 next if $archiveName =~ m{^\.?/?$}; # skip current dir
1135 89 100       300 my $member =
1136             $isDir
1137             ? $self->addDirectory($fileName, $archiveName)
1138             : $self->addFile($fileName, $archiveName);
1139 89         222 $member->desiredCompressionLevel($compressionLevel);
1140              
1141 89 50       213 return _error("add $fileName failed in addTree()") if !$member;
1142             }
1143 5         85 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. <https://bugzilla.redhat.com/show_bug.cgi?id=1591449>
1177             sub _extractionNameIsSafe {
1178 53     53   82 my $name = shift;
1179 53         467 my ($volume, $directories) = File::Spec->splitpath($name, 1);
1180 53         275 my @directories = File::Spec->splitdir($directories);
1181 53 100       194 if (grep '..' eq $_, @directories) {
1182 2         8 return _error(
1183             "Could not extract $name safely: a parent directory is used");
1184             }
1185 51         72 my @path;
1186             my $path;
1187 51         118 for my $directory (@directories) {
1188 194         395 push @path, $directory;
1189 194         1415 $path = File::Spec->catpath($volume, File::Spec->catdir(@path), '');
1190 194 100       2272 if (-l $path) {
1191 5         28 return _error(
1192             "Could not extract $name safely: $path is an existing symbolic link");
1193             }
1194 189 100       1631 if (!-e $path) {
1195 34         75 last;
1196             }
1197             }
1198 46         216 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 5     5 1 1509 my $self = shift;
1208              
1209 5         11 my ($root, $dest, $volume);
1210 5 50       14 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 5         16 ($root, $dest, $volume) = @_;
1216             }
1217              
1218 5 100       66 $root = '' unless defined($root);
1219 5 100       60 if (defined $dest) {
1220 2 50       7 if ($dest !~ m{/$}) {
1221 2         6 $dest .= '/';
1222             }
1223             } else {
1224 3         7 $dest = './';
1225             }
1226              
1227 5         15 my $pattern = "^\Q$root";
1228 5         18 my @members = $self->membersMatching($pattern);
1229              
1230 5         15 foreach my $member (@members) {
1231 10         23 my $fileName = $member->fileName(); # in Unix format
1232 10         70 $fileName =~ s{$pattern}{$dest}; # in Unix format
1233             # convert to platform format:
1234 10         34 $fileName = Archive::Zip::_asLocalName($fileName, $volume);
1235 10 100       44 if ((my $ret = _extractionNameIsSafe($fileName))
1236 3         10 != AZ_OK) { return $ret; }
1237 7         33 my $status = $member->extractToFileNamed($fileName);
1238 7 50       25 return $status if $status != AZ_OK;
1239             }
1240 2         9 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 253     253 1 404 my $self = shift;
1248              
1249 253         344 my ($oldMember, $fileName);
1250 253 50       876 if (ref($_[0]) eq 'HASH') {
1251 0         0 $oldMember = $_[0]->{memberOrZipName};
1252 0         0 $fileName = $_[0]->{name};
1253             } else {
1254 253         404 ($oldMember, $fileName) = @_;
1255             }
1256              
1257 253 50       479 if (!defined($fileName)) {
1258 0         0 _error("updateMember(): missing fileName argument");
1259 0         0 return undef;
1260             }
1261              
1262 253         3158 my @newStat = stat($fileName);
1263 253 50       660 if (!@newStat) {
1264 0         0 _ioError("Can't stat $fileName");
1265 0         0 return undef;
1266             }
1267              
1268 253         451 my $isDir = -d _;
1269              
1270 253         286 my $memberName;
1271              
1272 253 50       384 if (ref($oldMember)) {
1273 0         0 $memberName = $oldMember->fileName();
1274             } else {
1275 253   66     767 $oldMember = $self->memberNamed($memberName = $oldMember)
1276             || $self->memberNamed($memberName =
1277             _asZipDirName($oldMember, $isDir));
1278             }
1279              
1280 253 50 100     888 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 244 100       766 my $newMember =
1287             $isDir
1288             ? Archive::Zip::Member->newDirectoryNamed($fileName, $memberName)
1289             : Archive::Zip::Member->newFromFile($fileName, $memberName);
1290              
1291 244 50       470 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 244 100       398 if (defined($oldMember)) {
1298 180         414 $self->replaceMember($oldMember, $newMember);
1299             } else {
1300 64         137 $self->addMember($newMember);
1301             }
1302              
1303 244         1174 return $newMember;
1304             }
1305              
1306 9         26 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 2857 my $self = shift;
1319              
1320 4         12 my ($root, $dest, $pred, $mirror, $compressionLevel);
1321 4 50       17 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         12 ($root, $dest, $pred, $mirror, $compressionLevel) = @_;
1329             }
1330              
1331 4 50       25 return _error("root arg missing in call to updateTree()")
1332             unless defined($root);
1333 4 50       15 $dest = '' unless defined($dest);
1334 257     257   2998 $pred = sub { -r }
1335 4 50       37 unless defined($pred);
1336              
1337 4         14 $dest = _asZipDirName($dest, 1);
1338 4         10 my $rootZipName = _asZipDirName($root, 1); # with trailing slash
1339 4 50       17 my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E";
1340              
1341 4         6 my @files;
1342 4         23452 my $startDir = _untaintDir(cwd());
1343              
1344 4 50       35 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 257     257   615 local $main::_ = $File::Find::name;
1351 257         465 my $dir = _untaintDir($File::Find::dir);
1352 257         1648 chdir($startDir);
1353 257 50       519 push(@files, $File::Find::name) if (&$pred);
1354 257         4823 chdir($dir);
1355 4         98 };
1356              
1357 4         846 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         14 my %done;
1363 4         32 foreach my $fileName (@files) {
1364 257         3488 my @newStat = stat($fileName);
1365 257         640 my $isDir = -d _;
1366              
1367             # normalize, remove leading ./
1368 257         753 my $memberName = _asZipDirName($fileName, $isDir);
1369 257 100       506 if ($memberName eq $rootZipName) { $memberName = $dest }
  4         14  
1370 253         1278 else { $memberName =~ s{$pattern}{$dest} }
1371 257 100       1095 next if $memberName =~ m{^\.?/?$}; # skip current dir
1372              
1373 253         1053 $done{$memberName} = 1;
1374 253         606 my $changedMember = $self->updateMember($memberName, $fileName);
1375 253         620 $changedMember->desiredCompressionLevel($compressionLevel);
1376 253 50       1039 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       13 if ($mirror) {
1383 1         22 foreach my $member ($self->members()) {
1384             $self->removeMember($member)
1385 64 100       96 unless $done{$member->fileName()};
1386             }
1387             }
1388              
1389 4         235 return AZ_OK;
1390             }
1391              
1392             1;