From 49b5324588c3a666ce99baaa44b459560e9f883a Mon Sep 17 00:00:00 2001 From: Jon Hope Date: Thu, 9 Feb 2017 00:24:19 +0000 Subject: [PATCH] Add support for parameters when creating mime types --- lib/sinatra/base.rb | 38 +++++++++++++++++++++++++++++++++----- test/request_test.rb | 20 ++++++++++++++++++++ test/routing_test.rb | 23 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index 0a81e98962..534431e0c7 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -42,12 +42,11 @@ def accept?(type) end def preferred_type(*types) - accepts = accept # just evaluate once - return accepts.first if types.empty? + return accept.first if types.empty? types.flatten! - return types.first if accepts.empty? - accepts.detect do |pattern| - type = types.detect { |t| File.fnmatch(pattern, t) } + return types.first if accept.empty? + accept.detect do |accept_header| + type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) } return type if type end end @@ -124,6 +123,35 @@ def method_missing(*args, &block) to_str.send(*args, &block) end end + + class MimeTypeEntry + attr_reader :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] + end + + def accepts?(entry) + File.fnmatch(entry, self) && matches_params?(entry.params) + end + + def to_str + @type + end + + def matches_params?(params) + return true if @params.empty? + + params.all? { |k,v| !@params.has_key?(k) || @params[k] == v } + end + end end # The response object. See Rack::Response and Rack::Response::Helpers for diff --git a/test/request_test.rb b/test/request_test.rb index 5c97b0280e..bbe36dd168 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -102,4 +102,24 @@ class RequestTest < Minitest::Test request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/json' assert !request.accept?('text/html') end + + it 'will accept types that fulfill HTTP_ACCEPT parameters' do + request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/rss+xml; version="http://purl.org/rss/1.0/"' + + assert request.accept?('application/rss+xml; version="http://purl.org/rss/1.0/"') + assert request.accept?('application/rss+xml; version="http://purl.org/rss/1.0/"; charset=utf-8') + assert !request.accept?('application/rss+xml; version="https://cyber.harvard.edu/rss/rss.html"') + end + + it 'will accept more generic types that include HTTP_ACCEPT parameters' do + request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/rss+xml; charset=utf-8; version="http://purl.org/rss/1.0/"' + + assert request.accept?('application/rss+xml') + assert request.accept?('application/rss+xml; version="http://purl.org/rss/1.0/"') + end + + it 'will accept types matching HTTP_ACCEPT when parameters in arbitrary order' do + request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/rss+xml; charset=utf-8; version="http://purl.org/rss/1.0/"' + assert request.accept?('application/rss+xml; version="http://purl.org/rss/1.0/"; charset=utf-8') + end end diff --git a/test/routing_test.rb b/test/routing_test.rb index 7bab661f9d..662198527e 100644 --- a/test/routing_test.rb +++ b/test/routing_test.rb @@ -1228,6 +1228,29 @@ class RoutingTest < Minitest::Test assert_body 'text/xml;charset=utf-8' end + it 'matches content-type to mime_type' do + mime_type = 'application/rss+xml;version="http://purl.org/rss/1.0/"' + mock_app do + configure { mime_type(:rss10, mime_type) } + get('/', :provides => [:rss10]) { content_type } + end + + get '/', {}, { 'HTTP_ACCEPT' => 'application/rss+xml' } + assert ok? + assert_body mime_type + end + + it 'handles missing mime_types with 404' do + mock_app do + configure { mime_type(:rss10, 'application/rss+xml') } + get('/', :provides => [:jpg]) { content_type } + end + + get '/', {}, { 'HTTP_ACCEPT' => 'application/rss+xml' } + assert_equal 404, status + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + it 'passes a single url param as block parameters when one param is specified' do mock_app { get '/:foo' do |foo|