File Coverage

blib/lib/Archive/Zip/Archive.pm
Criterion Covered Total %
statement 506 649 77.9
branch 204 384 53.1
condition 45 90 50.0
subroutine 53 65 81.5
pod 41 42 97.6
total 849 1230 69.0


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