Module: ActionController::Redirecting

Extended by:
ActiveSupport::Concern
Includes:
AbstractController::Logger, UrlFor
Included in:
Base
Defined in:
lib/action_controller/metal/redirecting.rb

Defined Under Namespace

Classes: UnsafeRedirectError

Constant Summary collapse

ILLEGAL_HEADER_VALUE_REGEX =
/[\x00-\x08\x0A-\x1F]/

Class Method Summary collapse

Instance Method Summary collapse

Methods included from UrlFor

#initialize, #url_options

Methods included from AbstractController::UrlFor

#_routes

Methods included from ActionDispatch::Routing::UrlFor

#full_url_for, #initialize, #route_for, #url_for, #url_options

Methods included from ActionDispatch::Routing::PolymorphicRoutes

#polymorphic_path, #polymorphic_url

Class Method Details

._compute_redirect_to_location(request, options) ⇒ Object

:nodoc:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/action_controller/metal/redirecting.rb', line 158

def _compute_redirect_to_location(request, options) # :nodoc:
  case options
  # The scheme name consist of a letter followed by any combination of letters,
  # digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
  # terminated by a colon (":"). See
  # https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
  # starts with a double slash "//".
  when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
    options.to_str
  when String
    request.protocol + request.host_with_port + options
  when Proc
    _compute_redirect_to_location request, instance_eval(&options)
  else
    url_for(options)
  end.delete("\0\r\n")
end

Instance Method Details

#_compute_redirect_to_location(request, options) ⇒ Object

:nodoc:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/action_controller/metal/redirecting.rb', line 158

def _compute_redirect_to_location(request, options) # :nodoc:
  case options
  # The scheme name consist of a letter followed by any combination of letters,
  # digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
  # terminated by a colon (":"). See
  # https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
  # starts with a double slash "//".
  when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
    options.to_str
  when String
    request.protocol + request.host_with_port + options
  when Proc
    _compute_redirect_to_location request, instance_eval(&options)
  else
    url_for(options)
  end.delete("\0\r\n")
end

#redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args) ⇒ Object

Soft deprecated alias for #redirect_back_or_to where the ‘fallback_location` location is supplied as a keyword argument instead of the first positional argument.



121
122
123
# File 'lib/action_controller/metal/redirecting.rb', line 121

def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
  redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
end

#redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options) ⇒ Object

Redirects the browser to the page that issued the request (the referrer) if possible, otherwise redirects to the provided default fallback location.

The referrer information is pulled from the HTTP ‘Referer` (sic) header on the request. This is an optional header and its presence on the request is subject to browser security settings and user preferences. If the request is missing this header, the `fallback_location` will be used.

redirect_back_or_to({ action: "show", id: 5 })
redirect_back_or_to @post
redirect_back_or_to "http://www.rubyonrails.org"
redirect_back_or_to "/images/screenshot.jpg"
redirect_back_or_to posts_url
redirect_back_or_to proc { edit_post_url(@post) }
redirect_back_or_to '/', allow_other_host: false

#### Options

  • ‘:allow_other_host` - Allow or disallow redirection to the host that is different to the current host, defaults to true.

All other options that can be passed to #redirect_to are accepted as options, and the behavior is identical.



148
149
150
151
152
153
154
155
156
# File 'lib/action_controller/metal/redirecting.rb', line 148

def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
  if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
    redirect_to request.referer, allow_other_host: allow_other_host, **options
  else
    # The method level `allow_other_host` doesn't apply in the fallback case, omit
    # and let the `redirect_to` handling take over.
    redirect_to fallback_location, **options
  end
end

#redirect_to(options = {}, response_options = {}) ⇒ Object

