diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index cc40421b..c5b6e7be 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -16,12 +16,16 @@ module Sinatra # The request object. See Rack::Request for more info: # http://rack.rubyforge.org/doc/classes/Rack/Request.html class Request < Rack::Request + HEADER_PARAM = /;[\d\w.]+=(?:[\d\w.]+|"(?:[^"\\]|\\.)*")?/ + HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+|\*))(?:#{HEADER_PARAM})*/ + # Returns an array of acceptable media types for the response def accept - @env['sinatra.accept'] ||= begin - entries = @env['HTTP_ACCEPT'].to_s.split(',') - entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first) + @env['sinatra.accept_entries'] ||= begin + entries = @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS) + entries.map { |e| accept_entry(e) }.sort_by(&:last) end + @env['sinatra.accept'] ||= @env['sinatra.accept_entries'].map(&:first) end def preferred_type(*types) @@ -51,10 +55,11 @@ module Sinatra private def accept_entry(entry) - type, *options = entry.delete(' ').split(';') + type = entry.delete(' ').split(';').first + options = entry.scan(HEADER_PARAM).map { |s| s[1..-1] } quality = 0 # we sort smallest first options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } - [type, [quality, type.count('*'), 1 - options.size]] + [type, options, [quality, type.count('*'), 1 - options.size]] end end diff --git a/test/filter_test.rb b/test/filter_test.rb index dfc58547..45e0a1d5 100644 --- a/test/filter_test.rb +++ b/test/filter_test.rb @@ -431,7 +431,7 @@ class AfterFilterTest < Test::Unit::TestCase get('/') { @type } end - get('/', {}, { 'HTTP_ACCEPT' => '*' }) + get('/', {}, { 'HTTP_ACCEPT' => '*/*' }) assert_body 'txt' end end diff --git a/test/routing_test.rb b/test/routing_test.rb index 80b0a00b..2dea753a 100644 --- a/test/routing_test.rb +++ b/test/routing_test.rb @@ -779,7 +779,7 @@ class RoutingTest < Test::Unit::TestCase get('*', :provides => :html) { 'html' } end - get '/', {}, { 'HTTP_ACCEPT' => '*' } + get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] assert_body 'txt' @@ -826,7 +826,7 @@ class RoutingTest < Test::Unit::TestCase assert_equal 'default', body end - it 'respects user agent prefferences for the content type' do + it 'respects user agent preferences for the content type' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' } assert_body 'text/html;charset=utf-8' @@ -881,6 +881,55 @@ class RoutingTest < Test::Unit::TestCase assert_body 'text/html;charset=utf-8' end + it 'supplies a default quality of 1.0' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*' } + assert_body 'text/html;charset=utf-8' + end + + it 'orders types with equal quality by parameter count' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + lo_png = 'image/png;q=0.5' + hi_png = 'image/png;q=0.5;profile=FOGRA40;gamma=0.8' + jpeg = 'image/jpeg;q=0.5;compress=0.25' + + get '/', {}, { 'HTTP_ACCEPT' => "#{lo_png}, #{jpeg}" } + assert_body 'image/jpeg' + get '/', {}, { 'HTTP_ACCEPT' => "#{hi_png}, #{jpeg}" } + assert_body 'image/png' + end + + it 'ignores the quality parameter when ordering by parameter count' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + lo_png = 'image/png' + hi_png = 'image/png;profile=FOGRA40;gamma=0.8' + jpeg = 'image/jpeg;q=1.0;compress=0.25' + + get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{lo_png}" } + assert_body 'image/jpeg' + get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{hi_png}" } + assert_body 'image/png' + end + + it 'properly handles quoted strings in parameters' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5;profile=",image/jpeg,"' } + assert_body 'image/png' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x=";q=1.0"' } + assert_body 'image/png' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x="\";q=1.0"' } + assert_body 'image/png' + end + it 'accepts both text/javascript and application/javascript for js' do mock_app { get('/', :provides => :js) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' }