File Coverage

blib/lib/AMF/Perl/IO/Deserializer.pm
Criterion Covered Total %
statement 6 105 5.7
branch 0 40 0.0
condition n/a
subroutine 2 18 11.1
pod 2 16 12.5
total 10 179 5.5


line stmt bran cond sub pod time code
1             package AMF::Perl::IO::Deserializer;
2             # Copyright (c) 2003 by Vsevolod (Simon) Ilyushchenko. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5             # The code is based on the -PHP project (http://amfphp.sourceforge.net/)
6              
7             =head1 NAME
8              
9             AMF::Perl::IO::Deserializer
10              
11             =head1 DESCRIPTION
12              
13             Package used to turn the binary data into physical perl objects.
14              
15             =head1 CHANGES
16              
17             =head2 Sun Sep 19 13:01:35 EDT 2004
18              
19             =item Patch from Kostas Chatzikokolakis about error checking of input data length.
20              
21             =head2 Sat Mar 13 16:31:31 EST 2004
22              
23             =item Patch from Kostas Chatzikokolakis handling encoding.
24              
25             =head2 Sun Mar 9 18:17:31 EST 2003
26              
27             =item The return value of readArray should be \@ret, not @ret.
28              
29             =head2 Tue Mar 11 21:55:41 EST 2003
30              
31             =item Fixed reading keys of objects.
32              
33             =item Added floor(), as Perl lacks it.
34              
35             =head2 Sun Apr 6 14:24:00 2003
36              
37             =item Added code to read objects of type 8. Useful for decoding real AMF server packages, but hardly anywhere else.
38              
39             =cut
40              
41 1     1   5 use strict;
  1         2  
  1         42  
42              
43 1     1   929 use Encode qw/from_to/;
  1         12303  
  1         1307  
