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.
- data/CONTRIBUTORS +33 -0
- data/README +7 -3
- data/Rakefile +3 -3
- data/lib/merb-core.rb +165 -94
- data/lib/merb-core/bootloader.rb +469 -100
- data/lib/merb-core/config.rb +79 -3
- data/lib/merb-core/constants.rb +24 -2
- data/lib/merb-core/controller/abstract_controller.rb +172 -67
- data/lib/merb-core/controller/exceptions.rb +50 -6
- data/lib/merb-core/controller/merb_controller.rb +215 -108
- data/lib/merb-core/controller/mime.rb +36 -12
- data/lib/merb-core/controller/mixins/authentication.rb +52 -7
- data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
- data/lib/merb-core/controller/mixins/controller.rb +90 -58
- data/lib/merb-core/controller/mixins/render.rb +34 -10
- data/lib/merb-core/controller/mixins/responder.rb +40 -16
- data/lib/merb-core/controller/template.rb +37 -16
- data/lib/merb-core/core_ext/hash.rb +9 -0
- data/lib/merb-core/core_ext/kernel.rb +92 -41
- data/lib/merb-core/dispatch/dispatcher.rb +29 -45
- data/lib/merb-core/dispatch/request.rb +186 -82
- data/lib/merb-core/dispatch/router.rb +141 -53
- data/lib/merb-core/dispatch/router/behavior.rb +296 -139
- data/lib/merb-core/dispatch/router/resources.rb +51 -19
- data/lib/merb-core/dispatch/router/route.rb +76 -23
- data/lib/merb-core/dispatch/session.rb +80 -36
- data/lib/merb-core/dispatch/session/container.rb +31 -15
- data/lib/merb-core/dispatch/session/cookie.rb +51 -22
- data/lib/merb-core/dispatch/session/memcached.rb +10 -6
- data/lib/merb-core/dispatch/session/memory.rb +17 -5
- data/lib/merb-core/dispatch/session/store_container.rb +21 -9
- data/lib/merb-core/dispatch/worker.rb +16 -2
- data/lib/merb-core/gem_ext/erubis.rb +4 -0
- data/lib/merb-core/plugins.rb +13 -0
- data/lib/merb-core/rack.rb +1 -0
- data/lib/merb-core/rack/adapter.rb +1 -0
- data/lib/merb-core/rack/adapter/abstract.rb +95 -17
- data/lib/merb-core/rack/adapter/irb.rb +50 -5
- data/lib/merb-core/rack/application.rb +27 -5
- data/lib/merb-core/rack/handler/mongrel.rb +6 -6
- data/lib/merb-core/rack/helpers.rb +33 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
- data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
- data/lib/merb-core/rack/middleware/static.rb +11 -7
- data/lib/merb-core/server.rb +134 -69
- data/lib/merb-core/tasks/gem_management.rb +153 -80
- data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
- data/lib/merb-core/tasks/stats.rake +1 -1
- data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
- data/lib/merb-core/test/helpers/request_helper.rb +1 -1
- data/lib/merb-core/test/helpers/route_helper.rb +50 -4
- data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
- data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
- data/lib/merb-core/test/run_specs.rb +6 -5
- data/lib/merb-core/test/test_ext/rspec.rb +6 -19
- data/lib/merb-core/version.rb +1 -1
- 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.
|
31
|
-
#
|
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::
|
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.
|
66
|
-
#
|
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
|