accept_headers 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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