http-accept 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +54 -45
- data/lib/http/accept.rb +3 -0
- data/lib/http/accept/charsets.rb +89 -0
- data/lib/http/accept/encodings.rb +94 -0
- data/lib/http/accept/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b3754be244c82ac53b2ab75fcec39f8d34400fc
|
4
|
+
data.tar.gz: ce62525b629b972c86c0073d567fba3e1690f7d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
[](http://travis-ci.org/ioquatix/http-accept)
|
8
6
|
[](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
|
-
|
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
|
-
|
41
|
+
```ruby
|
42
|
+
media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1")
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
85
|
+
```ruby
|
86
|
+
languages = HTTP::Accept::Languages.parse("da, en-gb;q=0.8, en;q=0.7")
|
82
87
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
data/lib/http/accept.rb
CHANGED
@@ -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
|
data/lib/http/accept/version.rb
CHANGED
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.
|
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:
|
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.
|
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.
|