Exposing Accept header parameters.

This commit is contained in:
Pieter van de Bruggen 2013-01-28 17:31:08 -08:00
parent 7721545ff2
commit b7c615c335
2 changed files with 52 additions and 12 deletions

View File

@ -16,16 +16,15 @@ 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})*/
HEADER_PARAM = /\s*[\d\w._]+=(?:[\d\w._]+|"(?:[^"\\]|\\.)*")?\s*/
HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+|\*))\s*(?:;#{HEADER_PARAM})*/
# Returns an array of acceptable media types for the response
def accept
@env['sinatra.accept_entries'] ||= begin
@env['sinatra.accept'] ||= begin
entries = @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS)
entries.map { |e| accept_entry(e) }.sort_by(&:last)
entries.map { |e| AcceptEntry.new(e) }.sort
end
@env['sinatra.accept'] ||= @env['sinatra.accept_entries'].map(&:first)
end
def preferred_type(*types)
@ -54,12 +53,37 @@ module Sinatra
private
def accept_entry(entry)
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, options, [quality, type.count('*'), 1 - options.size]]
class AcceptEntry
attr_accessor :params
def initialize(entry)
params = entry.scan(HEADER_PARAM).map do |s|
key, value = s.strip.split('=', 2)
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
[key, value]
end
@type = entry[/[^;]+/].delete(' ')
@params = Hash[params]
@q = @params.delete('q') { "1.0" }.to_f
end
def <=>(other)
other.priority <=> self.priority
end
def priority
# We sort in descending order; better matches should be higher.
[ @q, -@type.count('*'), @params.size ]
end
def [](param)
@params[param]
end
def to_str
@type
end
end
end
@ -1293,7 +1317,8 @@ module Sinatra
if type = response['Content-Type']
types.include? type or types.include? type[/^[^;]+/]
elsif type = request.preferred_type(types)
content_type(type)
params = (type.respond_to?(:params) ? type.params : {})
content_type(type, params)
true
else
false

View File

@ -42,4 +42,19 @@ class RequestTest < Test::Unit::TestCase
dumped = Marshal.dump(request.params)
assert_equal 'bar', Marshal.load(dumped)['foo']
end
it "exposes the preferred type's parameters" do
request = Sinatra::Request.new(
'HTTP_ACCEPT' => 'image/jpeg; compress=0.25'
)
assert_equal({ 'compress' => '0.25' }, request.preferred_type.params)
end
it "properly decodes MIME type parameters" do
request = Sinatra::Request.new(
'HTTP_ACCEPT' => 'image/jpeg;unquoted=0.25;quoted="0.25";chartest="\";,\x"'
)
expected = { 'unquoted' => '0.25', 'quoted' => '0.25', 'chartest' => '";,x' }
assert_equal(expected, request.preferred_type.params)
end
end