diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index ca48b301d4..768fc14b00 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -50,6 +50,10 @@ class BaseHandler(object): def get_response(self, request): "Returns an HttpResponse object for the given HttpRequest" + response = self._real_get_response(request) + return fix_location_header(request, response) + + def _real_get_response(self, request): from django.core import exceptions, urlresolvers from django.core.mail import mail_admins from django.conf import settings @@ -129,3 +133,16 @@ class BaseHandler(object): "Helper function to return the traceback as a string" import traceback return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info()))) + +def fix_location_header(request, response): + """ + Ensure that we always use an absolute URI in any location header in the + response. This is required by RFC 2616, section 14.30. + + Code constructing response objects is free to insert relative paths and + this function converts them to absolute paths. + """ + if 'Location' in response.headers and http.get_host(request): + response['Location'] = request.build_absolute_uri(response['Location']) + return response + diff --git a/django/http/__init__.py b/django/http/__init__.py index 20818f138b..74764690a3 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -2,6 +2,7 @@ import os from Cookie import SimpleCookie from pprint import pformat from urllib import urlencode +from urlparse import urljoin from django.utils.datastructures import MultiValueDict, FileDict from django.utils.encoding import smart_str, iri_to_uri, force_unicode @@ -42,10 +43,24 @@ class HttpRequest(object): return key in self.GET or key in self.POST __contains__ = has_key - + def get_full_path(self): return '' + def build_absolute_uri(self, location=None): + """ + Builds an absolute URI from the location and the variables available in + this request. If no location is specified, the absolute URI is built on + ``request.get_full_path()``. + """ + if not location: + location = request.get_full_path() + if not ':' in location: + current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http', + get_host(self), self.path) + location = urljoin(current_uri, location) + return location + def is_secure(self): return os.environ.get("HTTPS") == "on" diff --git a/django/test/testcases.py b/django/test/testcases.py index baa6e7bb19..6b7714ec7b 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -84,12 +84,8 @@ class TestCase(unittest.TestCase): self.assertEqual(response.status_code, status_code, ("Response didn't redirect as expected: Response code was %d" " (expected %d)" % (response.status_code, status_code))) - scheme, netloc, path, query, fragment = urlsplit(response['Location']) - url = path - if query: - url += '?' + query - if fragment: - url += '#' + fragment + url = response['Location'] + scheme, netloc, path, query, fragment = urlsplit(url) self.assertEqual(url, expected_url, "Response redirected to '%s', expected '%s'" % (url, expected_url)) diff --git a/docs/request_response.txt b/docs/request_response.txt index cf68505066..5c1abfa2ad 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -161,6 +161,16 @@ Methods Example: ``"/music/bands/the_beatles/?print=true"`` +``build_absolute_uri(location)`` + Returns the absolute URI form of ``location``. If no location is provided, + the location will be set to ``request.get_full_path()``. + + If the location is already an absolute URI, it will not be altered. + Otherwise the absolute URI is built using the server variables available in + this request. + + Example: ``"http://example.com/music/bands/the_beatles/?print=true"`` + ``is_secure()`` Returns ``True`` if the request is secure; that is, if it was made with HTTPS. diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 2bc1dfe02b..5e1eb529c0 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -83,9 +83,13 @@ class ClientTest(TestCase): def test_redirect(self): "GET a URL that redirects elsewhere" response = self.client.get('/test_client/redirect_view/') - # Check that the response was a 302 (redirect) self.assertRedirects(response, '/test_client/get_view/') + + client_providing_host = Client(HTTP_HOST='django.testserver') + response = client_providing_host.get('/test_client/redirect_view/') + # Check that the response was a 302 (redirect) with absolute URI + self.assertRedirects(response, 'http://django.testserver/test_client/get_view/') def test_redirect_with_query(self): "GET a URL that redirects with given GET parameters" @@ -97,10 +101,14 @@ class ClientTest(TestCase): def test_permanent_redirect(self): "GET a URL that redirects permanently elsewhere" response = self.client.get('/test_client/permanent_redirect_view/') - # Check that the response was a 301 (permanent redirect) self.assertRedirects(response, '/test_client/get_view/', status_code=301) + client_providing_host = Client(HTTP_HOST='django.testserver') + response = client_providing_host.get('/test_client/permanent_redirect_view/') + # Check that the response was a 301 (permanent redirect) with absolute URI + self.assertRedirects(response, 'http://django.testserver/test_client/get_view/', status_code=301) + def test_redirect_to_strange_location(self): "GET a URL that redirects to a non-200 page" response = self.client.get('/test_client/double_redirect_view/')