File Coverage

blib/lib/Data/Plist/BinaryReader.pm
Criterion Covered Total %
statement 156 162 96.3
branch 62 76 81.5
condition 25 30 83.3
subroutine 21 21 100.0
pod 14 14 100.0
total 278 303 91.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Data::Plist::BinaryReader - Creates Data::Plists from binary files
4              
5             =head1 SYNOPSIS
6              
7             # Create new
8             my $read = Data::Plist::BinaryReader->new;
9              
10             # Read from a string
11             my $plist = $read->open_string($binarystring);
12              
13             # Read from a binary file
14             $plist = $read->open_fh($filename);
15              
16             =head1 DESCRIPTION
17              
18             C takes data formatted as one of
19             Apple's binary property lists, either from a string or a
20             filehandle and returns it as a C.
21              
22             =cut
23              
24             package Data::Plist::BinaryReader;
25              
26 4     4   59217 use strict;
  4         10  
  4         204  
27 4     4   23 use warnings;
  4         10  
  4         125  
28              
29 4     4   20 use base qw/Data::Plist::Reader/;
  4         7  
  4         2736  
30 4     4   1732 use Data::Plist;
  4         13  
  4         74  
31              
32 4     4   4547 use Encode qw(decode);
  4         50955  
  4         399  
33 4     4   45 use Fcntl qw(:seek);
  4         10  
  4         543  
34 4     4   7336 use Math::BigInt;
  4         70689  
  4         28  
