merb-core 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CONTRIBUTORS +33 -0
  2. data/README +7 -3
  3. data/Rakefile +3 -3
  4. data/lib/merb-core.rb +165 -94
  5. data/lib/merb-core/bootloader.rb +469 -100
  6. data/lib/merb-core/config.rb +79 -3
  7. data/lib/merb-core/constants.rb +24 -2
  8. data/lib/merb-core/controller/abstract_controller.rb +172 -67
  9. data/lib/merb-core/controller/exceptions.rb +50 -6
  10. data/lib/merb-core/controller/merb_controller.rb +215 -108
  11. data/lib/merb-core/controller/mime.rb +36 -12
  12. data/lib/merb-core/controller/mixins/authentication.rb +52 -7
  13. data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
  14. data/lib/merb-core/controller/mixins/controller.rb +90 -58
  15. data/lib/merb-core/controller/mixins/render.rb +34 -10
  16. data/lib/merb-core/controller/mixins/responder.rb +40 -16
  17. data/lib/merb-core/controller/template.rb +37 -16
  18. data/lib/merb-core/core_ext/hash.rb +9 -0
  19. data/lib/merb-core/core_ext/kernel.rb +92 -41
  20. data/lib/merb-core/dispatch/dispatcher.rb +29 -45
  21. data/lib/merb-core/dispatch/request.rb +186 -82
  22. data/lib/merb-core/dispatch/router.rb +141 -53
  23. data/lib/merb-core/dispatch/router/behavior.rb +296 -139
  24. data/lib/merb-core/dispatch/router/resources.rb +51 -19
  25. data/lib/merb-core/dispatch/router/route.rb +76 -23
  26. data/lib/merb-core/dispatch/session.rb +80 -36
  27. data/lib/merb-core/dispatch/session/container.rb +31 -15
  28. data/lib/merb-core/dispatch/session/cookie.rb +51 -22
  29. data/lib/merb-core/dispatch/session/memcached.rb +10 -6
  30. data/lib/merb-core/dispatch/session/memory.rb +17 -5
  31. data/lib/merb-core/dispatch/session/store_container.rb +21 -9
  32. data/lib/merb-core/dispatch/worker.rb +16 -2
  33. data/lib/merb-core/gem_ext/erubis.rb +4 -0
  34. data/lib/merb-core/plugins.rb +13 -0
  35. data/lib/merb-core/rack.rb +1 -0
  36. data/lib/merb-core/rack/adapter.rb +1 -0
  37. data/lib/merb-core/rack/adapter/abstract.rb +95 -17
  38. data/lib/merb-core/rack/adapter/irb.rb +50 -5
  39. data/lib/merb-core/rack/application.rb +27 -5
  40. data/lib/merb-core/rack/handler/mongrel.rb +6 -6
  41. data/lib/merb-core/rack/helpers.rb +33 -0
  42. data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
  43. data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
  44. data/lib/merb-core/rack/middleware/static.rb +11 -7
  45. data/lib/merb-core/server.rb +134 -69
  46. data/lib/merb-core/tasks/gem_management.rb +153 -80
  47. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
  48. data/lib/merb-core/tasks/stats.rake +1 -1
  49. data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
  50. data/lib/merb-core/test/helpers/request_helper.rb +1 -1
  51. data/lib/merb-core/test/helpers/route_helper.rb +50 -4
  52. data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
  53. data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
  54. data/lib/merb-core/test/run_specs.rb +6 -5
  55. data/lib/merb-core/test/test_ext/rspec.rb +6 -19
  56. data/lib/merb-core/version.rb +1 -1
  57. metadata +5 -4
@@ -1,41 +1,49 @@
1
1
  module Merb
2
2
  class SessionContainer < Mash
3
-
3
+
4
4
  class_inheritable_accessor :session_store_type
5
5
  cattr_accessor :subclasses
6
6
  self.subclasses = []
7
-
7
+
8
8
  attr_reader :session_id
9
9
  attr_accessor :needs_new_cookie
10
-
10
+
11
11
  class << self
12
-
12
+
13
13
  # Register the subclass as an available session store type.
14
14
  def inherited(klass)
15
15
  self.subclasses << klass.to_s
16
16
  super
17
17
  end
18
-
18
+
19
19
  # Generates a new session ID and creates a new session.
20
20
  #
21
21
  # ==== Returns
22
22
  # SessionContainer:: The new session.
23
+ #
24
+ # @api private
23
25
  def generate
24
26
  end
25
-
27
+
26
28
  # ==== Parameters
27
29
  # request<Merb::Request>:: The Merb::Request that came in from Rack.
28
30
  #
