123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- # Same vulnerability as CVE-2013-0156
- # https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion
- # Code has been submitted back to the project:
- # https://github.com/sferik/multi_xml/pull/34
- # Until the fix is released, use this monkey-patch.
- require "multi_xml"
- module MultiXml
- class DisallowedTypeError < StandardError
- def initialize(type)
- super "Disallowed type attribute: #{type.inspect}"
- end
- end
- DISALLOWED_XML_TYPES = %w(symbol yaml) unless defined?(DISALLOWED_XML_TYPES)
- class << self
- def parse(xml, options={})
- xml ||= ''
- xml.strip! if xml.respond_to?(:strip!)
- begin
- xml = StringIO.new(xml) unless xml.respond_to?(:read)
- char = xml.getc
- return {} if char.nil?
- xml.ungetc(char)
- hash = typecast_xml_value(undasherize_keys(parser.parse(xml)), options[:disallowed_types]) || {}
- rescue DisallowedTypeError
- raise
- rescue parser.parse_error => error
- raise ParseError, error.to_s, error.backtrace
- end
- hash = symbolize_keys(hash) if options[:symbolize_keys]
- hash
- end
- private
- def typecast_xml_value(value, disallowed_types=nil)
- disallowed_types ||= DISALLOWED_XML_TYPES
- case value
- when Hash
- if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
- raise DisallowedTypeError, value['type']
- end
- if value['type'] == 'array'
- # this commented-out suggestion helps to avoid the multiple attribute
- # problem, but it breaks when there is only one item in the array.
- #
- # from: https://github.com/jnunemaker/httparty/issues/102
- #
- # _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) }
- # This attempt fails to consider the order that the detect method
- # retrieves the entries.
- #_, entries = value.detect {|key, _| key != 'type'}
- # This approach ignores attribute entries that are not convertable
- # to an Array which allows attributes to be ignored.
- _, entries = value.detect {|k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) }
- if entries.nil? || (entries.is_a?(String) && entries.strip.empty?)
- []
- else
- case entries
- when Array
- entries.map {|entry| typecast_xml_value(entry, disallowed_types)}
- when Hash
- [typecast_xml_value(entries, disallowed_types)]
- else
- raise "can't typecast #{entries.class.name}: #{entries.inspect}"
- end
- end
- elsif value.has_key?(CONTENT_ROOT)
- content = value[CONTENT_ROOT]
- if block = PARSING[value['type']]
- if block.arity == 1
- value.delete('type') if PARSING[value['type']]
- if value.keys.size > 1
- value[CONTENT_ROOT] = block.call(content)
- value
- else
- block.call(content)
- end
- else
- block.call(content, value)
- end
- else
- value.keys.size > 1 ? value : content
- end
- elsif value['type'] == 'string' && value['nil'] != 'true'
- ''
- # blank or nil parsed values are represented by nil
- elsif value.empty? || value['nil'] == 'true'
- nil
- # If the type is the only element which makes it then
- # this still makes the value nil, except if type is
- # a XML node(where type['value'] is a Hash)
- elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash)
- nil
- else
- xml_value = value.inject({}) do |hash, (k, v)|
- hash[k] = typecast_xml_value(v, disallowed_types)
- hash
- end
- # Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
- # how multipart uploaded files from HTML appear
- xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
- end
- when Array
- value.map!{|i| typecast_xml_value(i, disallowed_types)}
- value.length > 1 ? value : value.first
- when String
- value
- else
- raise "can't typecast #{value.class.name}: #{value.inspect}"
- end
- end
- end
- end
|