Origin: upstream, https://github.com/django/django/commit/4dea4883e6c50d75f215a6b9bcbd95273f57c72d
Origin: upstream, https://github.com/django/django/commit/d0d5dc6cd76f01c8a71b677357ad2f702cb54416
Subject: cross-site redirection issue

https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued/

CVE-2012-3442

Index: python-django-1.3/django/http/__init__.py
===================================================================
--- python-django-1.3.orig/django/http/__init__.py	2012-09-06 08:41:00.663981293 -0400
+++ python-django-1.3/django/http/__init__.py	2012-09-06 08:41:00.671981294 -0400
@@ -4,7 +4,7 @@
 import time
 from pprint import pformat
 from urllib import urlencode, quote
-from urlparse import urljoin
+from urlparse import urljoin, urlparse
 try:
     from cStringIO import StringIO
 except ImportError:
@@ -117,6 +117,7 @@
         warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.",
                       PendingDeprecationWarning)
 
+from django.core.exceptions import SuspiciousOperation
 from django.utils.datastructures import MultiValueDict, ImmutableList
 from django.utils.encoding import smart_str, iri_to_uri, force_unicode
 from django.utils.http import cookie_date
@@ -631,19 +632,21 @@
             raise Exception("This %s instance cannot tell its position" % self.__class__)
         return sum([len(chunk) for chunk in self._container])
 
-class HttpResponseRedirect(HttpResponse):
-    status_code = 302
+class HttpResponseRedirectBase(HttpResponse):
+    allowed_schemes = ['http', 'https', 'ftp']
 
     def __init__(self, redirect_to):
-        super(HttpResponseRedirect, self).__init__()
+        super(HttpResponseRedirectBase, self).__init__()
+        parsed = urlparse(redirect_to)
+        if parsed[0] and parsed[0] not in self.allowed_schemes:
+            raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed[0])
         self['Location'] = iri_to_uri(redirect_to)
 
-class HttpResponsePermanentRedirect(HttpResponse):
-    status_code = 301
+class HttpResponseRedirect(HttpResponseRedirectBase):
+    status_code = 302
 
-    def __init__(self, redirect_to):
-        super(HttpResponsePermanentRedirect, self).__init__()
-        self['Location'] = iri_to_uri(redirect_to)
+class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
+    status_code = 301
 
 class HttpResponseNotModified(HttpResponse):
     status_code = 304
Index: python-django-1.3/tests/regressiontests/httpwrappers/tests.py
===================================================================
--- python-django-1.3.orig/tests/regressiontests/httpwrappers/tests.py	2012-09-06 08:40:50.463981033 -0400
+++ python-django-1.3/tests/regressiontests/httpwrappers/tests.py	2012-09-06 08:41:00.671981294 -0400
@@ -1,8 +1,11 @@
 import copy
 import pickle
 
-from django.http import (QueryDict, HttpResponse, SimpleCookie, BadHeaderError,
-        parse_cookie)
+from django.core.exceptions import SuspiciousOperation
+from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
+                         HttpResponsePermanentRedirect,
+                         SimpleCookie, BadHeaderError,
+                         parse_cookie)
 from django.utils import unittest
 
 class QueryDictTests(unittest.TestCase):
@@ -243,6 +246,18 @@
         self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test')
         self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test')
 
+    def test_unsafe_redirects(self):
+        bad_urls = [
+            'data:text/html,<script>window.alert("xss")</script>',
+            'mailto:test@example.com',
+            'file:///etc/passwd',
+        ]
+        for url in bad_urls:
+            self.assertRaises(SuspiciousOperation,
+                              HttpResponseRedirect, url)
+            self.assertRaises(SuspiciousOperation,
+                              HttpResponsePermanentRedirect, url)
+
 class CookieTests(unittest.TestCase):
     def test_encode(self):
         """
