actionpack 2.2.3 → 2.3.2
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 +433 -375
- data/MIT-LICENSE +1 -1
- data/README +21 -75
- data/Rakefile +1 -1
- data/lib/action_controller.rb +80 -43
- data/lib/action_controller/assertions/model_assertions.rb +1 -0
- data/lib/action_controller/assertions/response_assertions.rb +43 -16
- data/lib/action_controller/assertions/routing_assertions.rb +1 -1
- data/lib/action_controller/assertions/selector_assertions.rb +17 -12
- data/lib/action_controller/assertions/tag_assertions.rb +1 -4
- data/lib/action_controller/base.rb +153 -82
- data/lib/action_controller/benchmarking.rb +9 -9
- data/lib/action_controller/caching.rb +9 -11
- data/lib/action_controller/caching/actions.rb +11 -18
- data/lib/action_controller/caching/fragments.rb +28 -20
- data/lib/action_controller/caching/pages.rb +13 -15
- data/lib/action_controller/caching/sweeping.rb +2 -2
- data/lib/action_controller/cgi_ext.rb +0 -1
- data/lib/action_controller/cgi_ext/cookie.rb +2 -0
- data/lib/action_controller/cgi_process.rb +54 -162
- data/lib/action_controller/cookies.rb +13 -25
- data/lib/action_controller/dispatcher.rb +43 -122
- data/lib/action_controller/failsafe.rb +52 -0
- data/lib/action_controller/flash.rb +38 -47
- data/lib/action_controller/helpers.rb +13 -9
- data/lib/action_controller/http_authentication.rb +203 -23
- data/lib/action_controller/integration.rb +126 -70
- data/lib/action_controller/layout.rb +36 -39
- data/lib/action_controller/middleware_stack.rb +119 -0
- data/lib/action_controller/middlewares.rb +13 -0
- data/lib/action_controller/mime_responds.rb +19 -4
- data/lib/action_controller/mime_type.rb +8 -0
- data/lib/action_controller/params_parser.rb +71 -0
- data/lib/action_controller/performance_test.rb +0 -1
- data/lib/action_controller/polymorphic_routes.rb +36 -30
- data/lib/action_controller/reloader.rb +14 -0
- data/lib/action_controller/request.rb +107 -499
- data/lib/action_controller/request_forgery_protection.rb +7 -39
- data/lib/action_controller/rescue.rb +55 -35
- data/lib/action_controller/resources.rb +34 -31
- data/lib/action_controller/response.rb +99 -57
- data/lib/action_controller/rewindable_input.rb +28 -0
- data/lib/action_controller/routing.rb +7 -7
- data/lib/action_controller/routing/builder.rb +4 -1
- data/lib/action_controller/routing/optimisations.rb +1 -1
- data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
- data/lib/action_controller/routing/route.rb +15 -5
- data/lib/action_controller/routing/route_set.rb +82 -35
- data/lib/action_controller/routing/segments.rb +35 -0
- data/lib/action_controller/session/abstract_store.rb +181 -0
- data/lib/action_controller/session/cookie_store.rb +197 -175
- data/lib/action_controller/session/mem_cache_store.rb +36 -83
- data/lib/action_controller/session_management.rb +26 -134
- data/lib/action_controller/streaming.rb +24 -7
- data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
- data/lib/action_controller/templates/rescues/template_error.erb +2 -2
- data/lib/action_controller/test_case.rb +87 -30
- data/lib/action_controller/test_process.rb +145 -104
- data/lib/action_controller/uploaded_file.rb +44 -0
- data/lib/action_controller/url_rewriter.rb +3 -6
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
- data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
- data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
- data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
- data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
- data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
- data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
- data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
- data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
- data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
- data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
- data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
- data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
- data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
- data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
- data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
- data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
- data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
- data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
- data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
- data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
- data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
- data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
- data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
- data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
- data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
- data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
- data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
- data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
- data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
- data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
- data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
- data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
- data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
- data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
- data/lib/action_controller/verification.rb +1 -1
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +2 -2
- data/lib/action_view.rb +22 -17
- data/lib/action_view/base.rb +53 -79
- data/lib/action_view/erb/util.rb +38 -0
- data/lib/action_view/helpers.rb +24 -5
- data/lib/action_view/helpers/active_record_helper.rb +2 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
- data/lib/action_view/helpers/benchmark_helper.rb +26 -5
- data/lib/action_view/helpers/date_helper.rb +82 -7
- data/lib/action_view/helpers/form_helper.rb +295 -64
- data/lib/action_view/helpers/form_options_helper.rb +160 -18
- data/lib/action_view/helpers/form_tag_helper.rb +2 -2
- data/lib/action_view/helpers/number_helper.rb +31 -18
- data/lib/action_view/helpers/prototype_helper.rb +2 -12
- data/lib/action_view/helpers/sanitize_helper.rb +0 -10
- data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
- data/lib/action_view/helpers/tag_helper.rb +3 -4
- data/lib/action_view/helpers/text_helper.rb +99 -122
- data/lib/action_view/helpers/translation_helper.rb +19 -1
- data/lib/action_view/helpers/url_helper.rb +25 -2
- data/lib/action_view/inline_template.rb +1 -1
- data/lib/action_view/locale/en.yml +19 -1
- data/lib/action_view/partials.rb +46 -9
- data/lib/action_view/paths.rb +28 -84
- data/lib/action_view/reloadable_template.rb +117 -0
- data/lib/action_view/renderable.rb +28 -35
- data/lib/action_view/renderable_partial.rb +3 -4
- data/lib/action_view/template.rb +172 -31
- data/lib/action_view/template_error.rb +8 -9
- data/lib/action_view/template_handler.rb +1 -1
- data/lib/action_view/template_handlers.rb +9 -6
- data/lib/action_view/template_handlers/erb.rb +2 -39
- data/lib/action_view/template_handlers/rjs.rb +1 -0
- data/lib/action_view/test_case.rb +27 -1
- data/test/abstract_unit.rb +23 -17
- data/test/active_record_unit.rb +5 -4
- data/test/activerecord/active_record_store_test.rb +139 -106
- data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
- data/test/controller/action_pack_assertions_test.rb +25 -23
- data/test/controller/addresses_render_test.rb +3 -6
- data/test/controller/assert_select_test.rb +83 -70
- data/test/controller/base_test.rb +11 -13
- data/test/controller/benchmark_test.rb +3 -3
- data/test/controller/caching_test.rb +34 -24
- data/test/controller/capture_test.rb +3 -6
- data/test/controller/content_type_test.rb +3 -6
- data/test/controller/cookie_test.rb +31 -66
- data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
- data/test/controller/dispatcher_test.rb +23 -28
- data/test/controller/fake_models.rb +8 -0
- data/test/controller/filters_test.rb +6 -2
- data/test/controller/flash_test.rb +2 -6
- data/test/controller/helper_test.rb +15 -1
- data/test/controller/html-scanner/document_test.rb +1 -1
- data/test/controller/html-scanner/sanitizer_test.rb +1 -1
- data/test/controller/http_basic_authentication_test.rb +88 -0
- data/test/controller/http_digest_authentication_test.rb +178 -0
- data/test/controller/integration_test.rb +56 -52
- data/test/controller/layout_test.rb +46 -44
- data/test/controller/middleware_stack_test.rb +90 -0
- data/test/controller/mime_responds_test.rb +7 -11
- data/test/controller/mime_type_test.rb +9 -0
- data/test/controller/polymorphic_routes_test.rb +235 -151
- data/test/controller/rack_test.rb +52 -81
- data/test/controller/redirect_test.rb +6 -14
- data/test/controller/render_test.rb +273 -60
- data/test/controller/request/json_params_parsing_test.rb +45 -0
- data/test/controller/request/multipart_params_parsing_test.rb +223 -0
- data/test/controller/request/query_string_parsing_test.rb +120 -0
- data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
- data/test/controller/request/xml_params_parsing_test.rb +88 -0
- data/test/controller/request_forgery_protection_test.rb +17 -98
- data/test/controller/request_test.rb +45 -530
- data/test/controller/rescue_test.rb +45 -22
- data/test/controller/resources_test.rb +112 -37
- data/test/controller/routing_test.rb +1442 -1384
- data/test/controller/selector_test.rb +3 -3
- data/test/controller/send_file_test.rb +30 -3
- data/test/controller/session/cookie_store_test.rb +169 -240
- data/test/controller/session/mem_cache_store_test.rb +94 -148
- data/test/controller/session/test_session_test.rb +58 -0
- data/test/controller/test_test.rb +32 -13
- data/test/controller/url_rewriter_test.rb +54 -4
- data/test/controller/verification_test.rb +1 -1
- data/test/controller/view_paths_test.rb +15 -15
- data/test/controller/webservice_test.rb +178 -147
- data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
- data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
- data/test/fixtures/layouts/default_html.html.erb +1 -0
- data/test/fixtures/layouts/xhr.html.erb +2 -0
- data/test/fixtures/multipart/empty +10 -0
- data/test/fixtures/multipart/hello.txt +1 -0
- data/test/fixtures/multipart/none +9 -0
- data/test/fixtures/public/500.da.html +1 -0
- data/test/fixtures/quiz/questions/_question.html.erb +1 -0
- data/test/fixtures/replies.yml +1 -1
- data/test/fixtures/test/_one.html.erb +1 -0
- data/test/fixtures/test/_two.html.erb +1 -0
- data/test/fixtures/test/dont_pick_me +1 -0
- data/test/fixtures/test/hello.builder +1 -1
- data/test/fixtures/test/hello_world.da.html.erb +1 -0
- data/test/fixtures/test/hello_world.erb~ +1 -0
- data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
- data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
- data/test/fixtures/test/malformed/malformed.erb~ +1 -0
- data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
- data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
- data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
- data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
- data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
- data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
- data/test/fixtures/test/utf8.html.erb +2 -0
- data/test/template/active_record_helper_i18n_test.rb +31 -33
- data/test/template/active_record_helper_test.rb +34 -0
- data/test/template/asset_tag_helper_test.rb +52 -14
- data/test/template/atom_feed_helper_test.rb +3 -5
- data/test/template/benchmark_helper_test.rb +50 -24
- data/test/template/compiled_templates_test.rb +177 -33
- data/test/template/date_helper_i18n_test.rb +88 -81
- data/test/template/date_helper_test.rb +427 -43
- data/test/template/form_helper_test.rb +243 -44
- data/test/template/form_options_helper_test.rb +631 -565
- data/test/template/form_tag_helper_test.rb +9 -2
- data/test/template/javascript_helper_test.rb +0 -5
- data/test/template/number_helper_i18n_test.rb +60 -48
- data/test/template/number_helper_test.rb +1 -0
- data/test/template/render_test.rb +117 -35
- data/test/template/test_test.rb +4 -6
- data/test/template/text_helper_test.rb +129 -50
- data/test/template/translation_helper_test.rb +23 -19
- data/test/template/url_helper_test.rb +35 -2
- data/test/view/test_case_test.rb +8 -0
- metadata +197 -23
- data/lib/action_controller/assertions.rb +0 -69
- data/lib/action_controller/caching/sql_cache.rb +0 -18
- data/lib/action_controller/cgi_ext/session.rb +0 -53
- data/lib/action_controller/components.rb +0 -169
- data/lib/action_controller/rack_process.rb +0 -297
- data/lib/action_controller/request_profiler.rb +0 -169
- data/lib/action_controller/session/active_record_store.rb +0 -340
- data/lib/action_controller/session/drb_server.rb +0 -32
- data/lib/action_controller/session/drb_store.rb +0 -35
- data/test/controller/cgi_test.rb +0 -269
- data/test/controller/components_test.rb +0 -156
- data/test/controller/http_authentication_test.rb +0 -54
- data/test/controller/integration_upload_test.rb +0 -43
- data/test/controller/session_fixation_test.rb +0 -89
- data/test/controller/session_management_test.rb +0 -178
- data/test/fixtures/test/hello_world.js +0 -1
@@ -5,8 +5,6 @@ module ActionController #:nodoc:
|
|
5
5
|
module RequestForgeryProtection
|
6
6
|
def self.included(base)
|
7
7
|
base.class_eval do
|
8
|
-
class_inheritable_accessor :request_forgery_protection_options
|
9
|
-
self.request_forgery_protection_options = {}
|
10
8
|
helper_method :form_authenticity_token
|
11
9
|
helper_method :protect_against_forgery?
|
12
10
|
end
|
@@ -14,7 +12,7 @@ module ActionController #:nodoc:
|
|
14
12
|
end
|
15
13
|
|
16
14
|
# Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
|
17
|
-
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
|
15
|
+
# forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
|
18
16
|
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
19
17
|
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
20
18
|
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
|
@@ -57,12 +55,8 @@ module ActionController #:nodoc:
|
|
57
55
|
# Example:
|
58
56
|
#
|
59
57
|
# class FooController < ApplicationController
|
60
|
-
# # uses the cookie session store (then you don't need a separate :secret)
|
61
58
|
# protect_from_forgery :except => :index
|
62
59
|
#
|
63
|
-
# # uses one of the other session stores that uses a session_id value.
|
64
|
-
# protect_from_forgery :secret => 'my-little-pony', :except => :index
|
65
|
-
#
|
66
60
|
# # you can disable csrf protection on controller-by-controller basis:
|
67
61
|
# skip_before_filter :verify_authenticity_token
|
68
62
|
# end
|
@@ -70,13 +64,12 @@ module ActionController #:nodoc:
|
|
70
64
|
# Valid Options:
|
71
65
|
#
|
72
66
|
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
|
73
|
-
# * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
|
74
|
-
# Leave this off if you are using the cookie session store.
|
75
|
-
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
|
76
67
|
def protect_from_forgery(options = {})
|
77
68
|
self.request_forgery_protection_token ||= :authenticity_token
|
78
69
|
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
|
79
|
-
|
70
|
+
if options[:secret] || options[:digest]
|
71
|
+
ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
|
72
|
+
end
|
80
73
|
end
|
81
74
|
end
|
82
75
|
|
@@ -90,7 +83,7 @@ module ActionController #:nodoc:
|
|
90
83
|
#
|
91
84
|
# * is the format restricted? By default, only HTML and AJAX requests are checked.
|
92
85
|
# * is it a GET request? Gets should be safe and idempotent
|
93
|
-
# * Does the form_authenticity_token match the given
|
86
|
+
# * Does the form_authenticity_token match the given token value from the params?
|
94
87
|
def verified_request?
|
95
88
|
!protect_against_forgery? ||
|
96
89
|
request.method == :get ||
|
@@ -105,34 +98,9 @@ module ActionController #:nodoc:
|
|
105
98
|
# Sets the token value for the current session. Pass a <tt>:secret</tt> option
|
106
99
|
# in +protect_from_forgery+ to add a custom salt to the hash.
|
107
100
|
def form_authenticity_token
|
108
|
-
|
109
|
-
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
110
|
-
elsif request_forgery_protection_options[:secret]
|
111
|
-
authenticity_token_from_session_id
|
112
|
-
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
113
|
-
authenticity_token_from_cookie_session
|
114
|
-
else
|
115
|
-
raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# Generates a unique digest using the session_id and the CSRF secret.
|
120
|
-
def authenticity_token_from_session_id
|
121
|
-
key = if request_forgery_protection_options[:secret].respond_to?(:call)
|
122
|
-
request_forgery_protection_options[:secret].call(@session)
|
123
|
-
else
|
124
|
-
request_forgery_protection_options[:secret]
|
125
|
-
end
|
126
|
-
digest = request_forgery_protection_options[:digest] ||= 'SHA1'
|
127
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
|
128
|
-
end
|
129
|
-
|
130
|
-
# No secret was given, so assume this is a cookie session store.
|
131
|
-
def authenticity_token_from_cookie_session
|
132
|
-
session[:csrf_id] ||= CGI::Session.generate_unique_id
|
133
|
-
session.dbman.generate_digest(session[:csrf_id])
|
101
|
+
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
|
134
102
|
end
|
135
|
-
|
103
|
+
|
136
104
|
def protect_against_forgery?
|
137
105
|
allow_forgery_protection && request_forgery_protection_token
|
138
106
|
end
|
@@ -1,13 +1,19 @@
|
|
1
1
|
module ActionController #:nodoc:
|
2
|
-
# Actions that fail to perform as expected throw exceptions. These
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
2
|
+
# Actions that fail to perform as expected throw exceptions. These
|
3
|
+
# exceptions can either be rescued for the public view (with a nice
|
4
|
+
# user-friendly explanation) or for the developers view (with tons of
|
5
|
+
# debugging information). The developers view is already implemented by
|
6
|
+
# the Action Controller, but the public view should be tailored to your
|
7
|
+
# specific application.
|
8
8
|
#
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# The default behavior for public exceptions is to render a static html
|
10
|
+
# file with the name of the error code thrown. If no such file exists, an
|
11
|
+
# empty response is sent with the correct status code.
|
12
|
+
#
|
13
|
+
# You can override what constitutes a local request by overriding the
|
14
|
+
# <tt>local_request?</tt> method in your own controller. Custom rescue
|
15
|
+
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
|
16
|
+
# and <tt>rescue_action_locally</tt> methods.
|
11
17
|
module Rescue
|
12
18
|
LOCALHOST = '127.0.0.1'.freeze
|
13
19
|
|
@@ -32,6 +38,9 @@ module ActionController #:nodoc:
|
|
32
38
|
'ActionView::TemplateError' => 'template_error'
|
33
39
|
}
|
34
40
|
|
41
|
+
RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new_and_loaded(
|
42
|
+
File.join(File.dirname(__FILE__), "templates"))
|
43
|
+
|
35
44
|
def self.included(base) #:nodoc:
|
36
45
|
base.cattr_accessor :rescue_responses
|
37
46
|
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
@@ -50,47 +59,60 @@ module ActionController #:nodoc:
|
|
50
59
|
end
|
51
60
|
|
52
61
|
module ClassMethods
|
53
|
-
def
|
62
|
+
def call_with_exception(env, exception) #:nodoc:
|
63
|
+
request = env["action_controller.rescue.request"] ||= Request.new(env)
|
64
|
+
response = env["action_controller.rescue.response"] ||= Response.new
|
54
65
|
new.process(request, response, :rescue_action, exception)
|
55
66
|
end
|
56
67
|
end
|
57
68
|
|
58
69
|
protected
|
59
|
-
# Exception handler called when the performance of an action raises
|
70
|
+
# Exception handler called when the performance of an action raises
|
71
|
+
# an exception.
|
60
72
|
def rescue_action(exception)
|
61
|
-
rescue_with_handler(exception) ||
|
73
|
+
rescue_with_handler(exception) ||
|
74
|
+
rescue_action_without_handler(exception)
|
62
75
|
end
|
63
76
|
|
64
|
-
# Overwrite to implement custom logging of errors. By default
|
77
|
+
# Overwrite to implement custom logging of errors. By default
|
78
|
+
# logs as fatal.
|
65
79
|
def log_error(exception) #:doc:
|
66
80
|
ActiveSupport::Deprecation.silence do
|
67
81
|
if ActionView::TemplateError === exception
|
68
82
|
logger.fatal(exception.to_s)
|
69
83
|
else
|
70
84
|
logger.fatal(
|
71
|
-
"\n
|
72
|
-
clean_backtrace(exception).join("\n
|
73
|
-
"\n\n"
|
85
|
+
"\n#{exception.class} (#{exception.message}):\n " +
|
86
|
+
clean_backtrace(exception).join("\n ") + "\n\n"
|
74
87
|
)
|
75
88
|
end
|
76
89
|
end
|
77
90
|
end
|
78
91
|
|
79
|
-
# Overwrite to implement public exception handling (for requests
|
80
|
-
#
|
92
|
+
# Overwrite to implement public exception handling (for requests
|
93
|
+
# answering false to <tt>local_request?</tt>). By default will call
|
94
|
+
# render_optional_error_file. Override this method to provide more
|
95
|
+
# user friendly error messages.
|
81
96
|
def rescue_action_in_public(exception) #:doc:
|
82
97
|
render_optional_error_file response_code_for_rescue(exception)
|
83
98
|
end
|
84
|
-
|
85
|
-
# Attempts to render a static error page based on the
|
86
|
-
# or just return headers if no such file
|
87
|
-
#
|
88
|
-
#
|
99
|
+
|
100
|
+
# Attempts to render a static error page based on the
|
101
|
+
# <tt>status_code</tt> thrown, or just return headers if no such file
|
102
|
+
# exists. At first, it will try to render a localized static page.
|
103
|
+
# For example, if a 500 error is being handled Rails and locale is :da,
|
104
|
+
# it will first attempt to render the file at <tt>public/500.da.html</tt>
|
105
|
+
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
|
106
|
+
# the body of the response will be left empty.
|
89
107
|
def render_optional_error_file(status_code)
|
90
108
|
status = interpret_status(status_code)
|
109
|
+
locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale
|
91
110
|
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
92
|
-
|
93
|
-
|
111
|
+
|
112
|
+
if locale_path && File.exist?(locale_path)
|
113
|
+
render :file => locale_path, :status => status, :content_type => Mime::HTML
|
114
|
+
elsif File.exist?(path)
|
115
|
+
render :file => path, :status => status, :content_type => Mime::HTML
|
94
116
|
else
|
95
117
|
head status
|
96
118
|
end
|
@@ -107,11 +129,13 @@ module ActionController #:nodoc:
|
|
107
129
|
# a controller action.
|
108
130
|
def rescue_action_locally(exception)
|
109
131
|
@template.instance_variable_set("@exception", exception)
|
110
|
-
@template.instance_variable_set("@rescues_path",
|
111
|
-
@template.instance_variable_set("@contents",
|
132
|
+
@template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
|
133
|
+
@template.instance_variable_set("@contents",
|
134
|
+
@template.render(:file => template_path_for_local_rescue(exception)))
|
112
135
|
|
113
136
|
response.content_type = Mime::HTML
|
114
|
-
render_for_file(rescues_path("layout"),
|
137
|
+
render_for_file(rescues_path("layout"),
|
138
|
+
response_code_for_rescue(exception))
|
115
139
|
end
|
116
140
|
|
117
141
|
def rescue_action_without_handler(exception)
|
@@ -139,7 +163,7 @@ module ActionController #:nodoc:
|
|
139
163
|
end
|
140
164
|
|
141
165
|
def rescues_path(template_name)
|
142
|
-
"
|
166
|
+
RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
|
143
167
|
end
|
144
168
|
|
145
169
|
def template_path_for_local_rescue(exception)
|
@@ -151,13 +175,9 @@ module ActionController #:nodoc:
|
|
151
175
|
end
|
152
176
|
|
153
177
|
def clean_backtrace(exception)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
else
|
158
|
-
backtrace
|
159
|
-
end
|
160
|
-
end
|
178
|
+
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
|
179
|
+
Rails.backtrace_cleaner.clean(exception.backtrace) :
|
180
|
+
exception.backtrace
|
161
181
|
end
|
162
182
|
end
|
163
183
|
end
|
@@ -42,7 +42,7 @@ module ActionController
|
|
42
42
|
#
|
43
43
|
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
44
44
|
module Resources
|
45
|
-
INHERITABLE_OPTIONS = :namespace, :shallow
|
45
|
+
INHERITABLE_OPTIONS = :namespace, :shallow
|
46
46
|
|
47
47
|
class Resource #:nodoc:
|
48
48
|
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
|
@@ -91,7 +91,7 @@ module ActionController
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def shallow_path_prefix
|
94
|
-
@shallow_path_prefix ||=
|
94
|
+
@shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
|
95
95
|
end
|
96
96
|
|
97
97
|
def member_path
|
@@ -103,7 +103,7 @@ module ActionController
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def shallow_name_prefix
|
106
|
-
@shallow_name_prefix ||=
|
106
|
+
@shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
|
107
107
|
end
|
108
108
|
|
109
109
|
def nesting_name_prefix
|
@@ -119,7 +119,7 @@ module ActionController
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def has_action?(action)
|
122
|
-
!DEFAULT_ACTIONS.include?(action) ||
|
122
|
+
!DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
|
123
123
|
end
|
124
124
|
|
125
125
|
protected
|
@@ -135,24 +135,29 @@ module ActionController
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def set_allowed_actions
|
138
|
-
only
|
139
|
-
|
138
|
+
only, except = @options.values_at(:only, :except)
|
139
|
+
@allowed_actions ||= {}
|
140
140
|
|
141
|
-
if only
|
142
|
-
|
143
|
-
|
144
|
-
options[:actions] = DEFAULT_ACTIONS
|
141
|
+
if only == :all || except == :none
|
142
|
+
only = nil
|
143
|
+
except = []
|
145
144
|
elsif only == :none || except == :all
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
only = []
|
146
|
+
except = nil
|
147
|
+
end
|
148
|
+
|
149
|
+
if only
|
150
|
+
@allowed_actions[:only] = Array(only).map(&:to_sym)
|
149
151
|
elsif except
|
150
|
-
|
151
|
-
else
|
152
|
-
# leave options[:actions] alone
|
152
|
+
@allowed_actions[:except] = Array(except).map(&:to_sym)
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
|
+
def action_allowed?(action)
|
157
|
+
only, except = @allowed_actions.values_at(:only, :except)
|
158
|
+
(!only || only.include?(action)) && (!except || !except.include?(action))
|
159
|
+
end
|
160
|
+
|
156
161
|
def set_prefixes
|
157
162
|
@path_prefix = options.delete(:path_prefix)
|
158
163
|
@name_prefix = options.delete(:name_prefix)
|
@@ -283,7 +288,12 @@ module ActionController
|
|
283
288
|
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
|
284
289
|
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
285
290
|
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
286
|
-
# * <tt>:requirements</tt> - Set custom routing parameter requirements
|
291
|
+
# * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
|
292
|
+
# regular expressions (which must match for the route to match) or extra parameters. For example:
|
293
|
+
#
|
294
|
+
# map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
|
295
|
+
#
|
296
|
+
# will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
|
287
297
|
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
|
288
298
|
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
|
289
299
|
# # products_path == '/productos'
|
@@ -398,8 +408,6 @@ module ActionController
|
|
398
408
|
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
|
399
409
|
# # --> PUT /posts/1/comments/1 (fails)
|
400
410
|
#
|
401
|
-
# The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
|
402
|
-
#
|
403
411
|
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
404
412
|
#
|
405
413
|
# Examples:
|
@@ -622,7 +630,7 @@ module ActionController
|
|
622
630
|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
623
631
|
action_path ||= Base.resources_path_names[action] || action
|
624
632
|
|
625
|
-
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
|
633
|
+
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
|
626
634
|
end
|
627
635
|
end
|
628
636
|
end
|
@@ -633,16 +641,14 @@ module ActionController
|
|
633
641
|
map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
|
634
642
|
end
|
635
643
|
|
636
|
-
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
|
644
|
+
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
|
637
645
|
if resource.has_action?(action)
|
638
|
-
action_options = action_options_for(action, resource, method)
|
646
|
+
action_options = action_options_for(action, resource, method, resource_options)
|
639
647
|
formatted_route_path = "#{route_path}.:format"
|
640
648
|
|
641
649
|
if route_name && @set.named_routes[route_name.to_sym].nil?
|
642
|
-
map.named_route(route_name,
|
643
|
-
map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
|
650
|
+
map.named_route(route_name, formatted_route_path, action_options)
|
644
651
|
else
|
645
|
-
map.connect(route_path, action_options)
|
646
652
|
map.connect(formatted_route_path, action_options)
|
647
653
|
end
|
648
654
|
end
|
@@ -654,9 +660,10 @@ module ActionController
|
|
654
660
|
end
|
655
661
|
end
|
656
662
|
|
657
|
-
def action_options_for(action, resource, method = nil)
|
663
|
+
def action_options_for(action, resource, method = nil, resource_options = {})
|
658
664
|
default_options = { :action => action.to_s }
|
659
665
|
require_id = !resource.kind_of?(SingletonResource)
|
666
|
+
force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
|
660
667
|
|
661
668
|
case default_options[:action]
|
662
669
|
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
@@ -664,12 +671,8 @@ module ActionController
|
|
664
671
|
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
|
665
672
|
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
|
666
673
|
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
|
667
|
-
else
|
674
|
+
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
|
668
675
|
end
|
669
676
|
end
|
670
677
|
end
|
671
678
|
end
|
672
|
-
|
673
|
-
class ActionController::Routing::RouteSet::Mapper
|
674
|
-
include ActionController::Resources
|
675
|
-
end
|
@@ -1,24 +1,25 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
3
|
module ActionController # :nodoc:
|
4
|
-
# Represents an HTTP response generated by a controller action. One can use
|
5
|
-
# ActionController::
|
6
|
-
# response, or customize the response. An
|
7
|
-
# represent a "real" HTTP response (i.e. one that is meant to be sent
|
8
|
-
# web browser) or a test response (i.e. one that is generated
|
9
|
-
# tests). See CgiResponse and TestResponse, respectively.
|
4
|
+
# Represents an HTTP response generated by a controller action. One can use
|
5
|
+
# an ActionController::Response object to retrieve the current state
|
6
|
+
# of the response, or customize the response. An Response object can
|
7
|
+
# either represent a "real" HTTP response (i.e. one that is meant to be sent
|
8
|
+
# back to the web browser) or a test response (i.e. one that is generated
|
9
|
+
# from integration tests). See CgiResponse and TestResponse, respectively.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# never be used directly in controllers. Controllers should use the
|
13
|
-
# in ActionController::Base instead. For example, if you want
|
14
|
-
# response's content MIME type, then use
|
15
|
-
#
|
11
|
+
# Response is mostly a Ruby on Rails framework implement detail, and
|
12
|
+
# should never be used directly in controllers. Controllers should use the
|
13
|
+
# methods defined in ActionController::Base instead. For example, if you want
|
14
|
+
# to set the HTTP response's content MIME type, then use
|
15
|
+
# ActionControllerBase#headers instead of Response#headers.
|
16
16
|
#
|
17
|
-
# Nevertheless, integration tests may want to inspect controller responses in
|
18
|
-
# detail, and that's when
|
19
|
-
# Integration test methods such as
|
20
|
-
# ActionController::Integration::Session#
|
21
|
-
#
|
17
|
+
# Nevertheless, integration tests may want to inspect controller responses in
|
18
|
+
# more detail, and that's when Response can be useful for application
|
19
|
+
# developers. Integration test methods such as
|
20
|
+
# ActionController::Integration::Session#get and
|
21
|
+
# ActionController::Integration::Session#post return objects of type
|
22
|
+
# TestResponse (which are of course also of type Response).
|
22
23
|
#
|
23
24
|
# For example, the following demo integration "test" prints the body of the
|
24
25
|
# controller response to the console:
|
@@ -29,25 +30,25 @@ module ActionController # :nodoc:
|
|
29
30
|
# puts @response.body
|
30
31
|
# end
|
31
32
|
# end
|
32
|
-
class
|
33
|
+
class Response < Rack::Response
|
33
34
|
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
34
35
|
attr_accessor :request
|
35
36
|
|
36
|
-
|
37
|
-
attr_accessor :body
|
38
|
-
# The headers of the response, as a Hash. It maps header names to header values.
|
39
|
-
attr_accessor :headers
|
40
|
-
attr_accessor :session, :cookies, :assigns, :template, :layout
|
37
|
+
attr_accessor :session, :assigns, :template, :layout
|
41
38
|
attr_accessor :redirected_to, :redirected_to_method_params
|
42
39
|
|
43
40
|
delegate :default_charset, :to => 'ActionController::Base'
|
44
41
|
|
45
42
|
def initialize
|
46
|
-
@
|
47
|
-
|
43
|
+
@status = 200
|
44
|
+
@header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
|
45
|
+
|
46
|
+
@writer = lambda { |x| @body << x }
|
47
|
+
@block = nil
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
@body = "",
|
50
|
+
@session, @assigns = [], []
|
51
|
+
end
|
51
52
|
|
52
53
|
def location; headers['Location'] end
|
53
54
|
def location=(url) headers['Location'] = url end
|
@@ -109,13 +110,17 @@ module ActionController # :nodoc:
|
|
109
110
|
def etag
|
110
111
|
headers['ETag']
|
111
112
|
end
|
112
|
-
|
113
|
+
|
113
114
|
def etag?
|
114
115
|
headers.include?('ETag')
|
115
116
|
end
|
116
|
-
|
117
|
+
|
117
118
|
def etag=(etag)
|
118
|
-
|
119
|
+
if etag.blank?
|
120
|
+
headers.delete('ETag')
|
121
|
+
else
|
122
|
+
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
123
|
+
end
|
119
124
|
end
|
120
125
|
|
121
126
|
def redirect(url, status)
|
@@ -138,26 +143,58 @@ module ActionController # :nodoc:
|
|
138
143
|
handle_conditional_get!
|
139
144
|
set_content_length!
|
140
145
|
convert_content_type!
|
146
|
+
convert_language!
|
147
|
+
convert_cookies!
|
148
|
+
end
|
149
|
+
|
150
|
+
def each(&callback)
|
151
|
+
if @body.respond_to?(:call)
|
152
|
+
@writer = lambda { |x| callback.call(x) }
|
153
|
+
@body.call(self, self)
|
154
|
+
elsif @body.is_a?(String)
|
155
|
+
@body.each_line(&callback)
|
156
|
+
else
|
157
|
+
@body.each(&callback)
|
158
|
+
end
|
159
|
+
|
160
|
+
@writer = callback
|
161
|
+
@block.call(self) if @block
|
162
|
+
end
|
163
|
+
|
164
|
+
def write(str)
|
165
|
+
@writer.call str.to_s
|
166
|
+
str
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_cookie(key, value)
|
170
|
+
if value.has_key?(:http_only)
|
171
|
+
ActiveSupport::Deprecation.warn(
|
172
|
+
"The :http_only option in ActionController::Response#set_cookie " +
|
173
|
+
"has been renamed. Please use :httponly instead.", caller)
|
174
|
+
value[:httponly] ||= value.delete(:http_only)
|
175
|
+
end
|
176
|
+
|
177
|
+
super(key, value)
|
141
178
|
end
|
142
179
|
|
143
180
|
private
|
144
|
-
def handle_conditional_get!
|
145
|
-
if etag? || last_modified?
|
146
|
-
set_conditional_cache_control!
|
147
|
-
elsif nonempty_ok_response?
|
148
|
-
self.etag = body
|
149
|
-
|
150
|
-
if request && request.etag_matches?(etag)
|
151
|
-
self.status = '304 Not Modified'
|
152
|
-
self.body = ''
|
153
|
-
end
|
154
|
-
|
155
|
-
set_conditional_cache_control!
|
156
|
-
end
|
181
|
+
def handle_conditional_get!
|
182
|
+
if etag? || last_modified?
|
183
|
+
set_conditional_cache_control!
|
184
|
+
elsif nonempty_ok_response?
|
185
|
+
self.etag = body
|
186
|
+
|
187
|
+
if request && request.etag_matches?(etag)
|
188
|
+
self.status = '304 Not Modified'
|
189
|
+
self.body = ''
|
190
|
+
end
|
191
|
+
|
192
|
+
set_conditional_cache_control!
|
193
|
+
end
|
157
194
|
end
|
158
195
|
|
159
196
|
def nonempty_ok_response?
|
160
|
-
ok = !status || status[0..2] == '200'
|
197
|
+
ok = !status || status.to_s[0..2] == '200'
|
161
198
|
ok && body.is_a?(String) && !body.empty?
|
162
199
|
end
|
163
200
|
|
@@ -168,23 +205,28 @@ module ActionController # :nodoc:
|
|
168
205
|
end
|
169
206
|
|
170
207
|
def convert_content_type!
|
171
|
-
|
172
|
-
|
173
|
-
end
|
174
|
-
if content_type = headers.delete("Content-type")
|
175
|
-
self.headers["type"] = content_type
|
176
|
-
end
|
177
|
-
if content_type = headers.delete("content-type")
|
178
|
-
self.headers["type"] = content_type
|
179
|
-
end
|
208
|
+
headers['Content-Type'] ||= "text/html"
|
209
|
+
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
180
210
|
end
|
181
|
-
|
182
|
-
# Don't set the Content-Length for block-based bodies as that would mean
|
183
|
-
# for, say, a 2GB streaming file.
|
211
|
+
|
212
|
+
# Don't set the Content-Length for block-based bodies as that would mean
|
213
|
+
# reading it all into memory. Not nice for, say, a 2GB streaming file.
|
184
214
|
def set_content_length!
|
185
|
-
|
186
|
-
|
215
|
+
if status && status.to_s[0..2] == '204'
|
216
|
+
headers.delete('Content-Length')
|
217
|
+
elsif length = headers['Content-Length']
|
218
|
+
headers['Content-Length'] = length.to_s
|
219
|
+
elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
|
220
|
+
headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
|
187
221
|
end
|
188
222
|
end
|
223
|
+
|
224
|
+
def convert_language!
|
225
|
+
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
226
|
+
end
|
227
|
+
|
228
|
+
def convert_cookies!
|
229
|
+
headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
|
230
|
+
end
|
189
231
|
end
|
190
232
|
end
|