From b7c615c335ba4f522cc43e8ad1cc74b6dbec6ca7 Mon Sep 17 00:00:00 2001 From: Pieter van de Bruggen Date: Mon, 28 Jan 2013 17:31:08 -0800 Subject: [PATCH] Exposing Accept header parameters. --- lib/sinatra/base.rb | 49 +++++++++++++++++++++++++++++++++----------- test/request_test.rb | 15 ++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index a8dcb5a1..65c7c69b 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -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 diff --git a/test/request_test.rb b/test/request_test.rb index 13abad4c..fa28bd8b 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -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