35              
36             =head1 METHODS
37              
38             =head2 read_misc $type
39              
40             Takes an integer C<$type> indicating which misc is being
41             read. Returns an array containing the type of misc and its
42             associated integer.
43              
44             =cut
45              
46             sub read_misc {
47 7     7 1 12 my $self = shift;
48              
49 7         12 my ($type) = @_;
50 7 100       26 if ( $type == 0 ) {
    100          
    100          
    50          
51 4         23 return [ "null", 0 ];
52             } elsif ( $type == 8 ) {
53 1         7 return [ "false", 0 ];
54             } elsif ( $type == 9 ) {
55 1         7 return [ "true", 1 ];
56             } elsif ( $type == 15 ) {
57 1         7 return [ "fill", 15 ];
58             } else {
59 0         0 return [ "???", $type ];
60             }
61             }
62              
63             =head2 read_integer $size
64              
65             Takes an integer C<$size> indicating number of bytes needed
66             to encode the integer (2**C<$size> = number of
67             bytes). Reads that number of bytes from the filehandle and
68             unpacks it. Returns an array containing the string
69             "integer" and the value of the integer read from the
70             filehandle.
71              
72             =cut
73              
74             sub read_integer {
75 321     321 1 369 my $self = shift;
76 321         357 my ($size) = @_;
77              
78 321         322 my ( $buf, $val );
79 321         652 read( $self->{fh}, $buf, 1 << $size );
80 321 100       613 if ( $size == 0 ) { # 8 bit
    100          
    100          
    50          
81 268         438 $val = unpack( "C", $buf );
82             } elsif ( $size == 1 ) { # 16 bit
83 48         89 $val = unpack( "n", $buf );
84             } elsif ( $size == 2 ) { # 32 bit
85 3         9 $val = unpack( "N", $buf );
86             } elsif ( $size == 3 ) { # 64 bit
87              
88 2         5 my ( $hw, $lw ) = unpack( "NN", $buf );
89 2         14 $val = Math::BigInt->new($hw)->blsft(32)->bior($lw);
90 2 100       1154 if ( $val->bcmp( Math::BigInt->new(2)->bpow(63) ) > 0 ) {
91 1         274 $val -= Math::BigInt->new(2)->bpow(64);
92             }
93             } else {
94 0         0 die "Invalid size for integer ($size)";
95             }
96              
97 321         2379 return [ "integer", $val ];
98             }
99              
100             =head2 read_real $size
101              
102             Takes an integer C<$size> indicating the number of bytes
103             needed to encode the float (see L). Reads
104             that number of bytes from the filehandle and unpacks
105             it. The number of bytes is limited to 4 and 8. Returns an
106             array containing the string "array" and the float read from
107             the filehandle.
108              
109             =cut
110              
111             sub read_real {
112 3     3 1 6 my $self = shift;
113 3         4 my ($size) = @_;
114              
115 3         5 my ( $buf, $val );
116 3         8 read( $self->{fh}, $buf, 1 << $size );
117 3 50       11 if ( $size == 2 ) { # 32 bit
    50          
118 0         0 $val = unpack( "f", reverse $buf );
119             } elsif ( $size == 3 ) { # 64 bit
120 3         13 $val = unpack( "d", reverse $buf );
121             } else {
122 0         0 die "Invalid size for real ($size)";
123             }
124              
125 3         19 return [ "real", $val ];
126             }
127              
128             =head2 read_date $size
129              
130             Takes an integer C<$size>, checks to ensure that it's
131             within the proper boundaries, and then passes it to
132             L to be dealt with, since dates are just stored
133             as floats. Returns an array containing the string "date"
134             and the date read from the filehandle.
135              
136             =cut
137              
138             sub read_date {
139 1     1 1 2 my $self = shift;
140 1         3 my ($size) = @_;
141 1 50 33     11 die "Invalid size for date ($size)"
142             if ( $size > 3 or $size < 2 );
143              
144             # Dates are just stored as floats
145 1         5 return [ "date", $self->read_real($size)->[1] ];
146             }
147              
148             =head2 read_data $size
149              
150             Takes an integer C<$size>, indicating the number of bytes
151             of binary data stored and reads them from the
152             filehandle. Checks if the bytes are actually another binary
153             plist and unpacks it if so. Returns an array containing the
154             string "data" and the binary data read from the filehandle.
155              
156             =cut
157              
158             sub read_data {
159 5     5 1 11 my $self = shift;
160 5         7 my ($size) = @_;
161              
162 5         9 my $buf;
163 5         368 read( $self->{fh}, $buf, $size );
164              
165             # Binary data is often a binary plist! Unpack it.
166 5 100       19 if ( $buf =~ /^bplist00/ ) {
167 1   33     2 $buf = eval { ( ref $self )->open_string($buf) } || $buf;
168             }
169              
170 5         110 return [ "data", $buf ];
171             }
172              
173             =head2 read_string $size
174              
175             Takes an integer C<$size> indicating the number of bytes
176             used to encode the UTF-8 string stored and reads them from
177             the filehandle. Marks them as Unicode and returns an array
178             containing the string "string" and the string read from the
179             filehandle.
180              
181             =cut
182              
183             sub read_string {
184 40     40 1 50 my $self = shift;
185 40         40 my ($size) = @_;
186              
187 40         40 my $buf;
188 40         72 read( $self->{fh}, $buf, $size );
189              
190 40         115 $buf = pack "U0C*", unpack "C*", $buf; # mark as Unicode
191              
192 40         408 return [ "string", $buf ];
193             }
194              
195             =head2 read_ustring
196              
197             Takes an integer C<$size> indicating the number of bytes
198             used to encode the UTF-16 string stored and reads them from
199             the filehandle. Returns an array containing the string
200             "ustring" and the string read from the filehandle.
201              
202             =cut
203              
204             sub read_ustring {
205 2     2 1 5 my $self = shift;
206 2         4 my ($size) = @_;
207              
208 2         3 my $buf;
209 2         8 read( $self->{fh}, $buf, 2 * $size );
210              
211 2         15 return [ "ustring", decode( "UTF-16BE", $buf ) ];
212             }
213              
214             =head2 read_refs $count
215              
216             Takes an integer C<$count> indicating the number of
217             references in either a dict or an array. Returns the
218             references pointing to the locations fo the contents of the
219             dict or array.
220              
221             =cut
222              
223             sub read_refs {
224 36     36 1 44 my $self = shift;
225 36         41 my ($count) = @_;
226 36         34 my $buf;
227 36         92 read( $self->{fh}, $buf, $count * $self->{refsize} );
228 36 100       143 return unpack( ( $self->{refsize} == 1 ? "C*" : "n*" ), $buf );
229             }
230              
231             =head2 read_array $size
232              
233             Takes an integer C<$size> indicating the number of objects
234             that are contained in the array. Returns an array
235             containing the string "array" and the references pointing
236             to the location of the contents of the array in the file.
237              
238             =cut
239              
240             sub read_array {
241 10     10 1 17 my $self = shift;
242 10         13 my ($size) = @_;
243              
244             return [
245 10         28 "array", [ map { $self->binary_read($_) } $self->read_refs($size) ]
  318         648  
246             ];
247             }
248              
249             =head2 read_dict $size
250              
251             Takes an integer C<$size> indicating the number of
252             key-value pairs contained in the dict. Returns an array
253             containing the string "dict" and the references pointing to
254             the location of the key-value pairs of the dict in the
255             file.
256              
257             =cut
258              
259             sub read_dict {
260 13     13 1 19 my $self = shift;
261 13         15 my ($size) = @_;
262 13         14 my %dict;
263              
264             # read keys
265 13         32 my @keys = $self->read_refs($size);
266 13         25 my @objs = $self->read_refs($size);
267              
268 13         40 for my $j ( 0 .. $#keys ) {
269 20         94 my $key = $self->binary_read( $keys[$j] );
270 20 50       48 die "Key of hash isn't a string!" unless $key->[0] eq "string";
271 20         27 $key = $key->[1];
272 20         49 my $obj = $self->binary_read( $objs[$j] );
273 20         6778 $dict{$key} = $obj;
274             }
275              
276 13         80 return [ "dict", \%dict ];
277             }
278              
279             =head2 read_uid $size
280              
281             Takes an integer C<$size> indicating number of bytes needed
282             to encode the uid (2**C<$size> = number of bytes) and then
283             passes it to L to be dealt with, since uids
284             are stored identically to integers. Returns an array
285             containing the string "uid" and the uid read from the
286             filehandle.
287              
288             =cut
289              
290             sub read_uid {
291 1     1 1 2 my $self = shift;
292 1         3 my ($size) = @_;
293              
294             # UIDs are stored internally identically to ints
295 1         5 my $v = $self->read_integer($size)->[1];
296 1         7 return [ UID => $v ];
297             }
298              
299             =head2 binary_read $objNum
300              
301             Takes an integer indicating the offset number of the
302             current object C<$objNum> and checks to make sure it's
303             valid. Reads the object's type and size and then matches
304             the type to its read method. Passes the size to the correct
305             method and returns what that method returns.
306              
307             =cut
308              
309             sub binary_read {
310 401     401 1 494 my $self = shift;
311 401         439 my ($objNum) = @_;
312              
313 401 100       743 if ( defined $objNum ) {
314 393         981 die "Bad offset: $objNum"
315 393 50       351 unless $objNum < @{ $self->{offsets} };
316 393         945 seek( $self->{fh}, $self->{offsets}[$objNum], SEEK_SET );
317             }
318              
319             # get object type/size
320 401         394 my $buf;
321 401 50       1238 read( $self->{fh}, $buf, 1 )
322             or die "Can't read type byte: $!\byte:";
323              
324 401         733 my $size = unpack( "C*", $buf ) & 0x0F; # Low nybble is size
325 401         635 my $objType = unpack( "C*", $buf ) >> 4; # High nybble is type
326 401 100 100     1578 $size = $self->binary_read->[1]
327             if $objType != 0 and $size == 15;
328              
329 401         2051 my %types = (
330             0 => "misc",
331             1 => "integer",
332             2 => "real",
333             3 => "date",
334             4 => "data",
335             5 => "string",
336             6 => "ustring",
337             8 => "uid",
338             10 => "array",
339             13 => "dict",
340             );
341              
342 401 50       834 die "Unknown type $objType" unless $types{$objType};
343 401         660 my $method = "read_" . $types{$objType};
344 401 50       1254 die "Can't $method" unless $self->can($method);
345 401         884 return $self->$method($size);
346             }
347              
348             =head2 open_string $string
349              
350             Takes a string of binary information in Apple's binary
351             property list format C<$string>. Checks to ensure that it's
352             of the correct format and then passes its superclass's
353             L. The error proofing is done because
354             seeking in in-memory filehandles can cause perl 5.8.8 to
355             explode with "Out of memory" or "panic: memory wrap".
356              
357             =cut
358              
359             sub open_string {
360 52     52 1 52505 my $self = shift;
361 52         102 my ($str) = @_;
362              
363 52 100 100     378 die "Not a binary plist file\n"
364             unless length $str >= 8 and substr( $str, 0, 8 ) eq "bplist00";
365 50 100       124 die "Read of plist trailer failed\n"
366             unless length $str >= 40;
367 46 100       108 die "Invalid top object identifier\n"
368             unless length $str > 40;
369              
370 45         196 return $self->SUPER::open_string($str);
371             }
372              
373             =head2 open_fh $filehandle
374              
375             Used for reading binary data from a filehandle
376             C<$filehandle> rather than a string. Opens the filehandle
377             and sanity checks the header, trailer and offset
378             table. Returns a C containing the top object
379             of the filehandle after it's been passed to
380             L.
381              
382             =cut
383              
384             sub open_fh {
385 49     49 1 71 my $self = shift;
386 49 100       179 $self = $self->new() unless ref $self;
387              
388 49         69 my ($fh) = @_;
389              
390 49         59 my $buf;
391 49         110 $self->{fh} = $fh;
392 49         173 seek( $self->{fh}, 0, SEEK_SET );
393 49         180 read( $self->{fh}, $buf, 8 );
394 49 100       131 unless ( $buf eq "bplist00" ) {
395 1         6 die "Not a binary plist file\n";
396             }
397              
398             # get trailer
399 48 50       69 eval { seek( $self->{fh}, -32, SEEK_END ) }
  48         189  
400             or die "Read of plist trailer failed\n";
401 48         101 my $end = tell( $self->{fh} );
402              
403 48 50       104 die "Read of plist trailer failed\n"
404             unless $end >= 8;
405              
406 48 50       160 unless ( read( $self->{fh}, $buf, 32 ) == 32 ) {
407 0         0 die "Read of plist trailer failed\n";
408             }
409 48         93 local $self->{refsize};
410 48         58 my ( $OffsetSize, $NumObjects, $TopObject, $OffsetTableOffset );
411 48         328 ( $OffsetSize, $self->{refsize}, $NumObjects, $TopObject,
412             $OffsetTableOffset
413             ) = unpack "x6CC(x4N)3", $buf;
414              
415             # Sanity check the trailer
416 48 100 100     756 if ( $OffsetSize < 1 or $OffsetSize > 4 ) {
    100 100        
    100 66        
    100 100        
    100          
417 2         12 die "Invalid offset size\n";
418             } elsif ( $self->{refsize} < 1 or $self->{refsize} > 2 ) {
419 2         11 die "Invalid reference size\n";
420             } elsif ( 2**( 8 * $self->{refsize} ) < $NumObjects ) {
421 1         3 die
422 1         11 "Reference size (@{[$self->{refsize}]}) is too small for purported number of objects ($NumObjects)\n";
423             } elsif ( $TopObject >= $NumObjects ) {
424 1         6 die "Invalid top object identifier\n";
425             } elsif ( $OffsetTableOffset < 8
426             or $OffsetTableOffset > $end
427             or $OffsetTableOffset + $NumObjects * $OffsetSize > $end )
428             {
429 2         18 die "Invalid offset table address (overlap with header or footer).";
430             }
431              
432             # get the offset table
433 40         3560 seek( $fh, $OffsetTableOffset, SEEK_SET );
434              
435 40         209 my $offsetTable;
436 40         2624 my $readSize
437             = read( $self->{fh}, $offsetTable, $NumObjects * $OffsetSize );
438 40 50       107 if ( $readSize != $NumObjects * $OffsetSize ) {
439 0         0 die "Offset table read $readSize bytes, expected ",
440             $NumObjects * $OffsetSize;
441             }
442              
443 40         247 my @Offsets = unpack( [ "", "C*", "n*", "(H6)*", "N*" ]->[$OffsetSize],
444             $offsetTable );
445 40 100       116 if ( $OffsetSize == 3 ) {
446 2         5 @Offsets = map { hex($_) } @Offsets;
  6         15  
447             }
448              
449             # Catch invalid offset addresses in the offset table
450 40 100 100     71 if (grep {
  397 100 100     2032  
451             $_ < 8
452             or $_ >= $end
453             or ($_ >= $OffsetTableOffset
454             and $_ < $OffsetTableOffset + $NumObjects * $OffsetSize )
455             } @Offsets
456             )
457             {
458 5         35 die "Invalid address in offset table\n";
459             }
460              
461 35         97 local $self->{offsets} = \@Offsets;
462              
463 35         116 my $top = $self->binary_read($TopObject);
464 35         140 close($fh);
465              
466 35         326 return Data::Plist->new( data => $top );
467             }
468              
469             1;