Redirects the browser to the target specified in ‘options`. This parameter can be any one of:

  • ‘Hash` - The URL will be generated by calling url_for with the `options`.

  • ‘Record` - The URL will be generated by calling url_for with the `options`, which will reference a named URL for that record.

  • ‘String` starting with `protocol://` (like `http://`) or a protocol relative reference (like `//`) - Is passed straight through as the target for redirection.

  • ‘String` not containing a protocol - The current protocol and host is prepended to the string.

  • ‘Proc` - A block that will be executed in the controller’s context. Should return any option accepted by ‘redirect_to`.

### Examples

redirect_to action: "show", id: 5
redirect_to @post
redirect_to "http://www.rubyonrails.org"
redirect_to "/images/screenshot.jpg"
redirect_to posts_url
redirect_to proc { edit_post_url(@post) }

The redirection happens as a ‘302 Found` header unless otherwise specified using the `:status` option:

redirect_to post_url(@post), status: :found
redirect_to action: 'atom', status: :moved_permanently
redirect_to post_url(@post), status: 301
redirect_to action: 'atom', status: 302

The status code can either be a standard [HTTP Status code](www.iana.org/assignments/http-status-codes) as an integer, or a symbol representing the downcased, underscored and symbolized description. Note that the status code must be a 3xx HTTP code, or redirection will not occur.

If you are using XHR requests other than GET or POST and redirecting after the request then some browsers will follow the redirect using the original request method. This may lead to undesirable behavior such as a double DELETE. To work around this you can return a ‘303 See Other` status code which will be followed using a GET request.

redirect_to posts_url, status: :see_other
redirect_to action: 'index', status: 303

It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names ‘alert` and `notice` as well as a general purpose `flash` bucket.

redirect_to post_url(@post), alert: "Watch it, mister!"
redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
redirect_to({ action: 'atom' }, alert: "Something serious happened")

Statements after ‘redirect_to` in our controller get executed, so `redirect_to` doesn’t stop the execution of the function. To terminate the execution of the function immediately after the ‘redirect_to`, use return.

redirect_to post_url(@post) and return

### Open Redirect protection

By default, Rails protects against redirecting to external hosts for your app’s safety, so called open redirects. Note: this was a new default in Rails 7.0, after upgrading opt-in by uncommenting the line with ‘raise_on_open_redirects` in `config/initializers/new_framework_defaults_7_0.rb`

Here #redirect_to automatically validates the potentially-unsafe URL:

redirect_to params[:redirect_url]

Raises UnsafeRedirectError in the case of an unsafe redirect.

To allow any external redirects pass ‘allow_other_host: true`, though using a user-provided param in that case is unsafe.

redirect_to "https://rubyonrails.org", allow_other_host: true

See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/action_controller/metal/redirecting.rb', line 103

def redirect_to(options = {}, response_options = {})
  raise ActionControllerError.new("Cannot redirect to nil!") unless options
  raise AbstractController::DoubleRenderError if response_body

  allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }

  self.status = _extract_redirect_to_status(options, response_options)

  redirect_to_location = _compute_redirect_to_location(request, options)
  _ensure_url_is_http_header_safe(redirect_to_location)

  self.location      = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
  self.response_body = ""
end

#url_from(location) ⇒ Object

Verifies the passed ‘location` is an internal URL that’s safe to redirect to and returns it, or nil if not. Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to:

redirect_to url_from(params[:redirect_url]) || root_url

The ‘location` is considered internal, and safe, if it’s on the same host as ‘request.host`:

# If request.host is example.com:
url_from("https://example.com/profile") # => "https://example.com/profile"
url_from("http://example.com/profile")  # => "http://example.com/profile"
url_from("http://evil.com/profile")     # => nil

Subdomains are considered part of the host:

# If request.host is on https://example.com or https://app.example.com, you'd get:
url_from("https://dev.example.com/profile") # => nil

NOTE: there’s a similarity with [url_for](ActionDispatch::Routing::UrlFor#url_for), which generates an internal URL from various options from within the app, e.g. ‘url_for(@post)`. However, #url_from is meant to take an external parameter to verify as in `url_from(params)`.



202
203
204
205
# File 'lib/action_controller/metal/redirecting.rb', line 202

def url_from(location)
  location = location.presence
  location if location && _url_host_allowed?(location)
end