31
+ # ==== Notes
32
+ # If no sessions were found, a new SessionContainer will be generated.
33
+ #
29
34
  # ==== Returns
30
- # SessionContainer:: a SessionContainer. If no sessions were found,
31
- # a new SessionContainer will be generated.
35
+ # SessionContainer:: a SessionContainer.
36
+ #
37
+ # @api private
32
38
  def setup(request)
33
- end
34
-
39
+ end
40
+
35
41
  end
36
-
42
+
37
43
  # ==== Parameters
38
44
  # session_id<String>:: A unique identifier for this session.
45
+ #
46
+ # @api private
39
47
  def initialize(session_id)
40
48
  @_destroy = false
41
49
  self.session_id = session_id
@@ -45,11 +53,13 @@ module Merb
45
53
  #
46
54
  # Recreates the cookie with the default expiration time. Useful during log
47
55
  # in for pushing back the expiration date.
56
+ #
57
+ # @api private
48
58
  def session_id=(sid)
49
59
  self.needs_new_cookie = (@session_id && @session_id != sid)
50
60
  @session_id = sid
51
61
  end
52
-
62
+
53
63
  # Teardown and/or persist the current session.
54
64
  #
55
65
  # If @_destroy is true, clear out the session completely, including
@@ -57,18 +67,24 @@ module Merb
57
67
  #
58
68
  # ==== Parameters
59
69
  # request<Merb::Request>:: The Merb::Request that came in from Rack.
70
+ #
71
+ # @api private
60
72
  def finalize(request)
61
73
  end
62
-
74
+
63
75
  # Destroy the current session - clears data and removes session cookie.
76
+ #
77
+ # @api private
64
78
  def clear!
65
79
  @_destroy = true
66
80
  self.clear
67
81
  end
68
-
82
+
69
83
  # Regenerate the session_id.
84
+ #
85
+ # @api private
70
86
  def regenerate
71
87
  end
72
-
88
+
73
89
  end
74
90
  end
@@ -1,7 +1,7 @@
1
1
  require 'base64' # to convert Marshal.dump to ASCII
2
2
  require 'openssl' # to generate the HMAC message digest
3
3
  module Merb
4
-
4
+
5
5
  # If you have more than 4K of session data or don't want your data to be
6
6
  # visible to the user, pick another session store.
7
7
  #
@@ -19,31 +19,33 @@ module Merb
19
19
  # TODO (maybe):
20
20
  # include request ip address
21
21
  # AES encrypt marshaled data
22
-
22
+
23
23
  # Raised when storing more than 4K of session data.
24
24
  class CookieOverflow < StandardError; end
25
-
25
+
26
26
  # Raised when the cookie fails its integrity check.
27
27
  class TamperedWithCookie < StandardError; end
28
-
28
+
29
29
  # Cookies can typically store 4096 bytes.
30
30
  MAX = 4096
31
31
  DIGEST = OpenSSL::Digest::Digest.new('SHA1') # or MD5, RIPEMD160, SHA256?
32
-
32
+
33
33
  attr_accessor :_original_session_data
34
-
34
+
35
35
  # The session store type
36
36
  self.session_store_type = :cookie
37
-
37
+
38
38
  class << self
39
39
  # Generates a new session ID and creates a new session.
40
40
  #
41
41
  # ==== Returns
42
42
  # SessionContainer:: The new session.
43
+ #
44
+ # @api private
43
45
  def generate
44
46
  self.new(Merb::SessionMixin.rand_uuid, "", Merb::Request._session_secret_key)
45
47
  end
46
-
48
+
47
49
  # Set up a new session on request: make it available on request instance.
48
50
  #
49
51
  # ==== Parameters
@@ -52,22 +54,26 @@ module Merb
52
54
  # ==== Returns
53
55
  # SessionContainer:: a SessionContainer. If no sessions were found,
54
56
  # a new SessionContainer will be generated.
57
+ #
58
+ # @api private
55
59
  def setup(request)
56
60
  session = self.new(Merb::SessionMixin.rand_uuid,
57
61
  request.session_cookie_value, request._session_secret_key)
58
62
  session._original_session_data = session.to_cookie
59
63
  request.session = session
60
64
  end
61
-
65
+
62
66
  end
63
-
67
+
64
68
  # ==== Parameters
65
69
  # session_id<String>:: A unique identifier for this session.
66
70
  # cookie<String>:: The raw cookie data.
67
71
  # secret<String>:: A session secret.
68
72
  #
69
73
  # ==== Raises
70
- # ArgumentError:: Nil or blank secret.
74
+ # ArgumentError:: blank or insufficiently long secret.
75
+ #
76
+ # @api private
71
77
  def initialize(session_id, cookie, secret)
72
78
  super session_id
