File Coverage

blib/lib/Mac/PropertyList/ReadBinary.pm
Criterion Covered Total %
statement 173 188 92.0
branch 24 44 54.5
condition 3 3 100.0
subroutine 27 27 100.0
pod 2 2 100.0
total 229 264 86.7


line stmt bran cond sub pod time code
1 5     5   2849 use v5.10;
  5         15  
2              
3             package Mac::PropertyList::ReadBinary;
4 5     5   25 use strict;
  5         10  
  5         115  
5 5     5   22 use warnings;
  5         9  
  5         139  
6              
7 5     5   23 use Carp;
  5         7  
  5         323  
8 5     5   1017 use Data::Dumper;
  5         10909  
  5         279  
9 5     5   1921 use Encode qw(decode);
  5         33164  
  5         335  
10 5     5   1040 use Mac::PropertyList;
  5         13  
  5         187  
11 5     5   4916 use Math::BigInt;
  5         107604  
  5         65  
12 5     5   100314 use MIME::Base64 qw(decode_base64);
  5         3038  
  5         335  
13 5     5   2052 use POSIX qw(SEEK_END SEEK_SET);
  5         28829  
  5         31  
14 5     5   6452 use XML::Entities ();
  5         11  
  5         9660  
15              
16             our $VERSION = '1.503';
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 L 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 1252 my( $class, $source ) = @_;
68              
69 9         41 my $self = bless { source => $source }, $class;
70              
71 9         52 $self->_read;
72              
73 9         40 $self;
74             }
75              
76 38     38   403 sub _source { $_[0]->{source} }
77 2205     2205   11498 sub _fh { $_[0]->{fh} }
78 249     249   783 sub _trailer { $_[0]->{trailer} }
79 488     488   2147 sub _offsets { $_[0]->{offsets} }
80 195     195   284 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             $_[0]->_trailer->{object_count} * $_[0]->_trailer->{offset_size}
92 9     9   26 }
93              
94             sub _read {
95 9     9   26 my( $self, $thingy ) = @_;
96              
97 9         44 $self->{fh} = $self->_get_filehandle;
98 9         35 $self->_read_plist_trailer;
99              
100 9         32 $self->_get_offset_table;
101              
102 9         27 my $top = $self->_read_object_at_offset( $self->_trailer->{top_object} );
103              
104 9         38 $self->{parsed} = $top;
105             }
106              
107             sub _get_filehandle {
108 13     13   2967 my( $self, $thingy ) = @_;
109              
110 13         23 my $fh;
111              
112 13 100       45 if( ! ref $self->_source ) { # filename
    100          
    50          
113 3 100       13 open $fh, "<", $self->_source
114 1         16 or die "Could not open [@{[$self->_source]}]! $!";
115             }
116             elsif( ref $self->_source eq ref \ '' ) { # scalar ref
117 3 50   3   24 open $fh, "<", $self->_source or croak "Could not open file! $!";
  3         7  
  3         22  
  9         53  
118             }
119             elsif( ref $self->_source ) { # filehandle
120 1         2 $fh = $self->_source;
121             }
122             else {
123 0         0 croak( 'No source to read from!' );
124             }
125              
126 12         2475 $fh;
127             }
128              
129             sub _read_plist_trailer {
130 9     9   22 my $self = shift;
131              
132 9         37 seek $self->_fh, -32, SEEK_END;
133              
134 9         27 my $buffer;
135 9         27 read $self->_fh, $buffer, 32;
136 9         21 my %hash;
137              
138 9         130 @hash{ qw( offset_size ref_size object_count top_object table_offset ) }
139             = unpack "x6 C C (x4 N)3", $buffer;
140              
141 9         28 $self->{trailer} = \%hash;
142             }
143              
144             sub _get_offset_table {
145 9     9   40 my $self = shift;
146              
147 9         25 seek $self->_fh, $self->_trailer->{table_offset}, SEEK_SET;
148              
149 9         32 my $try_to_read = $self->_object_size;
150              
151 9         39 my $raw_offset_table;
152 9         21 my $read = read $self->_fh, $raw_offset_table, $try_to_read;
153              
154 9 50       30 croak "reading offset table failed!" unless $read == $try_to_read;
155              
156 9         50 my @offsets = unpack ["","C*","n*","(H6)*","N*"]->[$self->_trailer->{offset_size}], $raw_offset_table;
157              
158 9         31 $self->{offsets} = \@offsets;
159              
160 9 50       24 if( $self->_trailer->{offset_size} == 3 ) {
161 0         0 @offsets = map { hex } @offsets;
  0         0  
162             }
163             }
164              
165             sub _read_object_at_offset {
166 488     488   776 my( $self, $offset ) = @_;
167              
168 488         2666 my @caller = caller(1);
169              
170 488         1016 seek $self->_fh, ${ $self->_offsets }[$offset], SEEK_SET;
  488         814  
171              
172 488         990 $self->_read_object;
173             }
174              
175             # # # # # # # # # # # # # #
176              
177 0         0 BEGIN {
178              
179 5     5   113 my %singletons = (
180             0 => undef,
181             8 => Mac::PropertyList::false->new(),
182             9 => Mac::PropertyList::true->new(),
183              
184             # 15 is also defined (as "fill") in the comments to Apple's
185             # implementation in CFBinaryPList.c but Apple's actual code has no
186             # support for it at all, either reading or writing, so it's
187             # probably not important to implement.
188             );
189              
190             my $type_readers = {
191             0 => sub { # the odd balls
192 4         14 my( $self, $length ) = @_;
193              
194 4 50       20 return $singletons{ $length } if exists $singletons{ $length };
195              
196 0         0 croak ( sprintf "Unknown type byte %02X\n", $length );
197             },
198              
199             1 => sub { # integers
200 174         313 my( $self, $power2 ) = @_;
201              
202 174 50       306 croak "Integer with <$power2> bytes is not supported" if $power2 > 3;
203              
204 174         244 my $byte_length = 1 << $power2;
205              
206 174         230 my( $buffer, $value );
207 174         334 read $self->_fh, $buffer, $byte_length;
208              
209 174         427 my @formats = qw( C n N NN NNNN );
210 174         410 my @values = unpack $formats[$power2], $buffer;
211              
212 174 50       425 if( $power2 == 3 ) {
    50          
213 0         0 my( $high, $low ) = @values;
214              
215 0         0 my $b = Math::BigInt->new($high)->blsft(32)->bior($low);
216 0 0       0 if( $b->bcmp(Math::BigInt->new(2)->bpow(63)) > 0) {
217 0         0 $b -= Math::BigInt->new(2)->bpow(64);
218             }
219              
220 0         0 @values = ( $b );
221             }
222             elsif( $power2 == 4 ) {
223 0         0 my( $highest, $higher, $high, $low ) = @values;
224 0         0 my $b = Math::BigInt
225             ->new($highest)
226             ->blsft(32)->bior($higher)
227             ->blsft(32)->bior($high)
228             ->blsft(32)->bior($low);
229              
230 0 0       0 if( $b->bcmp(Math::BigInt->new(2)->bpow(127)) > 0) {
231 0         0 $b -= Math::BigInt->new(2)->bpow(128);
232             }
233              
234 0         0 @values = ( $b );
235             }
236              
237 174         545 return Mac::PropertyList::integer->new( $values[0] );
238             },
239              
240             2 => sub { # reals
241 6         19 my( $self, $length ) = @_;
242 6 50       16 croak "Real > 8 bytes" if $length > 3;
243 6 50       18 croak "Bad length [$length]" if $length < 2;
244              
245 6         14 my $byte_length = 1 << $length;
246              
247 6         12 my( $buffer, $value );
248 6         15 read $self->_fh, $buffer, $byte_length;
249              
250 6         19 my @formats = qw( a a f> d> );
251 6         25 my @values = unpack $formats[$length], $buffer;
252              
253 6         90 return Mac::PropertyList::real->new( $values[0] );
254             },
255              
256             3 => sub { # date
257 6         21 my( $self, $length ) = @_;
258 6 50       15 croak "Date != 8 bytes" if $length != 3;
259 6         16 my $byte_length = 1 << $length;
260              
261 6         10 my( $buffer, $value );
262 6         15 read $self->_fh, $buffer, $byte_length;
263              
264 6         41 my @values = unpack 'd>', $buffer;
265              
266 6         15 $self->{MLen} += 9;
267              
268 6         419 my $adjusted_time = POSIX::strftime(
269             "%Y-%m-%dT%H:%M:%SZ",
270             gmtime( 978307200 + $values[0])
271             );
272              
273 6         58 return Mac::PropertyList::date->new( $adjusted_time );
274             },
275              
276             4 => sub { # binary data
277 2         7 my( $self, $length ) = @_;
278              
279 2         6 my( $buffer, $value );
280 2         9 read $self->_fh, $buffer, $length;
281              
282 2         27 return Mac::PropertyList::data->new( $buffer );
283             },
284              
285             5 => sub { # utf8 string
286 287         511 my( $self, $length ) = @_;
287              
288 287         348 my( $buffer, $value );
289 287         544 read $self->_fh, $buffer, $length;
290              
291 287         718 $buffer = Encode::decode( 'ascii', $buffer );
292              
293 287         7817 return Mac::PropertyList::string->new( $buffer );
294             },
295              
296             6 => sub { # unicode string
297 4         16 my( $self, $length ) = @_;
298              
299 4         9 my( $buffer, $value );
300 4         15 read $self->_fh, $buffer, 2 * $length;
301              
302 4         21 $buffer = Encode::decode( "UTF-16BE", $buffer );
303              
304 4         7442 return Mac::PropertyList::ustring->new( $buffer );
305             },
306              
307             8 => sub { # UIDs
308 8         21 my( $self, $length ) = @_;
309              
310 8         14 my $byte_length = $length + 1;
311              
312 8         22 read $self->_fh, ( my $buffer ), $byte_length;
313              
314 8         24 my $value = unpack 'H*', $buffer;
315              
316 8         41 return Mac::PropertyList::uid->new( $value );
317             },
318              
319             a => sub { # array
320 40         87 my( $self, $elements ) = @_;
321              
322 40         56 my @objects = do {
323 40         54 my $buffer;
324 40         94 read $self->_fh, $buffer, $elements * $self->_object_ref_size;
325 40 50       79 unpack(
326             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
327             );
328             };
329              
330             my @array =
331 40         94 map { $self->_read_object_at_offset( $objects[$_] ) }
  69         183  
332             0 .. $elements - 1;
333              
334 40         191 return Mac::PropertyList::array->new( \@array );
335             },
336              
337             d => sub { # dictionary
338 23         53 my( $self, $length ) = @_;
339              
340 23         34 my @key_indices = do {
341 23         33 my $buffer;
342 23         73 my $s = $self->_object_ref_size;
343 23         53 read $self->_fh, $buffer, $length * $self->_object_ref_size;
344 23 50       51 unpack(
345             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
346             );
347             };
348              
349 23         40 my @objects = do {
350 23         34 my $buffer;
351 23         48 read $self->_fh, $buffer, $length * $self->_object_ref_size;
352 23 50       48 unpack(
353             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
354             );
355             };
356              
357             my %dict = map {
358 23         79 my $key = $self->_read_object_at_offset($key_indices[$_])->value;
  205         511  
359 205         499 my $value = $self->_read_object_at_offset($objects[$_]);
360 205         708 ( $key, $value );
361             } 0 .. $length - 1;
362              
363 23         163 return Mac::PropertyList::dict->new( \%dict );
364             },
365 5         382 };
366              
367             sub _read_object {
368 554     554   736 my $self = shift;
369 554         6878 say "Reading object!";
370 554         1128 my $buffer;
371 554         1040 say "\tTELL: ", tell( $self->_fh );
372 554 50       1519 croak "read() failed while trying to get type byte! $!"
373             unless read( $self->_fh, $buffer, 1) == 1;
374              
375 554         1527 my $length = unpack( "C*", $buffer ) & 0x0F;
376 554         6132 say "\tlength is $length";
377 554         1694 $buffer = unpack "H*", $buffer;
378 554         1466 say "\t", join '', map { sprintf '%02x', ord } split //, $buffer;
  1108         8484  
379 554         1756 my $type = substr $buffer, 0, 1;
380 554         5851 say "\ttype is $type";
381              
382 554 100 100     2588 $length = $self->_read_object->value if $type ne "0" && $length == 15;
383              
384 554         1024 my $sub = $type_readers->{ $type };
385 554         712 my $result = eval { $sub->( $self, $length ) };
  554         1037  
386 554 50       930 croak "$@" if $@;
387 554         1103 say "RESULT: ", Dumper($result);
388 554         32889 return $result;
389             }
390              
391             }
392              
393             =back
394              
395             =head1 SEE ALSO
396              
397             Some of the ideas are cribbed from CFBinaryPList.c
398              
399             http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
400              
401             =head1 SOURCE AVAILABILITY
402              
403             This project is in Github:
404              
405             https://github.com/briandfoy/mac-propertylist.git
406              
407             =head1 CREDITS
408              
409             =head1 AUTHOR
410              
411             brian d foy, C<< >>
412              
413             Tom Wyant added support for UID types.
414              
415             =head1 COPYRIGHT AND LICENSE
416              
417             Copyright © 2004-2021, brian d foy . All rights reserved.
418              
419             This program is free software; you can redistribute it and/or modify
420             it under the terms of the Artistic License 2.0.
421              
422             =cut
423              
424             "See why 1984 won't be like 1984";