actionpack 2.3.8 → 2.3.9.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +5 -0
- data/Rakefile +1 -1
- data/lib/action_controller/caching/fragments.rb +1 -1
- data/lib/action_controller/integration.rb +16 -5
- data/lib/action_controller/request.rb +2 -2
- data/lib/action_controller/rescue.rb +2 -2
- data/lib/action_controller/resources.rb +1 -1
- data/lib/action_controller/routing.rb +1 -1
- data/lib/action_controller/routing/route.rb +3 -3
- data/lib/action_controller/session/abstract_store.rb +152 -56
- data/lib/action_controller/session/cookie_store.rb +51 -16
- data/lib/action_controller/session/mem_cache_store.rb +9 -0
- data/lib/action_controller/test_process.rb +1 -1
- data/lib/action_controller/url_rewriter.rb +9 -1
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view/helpers/form_helper.rb +3 -3
- data/lib/action_view/helpers/form_options_helper.rb +1 -1
- data/lib/action_view/helpers/form_tag_helper.rb +1 -1
- data/lib/action_view/helpers/prototype_helper.rb +3 -3
- data/lib/action_view/helpers/text_helper.rb +27 -17
- data/lib/action_view/locale/en.yml +14 -14
- data/lib/action_view/template.rb +2 -2
- data/test/abstract_unit.rb +17 -0
- data/test/activerecord/active_record_store_test.rb +65 -18
- data/test/controller/integration_test.rb +26 -0
- data/test/controller/request/multipart_params_parsing_test.rb +15 -0
- data/test/controller/rescue_test.rb +5 -6
- data/test/controller/session/cookie_store_test.rb +80 -2
- data/test/controller/session/mem_cache_store_test.rb +65 -2
- data/test/controller/url_rewriter_test.rb +9 -3
- data/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
- data/test/template/form_helper_test.rb +17 -0
- data/test/template/form_options_helper_test.rb +4 -0
- data/test/template/text_helper_test.rb +47 -18
- metadata +14 -9
data/CHANGELOG
CHANGED
data/Rakefile
CHANGED
@@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s|
|
|
79
79
|
s.has_rdoc = true
|
80
80
|
s.requirements << 'none'
|
81
81
|
|
82
|
-
s.add_dependency('activesupport', '= 2.3.
|
82
|
+
s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
|
83
83
|
s.add_dependency('rack', '~> 1.1.0')
|
84
84
|
|
85
85
|
s.require_path = 'lib'
|
@@ -65,8 +65,8 @@ module ActionController #:nodoc:
|
|
65
65
|
def read_fragment(key, options = nil)
|
66
66
|
return unless cache_configured?
|
67
67
|
|
68
|
+
key = fragment_cache_key(key)
|
68
69
|
self.class.benchmark "Cached fragment hit: #{key}" do
|
69
|
-
key = fragment_cache_key(key)
|
70
70
|
result = cache_store.read(key, options)
|
71
71
|
result.respond_to?(:html_safe) ? result.html_safe : result
|
72
72
|
end
|
@@ -414,15 +414,25 @@ module ActionController
|
|
414
414
|
end
|
415
415
|
|
416
416
|
def multipart_requestify(params, first=true)
|
417
|
-
|
417
|
+
Array.new.tap do |p|
|
418
418
|
params.each do |key, value|
|
419
419
|
k = first ? key.to_s : "[#{key.to_s}]"
|
420
420
|
if Hash === value
|
421
421
|
multipart_requestify(value, false).each do |subkey, subvalue|
|
422
|
-
p[k + subkey
|
422
|
+
p << [k + subkey, subvalue]
|
423
|
+
end
|
424
|
+
elsif Array === value
|
425
|
+
value.each do |element|
|
426
|
+
if Hash === element || Array === element
|
427
|
+
multipart_requestify(element, false).each do |subkey, subvalue|
|
428
|
+
p << ["#{k}[]#{subkey}", subvalue]
|
429
|
+
end
|
430
|
+
else
|
431
|
+
p << ["#{k}[]", element]
|
432
|
+
end
|
423
433
|
end
|
424
434
|
else
|
425
|
-
p[k
|
435
|
+
p << [k, value]
|
426
436
|
end
|
427
437
|
end
|
428
438
|
end
|
@@ -453,6 +463,7 @@ EOF
|
|
453
463
|
end
|
454
464
|
end.join("")+"--#{boundary}--\r"
|
455
465
|
end
|
466
|
+
|
456
467
|
end
|
457
468
|
|
458
469
|
# A module used to extend ActionController::Base, so that integration tests
|
@@ -500,7 +511,7 @@ EOF
|
|
500
511
|
reset! unless @integration_session
|
501
512
|
# reset the html_document variable, but only for new get/post calls
|
502
513
|
@html_document = nil unless %w(cookies assigns).include?(method)
|
503
|
-
|
514
|
+
@integration_session.__send__(method, *args).tap do
|
504
515
|
copy_session_variables!
|
505
516
|
end
|
506
517
|
end
|
@@ -556,7 +567,7 @@ EOF
|
|
556
567
|
def method_missing(sym, *args, &block)
|
557
568
|
reset! unless @integration_session
|
558
569
|
if @integration_session.respond_to?(sym)
|
559
|
-
|
570
|
+
@integration_session.__send__(sym, *args, &block).tap do
|
560
571
|
copy_session_variables!
|
561
572
|
end
|
562
573
|
else
|
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
|
|
15
15
|
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
|
16
16
|
# and <tt>rescue_action_locally</tt> methods.
|
17
17
|
module Rescue
|
18
|
-
LOCALHOST = [
|
18
|
+
LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
|
19
19
|
|
20
20
|
DEFAULT_RESCUE_RESPONSE = :internal_server_error
|
21
21
|
DEFAULT_RESCUE_RESPONSES = {
|
@@ -122,7 +122,7 @@ module ActionController #:nodoc:
|
|
122
122
|
# method if you wish to redefine the meaning of a local request to
|
123
123
|
# include remote IP addresses or other criteria.
|
124
124
|
def local_request? #:doc:
|
125
|
-
LOCALHOST.any?{ |local_ip| request.remote_addr
|
125
|
+
LOCALHOST.any?{ |local_ip| request.remote_addr =~ local_ip && request.remote_ip =~ local_ip }
|
126
126
|
end
|
127
127
|
|
128
128
|
# Render detailed diagnostics for unhandled exceptions rescued from
|
@@ -659,7 +659,7 @@ module ActionController
|
|
659
659
|
end
|
660
660
|
|
661
661
|
def add_conditions_for(conditions, method)
|
662
|
-
|
662
|
+
({:conditions => conditions.dup}).tap do |options|
|
663
663
|
options[:conditions][:method] = method unless method == :any
|
664
664
|
end
|
665
665
|
end
|
@@ -377,7 +377,7 @@ module ActionController
|
|
377
377
|
ActiveSupport::Inflector.module_eval do
|
378
378
|
# Ensures that routes are reloaded when Rails inflections are updated.
|
379
379
|
def inflections_with_route_reloading(&block)
|
380
|
-
|
380
|
+
(inflections_without_route_reloading(&block)).tap {
|
381
381
|
ActionController::Routing::Routes.reload! if block_given?
|
382
382
|
}
|
383
383
|
end
|
@@ -65,7 +65,7 @@ module ActionController
|
|
65
65
|
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
66
66
|
#
|
67
67
|
def parameter_shell
|
68
|
-
@parameter_shell ||=
|
68
|
+
@parameter_shell ||= {}.tap do |shell|
|
69
69
|
requirements.each do |key, requirement|
|
70
70
|
shell[key] = requirement unless requirement.is_a? Regexp
|
71
71
|
end
|
@@ -76,7 +76,7 @@ module ActionController
|
|
76
76
|
# includes keys that appear inside the path, and keys that have requirements
|
77
77
|
# placed upon them.
|
78
78
|
def significant_keys
|
79
|
-
@significant_keys ||=
|
79
|
+
@significant_keys ||= [].tap do |sk|
|
80
80
|
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
81
81
|
sk.concat requirements.keys
|
82
82
|
sk.uniq!
|
@@ -86,7 +86,7 @@ module ActionController
|
|
86
86
|
# Return a hash of key/value pairs representing the keys in the route that
|
87
87
|
# have defaults, or which are specified by non-regexp requirements.
|
88
88
|
def defaults
|
89
|
-
@defaults ||=
|
89
|
+
@defaults ||= {}.tap do |hash|
|
90
90
|
segments.each do |segment|
|
91
91
|
next unless segment.respond_to? :default
|
92
92
|
hash[segment.key] = segment.default unless segment.default.nil?
|
@@ -2,13 +2,42 @@ require 'rack/utils'
|
|
2
2
|
|
3
3
|
module ActionController
|
4
4
|
module Session
|
5
|
-
class AbstractStore
|
5
|
+
class AbstractStore
|
6
6
|
ENV_SESSION_KEY = 'rack.session'.freeze
|
7
7
|
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
8
8
|
|
9
9
|
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
10
10
|
SET_COOKIE = 'Set-Cookie'.freeze
|
11
11
|
|
12
|
+
# thin wrapper around Hash that allows us to lazily
|
13
|
+
# load session id into session_options
|
14
|
+
class OptionsHash < Hash
|
15
|
+
def initialize(by, env, default_options)
|
16
|
+
@by = by
|
17
|
+
@env = env
|
18
|
+
@session_id_loaded = false
|
19
|
+
merge!(default_options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
if key == :id
|
24
|
+
load_session_id! unless super(:id) || has_session_id?
|
25
|
+
end
|
26
|
+
super(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def has_session_id?
|
32
|
+
@session_id_loaded
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_session_id!
|
36
|
+
self[:id] = @by.send(:extract_session_id, @env)
|
37
|
+
@session_id_loaded = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
12
41
|
class SessionHash < Hash
|
13
42
|
def initialize(by, env)
|
14
43
|
super()
|
@@ -25,21 +54,42 @@ module ActionController
|
|
25
54
|
end
|
26
55
|
|
27
56
|
def [](key)
|
28
|
-
|
57
|
+
load_for_read!
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_key?(key)
|
62
|
+
load_for_read!
|
29
63
|
super
|
30
64
|
end
|
31
65
|
|
32
66
|
def []=(key, value)
|
33
|
-
|
67
|
+
load_for_write!
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear
|
72
|
+
load_for_write!
|
34
73
|
super
|
35
74
|
end
|
36
75
|
|
37
76
|
def to_hash
|
77
|
+
load_for_read!
|
38
78
|
h = {}.replace(self)
|
39
79
|
h.delete_if { |k,v| v.nil? }
|
40
80
|
h
|
41
81
|
end
|
42
82
|
|
83
|
+
def update(hash)
|
84
|
+
load_for_write!
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def delete(key)
|
89
|
+
load_for_write!
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
43
93
|
def data
|
44
94
|
ActiveSupport::Deprecation.warn(
|
45
95
|
"ActionController::Session::AbstractStore::SessionHash#data " +
|
@@ -48,40 +98,43 @@ module ActionController
|
|
48
98
|
end
|
49
99
|
|
50
100
|
def inspect
|
51
|
-
|
101
|
+
load_for_read!
|
52
102
|
super
|
53
103
|
end
|
54
104
|
|
105
|
+
def exists?
|
106
|
+
return @exists if instance_variable_defined?(:@exists)
|
107
|
+
@exists = @by.send(:exists?, @env)
|
108
|
+
end
|
109
|
+
|
110
|
+
def loaded?
|
111
|
+
@loaded
|
112
|
+
end
|
113
|
+
|
114
|
+
def destroy
|
115
|
+
clear
|
116
|
+
@by.send(:destroy, @env) if @by
|
117
|
+
@env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY]
|
118
|
+
@loaded = false
|
119
|
+
end
|
120
|
+
|
55
121
|
private
|
56
|
-
|
57
|
-
|
122
|
+
|
123
|
+
def load_for_read!
|
124
|
+
load! if !loaded? && exists?
|
58
125
|
end
|
59
126
|
|
60
|
-
def
|
61
|
-
|
62
|
-
id, session = @by.send(:load_session, @env)
|
63
|
-
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
|
64
|
-
replace(session)
|
65
|
-
@loaded = true
|
66
|
-
end
|
127
|
+
def load_for_write!
|
128
|
+
load! unless loaded?
|
67
129
|
end
|
68
130
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# Note that the regexp does not allow $1 to end with a ':'
|
75
|
-
$1.constantize
|
76
|
-
rescue LoadError, NameError => const_error
|
77
|
-
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
78
|
-
end
|
79
|
-
|
80
|
-
retry
|
81
|
-
else
|
82
|
-
raise
|
83
|
-
end
|
131
|
+
def load!
|
132
|
+
id, session = @by.send(:load_session, @env)
|
133
|
+
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
134
|
+
replace(session)
|
135
|
+
@loaded = true
|
84
136
|
end
|
137
|
+
|
85
138
|
end
|
86
139
|
|
87
140
|
DEFAULT_OPTIONS = {
|
@@ -120,18 +173,14 @@ module ActionController
|
|
120
173
|
end
|
121
174
|
|
122
175
|
def call(env)
|
123
|
-
|
124
|
-
|
125
|
-
env[ENV_SESSION_KEY] = session
|
126
|
-
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
127
|
-
|
176
|
+
prepare!(env)
|
128
177
|
response = @app.call(env)
|
129
178
|
|
130
179
|
session_data = env[ENV_SESSION_KEY]
|
131
180
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
132
181
|
|
133
|
-
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.
|
134
|
-
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.
|
182
|
+
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
|
183
|
+
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
135
184
|
|
136
185
|
sid = options[:id] || generate_sid
|
137
186
|
|
@@ -139,21 +188,23 @@ module ActionController
|
|
139
188
|
return response
|
140
189
|
end
|
141
190
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
headers[SET_COOKIE]
|
155
|
-
|
156
|
-
|
191
|
+
if (env["rack.request.cookie_hash"] && env["rack.request.cookie_hash"][@key] != sid) || options[:expire_after]
|
192
|
+
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
|
193
|
+
cookie << "; domain=#{options[:domain]}" if options[:domain]
|
194
|
+
cookie << "; path=#{options[:path]}" if options[:path]
|
195
|
+
if options[:expire_after]
|
196
|
+
expiry = Time.now + options[:expire_after]
|
197
|
+
cookie << "; expires=#{expiry.httpdate}"
|
198
|
+
end
|
199
|
+
cookie << "; Secure" if options[:secure]
|
200
|
+
cookie << "; HttpOnly" if options[:httponly]
|
201
|
+
|
202
|
+
headers = response[1]
|
203
|
+
unless headers[SET_COOKIE].blank?
|
204
|
+
headers[SET_COOKIE] << "\n#{cookie}"
|
205
|
+
else
|
206
|
+
headers[SET_COOKIE] = cookie
|
207
|
+
end
|
157
208
|
end
|
158
209
|
end
|
159
210
|
|
@@ -161,18 +212,39 @@ module ActionController
|
|
161
212
|
end
|
162
213
|
|
163
214
|
private
|
215
|
+
|
216
|
+
def prepare!(env)
|
217
|
+
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
218
|
+
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
219
|
+
end
|
220
|
+
|
164
221
|
def generate_sid
|
165
222
|
ActiveSupport::SecureRandom.hex(16)
|
166
223
|
end
|
167
224
|
|
168
225
|
def load_session(env)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
sid
|
226
|
+
stale_session_check! do
|
227
|
+
sid = current_session_id(env)
|
228
|
+
sid, session = get_session(env, sid)
|
229
|
+
[sid, session]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def extract_session_id(env)
|
234
|
+
stale_session_check! do
|
235
|
+
request = Rack::Request.new(env)
|
236
|
+
sid = request.cookies[@key]
|
237
|
+
sid ||= request.params[@key] unless @cookie_only
|
238
|
+
sid
|
173
239
|
end
|
174
|
-
|
175
|
-
|
240
|
+
end
|
241
|
+
|
242
|
+
def current_session_id(env)
|
243
|
+
env[ENV_SESSION_OPTIONS_KEY][:id]
|
244
|
+
end
|
245
|
+
|
246
|
+
def exists?(env)
|
247
|
+
current_session_id(env).present?
|
176
248
|
end
|
177
249
|
|
178
250
|
def get_session(env, sid)
|
@@ -182,6 +254,30 @@ module ActionController
|
|
182
254
|
def set_session(env, sid, session_data)
|
183
255
|
raise '#set_session needs to be implemented.'
|
184
256
|
end
|
257
|
+
|
258
|
+
def destroy(env)
|
259
|
+
raise '#destroy needs to be implemented.'
|
260
|
+
end
|
261
|
+
|
262
|
+
module SessionUtils
|
263
|
+
private
|
264
|
+
def stale_session_check!
|
265
|
+
yield
|
266
|
+
rescue ArgumentError => argument_error
|
267
|
+
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
268
|
+
begin
|
269
|
+
# Note that the regexp does not allow $1 to end with a ':'
|
270
|
+
$1.constantize
|
271
|
+
rescue LoadError, NameError => const_error
|
272
|
+
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
273
|
+
end
|
274
|
+
retry
|
275
|
+
else
|
276
|
+
raise
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
include SessionUtils
|
185
281
|
end
|
186
282
|
end
|
187
283
|
end
|