xenon-routing 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 +145 -0
@@ -0,0 +1,112 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module Xenon
4
+ class Headers
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @hash = {}
9
+ end
10
+
11
+ def initialize_dup(other)
12
+ super
13
+ @hash = @hash.dup
14
+ end
15
+
16
+ def freeze
17
+ @hash.freeze
18
+ super
19
+ end
20
+
21
+ def each(&block)
22
+ @hash.values.each(&block)
23
+ end
24
+
25
+ def set!(header)
26
+ @hash[header.name] = header
27
+ self
28
+ end
29
+
30
+ def add!(header)
31
+ existing = @hash[header.name]
32
+ if existing
33
+ if existing.respond_to?(:merge)
34
+ set!(existing.merge(header))
35
+ else
36
+ raise "Unmergeable header '#{header.name}' already exists"
37
+ end
38
+ else
39
+ set!(header)
40
+ end
41
+ self
42
+ end
43
+
44
+ %i(set add).each do |name|
45
+ define_method name do |header|
46
+ dup.send("#{name}!", header)
47
+ end
48
+ end
49
+
50
+ alias_method :<<, :add!
51
+
52
+ class << self
53
+ def register(klass)
54
+ (@registered ||= {})[klass.const_get(:NAME)] = klass
55
+ end
56
+
57
+ def header_class(name)
58
+ (@registered || {})[name]
59
+ end
60
+
61
+ def Header(name)
62
+ klass = Class.new do
63
+ def name
64
+ self.class.const_get(:NAME)
65
+ end
66
+
67
+ def self.inherited(base)
68
+ Headers.register(base)
69
+ end
70
+ end
71
+ Headers.const_set("#{name.tr('-', '_').classify}Header", klass)
72
+ klass.const_set(:NAME, name)
73
+ klass
74
+ end
75
+
76
+ def ListHeader(name)
77
+ klass = Header(name)
78
+ klass.class_eval do
79
+ attr_reader :values
80
+
81
+ def initialize(values)
82
+ @values = values
83
+ end
84
+
85
+ def merge(other)
86
+ self.class.new(*(@values + other.values))
87
+ end
88
+
89
+ def to_s
90
+ @values.map(&:to_s).join(', ')
91
+ end
92
+ end
93
+ klass
94
+ end
95
+ end
96
+
97
+ class Raw
98
+ attr_reader :name, :value
99
+
100
+ def initialize(name, value)
101
+ @name = name
102
+ @value = value
103
+ end
104
+
105
+ def to_s
106
+ @value
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ Dir[File.join(__dir__, 'headers', '*.rb')].each { |f| require f }
@@ -0,0 +1,34 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+ require 'xenon/parsers/media_type'
4
+
5
+ module Xenon
6
+ class Headers
7
+ # http://tools.ietf.org/html/rfc7231#section-5.3.2
8
+ class Accept < ListHeader 'Accept'
9
+ def initialize(*media_ranges)
10
+ super(media_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
11
+ end
12
+
13
+ alias_method :media_ranges, :values
14
+
15
+ def self.parse(s)
16
+ tree = Parsers::AcceptHeader.new.parse(s)
17
+ Parsers::AcceptHeaderTransform.new.apply(tree)
18
+ end
19
+ end
20
+ end
21
+
22
+ module Parsers
23
+ class AcceptHeader < Parslet::Parser
24
+ include HeaderRules, MediaTypeRules
25
+ rule(:accept) { (media_range >> (list_sep >> media_range).repeat).as(:accept) }
26
+ root(:accept)
27
+ end
28
+
29
+ class AcceptHeaderTransform < MediaTypeTransform
30
+ rule(accept: sequence(:mr)) { Headers::Accept.new(*mr) }
31
+ rule(accept: simple(:mr)) { Headers::Accept.new(mr) }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class CharsetRange
6
+ attr_reader :charset, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(charset, q = DEFAULT_Q)
11
+ @charset = charset
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @charset.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.3
28
+ class AcceptCharset < ListHeader 'Accept-Charset'
29
+ def initialize(*charset_ranges)
30
+ super(charset_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :charset_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptCharsetHeader.new.parse(s)
37
+ Parsers::AcceptCharsetHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptCharsetHeader < Parslet::Parser
44
+ include HeaderRules
45
+ rule(:charset) { token.as(:charset) >> sp? }
46
+ rule(:wildcard) { str('*').as(:charset) >> sp? }
47
+ rule(:charset_range) { (charset | wildcard) >> weight.maybe }
48
+ rule(:accept_charset) { (charset_range >> (list_sep >> charset_range).repeat).as(:accept_charset) }
49
+ root(:accept_charset)
50
+ end
51
+
52
+ class AcceptCharsetHeaderTransform < HeaderTransform
53
+ rule(charset: simple(:c), q: simple(:q)) { CharsetRange.new(c, q) }
54
+ rule(charset: simple(:c)) { CharsetRange.new(c) }
55
+ rule(accept_charset: sequence(:cr)) { Headers::AcceptCharset.new(*cr) }
56
+ rule(accept_charset: simple(:cr)) { Headers::AcceptCharset.new(cr) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class ContentCodingRange
6
+ attr_reader :coding, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(coding, q = DEFAULT_Q)
11
+ @coding = coding
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @coding.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.4
28
+ class AcceptEncoding < ListHeader 'Accept-Encoding'
29
+ def initialize(*coding_ranges)
30
+ super(coding_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :coding_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptEncodingHeader.new.parse(s)
37
+ Parsers::AcceptEncodingHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptEncodingHeader < Parslet::Parser
44
+ include HeaderRules
45
+ %w(identity compress x-compress deflate gzip x-gzip).each do |c|
46
+ rule(c.tr('-', '_').to_sym) { str(c).as(:coding) >> sp? }
47
+ end
48
+ rule(:coding) { compress | x_compress | deflate | gzip | x_gzip }
49
+ rule(:wildcard) { str('*').as(:coding) >> sp? }
50
+ rule(:coding_range) { (coding | identity | wildcard) >> weight.maybe }
51
+ rule(:accept_encoding) { (coding_range >> (list_sep >> coding_range).repeat).maybe.as(:accept_encoding) }
52
+ root(:accept_encoding)
53
+ end
54
+
55
+ class AcceptEncodingHeaderTransform < HeaderTransform
56
+ rule(coding: simple(:c), q: simple(:q)) { ContentCodingRange.new(c, q) }
57
+ rule(coding: simple(:c)) { ContentCodingRange.new(c) }
58
+ rule(accept_encoding: sequence(:er)) { Headers::AcceptEncoding.new(*er) }
59
+ rule(accept_encoding: simple(:cc)) { Headers::AcceptEncoding.new(cc) }
60
+ rule(accept_encoding: nil) { Headers::AcceptEncoding.new }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class LanguageRange
6
+ attr_reader :language, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(language, q = DEFAULT_Q)
11
+ @language = language
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @language.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.5
28
+ class AcceptLanguage < ListHeader 'Accept-Language'
29
+ def initialize(*language_ranges)
30
+ super(language_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :language_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptLanguageHeader.new.parse(s)
37
+ Parsers::AcceptLanguageHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptLanguageHeader < Parslet::Parser
44
+ include HeaderRules
45
+ rule(:language) { (alpha.repeat(1, 8) >> (str('-') >> alphanum.repeat(1, 8)).maybe).as(:language) >> sp? }
46
+ rule(:wildcard) { str('*').as(:language) >> sp? }
47
+ rule(:language_range) { (language | wildcard) >> weight.maybe }
48
+ rule(:accept_language) { (language_range >> (list_sep >> language_range).repeat).as(:accept_language) }
49
+ root(:accept_language)
50
+ end
51
+
52
+ class AcceptLanguageHeaderTransform < HeaderTransform
53
+ rule(language: simple(:e), q: simple(:q)) { LanguageRange.new(e, q) }
54
+ rule(language: simple(:e)) { LanguageRange.new(e) }
55
+ rule(accept_language: sequence(:lr)) { Headers::AcceptLanguage.new(*lr) }
56
+ rule(accept_language: simple(:lr)) { Headers::AcceptLanguage.new(lr) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ require 'base64'
2
+ require 'xenon/auth'
3
+ require 'xenon/headers'
4
+ require 'xenon/parsers/header_rules'
5
+
6
+ module Xenon
7
+ class Headers
8
+ # http://tools.ietf.org/html/rfc7235#section-4.2
9
+ class Authorization < Header 'Authorization'
10
+ attr_reader :credentials
11
+
12
+ def initialize(credentials)
13
+ @credentials = credentials
14
+ end
15
+
16
+ def self.parse(s)
17
+ tree = Parsers::AuthorizationHeader.new.parse(s)
18
+ Parsers::AuthorizationHeaderTransform.new.apply(tree)
19
+ rescue Parslet::ParseFailed
20
+ raise Xenon::ParseError.new("Invalid Authorization header (#{s}).")
21
+ end
22
+
23
+ def to_s
24
+ @credentials.to_s
25
+ end
26
+ end
27
+ end
28
+
29
+ module Parsers
30
+ class AuthorizationHeader < Parslet::Parser
31
+ include AuthHeaderRules
32
+ rule(:credentials) { auth_scheme >> sp >> (token68 | auth_params) }
33
+ rule(:authorization) { credentials.as(:authorization) }
34
+ root(:authorization)
35
+ end
36
+
37
+ class AuthorizationHeaderTransform < HeaderTransform
38
+ rule(auth_param: { name: simple(:n), value: simple(:v) }) { [n, v] }
39
+ rule(auth_params: subtree(:x)) { { foo: x } }
40
+ rule(auth_scheme: simple(:s), token: simple(:t)) {
41
+ case s
42
+ when 'Basic' then BasicCredentials.decode(t)
43
+ else GenericCredentials.new(s, token: t)
44
+ end
45
+ }
46
+ rule(auth_scheme: simple(:s), auth_params: subtree(:p)) { GenericCredentials.new(s, params: Hash[p]) }
47
+ rule(authorization: simple(:c)) { Headers::Authorization.new(c) }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+ require 'xenon/quoted_string'
4
+
5
+ module Xenon
6
+ class CacheDirective
7
+ using QuotedString
8
+
9
+ attr_reader :name, :value
10
+
11
+ def initialize(name, value = nil)
12
+ @name = name
13
+ @value = value
14
+ end
15
+
16
+ def to_s
17
+ s = @name.dup
18
+ s << '=' << @value.quote if @value
19
+ s
20
+ end
21
+ end
22
+
23
+ class Headers
24
+ # http://tools.ietf.org/html/rfc7234#section-5.2
25
+ class CacheControl < ListHeader 'Cache-Control'
26
+ def initialize(*directives)
27
+ super(directives)
28
+ end
29
+
30
+ alias_method :directives, :values
31
+
32
+ def self.parse(s)
33
+ tree = Parsers::CacheControlHeader.new.parse(s)
34
+ Parsers::CacheControlHeaderTransform.new.apply(tree)
35
+ end
36
+ end
37
+ end
38
+
39
+ module Parsers
40
+ class CacheControlHeader < Parslet::Parser
41
+ include HeaderRules
42
+ rule(:name) { token.as(:name) }
43
+ rule(:value) { str('=') >> (token | quoted_string).as(:value) }
44
+ rule(:directive) { (name >> value.maybe).as(:directive) >> sp? }
45
+ rule(:cache_control) { (directive >> (list_sep >> directive).repeat).as(:cache_control) }
46
+ root(:cache_control)
47
+ end
48
+
49
+ class CacheControlHeaderTransform < HeaderTransform
50
+ rule(directive: { name: simple(:n), value: simple(:v) }) { CacheDirective.new(n, v) }
51
+ rule(directive: { name: simple(:n) }) { CacheDirective.new(n) }
52
+ rule(cache_control: sequence(:d)) { Headers::CacheControl.new(*d) }
53
+ rule(cache_control: simple(:d)) { Headers::CacheControl.new(d) }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/media_type'
3
+
4
+ module Xenon
5
+ class Headers
6
+ # http://tools.ietf.org/html/rfc7231#section-3.1.1.5
7
+ class ContentType < Header 'Content-Type'
8
+ attr_reader :content_type
9
+
10
+ def initialize(content_type)
11
+ @content_type = content_type
12
+ end
13
+
14
+ def self.parse(s)
15
+ new(Xenon::ContentType.parse(s))
16
+ end
17
+
18
+ def to_s
19
+ @content_type.to_s
20
+ end
21
+ end
22
+ end
23
+ end