xenon 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +18 -0
- data/.gitignore +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +10 -0
- data/Guardfile +0 -32
- data/README.md +59 -5
- data/examples/hello_world/config.ru +3 -0
- data/examples/hello_world/hello_world.rb +17 -0
- data/lib/xenon.rb +62 -49
- data/lib/xenon/auth.rb +63 -0
- data/lib/xenon/errors.rb +1 -0
- data/lib/xenon/etag.rb +48 -0
- data/lib/xenon/headers.rb +2 -4
- data/lib/xenon/headers/accept.rb +3 -2
- data/lib/xenon/headers/accept_charset.rb +5 -5
- data/lib/xenon/headers/accept_encoding.rb +5 -5
- data/lib/xenon/headers/accept_language.rb +5 -5
- data/lib/xenon/headers/authorization.rb +7 -53
- data/lib/xenon/headers/cache_control.rb +3 -3
- data/lib/xenon/headers/content_type.rb +1 -1
- data/lib/xenon/headers/if_match.rb +53 -0
- data/lib/xenon/headers/if_modified_since.rb +22 -0
- data/lib/xenon/headers/if_none_match.rb +53 -0
- data/lib/xenon/headers/if_range.rb +45 -0
- data/lib/xenon/headers/if_unmodified_since.rb +22 -0
- data/lib/xenon/headers/user_agent.rb +65 -0
- data/lib/xenon/headers/www_authenticate.rb +70 -0
- data/lib/xenon/media_type.rb +24 -2
- data/lib/xenon/parsers/basic_rules.rb +38 -7
- data/lib/xenon/parsers/header_rules.rb +49 -3
- data/lib/xenon/parsers/media_type.rb +4 -3
- data/lib/xenon/quoted_string.rb +11 -1
- data/lib/xenon/routing/directives.rb +14 -0
- data/lib/xenon/routing/header_directives.rb +32 -0
- data/lib/xenon/routing/method_directives.rb +26 -0
- data/lib/xenon/routing/param_directives.rb +22 -0
- data/lib/xenon/routing/path_directives.rb +37 -0
- data/lib/xenon/routing/route_directives.rb +51 -0
- data/lib/xenon/routing/security_directives.rb +20 -0
- data/lib/xenon/version.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/xenon/etag_spec.rb +19 -0
- data/spec/xenon/headers/if_match_spec.rb +73 -0
- data/spec/xenon/headers/if_modified_since_spec.rb +19 -0
- data/spec/xenon/headers/if_none_match_spec.rb +79 -0
- data/spec/xenon/headers/if_range_spec.rb +45 -0
- data/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
- data/spec/xenon/headers/user_agent_spec.rb +67 -0
- data/spec/xenon/headers/www_authenticate_spec.rb +43 -0
- data/xenon.gemspec +4 -3
- metadata +60 -10
- data/lib/xenon/routing.rb +0 -133
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'xenon/routing/route_directives'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module MethodDirectives
|
6
|
+
include RouteDirectives
|
7
|
+
|
8
|
+
def request_method(method)
|
9
|
+
extract_request do |request|
|
10
|
+
if request.request_method == method
|
11
|
+
yield
|
12
|
+
else
|
13
|
+
reject Rejection.new(:method, { supported: method })
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
%i(delete get head options patch post put).each do |method|
|
19
|
+
define_method(method) do |&inner|
|
20
|
+
request_method(method, &inner)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'xenon/routing/route_directives'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module ParamDirectives
|
6
|
+
include RouteDirectives
|
7
|
+
|
8
|
+
def param_hash
|
9
|
+
extract_request do |request|
|
10
|
+
yield request.param_hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def params(*names)
|
15
|
+
param_hash do |hash|
|
16
|
+
yield *hash.slice(*names).values
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'xenon/routing/route_directives'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module PathDirectives
|
6
|
+
include RouteDirectives
|
7
|
+
|
8
|
+
def path_prefix(pattern)
|
9
|
+
extract_request do |request|
|
10
|
+
match = request.unmatched_path.match(pattern)
|
11
|
+
if match && match.pre_match == ''
|
12
|
+
map_request unmatched_path: match.post_match do
|
13
|
+
yield *match.captures
|
14
|
+
end
|
15
|
+
else
|
16
|
+
reject nil # path rejections are nil to allow more specific rejections to be seen
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def path_end
|
22
|
+
path_prefix(/\Z/) do
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def path(pattern)
|
28
|
+
path_prefix(pattern) do |*captures|
|
29
|
+
path_end do
|
30
|
+
yield *captures
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module RouteDirectives
|
6
|
+
|
7
|
+
def map_request(map)
|
8
|
+
context.branch do
|
9
|
+
context.request = map.respond_to?(:call) ? map.call(context.request) : context.request.copy(map)
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def map_response(map)
|
15
|
+
context.branch do
|
16
|
+
context.response = map.respond_to?(:call) ? map.call(context.response) : context.response.copy(map)
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def complete(status, body)
|
22
|
+
map_response complete: true, status: Rack::Utils.status_code(status), body: body do
|
23
|
+
throw :complete
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reject(rejection, info = {})
|
28
|
+
return if rejection.nil?
|
29
|
+
rejection = Rejection.new(rejection, info) unless rejection.is_a?(Rejection)
|
30
|
+
context.rejections << rejection
|
31
|
+
end
|
32
|
+
|
33
|
+
def fail(status, developer_message = nil)
|
34
|
+
body = {
|
35
|
+
status: status,
|
36
|
+
developer_message: developer_message || Rack::Utils::HTTP_STATUS_CODES[status]
|
37
|
+
}
|
38
|
+
complete status, body
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract(lambda)
|
42
|
+
yield lambda.call(context)
|
43
|
+
end
|
44
|
+
|
45
|
+
def extract_request(lambda = nil)
|
46
|
+
yield lambda ? lambda.call(context.request) : context.request
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'xenon/routing/route_directives'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module SecurityDirectives
|
6
|
+
include RouteDirectives
|
7
|
+
|
8
|
+
def authenticate(authenticator)
|
9
|
+
extract_request(authenticator) do |user|
|
10
|
+
if user
|
11
|
+
yield user
|
12
|
+
else
|
13
|
+
reject :unauthorized, { scheme: authenticator.scheme }.merge(authenticator.auth_params)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/xenon/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require "codeclimate-test-reporter"
|
2
|
+
CodeClimate::TestReporter.start
|
3
|
+
|
1
4
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
5
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
6
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'xenon/etag'
|
2
|
+
|
3
|
+
describe Xenon::ETag do
|
4
|
+
|
5
|
+
describe '::parse' do
|
6
|
+
it 'can parse a strong etag' do
|
7
|
+
etag = Xenon::ETag.parse('"xyzzy"')
|
8
|
+
expect(etag.opaque_tag).to eq 'xyzzy'
|
9
|
+
expect(etag).to be_strong
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can parse a weak etag' do
|
13
|
+
etag = Xenon::ETag.parse('W/"xyzzy"')
|
14
|
+
expect(etag.opaque_tag).to eq 'xyzzy'
|
15
|
+
expect(etag).to be_weak
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'xenon/headers/if_match'
|
2
|
+
|
3
|
+
describe Xenon::Headers::IfMatch do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse a single etag' do
|
7
|
+
header = Xenon::Headers::IfMatch.parse('"xyzzy"')
|
8
|
+
expect(header.etags.size).to eq(1)
|
9
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can parse multiple etags' do
|
13
|
+
header = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx", "c3piozzzz"')
|
14
|
+
expect(header.etags.size).to eq(3)
|
15
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
16
|
+
expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx'))
|
17
|
+
expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can parse a wildcard header' do
|
21
|
+
header = Xenon::Headers::IfMatch.parse('*')
|
22
|
+
expect(header.etags.size).to eq(0)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#merge' do
|
27
|
+
it 'can merge two headers and maintain etag order' do
|
28
|
+
h1 = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx"')
|
29
|
+
h2 = Xenon::Headers::IfMatch.parse('"c3piozzzz"')
|
30
|
+
header = h1.merge(h2)
|
31
|
+
expect(header.etags.size).to eq(3)
|
32
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
33
|
+
expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx'))
|
34
|
+
expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'raises a protocol error when trying to merge into a wildcard header' do
|
38
|
+
h1 = Xenon::Headers::IfMatch.parse('*')
|
39
|
+
h2 = Xenon::Headers::IfMatch.parse('"c3piozzzz"')
|
40
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises a protocol error when trying to merge a wildcard into a header' do
|
44
|
+
h1 = Xenon::Headers::IfMatch.parse('"xyzzy"')
|
45
|
+
h2 = Xenon::Headers::IfMatch.parse('*')
|
46
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises a protocol error when trying to merge two wildcard headers' do
|
50
|
+
h1 = Xenon::Headers::IfMatch.parse('*')
|
51
|
+
h2 = Xenon::Headers::IfMatch.parse('*')
|
52
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context '#to_s' do
|
57
|
+
it 'returns the string representation a single etag' do
|
58
|
+
header = Xenon::Headers::IfMatch.parse('"xyzzy"')
|
59
|
+
expect(header.to_s).to eq('"xyzzy"')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns the string representation of multiple etags' do
|
63
|
+
header = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx", "c3piozzzz"')
|
64
|
+
expect(header.to_s).to eq('"xyzzy", "r2d2xxxx", "c3piozzzz"')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns the string representation of a wildcard header' do
|
68
|
+
header = Xenon::Headers::IfMatch.wildcard
|
69
|
+
expect(header.to_s).to eq('*')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'xenon/headers/if_modified_since'
|
2
|
+
|
3
|
+
describe Xenon::Headers::IfModifiedSince do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse an http date' do
|
7
|
+
header = Xenon::Headers::IfModifiedSince.parse('Sat, 29 Oct 1994 19:43:31 GMT')
|
8
|
+
expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context '#to_s' do
|
13
|
+
it 'returns the http date format' do
|
14
|
+
header = Xenon::Headers::IfModifiedSince.new(Time.utc(1994, 10, 29, 19, 43, 31))
|
15
|
+
expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'xenon/headers/if_none_match'
|
2
|
+
|
3
|
+
describe Xenon::Headers::IfNoneMatch do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse a single strong etag' do
|
7
|
+
header = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
|
8
|
+
expect(header.etags.size).to eq(1)
|
9
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can parse a single weak etag' do
|
13
|
+
header = Xenon::Headers::IfNoneMatch.parse('W/"xyzzy"')
|
14
|
+
expect(header.etags.size).to eq(1)
|
15
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy', weak: true))
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can parse multiple etags' do
|
19
|
+
header = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
|
20
|
+
expect(header.etags.size).to eq(3)
|
21
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
22
|
+
expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx', weak: true))
|
23
|
+
expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can parse a wildcard header' do
|
27
|
+
header = Xenon::Headers::IfNoneMatch.parse('*')
|
28
|
+
expect(header.etags.size).to eq(0)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#merge' do
|
33
|
+
it 'can merge two headers and maintain etag order' do
|
34
|
+
h1 = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx"')
|
35
|
+
h2 = Xenon::Headers::IfNoneMatch.parse('"c3piozzzz"')
|
36
|
+
header = h1.merge(h2)
|
37
|
+
expect(header.etags.size).to eq(3)
|
38
|
+
expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
|
39
|
+
expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx', weak: true))
|
40
|
+
expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises a protocol error when trying to merge into a wildcard header' do
|
44
|
+
h1 = Xenon::Headers::IfNoneMatch.parse('*')
|
45
|
+
h2 = Xenon::Headers::IfNoneMatch.parse('"c3piozzzz"')
|
46
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises a protocol error when trying to merge a wildcard into a header' do
|
50
|
+
h1 = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
|
51
|
+
h2 = Xenon::Headers::IfNoneMatch.parse('*')
|
52
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'raises a protocol error when trying to merge two wildcard headers' do
|
56
|
+
h1 = Xenon::Headers::IfNoneMatch.parse('*')
|
57
|
+
h2 = Xenon::Headers::IfNoneMatch.parse('*')
|
58
|
+
expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#to_s' do
|
63
|
+
it 'returns the string representation a single etag' do
|
64
|
+
header = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
|
65
|
+
expect(header.to_s).to eq('"xyzzy"')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns the string representation of multiple etags' do
|
69
|
+
header = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
|
70
|
+
expect(header.to_s).to eq('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns the string representation of a wildcard header' do
|
74
|
+
header = Xenon::Headers::IfNoneMatch.wildcard
|
75
|
+
expect(header.to_s).to eq('*')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'xenon/headers/if_range'
|
2
|
+
|
3
|
+
describe Xenon::Headers::IfRange do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse an http date' do
|
7
|
+
header = Xenon::Headers::IfRange.parse('Sat, 29 Oct 1994 19:43:31 GMT')
|
8
|
+
expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'can parse an obsolete RFC 850 date' do
|
12
|
+
header = Xenon::Headers::IfRange.parse('Sunday, 06-Nov-94 08:49:37 GMT')
|
13
|
+
expect(header.date).to eq(Time.utc(1994, 11, 6, 8, 49, 37))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can parse an obsolete asctime date' do
|
17
|
+
header = Xenon::Headers::IfRange.parse('Sun Nov 6 08:49:37 1994')
|
18
|
+
expect(header.date).to eq(Time.utc(1994, 11, 6, 8, 49, 37))
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can parse a strong etag' do
|
22
|
+
header = Xenon::Headers::IfRange.parse('"xyzzy"')
|
23
|
+
expect(header.etag).to_not be_nil
|
24
|
+
expect(header.etag.opaque_tag).to eq 'xyzzy'
|
25
|
+
expect(header.etag).to be_strong
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should raise a ProtocolError if the etag is weak' do
|
29
|
+
expect { Xenon::Headers::IfRange.parse('W/"xyzzy"') }.to raise_error Xenon::ProtocolError
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context '#to_s' do
|
34
|
+
it 'returns the http date format for dates' do
|
35
|
+
header = Xenon::Headers::IfRange.new(Time.utc(1994, 10, 29, 19, 43, 31))
|
36
|
+
expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns the etag format for etags' do
|
40
|
+
header = Xenon::Headers::IfRange.new(Xenon::ETag.new('xyzzy'))
|
41
|
+
expect(header.to_s).to eq('"xyzzy"')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'xenon/headers/if_unmodified_since'
|
2
|
+
|
3
|
+
describe Xenon::Headers::IfUnmodifiedSince do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse an http date' do
|
7
|
+
header = Xenon::Headers::IfUnmodifiedSince.parse('Sat, 29 Oct 1994 19:43:31 GMT')
|
8
|
+
expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context '#to_s' do
|
13
|
+
it 'returns the http date format' do
|
14
|
+
header = Xenon::Headers::IfUnmodifiedSince.new(Time.utc(1994, 10, 29, 19, 43, 31))
|
15
|
+
expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'xenon/headers/user_agent'
|
2
|
+
|
3
|
+
describe Xenon::Headers::UserAgent do
|
4
|
+
|
5
|
+
context '::parse' do
|
6
|
+
it 'can parse a user agent with a product name' do
|
7
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla')
|
8
|
+
expect(header.products.size).to eq(1)
|
9
|
+
expect(header.products[0].name).to eq('Mozilla')
|
10
|
+
expect(header.products[0].version).to be_nil
|
11
|
+
expect(header.products[0].comment).to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can parse a user agent with a product name and version' do
|
15
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla/5.0')
|
16
|
+
expect(header.products.size).to eq(1)
|
17
|
+
expect(header.products[0].name).to eq('Mozilla')
|
18
|
+
expect(header.products[0].version).to eq('5.0')
|
19
|
+
expect(header.products[0].comment).to be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can parse a user agent with a product name and comment' do
|
23
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla (Macintosh; Intel Mac OS X 10_10_2)')
|
24
|
+
expect(header.products.size).to eq(1)
|
25
|
+
expect(header.products[0].name).to eq('Mozilla')
|
26
|
+
expect(header.products[0].version).to be_nil
|
27
|
+
expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can parse a user agent with a product name, version and comment' do
|
31
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2)')
|
32
|
+
expect(header.products.size).to eq(1)
|
33
|
+
expect(header.products[0].name).to eq('Mozilla')
|
34
|
+
expect(header.products[0].version).to eq('5.0')
|
35
|
+
expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can parse a user agent with multiple comments' do
|
39
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh) (Intel Mac OS X 10_10_2)')
|
40
|
+
expect(header.products.size).to eq(2)
|
41
|
+
expect(header.products[0].name).to eq('Mozilla')
|
42
|
+
expect(header.products[0].version).to eq('5.0')
|
43
|
+
expect(header.products[0].comment).to eq('Macintosh')
|
44
|
+
expect(header.products[1].name).to be_nil
|
45
|
+
expect(header.products[1].version).to be_nil
|
46
|
+
expect(header.products[1].comment).to eq('Intel Mac OS X 10_10_2')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'can parse a typical Chrome user agent' do
|
50
|
+
header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36')
|
51
|
+
expect(header.products.size).to eq(4)
|
52
|
+
expect(header.products[0].name).to eq('Mozilla')
|
53
|
+
expect(header.products[0].version).to eq('5.0')
|
54
|
+
expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
|
55
|
+
expect(header.products[1].name).to eq('AppleWebKit')
|
56
|
+
expect(header.products[1].version).to eq('537.36')
|
57
|
+
expect(header.products[1].comment).to eq('KHTML, like Gecko')
|
58
|
+
expect(header.products[2].name).to eq('Chrome')
|
59
|
+
expect(header.products[2].version).to eq('41.0.2272.89')
|
60
|
+
expect(header.products[2].comment).to be_nil
|
61
|
+
expect(header.products[3].name).to eq('Safari')
|
62
|
+
expect(header.products[3].version).to eq('537.36')
|
63
|
+
expect(header.products[3].comment).to be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|