xenon-http 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +18 -0
  3. data/.gitignore +25 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +20 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE +22 -0
  9. data/README.md +116 -0
  10. data/Rakefile +40 -0
  11. data/VERSION +1 -0
  12. data/examples/hello_world/config.ru +3 -0
  13. data/examples/hello_world/hello_world.rb +27 -0
  14. data/xenon-http/lib/xenon/auth.rb +63 -0
  15. data/xenon-http/lib/xenon/errors.rb +5 -0
  16. data/xenon-http/lib/xenon/etag.rb +48 -0
  17. data/xenon-http/lib/xenon/headers.rb +112 -0
  18. data/xenon-http/lib/xenon/headers/accept.rb +34 -0
  19. data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
  20. data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
  21. data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
  22. data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
  23. data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
  24. data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
  25. data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
  26. data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
  27. data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
  28. data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
  29. data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
  30. data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
  31. data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
  32. data/xenon-http/lib/xenon/http.rb +7 -0
  33. data/xenon-http/lib/xenon/http_version.rb +3 -0
  34. data/xenon-http/lib/xenon/media_type.rb +162 -0
  35. data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
  36. data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
  37. data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
  38. data/xenon-http/lib/xenon/quoted_string.rb +20 -0
  39. data/xenon-http/spec/spec_helper.rb +94 -0
  40. data/xenon-http/spec/xenon/etag_spec.rb +19 -0
  41. data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
  42. data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
  43. data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
  44. data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
  45. data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
  46. data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
  47. data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
  48. data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
  49. data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
  50. data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
  51. data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
  52. data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
  53. data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
  54. data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
  55. data/xenon-http/xenon-http.gemspec +25 -0
  56. data/xenon-routing/lib/xenon/api.rb +118 -0
  57. data/xenon-routing/lib/xenon/marshallers.rb +48 -0
  58. data/xenon-routing/lib/xenon/request.rb +40 -0
  59. data/xenon-routing/lib/xenon/response.rb +29 -0
  60. data/xenon-routing/lib/xenon/routing.rb +6 -0
  61. data/xenon-routing/lib/xenon/routing/context.rb +35 -0
  62. data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
  63. data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
  64. data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
  65. data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
  66. data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
  67. data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
  68. data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
  69. data/xenon-routing/lib/xenon/routing_version.rb +3 -0
  70. data/xenon-routing/spec/spec_helper.rb +94 -0
  71. data/xenon-routing/xenon-routing.gemspec +25 -0
  72. data/xenon.gemspec +26 -0
  73. metadata +160 -0
