Debian lenny version packages
[pkg-perl] / deb-src / libarchive-zip-perl / libarchive-zip-perl-1.18 / lib / Archive / Zip / ZipFileMember.pm
1 package Archive::Zip::ZipFileMember;
2
3 use strict;
4 use vars qw( $VERSION @ISA );
5
6 BEGIN {
7     $VERSION = '1.18';
8     @ISA     = qw ( Archive::Zip::FileMember );
9 }
10
11 use Archive::Zip qw(
12   :CONSTANTS
13   :ERROR_CODES
14   :PKZIP_CONSTANTS
15   :UTILITY_METHODS
16 );
17
18 # Create a new Archive::Zip::ZipFileMember
19 # given a filename and optional open file handle
20 #
21 sub _newFromZipFile {
22     my $class              = shift;
23     my $fh                 = shift;
24     my $externalFileName   = shift;
25     my $possibleEocdOffset = shift;    # normally 0
26
27     my $self = $class->new(
28         'crc32'                     => 0,
29         'diskNumberStart'           => 0,
30         'localHeaderRelativeOffset' => 0,
31         'dataOffset' => 0,    # localHeaderRelativeOffset + header length
32         @_
33     );
34     $self->{'externalFileName'}   = $externalFileName;
35     $self->{'fh'}                 = $fh;
36     $self->{'possibleEocdOffset'} = $possibleEocdOffset;
37     return $self;
38 }
39
40 sub isDirectory {
41     my $self = shift;
42     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     my $self          = shift;
53     my $where         = shift;    # optional
54     my $previousWhere = shift;    # optional
55
56     $where = $self->localHeaderRelativeOffset() unless defined($where);
57
58     # avoid loop on certain corrupt files (from Julian Field)
59     return _formatError("corrupt zip file")
60       if defined($previousWhere) && $where == $previousWhere;
61
62     my $status;
63     my $signature;
64
65     $status = $self->fh()->seek( $where, IO::Seekable::SEEK_SET );
66     return _ioError("seeking to local header") unless $status;
67
68     ( $status, $signature ) =
69       _readSignature( $self->fh(), $self->externalFileName(),
70         LOCAL_FILE_HEADER_SIGNATURE );
71     return $status if $status == AZ_IO_ERROR;
72
73     # retry with EOCD offset if any was given.
74     if ( $status == AZ_FORMAT_ERROR && $self->{'possibleEocdOffset'} ) {
75         $status = $self->_seekToLocalHeader(
76             $self->localHeaderRelativeOffset() + $self->{'possibleEocdOffset'},
77             $where
78         );
79         if ( $status == AZ_OK ) {
80             $self->{'localHeaderRelativeOffset'} +=
81               $self->{'possibleEocdOffset'};
82             $self->{'possibleEocdOffset'} = 0;
83         }
84     }
85
86     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     my $self     = shift;
96     my $newClass = shift;
97     return $self if ref($self) eq $newClass;
98
99     my $status = AZ_OK;
100
101     if ( _isSeekable( $self->fh() ) ) {
102         my $here = $self->fh()->tell();
103         $status = $self->_seekToLocalHeader();
104         $status = $self->_readLocalFileHeader() if $status == AZ_OK;
105         $self->fh()->seek( $here, IO::Seekable::SEEK_SET );
106         return $status unless $status == AZ_OK;
107     }
108
109     delete( $self->{'eocdCrc32'} );
110     delete( $self->{'diskNumberStart'} );
111     delete( $self->{'localHeaderRelativeOffset'} );
112     delete( $self->{'dataOffset'} );
113
114     return $self->SUPER::_become($newClass);
115 }
116
117 sub diskNumberStart {
118     shift->{'diskNumberStart'};
119 }
120
121 sub localHeaderRelativeOffset {
122     shift->{'localHeaderRelativeOffset'};
123 }
124
125 sub dataOffset {
126     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     my $self = shift;
133     my $header;
134     my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH );
135     if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) {
136         return _ioError("reading local file header");
137     }
138     my $fileNameLength;
139     my $extraFieldLength;
140     my $bitFlag;
141     (
142         undef,    # $self->{'versionNeededToExtract'},
143         $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     if ($fileNameLength) {
154         $self->fh()->seek( $fileNameLength, IO::Seekable::SEEK_CUR )
155           or return _ioError("skipping local file name");
156     }
157
158     if ($extraFieldLength) {
159         $bytesRead =
160           $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength );
161         if ( $bytesRead != $extraFieldLength ) {
162             return _ioError("reading local extra field");
163         }
164     }
165
166     $self->{'dataOffset'} = $self->fh()->tell();
167
168     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         $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         my $oldCrc32            = $self->{'eocdCrc32'};
180         my $oldCompressedSize   = $self->{'compressedSize'};
181         my $oldUncompressedSize = $self->{'uncompressedSize'};
182
183         my $status = $self->_readDataDescriptor();
184         return $status unless $status == AZ_OK;
185
186         return _formatError(
187             "CRC or size mismatch while skipping data descriptor")
188           if ( $oldCrc32 != $self->{'crc32'}
189             || $oldUncompressedSize != $self->{'uncompressedSize'} );
190     }
191
192     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     my $self = shift;
202     my $header;
203     my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH );
204     if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) {
205         return _ioError("reading local file header");
206     }
207     my $fileNameLength;
208     my $crc32;
209     my $compressedSize;
210     my $uncompressedSize;
211     my $extraFieldLength;
212     (
213         $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     if ($fileNameLength) {
221         my $fileName;
222         $bytesRead = $self->fh()->read( $fileName, $fileNameLength );
223         if ( $bytesRead != $fileNameLength ) {
224             return _ioError("reading local file name");
225         }
226         $self->fileName($fileName);
227     }
228
229     if ($extraFieldLength) {
230         $bytesRead =
231           $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength );
232         if ( $bytesRead != $extraFieldLength ) {
233             return _ioError("reading local extra field");
234         }
235     }
236
237     $self->{'dataOffset'} = $self->fh()->tell();
238
239     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         $self->fh()->seek( $self->{'compressedSize'}, IO::Seekable::SEEK_CUR )
246           or return _ioError("seeking to extended local header");
247
248         my $status = $self->_readDataDescriptor();
249         return $status unless $status == AZ_OK;
250     }
251     else {
252         return _formatError(
253             "CRC or size mismatch after reading data descriptor")
254           if ( $self->{'crc32'} != $crc32
255             || $self->{'uncompressedSize'} != $uncompressedSize );
256     }
257
258     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     my $self = shift;
269     my $signatureData;
270     my $header;
271     my $crc32;
272     my $compressedSize;
273     my $uncompressedSize;
274
275     my $bytesRead = $self->fh()->read( $signatureData, SIGNATURE_LENGTH );
276     return _ioError("reading header signature")
277       if $bytesRead != SIGNATURE_LENGTH;
278     my $signature = unpack( SIGNATURE_FORMAT, $signatureData );
279
280     # unfortunately, the signature appears to be optional.
281     if ( $signature == DATA_DESCRIPTOR_SIGNATURE
282         && ( $signature != $self->{'crc32'} ) )
283     {
284         $bytesRead = $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH );
285         return _ioError("reading data descriptor")
286           if $bytesRead != DATA_DESCRIPTOR_LENGTH;
287
288         ( $crc32, $compressedSize, $uncompressedSize ) =
289           unpack( DATA_DESCRIPTOR_FORMAT, $header );
290     }
291     else {
292         $bytesRead =
293           $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH_NO_SIG );
294         return _ioError("reading data descriptor")
295           if $bytesRead != DATA_DESCRIPTOR_LENGTH_NO_SIG;
296
297         $crc32 = $signature;
298         ( $compressedSize, $uncompressedSize ) =
299           unpack( DATA_DESCRIPTOR_FORMAT_NO_SIG, $header );
300     }
301
302     $self->{'eocdCrc32'} = $self->{'crc32'}
303       unless defined( $self->{'eocdCrc32'} );
304     $self->{'crc32'}            = $crc32;
305     $self->{'compressedSize'}   = $compressedSize;
306     $self->{'uncompressedSize'} = $uncompressedSize;
307
308     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     my $self      = shift;
316     my $fh        = $self->fh();
317     my $header    = '';
318     my $bytesRead = $fh->read( $header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH );
319     if ( $bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH ) {
320         return _ioError("reading central dir header");
321     }
322     my ( $fileNameLength, $extraFieldLength, $fileCommentLength );
323     (
324         $self->{'versionMadeBy'},
325         $self->{'fileAttributeFormat'},
326         $self->{'versionNeededToExtract'},
327         $self->{'bitFlag'},
328         $self->{'compressionMethod'},
329         $self->{'lastModFileDateTime'},
330         $self->{'crc32'},
331         $self->{'compressedSize'},
332         $self->{'uncompressedSize'},
333         $fileNameLength,
334         $extraFieldLength,
335         $fileCommentLength,
336         $self->{'diskNumberStart'},
337         $self->{'internalFileAttributes'},
338         $self->{'externalFileAttributes'},
339         $self->{'localHeaderRelativeOffset'}
340     ) = unpack( CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header );
341
342     $self->{'eocdCrc32'} = $self->{'crc32'};
343
344     if ($fileNameLength) {
345         $bytesRead = $fh->read( $self->{'fileName'}, $fileNameLength );
346         if ( $bytesRead != $fileNameLength ) {
347             _ioError("reading central dir filename");
348         }
349     }
350     if ($extraFieldLength) {
351         $bytesRead = $fh->read( $self->{'cdExtraField'}, $extraFieldLength );
352         if ( $bytesRead != $extraFieldLength ) {
353             return _ioError("reading central dir extra field");
354         }
355     }
356     if ($fileCommentLength) {
357         $bytesRead = $fh->read( $self->{'fileComment'}, $fileCommentLength );
358         if ( $bytesRead != $fileCommentLength ) {
359             return _ioError("reading central dir file comment");
360         }
361     }
362
363     # NK 10/21/04: added to avoid problems with manipulated headers
364     if (    $self->{'uncompressedSize'} != $self->{'compressedSize'}
365         and $self->{'compressionMethod'} == COMPRESSION_STORED )
366     {
367         $self->{'uncompressedSize'} = $self->{'compressedSize'};
368     }
369
370     $self->desiredCompressionMethod( $self->compressionMethod() );
371
372     return AZ_OK;
373 }
374
375 sub rewindData {
376     my $self = shift;
377
378     my $status = $self->SUPER::rewindData(@_);
379     return $status unless $status == AZ_OK;
380
381     return AZ_IO_ERROR unless $self->fh();
382
383     $self->fh()->clearerr();
384
385     # Seek to local file header.
386     # The only reason that I'm doing this this way is that the extraField
387     # length seems to be different between the CD header and the LF header.
388     $status = $self->_seekToLocalHeader();
389     return $status unless $status == AZ_OK;
390
391     # skip local file header
392     $status = $self->_skipLocalFileHeader();
393     return $status unless $status == AZ_OK;
394
395     # Seek to beginning of file data
396     $self->fh()->seek( $self->dataOffset(), IO::Seekable::SEEK_SET )
397       or return _ioError("seeking to beginning of file data");
398
399     return AZ_OK;
400 }
401
402 # Return bytes read. Note that first parameter is a ref to a buffer.
403 # my $data;
404 # my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize );
405 sub _readRawChunk {
406     my ( $self, $dataRef, $chunkSize ) = @_;
407     return ( 0, AZ_OK ) unless $chunkSize;
408     my $bytesRead = $self->fh()->read( $$dataRef, $chunkSize )
409       or return ( 0, _ioError("reading data") );
410     return ( $bytesRead, AZ_OK );
411 }
412
413 1;