File Coverage

blib/lib/Archive/Zip/ZipFileMember.pm
Criterion Covered Total %
statement 183 203 90.1
branch 69 120 57.5
condition 16 33 48.4
subroutine 16 17 94.1
pod 5 5 100.0
total 289 378 76.4


line stmt bran cond sub pod time code
1             package Archive::Zip::ZipFileMember;
2              
3 28     28   178 use strict;
  28         61  
  28         880  
4 28     28   137 use vars qw( $VERSION @ISA );
  28         49  
  28         1479  
5              
6             BEGIN {
7 28     28   92 $VERSION = '1.68';
8 28         1409 @ISA = qw ( Archive::Zip::FileMember );
9             }
10              
11 28         62695 use Archive::Zip qw(
12             :CONSTANTS
13             :ERROR_CODES
14             :PKZIP_CONSTANTS
15             :UTILITY_METHODS
16 28     28   182 );
  28         53  
17              
18             # Create a new Archive::Zip::ZipFileMember
19             # given a filename and optional open file handle
20             #
21             sub _newFromZipFile {
22 117     117   295 my $class = shift;
23 117         220 my $fh = shift;
24 117         157 my $externalFileName = shift;
25 117 50       235 my $archiveZip64 = @_ ? shift : 0;
26 117 50       223 my $possibleEocdOffset = @_ ? shift : 0; # normally 0
27              
28 117         546 my $self = $class->new(
29             'eocdCrc32' => 0,
30             'diskNumberStart' => 0,
31             'localHeaderRelativeOffset' => 0,
32             'dataOffset' => 0, # localHeaderRelativeOffset + header length
33             @_
34             );
35 117         265 $self->{'externalFileName'} = $externalFileName;
36 117         271 $self->{'fh'} = $fh;
37 117         212 $self->{'archiveZip64'} = $archiveZip64;
38 117         219 $self->{'possibleEocdOffset'} = $possibleEocdOffset;
39 117         239 return $self;
40             }
41              
42             sub isDirectory {
43 199     199 1 317 my $self = shift;
44 199   66     554 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 172     172   292 my $self = shift;
55 172         282 my $where = shift; # optional
56 172         236 my $previousWhere = shift; # optional
57              
58 172 50       605 $where = $self->localHeaderRelativeOffset() unless defined($where);
59              
60             # avoid loop on certain corrupt files (from Julian Field)
61 172 50 33     484 return _formatError("corrupt zip file")
62             if defined($previousWhere) && $where == $previousWhere;
63              
64 172         296 my $status;
65             my $signature;
66              
67 172         369 $status = $self->fh()->seek($where, IO::Seekable::SEEK_SET);
68 172 50       2344 return _ioError("seeking to local header") unless $status;
69              
70 172         552 ($status, $signature) =
71             _readSignature($self->fh(), $self->externalFileName(),
72             LOCAL_FILE_HEADER_SIGNATURE, 1);
73 172 50       499 return $status if $status == AZ_IO_ERROR;
74              
75             # retry with EOCD offset if any was given.
76 172 0 33     426 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 172         388 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 16     16   44 my $self = shift;
98 16         63 my $newClass = shift;
99 16 50       76 return $self if ref($self) eq $newClass;
100              
101 16         43 my $status = AZ_OK;
102              
103 16 50       69 if (_isSeekable($self->fh())) {
104 16         63 my $here = $self->fh()->tell();
105 16         130 $status = $self->_seekToLocalHeader();
106 16 50       91 $status = $self->_readLocalFileHeader() if $status == AZ_OK;
107 16         54 $self->fh()->seek($here, IO::Seekable::SEEK_SET);
108 16 50       251 return $status unless $status == AZ_OK;
109             }
110              
111 16         55 delete($self->{'eocdCrc32'});
112 16         45 delete($self->{'diskNumberStart'});
113 16         41 delete($self->{'localHeaderRelativeOffset'});
114 16         32 delete($self->{'dataOffset'});
115 16         28 delete($self->{'archiveZip64'});
116 16         49 delete($self->{'possibleEocdOffset'});
117              
118 16         101 return $self->SUPER::_become($newClass);
119             }
120              
121             sub diskNumberStart {
122 0     0 1 0 shift->{'diskNumberStart'};
123             }
124              
125             sub localHeaderRelativeOffset {
126 172     172 1 387 shift->{'localHeaderRelativeOffset'};
127             }
128              
129             sub dataOffset {
130 156     156 1 469 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 156     156   265 my $self = shift;
137 156         4845 my $header;
138 156         456 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
139 156 50       1025 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
140 0         0 return _ioError("reading local file header");
141             }
142 156         380 my $fileNameLength;
143             my $extraFieldLength;
144 156         0 my $bitFlag;
145             (
146             undef, # $self->{'versionNeededToExtract'},
147 156         533 $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 156 50       389 if ($fileNameLength) {
158 156 50       342 $self->fh()->seek($fileNameLength, IO::Seekable::SEEK_CUR)
159             or return _ioError("skipping local file name");
160             }
161              
162 156         2528 my $zip64 = 0;
163 156 100       735 if ($extraFieldLength) {
164             $bytesRead =
165 50         175 $self->fh()->read($self->{'localExtraField'}, $extraFieldLength);
166 50 50       666 if ($bytesRead != $extraFieldLength) {
167 0         0 return _ioError("reading local extra field");
168             }
169 50 100       142 if ($self->{'archiveZip64'}) {
170 38         65 my $status;
171             ($status, $zip64) =
172 38         164 $self->_extractZip64ExtraField($self->{'localExtraField'}, undef, undef);
173 38 50       110 return $status if $status != AZ_OK;
174 38   33     153 $self->{'zip64'} ||= $zip64;
175             }
176             }
177              
178 156         485 $self->{'dataOffset'} = $self->fh()->tell();
179              
180 156 100       1033 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 134 50       310 $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 134         1844 my $oldCrc32 = $self->{'eocdCrc32'};
192 134         258 my $oldCompressedSize = $self->{'compressedSize'};
193 134         224 my $oldUncompressedSize = $self->{'uncompressedSize'};
194              
195 134         394 my $status = $self->_readDataDescriptor($zip64);
196 134 50       294 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 134 100 100     417 $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 134 50 33     690 || $oldUncompressedSize != $self->{'uncompressedSize'});
208              
209 134 100       376 $self->{'crc32'} = 0
210             if $self->compressionMethod() == COMPRESSION_STORED ;
211             }
212              
213 156         372 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 16     16   31 my $self = shift;
225 16         29 my $header;
226 16         62 my $bytesRead = $self->fh()->read($header, LOCAL_FILE_HEADER_LENGTH);
227 16 50       157 if ($bytesRead != LOCAL_FILE_HEADER_LENGTH) {
228 0         0 return _ioError("reading local file header");
229             }
230 16         86 my $fileNameLength;
231             my $crc32;
232 16         0 my $compressedSize;
233 16         0 my $uncompressedSize;
234 16         0 my $extraFieldLength;
235             (
236             $self->{'versionNeededToExtract'}, $self->{'bitFlag'},
237 16         93 $self->{'compressionMethod'}, $self->{'lastModFileDateTime'},
238             $crc32, $compressedSize,
239             $uncompressedSize, $fileNameLength,
240             $extraFieldLength
241             ) = unpack(LOCAL_FILE_HEADER_FORMAT, $header);
242              
243 16 50       126 if ($fileNameLength) {
244 16         27 my $fileName;
245 16         51 $bytesRead = $self->fh()->read($fileName, $fileNameLength);
246 16 50       131 if ($bytesRead != $fileNameLength) {
247 0         0 return _ioError("reading local file name");
248             }
249 16         74 $self->fileName($fileName);
250             }
251              
252 16         34 my $zip64 = 0;
253 16 100       46 if ($extraFieldLength) {
254             $bytesRead =
255 8         43 $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       26 if ($self->{'archiveZip64'}) {
260 2         4 my $status;
261             ($status, $zip64) =
262 2         10 $self->_extractZip64ExtraField($self->{'localExtraField'},
263             $uncompressedSize,
264             $compressedSize);
265 2 50       13 return $status if $status != AZ_OK;
266 2   33     9 $self->{'zip64'} ||= $zip64;
267             }
268             }
269              
270 16         63 $self->{'dataOffset'} = $self->fh()->tell();
271              
272 16 100       159 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 5 50       45 $self->fh()->seek($self->{'compressedSize'}, IO::Seekable::SEEK_CUR)
279             or return _ioError("seeking to extended local header");
280              
281 5         131 my $status = $self->_readDataDescriptor($zip64);
282 5 50       35 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 11 50 33     132 || $self->{'uncompressedSize'} != $uncompressedSize);
288             }
289              
290             return
291             wantarray
292 16 50       84 ? (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 139     139   241 my $self = shift;
307 139         206 my $zip64 = shift;
308 139         498 my $signatureData;
309             my $header;
310 139         0 my $crc32;
311 139         0 my $compressedSize;
312 139         0 my $uncompressedSize;
313              
314 139         337 my $bytesRead = $self->fh()->read($signatureData, SIGNATURE_LENGTH);
315 139 50       1822 return _ioError("reading header signature")
316             if $bytesRead != SIGNATURE_LENGTH;
317 139         354 my $signature = unpack(SIGNATURE_FORMAT, $signatureData);
318              
319 139         439 my $dataDescriptorLength;
320             my $dataDescriptorFormat;
321 139         0 my $dataDescriptorLengthNoSig;
322 139         0 my $dataDescriptorFormatNoSig;
323 139 100       310 if (! $zip64) {
324 100         140 $dataDescriptorLength = DATA_DESCRIPTOR_LENGTH;
325 100         226 $dataDescriptorFormat = DATA_DESCRIPTOR_FORMAT;
326 100         144 $dataDescriptorLengthNoSig = DATA_DESCRIPTOR_LENGTH_NO_SIG;
327 100         206 $dataDescriptorFormatNoSig = DATA_DESCRIPTOR_FORMAT_NO_SIG
328             }
329             else {
330 39         77 $dataDescriptorLength = DATA_DESCRIPTOR_ZIP64_LENGTH;
331 39         78 $dataDescriptorFormat = DATA_DESCRIPTOR_ZIP64_FORMAT;
332 39         57 $dataDescriptorLengthNoSig = DATA_DESCRIPTOR_ZIP64_LENGTH_NO_SIG;
333 39         73 $dataDescriptorFormatNoSig = DATA_DESCRIPTOR_ZIP64_FORMAT_NO_SIG
334             }
335              
336             # unfortunately, the signature appears to be optional.
337 139 50 33     741 if ($signature == DATA_DESCRIPTOR_SIGNATURE
338             && ($signature != $self->{'crc32'})) {
339 139         419 $bytesRead = $self->fh()->read($header, $dataDescriptorLength);
340 139 50       965 return _ioError("reading data descriptor")
341             if $bytesRead != $dataDescriptorLength;
342              
343 139         417 ($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 139 50       348 unless defined($self->{'eocdCrc32'});
357 139         243 $self->{'crc32'} = $crc32;
358 139         193 $self->{'compressedSize'} = $compressedSize;
359 139         199 $self->{'uncompressedSize'} = $uncompressedSize;
360              
361 139         410 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 70     70   140 my $self = shift;
369 70         354 my $fh = $self->fh();
370 70         191 my $header = '';
371 70         247 my $bytesRead = $fh->read($header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH);
372 70 50       459 if ($bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH) {
373 0         0 return _ioError("reading central dir header");
374             }
375 70         133 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 70         441 $self->{'localHeaderRelativeOffset'}
393             ) = unpack(CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header);
394              
395 70         163 $self->{'eocdCrc32'} = $self->{'crc32'};
396              
397 70 100       152 if ($fileNameLength) {
398 69         182 $bytesRead = $fh->read($self->{'fileName'}, $fileNameLength);
399 69 50       403 if ($bytesRead != $fileNameLength) {
400 0         0 _ioError("reading central dir filename");
401             }
402             }
403 70 100       173 if ($extraFieldLength) {
404 21         72 $bytesRead = $fh->read($self->{'cdExtraField'}, $extraFieldLength);
405 21 50       136 if ($bytesRead != $extraFieldLength) {
406 0         0 return _ioError("reading central dir extra field");
407             }
408 21 100       73 if ($self->{'archiveZip64'}) {
409             my ($status, $zip64) =
410             $self->_extractZip64ExtraField($self->{'cdExtraField'},
411             $self->{'uncompressedSize'},
412             $self->{'compressedSize'},
413             $self->{'localHeaderRelativeOffset'},
414 8         99 $self->{'diskNumberStart'});
415 8 50       20 return $status if $status != AZ_OK;
416 8   33     114 $self->{'zip64'} ||= $zip64;
417             }
418             }
419 70 50       162 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 70 100 100     384 if ( $self->{'uncompressedSize'} != $self->{'compressedSize'}
428             and $self->{'compressionMethod'} == COMPRESSION_STORED) {
429 2         5 $self->{'uncompressedSize'} = $self->{'compressedSize'};
430             }
431              
432 70         321 $self->desiredCompressionMethod($self->compressionMethod());
433              
434 70         167 return AZ_OK;
435             }
436              
437             sub rewindData {
438 156     156 1 348 my $self = shift;
439              
440 156         536 my $status = $self->SUPER::rewindData(@_);
441 156 50       413 return $status unless $status == AZ_OK;
442              
443 156 50       519 return AZ_IO_ERROR unless $self->fh();
444              
445 156         501 $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 156         526 $status = $self->_seekToLocalHeader();
451 156 50       377 return $status unless $status == AZ_OK;
452              
453             # skip local file header
454 156         472 $status = $self->_skipLocalFileHeader();
455 156 50       375 return $status unless $status == AZ_OK;
456              
457             # Seek to beginning of file data
458 156 50       357 $self->fh()->seek($self->dataOffset(), IO::Seekable::SEEK_SET)
459             or return _ioError("seeking to beginning of file data");
460              
461 156         2594 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 143     143   321 my ($self, $dataRef, $chunkSize) = @_;
469 143 50       309 return (0, AZ_OK) unless $chunkSize;
470 143 50       405 my $bytesRead = $self->fh()->read($$dataRef, $chunkSize)
471             or return (0, _ioError("reading data"));
472 143         2803 return ($bytesRead, AZ_OK);
473             }
474              
475             1;