accept_headers 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +16 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +117 -0
- data/Rakefile +10 -0
- data/accept_headers.gemspec +27 -0
- data/lib/accept_headers.rb +8 -0
- data/lib/accept_headers/acceptable.rb +51 -0
- data/lib/accept_headers/charset.rb +53 -0
- data/lib/accept_headers/encoding.rb +53 -0
- data/lib/accept_headers/language.rb +83 -0
- data/lib/accept_headers/media_type.rb +114 -0
- data/lib/accept_headers/version.rb +3 -0
- data/spec/charset_spec.rb +126 -0
- data/spec/encoding_spec.rb +126 -0
- data/spec/language_spec.rb +149 -0
- data/spec/media_type_spec.rb +173 -0
- data/spec/spec_helper.rb +21 -0
- data/wercker.yml +27 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3e4d8ab6edced3cc033d6a693d51c7cbd9fee32d
|
4
|
+
data.tar.gz: 2fe2cf946fe203666424c666fb9d00e1fb3d9eb0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e9dc99f82ca6f752b2f6d562ad63c93aa8e32527a41910e0935d58137349d47b82a1ed2f69bb7b4c52f030281e551dba84a2dd3f49c5e93e1c890b2d77161002
|
7
|
+
data.tar.gz: 92c33af011978e0dbbaa9d88d0c060368edb969dfea3e73e072754e4ae4d1c634643f6ef6a834b22c45ad46194be7cd313e073a57188b6ac74eff41d5d0a8487
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.0.0
|
4
|
+
- 2.1.5
|
5
|
+
- rbx
|
6
|
+
- ruby-head
|
7
|
+
- jruby-head
|
8
|
+
|
9
|
+
matrix:
|
10
|
+
allow_failures:
|
11
|
+
- rvm: ruby-head
|
12
|
+
- rvm: jruby-head
|
13
|
+
- rvm: rbx
|
14
|
+
|
15
|
+
addons:
|
16
|
+
code_climate:
|
17
|
+
repo_token: 50c636c924d1aa253e29c57822931c1a172cd65675ad66396bba39d255373a58
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in retriable.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem "ruby_gntp"
|
8
|
+
gem "minitest-focus"
|
9
|
+
gem "codeclimate-test-reporter"
|
10
|
+
gem "simplecov"
|
11
|
+
end
|
12
|
+
|
13
|
+
group :development, :test do
|
14
|
+
gem "pry"
|
15
|
+
gem "awesome_print"
|
16
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :minitest do
|
5
|
+
watch(%r{^spec/(.*)_spec\.rb$})
|
6
|
+
watch(%r{^lib/accept_headers/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
|
8
|
+
watch(%r{^lib/accept_headers/acceptable\.rb$}) { 'spec' }
|
9
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jack Chu
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/kamui/accept_headers.png)](https://travis-ci.org/kamui/accept_headers)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/kamui/accept_headers/badges/gpa.svg)](https://codeclimate.com/github/kamui/accept_headers)
|
3
|
+
[![Test Coverage](https://codeclimate.com/github/kamui/accept_headers/badges/coverage.svg)](https://codeclimate.com/github/kamui/accept_headers)
|
4
|
+
|
5
|
+
# AcceptHeaders
|
6
|
+
|
7
|
+
**AcceptHeaders** is a ruby library that parses and sorts http accept headers.
|
8
|
+
|
9
|
+
Some features of the library are:
|
10
|
+
|
11
|
+
* Strict adherence to [RFC 2616][rfc], specifically [section 14][rfc-sec14]
|
12
|
+
* Full support for the [Accept][rfc-sec14-1], [Accept-Charset][rfc-sec14-2],
|
13
|
+
[Accept-Encoding][rfc-sec14-3], and [Accept-Language][rfc-sec14-4] HTTP
|
14
|
+
request headers
|
15
|
+
* A comprehensive [spec suite][spec] that covers many edge cases
|
16
|
+
|
17
|
+
[rfc]: http://www.w3.org/Protocols/rfc2616/rfc2616.html
|
18
|
+
[rfc-sec14]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
19
|
+
[rfc-sec14-1]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
|
20
|
+
[rfc-sec14-2]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
|
21
|
+
[rfc-sec14-3]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
|
22
|
+
[rfc-sec14-4]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
23
|
+
[spec]: http://github.com/kamui/accept_headers/tree/master/spec/
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add this line to your application's Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'accept_headers'
|
31
|
+
```
|
32
|
+
|
33
|
+
And then execute:
|
34
|
+
|
35
|
+
$ bundle
|
36
|
+
|
37
|
+
Or install it yourself as:
|
38
|
+
|
39
|
+
$ gem install accept_headers
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
`AcceptHeaders` can parse the Accept Header and return an array of `MediaType`s in descending order according to the spec, which takes into account `q` value, `type`/`subtype` and `params` specificity.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
AcceptHeaders::MediaType.parse("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5")
|
47
|
+
```
|
48
|
+
|
49
|
+
Will generate this equivalent array:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
[
|
53
|
+
MediaType.new('text', 'html', params: { 'level' => '1' }),
|
54
|
+
MediaType.new('text', 'html', q: 0.7),
|
55
|
+
MediaType.new('*', '*', q: 0.5),
|
56
|
+
MediaType.new('text', 'html', q: 0.4, params: { 'level' => '2' }),
|
57
|
+
MediaType.new('text', '*', q: 0.3)
|
58
|
+
]
|
59
|
+
```
|
60
|
+
|
61
|
+
Parsing `Charset`:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
AcceptHeaders::Charset.parse("us-ascii; q=0.5, iso-8859-1, utf-8; q=0.8, macintosh")
|
65
|
+
```
|
66
|
+
|
67
|
+
generates:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
[
|
71
|
+
Charset.new('iso-8859-1'),
|
72
|
+
Charset.new('macintosh'),
|
73
|
+
Charset.new('utf-8', q: 0.8),
|
74
|
+
Charset.new('us-ascii', q: 0.5)
|
75
|
+
]
|
76
|
+
```
|
77
|
+
|
78
|
+
Parsing `Encoding`:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
AcceptHeaders::Encoding.parse("deflate; q=0.5, gzip, compress; q=0.8, identity")
|
82
|
+
```
|
83
|
+
|
84
|
+
generates:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
[
|
88
|
+
Encoding.new('gzip'),
|
89
|
+
Encoding.new('identity'),
|
90
|
+
Encoding.new('compress', q: 0.8),
|
91
|
+
Encoding.new('deflate', q: 0.5)
|
92
|
+
]
|
93
|
+
```
|
94
|
+
|
95
|
+
Parsing `Language`:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
AcceptHeaders::Language.parse("en-*, en-us, *;q=0.8")
|
99
|
+
```
|
100
|
+
|
101
|
+
generates:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
[
|
105
|
+
Language.new('en', 'us'),
|
106
|
+
Language.new('en', '*'),
|
107
|
+
Language.new('*', '*', q: 0.8)
|
108
|
+
]
|
109
|
+
```
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
1. Fork it ( https://github.com/[my-github-username]/accept_headers/fork )
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'accept_headers/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "accept_headers"
|
8
|
+
spec.version = AcceptHeaders::VERSION
|
9
|
+
spec.authors = ["Jack Chu"]
|
10
|
+
spec.email = ["kamuigt@gmail.com"]
|
11
|
+
spec.summary = %q{A ruby library that parses and sorts http accept headers.}
|
12
|
+
spec.description = %q{A ruby library that parses and sorts http accept headers. Adheres to RFC 2616.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "minitest", "~> 5.4"
|
25
|
+
spec.add_development_dependency "guard"
|
26
|
+
spec.add_development_dependency "guard-minitest"
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module AcceptHeaders
|
2
|
+
module Acceptable
|
3
|
+
class Error < StandardError; end
|
4
|
+
class OutOfRangeError < Error; end
|
5
|
+
class InvalidPrecisionError < Error; end
|
6
|
+
class InvalidQError < Error; end
|
7
|
+
class ParseError < Error; end
|
8
|
+
|
9
|
+
attr_reader :q
|
10
|
+
|
11
|
+
TOKEN_PATTERN = /^\s*(?<token>[\w!#$%^&*\-\+{}\\|'.`~]+)\s*$/
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
def q=(value)
|
18
|
+
begin
|
19
|
+
q_float = Float(value)
|
20
|
+
rescue ArgumentError => e
|
21
|
+
raise InvalidQError.new(e.message)
|
22
|
+
end
|
23
|
+
if !q_float.between?(0.0, 1.0)
|
24
|
+
raise OutOfRangeError.new("q must be between 0 and 1")
|
25
|
+
end
|
26
|
+
if q_float.to_s.match(/^\d\.(\d+)$/) && $1 && $1.size > 3
|
27
|
+
raise InvalidPrecisionError.new("q must be at most 3 decimal places")
|
28
|
+
end
|
29
|
+
@q = q_float
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
Q_PATTERN = /(?:\A|;)\s*(?<exists>qs*\=)\s*(?:(?<q>0\.\d{1,3}|[01])|(?:[^;]*))\s*(?:\z|;)/
|
34
|
+
|
35
|
+
private
|
36
|
+
def parse_q(header)
|
37
|
+
q = 1
|
38
|
+
return q unless header
|
39
|
+
q_match = Q_PATTERN.match(header)
|
40
|
+
if q_match && q_match[:exists]
|
41
|
+
if q_match[:q]
|
42
|
+
q = q_match[:q]
|
43
|
+
else
|
44
|
+
q = 0.001
|
45
|
+
end
|
46
|
+
end
|
47
|
+
q
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "accept_headers/acceptable"
|
2
|
+
|
3
|
+
module AcceptHeaders
|
4
|
+
class Charset
|
5
|
+
include Comparable
|
6
|
+
include Acceptable
|
7
|
+
|
8
|
+
class InvalidCharsetError < Error; end
|
9
|
+
|
10
|
+
attr_reader :charset
|
11
|
+
|
12
|
+
def initialize(charset = '*', q: 1.0)
|
13
|
+
self.charset = charset
|
14
|
+
self.q = q
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
q <=> other.q
|
19
|
+
end
|
20
|
+
|
21
|
+
def charset=(value)
|
22
|
+
@charset = value.strip.downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
charset: charset,
|
28
|
+
q: q
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
qvalue = (q == 0 || q == 1) ? q.to_i : q
|
34
|
+
"#{charset};q=#{qvalue}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse(original_header)
|
38
|
+
header = original_header.dup
|
39
|
+
header.sub!(/\AAccept-Charset:\s*/, '')
|
40
|
+
header.strip!
|
41
|
+
return [Charset.new('iso-8859-5', q: 1)] if header.empty?
|
42
|
+
charsets = []
|
43
|
+
header.split(',').each do |entry|
|
44
|
+
charset_arr = entry.split(';', 2)
|
45
|
+
next if charset_arr[0].nil?
|
46
|
+
charset = TOKEN_PATTERN.match(charset_arr[0])
|
47
|
+
next if charset.nil?
|
48
|
+
charsets << Charset.new(charset[:token], q: parse_q(charset_arr[1]))
|
49
|
+
end
|
50
|
+
charsets.sort! { |x,y| y <=> x }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "accept_headers/acceptable"
|
2
|
+
|
3
|
+
module AcceptHeaders
|
4
|
+
class Encoding
|
5
|
+
include Comparable
|
6
|
+
include Acceptable
|
7
|
+
|
8
|
+
class InvalidEncodingError < Error; end
|
9
|
+
|
10
|
+
attr_reader :encoding
|
11
|
+
|
12
|
+
def initialize(encoding = '*', q: 1.0)
|
13
|
+
self.encoding = encoding
|
14
|
+
self.q = q
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
q <=> other.q
|
19
|
+
end
|
20
|
+
|
21
|
+
def encoding=(value)
|
22
|
+
@encoding = value.strip.downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
encoding: encoding,
|
28
|
+
q: q
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
qvalue = (q == 0 || q == 1) ? q.to_i : q
|
34
|
+
"#{encoding};q=#{qvalue}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse(original_header)
|
38
|
+
header = original_header.dup
|
39
|
+
header.sub!(/\AAccept-Encoding:\s*/, '')
|
40
|
+
header.strip!
|
41
|
+
return [Charset.new] if header.empty?
|
42
|
+
encodings = []
|
43
|
+
header.split(',').each do |entry|
|
44
|
+
encoding_arr = entry.split(';', 2)
|
45
|
+
next if encoding_arr[0].nil?
|
46
|
+
encoding = TOKEN_PATTERN.match(encoding_arr[0])
|
47
|
+
next if encoding.nil?
|
48
|
+
encodings << Encoding.new(encoding[:token], q: parse_q(encoding_arr[1]))
|
49
|
+
end
|
50
|
+
encodings.sort! { |x,y| y <=> x }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "accept_headers/acceptable"
|
2
|
+
|
3
|
+
module AcceptHeaders
|
4
|
+
class Language
|
5
|
+
include Comparable
|
6
|
+
include Acceptable
|
7
|
+
|
8
|
+
class InvalidLanguageTagError < Error; end
|
9
|
+
|
10
|
+
attr_reader :primary_tag, :subtag, :params
|
11
|
+
|
12
|
+
def initialize(primary_tag = '*', subtag = nil, q: 1.0)
|
13
|
+
self.primary_tag = primary_tag
|
14
|
+
self.subtag = subtag
|
15
|
+
self.q = q
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=>(other)
|
19
|
+
if q < other.q
|
20
|
+
-1
|
21
|
+
elsif q > other.q
|
22
|
+
1
|
23
|
+
elsif (primary_tag == '*' && other.primary_tag != '*') || (subtag == '*' && other.subtag != '*')
|
24
|
+
-1
|
25
|
+
elsif (primary_tag != '*' && other.primary_tag == '*') || (subtag != '*' && other.subtag == '*')
|
26
|
+
1
|
27
|
+
else
|
28
|
+
0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def primary_tag=(value)
|
33
|
+
@primary_tag = value.strip.downcase
|
34
|
+
end
|
35
|
+
|
36
|
+
def subtag=(value)
|
37
|
+
@subtag = if value.nil?
|
38
|
+
'*'
|
39
|
+
else
|
40
|
+
value.strip.downcase
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
{
|
46
|
+
primary_tag: primary_tag,
|
47
|
+
subtag: subtag,
|
48
|
+
q: q
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
qvalue = (q == 0 || q == 1) ? q.to_i : q
|
54
|
+
"#{primary_tag}-#{subtag};q=#{qvalue}"
|
55
|
+
end
|
56
|
+
|
57
|
+
LANGUAGE_PATTERN = /^\s*(?<primary_tag>[\w]{1,8}|\*)(?:\s*\-\s*(?<subtag>[\w]{1,8}|\*))?\s*$/
|
58
|
+
|
59
|
+
def self.parse(original_header)
|
60
|
+
header = original_header.dup
|
61
|
+
header.sub!(/\AAccept-Language:\s*/, '')
|
62
|
+
header.strip!
|
63
|
+
return [Language.new] if header.empty?
|
64
|
+
languages = []
|
65
|
+
header.split(',').each do |entry|
|
66
|
+
language_arr = entry.split(';', 2)
|
67
|
+
next if language_arr[0].nil?
|
68
|
+
language_range = LANGUAGE_PATTERN.match(language_arr[0])
|
69
|
+
next if language_range.nil?
|
70
|
+
begin
|
71
|
+
languages << Language.new(
|
72
|
+
language_range[:primary_tag],
|
73
|
+
language_range[:subtag],
|
74
|
+
q: parse_q(language_arr[1])
|
75
|
+
)
|
76
|
+
rescue Error
|
77
|
+
next
|
78
|
+
end
|
79
|
+
end
|
80
|
+
languages.sort! { |x,y| y <=> x }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|