wycats-merb-core 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +458 -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 +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -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 +319 -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 +340 -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 +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -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 +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -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 +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -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