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 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.