rack-amf 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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