accept_headers 0.0.1

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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
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
@@ -0,0 +1,3 @@
1
+ ## 0.1 / November 14, 2014
2
+
3
+ * Initial release
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,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task default: :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "spec"
8
+ t.test_files = FileList['spec/*_spec.rb']
9
+ t.verbose = true
10
+ end
@@ -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,8 @@
1
+ require "accept_headers/version"
2
+ require "accept_headers/charset"
3
+ require "accept_headers/encoding"
4
+ require "accept_headers/media_type"
5
+ require "accept_headers/language"
6
+
7
+ module AcceptHeaders
8
+ 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