@@ -0,0 +1,54 @@
1
+ require 'xenon/headers/accept'
2
+
3
+ describe Xenon::Headers::Accept do
4
+
5
+ context '::parse' do
6
+ it 'can parse a basic accept range' do
7
+ header = Xenon::Headers::Accept.parse('text/plain; q=0.5')
8
+ expect(header.media_ranges.size).to eq(1)
9
+ expect(header.media_ranges[0].to_s).to eq('text/plain; q=0.5')
10
+ end
11
+
12
+ it 'can parse the first example from RFC 7231 § 5.3.2 with the right precedence' do
13
+ header = Xenon::Headers::Accept.parse('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')
14
+ expect(header.media_ranges.size).to eq(4)
15
+ expect(header.media_ranges[0].to_s).to eq('text/html')
16
+ expect(header.media_ranges[1].to_s).to eq('text/x-c')
17
+ expect(header.media_ranges[2].to_s).to eq('text/x-dvi; q=0.8')
18
+ expect(header.media_ranges[3].to_s).to eq('text/plain; q=0.5')
19
+ end
20
+
21
+ it 'can parse the second example from RFC 7231 § 5.3.2 with the right precedence' do
22
+ header = Xenon::Headers::Accept.parse('text/*, text/plain, text/plain;format=flowed, */*')
23
+ expect(header.media_ranges.size).to eq(4)
24
+ expect(header.media_ranges[0].to_s).to eq('text/plain; format=flowed')
25
+ expect(header.media_ranges[1].to_s).to eq('text/plain')
26
+ expect(header.media_ranges[2].to_s).to eq('text/*')
27
+ expect(header.media_ranges[3].to_s).to eq('*/*')
28
+ end
29
+
30
+ it 'can parse the third example from RFC 7231 § 5.3.2 with the right precedence' do
31
+ header = Xenon::Headers::Accept.parse('text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
32
+ expect(header.media_ranges.size).to eq(5)
33
+ expect(header.media_ranges[0].to_s).to eq('text/html; level=1')
34
+ expect(header.media_ranges[1].to_s).to eq('text/html; level=2; q=0.4')
35
+ expect(header.media_ranges[2].to_s).to eq('text/html; q=0.7')
36
+ expect(header.media_ranges[3].to_s).to eq('text/*; q=0.3')
37
+ expect(header.media_ranges[4].to_s).to eq('*/*; q=0.5')
38
+ end
39
+ end
40
+
41
+ context '#merge' do
42
+ it 'can merge two headers with the right precedence' do
43
+ h1 = Xenon::Headers::Accept.parse('text/plain; q=0.5, text/html')
44
+ h2 = Xenon::Headers::Accept.parse('text/x-c, text/x-dvi; q=0.8')
45
+ header = h1.merge(h2)
46
+ expect(header.media_ranges.size).to eq(4)
47
+ expect(header.media_ranges[0].to_s).to eq('text/html')
48
+ expect(header.media_ranges[1].to_s).to eq('text/x-c')
49
+ expect(header.media_ranges[2].to_s).to eq('text/x-dvi; q=0.8')
50
+ expect(header.media_ranges[3].to_s).to eq('text/plain; q=0.5')
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,47 @@
1
+ require 'xenon/headers/authorization'
2
+
3
+ describe Xenon::Headers::Authorization do
4
+
5
+ context '::parse' do
6
+ it 'can parse Basic credentials' do
7
+ header = Xenon::Headers::Authorization.parse('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
8
+ expect(header.credentials.class).to eq(Xenon::BasicCredentials)
9
+ expect(header.credentials.username).to eq('Aladdin')
10
+ expect(header.credentials.password).to eq('open sesame')
11
+ end
12
+
13
+ it 'can parse Digest credentials' do
14
+ header = Xenon::Headers::Authorization.parse('Digest username="Mufasa"' +
15
+ ', realm="testrealm@host.com"' +
16
+ ', nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"' +
17
+ ', uri="/dir/index.html"' +
18
+ ', qop=auth' +
19
+ ', nc=00000001' +
20
+ ', cnonce="0a4f113b"' +
21
+ ', response="6629fae49393a05397450978507c4ef1"' +
22
+ ', opaque="5ccc069c403ebaf9f0171e9517f40e41"')
23
+ expect(header.credentials.class).to eq(Xenon::GenericCredentials)
24
+ expect(header.credentials.scheme).to eq('Digest')
25
+ expect(header.credentials.token).to eq(nil)
26
+ expect(header.credentials.params).to eq(
27
+ 'username' => 'Mufasa',
28
+ 'realm' => 'testrealm@host.com',
29
+ 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093',
30
+ 'uri' => '/dir/index.html',
31
+ 'qop' => 'auth',
32
+ 'nc' => '00000001',
33
+ 'cnonce' => '0a4f113b',
34
+ 'response' => '6629fae49393a05397450978507c4ef1',
35
+ 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41')
36
+ end
37
+
38
+ it 'can parse Bearer credentials' do
39
+ header = Xenon::Headers::Authorization.parse('Bearer eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.')
40
+ expect(header.credentials.class).to eq(Xenon::GenericCredentials)
41
+ expect(header.credentials.scheme).to eq('Bearer')
42
+ expect(header.credentials.token).to eq('eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.')
43
+ expect(header.credentials.params).to eq({})
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,64 @@
1
+ require 'xenon/headers/cache_control'
2
+
3
+ describe Xenon::Headers::CacheControl do
4
+
5
+ context '::parse' do
6
+ ['max-age', 'max-stale', 'min-fresh', 's-maxage'].each do |dir|
7
+ it "can parse the #{dir} directive" do
8
+ header = Xenon::Headers::CacheControl.parse("#{dir}=5")
9
+ expect(header.directives.size).to eq(1)
10
+ expect(header.directives[0].to_s).to eq("#{dir}=5")
11
+ end
12
+
13
+ it "can parse the #{dir} directive with a quoted value" do # should not be sent by clients but is permitted
14
+ header = Xenon::Headers::CacheControl.parse("#{dir}=\"5\"")
15
+ expect(header.directives.size).to eq(1)
16
+ expect(header.directives[0].to_s).to eq("#{dir}=5")
17
+ end
18
+ end
19
+
20
+ ['no-cache', 'no-store', 'no-transform', 'only-if-cached', 'must-revalidate', 'public', 'proxy-revalidate'].each do |dir|
21
+ it "can parse the #{dir} directive" do
22
+ header = Xenon::Headers::CacheControl.parse("#{dir}")
23
+ expect(header.directives.size).to eq(1)
24
+ expect(header.directives[0].to_s).to eq("#{dir}")
25
+ end
26
+ end
27
+
28
+ it "can parse the private directive with no field names" do
29
+ header = Xenon::Headers::CacheControl.parse('private')
30
+ expect(header.directives.size).to eq(1)
31
+ expect(header.directives[0].to_s).to eq('private')
32
+ end
33
+
34
+ # TODO: private directive with field names
35
+
36
+ it 'can parse extension directives with quoted string values' do
37
+ header = Xenon::Headers::CacheControl.parse('ext="hello \"world\""')
38
+ expect(header.directives.size).to eq(1)
39
+ expect(header.directives[0].name).to eq('ext')
40
+ expect(header.directives[0].value).to eq("hello \"world\"")
41
+ end
42
+
43
+ it 'can parse more complex directives' do
44
+ header = Xenon::Headers::CacheControl.parse('public, max-age=3600, must-revalidate')
45
+ expect(header.directives.size).to eq(3)
46
+ expect(header.directives[0].to_s).to eq('public')
47
+ expect(header.directives[1].to_s).to eq('max-age=3600')
48
+ expect(header.directives[2].to_s).to eq('must-revalidate')
49
+ end
50
+ end
51
+
52
+ context '#merge' do
53
+ it 'can merge two headers and maintain directive order' do
54
+ h1 = Xenon::Headers::CacheControl.parse('public, max-age=3600')
55
+ h2 = Xenon::Headers::CacheControl.parse('must-revalidate')
56
+ header = h1.merge(h2)
57
+ expect(header.directives.size).to eq(3)
58
+ expect(header.directives[0].to_s).to eq('public')
59
+ expect(header.directives[1].to_s).to eq('max-age=3600')
60
+ expect(header.directives[2].to_s).to eq('must-revalidate')
61
+ end
62
+ end
63
+
64
+ 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