http-accept-1.7.0/0000755000175000017500000000000013777142315012745 5ustar lucaslucashttp-accept-1.7.0/Gemfile0000644000175000017500000000024613777142315014242 0ustar lucaslucassource 'https://rubygems.org' # Specify your gem's dependencies in http-accept.gemspec gemspec group :test do gem 'simplecov' gem 'coveralls', require: false end http-accept-1.7.0/http-accept.gemspec0000644000175000017500000000160313777142315016526 0ustar lucaslucas# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'http/accept/version' Gem::Specification.new do |spec| spec.name = "http-accept" spec.version = HTTP::Accept::VERSION spec.authors = ["Samuel Williams"] spec.email = ["samuel.williams@oriontransfer.co.nz"] spec.summary = %q{Parse Accept and Accept-Language HTTP headers.} spec.homepage = "https://github.com/ioquatix/http-accept" spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.add_development_dependency "bundler", "~> 1.11" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" end http-accept-1.7.0/Rakefile0000644000175000017500000000030413777142315014407 0ustar lucaslucasrequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |task| task.rspec_opts = ["--require", "simplecov"] if ENV['COVERAGE'] end task :default => :spec http-accept-1.7.0/lib/0000755000175000017500000000000013777142315013513 5ustar lucaslucashttp-accept-1.7.0/lib/http/0000755000175000017500000000000013777142315014472 5ustar lucaslucashttp-accept-1.7.0/lib/http/accept/0000755000175000017500000000000013777142315015731 5ustar lucaslucashttp-accept-1.7.0/lib/http/accept/parse_error.rb0000644000175000017500000000227213777142315020604 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module HTTP module Accept class ParseError < ArgumentError end end end http-accept-1.7.0/lib/http/accept/quoted_string.rb0000644000175000017500000000374313777142315021154 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module HTTP module Accept # According to https://tools.ietf.org/html/rfc7231#appendix-C TOKEN = /[!#$%&'*+\-.^_`|~0-9A-Z]+/i QUOTED_STRING = /"(?:.(?!(? # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'strscan' require_relative 'parse_error' require_relative 'sort' module HTTP module Accept module Languages # https://tools.ietf.org/html/rfc3066#section-2.1 LOCALE = /\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i # https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9 QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/ # https://greenbytes.de/tech/webdav/rfc7231.html#quality.values LANGUAGE_RANGE = /(?#{LOCALE})(\s*;\s*q=(?#{QVALUE}))?/ # Provides an efficient data-structure for matching the Accept-Languages header to set of available locales according to https://tools.ietf.org/html/rfc7231#section-5.3.5 and https://tools.ietf.org/html/rfc4647#section-2.3 class Locales def self.expand(locale, into) parts = locale.split('-') while parts.size > 0 key = parts.join('-') into[key] ||= locale parts.pop end end def initialize(names) @names = names @patterns = {} @names.each{|name| self.class.expand(name, @patterns)} self.freeze end def freeze @names.freeze @patterns.freeze super end def each(&block) return to_enum unless block_given? @names.each(&block) end attr :names attr :patterns # Returns the intersection of others retaining order. def & languages languages.collect{|language_range| @patterns[language_range.locale]}.compact end def include? locale_name @patterns.include? locale_name end def join(*args) @names.join(*args) end def + others self.class.new(@names + others.to_a) end def to_a @names end end LanguageRange = Struct.new(:locale, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(LANGUAGE_RANGE) yield self.new(scanner[:locale], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new("Could not parse entire string!") unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) languages = LanguageRange.parse(scanner) return Sort.by_quality_factor(languages) end end end end http-accept-1.7.0/lib/http/accept/content_type.rb0000644000175000017500000000311013777142315020764 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require_relative 'media_types' require_relative 'quoted_string' module HTTP module Accept # A content type is different from a media range, in that a content type should not have any wild cards. class ContentType < MediaTypes::MediaRange def initialize(mime_type, parameters = {}) # We do some basic validation here: raise ArgumentError.new("#{self.class} can not have wildcards: #{mime_type}") if mime_type.include? '*' super end end end end http-accept-1.7.0/lib/http/accept/media_types.rb0000644000175000017500000001046213777142315020564 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'strscan' require_relative 'parse_error' require_relative 'quoted_string' require_relative 'sort' require_relative 'media_types/map' module HTTP module Accept # Parse and process the HTTP Accept: header. module MediaTypes # According to https://tools.ietf.org/html/rfc7231#section-5.3.2 MIME_TYPE = /(#{TOKEN})\/(#{TOKEN})/ PARAMETER = /\s*;\s*(?#{TOKEN})=((?#{TOKEN})|(?#{QUOTED_STRING}))/ # A single entry in the Accept: header, which includes a mime type and associated parameters. MediaRange = Struct.new(:mime_type, :parameters) do def parameters_string return '' if parameters == nil or parameters.empty? parameters.collect do |key, value| "; #{key.to_s}=#{QuotedString.quote(value.to_s)}" end.join end def === other if other.is_a? self.class super else return self.mime_type === other end end def to_s "#{mime_type}#{parameters_string}" end alias to_str to_s def quality_factor parameters.fetch('q', 1.0).to_f end def split(on = '/', count = 2) mime_type.split(on, count) end def self.parse_parameters(scanner, normalize_whitespace) parameters = {} while scanner.scan(PARAMETER) key = scanner[:key] # If the regular expression PARAMETER matched, it must be one of these two: if value = scanner[:value] parameters[key] = value elsif quoted_value = scanner[:quoted_value] parameters[key] = QuotedString.unquote(quoted_value, normalize_whitespace) end end return parameters end def self.parse(scanner, normalize_whitespace = true) return to_enum(:parse, scanner, normalize_whitespace) unless block_given? while mime_type = scanner.scan(MIME_TYPE) parameters = parse_parameters(scanner, normalize_whitespace) yield self.new(mime_type, parameters) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new("Could not parse entire string!") unless scanner.eos? end end def self.parse(text, normalize_whitespace = true) scanner = StringScanner.new(text) media_types = MediaRange.parse(scanner, normalize_whitespace) return Sort.by_quality_factor(media_types) end HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze WILDCARD_MEDIA_RANGE = MediaRange.new("*/*", {}).freeze # Parse the list of browser preferred content types and return ordered by priority. If no `Accept:` header is specified, the behaviour is the same as if `Accept: */*` was provided (according to RFC). def self.browser_preferred_media_types(env) if accept_content_types = env[HTTP_ACCEPT] accept_content_types.strip! unless accept_content_types.empty? return HTTP::Accept::MediaTypes.parse(accept_content_types) end end # According to http://tools.ietf.org/html/rfc7231#section-5.3.2: # A request without any Accept header field implies that the user agent will accept any media type in response. # You should treat a non-existent Accept header as */*. return [WILDCARD_MEDIA_RANGE] end end end end http-accept-1.7.0/lib/http/accept/encodings.rb0000644000175000017500000000655413777142315020241 0ustar lucaslucas# Copyright (C) 2016, Matthew Kerwin # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'strscan' require_relative 'parse_error' require_relative 'quoted_string' require_relative 'sort' module HTTP module Accept module Encodings # https://tools.ietf.org/html/rfc7231#section-5.3.4 CONTENT_CODING = TOKEN # https://tools.ietf.org/html/rfc7231#section-5.3.1 QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/ CODINGS = /(?#{CONTENT_CODING})(;q=(?#{QVALUE}))?/ ContentCoding = Struct.new(:encoding, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(CODINGS) yield self.new(scanner[:encoding], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new('Could not parse entire string!') unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) encodings = ContentCoding.parse(scanner) return Sort.by_quality_factor(encodings) end HTTP_ACCEPT_ENCODING = 'HTTP_ACCEPT_ENCODING'.freeze WILDCARD_CONTENT_CODING = ContentCoding.new('*', nil).freeze IDENTITY_CONTENT_CODING = ContentCoding.new('identity', nil).freeze # Parse the list of browser preferred content codings and return ordered by priority. If no # `Accept-Encoding:` header is specified, the behaviour is the same as if # `Accept-Encoding: *` was provided, and if a blank `Accept-Encoding:` header value is # specified, the behaviour is the same as if `Accept-Encoding: identity` was provided # (according to RFC). def self.browser_preferred_content_codings(env) if accept_content_codings = env[HTTP_ACCEPT_ENCODING] accept_content_codings.strip! if accept_content_codings.empty? # "An Accept-Encoding header field with a combined field-value that is # empty implies that the user agent does not want any content-coding in # response." return [IDENTITY_CONTENT_CODING] else return HTTP::Accept::Encodings.parse(accept_content_codings) end end # "If no Accept-Encoding field is in the request, any content-coding # is considered acceptable by the user agent." return [WILDCARD_CONTENT_CODING] end end end end http-accept-1.7.0/lib/http/accept/version.rb0000644000175000017500000000224513777142315017746 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module HTTP module Accept VERSION = "1.7.0" end end http-accept-1.7.0/lib/http/accept/charsets.rb0000644000175000017500000000572613777142315020104 0ustar lucaslucas# Copyright (C) 2016, Matthew Kerwin # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require 'strscan' require_relative 'parse_error' require_relative 'quoted_string' require_relative 'sort' module HTTP module Accept module Charsets # https://tools.ietf.org/html/rfc7231#section-5.3.1 QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/ # https://tools.ietf.org/html/rfc7231#section-5.3.3 CHARSETS = /(?#{TOKEN})(;q=(?#{QVALUE}))?/ Charset = Struct.new(:charset, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(CHARSETS) yield self.new(scanner[:charset], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new('Could not parse entire string!') unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) charsets = Charset.parse(scanner) return Sort.by_quality_factor(charsets) end HTTP_ACCEPT_CHARSET = 'HTTP_ACCEPT_CHARSET'.freeze WILDCARD_CHARSET = Charset.new('*', nil).freeze # Parse the list of browser preferred charsets and return ordered by priority. def self.browser_preferred_charsets(env) if accept_charsets = env[HTTP_ACCEPT_CHARSET] accept_charsets.strip! if accept_charsets.empty? # https://tools.ietf.org/html/rfc7231#section-5.3.3 : # # Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) # # Because of the `1#` rule, an empty header value is not considered valid. raise ParseError.new('Could not parse entire string!') else return HTTP::Accept::Charsets.parse(accept_charsets) end end # "A request without any Accept-Charset header field implies that the # user agent will accept any charset in response." return [WILDCARD_CHARSET] end end end end http-accept-1.7.0/lib/http/accept/media_types/0000755000175000017500000000000013777142315020234 5ustar lucaslucashttp-accept-1.7.0/lib/http/accept/media_types/map.rb0000644000175000017500000000540213777142315021337 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module HTTP module Accept module MediaTypes # Map a set of mime types to objects. class Map WILDCARD = "*/*".freeze def initialize @media_types = {} end def freeze unless frozen? @media_types.freeze @media_types.each{|key,value| value.freeze} super end end # Given a list of content types (e.g. from browser_preferred_content_types), return the best converter. Media types can be an array of MediaRange or String values. def for(media_types) media_types.each do |media_range| mime_type = case media_range when String then media_range else media_range.mime_type end if object = @media_types[mime_type] return object, media_range end end return nil end def []= media_range, object @media_types[media_range] = object end def [] media_range @media_types[media_range] end # Add a converter to the collection. A converter can be anything that responds to #content_type. Objects will be considered in the order they are added, subsequent objects cannot override previously defined media types. `object` must respond to #split('/', 2) which should give the type and subtype. def << object type, subtype = object.split('/', 2) # We set the default if not specified already: @media_types[WILDCARD] = object if @media_types.empty? if type != '*' @media_types["#{type}/*"] ||= object if subtype != '*' @media_types["#{type}/#{subtype}"] ||= object end end return self end end end end end http-accept-1.7.0/lib/http/accept/sort.rb0000644000175000017500000000267313777142315017255 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. module HTTP module Accept module Sort # This sorts items with higher priority first, and keeps items with the same priority in the same relative order. def self.by_quality_factor(items) # We do this to get a stable sort: items.sort_by.with_index{|object, index| [-object.quality_factor, index]} end end end end http-accept-1.7.0/lib/http/accept.rb0000644000175000017500000000266013777142315016262 0ustar lucaslucas# Copyright, 2016, by Samuel G. D. Williams. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require "http/accept/version" require_relative 'accept/version' # Accept: header require_relative 'accept/media_types' require_relative 'accept/content_type' # Accept-Encoding: header require_relative 'accept/encodings' # Accept-Language: header require_relative 'accept/languages' module HTTP module Accept end end http-accept-1.7.0/README.md0000644000175000017500000001230413777142315014224 0ustar lucaslucas# HTTP::Accept Provides a robust set of parsers for dealing with HTTP `Accept`, `Accept-Language`, `Accept-Encoding`, `Accept-Charset` headers. [![Build Status](https://secure.travis-ci.org/ioquatix/http-accept.svg)](http://travis-ci.org/ioquatix/http-accept) [![Code Climate](https://codeclimate.com/github/ioquatix/http-accept.svg)](https://codeclimate.com/github/ioquatix/http-accept) [![Coverage Status](https://coveralls.io/repos/ioquatix/http-accept/badge.svg)](https://coveralls.io/r/ioquatix/http-accept) ## Motivation I've been [developing some tools for building RESTful endpoints](https://github.com/ioquatix/utopia/blob/master/lib/utopia/controller/respond.rb) and part of that involved versioning. After reviewing the options, I settled on using the `Accept: application/json;version=1` method [as outlined here](http://labs.qandidate.com/blog/2014/10/16/using-the-accept-header-to-version-your-api/). The `version=1` part of the `media-type` is a `parameter` as defined by [RFC7231 Section 3.1.1.1](https://tools.ietf.org/html/rfc7231#section-3.1.1.1). After reviewing several existing different options for parsing the `Accept:` header, I noticed a disturbing trend: `header.split(',')`. Because parameters may contain quoted strings which contain commas, this is clearly not an appropriate way to parse the header. I am concerned about correctness, security and performance. As such, I implemented this gem to provide a simple high level interface for both parsing and correctly interpreting these headers. ## Installation Add this line to your application's Gemfile: ```ruby gem 'http-accept' ``` And then execute: $ bundle Or install it yourself as: $ gem install http-accept ## Usage Here are some examples of how to parse various headers. ### Parsing Accept: headers You can parse the incoming `Accept:` header: ```ruby media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1") expect(media_types[0].mime_type).to be == "application/json" expect(media_types[0].parameters).to be == {'version' => '1'} expect(media_types[1].mime_type).to be == "text/html" expect(media_types[1].parameters).to be == {'q' => '0.5'} ``` Normally, you'd want to match the media types against some set of available mime types: ```ruby module ToJSON def content_type HTTP::Accept::ContentType.new("application/json", charset: 'utf-8') end # Used for inserting into map. def split(*args) content_type.split(*args) end def convert(object, options) object.to_json end end module ToXML # Are you kidding? end map = HTTP::Accept::MediaTypes::Map.new map << ToJSON map << ToXML object, media_range = map.for(media_types) content = object.convert(model, media_range.parameters) response = [200, {'Content-Type' => object.content_type}, [content]] ``` ### Parsing Accept-Language: headers You can parse the incoming `Accept-Language:` header: ```ruby languages = HTTP::Accept::Languages.parse("da, en-gb;q=0.8, en;q=0.7") expect(languages[0].locale).to be == "da" expect(languages[1].locale).to be == "en-gb" expect(languages[2].locale).to be == "en" ``` Normally, you'd want to match the languages against some set of available localizations: ```ruby available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"]) # Given the languages that the user wants, and the localizations available, compute the set of desired localizations. desired_localizations = available_localizations & languages ``` The `desired_localizations` in the example above is a subset of `available_localizations`. `HTTP::Accept::Languages::Locales` provides an efficient data-structure for matching the Accept-Languages header to set of available localizations according to https://tools.ietf.org/html/rfc7231#section-5.3.5 and https://tools.ietf.org/html/rfc4647#section-2.3 ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## License Released under the MIT license. Copyright, 2016, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). Copyright, 2016, by [Matthew Kerwin](http://kerwin.net.au). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http-accept-1.7.0/.travis.yml0000644000175000017500000000007513777142315015060 0ustar lucaslucaslanguage: ruby sudo: false env: COVERAGE=true rvm: - 2.3.0 http-accept-1.7.0/.simplecov0000644000175000017500000000015113777142315014744 0ustar lucaslucas SimpleCov.start do add_filter "/spec/" end if ENV['TRAVIS'] require 'coveralls' Coveralls.wear! end http-accept-1.7.0/.rspec0000644000175000017500000000006513777142315014063 0ustar lucaslucas--color --format documentation --backtrace --warningshttp-accept-1.7.0/.gitignore0000644000175000017500000000012713777142315014735 0ustar lucaslucas/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/