73
79
  if secret.blank? || secret.length < 16
@@ -78,7 +84,7 @@ module Merb
78
84
  @secret = secret
79
85
  self.update(unmarshal(cookie))
80
86
  end
81
-
87
+
82
88
  # Teardown and/or persist the current session.
83
89
  #
84
90
  # If @_destroy is true, clear out the session completely, including
@@ -86,6 +92,8 @@ module Merb
86
92
  #
87
93
  # ==== Parameters
88
94
  # request<Merb::Request>:: request object created from Rack environment.
95
+ #
96
+ # @api private
89
97
  def finalize(request)
90
98
  if @_destroy
91
99
  request.destroy_session_cookie
@@ -95,10 +103,12 @@ module Merb
95
103
  end
96
104
 
97
105
  # Regenerate the session_id.
106
+ #
107
+ # @api private
98
108
  def regenerate
99
109
  self.session_id = Merb::SessionMixin.rand_uuid
100
110
  end
101
-
111
+
102
112
  # Create the raw cookie string; includes an HMAC keyed message digest.
103
113
  #
104
114
  # ==== Returns
@@ -111,6 +121,8 @@ module Merb
111
121
  # Session data is converted to a Hash first, since a container might
112
122
  # choose to marshal it, which would make it persist
113
123
  # attributes like 'needs_new_cookie', which it shouldn't.
124
+ #
125
+ # @api private
114
126
  def to_cookie
115
127
  unless self.empty?
116
128
  data = self.serialize
@@ -123,24 +135,31 @@ module Merb
123
135
  value
124
136
  end
125
137
  end
126
-
138
+
127
139
  private
128
-
140
+
129
141
  # Generate the HMAC keyed message digest. Uses SHA1.
142
+ #
143
+ # ==== Returns
144
+ # String:: an HMAC digest of the cookie data.
145
+ #
146
+ # @api private
130
147
  def generate_digest(data)
131
148
  OpenSSL::HMAC.hexdigest(DIGEST, @secret, data)
132
149
  end
133
-
150
+
134
151
  # Unmarshal cookie data to a hash and verify its integrity.
135
- #
152
+ #
136
153
  # ==== Parameters
137
154
  # cookie<~to_s>:: The cookie to unmarshal.
138
- #
155
+ #
139
156
  # ==== Raises
140
157
  # TamperedWithCookie:: The digests don't match.
141
- #
158
+ #
142
159
  # ==== Returns
143
160
  # Hash:: The stored session data.
161
+ #
162
+ # @api private
144
163
  def unmarshal(cookie)
145
164
  if cookie.blank?
146
165
  {}
@@ -156,16 +175,26 @@ module Merb
156
175
  unserialize(data)
157
176
  end
158
177
  end
159
-
178
+
160
179
  protected
161
-
180
+
162
181
  # Serialize current session data as a Hash.
163
182
  # Uses Base64 encoding for integrity.
183
+ #
184
+ # ==== Returns
185
+ # String:: Base64 encoded dump of the session hash.
186
+ #
187
+ # @api private
164
188
  def serialize
165
189
  Base64.encode64(Marshal.dump(self.to_hash)).chop
166
190
  end
167
-
191
+
168
192
  # Unserialize the raw cookie data to a Hash
193
+ #
194
+ # ==== Returns
195
+ # Hash:: the session hash Base64 decoded from the data dump.
196
+ #
197
+ # @api private
169
198
  def unserialize(data)
170
199
  Marshal.load(Base64.decode64(data)) rescue {}
171
200
  end
@@ -28,33 +28,37 @@ module Merb
28
28
  end
29
29
 
30
30
  module MemcacheStore
31
-
31
+
32
32
  # Make the Memcached gem conform to the SessionStoreContainer interface
33
-
33
+
34
34
  # ==== Parameters
35
35
  # session_id<String>:: ID of the session to retrieve.
36
36
  #
37
37
  # ==== Returns
38
38
  # ContainerSession:: The session corresponding to the ID.
39
+ #
40
+ # @api private
39
41
  def retrieve_session(session_id)
40
42
  get("session:#{session_id}")
41
43
  end
42
-
44
+
43
45
  # ==== Parameters
44
46
  # session_id<String>:: ID of the session to set.
45
47
  # data<ContainerSession>:: The session to set.
48
+ #
49
+ # @api private
46
50
  def store_session(session_id, data)
47
51
  set("session:#{session_id}", data)
48
52
  end
49
-
53
+
50
54
  # ==== Parameters
51
55
  # session_id<String>:: ID of the session to delete.
52
56
  def delete_session(session_id)
53
57
  delete("session:#{session_id}")
54
58
  end
