actionpack 4.2.10 → 7.2.0.rc1
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,96 +1,173 @@
|
|
1
|
-
|
2
|
-
class RedirectBackError < AbstractController::Error #:nodoc:
|
3
|
-
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
super(message || DEFAULT_MESSAGE)
|
7
|
-
end
|
8
|
-
end
|
3
|
+
# :markup: markdown
|
9
4
|
|
5
|
+
module ActionController
|
10
6
|
module Redirecting
|
11
7
|
extend ActiveSupport::Concern
|
12
8
|
|
13
9
|
include AbstractController::Logger
|
14
|
-
include ActionController::RackDelegation
|
15
10
|
include ActionController::UrlFor
|
16
11
|
|
17
|
-
|
12
|
+
class UnsafeRedirectError < StandardError; end
|
13
|
+
|
14
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/
|
15
|
+
|
16
|
+
included do
|
17
|
+
mattr_accessor :raise_on_open_redirects, default: false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Redirects the browser to the target specified in `options`. This parameter can
|
21
|
+
# be any one of:
|
22
|
+
#
|
23
|
+
# * `Hash` - The URL will be generated by calling url_for with the `options`.
|
24
|
+
# * `Record` - The URL will be generated by calling url_for with the
|
25
|
+
# `options`, which will reference a named URL for that record.
|
26
|
+
# * `String` starting with `protocol://` (like `http://`) or a protocol
|
27
|
+
# relative reference (like `//`) - Is passed straight through as the target
|
28
|
+
# for redirection.
|
29
|
+
# * `String` not containing a protocol - The current protocol and host is
|
30
|
+
# prepended to the string.
|
31
|
+
# * `Proc` - A block that will be executed in the controller's context. Should
|
32
|
+
# return any option accepted by `redirect_to`.
|
18
33
|
#
|
19
|
-
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
20
|
-
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
21
|
-
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
|
22
|
-
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
23
|
-
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
|
24
|
-
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
25
|
-
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
|
26
34
|
#
|
27
|
-
#
|
35
|
+
# ### Examples
|
28
36
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# redirect_to proc { edit_post_url(@post) }
|
37
|
+
# redirect_to action: "show", id: 5
|
38
|
+
# redirect_to @post
|
39
|
+
# redirect_to "http://www.rubyonrails.org"
|
40
|
+
# redirect_to "/images/screenshot.jpg"
|
41
|
+
# redirect_to posts_url
|
42
|
+
# redirect_to proc { edit_post_url(@post) }
|
36
43
|
#
|
37
|
-
# The redirection happens as a
|
44
|
+
# The redirection happens as a `302 Found` header unless otherwise specified
|
45
|
+
# using the `:status` option:
|
38
46
|
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
47
|
+
# redirect_to post_url(@post), status: :found
|
48
|
+
# redirect_to action: 'atom', status: :moved_permanently
|
49
|
+
# redirect_to post_url(@post), status: 301
|
50
|
+
# redirect_to action: 'atom', status: 302
|
43
51
|
#
|
44
|
-
# The status code can either be a standard
|
45
|
-
# integer, or a
|
46
|
-
#
|
52
|
+
# The status code can either be a standard [HTTP Status
|
53
|
+
# code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
|
54
|
+
# symbol representing the downcased, underscored and symbolized description.
|
55
|
+
# Note that the status code must be a 3xx HTTP code, or redirection will not
|
56
|
+
# occur.
|
47
57
|
#
|
48
58
|
# If you are using XHR requests other than GET or POST and redirecting after the
|
49
59
|
# request then some browsers will follow the redirect using the original request
|
50
60
|
# method. This may lead to undesirable behavior such as a double DELETE. To work
|
51
|
-
# around this
|
61
|
+
# around this you can return a `303 See Other` status code which will be
|
52
62
|
# followed using a GET request.
|
53
63
|
#
|
54
|
-
#
|
55
|
-
#
|
64
|
+
# redirect_to posts_url, status: :see_other
|
65
|
+
# redirect_to action: 'index', status: 303
|
66
|
+
#
|
67
|
+
# It is also possible to assign a flash message as part of the redirection.
|
68
|
+
# There are two special accessors for the commonly used flash names `alert` and
|
69
|
+
# `notice` as well as a general purpose `flash` bucket.
|
70
|
+
#
|
71
|
+
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
72
|
+
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
73
|
+
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
74
|
+
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
75
|
+
#
|
76
|
+
# Statements after `redirect_to` in our controller get executed, so
|
77
|
+
# `redirect_to` doesn't stop the execution of the function. To terminate the
|
78
|
+
# execution of the function immediately after the `redirect_to`, use return.
|
79
|
+
#
|
80
|
+
# redirect_to post_url(@post) and return
|
56
81
|
#
|
57
|
-
#
|
58
|
-
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
|
82
|
+
# ### Open Redirect protection
|
59
83
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
84
|
+
# By default, Rails protects against redirecting to external hosts for your
|
85
|
+
# app's safety, so called open redirects. Note: this was a new default in Rails
|
86
|
+
# 7.0, after upgrading opt-in by uncommenting the line with
|
87
|
+
# `raise_on_open_redirects` in
|
88
|
+
# `config/initializers/new_framework_defaults_7_0.rb`
|
64
89
|
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
|
90
|
+
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
91
|
+
#
|
92
|
+
# redirect_to params[:redirect_url]
|
93
|
+
#
|
94
|
+
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
95
|
+
#
|
96
|
+
# To allow any external redirects pass `allow_other_host: true`, though using a
|
97
|
+
# user-provided param in that case is unsafe.
|
98
|
+
#
|
99
|
+
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
100
|
+
#
|
101
|
+
# See #url_from for more information on what an internal and safe URL is, or how
|
102
|
+
# to fall back to an alternate redirect URL in the unsafe case.
|
103
|
+
def redirect_to(options = {}, response_options = {})
|
70
104
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
71
|
-
raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
|
72
105
|
raise AbstractController::DoubleRenderError if response_body
|
73
106
|
|
74
|
-
|
75
|
-
|
76
|
-
self.
|
107
|
+
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
|
108
|
+
|
109
|
+
self.status = _extract_redirect_to_status(options, response_options)
|
110
|
+
|
111
|
+
redirect_to_location = _compute_redirect_to_location(request, options)
|
112
|
+
_ensure_url_is_http_header_safe(redirect_to_location)
|
113
|
+
|
114
|
+
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
|
115
|
+
self.response_body = ""
|
116
|
+
end
|
117
|
+
|
118
|
+
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
|
119
|
+
# location is supplied as a keyword argument instead of the first positional
|
120
|
+
# argument.
|
121
|
+
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
122
|
+
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
123
|
+
end
|
124
|
+
|
125
|
+
# Redirects the browser to the page that issued the request (the referrer) if
|
126
|
+
# possible, otherwise redirects to the provided default fallback location.
|
127
|
+
#
|
128
|
+
# The referrer information is pulled from the HTTP `Referer` (sic) header on the
|
129
|
+
# request. This is an optional header and its presence on the request is subject
|
130
|
+
# to browser security settings and user preferences. If the request is missing
|
131
|
+
# this header, the `fallback_location` will be used.
|
132
|
+
#
|
133
|
+
# redirect_back_or_to({ action: "show", id: 5 })
|
134
|
+
# redirect_back_or_to @post
|
135
|
+
# redirect_back_or_to "http://www.rubyonrails.org"
|
136
|
+
# redirect_back_or_to "/images/screenshot.jpg"
|
137
|
+
# redirect_back_or_to posts_url
|
138
|
+
# redirect_back_or_to proc { edit_post_url(@post) }
|
139
|
+
# redirect_back_or_to '/', allow_other_host: false
|
140
|
+
#
|
141
|
+
# #### Options
|
142
|
+
# * `:allow_other_host` - Allow or disallow redirection to the host that is
|
143
|
+
# different to the current host, defaults to true.
|
144
|
+
#
|
145
|
+
#
|
146
|
+
# All other options that can be passed to #redirect_to are accepted as options,
|
147
|
+
# and the behavior is identical.
|
148
|
+
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
149
|
+
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
150
|
+
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
151
|
+
else
|
152
|
+
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
153
|
+
# and let the `redirect_to` handling take over.
|
154
|
+
redirect_to fallback_location, **options
|
155
|
+
end
|
77
156
|
end
|
78
157
|
|
79
|
-
def _compute_redirect_to_location(request, options)
|
158
|
+
def _compute_redirect_to_location(request, options) # :nodoc:
|
80
159
|
case options
|
81
|
-
# The scheme name consist of a letter followed by any combination of
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
when /\A([a-z][a-z\d
|
87
|
-
options
|
160
|
+
# The scheme name consist of a letter followed by any combination of letters,
|
161
|
+
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
|
162
|
+
# terminated by a colon (":"). See
|
163
|
+
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
|
164
|
+
# starts with a double slash "//".
|
165
|
+
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
166
|
+
options.to_str
|
88
167
|
when String
|
89
168
|
request.protocol + request.host_with_port + options
|
90
|
-
when :back
|
91
|
-
request.headers["Referer"] or raise RedirectBackError
|
92
169
|
when Proc
|
93
|
-
_compute_redirect_to_location request, options
|
170
|
+
_compute_redirect_to_location request, instance_eval(&options)
|
94
171
|
else
|
95
172
|
url_for(options)
|
96
173
|
end.delete("\0\r\n")
|
@@ -98,15 +175,77 @@ module ActionController
|
|
98
175
|
module_function :_compute_redirect_to_location
|
99
176
|
public :_compute_redirect_to_location
|
100
177
|
|
178
|
+
# Verifies the passed `location` is an internal URL that's safe to redirect to
|
179
|
+
# and returns it, or nil if not. Useful to wrap a params provided redirect URL
|
180
|
+
# and fall back to an alternate URL to redirect to:
|
181
|
+
#
|
182
|
+
# redirect_to url_from(params[:redirect_url]) || root_url
|
183
|
+
#
|
184
|
+
# The `location` is considered internal, and safe, if it's on the same host as
|
185
|
+
# `request.host`:
|
186
|
+
#
|
187
|
+
# # If request.host is example.com:
|
188
|
+
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
189
|
+
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
190
|
+
# url_from("http://evil.com/profile") # => nil
|
191
|
+
#
|
192
|
+
# Subdomains are considered part of the host:
|
193
|
+
#
|
194
|
+
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
195
|
+
# url_from("https://dev.example.com/profile") # => nil
|
196
|
+
#
|
197
|
+
# NOTE: there's a similarity with
|
198
|
+
# [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
|
199
|
+
# an internal URL from various options from within the app, e.g.
|
200
|
+
# `url_for(@post)`. However, #url_from is meant to take an external parameter to
|
201
|
+
# verify as in `url_from(params[:redirect_url])`.
|
202
|
+
def url_from(location)
|
203
|
+
location = location.presence
|
204
|
+
location if location && _url_host_allowed?(location)
|
205
|
+
end
|
206
|
+
|
101
207
|
private
|
102
|
-
def
|
208
|
+
def _allow_other_host
|
209
|
+
!raise_on_open_redirects
|
210
|
+
end
|
211
|
+
|
212
|
+
def _extract_redirect_to_status(options, response_options)
|
103
213
|
if options.is_a?(Hash) && options.key?(:status)
|
104
214
|
Rack::Utils.status_code(options.delete(:status))
|
105
|
-
elsif
|
106
|
-
Rack::Utils.status_code(
|
215
|
+
elsif response_options.key?(:status)
|
216
|
+
Rack::Utils.status_code(response_options[:status])
|
107
217
|
else
|
108
218
|
302
|
109
219
|
end
|
110
220
|
end
|
221
|
+
|
222
|
+
def _enforce_open_redirect_protection(location, allow_other_host:)
|
223
|
+
if allow_other_host || _url_host_allowed?(location)
|
224
|
+
location
|
225
|
+
else
|
226
|
+
raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def _url_host_allowed?(url)
|
231
|
+
host = URI(url.to_s).host
|
232
|
+
|
233
|
+
return true if host == request.host
|
234
|
+
return false unless host.nil?
|
235
|
+
return false unless url.to_s.start_with?("/")
|
236
|
+
!url.to_s.start_with?("//")
|
237
|
+
rescue ArgumentError, URI::Error
|
238
|
+
false
|
239
|
+
end
|
240
|
+
|
241
|
+
def _ensure_url_is_http_header_safe(url)
|
242
|
+
# Attempt to comply with the set of valid token characters defined for an HTTP
|
243
|
+
# header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
244
|
+
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
245
|
+
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
246
|
+
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
247
|
+
raise UnsafeRedirectError, msg
|
248
|
+
end
|
249
|
+
end
|
111
250
|
end
|
112
251
|
end
|
@@ -1,16 +1,21 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "set"
|
2
6
|
|
3
7
|
module ActionController
|
4
|
-
# See
|
8
|
+
# See Renderers.add
|
5
9
|
def self.add_renderer(key, &block)
|
6
10
|
Renderers.add(key, &block)
|
7
11
|
end
|
8
12
|
|
9
|
-
# See
|
13
|
+
# See Renderers.remove
|
10
14
|
def self.remove_renderer(key)
|
11
15
|
Renderers.remove(key)
|
12
16
|
end
|
13
17
|
|
18
|
+
# See `Responder#api_behavior`
|
14
19
|
class MissingRenderer < LoadError
|
15
20
|
def initialize(format)
|
16
21
|
super "No renderer defined for format: #{format}"
|
@@ -20,71 +25,53 @@ module ActionController
|
|
20
25
|
module Renderers
|
21
26
|
extend ActiveSupport::Concern
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
28
|
+
# A Set containing renderer names that correspond to available renderer procs.
|
29
|
+
# Default values are `:json`, `:js`, `:xml`.
|
30
|
+
RENDERERS = Set.new
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
renderers = _renderers + args
|
31
|
-
self._renderers = renderers.freeze
|
32
|
-
end
|
33
|
-
alias use_renderer use_renderers
|
32
|
+
included do
|
33
|
+
class_attribute :_renderers, default: Set.new.freeze
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
# Used in ActionController::Base and ActionController::API to include all
|
37
|
+
# renderers by default.
|
38
|
+
module All
|
39
|
+
extend ActiveSupport::Concern
|
40
|
+
include Renderers
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
if options.key?(name)
|
43
|
-
_process_options(options)
|
44
|
-
method_name = Renderers._render_with_renderer_method_name(name)
|
45
|
-
return send(method_name, options.delete(name), options)
|
46
|
-
end
|
42
|
+
included do
|
43
|
+
self._renderers = RENDERERS
|
47
44
|
end
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
|
51
|
-
# A Set containing renderer names that correspond to available renderer procs.
|
52
|
-
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
|
53
|
-
RENDERERS = Set.new
|
54
|
-
|
55
|
-
def self._render_with_renderer_method_name(key)
|
56
|
-
"_render_with_renderer_#{key}"
|
57
45
|
end
|
58
46
|
|
59
|
-
# Adds a new renderer to call within controller actions.
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# hash of options passed to +render+.
|
47
|
+
# Adds a new renderer to call within controller actions. A renderer is invoked
|
48
|
+
# by passing its name as an option to AbstractController::Rendering#render. To
|
49
|
+
# create a renderer pass it a name and a block. The block takes two arguments,
|
50
|
+
# the first is the value paired with its key and the second is the remaining
|
51
|
+
# hash of options passed to `render`.
|
65
52
|
#
|
66
53
|
# Create a csv renderer:
|
67
54
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
55
|
+
# ActionController::Renderers.add :csv do |obj, options|
|
56
|
+
# filename = options[:filename] || 'data'
|
57
|
+
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
|
58
|
+
# send_data str, type: Mime[:csv],
|
59
|
+
# disposition: "attachment; filename=#{filename}.csv"
|
60
|
+
# end
|
74
61
|
#
|
75
|
-
# Note that we used Mime
|
62
|
+
# Note that we used [Mime](:csv) for the csv mime type as it comes with Rails.
|
76
63
|
# For a custom renderer, you'll need to register a mime type with
|
77
|
-
#
|
64
|
+
# `Mime::Type.register`.
|
78
65
|
#
|
79
66
|
# To use the csv renderer in a controller action:
|
80
67
|
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
68
|
+
# def show
|
69
|
+
# @csvable = Csvable.find(params[:id])
|
70
|
+
# respond_to do |format|
|
71
|
+
# format.html
|
72
|
+
# format.csv { render csv: @csvable, filename: @csvable.name }
|
73
|
+
# end
|
86
74
|
# end
|
87
|
-
# end
|
88
75
|
def self.add(key, &block)
|
89
76
|
define_method(_render_with_renderer_method_name(key), &block)
|
90
77
|
RENDERERS << key.to_sym
|
@@ -92,46 +79,102 @@ module ActionController
|
|
92
79
|
|
93
80
|
# This method is the opposite of add method.
|
94
81
|
#
|
95
|
-
#
|
82
|
+
# To remove a csv renderer:
|
96
83
|
#
|
97
|
-
#
|
84
|
+
# ActionController::Renderers.remove(:csv)
|
98
85
|
def self.remove(key)
|
99
86
|
RENDERERS.delete(key.to_sym)
|
100
87
|
method_name = _render_with_renderer_method_name(key)
|
101
|
-
|
88
|
+
remove_possible_method(method_name)
|
102
89
|
end
|
103
90
|
|
104
|
-
|
105
|
-
|
106
|
-
|
91
|
+
def self._render_with_renderer_method_name(key)
|
92
|
+
"_render_with_renderer_#{key}"
|
93
|
+
end
|
107
94
|
|
108
|
-
|
109
|
-
|
95
|
+
module ClassMethods
|
96
|
+
# Adds, by name, a renderer or renderers to the `_renderers` available to call
|
97
|
+
# within controller actions.
|
98
|
+
#
|
99
|
+
# It is useful when rendering from an ActionController::Metal controller or
|
100
|
+
# otherwise to add an available renderer proc to a specific controller.
|
101
|
+
#
|
102
|
+
# Both ActionController::Base and ActionController::API include
|
103
|
+
# ActionController::Renderers::All, making all renderers available in the
|
104
|
+
# controller. See Renderers::RENDERERS and Renderers.add.
|
105
|
+
#
|
106
|
+
# Since ActionController::Metal controllers cannot render, the controller must
|
107
|
+
# include AbstractController::Rendering, ActionController::Rendering, and
|
108
|
+
# ActionController::Renderers, and have at least one renderer.
|
109
|
+
#
|
110
|
+
# Rather than including ActionController::Renderers::All and including all
|
111
|
+
# renderers, you may specify which renderers to include by passing the renderer
|
112
|
+
# name or names to `use_renderers`. For example, a controller that includes only
|
113
|
+
# the `:json` renderer (`_render_with_renderer_json`) might look like:
|
114
|
+
#
|
115
|
+
# class MetalRenderingController < ActionController::Metal
|
116
|
+
# include AbstractController::Rendering
|
117
|
+
# include ActionController::Rendering
|
118
|
+
# include ActionController::Renderers
|
119
|
+
#
|
120
|
+
# use_renderers :json
|
121
|
+
#
|
122
|
+
# def show
|
123
|
+
# render json: record
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# You must specify a `use_renderer`, else the `controller.renderer` and
|
128
|
+
# `controller._renderers` will be `nil`, and the action will fail.
|
129
|
+
def use_renderers(*args)
|
130
|
+
renderers = _renderers + args
|
131
|
+
self._renderers = renderers.freeze
|
110
132
|
end
|
133
|
+
alias use_renderer use_renderers
|
134
|
+
end
|
135
|
+
|
136
|
+
# Called by `render` in AbstractController::Rendering which sets the return
|
137
|
+
# value as the `response_body`.
|
138
|
+
#
|
139
|
+
# If no renderer is found, `super` returns control to
|
140
|
+
# `ActionView::Rendering.render_to_body`, if present.
|
141
|
+
def render_to_body(options)
|
142
|
+
_render_to_body_with_renderer(options) || super
|
143
|
+
end
|
144
|
+
|
145
|
+
def _render_to_body_with_renderer(options)
|
146
|
+
_renderers.each do |name|
|
147
|
+
if options.key?(name)
|
148
|
+
_process_options(options)
|
149
|
+
method_name = Renderers._render_with_renderer_method_name(name)
|
150
|
+
return send(method_name, options.delete(name), options)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
nil
|
111
154
|
end
|
112
155
|
|
113
156
|
add :json do |json, options|
|
114
157
|
json = json.to_json(options) unless json.kind_of?(String)
|
115
158
|
|
116
159
|
if options[:callback].present?
|
117
|
-
if
|
118
|
-
self.content_type = Mime
|
160
|
+
if media_type.nil? || media_type == Mime[:json]
|
161
|
+
self.content_type = Mime[:js]
|
119
162
|
end
|
120
163
|
|
121
164
|
"/**/#{options[:callback]}(#{json})"
|
122
165
|
else
|
123
|
-
self.content_type
|
166
|
+
self.content_type = Mime[:json] if media_type.nil?
|
124
167
|
json
|
125
168
|
end
|
126
169
|
end
|
127
170
|
|
128
171
|
add :js do |js, options|
|
129
|
-
self.content_type
|
172
|
+
self.content_type = Mime[:js] if media_type.nil?
|
130
173
|
js.respond_to?(:to_js) ? js.to_js(options) : js
|
131
174
|
end
|
132
175
|
|
133
176
|
add :xml do |xml, options|
|
134
|
-
self.content_type
|
177
|
+
self.content_type = Mime[:xml] if media_type.nil?
|
135
178
|
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
|
136
179
|
end
|
137
180
|
end
|