File Coverage

blib/lib/Data/AMF/Parser/AMF3.pm
Criterion Covered Total %
statement 145 216 67.1
branch 21 54 38.8
condition 5 13 38.4
subroutine 22 24 91.6
pod 1 18 5.5
total 194 325 59.6


line stmt bran cond sub pod time code
1             package Data::AMF::Parser::AMF3;
2 7     7   38 use strict;
  7         15  
  7         344  
3 7     7   40 use warnings;
  7         13  
  7         183  
4              
5 7     7   36 use Data::AMF::IO;
  7         14  
  7         227  
6 7     7   6735 use UNIVERSAL::require;
  7         15243  
  7         73  
7              
8             # ----------------------------------------------------------------------
9             # Class Constants
10             # ----------------------------------------------------------------------
11              
12 7         470 use constant AMF3_TYPES =>
13             [
14             'undefined',
15             'null',
16             'false',
17             'true',
18             'integer',
19             'number',
20             'string',
21             'xml_document',
22             'date',
23             'array',
24             'object',
25             'xml',
26             'byte_array',
27 7     7   340 ];
  7         13  
28              
29 7     7   34 use constant AMF3_INTEGER_MAX => "268435455";
  7         16  
  7         15694  
30              
31             # ----------------------------------------------------------------------
32             # Class Methods
33             # ----------------------------------------------------------------------
34              
35             sub parse
36             {
37 23     23 1 46 my ($class, $data) = @_;
38            
39 23         72 my $self = $class->new;
40 23         243 $self->{'io'} = Data::AMF::IO->new(data => $data);
41            
42 23         166 return $self->read;
43             }
44              
45             # ----------------------------------------------------------------------
46             # Constructor
47             # ----------------------------------------------------------------------
48              
49             sub new
50             {
51 23     23 0 33 my $class = shift;
52 23         181 my $self = bless {
53             io => undef,
54             class_member_defs => {},
55             stored_strings => [],
56             stored_objects => [],
57             stored_defs => [],
58             @_
59             }, $class;
60 23         53 return $self;
61             }
62              
63             # ----------------------------------------------------------------------
64             # Properties
65             # ----------------------------------------------------------------------
66              
67 174     174 0 627 sub io { return $_[0]->{'io'} }
68              
69             # ----------------------------------------------------------------------
70             # Methods
71             # ----------------------------------------------------------------------
72              
73             sub read
74             {
75 23     23 0 34 my $self = shift;
76            
77 23         32 my @res;
78            
79 23         51 while (defined(my $marker = $self->io->read_u8))
80             {
81 23 50       81 my $method = 'read_' . AMF3_TYPES->[$marker] or die;
82 23         88 push @res, $self->$method();
83             }
84            
85 23         242 @res;
86             }
87              
88             sub read_one
89             {
90 25     25 0 32 my $self = shift;
91              
92 25         65 my $marker = $self->io->read_u8;
93 25 50       59 return unless defined $marker;
94            
95 25 50       74 my $method = 'read_' . AMF3_TYPES->[$marker] or die;
96 25         74 return $self->$method();
97             }
98              
99             sub read_undefined
100             {
101 2     2 0 9 return undef;
102             }
103              
104             sub read_null
105             {
106 2     2 0 26 Data::AMF::Type::Null->require;
107 2         73 return Data::AMF::Type::Null->new;
108             }
109              
110             sub read_false
111             {
112 2     2 0 15 Data::AMF::Type::Boolean->require;
113 2         65 return Data::AMF::Type::Boolean->new(0);
114             }
115              
116             sub read_true
117             {
118 2     2 0 27 Data::AMF::Type::Boolean->require;
119 2         139 return Data::AMF::Type::Boolean->new(1);
120             }
121              
122             sub read_integer
123             {
124 77     77 0 94 my $self = shift;
125            
126 77         78 my $n = 0;
127 77   50     149 my $b = $self->io->read_u8 || 0;
128 77         104 my $result = 0;
129            
130 77   33     217 while (($b & 0x80) != 0 && $n < 3)
131             {
132 0         0 $result = $result << 7;
133 0         0 $result = $result | ($b & 0x7f);
134 0   0     0 $b = $self->io->read_u8 || 0;
135 0         0 $n++;
136             }
137            
138 77 50       133 if ($n < 3)
139             {
140 77         83 $result = $result << 7;
141 77         105 $result = $result | $b;
142             }
143             else
144             {
145             # Use all 8 bits from the 4th byte
146 0         0 $result = $result << 8;
147 0         0 $result = $result | $b;
148            
149             # Check if the integer should be negative
150 0 0       0 if ($result > AMF3_INTEGER_MAX)
151             {
152             # and extend the sign bit
153 0         0 $result -= (1 << 29);
154             }
155             }
156            
157 77         143 return $result;
158             }
159              
160             sub read_number
161             {
162 2     2 0 6 my $self = shift;
163 2         6 return $self->io->read_double;
164             }
165              
166             sub read_string
167             {
168 55     55 0 113 my $self = shift;
169            
170 55         102 my $type = $self->read_integer();
171 55         91 my $isReference = ($type & 0x01) == 0;
172              
173 55 100       103 if ($isReference)
174             {
175 9         14 my $reference = $type >> 1;
176 9 50       12 if ($reference < @{ $self->{'stored_strings'} })
  9         30  
177             {
178 9 50       36 if (not defined $self->{'stored_strings'}->[$reference])
179             {
180 0         0 die "Reference to non existant object at index #{$reference}.";
181             }
182            
183 9         57 return $self->{'stored_strings'}->[$reference];
184             }
185             else
186             {
187 0         0 die "Reference to non existant object at index #{$reference}.";
188             }
189             }
190             else
191             {
192 46         56 my $length = $type >> 1;
193 46         132 my $str = '';
194            
195 46 100       92 if ($length > 0)
196             {
197 20         40 $str = $self->io->read($length);
198 20         30 push @{ $self->{'stored_strings'} }, $str;
  20         48  
199             }
200            
201 46         220 return $str;
202             }
203             }
204              
205             sub read_xml_document
206             {
207 0     0 0 0 my $self = shift;
208 0         0 my $type = $self->read_integer();
209 0         0 my $length = $type >> 1;
210 0         0 my $obj = $self->io->read($length);
211 0         0 push @{ $self->{'stored_objects'} }, $obj;
  0         0  
212 0         0 return $obj;
213             }
214              
215             sub read_date
216             {
217 2     2 0 6 my $self = shift;
218            
219 2         12 my $type = $self->read_integer();
220 2         8 my $isReference = ($type & 0x01) == 0;
221            
222 2 50       9 if ($isReference)
223             {
224 0         0 my $reference = $type >> 1;
225 0 0       0 if ($reference < @{ $self->{'stored_objects'} })
  0         0  
226             {
227 0 0       0 if (not defined $self->{'stored_objects'}->[$reference])
228             {
229 0         0 die "Reference to non existant object at index #{$reference}.";
230             }
231            
232 0         0 return $self->{'stored_objects'}->[$reference];
233             }
234             else
235             {
236 0         0 die "Reference to non existant object at index #{$reference}.";
237             }
238             }
239             else
240             {
241 2         8 my $epoch = $self->io->read_double / 1000;
242            
243 2         18 DateTime->require;
244 2         3220 my $datetime = DateTime->from_epoch( epoch => $epoch );
245            
246 2         820 push @{ $self->{'stored_objects'} }, $datetime;
  2         9  
247 2         13 return $datetime;
248             }
249             }
250              
251             sub read_array
252             {
253 3     3 0 8 my $self = shift;
254            
255 3         8 my $type = $self->read_integer();
256 3         7 my $isReference = ($type & 0x01) == 0;
257            
258 3 50       9 if ($isReference)
259             {
260 0         0 my $reference = $type >> 1;
261 0 0       0 if ($reference < @{ $self->{'stored_objects'} })
  0         0  
262             {
263 0 0       0 if (not defined $self->{'stored_objects'}->[$reference])
264             {
265 0         0 die "Reference to non existant object at index #{$reference}.";
266             }
267              
268 0         0 return $self->{'stored_objects'}->[$reference];
269             }
270             else
271             {
272 0         0 die "Reference to non existant object at index #{$reference}.";
273             }
274             }
275             else
276             {
277 3         8 my $length = $type >> 1;
278 3         8 my $key = $self->read_string();
279 3         8 my $array;
280            
281 3 50       10 if ($key ne '')
282             {
283 0         0 $array = {};
284 0         0 push @{ $self->{'stored_objects'} }, $array;
  0         0  
285            
286 0         0 while($key ne '')
287             {
288 0         0 my $value = $self->read_one();
289 0         0 $array->{$key} = $value;
290 0         0 $key = $self->read_string();
291             }
292            
293 0         0 for (0 .. $length - 1)
294             {
295 0         0 $array->{$_} = $self->read_one();
296             }
297             }
298             else
299             {
300 3         6 $array = [];
301 3         7 push @{ $self->{'stored_objects'} }, $array;
  3         7  
302            
303 3         12 for (0 .. $length - 1)
304             {
305 10         14 push @{ $array }, $self->read_one();
  10         27  
306             }
307             }
308            
309 3         24 return $array;
310             }
311             }
312              
313             sub read_object
314             {
315 11     11 0 14 my $self = shift;
316            
317 11         26 my $type = $self->read_integer();
318 11         22 my $isReference = ($type & 0x01) == 0;
319            
320 11 50       29 if ($isReference)
321             {
322 0         0 my $reference = $type >> 1;
323            
324 0 0       0 if ($reference < @{ $self->{'stored_objects'} })
  0         0  
325             {
326 0 0       0 if (not defined $self->{'stored_objects'}->[$reference])
327             {
328 0         0 die "Reference to non existant object at index #{$reference}.";
329             }
330            
331 0         0 return $self->{'stored_objects'}->[$reference];
332             }
333             else
334             {
335 0         0 warn "Reference to non existant object at index #{$reference}.";
336             }
337             }
338             else
339             {
340 11         16 my $class_type = $type >> 1;
341 11         16 my $class_is_reference = ($class_type & 0x01) == 0;
342 11         12 my $class_definition;
343            
344 11 100       32 if ($class_is_reference)
345             {
346 1         2 my $class_reference = $class_type >> 1;
347            
348 1 50       2 if ($class_reference < @{ $self->{'stored_defs'} })
  1         5  
349             {
350 1         2 $class_definition = $self->{'stored_defs'}->[$class_reference];
351             }
352             else
353             {
354 0         0 die "Reference to non existant object at index #{$class_reference}.";
355             }
356             }
357             else
358             {
359 10         24 my $as_class_name = $self->read_string();
360 10         19 my $externalizable = ($class_type & 0x02) != 0;
361 10         15 my $dynamic = ($class_type & 0x04) != 0;
362 10         13 my $attr_count = $class_type >> 3;
363            
364 10         15 my $members = [];
365 10         25 for (1 .. $attr_count)
366             {
367 0         0 push @{ $members }, $self->read_string();
  0         0  
368             }
369            
370             $class_definition =
371             {
372 10         61 "as_class_name" => $as_class_name,
373             "members" => $members,
374             "externalizable" => $externalizable,
375             "dynamic" => $dynamic
376             };
377            
378 10         13 push @{ $self->{'stored_defs'} }, $class_definition;
  10         29  
379             }
380            
381 11         18 my $action_class_name = $class_definition->{'as_class_name'};
382 11         15 my ($skip_mapping, $obj);
383            
384 11 50 33     30 if ($action_class_name && $action_class_name =~ /flex\.messaging/)
385             {
386 0         0 $obj = {};
387 0         0 $obj->{'_explicitType'} = $action_class_name;
388 0         0 $skip_mapping = 1;
389             }
390             else
391             {
392 11         17 $obj = {};
393 11         13 $skip_mapping = 0;
394             }
395            
396 11         13 my $obj_position = @{ $self->{'stored_objects'} };
  11         19  
397 11         15 push @{ $self->{'stored_objects'} }, $obj;
  11         20  
398            
399 11 50       51 if ($class_definition->{'externalizable'})
400             {
401 0         0 $obj = $self->read_one();
402             }
403             else
404             {
405 11         13 for my $key (@{ $class_definition->{'members'} })
  11         26  
406             {
407 0         0 $obj->{$key} = $self->read_one();
408             }
409             }
410            
411 11 50       25 if ($class_definition->{'dynamic'})
412             {
413 11         16 my $key;
414 11   66     23 while (($key = $self->read_string()) && $key ne '') {
415 15         43 $obj->{$key} = $self->read_one();
416             }
417             }
418            
419 11         51 return $obj;
420             }
421             }
422              
423             sub read_xml
424             {
425 0     0 0 0 my $self = shift;
426 0         0 my $type = $self->read_integer();
427 0         0 my $length = $type >> 1;
428 0         0 my $obj = $self->io->read($length);
429            
430 0         0 XML::LibXML->require;
431 0         0 my $xml = XML::LibXML->new()->parse_string($obj);
432            
433 0         0 push @{ $self->{'stored_objects'} }, $xml;
  0         0  
434 0         0 return $xml;
435             }
436              
437             sub read_byte_array
438             {
439 2     2 0 6 my $self = shift;
440            
441 2         11 my $type = $self->read_integer();
442 2         9 my $isReference = ($type & 0x01) == 0;
443            
444 2 50       10 if ($isReference)
445             {
446 0         0 my $reference = $type >> 1;
447 0 0       0 if ($reference < @{ $self->{'stored_objects'} })
  0         0  
448             {
449 0 0       0 if (not defined $self->{'stored_objects'}->[$reference])
450             {
451 0         0 die "Reference to non existant object at index #{$reference}.";
452             }
453            
454 0         0 return $self->{'stored_objects'}->[$reference];
455             }
456             else
457             {
458 0         0 die "Reference to non existant object at index #{$reference}.";
459             }
460             }
461             else
462             {
463 2         6 my $length = $type >> 1;
464 2         11 my @obj = unpack('C' . $length, $self->io->read($length));
465            
466 2         24 Data::AMF::Type::ByteArray->require;
467 2         51 my $obj = Data::AMF::Type::ByteArray->new(\@obj);
468            
469 2         5 push @{ $self->{'stored_objects'} }, $obj;
  2         7  
470 2         22 return $obj;
471             }
472             }
473              
474             1;
475              
476             __END__