55
-
59
+
56
60
  end
57
-
61
+
58
62
  end
59
63
 
60
64
  # For the memcached gem.
@@ -34,9 +34,11 @@ module Merb
34
34
 
35
35
  # Used for handling multiple sessions stored in memory.
36
36
  class MemorySessionStore
37
-
37
+
38
38
  # ==== Parameters
39
39
  # ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
40
+ #
41
+ # @api private
40
42
  def initialize(ttl=nil)
41
43
  @sessions = Hash.new
42
44
  @timestamps = Hash.new
@@ -50,41 +52,51 @@ module Merb
50
52
  #
51
53
  # ==== Returns
52
54
  # ContainerSession:: The session corresponding to the ID.
55
+ #
56
+ # @api private
53
57
  def retrieve_session(session_id)
54
58
  @mutex.synchronize {
55
59
  @timestamps[session_id] = Time.now
56
60
  @sessions[session_id]
57
61
  }
58
62
  end
59
-
63
+
60
64
  # ==== Parameters
61
65
  # session_id<String>:: ID of the session to set.
62
66
  # data<ContainerSession>:: The session to set.
67
+ #
68
+ # @api private
63
69
  def store_session(session_id, data)
64
70
  @mutex.synchronize {
65
71
  @timestamps[session_id] = Time.now
66
72
  @sessions[session_id] = data
67
73
  }
68
74
  end
69
-
75
+
70
76
  # ==== Parameters
71
77
  # session_id<String>:: ID of the session to delete.
78
+ #
79
+ # @api private
72
80
  def delete_session(session_id)
73
81
  @mutex.synchronize {
74
82
  @timestamps.delete(session_id)
75
83
  @sessions.delete(session_id)
76
84
  }
77
85
  end
78
-
86
+
79
87
  # Deletes any sessions that have reached their maximum validity.
88
+ #
89
+ # @api private
80
90
  def reap_expired_sessions
81
91
  @timestamps.each do |session_id,stamp|
82
92
  delete_session(session_id) if (stamp + @session_ttl) < Time.now
83
93
  end
84
94
  GC.start
85
95
  end
86
-
96
+
87
97
  # Starts the timer that will eventually reap outdated sessions.
98
+ #
99
+ # @api private
88
100
  def start_timer
89
101
  Thread.new do
90
102
  loop {
@@ -45,25 +45,31 @@ module Merb
45
45
  self.session_store_type = :store
46
46
 
47
47
  class << self
48
-
48
+
49
49
  # Generates a new session ID and creates a new session.
50
- #
50
+ #
51
51
  # ==== Returns
52
52
  # SessionStoreContainer:: The new session.
53
+ #
54
+ # @api private
53
55
  def generate
54
56
  session = new(Merb::SessionMixin.rand_uuid)
55
57
  session.needs_new_cookie = true
56
58
  session
57
59
  end
58
-
59
- # Setup a new session.
60
- #
60
+
61
+ # Setup a new session or retreive an existing session.
62
+ #
61
63
  # ==== Parameters
62
64
  # request<Merb::Request>:: The Merb::Request that came in from Rack.
63
- #
65
+ #
66
+ # ==== Notes
67
+ # If no sessions were found, a new SessionContainer will be generated.
68
+ #
64
69
  # ==== Returns
65
- # SessionContainer:: a SessionContainer. If no sessions were found,
66
- # a new SessionContainer will be generated.
70
+ # SessionContainer:: a SessionContainer.
71
+ #
72
+ # @api private
67
73
  def setup(request)
68
74
  session = retrieve(request.session_id)
69
75
  request.session = session
@@ -84,6 +90,8 @@ module Merb
84
90
  # ==== Notes
85
91
  # If there are persisted exceptions callbacks to execute, they all get executed
86
92
  # when Memcache library raises an exception.
93
+ #
94
+ # @api private
87
95
  def retrieve(session_id)
88
96
  unless session_id.blank?
89
97
  begin
@@ -121,6 +129,8 @@ module Merb
121
129
  # The data (self) is converted to a Hash first, since a container might
122
130
  # choose to do a full Marshal on the data, which would make it persist
123
131
  # attributes like 'needs_new_cookie', which it shouldn't.
132
+ #
133
+ # @api private
124
134
  def finalize(request)
125
135
  if @_destroy
126
136
  store.delete_session(self.session_id)
@@ -138,8 +148,10 @@ module Merb
138
148
  end
139
149
  end
140
150
  end
141
-
151
+
142
152
  # Regenerate the session ID.
153
+ #
154
+ # @api private
143
155
  def regenerate
144
156
  store.delete_session(self.session_id)
145
157
  self.session_id = Merb::SessionMixin.rand_uuid