joe-merb-core 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +456 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +648 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +889 -0
  13. data/lib/merb-core/config.rb +380 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +620 -0
  16. data/lib/merb-core/controller/exceptions.rb +302 -0
  17. data/lib/merb-core/controller/merb_controller.rb +283 -0
  18. data/lib/merb-core/controller/mime.rb +111 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +316 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +345 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +200 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +77 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +98 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +172 -0
  34. data/lib/merb-core/dispatch/request.rb +718 -0
  35. data/lib/merb-core/dispatch/router.rb +228 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +610 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +220 -0
  39. data/lib/merb-core/dispatch/router/route.rb +560 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +215 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +27 -0
  51. data/lib/merb-core/rack/adapter.rb +47 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +24 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +13 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +119 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +33 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +14 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +40 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +17 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +72 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +96 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +321 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +252 -0
  75. data/lib/merb-core/tasks/merb.rb +2 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +51 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +17 -0
  79. data/lib/merb-core/test/helpers.rb +10 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +176 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +61 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +47 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +10 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +108 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +393 -0
  89. data/lib/merb-core/test/run_specs.rb +141 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. metadata +253 -0
@@ -0,0 +1,222 @@
1
+ require 'merb-core/dispatch/session/container'
2
+ require 'merb-core/dispatch/session/store_container'
3
+
4
+ module Merb
5
+ class Config
6
+ # Returns stores list constructed from
7
+ # configured session stores (:session_stores config option)
8
+ # or default one (:session_store config option).
9
+ def self.session_stores
10
+ @session_stores ||= begin
11
+ config_stores = Array(
12
+ Merb::Config[:session_stores] || Merb::Config[:session_store]
13
+ )
14
+ config_stores.map { |name| name.to_sym }
15
+ end
16
+ end
17
+ end # Config
18
+
19
+ # The Merb::Session module gets mixed into Merb::SessionContainer to allow
20
+ # app-level functionality (usually found in app/models/merb/session.rb) for
21
+ # session.
22
+ #
23
+ # You can use this module to implement additional methods to simplify
24
+ # building wizard-like application components,
25
+ # authentication frameworks, etc.
26
+ module Session
27
+ end
28
+
29
+ # This is mixed into Merb::Controller on framework boot.
30
+ module SessionMixin
31
+ # Raised when no suitable session store has been setup.
32
+ class NoSessionContainer < StandardError; end
33
+
34
+ # Raised when storing more data than the available space reserved.
35
+ class SessionOverflow < StandardError; end
36
+
37
+ # Session configuration options:
38
+ #
39
+ # :session_id_key The key by which a session value/id is
40
+ # retrieved; defaults to _session_id
41
+ #
42
+ # :session_expiry When to expire the session cookie;
43
+ # defaults to 2 weeks
44
+ #
45
+ # :session_secret_key A secret string which is used to sign/validate
46
+ # session data; min. 16 chars
47
+ #
48
+ # :default_cookie_domain The default domain to write cookies for.
49
+ def self.included(base)
50
+ # Register a callback to finalize sessions - needs to run before the cookie
51
+ # callback extracts Set-Cookie headers from request.cookies.
52
+ base._after_dispatch_callbacks.unshift lambda { |c| c.request.finalize_session }
53
+ end
54
+
55
+ # ==== Parameters
56
+ # session_store<String>:: The type of session store to access.
57
+ #
58
+ # ==== Returns
59
+ # SessionContainer:: The session that was extracted from the request object.
60
+ def session(session_store = nil)
61
+ request.session(session_store)
62
+ end
63
+
64
+ # Module methods
65
+
66
+ # ==== Returns
67
+ # String:: A random 32 character string for use as a unique session ID.
68
+ def rand_uuid
69
+ values = [
70
+ rand(0x0010000),
71
+ rand(0x0010000),
72
+ rand(0x0010000),
73
+ rand(0x0010000),
74
+ rand(0x0010000),
75
+ rand(0x1000000),
76
+ rand(0x1000000),
77
+ ]
78
+ "%04x%04x%04x%04x%04x%06x%06x" % values
79
+ end
80
+
81
+ # Marks this session as needing a new cookie.
82
+ def needs_new_cookie!
83
+ @_new_cookie = true
84
+ end
85
+
86
+ def needs_new_cookie?
87
+ @_new_cookie
88
+ end
89
+
90
+ module_function :rand_uuid, :needs_new_cookie!, :needs_new_cookie?
91
+
92
+ module RequestMixin
93
+
94
+ def self.included(base)
95
+ base.extend ClassMethods
96
+
97
+ # Keep track of all known session store types.
98
+ base.cattr_accessor :registered_session_types
99
+ base.registered_session_types = Dictionary.new
100
+ base.class_inheritable_accessor :_session_id_key, :_session_secret_key,
101
+ :_session_expiry
102
+
103
+ base._session_id_key = Merb::Config[:session_id_key] || '_session_id'
104
+ base._session_expiry = Merb::Config[:session_expiry] || 0
105
+ base._session_secret_key = Merb::Config[:session_secret_key]
106
+ end
107
+
108
+ module ClassMethods
109
+
110
+ # ==== Parameters
111
+ # name<~to_sym>:: Name of the session type to register.
112
+ # class_name<String>:: The corresponding class name.
113
+ #
114
+ # === Notres
115
+ # This is automatically called when Merb::SessionContainer is subclassed.
116
+ def register_session_type(name, class_name)
117
+ self.registered_session_types[name.to_sym] = class_name
118
+ end
119
+
120
+ end
121
+
122
+ # The default session store type.
123
+ def default_session_store
124
+ Merb::Config[:session_store] && Merb::Config[:session_store].to_sym
125
+ end
126
+
127
+ # ==== Returns
128
+ # Hash:: All active session stores by type.
129
+ def session_stores
130
+ @session_stores ||= {}
131
+ end
132
+
133
+ # Returns session container. Merb is able to handle multiple session
134
+ # stores, hence a parameter to pick it.
135
+ #
136
+ # ==== Parameters
137
+ # session_store<String>:: The type of session store to access,
138
+ # defaults to default_session_store.
139
+ #
140
+ # === Notes
141
+ # If no suitable session store type is given, it defaults to
142
+ # cookie-based sessions.
143
+ def session(session_store = nil)
144
+ session_store ||= default_session_store
145
+ if class_name = self.class.registered_session_types[session_store]
146
+ session_stores[session_store] ||= Object.full_const_get(class_name).setup(self)
147
+ elsif fallback = self.class.registered_session_types.keys.first
148
+ Merb.logger.warn "Session store '#{session_store}' not found. Check your configuration in init file."
149
+ Merb.logger.warn "Falling back to #{fallback} session store."
150
+ session(fallback)
151
+ else
152
+ msg = "No session store set. Set it in init file like this: c[:session_store] = 'activerecord'"
153
+ Merb.logger.error!(msg)
154
+ raise NoSessionContainer, msg
155
+ end
156
+ end
157
+
158
+ # ==== Parameters
159
+ # new_session<Merb::SessionContainer>:: A session store instance.
160
+ #
161
+ # === Notes
162
+ # The session is assigned internally by its session_store_type key.
163
+ def session=(new_session)
164
+ if self.session?(new_session.class.session_store_type)
165
+ original_session_id = self.session(new_session.class.session_store_type).session_id
166
+ if new_session.session_id != original_session_id
167
+ set_session_id_cookie(new_session.session_id)
168
+ end
169
+ end
170
+ session_stores[new_session.class.session_store_type] = new_session
171
+ end
172
+
173
+ # Whether a session has been setup
174
+ def session?(session_store = nil)
175
+ (session_store ? [session_store] : session_stores).any? do |type, store|
176
+ store.is_a?(Merb::SessionContainer)
177
+ end
178
+ end
179
+
180
+ # Teardown and/or persist the current sessions.
181
+ def finalize_session
182
+ session_stores.each { |name, store| store.finalize(self) }
183
+ end
184
+ alias :finalize_sessions :finalize_session
185
+
186
+ # Assign default cookie values
187
+ def default_cookies
188
+ defaults = {}
189
+ if route && route.allow_fixation? && params.key?(_session_id_key)
190
+ Merb.logger.info("Fixated session id: #{_session_id_key}")
191
+ defaults[_session_id_key] = params[_session_id_key]
192
+ end
193
+ defaults
194
+ end
195
+
196
+ # Sets session cookie value.
197
+ #
198
+ # ==== Parameters
199
+ # value<String>:: The value of the session cookie; either the session id or the actual encoded data.
200
+ # options<Hash>:: Cookie options like domain, path and expired.
201
+ def set_session_cookie_value(value, options = {})
202
+ defaults = {}
203
+ defaults[:expires] = Time.now + _session_expiry if _session_expiry > 0
204
+ cookies.set_cookie(_session_id_key, value, defaults.merge(options))
205
+ end
206
+ alias :set_session_id_cookie :set_session_cookie_value
207
+
208
+ # ==== Returns
209
+ # String:: The value of the session cookie; either the session id or the actual encoded data.
210
+ def session_cookie_value
211
+ cookies[_session_id_key]
212
+ end
213
+ alias :session_id :session_cookie_value
214
+
215
+ # Destroy the session cookie.
216
+ def destroy_session_cookie
217
+ cookies.delete(_session_id_key)
218
+ end
219
+
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,74 @@
1
+ module Merb
2
+ class SessionContainer < Mash
3
+
4
+ class_inheritable_accessor :session_store_type
5
+ cattr_accessor :subclasses
6
+ self.subclasses = []
7
+
8
+ attr_reader :session_id
9
+ attr_accessor :needs_new_cookie
10
+
11
+ class << self
12
+
13
+ # Register the subclass as an available session store type.
14
+ def inherited(klass)
15
+ self.subclasses << klass.to_s
16
+ super
17
+ end
18
+
19
+ # Generates a new session ID and creates a new session.
20
+ #
21
+ # ==== Returns
22
+ # SessionContainer:: The new session.
23
+ def generate
24
+ end
25
+
26
+ # ==== Parameters
27
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
28
+ #
29
+ # ==== Returns
30
+ # SessionContainer:: a SessionContainer. If no sessions were found,
31
+ # a new SessionContainer will be generated.
32
+ def setup(request)
33
+ end
34
+
35
+ end
36
+
37
+ # ==== Parameters
38
+ # session_id<String>:: A unique identifier for this session.
39
+ def initialize(session_id)
40
+ @_destroy = false
41
+ self.session_id = session_id
42
+ end
43
+
44
+ # Assign a new session_id.
45
+ #
46
+ # Recreates the cookie with the default expiration time. Useful during log
47
+ # in for pushing back the expiration date.
48
+ def session_id=(sid)
49
+ self.needs_new_cookie = (@session_id && @session_id != sid)
50
+ @session_id = sid
51
+ end
52
+
53
+ # Teardown and/or persist the current session.
54
+ #
55
+ # If @_destroy is true, clear out the session completely, including
56
+ # removal of the session cookie itself.
57
+ #
58
+ # ==== Parameters
59
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
60
+ def finalize(request)
61
+ end
62
+
63
+ # Destroy the current session - clears data and removes session cookie.
64
+ def clear!
65
+ @_destroy = true
66
+ self.clear
67
+ end
68
+
69
+ # Regenerate the session_id.
70
+ def regenerate
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,173 @@
1
+ require 'base64' # to convert Marshal.dump to ASCII
2
+ require 'openssl' # to generate the HMAC message digest
3
+ module Merb
4
+
5
+ # If you have more than 4K of session data or don't want your data to be
6
+ # visible to the user, pick another session store.
7
+ #
8
+ # CookieOverflow is raised if you attempt to store more than 4K of data.
9
+ # TamperedWithCookie is raised if the data integrity check fails.
10
+ #
11
+ # A message digest is included with the cookie to ensure data integrity:
12
+ # a user cannot alter session data without knowing the secret key included
13
+ # in the hash.
14
+ #
15
+ # To use Cookie Sessions, set in config/merb.yml
16
+ # :session_secret_key - your secret digest key
17
+ # :session_store: cookie
18
+ class CookieSession < SessionContainer
19
+ # TODO (maybe):
20
+ # include request ip address
21
+ # AES encrypt marshaled data
22
+
23
+ # Raised when storing more than 4K of session data.
24
+ class CookieOverflow < StandardError; end
25
+
26
+ # Raised when the cookie fails its integrity check.
27
+ class TamperedWithCookie < StandardError; end
28
+
29
+ # Cookies can typically store 4096 bytes.
30
+ MAX = 4096
31
+ DIGEST = OpenSSL::Digest::Digest.new('SHA1') # or MD5, RIPEMD160, SHA256?
32
+
33
+ attr_accessor :_original_session_data
34
+
35
+ # The session store type
36
+ self.session_store_type = :cookie
37
+
38
+ class << self
39
+ # Generates a new session ID and creates a new session.
40
+ #
41
+ # ==== Returns
42
+ # SessionContainer:: The new session.
43
+ def generate
44
+ self.new(Merb::SessionMixin.rand_uuid, "", Merb::Request._session_secret_key)
45
+ end
46
+
47
+ # Set up a new session on request: make it available on request instance.
48
+ #
49
+ # ==== Parameters
50
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
51
+ #
52
+ # ==== Returns
53
+ # SessionContainer:: a SessionContainer. If no sessions were found,
54
+ # a new SessionContainer will be generated.
55
+ def setup(request)
56
+ session = self.new(Merb::SessionMixin.rand_uuid,
57
+ request.session_cookie_value, request._session_secret_key)
58
+ session._original_session_data = session.to_cookie
59
+ request.session = session
60
+ end
61
+
62
+ end
63
+
64
+ # ==== Parameters
65
+ # session_id<String>:: A unique identifier for this session.
66
+ # cookie<String>:: The raw cookie data.
67
+ # secret<String>:: A session secret.
68
+ #
69
+ # ==== Raises
70
+ # ArgumentError:: Nil or blank secret.
71
+ def initialize(session_id, cookie, secret)
72
+ super session_id
73
+ if secret.blank? || secret.length < 16
74
+ msg = "You must specify a session_secret_key in your init file, and it must be at least 16 characters"
75
+ Merb.logger.warn(msg)
76
+ raise ArgumentError, msg
77
+ end
78
+ @secret = secret
79
+ self.update(unmarshal(cookie))
80
+ end
81
+
82
+ # Teardown and/or persist the current session.
83
+ #
84
+ # If @_destroy is true, clear out the session completely, including
85
+ # removal of the session cookie itself.
86
+ #
87
+ # ==== Parameters
88
+ # request<Merb::Request>:: request object created from Rack environment.
89
+ def finalize(request)
90
+ if @_destroy
91
+ request.destroy_session_cookie
92
+ elsif _original_session_data != (new_session_data = self.to_cookie)
93
+ request.set_session_cookie_value(new_session_data)
94
+ end
95
+ end
96
+
97
+ # Regenerate the session_id.
98
+ def regenerate
99
+ self.session_id = Merb::SessionMixin.rand_uuid
100
+ end
101
+
102
+ # Create the raw cookie string; includes an HMAC keyed message digest.
103
+ #
104
+ # ==== Returns
105
+ # String:: Cookie value.
106
+ #
107
+ # ==== Raises
108
+ # CookieOverflow:: More than 4K of data put into session.
109
+ #
110
+ # ==== Notes
111
+ # Session data is converted to a Hash first, since a container might
112
+ # choose to marshal it, which would make it persist
113
+ # attributes like 'needs_new_cookie', which it shouldn't.
114
+ def to_cookie
115
+ unless self.empty?
116
+ data = self.serialize
117
+ value = Merb::Request.escape "#{data}--#{generate_digest(data)}"
118
+ if value.size > MAX
119
+ msg = "Cookies have limit of 4K. Session contents: #{data.inspect}"
120
+ Merb.logger.error!(msg)
121
+ raise CookieOverflow, msg
122
+ end
123
+ value
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Generate the HMAC keyed message digest. Uses SHA1.
130
+ def generate_digest(data)
131
+ OpenSSL::HMAC.hexdigest(DIGEST, @secret, data)
132
+ end
133
+
134
+ # Unmarshal cookie data to a hash and verify its integrity.
135
+ #
136
+ # ==== Parameters
137
+ # cookie<~to_s>:: The cookie to unmarshal.
138
+ #
139
+ # ==== Raises
140
+ # TamperedWithCookie:: The digests don't match.
141
+ #
142
+ # ==== Returns
143
+ # Hash:: The stored session data.
144
+ def unmarshal(cookie)
145
+ if cookie.blank?
146
+ {}
147
+ else
148
+ data, digest = Merb::Request.unescape(cookie).split('--')
149
+ return {} if data.blank? || digest.blank?
150
+ unless digest == generate_digest(data)
151
+ clear
152
+ unless Merb::Config[:ignore_tampered_cookies]
153
+ raise TamperedWithCookie, "Maybe the site's session_secret_key has changed?"
154
+ end
155
+ end
156
+ unserialize(data)
157
+ end
158
+ end
159
+
160
+ protected
161
+
162
+ # Serialize current session data as a Hash.
163
+ # Uses Base64 encoding for integrity.
164
+ def serialize
165
+ Base64.encode64(Marshal.dump(self.to_hash)).chop
166
+ end
167
+
168
+ # Unserialize the raw cookie data to a Hash
169
+ def unserialize(data)
170
+ Marshal.load(Base64.decode64(data)) rescue {}
171
+ end
172
+ end
173
+ end