File Coverage

blib/lib/Mac/PropertyList/ReadBinary.pm
Criterion Covered Total %
statement 166 176 94.3
branch 23 40 57.5
condition 3 3 100.0
subroutine 27 27 100.0
pod 2 2 100.0
total 221 248 89.1


line stmt bran cond sub pod time code
1 5     5   3005 use v5.10;
  5         20  
2              
3             package Mac::PropertyList::ReadBinary;
4 5     5   31 use strict;
  5         9  
  5         108  
5 5     5   24 use warnings;
  5         10  
  5         130  
6              
7 5     5   25 use Carp;
  5         19  
  5         306  
8 5     5   695 use Data::Dumper;
  5         6994  
  5         306  
9 5     5   2480 use Encode qw(decode);
  5         40455  
  5         397  
10 5     5   540 use Mac::PropertyList;
  5         13  
  5         249  
11 5     5   5929 use Math::BigInt;
  5         131773  
  5         29  
12 5     5   122767 use MIME::Base64 qw(decode_base64);
  5         3690  
  5         412  
13 5     5   2741 use POSIX qw(SEEK_END SEEK_SET);
  5         34031  
  5         35  
14 5     5   7485 use XML::Entities ();
  5         13  
  5         11128  
15              
16             our $VERSION = '1.502';
17              
18             __PACKAGE__->_run( @ARGV ) unless caller;
19              
20             =encoding utf8
21              
22             =head1 NAME
23              
24             Mac::PropertyList::ReadBinary - read binary property list files
25              
26             =head1 SYNOPSIS
27              
28             # use directly
29             use Mac::PropertyList::ReadBinary;
30              
31             my $parser = Mac::PropertyList::ReadBinary->new( $file );
32              
33             my $plist = $parser->plist;
34              
35             # use indirectly, automatically selects right reader
36             use Mac::PropertyList;
37              
38             my $plist = parse_plist_file( $file );
39              
40             =head1 DESCRIPTION
41              
42             This module is a low-level interface to the Mac OS X Property List
43             (plist) format. You probably shouldn't use this in
44             applications—build interfaces on top of this so you don't have to
45             put all the heinous multi-level object stuff where people have to look
46             at it.
47              
48             You can parse a plist file and get back a data structure. You can take
49             that data structure and get back the plist as XML (but not binary
50             yet). If you want to change the structure inbetween that's your
51             business. :)
52              
53             See C for more details.
54              
55             =head2 Methods
56              
57             =over 4
58              
59             =item new( FILENAME | SCALAR_REF | FILEHANDLE )
60              
61             Opens the data source, doing the right thing for filenames,
62             scalar references, or a filehandle.
63              
64             =cut
65              
66             sub new {
67 9     9 1 1368 my( $class, $source ) = @_;
68              
69 9         32 my $self = bless { source => $source }, $class;
70              
71 9         47 $self->_read;
72              
73 9         29 $self;
74             }
75              
76 38     38   418 sub _source { $_[0]->{source} }
77 1651     1651   4737 sub _fh { $_[0]->{fh} }
78 249     249   752 sub _trailer { $_[0]->{trailer} }
79 488     488   2490 sub _offsets { $_[0]->{offsets} }
80 195     195   347 sub _object_ref_size { $_[0]->_trailer->{ref_size} }
81              
82             =item plist
83              
84             Returns the C data structure.
85              
86             =cut
87              
88 9     9 1 664 sub plist { $_[0]->{parsed} }
89              
90             sub _object_size
91             {
92             $_[0]->_trailer->{object_count} * $_[0]->_trailer->{offset_size}
93 9     9   22 }
94              
95             sub _read {
96 9     9   22 my( $self, $thingy ) = @_;
97              
98 9         30 $self->{fh} = $self->_get_filehandle;
99 9         33 $self->_read_plist_trailer;
100              
101 9         29 $self->_get_offset_table;
102              
103 9         20 my $top = $self->_read_object_at_offset( $self->_trailer->{top_object} );
104              
105 9         26 $self->{parsed} = $top;
106             }
107              
108             sub _get_filehandle {
109 13     13   2852 my( $self, $thingy ) = @_;
110              
111 13         23 my $fh;
112              
113 13 100       41 if( ! ref $self->_source ) { # filename
    100          
    50          
114 3 100       15 open $fh, "<", $self->_source
115 1         11 or die "Could not open [@{[$self->_source]}]! $!";
116             }
117             elsif( ref $self->_source eq ref \ '' ) { # scalar ref
118 3 50   3   21 open $fh, "<", $self->_source or croak "Could not open file! $!";
  3         8  
  3         28  
  9         47  
119             }
120             elsif( ref $self->_source ) { # filehandle
121 1         3 $fh = $self->_source;
122             }
123             else {
124 0         0 croak( 'No source to read from!' );
125             }
126              
127 12         2839 $fh;
128             }
129              
130             sub _read_plist_trailer
131             {
132 9     9   18 my $self = shift;
133              
134 9         23 seek $self->_fh, -32, SEEK_END;
135              
136 9         21 my $buffer;
137 9         17 read $self->_fh, $buffer, 32;
138 9         19 my %hash;
139              
140 9         115 @hash{ qw( offset_size ref_size object_count top_object table_offset ) }
141             = unpack "x6 C C (x4 N)3", $buffer;
142              
143 9         26 $self->{trailer} = \%hash;
144             }
145              
146             sub _get_offset_table
147             {
148 9     9   27 my $self = shift;
149              
150 9         22 seek $self->_fh, $self->_trailer->{table_offset}, SEEK_SET;
151              
152 9         24 my $try_to_read = $self->_object_size;
153              
154 9         35 my $raw_offset_table;
155 9         17 my $read = read $self->_fh, $raw_offset_table, $try_to_read;
156              
157 9 50       30 croak "reading offset table failed!" unless $read == $try_to_read;
158              
159 9         69 my @offsets = unpack ["","C*","n*","(H6)*","N*"]->[$self->_trailer->{offset_size}], $raw_offset_table;
160              
161 9         26 $self->{offsets} = \@offsets;
162              
163 9 50       21 if( $self->_trailer->{offset_size} == 3 )
164             {
165 0         0 @offsets = map { hex } @offsets;
  0         0  
166             }
167              
168             }
169              
170             sub _read_object_at_offset {
171 488     488   847 my( $self, $offset ) = @_;
172              
173 488         2582 my @caller = caller(1);
174              
175 488         1037 seek $self->_fh, ${ $self->_offsets }[$offset], SEEK_SET;
  488         804  
176              
177 488         1231 $self->_read_object;
178             }
179              
180             # # # # # # # # # # # # # #
181              
182 0         0 BEGIN {
183              
184 5     5   110 my %singletons = (
185             0 => undef,
186             8 => Mac::PropertyList::false->new(),
187             9 => Mac::PropertyList::true->new(),
188              
189             # 15 is also defined (as "fill") in the comments to Apple's
190             # implementation in CFBinaryPList.c but Apple's actual code has no
191             # support for it at all, either reading or writing, so it's
192             # probably not important to implement.
193              
194             );
195              
196             my $type_readers = {
197              
198             0 => sub { # the odd balls
199 4         8 my( $self, $length ) = @_;
200              
201 4 50       16 return $singletons{ $length } if exists $singletons{ $length };
202              
203 0         0 croak ( sprintf "Unknown type byte %02X\n", $length );
204             },
205              
206             1 => sub { # integers
207 174         302 my( $self, $length ) = @_;
208 174 50       316 croak "Integer > 8 bytes = $length" if $length > 3;
209              
210 174         264 my $byte_length = 1 << $length;
211              
212 174         247 my( $buffer, $value );
213 174         323 read $self->_fh, $buffer, $byte_length;
214              
215 174         382 my @formats = qw( C n N NN );
216 174         324 my @values = unpack $formats[$length], $buffer;
217              
218 174 50       340 if( $length == 3 )
219             {
220 0         0 my( $high, $low ) = @values;
221              
222 0         0 my $b = Math::BigInt->new($high)->blsft(32)->bior($low);
223 0 0       0 if( $b->bcmp(Math::BigInt->new(2)->bpow(63)) > 0)
224             {
225 0         0 $b -= Math::BigInt->new(2)->bpow(64);
226             }
227              
228 0         0 @values = ( $b );
229             }
230              
231 174         561 return Mac::PropertyList::integer->new( $values[0] );
232             },
233              
234             2 => sub { # reals
235 6         25 my( $self, $length ) = @_;
236 6 50       17 croak "Real > 8 bytes" if $length > 3;
237 6 50       15 croak "Bad length [$length]" if $length < 2;
238              
239 6         20 my $byte_length = 1 << $length;
240              
241 6         10 my( $buffer, $value );
242 6         15 read $self->_fh, $buffer, $byte_length;
243              
244 6         17 my @formats = qw( a a f> d> );
245 6         16 my @values = unpack $formats[$length], $buffer;
246              
247 6         34 return Mac::PropertyList::real->new( $values[0] );
248             },
249              
250             3 => sub { # date
251 6         13 my( $self, $length ) = @_;
252 6 50       15 croak "Date != 8 bytes" if $length != 3;
253 6         12 my $byte_length = 1 << $length;
254              
255 6         10 my( $buffer, $value );
256 6         16 read $self->_fh, $buffer, $byte_length;
257              
258 6         20 my @values = unpack 'd>', $buffer;
259              
260 6         17 $self->{MLen} += 9;
261              
262 6         440 my $adjusted_time = POSIX::strftime(
263             "%Y-%m-%dT%H:%M:%SZ",
264             gmtime( 978307200 + $values[0])
265             );
266              
267 6         59 return Mac::PropertyList::date->new( $adjusted_time );
268             },
269              
270             4 => sub { # binary data
271 2         6 my( $self, $length ) = @_;
272              
273 2         4 my( $buffer, $value );
274 2         7 read $self->_fh, $buffer, $length;
275              
276 2         19 return Mac::PropertyList::data->new( $buffer );
277             },
278              
279             5 => sub { # utf8 string
280 287         532 my( $self, $length ) = @_;
281              
282 287         436 my( $buffer, $value );
283 287         602 read $self->_fh, $buffer, $length;
284              
285 287         718 $buffer = Encode::decode( 'ascii', $buffer );
286              
287 287         8298 return Mac::PropertyList::string->new( $buffer );
288             },
289              
290             6 => sub { # unicode string
291 4         10 my( $self, $length ) = @_;
292              
293 4         8 my( $buffer, $value );
294 4         10 read $self->_fh, $buffer, 2 * $length;
295              
296 4         12 $buffer = Encode::decode( "UTF-16BE", $buffer );
297              
298 4         7895 return Mac::PropertyList::ustring->new( $buffer );
299             },
300              
301             8 => sub { # UIDs
302 8         20 my( $self, $length ) = @_;
303              
304 8         14 my $byte_length = $length + 1;
305              
306 8         95 read $self->_fh, ( my $buffer ), $byte_length;
307              
308 8         21 my $value = unpack 'H*', $buffer;
309              
310 8         33 return Mac::PropertyList::uid->new( $value );
311             },
312              
313             a => sub { # array
314 40         80 my( $self, $elements ) = @_;
315              
316 40         51 my @objects = do {
317 40         62 my $buffer;
318 40         77 read $self->_fh, $buffer, $elements * $self->_object_ref_size;
319 40 50       76 unpack(
320             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
321             );
322             };
323              
324             my @array =
325 40         97 map { $self->_read_object_at_offset( $objects[$_] ) }
  69         151  
326             0 .. $elements - 1;
327              
328 40         139 return Mac::PropertyList::array->new( \@array );
329             },
330              
331             d => sub { # dictionary
332 23         42 my( $self, $length ) = @_;
333              
334 23         37 my @key_indices = do {
335 23         30 my $buffer;
336 23         55 my $s = $self->_object_ref_size;
337 23         49 read $self->_fh, $buffer, $length * $self->_object_ref_size;
338 23 50       48 unpack(
339             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
340             );
341             };
342              
343 23         37 my @objects = do {
344 23         34 my $buffer;
345 23         38 read $self->_fh, $buffer, $length * $self->_object_ref_size;
346 23 50       46 unpack(
347             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
348             );
349             };
350              
351             my %dict = map {
352 23         88 my $key = $self->_read_object_at_offset($key_indices[$_])->value;
  205         438  
353 205         498 my $value = $self->_read_object_at_offset($objects[$_]);
354 205         658 ( $key, $value );
355             } 0 .. $length - 1;
356              
357 23         116 return Mac::PropertyList::dict->new( \%dict );
358             },
359 5         450 };
360              
361             sub _read_object
362             {
363 554     554   821 my $self = shift;
364              
365 554         703 my $buffer;
366 554 50       1025 croak "read() failed while trying to get type byte! $!"
367             unless read( $self->_fh, $buffer, 1) == 1;
368              
369 554         1503 my $length = unpack( "C*", $buffer ) & 0x0F;
370              
371 554         1200 $buffer = unpack "H*", $buffer;
372 554         1059 my $type = substr $buffer, 0, 1;
373              
374 554 100 100     1940 $length = $self->_read_object->value if $type ne "0" && $length == 15;
375              
376 554         971 my $sub = $type_readers->{ $type };
377 554         788 my $result = eval { $sub->( $self, $length ) };
  554         964  
378 554 50       1433 croak "$@" if $@;
379              
380 554         1536 return $result;
381             }
382              
383             }
384              
385             =back
386              
387             =head1 SEE ALSO
388              
389             Some of the ideas are cribbed from CFBinaryPList.c
390              
391             http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
392              
393             =head1 SOURCE AVAILABILITY
394              
395             This project is in Github:
396              
397             git://github.com/briandfoy/mac-propertylist.git
398              
399             =head1 CREDITS
400              
401             =head1 AUTHOR
402              
403             brian d foy, C<< >>
404              
405             Tom Wyant added support for UID types.
406              
407             =head1 COPYRIGHT AND LICENSE
408              
409             Copyright © 2004-2021, brian d foy . All rights reserved.
410              
411             This program is free software; you can redistribute it and/or modify
412             it under the terms of the Artistic License 2.0.
413              
414             =cut
415              
416             "See why 1984 won't be like 1984";