44              
45             # the number of headers in the packet
46             my $header_count;
47             # the content of the headers
48             my $headers;
49             # the number of body elements
50             my $body_count;
51             # the content of the body
52             my $body;
53              
54             sub floor
55             {
56 0     0 1   my $n = shift;
57              
58 0 0         return int($n) - ($n < 0 ? 1: 0) * ($n != int($n) ? 1 : 0);
    0          
59             }
60              
61              
62             #******************** PUBLIC METHODS ****************************/
63              
64             # constructor that also dserializes the raw data
65             sub new
66             {
67 0     0 0   my ($proto, $is, $encoding)=@_;
68 0           my $self = {};
69 0           bless $self, $proto;
70             # the object to store the deserialized data
71 0           $self->{amfdata} = new AMF::Perl::Util::Object();
72             # save the input stream in this object
73 0           $self->{inputStream} = $is;
74             # save the encoding in this object
75 0           $self->{encoding} = $encoding;
76             # read the binary header
77 0           $self->readHeader();
78             # read the binary body
79 0           $self->readBody();
80 0           return $self;
81             }
82              
83             # returns the instance of the Object package
84             sub getObject
85             {
86 0     0 0   my ($self)=@_;
87 0           return $self->{amfdata};
88             }
89              
90             #******************** PRIVATE METHODS ****************************/
91              
92             sub readHeader
93             {
94 0     0 0   my ($self)=@_;
95             # ignore the first two bytes -- version or something
96 0           $self->{inputStream}->readInt();
97             # find the total number of header elements
98 0           $self->{header_count} = $self->{inputStream}->readInt();
99             # loop over all of the header elements
100 0           while($self->{header_count}--)
101             {
102 0           my $name = $self->{inputStream}->readUTF();
103             # find the must understand flag
104 0           my $required = $self->readBoolean();
105             # grab the length of the header element
106 0           my $length = $self->{inputStream}->readLong();
107             # grab the type of the element
108 0           my $type = $self->{inputStream}->readByte();
109             # turn the element into real data
110 0           my $content = $self->readData($type);
111             # save the name/value into the headers array
112 0           $self->{amfdata}->addHeader($name, $required, $content);
113             }
114             }
115              
116             sub readBody
117             {
118 0     0 0   my ($self)=@_;
119             # find the total number of body elements
120 0           $self->{body_count} = $self->{inputStream}->readInt();
121             # loop over all of the body elements
122 0           while($self->{body_count}--)
123             {
124 0           my $method = $self->readString();
125             # the target that the client understands
126 0           my $target = $self->readString();
127             # grab the length of the body element
128 0           my $length = $self->{inputStream}->readLong();
129            
130             # grab the type of the element
131 0           my $type = $self->{inputStream}->readByte();
132             # turn the argument elements into real data
133 0           my $data = $self->readData($type);
134             # add the body element to the body object
135 0           $self->{amfdata}->addBody($method, $target, $data);
136             }
137             }
138              
139              
140             # reads an object and converts the binary data into a Perl object
141             sub readObject
142             {
143 0     0 0   my ($self)=@_;
144             # init the array
145 0           my %ret;
146            
147             # grab the key
148 0           my $key = $self->{inputStream}->readUTF();
149            
150 0           for (my $type = $self->{inputStream}->readByte(); $type != 9; $type = $self->{inputStream}->readByte())
151             {
152 0 0         die "Malformed AMF data, no object end byte" unless defined($type);
153             # grab the value
154 0           my $val = $self->readData($type);
155             # save the name/value pair in the array
156 0           $ret{$key} = $val;
157             # get the next name
158 0           $key = $self->{inputStream}->readUTF();
159             }
160             # return the array
161 0           return \%ret;
162             }
163              
164             # reads and array object and converts the binary data into a Perl array
165             sub readArray
166             {
167 0     0 1   my ($self)=@_;
168             # init the array object
169 0           my @ret;
170             # get the length of the array
171 0           my $length = $self->{inputStream}->readLong();
172 0 0         die "Malformed AMF data, array length too big" if $length > $self->{inputStream}{content_length};
173             # loop over all of the elements in the data
174 0           for (my $i=0; $i<$length; $i++)
175             {
176             # grab the type for each element
177 0           my $type = $self->{inputStream}->readByte();
178             # grab each element
179 0           push @ret, $self->readData($type);
180             }
181             # return the data
182 0           return \@ret;
183             }
184              
185             sub readCustomClass
186             {
187 0     0 0   my ($self)=@_;
188             # grab the explicit type -- I'm not really convinced on this one but it works,
189             # the only example i've seen is the NetDebugConfig object
190 0           my $typeIdentifier = $self->{inputStream}->readUTF();
191             # the rest of the bytes are an object without the 0x03 header
192 0           my $value = $self->readObject();
193             # save that type because we may need it if we can find a way to add debugging features
194 0           $value->{"_explicitType"} = $typeIdentifier;
195             # return the object
196 0           return $value;
197             }
198              
199             sub readNumber
200             {
201 0     0 0   my ($self)=@_;
202             # grab the binary representation of the number
203 0           return $self->{inputStream}->readDouble();
204             }
205              
206             # read the next byte and return it's boolean value
207             sub readBoolean
208             {
209 0     0 0   my ($self)=@_;
210             # grab the int value of the next byte
211 0           my $int = $self->{inputStream}->readByte();
212             # if it's a 0x01 return true else return false
213 0           return ($int == 1);
214             }
215              
216             sub readString
217             {
218 0     0 0   my ($self)=@_;
219 0           my $s = $self->{inputStream}->readUTF();
220 0 0         from_to($s, "utf8", $self->{encoding}) if $self->{encoding};
221 0           return $s;
222             }
223              
224             sub readDate
225             {
226 0     0 0   my ($self)=@_;
227 0           my $ms = $self->{inputStream}->readDouble(); # date in milliseconds from 01/01/1970
228              
229             # nasty way to get timezone
230 0           my $int = $self->{inputStream}->readInt();
231 0 0         if($int > 720)
232             {
233 0           $int = -(65536 - $int);
234             }
235 0           my $hr = floor($int / 60);
236 0           my $min = $int % 60;
237 0           my $timezone = "GMT " . -$hr . ":" . abs($min);
238             # end nastiness
239              
240             # is there a nice way to return entire date(milliseconds and timezone) in PHP???
241 0           return $ms;
242             }
243              
244             # XML comes in as a plain string except it has a long displaying the length instead of a short?
245             sub readXML
246             {
247 0     0 0   my ($self)=@_;
248             # reads XML
249 0           my $rawXML = $self->{inputStream}->readLongUTF();
250 0 0         from_to($rawXML, "utf8", $self->{encoding}) if $self->{encoding};
251            
252             # maybe parse the XML into a PHP XML structure??? or leave it to the developer
253            
254             # return the xml
255 0           return $rawXML;
256             }
257             sub readFlushedSO
258             {
259 0     0 0   my ($self)=@_;
260             # receives [type(07) 00 00] if SO is flushed and contains 'public' properties
261             # see debugger readout ???
262 0           return $self->{inputStream}->readInt();
263             }
264              
265             sub readASObject
266             {
267 0     0 0   my ($self)=@_;
268              
269             #object Button, object Textformat, object Sound, object Number, object Boolean, object String,
270             #SharedObject unflushed, XMLNode, used XMLSocket??, NetConnection,
271             #SharedObject.data, SharedObject containing 'private' properties
272              
273             #the final byte seems to be the dataType -> 0D
274 0           return undef;
275             }
276              
277             # main switch function to process all of the data types
278             sub readData
279             {
280 0     0 0   my ($self, $type) = @_;
281 0           my $data;
282             #print STDERR "Reading data of type $type\n";
283 0 0         if ($type == 0) # number
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
284             {
285 0           $data = $self->readNumber();
286             }
287             elsif ($type == 1) # boolean
288             {
289 0           $data = $self->readBoolean();
290             }
291             elsif ($type == 2) # string
292             {
293 0           $data = $self->readString();
294             }
295             elsif ($type == 3) # object Object
296             {
297 0           $data = $self->readObject();
298             }
299             elsif ($type == 5) # null
300             {
301 0           $data = undef;
302             }
303             elsif ($type == 6) # undefined
304             {
305 0           $data = undef;
306             }
307             elsif ($type == 7) # flushed SharedObject containing 'public' properties
308             {
309 0           $data = $self->readFlushedSO();
310             }
311             elsif ($type == 8) # array
312             {
313             # shared object format only (*.sol)
314             # only time I saw it was the serverinfo value in a ColdFusion RecordSet
315             # It was just four zeroes - skip them.
316 0           for (my $i=0; $i<4; $i++)
317             {
318 0           $self->{inputStream}->readByte();
319             }
320             }
321             elsif ($type == 10) # array
322             {
323 0           $data = $self->readArray();
324             }
325             elsif ($type == 11) # date
326             {
327 0           $data = $self->readDate();
328             }
329             elsif ($type == 13) # mainly internal AS objects
330             {
331 0           $data = $self->readASObject();
332             }
333             elsif ($type == 15) # XML
334             {
335 0           $data = $self->readXML();
336             }
337             elsif ($type == 16) # Custom Class
338             {
339 0           $data = $self->readCustomClass();
340             }
341             else # unknown case
342             {
343 0           print STDERR "Unknown data type: $type\n";
344             }
345              
346 0           return $data;
347             }
348            
349             1;