File Coverage

blib/lib/Mac/PropertyList/ReadBinary.pm
Criterion Covered Total %
statement 161 171 94.1
branch 27 44 61.3
condition 3 3 100.0
subroutine 24 24 100.0
pod 2 2 100.0
total 217 244 88.9


line stmt bran cond sub pod time code
1 6     6   593980 use v5.10;
  6         24  
2              
3             package Mac::PropertyList::ReadBinary;
4 6     6   50 use strict;
  6         10  
  6         170  
5 6     6   32 use warnings;
  6         11  
  6         404  
6              
7 6     6   35 use Carp;
  6         31  
  6         556  
8 6     6   1263 use Data::Dumper;
  6         15744  
  6         495  
9 6     6   55 use Encode qw(decode);
  6         14  
  6         598  
10 6     6   1280 use Mac::PropertyList;
  6         39  
  6         359  
11 6     6   7408 use Math::BigInt;
  6         240242  
  6         46  
12 6     6   157369 use POSIX qw(SEEK_END SEEK_SET);
  6         49928  
  6         42  
13              
14             our $VERSION = '1.603_02';
15              
16             my $Debug = $ENV{PLIST_DEBUG} || 0;
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 10677 my( $class, $source ) = @_;
68              
69 9         37 my $self = bless { source => $source }, $class;
70              
71 9         44 $self->_read;
72              
73 9         31 $self;
74             }
75              
76 36     36   470 sub _source { $_[0]->{source} }
77 1739     1739   5745 sub _fh { $_[0]->{fh} }
78 257     257   978 sub _trailer { $_[0]->{trailer} }
79 516     516   2727 sub _offsets { $_[0]->{offsets} }
80 203     203   494 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 1352 sub plist { $_[0]->{parsed} }
89              
90             sub _object_size {
91             $_[0]->_trailer->{object_count} * $_[0]->_trailer->{offset_size}
92 9     9   22 }
93              
94             sub _read {
95 9     9   25 my( $self, $thingy ) = @_;
96              
97 9         31 $self->{fh} = $self->_get_filehandle;
98 9         38 $self->_read_plist_trailer;
99              
100 9         48 $self->_get_offset_table;
101              
102 9         22 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   3078 my( $self, $thingy ) = @_;
109              
110 13         29 my $fh;
111              
112 13 100       59 if( ! ref $self->_source ) { # filename
    100          
    50          
113 5 100       30 open $fh, "<", $self->_source
114 1         5 or die "Could not open [@{[$self->_source]}]! $!";
115             }
116             elsif( ref $self->_source eq ref \ '' ) { # scalar ref
117 7 50       38 open $fh, "<", $self->_source or croak "Could not open file! $!";
118             }
119             elsif( ref $self->_source ) { # filehandle
120 1         9 $fh = $self->_source;
121             }
122             else {
123 0         0 croak( 'No source to read from!' );
124             }
125              
126 12         50 $fh;
127             }
128              
129             sub _read_plist_trailer {
130 9     9   20 my $self = shift;
131              
132 9         37 seek $self->_fh, -32, SEEK_END;
133              
134 9         18 my $buffer;
135 9         23 read $self->_fh, $buffer, 32;
136 9         25 my %hash;
137              
138 9         89 @hash{ qw( offset_size ref_size object_count top_object table_offset ) }
139             = unpack "x6 C C (x4 N)3", $buffer;
140              
141 9         30 $self->{trailer} = \%hash;
142             }
143              
144             sub _get_offset_table {
145 9     9   20 my $self = shift;
146              
147 9         23 seek $self->_fh, $self->_trailer->{table_offset}, SEEK_SET;
148              
149 9         32 my $try_to_read = $self->_object_size;
150              
151 9         17 my $raw_offset_table;
152 9         19 my $read = read $self->_fh, $raw_offset_table, $try_to_read;
153              
154 9 50       55 croak "reading offset table failed!" unless $read == $try_to_read;
155              
156 9         53 my @offsets = unpack ["","C*","n*","(H6)*","N*"]->[$self->_trailer->{offset_size}], $raw_offset_table;
157              
158 9         34 $self->{offsets} = \@offsets;
159              
160 9 50       52 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 516     516   1165 my( $self, $offset ) = @_;
167              
168 516         1109 seek $self->_fh, ${ $self->_offsets }[$offset], SEEK_SET;
  516         1133  
169              
170 516         1283 $self->_read_object;
171             }
172              
173             # # # # # # # # # # # # # #
174              
175 0         0 BEGIN {
176              
177 6     6   26104 my %singletons = (
178             0 => undef,
179             8 => Mac::PropertyList::false->new(),
180             9 => Mac::PropertyList::true->new(),
181              
182             # 15 is also defined (as "fill") in the comments to Apple's
183             # implementation in CFBinaryPList.c but Apple's actual code has no
184             # support for it at all, either reading or writing, so it's
185             # probably not important to implement.
186             );
187              
188             my $type_readers = {
189             0 => sub { # the odd balls
190 2         7 my( $self, $length ) = @_;
191              
192 2 50       11 return $singletons{ $length } if exists $singletons{ $length };
193              
194 0         0 croak ( sprintf "Unknown type byte %02X\n", $length );
195             },
196              
197             1 => sub { # integers
198 207         474 my( $self, $power_of_2 ) = @_;
199              
200 207 50       482 croak "Integer with <$power_of_2> bytes is not supported" if $power_of_2 > 4;
201              
202 207         364 my $byte_length = 1 << $power_of_2;
203              
204 207         363 my( $buffer, $value );
205 207         409 read $self->_fh, $buffer, $byte_length;
206              
207 207         625 my @formats = qw( C n N NN NNNN );
208 207         525 my @values = unpack $formats[$power_of_2], $buffer;
209              
210 207 100       621 if( $power_of_2 == 3 ) { # 64 bits
    50          
211 28         61 my( $high, $low ) = @values;
212              
213 28         169 my $b = Math::BigInt->new($high)->blsft(32)->bior($low);
214 28 100       45496 if( $b->bcmp(Math::BigInt->new(2)->bpow(63)) >= 0) {
215 16         14233 $b -= Math::BigInt->new(2)->bpow(64);
216             }
217              
218 28         22903 @values = ( $b );
219             }
220             elsif( $power_of_2 == 4 ) { # 128 bits
221             # 128 bits aren't part of the public API, but apparently
222             # they are out there.
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 207         825 return Mac::PropertyList::integer->new( $values[0] );
238             },
239              
240             2 => sub { # reals
241 3         8 my( $self, $length ) = @_;
242 3 50       10 croak "Real > 8 bytes" if $length > 3;
243 3 50       9 croak "Bad length [$length]" if $length < 2;
244              
245 3         53 my $byte_length = 1 << $length;
246              
247 3         6 my( $buffer, $value );
248 3         9 read $self->_fh, $buffer, $byte_length;
249              
250 3         12 my @formats = qw( a a f> d> );
251 3         9 my @values = unpack $formats[$length], $buffer;
252              
253 3         22 return Mac::PropertyList::real->new( $values[0] );
254             },
255              
256             3 => sub { # date
257 6         15 my( $self, $length ) = @_;
258 6 50       18 croak "Date != 8 bytes" if $length != 3;
259 6         14 my $byte_length = 1 << $length;
260              
261 6         12 my( $buffer, $value );
262 6         16 read $self->_fh, $buffer, $byte_length;
263              
264 6         24 my @values = unpack 'd>', $buffer;
265              
266 6         27 $self->{MLen} += 9;
267              
268 6         304 my $adjusted_time = POSIX::strftime(
269             "%Y-%m-%dT%H:%M:%SZ",
270             gmtime( 978307200 + $values[0])
271             );
272              
273 6         56 return Mac::PropertyList::date->new( $adjusted_time );
274             },
275              
276             4 => sub { # binary data
277 1         3 my( $self, $length ) = @_;
278              
279 1         3 my( $buffer, $value );
280 1         4 read $self->_fh, $buffer, $length;
281              
282 1         11 return Mac::PropertyList::data->new( $buffer );
283             },
284              
285             5 => sub { # utf8 string
286 293         610 my( $self, $length ) = @_;
287              
288 293         568 my( $buffer, $value );
289 293         630 read $self->_fh, $buffer, $length;
290              
291 293         1925 $buffer = Encode::decode( 'ascii', $buffer );
292              
293 293         6452 return Mac::PropertyList::string->new( $buffer );
294             },
295              
296             6 => sub { # unicode string
297 2         7 my( $self, $length ) = @_;
298              
299 2         5 my( $buffer, $value );
300 2         6 read $self->_fh, $buffer, 2 * $length;
301              
302 2         15 $buffer = Encode::decode( "UTF-16BE", $buffer );
303              
304 2         4406 return Mac::PropertyList::ustring->new( $buffer );
305             },
306              
307             8 => sub { # UIDs
308 4         12 my( $self, $length ) = @_;
309              
310 4         8 my $byte_length = $length + 1;
311              
312 4         10 read $self->_fh, ( my $buffer ), $byte_length;
313              
314 4         11 my $value = unpack 'H*', $buffer;
315              
316 4         19 return Mac::PropertyList::uid->new( $value );
317             },
318              
319             a => sub { # array
320 39         100 my( $self, $elements ) = @_;
321              
322 39         65 my @objects = do {
323 39         62 my $buffer;
324 39         101 read $self->_fh, $buffer, $elements * $self->_object_ref_size;
325 39 50       85 unpack(
326             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
327             );
328             };
329              
330             my @array =
331 39         118 map { $self->_read_object_at_offset( $objects[$_] ) }
  81         245  
332             0 .. $elements - 1;
333              
334 39         171 return Mac::PropertyList::array->new( \@array );
335             },
336              
337             d => sub { # dictionary
338 25         72 my( $self, $length ) = @_;
339              
340 25         41 my @key_indices = do {
341 25         41 my $buffer;
342 25         63 my $s = $self->_object_ref_size;
343 25         61 read $self->_fh, $buffer, $length * $self->_object_ref_size;
344 25 50       63 unpack(
345             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
346             );
347             };
348              
349 25         44 my @objects = do {
350 25         52 my $buffer;
351 25         58 read $self->_fh, $buffer, $length * $self->_object_ref_size;
352 25 50       55 unpack(
353             ($self->_object_ref_size == 1 ? "C*" : "n*"), $buffer
354             );
355             };
356              
357             my %dict = map {
358 25         93 my $key = $self->_read_object_at_offset($key_indices[$_])->value;
  213         569  
359 213         682 my $value = $self->_read_object_at_offset($objects[$_]);
360 213         765 ( $key, $value );
361             } 0 .. $length - 1;
362              
363 25         158 return Mac::PropertyList::dict->new( \%dict );
364             },
365 6         656 };
366              
367             sub _read_object {
368 582     582   1047 my $self = shift;
369 582         941 my $buffer;
370              
371 582 50       1165 croak "read() failed while trying to get type byte! $!"
372             unless read( $self->_fh, $buffer, 1) == 1;
373              
374 582         1597 my $length = unpack( "C*", $buffer ) & 0x0F;
375 582         1379 $buffer = unpack "H*", $buffer;
376 582         1199 my $type = substr $buffer, 0, 1;
377              
378 582 100 100     2341 $length = $self->_read_object->value if $type ne "0" && $length == 15;
379              
380 582         1207 my $sub = $type_readers->{ $type };
381 582         1120 my $result = eval { $sub->( $self, $length ) };
  582         1221  
382 582 50       1416 croak "$@" if $@;
383              
384 582         1785 return $result;
385             }
386              
387             }
388              
389             =back
390              
391             =head1 SEE ALSO
392              
393             Some of the ideas are cribbed from CFBinaryPList.c
394              
395             http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
396              
397             =head1 SOURCE AVAILABILITY
398              
399             This project is in Github:
400              
401             https://github.com/briandfoy/mac-propertylist.git
402              
403             =head1 CREDITS
404              
405             =head1 AUTHOR
406              
407             brian d foy, C<< >>
408              
409             Tom Wyant added support for UID types.
410              
411             =head1 COPYRIGHT AND LICENSE
412              
413             Copyright © 2004-2026, brian d foy . All rights reserved.
414              
415             This program is free software; you can redistribute it and/or modify
416             it under the terms of the Artistic License 2.0.
417              
418             =cut
419              
420             "See why 1984 won't be like 1984";