File Coverage

blib/lib/Archive/Zip/ZipFileMember.pm
Criterion Covered Total %
statement 179 203 88.1
branch 66 116 56.9
condition 18 37 48.6
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 26     26   161 use strict;
  26         55  
  26         771  
4 26     26   124 use vars qw( $VERSION @ISA );
  26         80  
  26         1459  
5              
6             BEGIN {
7 26     26   81 $VERSION = '1.66';
8 26         1216 @ISA = qw ( Archive::Zip::FileMember );
9             }
10              
11 26         57465 use Archive::Zip qw(
12             :CONSTANTS
13             :ERROR_CODES
14             :PKZIP_CONSTANTS
15             :UTILITY_METHODS
16 26     26   168 );
  26         61  
17              
18             # Create a new Archive::Zip::ZipFileMember
19             # given a filename and optional open file handle
20             #
21             sub _newFromZipFile {
22 118     118   202 my $class = shift;
23 118         177 my $fh = shift;
24 118         190 my $externalFileName = shift;
25 118   50     281 my $archiveZip64 = shift // 0;
26 118   50     353 my $possibleEocdOffset = shift // 0; # normally 0
27              
28 118         562 my $self = $class->new(
29             'eocdCrc32' => 0,
30             'diskNumberStart' => 0,
31             'localHeaderRelativeOffset' => 0,
32             'dataOffset' => 0, # localHeaderRelativeOffset + header length
33             @_
34             );
35 118         307 $self->{'externalFileName'} = $externalFileName;
36 118         210 $self->{'fh'} = $fh;
37 118         244 $self->{'archiveZip64'} = $archiveZip64;
38 118         206 $self->{'possibleEocdOffset'} = $possibleEocdOffset;
39 118         260 return $self;
40             }
41              
42             sub isDirectory {
43 203     203 1 350 my $self = shift;
44 203   66     505 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 127     127   224 my $self = shift;
55 127         199 my $where = shift; # optional
56 127         207 my $previousWhere = shift; # optional
57              
58 127 50       424 $where = $self->localHeaderRelativeOffset() unless defined($where);
59              
60             # avoid loop on certain corrupt files (from Julian Field)
61 127 50 33     341 return _formatError("corrupt zip file")
62             if defined($previousWhere) && $where == $previousWhere;
63              
64 127         208 my $status;
65             my $signature;
66              
67 127         284 $status = $self->fh()->seek($where, IO::Seekable::SEEK_SET);
68 127 50       1661 return _ioError("seeking to local header") unless $status;
69              
70 127         392 ($status, $signature) =
71             _readSignature($self->fh(), $self->externalFileName(),
72             LOCAL_FILE_HEADER_SIGNATURE, 1);
73 127 50       343 return $status if $status == AZ_IO_ERROR;
74              
75             # retry with EOCD offset if any was given.
76 127 0 33     330 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 127         263 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   32 my $self = shift;
98 15         30 my $newClass = shift;
99 15 50       69 return $self if ref($self) eq $newClass;
100              
101 15         33 my $status = AZ_OK;
102              
103 15 50       50 if (_isSeekable($self->fh())) {
104 15         66 my $here = $self->fh()->tell();
105 15         104 $status = $self->_seekToLocalHeader();
106 15 50       99 $status = $self->_readLocalFileHeader() if $status == AZ_OK;
107 15         57 $self->fh()->seek($here, IO::Seekable::SEEK_SET);
108 15 50       236 return $status unless $status == AZ_OK;
109             }
110              
111 15         49 delete($self->{'eocdCrc32'});
112 15         49 delete($self->{'diskNumberStart'});
113 15         53 delete($self->{'localHeaderRelativeOffset'});
114 15         40 delete($self->{'dataOffset'});
115 15         28 delete($self->{'archiveZip64'});
116 15         40 delete($self->{'possibleEocdOffset'});
117              
118 15         85 return $self->SUPER::_become($newClass);
119             }
120              
121             sub diskNumberStart {
122 0     0 1 0 shift->{'diskNumberStart'};
123             }
124              
125             sub localHeaderRelativeOffset {
126 127     127 1 244 shift->{'localHeaderRelativeOffset'};
127             }
128              
129             sub dataOffset {
130 112     112 1 309 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 112     112   195 my $self = shift;
137 112         168 my $header;
138 112         304 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
139 112 50       679 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
140 0         0 return _ioError("reading local file header");
141             }
142 112         298 my $fileNameLength;
143             my $extraFieldLength;
144 112         0 my $bitFlag;
145             (
146             undef, # $self->{'versionNeededToExtract'},
147 112         390 $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 112 50       279 if ($fileNameLength) {
158 112 50       268 $self->fh()->seek($fileNameLength, IO::Seekable::SEEK_CUR)
159             or return _ioError("skipping local file name");
160             }
161              
162 112         1657 my $zip64 = 0;
163 112 100       308 if ($extraFieldLength) {
164             $bytesRead =
165 33         111 $self->fh()->read($self->{'localExtraField'}, $extraFieldLength);
166 33 50       390 if ($bytesRead != $extraFieldLength) {
167 0         0 return _ioError("reading local extra field");
168             }
169 33 100       123 if ($self->{'archiveZip64'}) {
170 23         33 my $status;
171             ($status, $zip64) =
172 23         83 $self->_extractZip64ExtraField($self->{'localExtraField'}, undef, undef);
173 23 50       59 return $status if $status != AZ_OK;
174 23   33     47 $self->{'zip64'} ||= $zip64;
175             }
176             }
177              
178 112         326 $self->{'dataOffset'} = $self->fh()->tell();
179              
180 112 100       680 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       42 $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         294 my $oldCrc32 = $self->{'eocdCrc32'};
192 16         54 my $oldCompressedSize = $self->{'compressedSize'};
193 16         34 my $oldUncompressedSize = $self->{'uncompressedSize'};
194              
195 16         53 my $status = $self->_readDataDescriptor($zip64);
196 16 50       56 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     79 $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     119 || $oldUncompressedSize != $self->{'uncompressedSize'});
208              
209 16 100       54 $self->{'crc32'} = 0
210             if $self->compressionMethod() == COMPRESSION_STORED ;
211             }
212              
213 112         223 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   41 my $self = shift;
225 15         31 my $header;
226 15         47 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
227 15 50       112 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
228 0         0 return _ioError("reading local file header");
229             }
230 15         75 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         81 $self->{'compressionMethod'}, $self->{'lastModFileDateTime'},
238             $crc32, $compressedSize,
239             $uncompressedSize, $fileNameLength,
240             $extraFieldLength
241             ) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);
242              
243 15 50       51 if ($fileNameLength) {
244 15         23 my $fileName;
245 15         48 $bytesRead = $self->fh()->read($fileName, $fileNameLength);
246 15 50       114 if ($bytesRead != $fileNameLength) {
247 0         0 return _ioError("reading local file name");
248             }
249 15         63 $self->fileName($fileName);
250             }
251              
252 15         36 my $zip64 = 0;
253 15 100       43 if ($extraFieldLength) {
254             $bytesRead =
255 8         29 $self->fh()->read($self->{'localExtraField'}, $extraFieldLength);
256 8 50       66 if ($bytesRead != $extraFieldLength) {
257 0         0 return _ioError("reading local extra field");
258             }
259 8 100       33 if ($self->{'archiveZip64'}) {
260 2         4 my $status;
261             ($status, $zip64) =
262 2         12 $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         44 $self->{'dataOffset'} = $self->fh()->tell();
271              
272 15 100       138 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       10 $self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)
279             or return _ioError("seeking to extended local header");
280              
281 2         45 my $status = $self->_readDataDescriptor($zip64);
282 2 50       9 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     142 || $self->{'uncompressedSize'} != $uncompressedSize);
288             }
289              
290             return
291             wantarray
292 15 50       60 ? (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   36 my $self = shift;
307 18         29 my $zip64 = shift;
308 18         99 my $signatureData;
309             my $header;
310 18         0 my $crc32;
311 18         0 my $compressedSize;
312 18         0 my $uncompressedSize;
313              
314 18         55 my $bytesRead = $self->fh()->read($signatureData, SIGNATURE_LENGTH);
315 18 50       258 return _ioError("reading header signature")
316             if $bytesRead != SIGNATURE_LENGTH;
317 18         48 my $signature = unpack(SIGNATURE_FORMAT, $signatureData);
318              
319 18         80 my $dataDescriptorLength;
320             my $dataDescriptorFormat;
321 18         0 my $dataDescriptorLengthNoSig;
322 18         0 my $dataDescriptorFormatNoSig;
323 18 50       59 if (! $zip64) {
324 18         30 $dataDescriptorLength = DATA_DESCRIPTOR_LENGTH;
325 18         49 $dataDescriptorFormat = DATA_DESCRIPTOR_FORMAT;
326 18         26 $dataDescriptorLengthNoSig = DATA_DESCRIPTOR_LENGTH_NO_SIG;
327 18         33 $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     121 if ($signature == DATA_DESCRIPTOR_SIGNATURE
338             && ($signature != $self->{'crc32'})) {
339 18         67 $bytesRead = $self->fh()->read($header, $dataDescriptorLength);
340 18 50       126 return _ioError("reading data descriptor")
341             if $bytesRead != $dataDescriptorLength;
342              
343 18         55 ($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       48 unless defined($self->{'eocdCrc32'});
357 18         37 $self->{'crc32'} = $crc32;
358 18         29 $self->{'compressedSize'} = $compressedSize;
359 18         31 $self->{'uncompressedSize'} = $uncompressedSize;
360              
361 18         40 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 71     71   143 my $self = shift;
369 71         366 my $fh = $self->fh();
370 71         184 my $header = '';
371 71         211 my $bytesRead = $fh->read($header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH);
372 71 50       483 if ($bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH) {
373 0         0 return _ioError("reading central dir header");
374             }
375 71         137 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 71         484 $self->{'localHeaderRelativeOffset'}
393             ) = unpack(CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header);
394              
395 71         160 $self->{'eocdCrc32'} = $self->{'crc32'};
396              
397 71 100       156 if ($fileNameLength) {
398 70         190 $bytesRead = $fh->read($self->{'fileName'}, $fileNameLength);
399 70 50       455 if ($bytesRead != $fileNameLength) {
400 0         0 _ioError("reading central dir filename");
401             }
402             }
403 71 100       184 if ($extraFieldLength) {
404 20         67 $bytesRead = $fh->read($self->{'cdExtraField'}, $extraFieldLength);
405 20 50       127 if ($bytesRead != $extraFieldLength) {
406 0         0 return _ioError("reading central dir extra field");
407             }
408 20 100       67 if ($self->{'archiveZip64'}) {
409             my ($status, $zip64) =
410             $self->_extractZip64ExtraField($self->{'cdExtraField'},
411             $self->{'uncompressedSize'},
412             $self->{'compressedSize'},
413             $self->{'localHeaderRelativeOffset'},
414 8         84 $self->{'diskNumberStart'});
415 8 50       19 return $status if $status != AZ_OK;
416 8   33     31 $self->{'zip64'} ||= $zip64;
417             }
418             }
419 71 50       191 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 71 100 100     355 if ( $self->{'uncompressedSize'} != $self->{'compressedSize'}
428             and $self->{'compressionMethod'} == COMPRESSION_STORED) {
429 2         2 $self->{'uncompressedSize'} = $self->{'compressedSize'};
430             }
431              
432 71         327 $self->desiredCompressionMethod($self->compressionMethod());
433              
434 71         171 return AZ_OK;
435             }
436              
437             sub rewindData {
438 112     112 1 199 my $self = shift;
439              
440 112         344 my $status = $self->SUPER::rewindData(@_);
441 112 50       282 return $status unless $status == AZ_OK;
442              
443 112 50       297 return AZ_IO_ERROR unless $self->fh();
444              
445 112         335 $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 112         316 $status = $self->_seekToLocalHeader();
451 112 50       287 return $status unless $status == AZ_OK;
452              
453             # skip local file header
454 112         324 $status = $self->_skipLocalFileHeader();
455 112 50       264 return $status unless $status == AZ_OK;
456              
457             # Seek to beginning of file data
458 112 50       250 $self->fh()->seek($self->dataOffset(), IO::Seekable::SEEK_SET)
459             or return _ioError("seeking to beginning of file data");
460              
461 112         1448 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 99     99   217 my ($self, $dataRef, $chunkSize) = @_;
469 99 50       214 return (0, AZ_OK) unless $chunkSize;
470 99 50       234 my $bytesRead = $self->fh()->read($$dataRef, $chunkSize)
471             or return (0, _ioError("reading data"));
472 99         1675 return ($bytesRead, AZ_OK);
473             }
474              
475             1;