actionpack 1.13.3 → 1.13.4
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 +44 -2
- data/Rakefile +1 -1
- data/lib/action_controller/assertions/dom_assertions.rb +2 -2
- data/lib/action_controller/assertions/model_assertions.rb +1 -1
- data/lib/action_controller/assertions/response_assertions.rb +2 -0
- data/lib/action_controller/assertions/routing_assertions.rb +1 -0
- data/lib/action_controller/base.rb +7 -1
- data/lib/action_controller/caching.rb +39 -38
- data/lib/action_controller/cgi_ext/pstore_performance_fix.rb +30 -0
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +1 -1
- data/lib/action_controller/cgi_process.rb +13 -4
- data/lib/action_controller/cookies.rb +5 -3
- data/lib/action_controller/filters.rb +176 -77
- data/lib/action_controller/integration.rb +31 -21
- data/lib/action_controller/pagination.rb +7 -1
- data/lib/action_controller/resources.rb +117 -32
- data/lib/action_controller/routing.rb +41 -1
- data/lib/action_controller/test_process.rb +5 -2
- data/lib/action_controller/url_rewriter.rb +4 -1
- data/lib/action_controller/verification.rb +1 -0
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view/base.rb +25 -19
- data/lib/action_view/compiled_templates.rb +2 -2
- data/lib/action_view/helpers/active_record_helper.rb +18 -18
- data/lib/action_view/helpers/debug_helper.rb +10 -0
- data/lib/action_view/helpers/deprecated_helper.rb +3 -0
- data/lib/action_view/helpers/prototype_helper.rb +33 -17
- data/test/activerecord/pagination_test.rb +9 -0
- data/test/controller/addresses_render_test.rb +4 -1
- data/test/controller/base_test.rb +1 -1
- data/test/controller/caching_test.rb +3 -2
- data/test/controller/cookie_test.rb +11 -0
- data/test/controller/deprecation/deprecated_base_methods_test.rb +18 -0
- data/test/controller/filter_params_test.rb +1 -0
- data/test/controller/filters_test.rb +149 -26
- data/test/controller/integration_test.rb +93 -8
- data/test/controller/resources_test.rb +215 -36
- data/test/controller/routing_test.rb +1 -1
- data/test/controller/test_test.rb +16 -0
- data/test/controller/url_rewriter_test.rb +13 -1
- data/test/controller/verification_test.rb +15 -0
- data/test/fixtures/test/hello_world.rxml +2 -1
- data/test/template/asset_tag_helper_test.rb +5 -0
- data/test/template/compiled_templates_test.rb +29 -17
- data/test/template/number_helper_test.rb +1 -1
- data/test/template/prototype_helper_test.rb +2 -2
- metadata +84 -83
data/CHANGELOG
CHANGED
@@ -1,6 +1,48 @@
|
|
1
|
+
*1.13.4* (October 4th, 2007)
|
2
|
+
|
3
|
+
* Only accept session ids from cookies, prevents session fixation attacks. [bradediger]
|
4
|
+
|
5
|
+
* Change the resource seperator from ; to / change the generated routes to use the new-style named routes. e.g. new_group_user_path(@group) instead of group_new_user_path(@group). [pixeltrix]
|
6
|
+
|
7
|
+
* Integration tests: introduce methods for other HTTP methods. #6353 [caboose]
|
8
|
+
|
9
|
+
* Improve performance of action caching. Closes #8231 [skaes]
|
10
|
+
|
11
|
+
* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes]
|
12
|
+
|
13
|
+
After filters will *no longer* be run if an around_filter fails to yield, users relying on
|
14
|
+
this behaviour are advised to put the code in question after a yield statement in an around filter.
|
15
|
+
|
16
|
+
* Allow you to delete cookies with options. Closes #3685 [josh, Chris Wanstrath]
|
17
|
+
|
18
|
+
* Deprecate pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 [Mislav Marohnic]
|
19
|
+
|
20
|
+
* Fix filtered parameter logging with nil parameter values. #8422 [choonkeat]
|
21
|
+
|
22
|
+
* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 [Nik Wakelin, Francois Beausoleil, Wizard]
|
23
|
+
|
24
|
+
* Document caches_action. #5419 [Jarkko Laine]
|
25
|
+
|
26
|
+
* observe_form always sends the serialized form. #5271 [manfred, normelton@gmail.com]
|
27
|
+
|
28
|
+
* Update UrlWriter to accept :anchor parameter. Closes #6771. [octopod]
|
29
|
+
|
30
|
+
* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. Closes #8226 [Stefan Kaes]
|
31
|
+
|
32
|
+
* Return the string representation from an Xml Builder when rendering a partial. #5044 [tpope]
|
33
|
+
|
34
|
+
* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 [jeremymcanally]
|
35
|
+
|
36
|
+
* Small collection of ActionController documentation cleanups. Closes #7319 [jeremymcanally]
|
37
|
+
|
38
|
+
* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 [Stefan Kaes]
|
39
|
+
|
40
|
+
* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 [Justin French]
|
41
|
+
|
42
|
+
|
1
43
|
*1.13.3* (March 12th, 2007)
|
2
44
|
|
3
|
-
*
|
45
|
+
* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. #6752 [Nicholas Seckar]
|
4
46
|
|
5
47
|
* session_enabled? works with session :off. #6680 [Catfish]
|
6
48
|
|
@@ -440,7 +482,7 @@
|
|
440
482
|
|
441
483
|
* Avoid naming collision among compiled view methods. [Jeremy Kemper]
|
442
484
|
|
443
|
-
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [
|
485
|
+
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [Mislav Marohnic]
|
444
486
|
|
445
487
|
* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
|
446
488
|
|
data/Rakefile
CHANGED
@@ -75,7 +75,7 @@ spec = Gem::Specification.new do |s|
|
|
75
75
|
s.has_rdoc = true
|
76
76
|
s.requirements << 'none'
|
77
77
|
|
78
|
-
s.add_dependency('activesupport', '= 1.4.
|
78
|
+
s.add_dependency('activesupport', '= 1.4.3' + PKG_BUILD)
|
79
79
|
|
80
80
|
s.require_path = 'lib'
|
81
81
|
s.autorequire = 'action_controller'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActionController
|
2
2
|
module Assertions
|
3
3
|
module DomAssertions
|
4
|
-
#
|
4
|
+
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
5
5
|
def assert_dom_equal(expected, actual, message="")
|
6
6
|
clean_backtrace do
|
7
7
|
expected_dom = HTML::Document.new(expected).root
|
@@ -11,7 +11,7 @@ module ActionController
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
# negated form of +assert_dom_equivalent
|
14
|
+
# The negated form of +assert_dom_equivalent+.
|
15
15
|
def assert_dom_not_equal(expected, actual, message="")
|
16
16
|
clean_backtrace do
|
17
17
|
expected_dom = HTML::Document.new(expected).root
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActionController
|
2
2
|
module Assertions
|
3
3
|
module ModelAssertions
|
4
|
-
#
|
4
|
+
# Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not.
|
5
5
|
def assert_valid(record)
|
6
6
|
clean_backtrace do
|
7
7
|
assert record.valid?, record.errors.full_messages.join("\n")
|
@@ -120,6 +120,7 @@ module ActionController
|
|
120
120
|
end
|
121
121
|
|
122
122
|
private
|
123
|
+
# Recognizes the route for a given path.
|
123
124
|
def recognized_request_for(path, request_method = nil)
|
124
125
|
path = "/#{path}" unless path.first == '/'
|
125
126
|
|
@@ -132,6 +133,7 @@ module ActionController
|
|
132
133
|
request
|
133
134
|
end
|
134
135
|
|
136
|
+
# Proxy to to_param if the object will respond to it.
|
135
137
|
def parameterize(value)
|
136
138
|
value.respond_to?(:to_param) ? value.to_param : value
|
137
139
|
end
|
@@ -292,6 +292,10 @@ module ActionController #:nodoc:
|
|
292
292
|
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
|
293
293
|
cattr_accessor :ignore_missing_templates
|
294
294
|
|
295
|
+
# Controls the resource action separator
|
296
|
+
@@resource_action_separator = "/"
|
297
|
+
cattr_accessor :resource_action_separator
|
298
|
+
|
295
299
|
# Holds the request object that's primarily used to get environment variables through access like
|
296
300
|
# <tt>request.env["REQUEST_URI"]</tt>.
|
297
301
|
attr_internal :request
|
@@ -393,7 +397,8 @@ module ActionController #:nodoc:
|
|
393
397
|
elsif value.is_a?(Hash)
|
394
398
|
filtered_parameters[key] = filter_parameters(value)
|
395
399
|
elsif block_given?
|
396
|
-
key
|
400
|
+
key = key.dup
|
401
|
+
value = value.dup if value
|
397
402
|
yield key, value
|
398
403
|
filtered_parameters[key] = value
|
399
404
|
else
|
@@ -538,6 +543,7 @@ module ActionController #:nodoc:
|
|
538
543
|
self.class.controller_path
|
539
544
|
end
|
540
545
|
|
546
|
+
# Test whether the session is enabled for this request.
|
541
547
|
def session_enabled?
|
542
548
|
request.session_options && request.session_options[:disabled] != false
|
543
549
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'uri'
|
3
|
+
require 'set'
|
3
4
|
|
4
5
|
module ActionController #:nodoc:
|
5
6
|
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
@@ -163,13 +164,24 @@ module ActionController #:nodoc:
|
|
163
164
|
module Actions
|
164
165
|
def self.included(base) #:nodoc:
|
165
166
|
base.extend(ClassMethods)
|
166
|
-
base.
|
167
|
+
base.class_eval do
|
168
|
+
attr_accessor :rendered_action_cache, :action_cache_path
|
169
|
+
alias_method_chain :protected_instance_variables, :action_caching
|
170
|
+
end
|
167
171
|
end
|
168
172
|
|
169
|
-
|
173
|
+
def protected_instance_variables_with_action_caching
|
174
|
+
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
175
|
+
end
|
176
|
+
|
177
|
+
module ClassMethods
|
178
|
+
# Declares that +actions+ should be cached.
|
179
|
+
# See ActionController::Caching::Actions for details.
|
170
180
|
def caches_action(*actions)
|
171
181
|
return unless perform_caching
|
172
|
-
|
182
|
+
action_cache_filter = ActionCacheFilter.new(*actions)
|
183
|
+
before_filter action_cache_filter
|
184
|
+
after_filter action_cache_filter
|
173
185
|
end
|
174
186
|
end
|
175
187
|
|
@@ -185,70 +197,59 @@ module ActionController #:nodoc:
|
|
185
197
|
end
|
186
198
|
|
187
199
|
class ActionCacheFilter #:nodoc:
|
188
|
-
def initialize(*actions
|
189
|
-
@actions = actions
|
200
|
+
def initialize(*actions)
|
201
|
+
@actions = Set.new actions
|
190
202
|
end
|
191
203
|
|
192
204
|
def before(controller)
|
193
|
-
return unless @actions.include?(controller.action_name.
|
194
|
-
|
195
|
-
if cache = controller.read_fragment(
|
205
|
+
return unless @actions.include?(controller.action_name.to_sym)
|
206
|
+
cache_path = ActionCachePath.new(controller, {})
|
207
|
+
if cache = controller.read_fragment(cache_path.path)
|
196
208
|
controller.rendered_action_cache = true
|
197
|
-
set_content_type!(
|
209
|
+
set_content_type!(controller, cache_path.extension)
|
198
210
|
controller.send(:render_text, cache)
|
199
211
|
false
|
212
|
+
else
|
213
|
+
controller.action_cache_path = cache_path
|
200
214
|
end
|
201
215
|
end
|
202
216
|
|
203
217
|
def after(controller)
|
204
|
-
return if !@actions.include?(controller.action_name.
|
205
|
-
controller.write_fragment(
|
218
|
+
return if !@actions.include?(controller.action_name.to_sym) || controller.rendered_action_cache
|
219
|
+
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
206
220
|
end
|
207
221
|
|
208
222
|
private
|
209
|
-
|
210
|
-
|
211
|
-
if extention = action_cache_path.extension
|
212
|
-
content_type = Mime::EXTENSION_LOOKUP[extention]
|
213
|
-
action_cache_path.controller.response.content_type = content_type.to_s
|
214
|
-
end
|
223
|
+
def set_content_type!(controller, extension)
|
224
|
+
controller.response.content_type = Mime::EXTENSION_LOOKUP[extension].to_s if extension
|
215
225
|
end
|
216
226
|
|
217
227
|
end
|
218
228
|
|
219
229
|
class ActionCachePath
|
220
|
-
attr_reader :
|
230
|
+
attr_reader :path, :extension
|
221
231
|
|
222
232
|
class << self
|
223
|
-
def path_for(
|
224
|
-
new(
|
233
|
+
def path_for(controller, options)
|
234
|
+
new(controller, options).path
|
225
235
|
end
|
226
236
|
end
|
227
237
|
|
228
238
|
def initialize(controller, options = {})
|
229
|
-
@
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
return @path if @path
|
235
|
-
@path = controller.url_for(options).split('://').last
|
236
|
-
normalize!
|
237
|
-
add_extension!
|
238
|
-
URI.unescape(@path)
|
239
|
-
end
|
240
|
-
|
241
|
-
def extension
|
242
|
-
@extension ||= extract_extension(controller.request.path)
|
239
|
+
@extension = extract_extension(controller.request.path)
|
240
|
+
path = controller.url_for(options).split('://').last
|
241
|
+
normalize!(path)
|
242
|
+
add_extension!(path, @extension)
|
243
|
+
@path = URI.unescape(path)
|
243
244
|
end
|
244
245
|
|
245
246
|
private
|
246
|
-
def normalize!
|
247
|
-
|
247
|
+
def normalize!(path)
|
248
|
+
path << 'index' if path[-1] == ?/
|
248
249
|
end
|
249
250
|
|
250
|
-
def add_extension!
|
251
|
-
|
251
|
+
def add_extension!(path, extension)
|
252
|
+
path << ".#{extension}" if extension
|
252
253
|
end
|
253
254
|
|
254
255
|
def extract_extension(file_path)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# CGI::Session::PStore.initialize requires 'digest/md5' on every call.
|
2
|
+
# This makes sense when spawning processes per request, but is
|
3
|
+
# unnecessarily expensive when serving requests from a long-lived
|
4
|
+
# process.
|
5
|
+
require 'cgi/session'
|
6
|
+
require 'cgi/session/pstore'
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
class CGI::Session::PStore #:nodoc:
|
10
|
+
def initialize(session, option={})
|
11
|
+
dir = option['tmpdir'] || Dir::tmpdir
|
12
|
+
prefix = option['prefix'] || ''
|
13
|
+
id = session.session_id
|
14
|
+
md5 = Digest::MD5.hexdigest(id)[0,16]
|
15
|
+
path = dir+"/"+prefix+md5
|
16
|
+
path.untaint
|
17
|
+
if File::exist?(path)
|
18
|
+
@hash = nil
|
19
|
+
else
|
20
|
+
unless session.new_session
|
21
|
+
raise CGI::Session::NoSession, "uninitialized session"
|
22
|
+
end
|
23
|
+
@hash = {}
|
24
|
+
end
|
25
|
+
@p = ::PStore.new(path)
|
26
|
+
@p.transaction do |p|
|
27
|
+
File.chmod(0600, p.path)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -2,6 +2,7 @@ require 'action_controller/cgi_ext/cgi_ext'
|
|
2
2
|
require 'action_controller/cgi_ext/cookie_performance_fix'
|
3
3
|
require 'action_controller/cgi_ext/raw_post_data_fix'
|
4
4
|
require 'action_controller/cgi_ext/session_performance_fix'
|
5
|
+
require 'action_controller/cgi_ext/pstore_performance_fix'
|
5
6
|
|
6
7
|
module ActionController #:nodoc:
|
7
8
|
class Base
|
@@ -12,8 +13,8 @@ module ActionController #:nodoc:
|
|
12
13
|
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
13
14
|
# lib/action_controller/session.
|
14
15
|
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
15
|
-
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+
|
16
|
-
#
|
16
|
+
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
|
17
|
+
# automatically generated for a new session.
|
17
18
|
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
18
19
|
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
19
20
|
# an ArgumentError is raised.
|
@@ -23,6 +24,8 @@ module ActionController #:nodoc:
|
|
23
24
|
# server.
|
24
25
|
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
25
26
|
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
27
|
+
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
|
28
|
+
# the query string or POST parameters. This protects against session fixation attacks.
|
26
29
|
def self.process_cgi(cgi = CGI.new, session_options = {})
|
27
30
|
new.process_cgi(cgi, session_options)
|
28
31
|
end
|
@@ -33,18 +36,21 @@ module ActionController #:nodoc:
|
|
33
36
|
end
|
34
37
|
|
35
38
|
class CgiRequest < AbstractRequest #:nodoc:
|
36
|
-
attr_accessor :cgi, :session_options
|
39
|
+
attr_accessor :cgi, :session_options, :cookie_only
|
40
|
+
class SessionFixationAttempt < StandardError; end #:nodoc:
|
37
41
|
|
38
42
|
DEFAULT_SESSION_OPTIONS = {
|
39
43
|
:database_manager => CGI::Session::PStore,
|
40
44
|
:prefix => "ruby_sess.",
|
41
|
-
:session_path => "/"
|
45
|
+
:session_path => "/",
|
46
|
+
:cookie_only => true
|
42
47
|
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
43
48
|
|
44
49
|
def initialize(cgi, session_options = {})
|
45
50
|
@cgi = cgi
|
46
51
|
@session_options = session_options
|
47
52
|
@env = @cgi.send(:env_table)
|
53
|
+
@cookie_only = session_options.delete :cookie_only
|
48
54
|
super()
|
49
55
|
end
|
50
56
|
|
@@ -108,6 +114,9 @@ module ActionController #:nodoc:
|
|
108
114
|
@session = Hash.new
|
109
115
|
else
|
110
116
|
stale_session_check! do
|
117
|
+
if @cookie_only && request_parameters[session_options_with_string_keys['session_key']]
|
118
|
+
raise SessionFixationAttempt
|
119
|
+
end
|
111
120
|
case value = session_options_with_string_keys['new_session']
|
112
121
|
when true
|
113
122
|
@session = new_session
|
@@ -62,9 +62,11 @@ module ActionController #:nodoc:
|
|
62
62
|
end
|
63
63
|
|
64
64
|
# Removes the cookie on the client machine by setting the value to an empty string
|
65
|
-
# and setting its expiration date into the past
|
66
|
-
|
67
|
-
|
65
|
+
# and setting its expiration date into the past. Like []=, you can pass in an options
|
66
|
+
# hash to delete cookies with extra data such as a +path+.
|
67
|
+
def delete(name, options = {})
|
68
|
+
options.stringify_keys!
|
69
|
+
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
68
70
|
end
|
69
71
|
|
70
72
|
private
|
@@ -214,9 +214,10 @@ module ActionController #:nodoc:
|
|
214
214
|
# == Filter Chain Halting
|
215
215
|
#
|
216
216
|
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
217
|
-
# before controller action is run. This is useful, for example, to deny
|
217
|
+
# before a controller action is run. This is useful, for example, to deny
|
218
218
|
# access to unauthenticated users or to redirect from http to https.
|
219
219
|
# Simply return false from the filter or call render or redirect.
|
220
|
+
# After filters will not be executed if the filter chain is halted.
|
220
221
|
#
|
221
222
|
# Around filters halt the request unless the action block is called.
|
222
223
|
# Given these filters
|
@@ -238,12 +239,12 @@ module ActionController #:nodoc:
|
|
238
239
|
# . . /
|
239
240
|
# . #around (code after yield)
|
240
241
|
# . /
|
241
|
-
# #after (actual filter code is run)
|
242
|
+
# #after (actual filter code is run, unless the around filter does not yield)
|
242
243
|
#
|
243
|
-
# If #around returns before yielding,
|
244
|
-
# filter and controller action will not be run.
|
245
|
-
# the second half of #around and
|
246
|
-
# action will not.
|
244
|
+
# If #around returns before yielding, #after will still not be run. The #before
|
245
|
+
# filter and controller action will not be run. If #before returns false,
|
246
|
+
# the second half of #around and will still run but #after and the
|
247
|
+
# action will not. If #around does not yield, #after will not be run.
|
247
248
|
module ClassMethods
|
248
249
|
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
249
250
|
# will execute before the action on this controller is performed.
|
@@ -263,13 +264,13 @@ module ActionController #:nodoc:
|
|
263
264
|
# The passed <tt>filters</tt> will be appended to the array of filters
|
264
265
|
# that run _after_ actions on this controller are performed.
|
265
266
|
def append_after_filter(*filters, &block)
|
266
|
-
|
267
|
+
append_filter_to_chain(filters, :after, &block)
|
267
268
|
end
|
268
269
|
|
269
270
|
# The passed <tt>filters</tt> will be prepended to the array of filters
|
270
271
|
# that run _after_ actions on this controller are performed.
|
271
272
|
def prepend_after_filter(*filters, &block)
|
272
|
-
|
273
|
+
prepend_filter_to_chain(filters, :after, &block)
|
273
274
|
end
|
274
275
|
|
275
276
|
# Shorthand for append_after_filter since it's the most common.
|
@@ -362,12 +363,12 @@ module ActionController #:nodoc:
|
|
362
363
|
|
363
364
|
# Returns a mapping between filters and the actions that may run them.
|
364
365
|
def included_actions #:nodoc:
|
365
|
-
read_inheritable_attribute("included_actions") || {}
|
366
|
+
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
366
367
|
end
|
367
368
|
|
368
369
|
# Returns a mapping between filters and actions that may not run them.
|
369
370
|
def excluded_actions #:nodoc:
|
370
|
-
read_inheritable_attribute("excluded_actions") || {}
|
371
|
+
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
371
372
|
end
|
372
373
|
|
373
374
|
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
@@ -381,10 +382,11 @@ module ActionController #:nodoc:
|
|
381
382
|
|
382
383
|
# Returns true if the filter is excluded from the given action
|
383
384
|
def filter_excluded_from_action?(filter,action) #:nodoc:
|
384
|
-
|
385
|
+
case
|
386
|
+
when ia = included_actions[filter]
|
385
387
|
!ia.include?(action)
|
386
|
-
|
387
|
-
|
388
|
+
when ea = excluded_actions[filter]
|
389
|
+
ea.include?(action)
|
388
390
|
end
|
389
391
|
end
|
390
392
|
|
@@ -397,20 +399,28 @@ module ActionController #:nodoc:
|
|
397
399
|
@filter = filter
|
398
400
|
end
|
399
401
|
|
402
|
+
def type
|
403
|
+
:around
|
404
|
+
end
|
405
|
+
|
400
406
|
def before?
|
401
|
-
|
407
|
+
type == :before
|
402
408
|
end
|
403
409
|
|
404
410
|
def after?
|
405
|
-
|
411
|
+
type == :after
|
406
412
|
end
|
407
413
|
|
408
414
|
def around?
|
409
|
-
|
415
|
+
type == :around
|
416
|
+
end
|
417
|
+
|
418
|
+
def run(controller)
|
419
|
+
raise ActionControllerError, 'No filter type: Nothing to do here.'
|
410
420
|
end
|
411
421
|
|
412
422
|
def call(controller, &block)
|
413
|
-
|
423
|
+
run(controller)
|
414
424
|
end
|
415
425
|
end
|
416
426
|
|
@@ -420,35 +430,38 @@ module ActionController #:nodoc:
|
|
420
430
|
def filter
|
421
431
|
@filter.filter
|
422
432
|
end
|
423
|
-
|
424
|
-
def around?
|
425
|
-
false
|
426
|
-
end
|
427
433
|
end
|
428
434
|
|
429
435
|
class BeforeFilterProxy < FilterProxy #:nodoc:
|
430
|
-
def
|
431
|
-
|
436
|
+
def type
|
437
|
+
:before
|
432
438
|
end
|
433
439
|
|
434
|
-
def
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
yield
|
440
|
+
def run(controller)
|
441
|
+
# only filters returning false are halted.
|
442
|
+
if false == @filter.call(controller)
|
443
|
+
controller.send :halt_filter_chain, @filter, :returned_false
|
439
444
|
end
|
440
445
|
end
|
446
|
+
|
447
|
+
def call(controller)
|
448
|
+
yield unless run(controller)
|
449
|
+
end
|
441
450
|
end
|
442
451
|
|
443
452
|
class AfterFilterProxy < FilterProxy #:nodoc:
|
444
|
-
def
|
445
|
-
|
453
|
+
def type
|
454
|
+
:after
|
446
455
|
end
|
447
456
|
|
448
|
-
def
|
449
|
-
yield
|
457
|
+
def run(controller)
|
450
458
|
@filter.call(controller)
|
451
459
|
end
|
460
|
+
|
461
|
+
def call(controller)
|
462
|
+
yield
|
463
|
+
run(controller)
|
464
|
+
end
|
452
465
|
end
|
453
466
|
|
454
467
|
class SymbolFilter < Filter #:nodoc:
|
@@ -485,29 +498,72 @@ module ActionController #:nodoc:
|
|
485
498
|
end
|
486
499
|
end
|
487
500
|
|
501
|
+
class ClassBeforeFilter < Filter #:nodoc:
|
502
|
+
def call(controller, &block)
|
503
|
+
@filter.before(controller)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
class ClassAfterFilter < Filter #:nodoc:
|
508
|
+
def call(controller, &block)
|
509
|
+
@filter.after(controller)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
488
513
|
protected
|
489
|
-
def append_filter_to_chain(filters,
|
490
|
-
|
514
|
+
def append_filter_to_chain(filters, filter_type = :around, &block)
|
515
|
+
pos = find_filter_append_position(filters, filter_type)
|
516
|
+
update_filter_chain(filters, filter_type, pos, &block)
|
491
517
|
end
|
492
518
|
|
493
|
-
def prepend_filter_to_chain(filters,
|
494
|
-
|
519
|
+
def prepend_filter_to_chain(filters, filter_type = :around, &block)
|
520
|
+
pos = find_filter_prepend_position(filters, filter_type)
|
521
|
+
update_filter_chain(filters, filter_type, pos, &block)
|
495
522
|
end
|
496
523
|
|
497
|
-
def
|
524
|
+
def update_filter_chain(filters, filter_type, pos, &block)
|
525
|
+
new_filters = create_filters(filters, filter_type, &block)
|
526
|
+
new_chain = filter_chain.insert(pos, new_filters).flatten
|
527
|
+
write_inheritable_attribute('filter_chain', new_chain)
|
528
|
+
end
|
529
|
+
|
530
|
+
def find_filter_append_position(filters, filter_type)
|
531
|
+
# appending an after filter puts it at the end of the call chain
|
532
|
+
# before and around filters go before the first after filter in the chain
|
533
|
+
unless filter_type == :after
|
534
|
+
filter_chain.each_with_index do |f,i|
|
535
|
+
return i if f.after?
|
536
|
+
end
|
537
|
+
end
|
538
|
+
return -1
|
539
|
+
end
|
540
|
+
|
541
|
+
def find_filter_prepend_position(filters, filter_type)
|
542
|
+
# prepending a before or around filter puts it at the front of the call chain
|
543
|
+
# after filters go before the first after filter in the chain
|
544
|
+
if filter_type == :after
|
545
|
+
filter_chain.each_with_index do |f,i|
|
546
|
+
return i if f.after?
|
547
|
+
end
|
548
|
+
return -1
|
549
|
+
end
|
550
|
+
return 0
|
551
|
+
end
|
552
|
+
|
553
|
+
def create_filters(filters, filter_type, &block) #:nodoc:
|
498
554
|
filters, conditions = extract_conditions(filters, &block)
|
499
|
-
filters.map! { |filter| find_or_create_filter(filter,
|
555
|
+
filters.map! { |filter| find_or_create_filter(filter, filter_type) }
|
500
556
|
update_conditions(filters, conditions)
|
501
557
|
filters
|
502
558
|
end
|
503
559
|
|
504
|
-
def find_or_create_filter(filter,
|
505
|
-
if found_filter = find_filter(filter) { |f| f.
|
560
|
+
def find_or_create_filter(filter, filter_type)
|
561
|
+
if found_filter = find_filter(filter) { |f| f.type == filter_type }
|
506
562
|
found_filter
|
507
563
|
else
|
508
|
-
f = class_for_filter(filter).new(filter)
|
564
|
+
f = class_for_filter(filter, filter_type).new(filter)
|
509
565
|
# apply proxy to filter if necessary
|
510
|
-
case
|
566
|
+
case filter_type
|
511
567
|
when :before
|
512
568
|
BeforeFilterProxy.new(f)
|
513
569
|
when :after
|
@@ -520,7 +576,7 @@ module ActionController #:nodoc:
|
|
520
576
|
|
521
577
|
# The determination of the filter type was once done at run time.
|
522
578
|
# This method is here to extract as much logic from the filter run time as possible
|
523
|
-
def class_for_filter(filter) #:nodoc:
|
579
|
+
def class_for_filter(filter, filter_type) #:nodoc:
|
524
580
|
case
|
525
581
|
when filter.is_a?(Symbol)
|
526
582
|
SymbolFilter
|
@@ -534,8 +590,12 @@ module ActionController #:nodoc:
|
|
534
590
|
end
|
535
591
|
when filter.respond_to?(:filter)
|
536
592
|
ClassFilter
|
593
|
+
when filter.respond_to?(:before) && filter_type == :before
|
594
|
+
ClassBeforeFilter
|
595
|
+
when filter.respond_to?(:after) && filter_type == :after
|
596
|
+
ClassAfterFilter
|
537
597
|
else
|
538
|
-
raise(ActionControllerError, 'A
|
598
|
+
raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
|
539
599
|
end
|
540
600
|
end
|
541
601
|
|
@@ -550,8 +610,8 @@ module ActionController #:nodoc:
|
|
550
610
|
return if conditions.empty?
|
551
611
|
if conditions[:only]
|
552
612
|
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
|
553
|
-
|
554
|
-
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
|
613
|
+
elsif conditions[:except]
|
614
|
+
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
|
555
615
|
end
|
556
616
|
end
|
557
617
|
|
@@ -576,9 +636,9 @@ module ActionController #:nodoc:
|
|
576
636
|
|
577
637
|
def remove_actions_from_included_actions!(filters,*actions)
|
578
638
|
actions = actions.flatten.map(&:to_s)
|
579
|
-
updated_hash = filters.inject(included_actions) do |hash,filter|
|
639
|
+
updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
|
580
640
|
ia = (hash[filter] || []) - actions
|
581
|
-
ia.
|
641
|
+
ia.empty? ? hash.delete(filter) : hash[filter] = ia
|
582
642
|
hash
|
583
643
|
end
|
584
644
|
write_inheritable_attribute('included_actions', updated_hash)
|
@@ -595,7 +655,9 @@ module ActionController #:nodoc:
|
|
595
655
|
def proxy_before_and_after_filter(filter) #:nodoc:
|
596
656
|
return filter unless filter_responds_to_before_and_after(filter)
|
597
657
|
Proc.new do |controller, action|
|
598
|
-
|
658
|
+
if filter.before(controller) == false
|
659
|
+
controller.send :halt_filter_chain, filter, :returned_false
|
660
|
+
else
|
599
661
|
begin
|
600
662
|
action.call
|
601
663
|
ensure
|
@@ -615,53 +677,90 @@ module ActionController #:nodoc:
|
|
615
677
|
end
|
616
678
|
end
|
617
679
|
|
618
|
-
|
619
|
-
call_filter(self.class.filter_chain, 0)
|
620
|
-
end
|
680
|
+
protected
|
621
681
|
|
622
682
|
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
623
683
|
@before_filter_chain_aborted = false
|
624
684
|
process_without_filters(request, response, method, *arguments)
|
625
685
|
end
|
626
686
|
|
627
|
-
def
|
628
|
-
self.class.filter_chain
|
687
|
+
def perform_action_with_filters
|
688
|
+
call_filters(self.class.filter_chain, 0, 0)
|
629
689
|
end
|
630
690
|
|
631
|
-
|
632
|
-
return (performed? || perform_action_without_filters) if index >= chain.size
|
633
|
-
filter = chain[index]
|
634
|
-
return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
|
691
|
+
private
|
635
692
|
|
636
|
-
|
637
|
-
|
638
|
-
|
693
|
+
def call_filters(chain, index, nesting)
|
694
|
+
index = run_before_filters(chain, index, nesting)
|
695
|
+
aborted = @before_filter_chain_aborted
|
696
|
+
perform_action_without_filters unless performed? || aborted
|
697
|
+
return index if nesting != 0 || aborted
|
698
|
+
run_after_filters(chain, index)
|
699
|
+
end
|
700
|
+
|
701
|
+
def skip_excluded_filters(chain, index)
|
702
|
+
while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
|
703
|
+
index = index.next
|
639
704
|
end
|
640
|
-
|
641
|
-
halted
|
705
|
+
[filter, index]
|
642
706
|
end
|
643
707
|
|
644
|
-
def
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
when :
|
650
|
-
|
708
|
+
def run_before_filters(chain, index, nesting)
|
709
|
+
while chain[index]
|
710
|
+
filter, index = skip_excluded_filters(chain, index)
|
711
|
+
break unless filter # end of call chain reached
|
712
|
+
case filter.type
|
713
|
+
when :before
|
714
|
+
filter.run(self) # invoke before filter
|
715
|
+
index = index.next
|
716
|
+
break if @before_filter_chain_aborted
|
717
|
+
when :around
|
718
|
+
yielded = false
|
719
|
+
filter.call(self) do
|
720
|
+
yielded = true
|
721
|
+
# all remaining before and around filters will be run in this call
|
722
|
+
index = call_filters(chain, index.next, nesting.next)
|
723
|
+
end
|
724
|
+
halt_filter_chain(filter, :did_not_yield) unless yielded
|
725
|
+
break
|
726
|
+
else
|
727
|
+
break # no before or around filters left
|
651
728
|
end
|
652
729
|
end
|
653
|
-
|
654
|
-
return false
|
730
|
+
index
|
655
731
|
end
|
656
732
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
733
|
+
def run_after_filters(chain, index)
|
734
|
+
seen_after_filter = false
|
735
|
+
while chain[index]
|
736
|
+
filter, index = skip_excluded_filters(chain, index)
|
737
|
+
break unless filter # end of call chain reached
|
738
|
+
case filter.type
|
739
|
+
when :after
|
740
|
+
seen_after_filter = true
|
741
|
+
filter.run(self) # invoke after filter
|
661
742
|
else
|
662
|
-
|
743
|
+
# implementation error or someone has mucked with the filter chain
|
744
|
+
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
663
745
|
end
|
746
|
+
index = index.next
|
747
|
+
end
|
748
|
+
index.next
|
749
|
+
end
|
750
|
+
|
751
|
+
def halt_filter_chain(filter, reason)
|
752
|
+
@before_filter_chain_aborted = true
|
753
|
+
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
754
|
+
false
|
755
|
+
end
|
756
|
+
|
757
|
+
def process_cleanup_with_filters
|
758
|
+
if @before_filter_chain_aborted
|
759
|
+
close_session
|
760
|
+
else
|
761
|
+
process_cleanup_without_filters
|
664
762
|
end
|
763
|
+
end
|
665
764
|
end
|
666
765
|
end
|
667
766
|
end
|