http-accept 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7447d3a4ffac6d5fe7dfe4343e3a5311ae23cbd
4
- data.tar.gz: 76e13950c937a706f2d5e5ddc46ae45120fa3057
3
+ metadata.gz: 3b3754be244c82ac53b2ab75fcec39f8d34400fc
4
+ data.tar.gz: ce62525b629b972c86c0073d567fba3e1690f7d1
5
5
  SHA512:
6
- metadata.gz: 9a606a9271cc042d8b39be14e46b1bb7de7dbf4c07e40b3c12493e81995b9f42852c5b1962ecafc0f181ea38fa9eb8f6a84764e8de34507f462062b0c25bd4d5
7
- data.tar.gz: b9a4904c398b22526717c8463d1d6d9cc79e43cf11681bb9cc73901d8909ba3ef45610b4e9879c0913597cd58f0388d88e91b3167b9113136181a377ce207a25
6
+ metadata.gz: 50a22597751d2dd3ed17ca4da33badae5c15d9941794763aaa199bdaff055b3edd6098cdc7ee6d932e7785069f57473ae054d6cef0a538703de6a245e949d841
7
+ data.tar.gz: 3955b2cf2ce082c40fbace1a98fba58e62d01efb92f1f5e4f6e07b4ea87fbe79958d0fe7047a90ae871d7b6c5ceb34483bf676e66fa3615cc34bd43e08169da0
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # HTTP::Accept
2
2
 
3
- Provides a robust set of parsers for dealing with Accept and Accept-Language HTTP headers.
4
-
5
- Current `Accept-Encoding:` and `Accept-Charset:` are not supported. This is because they are not particularly useful headers, but PRs are welcome.
3
+ Provides a robust set of parsers for dealing with HTTP `Accept`, `Accept-Language`, `Accept-Encoding`, `Accept-Charset` headers.
6
4
 
