rack-amf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,353 @@
1
+ require 'amf/pure/io_helpers'
2
+
3
+ module AMF
4
+ module Pure
5
+ # Pure ruby deserializer
6
+ #--
7
+ # AMF0 deserializer, it switches over to AMF3 when it sees the switch flag
8
+ class Deserializer
9
+ def initialize
10
+ @ref_cache = []
11
+ end
12
+
13
+ def deserialize(source, type=nil)
14
+ source = StringIO.new(source) unless StringIO === source
15
+ type = read_int8 source unless type
16
+ case type
17
+ when AMF0_NUMBER_MARKER
18
+ read_number source
19
+ when AMF0_BOOLEAN_MARKER
20
+ read_boolean source
21
+ when AMF0_STRING_MARKER
22
+ read_string source
23
+ when AMF0_OBJECT_MARKER
24
+ read_object source
25
+ when AMF0_NULL_MARKER
26
+ nil
27
+ when AMF0_UNDEFINED_MARKER
28
+ nil
29
+ when AMF0_REFERENCE_MARKER
30
+ read_reference source
31
+ when AMF0_HASH_MARKER
32
+ read_hash source
33
+ when AMF0_STRICT_ARRAY_MARKER
34
+ read_array source
35
+ when AMF0_DATE_MARKER
36
+ read_date source
37
+ when AMF0_LONG_STRING_MARKER
38
+ read_string source, true
39
+ when AMF0_UNSUPPORTED_MARKER
40
+ nil
41
+ when AMF0_XML_MARKER
42
+ #read_xml source
43
+ when AMF0_TYPED_OBJECT_MARKER
44
+ read_typed_object source
45
+ when AMF0_AMF3_MARKER
46
+ AMF3Deserializer.new.deserialize(source)
47
+ end
48
+ end
49
+
50
+ private
51
+ include AMF::Pure::ReadIOHelpers
52
+
53
+ def read_number source
54
+ res = read_double source
55
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
56
+ end
57
+
58
+ def read_boolean source
59
+ read_int8(source) != 0
60
+ end
61
+
62
+ def read_string source, long=false
63
+ len = long ? read_word32_network(source) : read_word16_network(source)
64
+ source.read(len)
65
+ end
66
+
67
+ def read_object source
68
+ obj = {}
69
+ while true
70
+ key = read_string source
71
+ type = read_int8 source
72
+ break if type == AMF0_OBJECT_END_MARKER
73
+ obj[key.to_sym] = deserialize(source, type)
74
+ end
75
+ @ref_cache << obj
76
+ obj
77
+ end
78
+
79
+ def read_reference source
80
+ index = read_word16_network(source)
81
+ @ref_cache[index]
82
+ end
83
+
84
+ def read_hash source
85
+ len = read_word32_network(source) # Read and ignore length
86
+
87
+ # Read first pair
88
+ key = read_string source
89
+ type = read_int8 source
90
+ return [] if type == AMF0_OBJECT_END_MARKER
91
+
92
+ # We need to figure out whether this is a real hash, or whether some stupid serializer gave up
93
+ if key.to_i.to_s == key
94
+ # Array
95
+ obj = []
96
+ obj[key.to_i] = deserialize(source, type)
97
+ while true
98
+ key = read_string source
99
+ type = read_int8 source
100
+ break if type == AMF0_OBJECT_END_MARKER
101
+ obj[key.to_i] = deserialize(source, type)
102
+ end
103
+ else
104
+ # Hash
105
+ obj = {key.to_sym => deserialize(source, type)}
106
+ while true
107
+ key = read_string source
108
+ type = read_int8 source
109
+ break if type == AMF0_OBJECT_END_MARKER
110
+ obj[key.to_sym] = deserialize(source, type)
111
+ end
112
+ end
113
+ @ref_cache << obj
114
+ obj
115
+ end
116
+
117
+ def read_array source
118
+ len = read_word32_network(source)
119
+ array = []
120
+ 0.upto(len - 1) do
121
+ array << deserialize(source)
122
+ end
123
+ @ref_cache << array
124
+ array
125
+ end
126
+
127
+ def read_date source
128
+ seconds = read_double(source).to_f/1000
129
+ time = Time.at(seconds)
130
+ tz = read_word16_network(source) # Unused
131
+ time
132
+ end
133
+
134
+ def read_typed_object source
135
+ class_name = read_string source
136
+ props = read_object source
137
+ @ref_cache.pop
138
+
139
+ obj = ClassMapper.get_ruby_obj class_name
140
+ ClassMapper.populate_ruby_obj obj, props, {}
141
+ @ref_cache << obj
142
+ obj
143
+ end
144
+ end
145
+
146
+ # AMF3 implementation of deserializer, loaded automatically by the AMF0
147
+ # deserializer when needed
148
+ class AMF3Deserializer
149
+ def initialize
150
+ @string_cache = []
151
+ @object_cache = []
152
+ @trait_cache = []
153
+ end
154
+
155
+ def deserialize(source, type=nil)
156
+ source = StringIO.new(source) unless StringIO === source
157
+ type = read_int8 source unless type
158
+ case type
159
+ when AMF3_UNDEFINED_MARKER
160
+ nil
161
+ when AMF3_NULL_MARKER
162
+ nil
163
+ when AMF3_FALSE_MARKER
164
+ false
165
+ when AMF3_TRUE_MARKER
166
+ true
167
+ when AMF3_INTEGER_MARKER
168
+ read_integer source
169
+ when AMF3_DOUBLE_MARKER
170
+ read_number source
171
+ when AMF3_STRING_MARKER
172
+ read_string source
173
+ when AMF3_XML_DOC_MARKER
174
+ #read_xml_string
175
+ when AMF3_DATE_MARKER
176
+ read_date source
177
+ when AMF3_ARRAY_MARKER
178
+ read_array source
179
+ when AMF3_OBJECT_MARKER
180
+ read_object source
181
+ when AMF3_XML_MARKER
182
+ #read_amf3_xml
183
+ when AMF3_BYTE_ARRAY_MARKER
184
+ #read_amf3_byte_array
185
+ end
186
+ end
187
+
188
+ private
189
+ include AMF::Pure::ReadIOHelpers
190
+
191
+ def read_integer source
192
+ n = 0
193
+ b = read_word8(source) || 0
194
+ result = 0
195
+
196
+ while ((b & 0x80) != 0 && n < 3)
197
+ result = result << 7
198
+ result = result | (b & 0x7f)
199
+ b = read_word8(source) || 0
200
+ n = n + 1
201
+ end
202
+
203
+ if (n < 3)
204
+ result = result << 7
205
+ result = result | b
206
+ else
207
+ #Use all 8 bits from the 4th byte
208
+ result = result << 8
209
+ result = result | b
210
+
211
+ #Check if the integer should be negative
212
+ if (result > MAX_INTEGER)
213
+ result -= (1 << 29)
214
+ end
215
+ end
216
+ result
217
+ end
218
+
219
+ def read_number source
220
+ res = read_double source
221
+ res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil
222
+ end
223
+
224
+ def read_string source
225
+ type = read_integer source
226
+ isReference = (type & 0x01) == 0
227
+
228
+ if isReference
229
+ reference = type >> 1
230
+ return @string_cache[reference]
231
+ else
232
+ length = type >> 1
233
+ #HACK needed for ['',''] array of empty strings
234
+ #It may be better to take one more parameter that
235
+ #would specify whether or not they expect us to return
236
+ #a string
237
+ str = "" #if stringRequest
238
+ if length > 0
239
+ str = source.read(length)
240
+ @string_cache << str
241
+ end
242
+ return str
243
+ end
244
+ end
245
+
246
+ def read_array source
247
+ type = read_integer source
248
+ isReference = (type & 0x01) == 0
249
+
250
+ if isReference
251
+ reference = type >> 1
252
+ return @object_cache[reference]
253
+ else
254
+ length = type >> 1
255
+ propertyName = read_string source
256
+ if propertyName != ""
257
+ array = {}
258
+ @object_cache << array
259
+ begin
260
+ while(propertyName.length)
261
+ value = deserialize(source)
262
+ array[propertyName] = value
263
+ propertyName = read_string source
264
+ end
265
+ rescue Exception => e #end of object exception, because propertyName.length will be non existent
266
+ end
267
+ 0.upto(length - 1) do |i|
268
+ array["" + i.to_s] = deserialize(source)
269
+ end
270
+ else
271
+ array = []
272
+ @object_cache << array
273
+ 0.upto(length - 1) do
274
+ array << deserialize(source)
275
+ end
276
+ end
277
+ array
278
+ end
279
+ end
280
+
281
+ def read_object source
282
+ type = read_integer source
283
+ isReference = (type & 0x01) == 0
284
+
285
+ if isReference
286
+ reference = type >> 1
287
+ return @object_cache[reference]
288
+ else
289
+ class_type = type >> 1
290
+ class_is_reference = (class_type & 0x01) == 0
291
+
292
+ if class_is_reference
293
+ reference = class_type >> 1
294
+ class_definition = @trait_cache[reference]
295
+ else
296
+ class_name = read_string source
297
+ externalizable = (class_type & 0x02) != 0
298
+ dynamic = (class_type & 0x04) != 0
299
+ attribute_count = class_type >> 3
300
+
301
+ class_attributes = []
302
+ attribute_count.times{class_attributes << read_string(source)} # Read class members
303
+
304
+ class_definition = {"class_name" => class_name,
305
+ "members" => class_attributes,
306
+ "externalizable" => externalizable,
307
+ "dynamic" => dynamic}
308
+ @trait_cache << class_definition
309
+ end
310
+
311
+ obj = ClassMapper.get_ruby_obj class_definition["class_name"]
312
+ @object_cache << obj
313
+
314
+ if class_definition['externalizable']
315
+ obj.externalized_data = deserialize(source)
316
+ else
317
+ props = {}
318
+ class_definition['members'].each do |key|
319
+ value = deserialize(source)
320
+ props[key.to_sym] = value
321
+ end
322
+
323
+ dynamic_props = nil
324
+ if class_definition['dynamic']
325
+ dynamic_props = {}
326
+ while (key = read_string source) && key.length != 0 do # read next key
327
+ value = deserialize(source)
328
+ dynamic_props[key.to_sym] = value
329
+ end
330
+ end
331
+
332
+ ClassMapper.populate_ruby_obj obj, props, dynamic_props
333
+ end
334
+ obj
335
+ end
336
+ end
337
+
338
+ def read_date source
339
+ type = read_integer source
340
+ isReference = (type & 0x01) == 0
341
+ if isReference
342
+ reference = type >> 1
343
+ return @object_cache[reference]
344
+ else
345
+ seconds = read_double(source).to_f/1000
346
+ time = Time.at(seconds)
347
+ @object_cache << time
348
+ time
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,94 @@
1
+ module AMF
2
+ module Pure
3
+ module ReadIOHelpers #:nodoc:
4
+ def read_int8 source
5
+ source.read(1).unpack('c').first
6
+ end
7
+
8
+ def read_word8 source
9
+ source.read(1).unpack('C').first
10
+ end
11
+
12
+ def read_double source
13
+ source.read(8).unpack('G').first
14
+ end
15
+
16
+ def read_word16_network source
17
+ source.read(2).unpack('n').first
18
+ end
19
+
20
+ def read_int16_network source
21
+ str = source.read(2)
22
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
23
+ str.unpack('s').first
24
+ end
25
+
26
+ def read_word32_network source
27
+ source.read(4).unpack('N').first
28
+ end
29
+
30
+ def byte_order
31
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
32
+ :BigEndian
33
+ else
34
+ :LittleEndian
35
+ end
36
+ end
37
+
38
+ def byte_order_little?
39
+ (byte_order == :LittleEndian) ? true : false;
40
+ end
41
+ end
42
+
43
+ module WriteIOHelpers #:nodoc:
44
+ def pack_integer(integer)
45
+ integer = integer & 0x1fffffff
46
+ if(integer < 0x80)
47
+ [integer].pack('c')
48
+ elsif(integer < 0x4000)
49
+ [integer >> 7 & 0x7f | 0x80].pack('c')+
50
+ [integer & 0x7f].pack('c')
51
+ elsif(integer < 0x200000)
52
+ [integer >> 14 & 0x7f | 0x80].pack('c') +
53
+ [integer >> 7 & 0x7f | 0x80].pack('c') +
54
+ [integer & 0x7f].pack('c')
55
+ else
56
+ [integer >> 22 & 0x7f | 0x80].pack('c')+
57
+ [integer >> 15 & 0x7f | 0x80].pack('c')+
58
+ [integer >> 8 & 0x7f | 0x80].pack('c')+
59
+ [integer & 0xff].pack('c')
60
+ end
61
+ end
62
+
63
+ def pack_double(double)
64
+ [double].pack('G')
65
+ end
66
+
67
+ def pack_int8(val)
68
+ [val].pack('c')
69
+ end
70
+
71
+ def pack_int16_network(val)
72
+ [val].pack('n')
73
+ end
74
+
75
+ def pack_word32_network(val)
76
+ str = [val].pack('L')
77
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
78
+ str
79
+ end
80
+
81
+ def byte_order
82
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
83
+ :BigEndian
84
+ else
85
+ :LittleEndian
86
+ end
87
+ end
88
+
89
+ def byte_order_little?
90
+ (byte_order == :LittleEndian) ? true : false;
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,120 @@
1
+ require 'amf/pure/io_helpers'
2
+
3
+ module AMF
4
+ module Pure
5
+ # AMF request object wrapper, it is responsible for deserializing AMF requests
6
+ class Request
7
+ attr_reader :amf_version, :headers, :messages
8
+
9
+ def initialize
10
+ @amf_version = 0
11
+ @headers = []
12
+ @messages = []
13
+ end
14
+
15
+ def populate_from_stream stream
16
+ stream = StringIO.new(stream) unless StringIO === stream
17
+
18
+ # Read AMF version
19
+ @amf_version = read_word16_network stream
20
+
21
+ # Read in headers
22
+ header_count = read_word16_network stream
23
+ 0.upto(header_count-1) do
24
+ name = stream.read(read_word16_network(stream))
25
+ must_understand = read_int8(stream) != 0
26
+ length = read_word32_network stream
27
+ data = AMF.deserialize stream
28
+ @headers << Header.new(name, must_understand, data)
29
+ end
30
+
31
+ # Read in messages
32
+ message_count = read_word16_network stream
33
+ 0.upto(message_count-1) do
34
+ target_uri = stream.read(read_word16_network(stream))
35
+ response_uri = stream.read(read_word16_network(stream))
36
+ length = read_word32_network stream
37
+ data = AMF.deserialize stream
38
+ if data.is_a?(Array) && data.length == 1 && data[0].is_a?(::AMF::Values::AbstractMessage)
39
+ data = data[0]
40
+ end
41
+ @messages << Message.new(target_uri, response_uri, data)
42
+ end
43
+
44
+ self
45
+ end
46
+
47
+ private
48
+ include AMF::Pure::ReadIOHelpers
49
+ end
50
+
51
+ # AMF response object wrapper, it is responsible for serializing the AMF response
52
+ class Response
53
+ attr_accessor :amf_version, :headers, :messages
54
+
55
+ def initialize
56
+ @amf_version = 3
57
+ @headers = []
58
+ @messages = []
59
+ end
60
+
61
+ def serialize
62
+ stream = ""
63
+
64
+ # Write version
65
+ stream << pack_int16_network(@amf_version)
66
+
67
+ # Write headers
68
+ stream << pack_int16_network(@headers.length) # Header count
69
+ @headers.each do |h|
70
+ stream << pack_int16_network(h.name.length)
71
+ stream << h.name
72
+ stream << pack_int8(h.must_understand ? 1 : 0)
73
+ stream << pack_word32_network(-1)
74
+ stream << AMF.serialize(h.data, 0)
75
+ end
76
+
77
+ # Write messages
78
+ stream << pack_int16_network(@messages.length) # Message count
79
+ @messages.each do |m|
80
+ stream << pack_int16_network(m.target_uri.length)
81
+ stream << m.target_uri
82
+
83
+ stream << pack_int16_network(m.response_uri.length)
84
+ stream << m.response_uri
85
+
86
+ stream << pack_word32_network(-1)
87
+ stream << AMF0_AMF3_MARKER if @amf_version == 3
88
+ stream << AMF.serialize(m.data, @amf_version)
89
+ end
90
+
91
+ stream
92
+ end
93
+
94
+ private
95
+ include AMF::Pure::WriteIOHelpers
96
+ end
97
+
98
+ # AMF::Request or AMF::Response header
99
+ class Header
100
+ attr_accessor :name, :must_understand, :data
101
+
102
+ def initialize name, must_understand, data
103
+ @name = name
104
+ @must_understand = must_understand
105
+ @data = data
106
+ end
107
+ end
108
+
109
+ # AMF::Request or AMF::Response message
110
+ class Message
111
+ attr_accessor :target_uri, :response_uri, :data
112
+
113
+ def initialize target_uri, response_uri, data
114
+ @target_uri = target_uri
115
+ @response_uri = response_uri
116
+ @data = data
117
+ end
118
+ end
119
+ end
120
+ end