rocketio 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -5
  3. data/.pryrc +2 -0
  4. data/.travis.yml +3 -0
  5. data/README.md +22 -5
  6. data/Rakefile +7 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +7 -0
  9. data/lib/rocketio.rb +131 -3
  10. data/lib/rocketio/application.rb +31 -0
  11. data/lib/rocketio/controller.rb +288 -0
  12. data/lib/rocketio/controller/authentication.rb +141 -0
  13. data/lib/rocketio/controller/authorization.rb +53 -0
  14. data/lib/rocketio/controller/cookies.rb +59 -0
  15. data/lib/rocketio/controller/error_handlers.rb +89 -0
  16. data/lib/rocketio/controller/filters.rb +119 -0
  17. data/lib/rocketio/controller/flash.rb +21 -0
  18. data/lib/rocketio/controller/helpers.rb +438 -0
  19. data/lib/rocketio/controller/middleware.rb +32 -0
  20. data/lib/rocketio/controller/render.rb +148 -0
  21. data/lib/rocketio/controller/render/engine.rb +76 -0
  22. data/lib/rocketio/controller/render/layout.rb +27 -0
  23. data/lib/rocketio/controller/render/layouts.rb +85 -0
  24. data/lib/rocketio/controller/render/templates.rb +83 -0
  25. data/lib/rocketio/controller/request.rb +115 -0
  26. data/lib/rocketio/controller/response.rb +84 -0
  27. data/lib/rocketio/controller/sessions.rb +64 -0
  28. data/lib/rocketio/controller/token_auth.rb +118 -0
  29. data/lib/rocketio/controller/websocket.rb +21 -0
  30. data/lib/rocketio/error_templates/404.html +3 -0
  31. data/lib/rocketio/error_templates/409.html +7 -0
  32. data/lib/rocketio/error_templates/500.html +3 -0
  33. data/lib/rocketio/error_templates/501.html +6 -0
  34. data/lib/rocketio/error_templates/layout.html +1 -0
  35. data/lib/rocketio/exceptions.rb +4 -0
  36. data/lib/rocketio/router.rb +65 -0
  37. data/lib/rocketio/util.rb +122 -0
  38. data/lib/rocketio/version.rb +2 -2
  39. data/rocketio.gemspec +21 -17
  40. data/test/aliases_test.rb +54 -0
  41. data/test/authentication_test.rb +307 -0
  42. data/test/authorization_test.rb +91 -0
  43. data/test/cache_control_test.rb +268 -0
  44. data/test/content_type_test.rb +124 -0
  45. data/test/cookies_test.rb +49 -0
  46. data/test/error_handlers_test.rb +125 -0
  47. data/test/etag_test.rb +445 -0
  48. data/test/filters_test.rb +177 -0
  49. data/test/halt_test.rb +73 -0
  50. data/test/helpers_test.rb +171 -0
  51. data/test/middleware_test.rb +57 -0
  52. data/test/redirect_test.rb +135 -0
  53. data/test/render/engine_test.rb +71 -0
  54. data/test/render/get.erb +1 -0
  55. data/test/render/items.erb +1 -0
  56. data/test/render/layout.erb +1 -0
  57. data/test/render/layout_test.rb +104 -0
  58. data/test/render/layouts/master.erb +1 -0
  59. data/test/render/layouts_test.rb +145 -0
  60. data/test/render/master.erb +1 -0
  61. data/test/render/post.erb +1 -0
  62. data/test/render/put.erb +1 -0
  63. data/test/render/render_test.rb +101 -0
  64. data/test/render/setup.rb +14 -0
  65. data/test/render/templates/a/get.erb +1 -0
  66. data/test/render/templates/master.erb +1 -0
  67. data/test/render/templates_test.rb +146 -0
  68. data/test/request_test.rb +105 -0
  69. data/test/response_test.rb +119 -0
  70. data/test/routes_test.rb +70 -0
  71. data/test/sendfile_test.rb +209 -0
  72. data/test/sessions_test.rb +176 -0
  73. data/test/setup.rb +59 -0
  74. metadata +144 -9
  75. data/LICENSE.txt +0 -22
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
2
+ # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
3
+ # Copyright (c) 2015 Slee Woo
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person
6
+ # obtaining a copy of this software and associated documentation
7
+ # files (the "Software"), to deal in the Software without
8
+ # restriction, including without limitation the rights to use,
9
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ # OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module RocketIO
27
+ class Request < Rack::Request
28
+
29
+ HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/
30
+ HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/
31
+
32
+ # Returns an array of acceptable media types for the response
33
+ def accept
34
+ @__accept__ ||= begin
35
+ if env.include?(HTTP_ACCEPT) and !(accept = env[HTTP_ACCEPT].to_s).empty?
36
+ accept.scan(HEADER_VALUE_WITH_PARAMS).map! {|e| AcceptEntry.new(e)}.sort
37
+ else
38
+ [AcceptEntry.new('*/*')]
39
+ end
40
+ end
41
+ end
42
+
43
+ def accept?(type)
44
+ preferred_type(type).to_s.include?(type)
45
+ end
46
+
47
+ def preferred_type(*types)
48
+ accepts = accept # just evaluate once
49
+ return accepts.first if types.empty?
50
+ types.flatten!
51
+ return types.first if accepts.empty?
52
+ accepts.detect do |pattern|
53
+ type = types.detect { |t| File.fnmatch(pattern, t) }
54
+ return type if type
55
+ end
56
+ end
57
+
58
+ alias secure? ssl?
59
+
60
+ def forwarded?
61
+ env.include?(HTTP_X_FORWARDED_HOST)
62
+ end
63
+
64
+ def safe?
65
+ get? or head? or options? or trace?
66
+ end
67
+
68
+ def idempotent?
69
+ safe? or put? or delete? or link? or unlink?
70
+ end
71
+
72
+ class AcceptEntry
73
+ attr_accessor :params
74
+ attr_reader :entry
75
+
76
+ def initialize(entry)
77
+ params = entry.scan(HEADER_PARAM).map! do |s|
78
+ key, value = s.strip.split('=', 2)
79
+ value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
80
+ [key, value]
81
+ end
82
+
83
+ @entry = entry
84
+ @type = entry[/[^;]+/].delete(' ')
85
+ @params = Hash[params]
86
+ @q = @params.delete('q') { 1.0 }.to_f
87
+ end
88
+
89
+ def <=>(other)
90
+ other.priority <=> self.priority
91
+ end
92
+
93
+ def priority
94
+ # We sort in descending order; better matches should be higher.
95
+ [ @q, -@type.count('*'), @params.size ]
96
+ end
97
+
98
+ def to_str
99
+ @type
100
+ end
101
+
102
+ def to_s(full = false)
103
+ full ? entry : to_str
104
+ end
105
+
106
+ def respond_to?(*args)
107
+ super or to_str.respond_to?(*args)
108
+ end
109
+
110
+ def method_missing(*args, &block)
111
+ to_str.send(*args, &block)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,84 @@
1
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
2
+ # Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
3
+ # Copyright (c) 2015 Slee Woo
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person
6
+ # obtaining a copy of this software and associated documentation
7
+ # files (the "Software"), to deal in the Software without
8
+ # restriction, including without limitation the rights to use,
9
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the
11
+ # Software is furnished to do so, subject to the following
12
+ # conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ # OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module RocketIO
27
+ class Response < Rack::Response
28
+
29
+ def body= value
30
+ @body = case value
31
+ when Rack::Response
32
+ value.body
33
+ when Proc
34
+ def value.each; yield(call) end
35
+ value
36
+ when String
37
+ [value]
38
+ else
39
+ value
40
+ end
41
+ end
42
+
43
+ def empty_body?
44
+ @body == EMPTY_ARRAY || @body.nil?
45
+ end
46
+
47
+ def each
48
+ block_given? ? super : enum_for(:each)
49
+ end
50
+
51
+ def finish
52
+ headers[CONTENT_TYPE] ||= DEFAULT_CONTENT_TYPE
53
+
54
+ if drop_content_info?
55
+ headers.delete(CONTENT_LENGTH)
56
+ headers.delete(CONTENT_TYPE)
57
+ end
58
+
59
+ if drop_body?
60
+ close
61
+ self.body = EMPTY_ARRAY
62
+ end
63
+
64
+ [status.to_i, headers, self.body]
65
+ end
66
+
67
+ def precondition_failed?
68
+ status.to_i == 412
69
+ end
70
+
71
+ def not_modified?
72
+ status.to_i == 304
73
+ end
74
+
75
+ private
76
+ def drop_content_info?
77
+ status.to_i / 100 == 1 || drop_body?
78
+ end
79
+
80
+ def drop_body?
81
+ DROP_BODY_RESPONSES[status.to_i]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,64 @@
1
+ module RocketIO
2
+ class Controller
3
+
4
+ # setup sessions.
5
+ # there are 2 setups out of the box:
6
+ # - cookies
7
+ # - memcache
8
+ # you can also use your own setup
9
+ #
10
+ # @note to disable sessions for some controller set sessions to nil or false
11
+ #
12
+ # @example keep sessions in cookies
13
+ # sessions :cookies
14
+ #
15
+ # @example keep sessions in cookies using custom options
16
+ # sessions :cookies, secret: 'someSecret', domain: 'some.domain'
17
+ #
18
+ # @example keep sessions in memcache
19
+ # sessions :memcache
20
+ #
21
+ # @example use a custom setup, i.e. github.com/migrs/rack-session-mongo
22
+ # $ gem install rack-session-mongo
23
+ #
24
+ # require 'rack/session/mongo'
25
+ #
26
+ # class Pages < RocketIO::Controller
27
+ # sessions Rack::Session::Mongo
28
+ # end
29
+ #
30
+ # @example disable sessions for current controller
31
+ # sessions nil # or false
32
+ #
33
+ # @param pool [Symbol, Class]
34
+ # @param opts [Hash]
35
+ #
36
+ def self.sessions pool = (noargs = true; nil), opts = {}
37
+ @__sessions__ = if pool.nil? || pool == false
38
+ nil
39
+ else
40
+ if pool == :cookies && opts[:secret].nil?
41
+ opts[:secret] = ::Digest::MD5.hexdigest([Time.now.to_f, rand]*'')
42
+ end
43
+ pool = case pool
44
+ when :cookies
45
+ ::Rack::Session::Cookie
46
+ when :memcache
47
+ ::Rack::Session::Memcache
48
+ else
49
+ pool
50
+ end
51
+ [pool, opts]
52
+ end
53
+ define_sessions_methods
54
+ end
55
+
56
+ def self.define_sessions_methods source = self
57
+ return unless source.instance_variables.include?(:@__sessions__)
58
+ sessions = source.instance_variable_get(:@__sessions__)
59
+ define_method(:sessions) {sessions}
60
+ end
61
+
62
+ def sessions; end
63
+ end
64
+ end
@@ -0,0 +1,118 @@
1
+ # Copyright (c) 2004-2015 David Heinemeier Hansson
2
+ # Copyright (c) 2015 Slee Woo
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RocketIO
24
+ module TokenAuth
25
+ extend self
26
+
27
+ TOKEN_KEY = 'token='.freeze
28
+ TOKEN_REGEX = /^Token /
29
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
30
+
31
+ HTTP_AUTHORIZATION = 'HTTP_AUTHORIZATION'.freeze
32
+ X_HTTP_AUTHORIZATION_I = 'X-HTTP_AUTHORIZATION'.freeze
33
+ X_HTTP_AUTHORIZATION_II = 'X_HTTP_AUTHORIZATION'.freeze
34
+ REDIRECT_X_HTTP_AUTHORIZATION = 'REDIRECT_X_HTTP_AUTHORIZATION'.freeze
35
+
36
+ WWW_AUTHENTICATE = 'WWW-Authenticate'.freeze
37
+ TOKEN_REALM_FORMAT = 'Token realm="%s"'.freeze
38
+ ACCESS_DENIED = "HTTP Token: Access denied.\n".freeze
39
+
40
+ # If token Authorization header is present, call the login
41
+ # procedure with the present token and options.
42
+ #
43
+ # @return the return value of given block if a token is found
44
+ # @return nil if no token is found
45
+ def authenticate env
46
+ token, options = token_and_options(env)
47
+ return if token.nil? || token.empty?
48
+ yield(token, options)
49
+ end
50
+
51
+ # Parses the token and options out of the token authorization header. If
52
+ # the header looks like this:
53
+ # Authorization: Token token="abc", nonce="def"
54
+ # Then the returned token is "abc", and the options is {nonce: "def"}
55
+ #
56
+ # @return [Array] if a token is present
57
+ # @return nil if no token is found
58
+ def token_and_options env
59
+ return unless authorization_request = authorization?(env)
60
+ return unless authorization_request[TOKEN_REGEX]
61
+ params = token_params_from(authorization_request)
62
+ [params.shift[1], RocketIO.indifferent_params(Hash[params])]
63
+ end
64
+
65
+ # Returns the authorization header regardless of whether it was specified directly or through one of the
66
+ # proxy alternatives.
67
+ def authorization? env
68
+ env[HTTP_AUTHORIZATION] ||
69
+ env[X_HTTP_AUTHORIZATION_I] ||
70
+ env[X_HTTP_AUTHORIZATION_II] ||
71
+ env[REDIRECT_X_HTTP_AUTHORIZATION]
72
+ end
73
+
74
+ def token_params_from auth
75
+ rewrite_param_values(params_array_from(raw_params(auth)))
76
+ end
77
+
78
+ # Takes raw_params and turns it into an array of parameters
79
+ #
80
+ # @param raw_params [Array]
81
+ # @return [Array]
82
+ def params_array_from raw_params
83
+ raw_params.map { |param| param.split %r/=(.+)?/ }
84
+ end
85
+
86
+ # This removes the `"` characters wrapping the value.
87
+ #
88
+ # @param array_params [Array]
89
+ def rewrite_param_values array_params
90
+ array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
91
+ end
92
+
93
+ # This method takes an authorization body and splits up the key-value
94
+ # pairs by the standardized `:`, `;`, or `\t`
95
+ #
96
+ # @param auth [String]
97
+ # @return [Array]
98
+ def raw_params auth
99
+ _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
100
+ unless _raw_params.first =~ %r{\A#{TOKEN_KEY}}
101
+ _raw_params[0] = [TOKEN_KEY, _raw_params.first]*''
102
+ end
103
+ _raw_params
104
+ end
105
+
106
+ # Sets a WWW-Authenticate to let the client know a token is desired.
107
+ #
108
+ # @param realm [String]
109
+ # @return [Array]
110
+ def authentication_request realm
111
+ [
112
+ 401,
113
+ {WWW_AUTHENTICATE => TOKEN_REALM_FORMAT % realm.tr('"', '')},
114
+ [ACCESS_DENIED]
115
+ ]
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,21 @@
1
+ module RocketIO
2
+ class Controller
3
+
4
+ def websocket?
5
+ @__is_websocket__ ||= websocket_connection? && websocket_upgrade? ? 1 : 0
6
+ @__is_websocket__ == 1
7
+ end
8
+
9
+ def websocket_connection?
10
+ env[RocketIO::HTTP_CONNECTION].to_s.downcase.split(/ *, */).include?(RocketIO::UPGRADE)
11
+ end
12
+
13
+ def websocket_upgrade?
14
+ env[RocketIO::HTTP_UPGRADE].to_s.downcase == RocketIO::WEBSOCKET
15
+ end
16
+
17
+ def websocket_response
18
+ [-1, {}, []]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ <h1>404: Not Found</h1>
2
+ <hr>
3
+ <h2>"{{env.PATH_INFO}}" page is not here :(</h2>
@@ -0,0 +1,7 @@
1
+ <h1>Wrong number of arguments received</h1>
2
+ <h2>
3
+ Controller: {{controller}}<br>
4
+ Resolved Path: {{resolved_path}}<br>
5
+ Expected Parameters: {{expected_parameters}}<br>
6
+ Received Parameters: {{received_parameters}}<br>
7
+ </h2>
@@ -0,0 +1,3 @@
1
+ <h1>500: Fatal Error</h1>
2
+ <hr>
3
+ <h4>{{error.message}}</h4>
@@ -0,0 +1,6 @@
1
+ <h1>501: Not Implemented</h1>
2
+ <hr>
3
+ <h2>
4
+ "{{env.PATH_INFO}}" page found
5
+ but it is not supposed to work through "{{env.REQUEST_METHOD}}" request method
6
+ </h2>
@@ -0,0 +1 @@
1
+ {{{ yield }}}
@@ -0,0 +1,4 @@
1
+ module RocketIO
2
+ class TemplateError < RuntimeError; end
3
+ class LayoutError < RuntimeError; end
4
+ end
@@ -0,0 +1,65 @@
1
+ module RocketIO
2
+ class Router
3
+ ROOT_PATH_PIECES = [''.freeze].freeze
4
+ attr_reader :controllers, :routes
5
+
6
+ def initialize *controllers
7
+ @controllers = controllers.flatten.compact.uniq
8
+ @routes = build_routes
9
+ freeze!
10
+ end
11
+
12
+ def resolve_path path
13
+ controllers = @routes
14
+ pieces = path_pieces(path)
15
+ depth = -1
16
+ controller = nil
17
+ offset = 0
18
+ while controllers = controllers[pieces[depth += 1]]
19
+ next unless controllers[0]
20
+ controller = controllers[0]
21
+ offset = depth + 1
22
+ end
23
+ [controller, pieces[offset .. -1]]
24
+ end
25
+
26
+ private
27
+ # split given path into an array
28
+ #
29
+ # @example /a/b/c
30
+ # ["", a", "b", "c"]
31
+ #
32
+ # @param path [String]
33
+ # @return [Array]
34
+ #
35
+ def path_pieces path
36
+ return ROOT_PATH_PIECES if path == SLASH # that's root path, /
37
+ path.to_s.split(/\/+/)
38
+ end
39
+
40
+ # building a routing tree
41
+ #
42
+ # @return [Hash]
43
+ #
44
+ def build_routes
45
+ map = {}
46
+ @controllers.each do |controller|
47
+ [controller.url, *controller.aliases].each do |url|
48
+ depth = 0
49
+ pieces = path_pieces(url)
50
+ pieces.inject(map) do |map,chunk|
51
+ map[chunk] ||= {}
52
+ map[chunk][0] = controller if pieces.size == (depth += 1)
53
+ map[chunk]
54
+ end
55
+ end
56
+ end
57
+ map
58
+ end
59
+
60
+ # freezing the application cause modifying it at runtime may blow the universe
61
+ def freeze!
62
+ self.freeze
63
+ end
64
+ end
65
+ end