From c9e99cf2cff201022337f03dfee1ff0063dcedb2 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 15 Feb 2013 17:16:45 +0100 Subject: [PATCH] Allow more than one type in Accept header --- lib/travis/api/app/helpers/accept.rb | 52 ++++++++++++++++++++++++++++ spec/unit/helpers/accept_spec.rb | 29 ++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 spec/unit/helpers/accept_spec.rb diff --git a/lib/travis/api/app/helpers/accept.rb b/lib/travis/api/app/helpers/accept.rb index ed29d328..b1ffc82b 100644 --- a/lib/travis/api/app/helpers/accept.rb +++ b/lib/travis/api/app/helpers/accept.rb @@ -7,6 +7,58 @@ class Travis::Api::App DEFAULT_VERSION = 'v1' DEFAULT_FORMAT = 'json' + class Entry + SEPARATORS = Regexp.escape("()<>@,;:\/[]?={}\t ") + TOKEN = /[^#{SEPARATORS}]+/ + attr_reader :type, :subtype, :quality, :params + def initialize(accept_string) + @type, @subtype, @quality, @params = parse(accept_string) + end + + def <=>(other) + [1 - quality, full_type.count('*'), 1 - params.size] <=> + [1 - other.quality, other.full_type.count('*'), 1 - other.params.size] + end + + def full_type + "#{type}/#{subtype}" + end + + def mime_type + subtype = self.subtype =~ HEADER_FORMAT ? $2 : self.subtype + "#{type}/#{subtype}" + end + + def version + $1 if subtype =~ HEADER_FORMAT + end + + def to_s + str = "#{full_type}; q=#{quality}" + str << "; #{params.map { |k,v| "#{k}=#{v}" }.join('; ')}" if params.length > 0 + str + end + + private + def parse(str) + # this handles only subset of what Accept header can + # contain, only the simplest cases, no quoted strings etc. + type, subtype, params = str.scan(%r{(#{TOKEN})/(#{TOKEN})(.*)}).flatten + quality = 1 + if params + params = Hash[*params.split(';').map { |p| p.scan /(#{TOKEN})=(#{TOKEN})/ }.flatten] + quality = params.delete('q').to_f if params['q'] + end + + [type, subtype, quality, params] + end + end + + def accept_entries + entries = env['HTTP_ACCEPT'].to_s.delete(' ').to_s.split(',').map { |e| Entry.new(e) } + entries.empty? ? [Entry.new('*/*')] : entries.sort + end + def accept_version @accept_version ||= request.accept.join =~ HEADER_FORMAT && "v#{$1}" || DEFAULT_VERSION end diff --git a/spec/unit/helpers/accept_spec.rb b/spec/unit/helpers/accept_spec.rb new file mode 100644 index 00000000..11f845ec --- /dev/null +++ b/spec/unit/helpers/accept_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +module Travis::Api::App::Helpers + describe Accept do + class FakeApp < Struct.new(:env) + include Accept + end + + it 'returns accept entries sorted properly' do + accept = "text/html; q=0.2; level=1, application/vnd.travis-ci.2+json, text/*, text/html;level=2; q=0.5" + FakeApp.new('HTTP_ACCEPT' => accept).accept_entries.map(&:to_s).should == + ["application/vnd.travis-ci.2+json; q=1", "text/*; q=1", "text/html; q=0.5; level=2", "text/html; q=0.2; level=1"] + end + + it 'properly parses params, quality and version' do + accept = "application/vnd.travis-ci.2+json; q=0.2; level=1; foo=bar" + accept_entry = FakeApp.new('HTTP_ACCEPT' => accept).accept_entries.first + accept_entry.quality.should == 0.2 + accept_entry.params.should == { 'level' => '1', 'foo' => 'bar' } + accept_entry.mime_type.should == 'application/json' + accept_entry.version.should == '2' + end + + it 'returns */* for empty accept header' do + accept_entry = FakeApp.new({}).accept_entries.first + accept_entry.mime_type.should == '*/*' + end + end +end