merb-core 0.9.8 → 0.9.9

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 (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