aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorDerek Prior <derekprior@gmail.com>2015-12-15 20:17:32 -0500
committerDerek Prior <derekprior@gmail.com>2015-12-16 11:42:05 -0500
commit13fd5586cef628a71e0e2900820010742a911099 (patch)
treeb6a82887247a24b6877e63156cf002df5028c856 /actionpack
parentd95351236071215a931c626ec2fe7059270f606c (diff)
downloadrails-13fd5586cef628a71e0e2900820010742a911099.tar.gz
rails-13fd5586cef628a71e0e2900820010742a911099.tar.bz2
rails-13fd5586cef628a71e0e2900820010742a911099.zip
Add `redirect_back` for safer referrer redirects
`redirect_to :back` is a somewhat common pattern in Rails apps, but it is not completely safe. There are a number of circumstances where HTTP referrer information is not available on the request. This happens often with bot traffic and occasionally to user traffic depending on browser security settings. When there is no referrer available on the request, `redirect_to :back` will raise `ActionController::RedirectBackError`, usually resulting in an application error. `redirect_back` takes a required `fallback_location` keyword argument that specifies the redirect when the referrer information is not available. This prevents 500 errors caused by `ActionController::RedirectBackError`.
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb23
-rw-r--r--actionpack/test/controller/redirect_test.rb21
3 files changed, 50 insertions, 0 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 608512d846..bdc0fbe01a 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Add `redirect_back` method to `ActionController::Redirecting` to provide a
+ way to safely redirect to the `HTTP_REFERER` if it is present, falling back
+ to a provided redirect otherwise.
+
+ *Derek Prior*
+
* `ActionController::TestCase` will be moved to it's own gem in Rails 5.1
With the speed improvements made to `ActionDispatch::IntegrationTest` we no
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 0febc905f1..72ceb9a5bc 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -75,6 +75,29 @@ module ActionController
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
end
+ # Redirects the browser to the page that issued the request if possible,
+ # otherwise redirects to provided default fallback location.
+ #
+ # This avoids the <tt>ActionController::RedirectBackError</tt> that can
+ # occur if the request has no associated <tt>HTTP_REFERER</tt>.
+ #
+ # redirect_back fallback_location: { action: "show", id: 5 }
+ # redirect_back fallback_location: post
+ # redirect_back fallback_location: "http://www.rubyonrails.org"
+ # redirect_back fallback_location: "/images/screenshot.jpg"
+ # redirect_back fallback_location: articles_url
+ # redirect_back fallback_location: proc { edit_post_url(@post) }
+ #
+ # All options that can be passed to <tt>redirect_to</tt> are accepted as
+ # options and the behavior is indetical.
+ def redirect_back(fallback_location:, **args)
+ if referer = request.headers["Referer"]
+ redirect_to referer, **args
+ else
+ redirect_to fallback_location, **args
+ end
+ end
+
def _compute_redirect_to_location(request, options) #:nodoc:
case options
# The scheme name consist of a letter followed by any combination of
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 631ff7d02a..28ff1f36cf 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -42,6 +42,10 @@ class RedirectController < ActionController::Base
redirect_to :back, :status => 307
end
+ def redirect_back_with_status
+ redirect_back(fallback_location: "/things/stuff", status: 307)
+ end
+
def host_redirect
redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
end
@@ -248,6 +252,23 @@ class RedirectTest < ActionController::TestCase
}
end
+ def test_redirect_back
+ referer = "http://www.example.com/coming/from"
+ @request.env["HTTP_REFERER"] = referer
+
+ get :redirect_back_with_status
+
+ assert_response 307
+ assert_equal referer, redirect_to_url
+ end
+
+ def test_redirect_back_with_no_referer
+ get :redirect_back_with_status
+
+ assert_response 307
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
def test_redirect_to_record
with_routing do |set|
set.draw do