multi_xml_patch.rb 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Same vulnerability as CVE-2013-0156
  2. # https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion
  3. # Code has been submitted back to the project:
  4. # https://github.com/sferik/multi_xml/pull/34
  5. # Until the fix is released, use this monkey-patch.
  6. require "multi_xml"
  7. module MultiXml
  8. class DisallowedTypeError < StandardError
  9. def initialize(type)
  10. super "Disallowed type attribute: #{type.inspect}"
  11. end
  12. end
  13. DISALLOWED_XML_TYPES = %w(symbol yaml) unless defined?(DISALLOWED_XML_TYPES)
  14. class << self
  15. def parse(xml, options={})
  16. xml ||= ''
  17. xml.strip! if xml.respond_to?(:strip!)
  18. begin
  19. xml = StringIO.new(xml) unless xml.respond_to?(:read)
  20. char = xml.getc
  21. return {} if char.nil?
  22. xml.ungetc(char)
  23. hash = typecast_xml_value(undasherize_keys(parser.parse(xml)), options[:disallowed_types]) || {}
  24. rescue DisallowedTypeError
  25. raise
  26. rescue parser.parse_error => error
  27. raise ParseError, error.to_s, error.backtrace
  28. end
  29. hash = symbolize_keys(hash) if options[:symbolize_keys]
  30. hash
  31. end
  32. private
  33. def typecast_xml_value(value, disallowed_types=nil)
  34. disallowed_types ||= DISALLOWED_XML_TYPES
  35. case value
  36. when Hash
  37. if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
  38. raise DisallowedTypeError, value['type']
  39. end
  40. if value['type'] == 'array'
  41. # this commented-out suggestion helps to avoid the multiple attribute
  42. # problem, but it breaks when there is only one item in the array.
  43. #
  44. # from: https://github.com/jnunemaker/httparty/issues/102
  45. #
  46. # _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) }
  47. # This attempt fails to consider the order that the detect method
  48. # retrieves the entries.
  49. #_, entries = value.detect {|key, _| key != 'type'}
  50. # This approach ignores attribute entries that are not convertable
  51. # to an Array which allows attributes to be ignored.
  52. _, entries = value.detect {|k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) }
  53. if entries.nil? || (entries.is_a?(String) && entries.strip.empty?)
  54. []
  55. else
  56. case entries
  57. when Array
  58. entries.map {|entry| typecast_xml_value(entry, disallowed_types)}
  59. when Hash
  60. [typecast_xml_value(entries, disallowed_types)]
  61. else
  62. raise "can't typecast #{entries.class.name}: #{entries.inspect}"
  63. end
  64. end
  65. elsif value.has_key?(CONTENT_ROOT)
  66. content = value[CONTENT_ROOT]
  67. if block = PARSING[value['type']]
  68. if block.arity == 1
  69. value.delete('type') if PARSING[value['type']]
  70. if value.keys.size > 1
  71. value[CONTENT_ROOT] = block.call(content)
  72. value
  73. else
  74. block.call(content)
  75. end
  76. else
  77. block.call(content, value)
  78. end
  79. else
  80. value.keys.size > 1 ? value : content
  81. end
  82. elsif value['type'] == 'string' && value['nil'] != 'true'
  83. ''
  84. # blank or nil parsed values are represented by nil
  85. elsif value.empty? || value['nil'] == 'true'
  86. nil
  87. # If the type is the only element which makes it then
  88. # this still makes the value nil, except if type is
  89. # a XML node(where type['value'] is a Hash)
  90. elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash)
  91. nil
  92. else
  93. xml_value = value.inject({}) do |hash, (k, v)|
  94. hash[k] = typecast_xml_value(v, disallowed_types)
  95. hash
  96. end
  97. # Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
  98. # how multipart uploaded files from HTML appear
  99. xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
  100. end
  101. when Array
  102. value.map!{|i| typecast_xml_value(i, disallowed_types)}
  103. value.length > 1 ? value : value.first
  104. when String
  105. value
  106. else
  107. raise "can't typecast #{value.class.name}: #{value.inspect}"
  108. end
  109. end
  110. end
  111. end