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   46468 use strict;
  4         8  
  4         108  
27 4     4   17 use warnings;
  4         8  
  4         92  
28              
29 4     4   17 use base qw/Data::Plist::Reader/;
  4         6  
  4         1956  
30 4     4   1308 use Data::Plist;
  4         8  
  4         35  
31              
32 4     4   3555 use Encode qw(decode);
  4         43528  
  4         339  
33 4     4   45 use Fcntl qw(:seek);
  4         8  
  4         552  
34 4     4   4898 use Math::BigInt;
  4         60823  
  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 10 my $self = shift;
48              
49 7         7 my ($type) = @_;
50 7 100       49 if ( $type == 0 ) {
    100          
    100          
    50          
51 4         19 return [ "null", 0 ];
52             } elsif ( $type == 8 ) {
53 1         5 return [ "false", 0 ];
54             } elsif ( $type == 9 ) {
55 1         6 return [ "true", 1 ];
56             } elsif ( $type == 15 ) {
57 1         5 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 309 my $self = shift;
76 321         282 my ($size) = @_;
77              
78 321         276 my ( $buf, $val );
79 321         503 read( $self->{fh}, $buf, 1 << $size );
80 321 100       431 if ( $size == 0 ) { # 8 bit
    100          
    100          
    50          
81 268         333 $val = unpack( "C", $buf );
82             } elsif ( $size == 1 ) { # 16 bit
83 48         66 $val = unpack( "n", $buf );
84             } elsif ( $size == 2 ) { # 32 bit
85 3         6 $val = unpack( "N", $buf );
86             } elsif ( $size == 3 ) { # 64 bit
87              
88 2         6 my ( $hw, $lw ) = unpack( "NN", $buf );
89 2         11 $val = Math::BigInt->new($hw)->blsft(32)->bior($lw);
90 2 100       5445 if ( $val->bcmp( Math::BigInt->new(2)->bpow(63) ) > 0 ) {
91 1         207 $val -= Math::BigInt->new(2)->bpow(64);
92             }
93             } else {
94 0         0 die "Invalid size for integer ($size)";
95             }
96              
97 321         3340 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 4 my $self = shift;
113 3         5 my ($size) = @_;
114              
115 3         4 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         9 $val = unpack( "d", reverse $buf );
121             } else {
122 0         0 die "Invalid size for real ($size)";
123             }
124              
125 3         18 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 3 my $self = shift;
140 1         2 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         4 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 10 my $self = shift;
160 5         5 my ($size) = @_;
161              
162 5         7 my $buf;
163 5         325 read( $self->{fh}, $buf, $size );
164              
165             # Binary data is often a binary plist! Unpack it.
166 5 100       20 if ( $buf =~ /^bplist00/ ) {
167 1   33     1 $buf = eval { ( ref $self )->open_string($buf) } || $buf;
168             }
169              
170 5         97 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 44 my $self = shift;
185 40         42 my ($size) = @_;
186              
187 40         33 my $buf;
188 40         60 read( $self->{fh}, $buf, $size );
189              
190 40         104 $buf = pack "U0C*", unpack "C*", $buf; # mark as Unicode
191              
192 40         172 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 4 my $self = shift;
206 2         5 my ($size) = @_;
207              
208 2         3 my $buf;
209 2         8 read( $self->{fh}, $buf, 2 * $size );
210              
211 2         13 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 38 my $self = shift;
225 36         38 my ($count) = @_;
226 36         31 my $buf;
227 36         64 read( $self->{fh}, $buf, $count * $self->{refsize} );
228 36 100       126 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 15 my $self = shift;
242 10         13 my ($size) = @_;
243              
244             return [
245 10         26 "array", [ map { $self->binary_read($_) } $self->read_refs($size) ]
  318         524  
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 20 my $self = shift;
261 13         14 my ($size) = @_;
262 13         12 my %dict;
263              
264             # read keys
265 13         33 my @keys = $self->read_refs($size);
266 13         32 my @objs = $self->read_refs($size);
267              
268 13         40 for my $j ( 0 .. $#keys ) {
269 20         87 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         57 my $obj = $self->binary_read( $objs[$j] );
273 20         6121 $dict{$key} = $obj;
274             }
275              
276 13         74 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 3 my $self = shift;
292 1         1 my ($size) = @_;
293              
294             # UIDs are stored internally identically to ints
295 1         3 my $v = $self->read_integer($size)->[1];
296 1         6 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 362 my $self = shift;
311 401         364 my ($objNum) = @_;
312              
313 401 100       661 if ( defined $objNum ) {
314 393         744 die "Bad offset: $objNum"
315 393 50       347 unless $objNum < @{ $self->{offsets} };
316 393         793 seek( $self->{fh}, $self->{offsets}[$objNum], SEEK_SET );
317             }
318              
319             # get object type/size
320 401         344 my $buf;
321 401 50       878 read( $self->{fh}, $buf, 1 )
322             or die "Can't read type byte: $!\byte:";
323              
324 401         555 my $size = unpack( "C*", $buf ) & 0x0F; # Low nybble is size
325 401         473 my $objType = unpack( "C*", $buf ) >> 4; # High nybble is type
326 401 100 100     1335 $size = $self->binary_read->[1]
327             if $objType != 0 and $size == 15;
328              
329 401         1514 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       703 die "Unknown type $objType" unless $types{$objType};
343 401         505 my $method = "read_" . $types{$objType};
344 401 50       1142 die "Can't $method" unless $self->can($method);
345 401         797 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 39801 my $self = shift;
361 52         77 my ($str) = @_;
362              
363 52 100 100     297 die "Not a binary plist file\n"
364             unless length $str >= 8 and substr( $str, 0, 8 ) eq "bplist00";
365 50 100       104 die "Read of plist trailer failed\n"
366             unless length $str >= 40;
367 46 100       101 die "Invalid top object identifier\n"
368             unless length $str > 40;
369              
370 45         167 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 63 my $self = shift;
386 49 100       116 $self = $self->new() unless ref $self;
387              
388 49         53 my ($fh) = @_;
389              
390 49         49 my $buf;
391 49         88 $self->{fh} = $fh;
392 49         145 seek( $self->{fh}, 0, SEEK_SET );
393 49         161 read( $self->{fh}, $buf, 8 );
394 49 100       99 unless ( $buf eq "bplist00" ) {
395 1         7 die "Not a binary plist file\n";
396             }
397              
398             # get trailer
399 48 50       54 eval { seek( $self->{fh}, -32, SEEK_END ) }
  48         157  
400             or die "Read of plist trailer failed\n";
401 48         88 my $end = tell( $self->{fh} );
402              
403 48 50       85 die "Read of plist trailer failed\n"
404             unless $end >= 8;
405              
406 48 50       128 unless ( read( $self->{fh}, $buf, 32 ) == 32 ) {
407 0         0 die "Read of plist trailer failed\n";
408             }
409 48         79 local $self->{refsize};
410 48         52 my ( $OffsetSize, $NumObjects, $TopObject, $OffsetTableOffset );
411 48         246 ( $OffsetSize, $self->{refsize}, $NumObjects, $TopObject,
412             $OffsetTableOffset
413             ) = unpack "x6CC(x4N)3", $buf;
414              
415             # Sanity check the trailer
416 48 100 100     613 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         7 die "Invalid top object identifier\n";
425             } elsif ( $OffsetTableOffset < 8
426             or $OffsetTableOffset > $end
427             or $OffsetTableOffset + $NumObjects * $OffsetSize > $end )
428             {
429 2         16 die "Invalid offset table address (overlap with header or footer).";
430             }
431              
432             # get the offset table
433 40         60 seek( $fh, $OffsetTableOffset, SEEK_SET );
434              
435 40         41 my $offsetTable;
436 40         88 my $readSize
437             = read( $self->{fh}, $offsetTable, $NumObjects * $OffsetSize );
438 40 50       90 if ( $readSize != $NumObjects * $OffsetSize ) {
439 0         0 die "Offset table read $readSize bytes, expected ",
440             $NumObjects * $OffsetSize;
441             }
442              
443 40         185 my @Offsets = unpack( [ "", "C*", "n*", "(H6)*", "N*" ]->[$OffsetSize],
444             $offsetTable );
445 40 100       104 if ( $OffsetSize == 3 ) {
446 2         5 @Offsets = map { hex($_) } @Offsets;
  6         14  
447             }
448              
449             # Catch invalid offset addresses in the offset table
450 40 100 100     63 if (grep {
  397 100 100     1805  
451             $_ < 8
452             or $_ >= $end
453             or ($_ >= $OffsetTableOffset
454             and $_ < $OffsetTableOffset + $NumObjects * $OffsetSize )
455             } @Offsets
456             )
457             {
458 5         34 die "Invalid address in offset table\n";
459             }
460              
461 35         66 local $self->{offsets} = \@Offsets;
462              
463 35         86 my $top = $self->binary_read($TopObject);
464 35         153 close($fh);
465              
466 35         206 return Data::Plist->new( data => $top );
467             }
468              
469             1;