7
5
  [![Build Status](https://secure.travis-ci.org/ioquatix/http-accept.svg)](http://travis-ci.org/ioquatix/http-accept)
8
6
  [![Code Climate](https://codeclimate.com/github/ioquatix/http-accept.svg)](https://codeclimate.com/github/ioquatix/http-accept)
@@ -20,7 +18,9 @@ I am concerned about correctness, security and performance. As such, I implement
20
18
 
21
19
  Add this line to your application's Gemfile:
22
20
 
23
- gem 'http-accept'
21
+ ```ruby
22
+ gem 'http-accept'
23
+ ```
24
24
 
25
25
  And then execute:
26
26
 
@@ -38,59 +38,67 @@ Here are some examples of how to parse various headers.
38
38
 
39
39
  You can parse the incoming `Accept:` header:
40
40
 
41
- media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1")
41
+ ```ruby
42
+ media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1")
42
43
 
43
- expect(media_types[0].mime_type).to be == "application/json"
44
- expect(media_types[0].parameters).to be == {'version' => '1'}
45
- expect(media_types[1].mime_type).to be == "text/html"
46
- expect(media_types[1].parameters).to be == {'q' => '0.5'}
44
+ expect(media_types[0].mime_type).to be == "application/json"
45
+ expect(media_types[0].parameters).to be == {'version' => '1'}
46
+ expect(media_types[1].mime_type).to be == "text/html"
47
+ expect(media_types[1].parameters).to be == {'q' => '0.5'}
48
+ ```
47
49
 
48
50
  Normally, you'd want to match the media types against some set of available mime types:
49
51
 
50
- module ToJSON
51
- def content_type
52
- HTTP::Accept::ContentType.new("application/json", charset: 'utf-8')
53
- end
54
-
55
- # Used for inserting into map.
56
- def split(*args)
57
- content_type.split(*args)
58
- end
59
-
60
- def convert(object, options)
61
- object.to_json
62
- end
63
- end
64
-
65
- module ToXML
66
- # Are you kidding?
67
- end
68
-
69
- map = HTTP::Accept::MediaTypes::Map.new
70
- map << ToJSON
71
- map << ToXML
72
-
73
- object, media_range = map.for(media_types)
74
- content = object.convert(model, media_range.parameters)
75
- response = [200, {'Content-Type' => object.content_type}, [content]]
52
+ ```ruby
53
+ module ToJSON
54
+ def content_type
55
+ HTTP::Accept::ContentType.new("application/json", charset: 'utf-8')
56
+ end
57
+
58
+ # Used for inserting into map.
59
+ def split(*args)
60
+ content_type.split(*args)
61
+ end
62
+
63
+ def convert(object, options)
64
+ object.to_json
65
+ end
66
+ end
67
+
68
+ module ToXML
69
+ # Are you kidding?
70
+ end
71
+
72
+ map = HTTP::Accept::MediaTypes::Map.new
73
+ map << ToJSON
74
+ map << ToXML
75
+
76
+ object, media_range = map.for(media_types)
77
+ content = object.convert(model, media_range.parameters)
78
+ response = [200, {'Content-Type' => object.content_type}, [content]]
79
+ ```
76
80
 
77
81
  ### Parsing Accept-Language: headers
78
82
 
79
83
  You can parse the incoming `Accept-Language:` header:
80
84
 
81
- languages = HTTP::Accept::Language.parse("da, en-gb;q=0.8, en;q=0.7")
85
+ ```ruby
86
+ languages = HTTP::Accept::Languages.parse("da, en-gb;q=0.8, en;q=0.7")
82
87
 
83
- expect(languages[0].locale).to be == "da"
84
- expect(languages[1].locale).to be == "en-gb"
85
- expect(languages[2].locale).to be == "en"
88
+ expect(languages[0].locale).to be == "da"
89
+ expect(languages[1].locale).to be == "en-gb"
90
+ expect(languages[2].locale).to be == "en"
91
+ ```
86
92
 
87
93
  Normally, you'd want to match the languages against some set of available localizations:
88
94
 
89
- available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"])
90
-
91
- # Given the languages that the user wants, and the localizations available, compute the set of desired localizations.
92
- desired_localizations = available_localizations & languages
93
-
95
+ ```ruby
96
+ available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"])
97
+
98
+ # Given the languages that the user wants, and the localizations available, compute the set of desired localizations.
99
+ desired_localizations = available_localizations & languages
100
+ ```
101
+
94
102
  The `desired_localizations` in the example above is a subset of `available_localizations`.
95
103
 
96
104
  `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
@@ -107,7 +115,8 @@ The `desired_localizations` in the example above is a subset of `available_local
107
115
 
108
116
  Released under the MIT license.
109
117
 
110
- Copyright, 2016, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
118
+ Copyright, 2016, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
119
+ Copyright, 2016, by [Matthew Kerwin](http://kerwin.net.au).
111
120
 
112
121
  Permission is hereby granted, free of charge, to any person obtaining a copy
113
122
  of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +26,9 @@ require_relative 'accept/version'
26
26
  require_relative 'accept/media_types'
27
27
  require_relative 'accept/content_type'
28
28
 
29
+ # Accept-Encoding: header
30
+ require_relative 'accept/encodings'
31
+
29
32
  # Accept-Language: header
30
33
  require_relative 'accept/languages'
31
34
 
@@ -0,0 +1,89 @@
1
+ # Copyright (C) 2016, Matthew Kerwin <matthew@kerwin.net.au>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'strscan'
22
+
23
+ require_relative 'parse_error'
24
+ require_relative 'quoted_string'
25
+ require_relative 'sort'
26
+
27
+ module HTTP
28
+ module Accept
29
+ module Charsets
30
+ # https://tools.ietf.org/html/rfc7231#section-5.3.1
31
+ QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
32
+
33
+ # https://tools.ietf.org/html/rfc7231#section-5.3.3
34
+ CHARSETS = /(?<charset>#{TOKEN})(;q=(?<q>#{QVALUE}))?/
35
+
36
+ Charset = Struct.new(:charset, :q) do
37
+ def quality_factor
38
+ (q || 1.0).to_f
39
+ end
40
+
41
+ def self.parse(scanner)
42
+ return to_enum(:parse, scanner) unless block_given?
43
+
44
+ while scanner.scan(CHARSETS)
45
+ yield self.new(scanner[:charset], scanner[:q])
46
+
47
+ # Are there more?
48
+ break unless scanner.scan(/\s*,\s*/)
49
+ end
50
+
51
+ raise ParseError.new('Could not parse entire string!') unless scanner.eos?
52
+ end
53
+ end
54
+
55
+ def self.parse(text)
56
+ scanner = StringScanner.new(text)
57
+
58
+ charsets = Charset.parse(scanner)
59
+
60
+ return Sort.by_quality_factor(charsets)
61
+ end
62
+
63
+ HTTP_ACCEPT_CHARSET = 'HTTP_ACCEPT_CHARSET'.freeze
64
+ WILDCARD_CHARSET = Charset.new('*', nil).freeze
65
+
66
+ # Parse the list of browser preferred charsets and return ordered by priority.
67
+ def self.browser_preferred_charsets(env)
68
+ if accept_charsets = env[HTTP_ACCEPT_CHARSET]
69
+ accept_charsets.strip!
70
+
71
+ if accept_charsets.empty?
72
+ # https://tools.ietf.org/html/rfc7231#section-5.3.3 :
73
+ #
74
+ # Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
75
+ #
76
+ # Because of the `1#` rule, an empty header value is not considered valid.
77
+ raise ParseError.new('Could not parse entire string!')
78
+ else
79
+ return HTTP::Accept::Charsets.parse(accept_charsets)
80
+ end
81
+ end
82
+
83
+ # "A request without any Accept-Charset header field implies that the
84
+ # user agent will accept any charset in response."
85
+ return [WILDCARD_CHARSET]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,94 @@
1
+ # Copyright (C) 2016, Matthew Kerwin <matthew@kerwin.net.au>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'strscan'
22
+
23
+ require_relative 'parse_error'
24
+ require_relative 'quoted_string'
25
+ require_relative 'sort'
26
+
27
+ module HTTP
28
+ module Accept
29
+ module Encodings
30
+ # https://tools.ietf.org/html/rfc7231#section-5.3.4
31
+ CONTENT_CODING = TOKEN
32
+
33
+ # https://tools.ietf.org/html/rfc7231#section-5.3.1
34
+ QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
35
+
36
+ CODINGS = /(?<encoding>#{CONTENT_CODING})(;q=(?<q>#{QVALUE}))?/
37
+
38
+ ContentCoding = Struct.new(:encoding, :q) do
39
+ def quality_factor
40
+ (q || 1.0).to_f
41
+ end
42
+
43
+ def self.parse(scanner)
44
+ return to_enum(:parse, scanner) unless block_given?
45
+
46
+ while scanner.scan(CODINGS)
47
+ yield self.new(scanner[:encoding], scanner[:q])
48
+
49
+ # Are there more?
50
+ break unless scanner.scan(/\s*,\s*/)
51
+ end
52
+
53
+ raise ParseError.new('Could not parse entire string!') unless scanner.eos?
54
+ end
55
+ end
56
+
57
+ def self.parse(text)
58
+ scanner = StringScanner.new(text)
59
+
60
+ encodings = ContentCoding.parse(scanner)
61
+
62
+ return Sort.by_quality_factor(encodings)
63
+ end
64
+
65
+ HTTP_ACCEPT_ENCODING = 'HTTP_ACCEPT_ENCODING'.freeze
66
+ WILDCARD_CONTENT_CODING = ContentCoding.new('*', nil).freeze
67
+ IDENTITY_CONTENT_CODING = ContentCoding.new('identity', nil).freeze
68
+
69
+ # Parse the list of browser preferred content codings and return ordered by priority. If no
70
+ # `Accept-Encoding:` header is specified, the behaviour is the same as if
71
+ # `Accept-Encoding: *` was provided, and if a blank `Accept-Encoding:` header value is
72
+ # specified, the behaviour is the same as if `Accept-Encoding: identity` was provided
73
+ # (according to RFC).
74
+ def self.browser_preferred_content_codings(env)
75
+ if accept_content_codings = env[HTTP_ACCEPT_ENCODING]
76
+ accept_content_codings.strip!
77
+
78
+ if accept_content_codings.empty?
79
+ # "An Accept-Encoding header field with a combined field-value that is
80
+ # empty implies that the user agent does not want any content-coding in
81
+ # response."
82
+ return [IDENTITY_CONTENT_CODING]
83
+ else
84
+ return HTTP::Accept::Encodings.parse(accept_content_codings)
85
+ end
86
+ end
87
+
88
+ # "If no Accept-Encoding field is in the request, any content-coding
89
+ # is considered acceptable by the user agent."
90
+ return [WILDCARD_CONTENT_CODING]
91
+ end
92
+ end
93
+ end
94
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module HTTP
22
22
  module Accept
23
- VERSION = "1.6.0"
23
+ VERSION = "1.7.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http-accept
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-29 00:00:00.000000000 Z
11
+ date: 2017-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -68,7 +68,9 @@ files:
68
68
  - Rakefile
69
69
  - http-accept.gemspec
70
70
  - lib/http/accept.rb
71
+ - lib/http/accept/charsets.rb
71
72
  - lib/http/accept/content_type.rb
73
+ - lib/http/accept/encodings.rb
72
74
  - lib/http/accept/languages.rb
73
75
  - lib/http/accept/media_types.rb
74
76
  - lib/http/accept/media_types/map.rb
@@ -95,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
97
  version: '0'
96
98
  requirements: []
97
99
  rubyforge_project:
98
- rubygems_version: 2.5.1
100
+ rubygems_version: 2.6.10
99
101
  signing_key:
100
102
  specification_version: 4
101
103
  summary: Parse Accept and Accept-Language HTTP headers.