File Coverage

blib/lib/Archive/Zip/Parser.pm
Criterion Covered Total %
statement 94 96 97.9
branch 14 18 77.7
condition n/a
subroutine 23 23 100.0
pod 5 5 100.0
total 136 142 95.7


line stmt bran cond sub pod time code
1             package Archive::Zip::Parser;
2              
3 2     2   31037 use warnings;
  2         6  
  2         70  
4 2     2   11 use strict;
  2         4  
  2         68  
5 2     2   1802 use autodie;
  2         78467  
  2         16  
6 2     2   30715 use Data::ParseBinary;
  2         142752  
  2         1085  
7              
8 2     2   30033 use Archive::Zip::Parser::Entry;
  2         6  
  2         69  
9 2     2   1380 use Archive::Zip::Parser::CentralDirectoryEnd;
  2         6  
  2         68  
10 2     2   10 use base qw( Archive::Zip::Parser::Exception );
  2         4  
  2         1579  
11              
12 2     2   1707 use version; our $VERSION = qv( '0.0.3' );
  2         9831  
  2         24  
13              
14             sub new {
15 2     2 1 9369 my ( $class, $arg_ref ) = @_;
16              
17 2         11 my $new_object = bless {}, $class;
18              
19 2 100       13 if ( !exists $arg_ref->{'file_name'} ) {
20 1         14 $new_object->_croak('Missing "file_name"');
21             }
22              
23 1         9 open my $file_handle, '<', $arg_ref->{'file_name'};
24 1         4156 $new_object->{'_file_handle'} = $file_handle;
25 1         10 $new_object->{'_bit_stream'}
26             = CreateStreamReader( File => $new_object->{'_file_handle'} );
27              
28 1         45 return $new_object;
29             }
30              
31             sub verify_signature {
32 2     2 1 4075 my $self = shift;
33              
34 2         5 my $previous_position_in_file;
35             eval {
36 2         9 $previous_position_in_file = tell $self->{'_file_handle'};
37 2         10 $self->_set_position_in_file( 0, 0 );
38              
39 2         217 Magic("PK\x03\x04")->parse( $self->{'_bit_stream'} );
40 2 50       4 } or do {
41 0         0 return 0;
42             };
43              
44 2         574 $self->_set_position_in_file( $previous_position_in_file, 0 );
45 2         13 return 1;
46             }
47              
48             sub parse {
49 1     1 1 7 my $self = shift;
50              
51 1 50       268 return 1 if $self->{'_is_parsed'};
52 1 50       6 if ( !$self->verify_signature() ) {
53 0         0 $self->_croak('Not a valid .ZIP archive');
54             }
55              
56 1         2 my @parsed_entry_struct;
57 1         3 while (1) {
58 6 100       596 last if $self->_check_signature('04034b50');
59              
60             my $local_file_header_struct
61             = Struct(
62             '_entry_struct',
63             Struct(
64             '_local_file_header',
65             ULInt32('_signature' ),
66             ULInt16('_version_needed'),
67             BitStruct( '_gp_bit',
68             Flag('_bit_7' ),
69             Flag('_bit_6' ),
70             Flag('_bit_5' ),
71             Flag('_bit_4' ),
72             Flag('_bit_3' ),
73             Flag('_bit_2' ),
74             Flag('_bit_1' ),
75             Flag('_bit_0' ),
76             Flag('_bit_15'),
77             Flag('_bit_14'),
78             Flag('_bit_13'),
79             Flag('_bit_12'),
80             Flag('_bit_11'),
81             Flag('_bit_10'),
82             Flag('_bit_9' ),
83             Flag('_bit_8' ),
84             ),
85             ULInt16('_compression_method'),
86             ULInt16('_last_mod_time' ),
87             ULInt16('_last_mod_date' ),
88             ULInt32('_crc_32' ),
89             ULInt32('_compressed_size' ),
90             ULInt32('_uncompressed_size' ),
91             ULInt16('_file_name_length' ),
92             ULInt16('_extra_field_length'),
93             String(
94             '_file_name',
95             sub {
96 5     5   13713 $_->ctx->{'_file_name_length'};
97             },
98             ),
99             Field(
100             '_extra_field',
101             sub {
102 5     5   1601 $_->ctx->{'_extra_field_length'};
103             },
104             ),
105             ),
106             Field(
107             '_file_data',
108             sub {
109 5     5   415 $_->ctx->{'_local_file_header'}->{'_compressed_size'};
110             }
111             ),
112             If(
113             sub {
114 5     5   318 $_->ctx->{'_local_file_header'}->{'_gp_bit'}->{'_bit_3'};
115             },
116 5         21 Struct(
117             '_data_descriptor',
118             ULInt32('_crc_32' ),
119             ULInt32('_compressed_size' ),
120             ULInt32('_uncompressed_size'),
121             ),
122             ),
123             );
124              
125 5         3603 push @parsed_entry_struct,
126             $local_file_header_struct->parse( $self->{'_bit_stream'} );
127             }
128              
129 1         3 my $entry_count = 0;
130 1         9 while (1) {
131 6 100       16 last if $self->_check_signature('02014b50');
132              
133             my $central_directory_struct
134             = Struct(
135             '_entry_struct',
136             Struct(
137             '_central_directory',
138             ULInt32('_signature' ),
139             Field( '_version_made_by', 2 ),
140             ULInt16('_version_needed' ),
141             BitStruct(
142             '_gp_bit',
143             Flag('_bit_7' ),
144             Flag('_bit_6' ),
145             Flag('_bit_5' ),
146             Flag('_bit_4' ),
147             Flag('_bit_3' ),
148             Flag('_bit_2' ),
149             Flag('_bit_1' ),
150             Flag('_bit_0' ),
151             Flag('_bit_15'),
152             Flag('_bit_14'),
153             Flag('_bit_13'),
154             Flag('_bit_12'),
155             Flag('_bit_11'),
156             Flag('_bit_10'),
157             Flag('_bit_9' ),
158             Flag('_bit_8' ),
159             ),
160             ULInt16('_compression_method' ),
161             ULInt16('_last_mod_time' ),
162             ULInt16('_last_mod_date' ),
163             ULInt32('_crc_32' ),
164             ULInt32('_compressed_size' ),
165             ULInt32('_uncompressed_size' ),
166             ULInt16('_file_name_length' ),
167             ULInt16('_extra_field_length' ),
168             ULInt16('_file_comment_length' ),
169             ULInt16('_start_disk_number' ),
170             ULInt16('_internal_file_attr' ),
171             ULInt32('_external_file_attr' ),
172             ULInt32('_rel_offset_local_header'),
173             String(
174             '_file_name',
175             sub {
176 5     5   10217 $_->ctx->{'_file_name_length'};
177             },
178             ),
179             Field(
180             '_extra_field',
181             sub {
182 5     5   1454 $_->ctx->{'_extra_field_length'};
183             },
184             ),
185             Field(
186             '_file_comment',
187             sub {
188 5     5   317 $_->ctx->{'_file_comment_length'};
189             }
190 5         17 ),
191             ),
192             );
193              
194 5         368 %{ $parsed_entry_struct[$entry_count] } = (
  5         19  
195 5         17 %{ $parsed_entry_struct[$entry_count] },
196 5         3141 %{ $central_directory_struct->parse(
197             $self->{'_bit_stream'}
198             )
199             }
200             );
201 5         130 $entry_count++;
202             }
203              
204 1 50       4 last if $self->_check_signature('06054b50');
205              
206             my $central_directory_end_struct
207             = Struct(
208             '_central_directory_end',
209             ULInt32('_signature' ),
210             ULInt16('_disk_number' ),
211             ULInt16('_start_disk_number' ),
212             ULInt16('_total_disk_entries'),
213             ULInt16('_total_entries' ),
214             ULInt32('_size' ),
215             ULInt32('_start_offset' ),
216             ULInt16('_zip_comment_length'),
217             String(
218             '_zip_comment',
219             sub {
220 1     1   441 $_->ctx->{'_zip_comment_length'};
221             },
222 1         6 ),
223             );
224              
225 1         214 my $parsed_central_directory_end_struct
226             = $central_directory_end_struct->parse( $self->{'_bit_stream'} );
227 1         74 $self->{'_central_directory_end'} = $parsed_central_directory_end_struct;
228 1         3 push @{ $self->{'_entry'} }, @parsed_entry_struct;
  1         5  
229              
230 1         4 $self->{'_is_parsed'} = 1;
231 1         17 return 1;
232             }
233              
234             sub get_entry {
235 4     4 1 2802 my ( $self, $entry_number ) = @_;
236              
237 4 100       14 if ( !defined $entry_number ) {
238 2         2 my @entry_objects;
239 2         4 for ( @{ $self->{'_entry'} } ) {
  2         6  
240 10         31 push @entry_objects, bless $_, 'Archive::Zip::Parser::Entry';
241             }
242              
243 2         10 return @entry_objects;
244             }
245              
246 2         20 return bless $self->{'_entry'}->[$entry_number],
247             'Archive::Zip::Parser::Entry';
248             }
249              
250             sub _set_position_in_file {
251 4     4   9 my ( $self, $position_in_file, $whence ) = @_;
252 4         10 my $file_handle = $self->{'_file_handle'};
253              
254 4         16 seek $file_handle, $position_in_file, $whence;
255              
256 4         953 return;
257             }
258              
259             sub get_central_directory_end {
260 1     1 1 1590 my $self = shift;
261 1         9 return bless $self->{'_central_directory_end'},
262             'Archive::Zip::Parser::CentralDirectoryEnd';
263             }
264              
265             sub _check_signature {
266 13     13   22 my ( $self, $next_signature ) = @_;
267              
268 13         42 my $signature_struct =
269             Struct(
270             '_signature_struct',
271             Peek(
272             ULInt32('_signature')
273             ),
274             );
275 13         831 my $parsed_signature_struct
276             = $signature_struct->parse( $self->{'_bit_stream'} );
277 13         2499 my $signature = unpack( 'H*', pack( 'N', $parsed_signature_struct->{'_signature'} ) );
278 13 100       45 if ( $signature ne $next_signature ) {
279 2         15 return 1;
280             }
281 11         98 return;
282             }
283              
284             1;
285             __END__