File Coverage

blib/lib/Archive/Zip/ZipFileMember.pm
Criterion Covered Total %
statement 117 172 68.0
branch 33 96 34.4
condition 7 21 33.3
subroutine 15 17 88.2
pod 5 5 100.0
total 177 311 56.9


line stmt bran cond sub pod time code
1             package Archive::Zip::ZipFileMember;
2              
3 6     6   130 use strict;
  6         58  
  6         100  
4 6     6   193 use vars qw( $VERSION @ISA );
  6         57  
  6         88  
5              
6             BEGIN {
7 6     6   95     $VERSION = '1.18';
8 6         78     @ISA = qw ( Archive::Zip::FileMember );
9             }
10              
11 6         2274 use Archive::Zip qw(
12             :CONSTANTS
13             :ERROR_CODES
14             :PKZIP_CONSTANTS
15             :UTILITY_METHODS
16 6     6   115 );
  6         53  
17              
18             # Create a new Archive::Zip::ZipFileMember
19             # given a filename and optional open file handle
20             #
21             sub _newFromZipFile {
22 6     6   54     my $class = shift;
23 6         51     my $fh = shift;
24 6         50     my $externalFileName = shift;
25 6         50     my $possibleEocdOffset = shift; # normally 0
26              
27 6         182     my $self = $class->new(
28                     'crc32' => 0,
29                     'diskNumberStart' => 0,
30                     'localHeaderRelativeOffset' => 0,
31                     'dataOffset' => 0, # localHeaderRelativeOffset + header length
32                     @_
33                 );
34 6         63     $self->{'externalFileName'} = $externalFileName;
35 6         73     $self->{'fh'} = $fh;
36 6         58     $self->{'possibleEocdOffset'} = $possibleEocdOffset;
37 6         65     return $self;
38             }
39              
40             sub isDirectory {
41 15     15 1 216     my $self = shift;
42 15   66     190     return ( substr( $self->fileName(), -1, 1 ) eq '/'
43                       and $self->uncompressedSize() == 0 );
44             }
45              
46             # Seek to the beginning of the local header, just past the signature.
47             # Verify that the local header signature is in fact correct.
48             # Update the localHeaderRelativeOffset if necessary by adding the possibleEocdOffset.
49             # Returns status.
50              
51             sub _seekToLocalHeader {
52 25     25   364     my $self = shift;
53 25         211     my $where = shift; # optional
54 25         246     my $previousWhere = shift; # optional
55              
56 25 50       774     $where = $self->localHeaderRelativeOffset() unless defined($where);
57              
58             # avoid loop on certain corrupt files (from Julian Field)
59 25 50 33     286     return _formatError("corrupt zip file")
60                   if defined($previousWhere) && $where == $previousWhere;
61              
62 25         200     my $status;
63 25         199     my $signature;
64              
65 25         280     $status = $self->fh()->seek( $where, IO::Seekable::SEEK_SET );
66 25 50       2143     return _ioError("seeking to local header") unless $status;
67              
68 25         326     ( $status, $signature ) =
69                   _readSignature( $self->fh(), $self->externalFileName(),
70                     LOCAL_FILE_HEADER_SIGNATURE );
71 25 50       360     return $status if $status == AZ_IO_ERROR;
72              
73             # retry with EOCD offset if any was given.
74 25 50 33     276     if ( $status == AZ_FORMAT_ERROR && $self->{'possibleEocdOffset'} ) {
75 0         0         $status = $self->_seekToLocalHeader(
76                         $self->localHeaderRelativeOffset() + $self->{'possibleEocdOffset'},
77                         $where
78                     );
79 0 0       0         if ( $status == AZ_OK ) {
80 0         0             $self->{'localHeaderRelativeOffset'} +=
81                           $self->{'possibleEocdOffset'};
82 0         0             $self->{'possibleEocdOffset'} = 0;
83                     }
84                 }
85              
86 25         250     return $status;
87             }
88              
89             # Because I'm going to delete the file handle, read the local file
90             # header if the file handle is seekable. If it isn't, I assume that
91             # I've already read the local header.
92             # Return ( $status, $self )
93              
94             sub _become {
95 2     2   120     my $self = shift;
96 2         47     my $newClass = shift;
97 2 50       26     return $self if ref($self) eq $newClass;
98              
99 2         20     my $status = AZ_OK;
100              
101 2 50       61     if ( _isSeekable( $self->fh() ) ) {
102 2         25         my $here = $self->fh()->tell();
103 2         87         $status = $self->_seekToLocalHeader();
104 2 50       84         $status = $self->_readLocalFileHeader() if $status == AZ_OK;
105 2         24         $self->fh()->seek( $here, IO::Seekable::SEEK_SET );
106 2 50       74         return $status unless $status == AZ_OK;
107                 }
108              
109 2         55     delete( $self->{'eocdCrc32'} );
110 2         21     delete( $self->{'diskNumberStart'} );
111 2         36     delete( $self->{'localHeaderRelativeOffset'} );
112 2         18     delete( $self->{'dataOffset'} );
113              
114 2         115     return $self->SUPER::_become($newClass);
115             }
116              
117             sub diskNumberStart {
118 0     0 1 0     shift->{'diskNumberStart'};
119             }
120              
121             sub localHeaderRelativeOffset {
122 25     25 1 255     shift->{'localHeaderRelativeOffset'};
123             }
124              
125             sub dataOffset {
126 23     23 1 337     shift->{'dataOffset'};
127             }
128              
129             # Skip local file header, updating only extra field stuff.
130             # Assumes that fh is positioned before signature.
131             sub _skipLocalFileHeader {
132 23     23   218     my $self = shift;
133 23         182     my $header;
134 23         452     my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH );
135 23 50       556     if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) {
136 0         0         return _ioError("reading local file header");
137                 }
138 23         2830     my $fileNameLength;
139 23         199     my $extraFieldLength;
140 23         179     my $bitFlag;
141                 (
142                     undef, # $self->{'versionNeededToExtract'},
143 23         298         $bitFlag,
144                     undef, # $self->{'compressionMethod'},
145                     undef, # $self->{'lastModFileDateTime'},
146                     undef, # $crc32,
147                     undef, # $compressedSize,
148                     undef, # $uncompressedSize,
149                     $fileNameLength,
150                     $extraFieldLength
151                 ) = unpack( LOCAL_FILE_HEADER_FORMAT, $header );
152              
153 23 50       238     if ($fileNameLength) {
154 23 50       254         $self->fh()->seek( $fileNameLength, IO::Seekable::SEEK_CUR )
155                       or return _ioError("skipping local file name");
156                 }
157              
158 23 50       978     if ($extraFieldLength) {
159 0         0         $bytesRead =
160                       $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength );
161 0 0       0         if ( $bytesRead != $extraFieldLength ) {
162 0         0             return _ioError("reading local extra field");
163                     }
164                 }
165              
166 23         332     $self->{'dataOffset'} = $self->fh()->tell();
167              
168 23 50       479     if ( $bitFlag & GPBF_HAS_DATA_DESCRIPTOR_MASK ) {
169              
170             # Read the crc32, compressedSize, and uncompressedSize from the
171             # extended data descriptor, which directly follows the compressed data.
172             #
173             # Skip over the compressed file data (assumes that EOCD compressedSize
174             # was correct)
175 0 0       0         $self->fh()->seek( $self->{'compressedSize'}, IO::Seekable::SEEK_CUR )
176                       or return _ioError("seeking to extended local header");
177              
178             # these values should be set correctly from before.
179 0         0         my $oldCrc32 = $self->{'eocdCrc32'};
180 0         0         my $oldCompressedSize = $self->{'compressedSize'};
181 0         0         my $oldUncompressedSize = $self->{'uncompressedSize'};
182              
183 0         0         my $status = $self->_readDataDescriptor();
184 0 0       0         return $status unless $status == AZ_OK;
185              
186 0 0 0     0         return _formatError(
187                         "CRC or size mismatch while skipping data descriptor")
188                       if ( $oldCrc32 != $self->{'crc32'}
189                         || $oldUncompressedSize != $self->{'uncompressedSize'} );
190                 }
191              
192 23         232     return AZ_OK;
193             }
194              
195             # Read from a local file header into myself. Returns AZ_OK if successful.
196             # Assumes that fh is positioned after signature.
197             # Note that crc32, compressedSize, and uncompressedSize will be 0 if
198             # GPBF_HAS_DATA_DESCRIPTOR_MASK is set in the bitFlag.
199              
200             sub _readLocalFileHeader {
201 2     2   18     my $self = shift;
202 2         17     my $header;
203 2         25     my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH );
204 2 50       69     if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) {
205 0         0         return _ioError("reading local file header");
206                 }
207 2         17     my $fileNameLength;
208 2         17     my $crc32;
209 2         17     my $compressedSize;
210 2         17     my $uncompressedSize;
211 2         18     my $extraFieldLength;
212                 (
213 2         33         $self->{'versionNeededToExtract'}, $self->{'bitFlag'},
214                     $self->{'compressionMethod'}, $self->{'lastModFileDateTime'},
215                     $crc32, $compressedSize,
216                     $uncompressedSize, $fileNameLength,
217                     $extraFieldLength
218                 ) = unpack( LOCAL_FILE_HEADER_FORMAT, $header );
219              
220 2 50       24     if ($fileNameLength) {
221 2         17         my $fileName;
222 2         23         $bytesRead = $self->fh()->read( $fileName, $fileNameLength );
223 2 50       43         if ( $bytesRead != $fileNameLength ) {
224 0         0             return _ioError("reading local file name");
225                     }
226 2         88         $self->fileName($fileName);
227                 }
228              
229 2 50       80     if ($extraFieldLength) {
230 0         0         $bytesRead =
231                       $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength );
232 0 0       0         if ( $bytesRead != $extraFieldLength ) {
233 0         0             return _ioError("reading local extra field");
234                     }
235                 }
236              
237 2         26     $self->{'dataOffset'} = $self->fh()->tell();
238              
239 2 50       133     if ( $self->hasDataDescriptor() ) {
240              
241             # Read the crc32, compressedSize, and uncompressedSize from the
242             # extended data descriptor.
243             # Skip over the compressed file data (assumes that EOCD compressedSize
244             # was correct)
245 0 0       0         $self->fh()->seek( $self->{'compressedSize'}, IO::Seekable::SEEK_CUR )
246                       or return _ioError("seeking to extended local header");
247              
248 0         0         my $status = $self->_readDataDescriptor();
249 0 0       0         return $status unless $status == AZ_OK;
250                 }
251                 else {
252 2 50 33     69         return _formatError(
253                         "CRC or size mismatch after reading data descriptor")
254                       if ( $self->{'crc32'} != $crc32
255                         || $self->{'uncompressedSize'} != $uncompressedSize );
256                 }
257              
258 2         22     return AZ_OK;
259             }
260              
261             # This will read the data descriptor, which is after the end of compressed file
262             # data in members that that have GPBF_HAS_DATA_DESCRIPTOR_MASK set in their
263             # bitFlag.
264             # The only reliable way to find these is to rely on the EOCD compressedSize.
265             # Assumes that file is positioned immediately after the compressed data.
266             # Returns status; sets crc32, compressedSize, and uncompressedSize.
267             sub _readDataDescriptor {
268 0     0   0     my $self = shift;
269 0         0     my $signatureData;
270 0         0     my $header;
271 0         0     my $crc32;
272 0         0     my $compressedSize;
273 0         0     my $uncompressedSize;
274              
275 0         0     my $bytesRead = $self->fh()->read( $signatureData, SIGNATURE_LENGTH );
276 0 0       0     return _ioError("reading header signature")
277                   if $bytesRead != SIGNATURE_LENGTH;
278 0         0     my $signature = unpack( SIGNATURE_FORMAT, $signatureData );
279              
280             # unfortunately, the signature appears to be optional.
281 0 0 0     0     if ( $signature == DATA_DESCRIPTOR_SIGNATURE
282                     && ( $signature != $self->{'crc32'} ) )
283                 {
284 0         0         $bytesRead = $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH );
285 0 0       0         return _ioError("reading data descriptor")
286                       if $bytesRead != DATA_DESCRIPTOR_LENGTH;
287              
288 0         0         ( $crc32, $compressedSize, $uncompressedSize ) =
289                       unpack( DATA_DESCRIPTOR_FORMAT, $header );
290                 }
291                 else {
292 0         0         $bytesRead =
293                       $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH_NO_SIG );
294 0 0       0         return _ioError("reading data descriptor")
295                       if $bytesRead != DATA_DESCRIPTOR_LENGTH_NO_SIG;
296              
297 0         0         $crc32 = $signature;
298 0         0         ( $compressedSize, $uncompressedSize ) =
299                       unpack( DATA_DESCRIPTOR_FORMAT_NO_SIG, $header );
300                 }
301              
302 0 0       0     $self->{'eocdCrc32'} = $self->{'crc32'}
303                   unless defined( $self->{'eocdCrc32'} );
304 0         0     $self->{'crc32'} = $crc32;
305 0         0     $self->{'compressedSize'} = $compressedSize;
306 0         0     $self->{'uncompressedSize'} = $uncompressedSize;
307              
308 0         0     return AZ_OK;
309             }
310              
311             # Read a Central Directory header. Return AZ_OK on success.
312             # Assumes that fh is positioned right after the signature.
313              
314             sub _readCentralDirectoryFileHeader {
315 5     5   44     my $self = shift;
316 5         123     my $fh = $self->fh();
317 5         51     my $header = '';
318 5         58     my $bytesRead = $fh->read( $header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH );
319 5 50       113     if ( $bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH ) {
320 0         0         return _ioError("reading central dir header");
321                 }
322 5         43     my ( $fileNameLength, $extraFieldLength, $fileCommentLength );
323