diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 014287b9243cc66cfc79a9ecf3d071048929c2a9..4024be84c1e6d95f229b050cec46fb379b104b6f 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Make Mime::Type.parse consider q values (if any) [Jamis Buck] + * XML-formatted requests are typecast according to "type" attributes for :xml_simple [Jamis Buck] * Added protection against proxy setups treating requests as local even when they're not #3898 [stephen_purcell@yahoo.com] diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 35095dc77065e6a10702e432bacba67e8ad67bdd..43c4b056f720586b1a9bf1617bb5216151656d85 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -1,31 +1,85 @@ module Mime class Type + + # A simple helper class used in parsing the accept header + class AcceptItem #:nodoc: + attr_accessor :order, :name, :q + + def initialize(order, name, q=nil) + @order = order + @name = name.strip + q ||= 0.0 if @name == "*/*" # default "*/*" to end of list + @q = ((q || 1.0).to_f * 100).to_i + end + + def to_s + @name + end + + def <=>(item) + result = item.q <=> q + result = order <=> item.order if result == 0 + result + end + + def ==(item) + name == (item.respond_to?(:name) ? item.name : item) + end + end + class << self def lookup(string) LOOKUP[string] end def parse(accept_header) - mime_types = accept_header.split(",").collect! do |mime_type| - mime_type.split(";").first.strip + # keep track of creation order to keep the subsequent sort stable + index = 0 + list = accept_header.split(/,/). + map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort! + + # Take care of the broken text/xml entry by renaming or deleting it + + text_xml = list.index("text/xml") + app_xml = list.index("application/xml") + + if text_xml && app_xml + # set the q value to the max of the two + list[app_xml].q = [list[text_xml].q, list[app_xml].q].max + + # make sure app_xml is ahead of text_xml in the list + if app_xml > text_xml + list[app_xml], list[text_xml] = list[text_xml], list[app_xml] + app_xml, text_xml = text_xml, app_xml + end + + # delete text_xml from the list + list.delete_at(text_xml) + + elsif text_xml + list[text_xml].name = "application/xml" end - reorder_xml_types!(mime_types) - mime_types.collect! { |mime_type| Mime::Type.lookup(mime_type) } - end - - private - def reorder_xml_types!(mime_types) - mime_types.delete("text/xml") if mime_types.include?("application/xml") + # Look for more specific xml-based types and sort them ahead of app/xml - if index_for_generic_xml = mime_types.index("application/xml") - specific_xml_types = mime_types[index_for_generic_xml..-1].grep(/application\/[a-z]*\+xml/) + if app_xml + idx = app_xml + app_xml_type = list[app_xml] - for specific_xml_type in specific_xml_types.reverse - mime_types.insert(index_for_generic_xml, mime_types.delete(specific_xml_type)) + while(idx < list.length) + type = list[idx] + break if type.q < app_xml_type.q + if type.name =~ /\+xml$/ + list[app_xml], list[idx] = list[idx], list[app_xml] + app_xml = idx end + idx += 1 end end + + list.map! { |i| Mime::Type.lookup(i.name) }.uniq! + list + end end def initialize(string, symbol = nil, synonyms = []) diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..aa1d4459eebe4f3dd16e4f8bb07c87c5587563fc --- /dev/null +++ b/actionpack/test/controller/mime_type_test.rb @@ -0,0 +1,24 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class MimeTypeTest < Test::Unit::TestCase + Mime::PNG = Mime::Type.new("image/png") + Mime::PLAIN = Mime::Type.new("text/plain") + + def test_parse_single + Mime::LOOKUP.keys.each do |mime_type| + assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) + end + end + + def test_parse_without_q + accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,*/*" + expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::PLAIN, Mime::ALL] + assert_equal expect, Mime::Type.parse(accept) + end + + def test_parse_with_q + accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,*/*; q=0.2" + expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PLAIN, Mime::YAML, Mime::ALL] + assert_equal expect, Mime::Type.parse(accept) + end +end \ No newline at end of file