Commits

Anonymous committed 25eec48

Fixed #987 -- Convert relative URI portions into absolute URIs in HTTP Location headers. Based on a patch from SmileyChris.

Comments (0)

Files changed (5)

django/core/handlers/base.py

 
     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
         "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
+

django/http/__init__.py

 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
 
         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"
 

django/test/testcases.py

         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))
 

docs/request_response.txt

 
    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.

tests/modeltests/test_client/models.py

     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"
     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/')