File Coverage

blib/lib/Archive/Zip/ZipFileMember.pm
Criterion Covered Total %
statement 179 203 88.1
branch 68 120 56.6
condition 16 33 48.4
subroutine 16 17 94.1
pod 5 5 100.0
total 284 378 75.1


line stmt bran cond sub pod time code
1             package Archive::Zip::ZipFileMember;
2              
3 28     28   160 use strict;
  28         54  
  28         733  
4 28     28   123 use vars qw( $VERSION @ISA );
  28         46  
  28         1299  
5              
6             BEGIN {
7 28     28   95 $VERSION = '1.67';
8 28         1443 @ISA = qw ( Archive::Zip::FileMember );
9             }
10              
11 28         56977 use Archive::Zip qw(
12             :CONSTANTS
13             :ERROR_CODES
14             :PKZIP_CONSTANTS
15             :UTILITY_METHODS
16 28     28   171 );
  28         55  
17              
18             # Create a new Archive::Zip::ZipFileMember
19             # given a filename and optional open file handle
20             #
21             sub _newFromZipFile {
22 120     120   237 my $class = shift;
23 120         148 my $fh = shift;
24 120         155 my $externalFileName = shift;
25 120 50       225 my $archiveZip64 = @_ ? shift : 0;
26 120 50       223 my $possibleEocdOffset = @_ ? shift : 0; # normally 0
27              
28 120         470 my $self = $class->new(
29             'eocdCrc32' => 0,
30             'diskNumberStart' => 0,
31             'localHeaderRelativeOffset' => 0,
32             'dataOffset' => 0, # localHeaderRelativeOffset + header length
33             @_
34             );
35 120         231 $self->{'externalFileName'} = $externalFileName;
36 120         199 $self->{'fh'} = $fh;
37 120         166 $self->{'archiveZip64'} = $archiveZip64;
38 120         176 $self->{'possibleEocdOffset'} = $possibleEocdOffset;
39 120         286 return $self;
40             }
41              
42             sub isDirectory {
43 206     206 1 299 my $self = shift;
44 206   66     492 return (substr($self->fileName, -1, 1) eq '/'
45             and $self->uncompressedSize == 0);
46             }
47              
48             # Seek to the beginning of the local header, just past the signature.
49             # Verify that the local header signature is in fact correct.
50             # Update the localHeaderRelativeOffset if necessary by adding the possibleEocdOffset.
51             # Returns status.
52              
53             sub _seekToLocalHeader {
54 128     128   200 my $self = shift;
55 128         188 my $where = shift; # optional
56 128         174 my $previousWhere = shift; # optional
57              
58 128 50       423 $where = $self->localHeaderRelativeOffset() unless defined($where);
59              
60             # avoid loop on certain corrupt files (from Julian Field)
61 128 50 33     290 return _formatError("corrupt zip file")
62             if defined($previousWhere) && $where == $previousWhere;
63              
64 128         189 my $status;
65             my $signature;
66              
67 128         265 $status = $self->fh()->seek($where, IO::Seekable::SEEK_SET);
68 128 50       1497 return _ioError("seeking to local header") unless $status;
69              
70 128         373 ($status, $signature) =
71             _readSignature($self->fh(), $self->externalFileName(),
72             LOCAL_FILE_HEADER_SIGNATURE, 1);
73 128 50       324 return $status if $status == AZ_IO_ERROR;
74              
75             # retry with EOCD offset if any was given.
76 128 0 33     281 if ($status == AZ_FORMAT_ERROR && $self->{'possibleEocdOffset'}) {
77             $status = $self->_seekToLocalHeader(
78 0         0 $self->localHeaderRelativeOffset() + $self->{'possibleEocdOffset'},
79             $where
80             );
81 0 0       0 if ($status == AZ_OK) {
82             $self->{'localHeaderRelativeOffset'} +=
83 0         0 $self->{'possibleEocdOffset'};
84 0         0 $self->{'possibleEocdOffset'} = 0;
85             }
86             }
87              
88 128         245 return $status;
89             }
90              
91             # Because I'm going to delete the file handle, read the local file
92             # header if the file handle is seekable. If it is not, I assume that
93             # I've already read the local header.
94             # Return ( $status, $self )
95              
96             sub _become {
97 15     15   26 my $self = shift;
98 15         27 my $newClass = shift;
99 15 50       46 return $self if ref($self) eq $newClass;
100              
101 15         23 my $status = AZ_OK;
102              
103 15 50       43 if (_isSeekable($self->fh())) {
104 15         45 my $here = $self->fh()->tell();
105 15         106 $status = $self->_seekToLocalHeader();
106 15 50       72 $status = $self->_readLocalFileHeader() if $status == AZ_OK;
107 15         48 $self->fh()->seek($here, IO::Seekable::SEEK_SET);
108 15 50       202 return $status unless $status == AZ_OK;
109             }
110              
111 15         49 delete($self->{'eocdCrc32'});
112 15         37 delete($self->{'diskNumberStart'});
113 15         39 delete($self->{'localHeaderRelativeOffset'});
114 15         27 delete($self->{'dataOffset'});
115 15         30 delete($self->{'archiveZip64'});
116 15         35 delete($self->{'possibleEocdOffset'});
117              
118 15         77 return $self->SUPER::_become($newClass);
119             }
120              
121             sub diskNumberStart {
122 0     0 1 0 shift->{'diskNumberStart'};
123             }
124              
125             sub localHeaderRelativeOffset {
126 128     128 1 220 shift->{'localHeaderRelativeOffset'};
127             }
128              
129             sub dataOffset {
130 113     113 1 260 shift->{'dataOffset'};
131             }
132              
133             # Skip local file header, updating only extra field stuff.
134             # Assumes that fh is positioned before signature.
135             sub _skipLocalFileHeader {
136 113     113   171 my $self = shift;
137 113         138 my $header;
138 113         283 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
139 113 50       956 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
140 0         0 return _ioError("reading local file header");
141             }
142 113         253 my $fileNameLength;
143             my $extraFieldLength;
144 113         0 my $bitFlag;
145             (
146             undef, # $self->{'versionNeededToExtract'},
147 113         463 $bitFlag,
148             undef, # $self->{'compressionMethod'},
149             undef, # $self->{'lastModFileDateTime'},
150             undef, # $crc32,
151             undef, # $compressedSize,
152             undef, # $uncompressedSize,
153             $fileNameLength,
154             $extraFieldLength
155             ) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);
156              
157 113 50       340 if ($fileNameLength) {
158 113 50       226 $self->fh()->seek($fileNameLength, IO::Seekable::SEEK_CUR)
159             or return _ioError("skipping local file name");
160             }
161              
162 113         1539 my $zip64 = 0;
163 113 100       216 if ($extraFieldLength) {
164             $bytesRead =
165 34         87 $self->fh()->read($self->{'localExtraField'}, $extraFieldLength);
166 34 50       351 if ($bytesRead != $extraFieldLength) {
167 0         0 return _ioError("reading local extra field");
168             }
169 34 100       84 if ($self->{'archiveZip64'}) {
170 23         28 my $status;
171             ($status, $zip64) =
172 23         77 $self->_extractZip64ExtraField($self->{'localExtraField'}, undef, undef);
173 23 50       46 return $status if $status != AZ_OK;
174 23   33     54 $self->{'zip64'} ||= $zip64;
175             }
176             }
177              
178 113         259 $self->{'dataOffset'} = $self->fh()->tell();
179              
180 113 100       624 if ($bitFlag & GPBF_HAS_DATA_DESCRIPTOR_MASK) {
181              
182             # Read the crc32, compressedSize, and uncompressedSize from the
183             # extended data descriptor, which directly follows the compressed data.
184             #
185             # Skip over the compressed file data (assumes that EOCD compressedSize
186             # was correct)
187 16 50       38 $self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)
188             or return _ioError("seeking to extended local header");
189              
190             # these values should be set correctly from before.
191 16         172 my $oldCrc32 = $self->{'eocdCrc32'};
192 16         29 my $oldCompressedSize = $self->{'compressedSize'};
193 16         24 my $oldUncompressedSize = $self->{'uncompressedSize'};
194              
195 16         42 my $status = $self->_readDataDescriptor($zip64);
196 16 50       38 return $status unless $status == AZ_OK;
197              
198             # The buffer with encrypted data is prefixed with a new
199             # encrypted 12 byte header. The size only changes when
200             # the buffer is also compressed
201 16 100 100     70 $self->isEncrypted && $oldUncompressedSize > $self->{'uncompressedSize'}
202             and $oldUncompressedSize -= DATA_DESCRIPTOR_LENGTH;
203              
204             return _formatError(
205             "CRC or size mismatch while skipping data descriptor")
206             if ( $oldCrc32 != $self->{'crc32'}
207 16 50 33     90 || $oldUncompressedSize != $self->{'uncompressedSize'});
208              
209 16 100       41 $self->{'crc32'} = 0
210             if $self->compressionMethod() == COMPRESSION_STORED ;
211             }
212              
213 113         193 return AZ_OK;
214             }
215              
216             # Read from a local file header into myself. Returns AZ_OK (in
217             # scalar context) or a pair (AZ_OK, $headerSize) (in list
218             # context) if successful.
219             # Assumes that fh is positioned after signature.
220             # Note that crc32, compressedSize, and uncompressedSize will be 0 if
221             # GPBF_HAS_DATA_DESCRIPTOR_MASK is set in the bitFlag.
222              
223             sub _readLocalFileHeader {
224 15     15   29 my $self = shift;
225 15         28 my $header;
226 15         46 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
227 15 50       115 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
228 0         0 return _ioError("reading local file header");
229             }
230 15         67 my $fileNameLength;
231             my $crc32;
232 15         0 my $compressedSize;
233 15         0 my $uncompressedSize;
234 15         0 my $extraFieldLength;
235             (
236             $self->{'versionNeededToExtract'}, $self->{'bitFlag'},
237 15         77 $self->{'compressionMethod'}, $self->{'lastModFileDateTime'},
238             $crc32, $compressedSize,
239             $uncompressedSize, $fileNameLength,
240             $extraFieldLength
241             ) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);
242              
243 15 50       44 if ($fileNameLength) {
244 15         20 my $fileName;
245 15         44 $bytesRead = $self->fh()->read($fileName, $fileNameLength);
246 15 50       87 if ($bytesRead != $fileNameLength) {
247 0         0 return _ioError("reading local file name");
248             }
249 15         47 $self->fileName($fileName);
250             }
251              
252 15         24 my $zip64 = 0;
253 15 100       48 if ($extraFieldLength) {
254             $bytesRead =
255 8         33 $self->fh()->read($self->{'localExtraField'}, $extraFieldLength);
256 8 50       50 if ($bytesRead != $extraFieldLength) {
257 0         0 return _ioError("reading local extra field");
258             }
259 8 100       42 if ($self->{'archiveZip64'}) {
260 2         5 my $status;
261             ($status, $zip64) =
262 2         8 $self->_extractZip64ExtraField($self->{'localExtraField'},
263             $uncompressedSize,
264             $compressedSize);
265 2 50       6 return $status if $status != AZ_OK;
266 2   33     6 $self->{'zip64'} ||= $zip64;
267             }
268             }
269              
270 15         47 $self->{'dataOffset'} = $self->fh()->tell();
271              
272 15 100       135 if ($self->hasDataDescriptor()) {
273              
274             # Read the crc32, compressedSize, and uncompressedSize from the
275             # extended data descriptor.
276             # Skip over the compressed file data (assumes that EOCD compressedSize
277             # was correct)
278 2 50       6 $self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)
279             or return _ioError("seeking to extended local header");
280              
281 2         34 my $status = $self->_readDataDescriptor($zip64);
282 2 50       5 return $status unless $status == AZ_OK;
283             } else {
284             return _formatError(
285             "CRC or size mismatch after reading data descriptor")
286             if ( $self->{'crc32'} != $crc32
287 13 50 33     139 || $self->{'uncompressedSize'} != $uncompressedSize);
288             }
289              
290             return
291             wantarray
292 15 50       56 ? (AZ_OK,
293             SIGNATURE_LENGTH,
294             LOCAL_FILE_HEADER_LENGTH +
295             $fileNameLength +
296             $extraFieldLength)
297             : AZ_OK;
298             }
299              
300             # This will read the data descriptor, which is after the end of compressed file
301             # data in members that have GPBF_HAS_DATA_DESCRIPTOR_MASK set in their bitFlag.
302             # The only reliable way to find these is to rely on the EOCD compressedSize.
303             # Assumes that file is positioned immediately after the compressed data.
304             # Returns status; sets crc32, compressedSize, and uncompressedSize.
305             sub _readDataDescriptor {
306 18     18   35 my $self = shift;
307 18         24 my $zip64 = shift;
308 18         73 my $signatureData;
309             my $header;
310 18         0 my $crc32;
311 18         0 my $compressedSize;
312 18         0 my $uncompressedSize;
313              
314 18         39 my $bytesRead = $self->fh()->read($signatureData, SIGNATURE_LENGTH);
315 18 50       204 return _ioError("reading header signature")
316             if $bytesRead != SIGNATURE_LENGTH;
317 18         44 my $signature = unpack(SIGNATURE_FORMAT, $signatureData);
318              
319 18         64 my $dataDescriptorLength;
320             my $dataDescriptorFormat;
321 18         0 my $dataDescriptorLengthNoSig;
322 18         0 my $dataDescriptorFormatNoSig;
323 18 50       44 if (! $zip64) {
324 18         26 $dataDescriptorLength = DATA_DESCRIPTOR_LENGTH;
325 18         31 $dataDescriptorFormat = DATA_DESCRIPTOR_FORMAT;
326 18         27 $dataDescriptorLengthNoSig = DATA_DESCRIPTOR_LENGTH_NO_SIG;
327 18         25 $dataDescriptorFormatNoSig = DATA_DESCRIPTOR_FORMAT_NO_SIG
328             }
329             else {
330 0         0 $dataDescriptorLength = DATA_DESCRIPTOR_ZIP64_LENGTH;
331 0         0 $dataDescriptorFormat = DATA_DESCRIPTOR_ZIP64_FORMAT;
332 0         0 $dataDescriptorLengthNoSig = DATA_DESCRIPTOR_ZIP64_LENGTH_NO_SIG;
333 0         0 $dataDescriptorFormatNoSig = DATA_DESCRIPTOR_ZIP64_FORMAT_NO_SIG
334             }
335              
336             # unfortunately, the signature appears to be optional.
337 18 50 33     120 if ($signature == DATA_DESCRIPTOR_SIGNATURE
338             && ($signature != $self->{'crc32'})) {
339 18         51 $bytesRead = $self->fh()->read($header, $dataDescriptorLength);
340 18 50       98 return _ioError("reading data descriptor")
341             if $bytesRead != $dataDescriptorLength;
342              
343 18         44 ($crc32, $compressedSize, $uncompressedSize) =
344             unpack($dataDescriptorFormat, $header);
345             } else {
346 0         0 $bytesRead = $self->fh()->read($header, $dataDescriptorLengthNoSig);
347 0 0       0 return _ioError("reading data descriptor")
348             if $bytesRead != $dataDescriptorLengthNoSig;
349              
350 0         0 $crc32 = $signature;
351 0         0 ($compressedSize, $uncompressedSize) =
352             unpack($dataDescriptorFormatNoSig, $header);
353             }
354              
355             $self->{'eocdCrc32'} = $self->{'crc32'}
356 18 50       57 unless defined($self->{'eocdCrc32'});
357 18         38 $self->{'crc32'} = $crc32;
358 18         25 $self->{'compressedSize'} = $compressedSize;
359 18         25 $self->{'uncompressedSize'} = $uncompressedSize;
360              
361 18         34 return AZ_OK;
362             }
363              
364             # Read a Central Directory header. Return AZ_OK on success.
365             # Assumes that fh is positioned right after the signature.
366              
367             sub _readCentralDirectoryFileHeader {
368 72     72   135 my $self = shift;
369 72         284 my $fh = $self->fh();
370 72         259 my $header = '';
371 72         217 my $bytesRead = $fh->read($header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH);
372 72 50       401 if ($bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH) {
373 0         0 return _ioError("reading central dir header");
374             }
375 72         119 my ($fileNameLength, $extraFieldLength, $fileCommentLength);
376             (
377             $self->{'versionMadeBy'},
378             $self->{'fileAttributeFormat'},
379             $self->{'versionNeededToExtract'},
380             $self->{'bitFlag'},
381             $self->{'compressionMethod'},
382             $self->{'lastModFileDateTime'},
383             $self->{'crc32'},
384             $self->{'compressedSize'},
385             $self->{'uncompressedSize'},
386             $fileNameLength,
387             $extraFieldLength,
388             $fileCommentLength,
389             $self->{'diskNumberStart'},
390             $self->{'internalFileAttributes'},
391             $self->{'externalFileAttributes'},
392 72         450 $self->{'localHeaderRelativeOffset'}
393             ) = unpack(CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header);
394              
395 72         171 $self->{'eocdCrc32'} = $self->{'crc32'};
396              
397 72 100       152 if ($fileNameLength) {
398 71         176 $bytesRead = $fh->read($self->{'fileName'}, $fileNameLength);
399 71 50       389 if ($bytesRead != $fileNameLength) {
400 0         0 _ioError("reading central dir filename");
401             }
402             }
403 72 100       143 if ($extraFieldLength) {
404 21         52 $bytesRead = $fh->read($self->{'cdExtraField'}, $extraFieldLength);
405 21 50       110 if ($bytesRead != $extraFieldLength) {
406 0         0 return _ioError("reading central dir extra field");
407             }
408 21 100       110 if ($self->{'archiveZip64'}) {
409             my ($status, $zip64) =
410             $self->_extractZip64ExtraField($self->{'cdExtraField'},
411             $self->{'uncompressedSize'},
412             $self->{'compressedSize'},
413             $self->{'localHeaderRelativeOffset'},
414 8         80 $self->{'diskNumberStart'});
415 8 50       15 return $status if $status != AZ_OK;
416 8   33     47 $self->{'zip64'} ||= $zip64;
417             }
418             }
419 72 50       144 if ($fileCommentLength) {
420 0         0 $bytesRead = $fh->read($self->{'fileComment'}, $fileCommentLength);
421 0 0       0 if ($bytesRead != $fileCommentLength) {
422 0         0 return _ioError("reading central dir file comment");
423             }
424             }
425              
426             # NK 10/21/04: added to avoid problems with manipulated headers
427 72 100 100     286 if ( $self->{'uncompressedSize'} != $self->{'compressedSize'}
428             and $self->{'compressionMethod'} == COMPRESSION_STORED) {
429 2         4 $self->{'uncompressedSize'} = $self->{'compressedSize'};
430             }
431              
432 72         335 $self->desiredCompressionMethod($self->compressionMethod());
433              
434 72         155 return AZ_OK;
435             }
436              
437             sub rewindData {
438 113     113 1 187 my $self = shift;
439              
440 113         319 my $status = $self->SUPER::rewindData(@_);
441 113 50       260 return $status unless $status == AZ_OK;
442              
443 113 50       289 return AZ_IO_ERROR unless $self->fh();
444              
445 113         276 $self->fh()->clearerr();
446              
447             # Seek to local file header.
448             # The only reason that I'm doing this this way is that the extraField
449             # length seems to be different between the CD header and the LF header.
450 113         311 $status = $self->_seekToLocalHeader();
451 113 50       248 return $status unless $status == AZ_OK;
452              
453             # skip local file header
454 113         313 $status = $self->_skipLocalFileHeader();
455 113 50       251 return $status unless $status == AZ_OK;
456              
457             # Seek to beginning of file data
458 113 50       221 $self->fh()->seek($self->dataOffset(), IO::Seekable::SEEK_SET)
459             or return _ioError("seeking to beginning of file data");
460              
461 113         1363 return AZ_OK;
462             }
463              
464             # Return bytes read. Note that first parameter is a ref to a buffer.
465             # my $data;
466             # my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize );
467             sub _readRawChunk {
468 100     100   201 my ($self, $dataRef, $chunkSize) = @_;
469 100 50       225 return (0, AZ_OK) unless $chunkSize;
470 100 50       263 my $bytesRead = $self->fh()->read($$dataRef, $chunkSize)
471             or return (0, _ioError("reading data"));
472 100         1487 return ($bytesRead, AZ_OK);
473             }
474              
475             1;