lib/rocketamf/pure/deserializer.rb in mrpin-rocketamf-1.0.4 vs lib/rocketamf/pure/deserializer.rb in mrpin-rocketamf-2.0.0

- old
+ new

@@ -1,184 +1,82 @@ -require 'rocketamf/pure/io_helpers' +require 'rocketamf/pure/helpers/io_helper_read' module RocketAMF module Pure - # Pure ruby deserializer for AMF0 and AMF3 + # Pure ruby deserializer for AMF3 requests class Deserializer - attr_accessor :source + # + # Modules + # + private + include RocketAMF::Pure::IOHelperRead + + # + # Properties + # + + public + attr_reader :source + # Pass in the class mapper instance to use when deserializing. This # enables better caching behavior in the class mapper and allows # one to change mappings between deserialization attempts. + public def initialize(class_mapper) @class_mapper = class_mapper end - # Deserialize the source using AMF0 or AMF3. Source should either + # Deserialize the source using AMF3. Source should either # be a string or StringIO object. If you pass a StringIO object, # it will have its position updated to the end of the deserialized # data. - def deserialize(version, source) - result = [] + # raise AMFError if error appeared in deserialize, source is nil + # return hash {requests: [], incomplete_request: String} + public + def deserialize(source) + raise AMFError, 'no source to deserialize' if source.nil? - @version = version + @source = source.is_a?(StringIO) ? source : StringIO.new(source) - if source.is_a?(StringIO) - @source = source - elsif source - @source = StringIO.new(source) - elsif @source.nil? - raise AMFError, 'no source to deserialize' - end + requests = [] - case @version - when 0 - until @source.eof? - @ref_cache = [] - result << amf0_deserialize - end - when 3 - until @source.eof? - @string_cache = [] - @object_cache = [] - @trait_cache = [] - result << amf3_deserialize - end - else - raise ArgumentError, "unsupported version #{version}" - end + incomplete_request = nil - result - end + until @source.eof? + begin + @string_cache = [] + @object_cache = [] + @trait_cache = [] - # Reads an object from the deserializer's stream and returns it. - def read_object - @version == 0 ? amf0_deserialize : amf3_deserialize - end + @position_request_read = @source.pos - private - include RocketAMF::Pure::ReadIOHelpers + requests << amf3_deserialize + rescue AMFErrorIncomplete => e + @source.pos = @position_request_read - def amf0_deserialize(type = nil) - type = read_int8 @source unless type - case type - when AMF0_NUMBER_MARKER - amf0_read_number - when AMF0_BOOLEAN_MARKER - amf0_read_boolean - when AMF0_STRING_MARKER - amf0_read_string - when AMF0_OBJECT_MARKER - amf0_read_object - when AMF0_NULL_MARKER - nil - when AMF0_UNDEFINED_MARKER - nil - when AMF0_REFERENCE_MARKER - amf0_read_reference - when AMF0_HASH_MARKER - amf0_read_hash - when AMF0_STRICT_ARRAY_MARKER - amf0_read_array - when AMF0_DATE_MARKER - amf0_read_date - when AMF0_LONG_STRING_MARKER - amf0_read_string true - when AMF0_UNSUPPORTED_MARKER - nil - when AMF0_XML_MARKER - amf0_read_string true - when AMF0_TYPED_OBJECT_MARKER - amf0_read_typed_object - when AMF0_AMF3_MARKER - deserialize(3, nil) - else - raise AMFError, "Invalid type: #{type}" - end - end + incomplete_request = @source.read - def amf0_read_number - result = read_double @source - (result.is_a?(Float) && result.nan?) ? nil : result # check for NaN and convert them to nil - end - - def amf0_read_boolean - read_int8(@source) != 0 - end - - def amf0_read_string(long=false) - len = long ? read_word32_network(@source) : read_word16_network(@source) - result = @source.read(len) - result.force_encoding('UTF-8') if result.respond_to?(:force_encoding) - result - end - - def amf0_read_reference - index = read_word16_network(@source) - @ref_cache[index] - end - - def amf0_read_array - len = read_word32_network(@source) - result = [] - @ref_cache << result - - 0.upto(len - 1) do - result << amf0_deserialize + break + end end - result - end - def amf0_read_date - seconds = read_double(@source).to_f/1000 - time = Time.at(seconds) - tz = read_word16_network(@source) # Unused - time + { + requests: requests, + incomplete_request: incomplete_request + } end - def amf0_read_props(obj = {}) - while true - key = amf0_read_string - type = read_int8 @source - break if type == AMF0_OBJECT_END_MARKER - obj[key] = amf0_deserialize(type) - end - obj + # Reads an object from the deserializer stream and returns it. + public + def read_object + amf3_deserialize end - def amf0_read_hash - len = read_word32_network(@source) # Read and ignore length - obj = {} - @ref_cache << obj - amf0_read_props obj - end - - def amf0_read_object add_to_ref_cache=true - # Create "object" and add to ref cache (it's always a Hash) - obj = @class_mapper.get_ruby_obj "" - @ref_cache << obj - - # Populate object - props = amf0_read_props - @class_mapper.populate_ruby_obj obj, props - return obj - end - - def amf0_read_typed_object - # Create object to add to ref cache - class_name = amf0_read_string - obj = @class_mapper.get_ruby_obj class_name - @ref_cache << obj - - # Populate object - props = amf0_read_props - @class_mapper.populate_ruby_obj obj, props - return obj - end - + private def amf3_deserialize - type = read_int8 @source + type = read_int8(@source) case type when AMF3_UNDEFINED_MARKER nil when AMF3_NULL_MARKER nil @@ -201,24 +99,50 @@ when AMF3_OBJECT_MARKER amf3_read_object when AMF3_BYTE_ARRAY_MARKER amf3_read_byte_array when AMF3_VECTOR_INT_MARKER, AMF3_VECTOR_UINT_MARKER, AMF3_VECTOR_DOUBLE_MARKER, AMF3_VECTOR_OBJECT_MARKER - amf3_read_vector type + amf3_read_vector(type) when AMF3_DICT_MARKER amf3_read_dict else raise AMFError, "Invalid type: #{type}" end end + private + def get_as_reference_object(type) + result = nil + + if (type & 0x01) == 0 #is reference? + reference = type >> 1 + result = @object_cache[reference] + end + + result + end + + private + def get_as_reference_string(type) + result = nil + + if (type & 0x01) == 0 #is reference? + reference = type >> 1 + result = @string_cache[reference] + end + + result + end + + private def amf3_read_integer - n = 0 - b = read_word8(@source) || 0 result = 0 - while ((b & 0x80) != 0 && n < 3) + n = 0 + b = read_word8(@source) || 0 + + while (b & 0x80) != 0 && n < 3 result = result << 7 result = result | (b & 0x7f) b = read_word8(@source) || 0 n = n + 1 end @@ -234,107 +158,139 @@ #Check if the integer should be negative if result > MAX_INTEGER result -= (1 << 29) end end + result end + private def amf3_read_number - res = read_double @source - (res.is_a?(Float) && res.nan?) ? nil : res # check for NaN and convert them to nil + result = read_double(@source) + + #check for NaN and convert them to nil + if result.is_a?(Float) && result.nan? + result = nil + end + + result end + private def amf3_read_string - type = amf3_read_integer - is_reference = (type & 0x01) == 0 + result = nil - if is_reference - reference = type >> 1 - return @string_cache[reference] - else + type = amf3_read_integer + + result = get_as_reference_string(type) + + if result.nil? length = type >> 1 - str = '' + result = '' + if length > 0 - str = @source.read(length) - str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) - @string_cache << str + + if length > (@source.size - @source.pos) + raise AMFErrorIncomplete.new + end + + result = @source.read(length) + result.force_encoding('UTF-8') if result.respond_to?(:force_encoding) + @string_cache << result end - return str end + + result end + private def amf3_read_xml result = nil - type = amf3_read_integer - is_reference = (type & 0x01) == 0 + type = amf3_read_integer - if is_reference - reference = type >> 1 - result = @object_cache[reference] - else + result = get_as_reference_object(type) + + if result.nil? length = type >> 1 - str = "" + + result = '' + if length > 0 - str = @source.read(length) - str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) - @object_cache << str + if length > (@source.size - @source.pos) + raise AMFErrorIncomplete.new + end + + result = @source.read(length) + result.force_encoding('UTF-8') if result.respond_to?(:force_encoding) + @object_cache << result end - result = str end result end + private def amf3_read_byte_array - type = amf3_read_integer - is_reference = (type & 0x01) == 0 + result = nil - if is_reference - reference = type >> 1 - return @object_cache[reference] - else + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? length = type >> 1 - obj = StringIO.new @source.read(length) - @object_cache << obj - obj + + if length > (@source.size - @source.pos) + raise AMFErrorIncomplete.new + end + + result = StringIO.new(@source.read(length)) + @object_cache << result end + + result end + private def amf3_read_array - type = amf3_read_integer - is_reference = (type & 0x01) == 0 + result = nil - if is_reference - reference = type >> 1 - return @object_cache[reference] - else + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? length = type >> 1 property_name = amf3_read_string - array = property_name.length > 0 ? {} : [] - @object_cache << array + result = property_name.length > 0 ? {} : [] + @object_cache << result while property_name.length > 0 - value = amf3_deserialize - array[property_name] = value - property_name = amf3_read_string + value = amf3_deserialize + result[property_name] = value + property_name = amf3_read_string end - 0.upto(length - 1) { |i| array[i] = amf3_deserialize } - array + 0.upto(length - 1) { |i| result[i] = amf3_deserialize } end + + result end + # externalizable - an instance of a Class that implements flash.utils.IExternalizable and completely controls the serialization of its members (no property names are included in the trait information) + # dynamic - c an instance of a Class definition with the dynamic trait declared; public variable members can be added and removed from instances dynamically at runtime + private def amf3_read_object - type = amf3_read_integer - is_reference = (type & 0x01) == 0 + result = nil - if is_reference - reference = type >> 1 - return @object_cache[reference] - else + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? class_type = type >> 1 class_is_reference = (class_type & 0x01) == 0 if class_is_reference reference = class_type >> 1 @@ -357,110 +313,126 @@ } @trait_cache << traits end # Optimization for deserializing ArrayCollection - if traits[:class_name] == "flex.messaging.io.ArrayCollection" - arr = amf3_deserialize # Adds ArrayCollection array to object cache - @object_cache << arr # Add again for ArrayCollection source array - return arr + if traits[:class_name] == 'flex.messaging.io.ArrayCollection' + result = amf3_deserialize # Adds ArrayCollection array to object cache + @object_cache << result # Add again for ArrayCollection source array + return result end - obj = @class_mapper.get_ruby_obj traits[:class_name] - @object_cache << obj + result = @class_mapper.get_ruby_obj(traits[:class_name]) + @object_cache << result if traits[:externalizable] - obj.read_external self + result.read_external(self) else - props = {} + properties = {} + traits[:members].each do |key| - value = amf3_deserialize - props[key] = value + value = amf3_deserialize + properties[key] = value end - dynamic_props = nil if traits[:dynamic] - dynamic_props = {} while (key = amf3_read_string) && key.length != 0 do # read next key - value = amf3_deserialize - dynamic_props[key] = value + value = amf3_deserialize + properties[key] = value end end - @class_mapper.populate_ruby_obj obj, props, dynamic_props + @class_mapper.populate_ruby_obj(result, properties) end - obj + end + + result end + private def amf3_read_date - type = amf3_read_integer - is_reference = (type & 0x01) == 0 - if is_reference - reference = type >> 1 - return @object_cache[reference] - else + result = nil + + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? seconds = read_double(@source).to_f/1000 - time = Time.at(seconds) - @object_cache << time - time + result = Time.at(seconds) + @object_cache << result end + + result end + private def amf3_read_dict - type = amf3_read_integer - is_reference = (type & 0x01) == 0 - if is_reference - reference = type >> 1 - return @object_cache[reference] - else - dict = {} - @object_cache << dict + result = nil + + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? + result = {} + @object_cache << result length = type >> 1 - weak_keys = read_int8 @source # Ignore: Not supported in ruby + weak_keys = read_int8(@source) # Ignore: Not supported in ruby + 0.upto(length - 1) do |i| - dict[amf3_deserialize] = amf3_deserialize + result[amf3_deserialize] = amf3_deserialize end - dict + end + + result end - def amf3_read_vector vector_type - type = amf3_read_integer - is_reference = (type & 0x01) == 0 - if is_reference - reference = type >> 1 - return @object_cache[reference] - else - vec = [] - @object_cache << vec + private + def amf3_read_vector(vector_type) + result = nil + + type = amf3_read_integer + + result = get_as_reference_object(type) + + if result.nil? + + result = [] + @object_cache << result + length = type >> 1 - fixed_vector = read_int8 @source # Ignore + fixed_vector = read_int8(@source) # Ignore + case vector_type when AMF3_VECTOR_INT_MARKER 0.upto(length - 1) do |i| int = read_word32_network(@source) int = int - 2**32 if int > MAX_INTEGER - vec << int + result << int end when AMF3_VECTOR_UINT_MARKER 0.upto(length - 1) do |i| - vec << read_word32_network(@source) - puts vec[i].to_s(2) + result << read_word32_network(@source) end when AMF3_VECTOR_DOUBLE_MARKER 0.upto(length - 1) do |i| - vec << amf3_read_number + result << amf3_read_number end when AMF3_VECTOR_OBJECT_MARKER vector_class = amf3_read_string # Ignore - puts vector_class 0.upto(length - 1) do |i| - vec << amf3_deserialize + result << amf3_deserialize end + else + #do nothing end - vec - end - end - end - end + end #if + + result + end #read_vector + + end #Deserializer + end #Pure end \ No newline at end of file