module ActionController::Redirecting

def _allow_other_host

def _allow_other_host
  !raise_on_open_redirects
end

def _compute_redirect_to_location(request, options) # :nodoc:

:nodoc:
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

def _enforce_open_redirect_protection(location, allow_other_host:)

def _enforce_open_redirect_protection(location, allow_other_host:)
  if allow_other_host || _url_host_allowed?(location)
    location
  else
    raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway."
  end
end

def _ensure_url_is_http_header_safe(url)

def _ensure_url_is_http_header_safe(url)
  # Attempt to comply with the set of valid token characters
  # defined for an HTTP header value in
  # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
  if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
    msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
      "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
    raise UnsafeRedirectError, msg
  end
end

def _extract_redirect_to_status(options, response_options)

def _extract_redirect_to_status(options, response_options)
  if options.is_a?(Hash) && options.key?(:status)
    Rack::Utils.status_code(options.delete(:status))
  elsif response_options.key?(:status)
    Rack::Utils.status_code(response_options[:status])
  else
    302
  end
end

def _url_host_allowed?(url)

def _url_host_allowed?(url)
  host = URI(url.to_s).host
  return true if host == request.host
  return false unless host.nil?
  return false unless url.to_s.start_with?("/")
  !url.to_s.start_with?("//")
rescue ArgumentError, URI::Error
  false
end

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

of the first positional argument.
Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
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

def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)

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

* :allow_other_host - Allow or disallow redirection to the host that is different to the current host, defaults to true.
==== Options

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

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

location.
if possible, otherwise redirects to the provided default fallback
Redirects the browser to the page that issued the request (the referrer)
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

def redirect_to(options = {}, response_options = {})

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.

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

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

Raises UnsafeRedirectError in the case of an unsafe redirect.

redirect_to params[:redirect_url]

Here #redirect_to automatically validates the potentially-unsafe URL:

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
By default, Rails protects against redirecting to external hosts for your app's safety, so called open redirects.

=== Open Redirect protection

redirect_to post_url(@post) and return

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

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

+alert+ and +notice+ as well as a general purpose +flash+ bucket.
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

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

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

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

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

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

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

=== Examples:

* Proc - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
* String not containing a protocol - The current protocol and host is prepended to the string.
* String starting with protocol:// (like http://) or a protocol relative reference (like //) - Is passed straight through as the target for redirection.
* Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
* Hash - The URL will be generated by calling url_for with the +options+.

Redirects the browser to the target specified in +options+. This parameter can be any one of:
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 = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end

def url_from(location)

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

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

Subdomains are considered part of the host:

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

The +location+ is considered internal, and safe, if it's on the same host as request.host:

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

Useful to wrap a params provided redirect URL and fallback to an alternate URL to redirect to:
Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
def url_from(location)
  location = location.presence
  location if location && _url_host_allowed?(location)
end