[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Wheezy update of python-django?



Chris Lamb <chris@chris-lamb.co.uk> writes:

>> I have a version of python-django 1.4.22 for wheezy-security available
>> for testing at:
>> 
>> https://people.debian.org/~bam/debian/pool/main/p/python-django/
>
> I've had a quick play with this and everything seems to check out although I do not have any Django 1.4 (!) projects lying around to test against. Could you upload a debdiff too?

Attached is my latest debdiff patch, only includes changes to debian/*.

I have also updated to git repository. I converted the wheezy branch to
use git-dpm, and imported every release since the last update.
-- 
Brian May <bam@debian.org>
diff -Nru python-django-1.4.5/debian/changelog python-django-1.4.22/debian/changelog
--- python-django-1.4.5/debian/changelog	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/changelog	2016-08-04 17:58:02.000000000 +1000
@@ -1,3 +1,10 @@
+python-django (1.4.22-1) wheezy-security; urgency=medium
+
+  * Update wheezy to latest version in 1.4.x series.
+  * Remove numerous security fixes that were applied upstream.
+
+ -- Brian May <bam@debian.org>  Wed, 03 Aug 2016 18:08:22 +1000
+
 python-django (1.4.5-1+deb7u16) wheezy-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff -Nru python-django-1.4.5/debian/.git-dpm python-django-1.4.22/debian/.git-dpm
--- python-django-1.4.5/debian/.git-dpm	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/.git-dpm	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,11 @@
+# see git-dpm(1) from git-dpm package
+cd296c0e8a82e71edb2c5fa3edd992b8d46b65fa
+cd296c0e8a82e71edb2c5fa3edd992b8d46b65fa
+6811f42291f01f7636726c8bdd8999842f0cd9ec
+6811f42291f01f7636726c8bdd8999842f0cd9ec
+python-django_1.4.22.orig.tar.gz
+cedd81e52f794c6f69b9a71c65e90f16570783c7
+7802249
+debianTag="debian/%e%v"
+patchedTag="patched/%e%v"
+upstreamTag="upstream/%e%u"
diff -Nru python-django-1.4.5/debian/patches/0001-Disable-creation-of-_sources-directory-by-Sphinx.patch python-django-1.4.22/debian/patches/0001-Disable-creation-of-_sources-directory-by-Sphinx.patch
--- python-django-1.4.5/debian/patches/0001-Disable-creation-of-_sources-directory-by-Sphinx.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0001-Disable-creation-of-_sources-directory-by-Sphinx.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,31 @@
+From 41f4e39d56efb633dd3b35fa1b901250c259fa1f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rapha=C3=ABl=20Hertzog?= <hertzog@debian.org>
+Date: Thu, 4 Aug 2016 17:50:23 +1000
+Subject: Disable creation of _sources directory by Sphinx
+
+ We do this to save some space as the sources of the documentation
+ are not really useful in a binary package.
+ .
+ This is a Debian specific patch.
+Forwarded: not-needed
+Origin: vendor
+---
+ docs/conf.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index d3679fe..7f96d7d 100644
+--- a/docs/conf.py
++++ b/docs/conf.py
+@@ -168,7 +168,10 @@ html_additional_pages = {}
+ #html_split_index = False
+ 
+ # If true, links to the reST sources are added to the pages.
+-#html_show_sourcelink = True
++html_show_sourcelink = False
++
++# Do not ship a copy of the sources
++html_copy_source = False
+ 
+ # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+ #html_show_sphinx = True
diff -Nru python-django-1.4.5/debian/patches/0002-Update-manual-page-to-refer-to-django-admin-instead-.patch python-django-1.4.22/debian/patches/0002-Update-manual-page-to-refer-to-django-admin-instead-.patch
--- python-django-1.4.5/debian/patches/0002-Update-manual-page-to-refer-to-django-admin-instead-.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0002-Update-manual-page-to-refer-to-django-admin-instead-.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,32 @@
+From 829566bc3484b3920bea81f793474ec6568e4c80 Mon Sep 17 00:00:00 2001
+From: Brett Parker <iDunno@sommitrealweird.co.uk>
+Date: Thu, 4 Aug 2016 17:50:24 +1000
+Subject: Update manual page to refer to django-admin instead of
+ django-admin.py
+
+ Update the manual page to speak of django-admin instead of
+ django-admin.py as that's the name used by the Debian package.
+ .
+ This is a Debian specific patch.
+Forwarded: not-needed
+Origin: vendor
+---
+ docs/man/django-admin.1 | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1
+index 1f693b8..602793f 100644
+--- a/docs/man/django-admin.1
++++ b/docs/man/django-admin.1
+@@ -1,8 +1,8 @@
+-.TH "django-admin.py" "1" "March 2008" "Django Project" ""
++.TH "django-admin" "1" "March 2008" "Django Project" ""
+ .SH "NAME"
+-django\-admin.py \- Utility script for the Django Web framework
++django\-admin \- Utility script for the Django Web framework
+ .SH "SYNOPSIS"
+-.B django\-admin.py
++.B django\-admin
+ .I <action>
+ .B [options]
+ .sp
diff -Nru python-django-1.4.5/debian/patches/0003-Use-Debian-GeoIP-database-path-as-default.patch python-django-1.4.22/debian/patches/0003-Use-Debian-GeoIP-database-path-as-default.patch
--- python-django-1.4.5/debian/patches/0003-Use-Debian-GeoIP-database-path-as-default.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0003-Use-Debian-GeoIP-database-path-as-default.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,66 @@
+From aab77a568c305d186e781533a5912515ca888a57 Mon Sep 17 00:00:00 2001
+From: Tapio Rantala <tapio.rantala@iki.fi>
+Date: Thu, 4 Aug 2016 17:50:25 +1000
+Subject: Use Debian GeoIP database path as default
+
+ Default to Debian standard path for GeoIP directory and for GeoIP city
+ file. Avoids the need to declare them in each project.
+ .
+ This is a Debian specific patch.
+Bug-Debian: http://bugs.debian.org/645094
+Forwarded: not-needed
+---
+ django/contrib/gis/geoip/base.py | 18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/django/contrib/gis/geoip/base.py b/django/contrib/gis/geoip/base.py
+index e00e0a4..d77784a 100644
+--- a/django/contrib/gis/geoip/base.py
++++ b/django/contrib/gis/geoip/base.py
+@@ -61,7 +61,8 @@ class GeoIP(object):
+         * path: Base directory to where GeoIP data is located or the full path
+             to where the city or country data files (*.dat) are located.
+             Assumes that both the city and country data sets are located in
+-            this directory; overrides the GEOIP_PATH settings attribute.
++            this directory. Overrides the GEOIP_PATH settings attribute.
++            If neither is set, defaults to '/usr/share/GeoIP'.
+ 
+         * cache: The cache settings when opening up the GeoIP datasets,
+             and may be an integer in (0, 1, 2, 4, 8) corresponding to
+@@ -70,11 +71,13 @@ class GeoIP(object):
+             settings,  respectively.  Defaults to 0, meaning that the data is read
+             from the disk.
+ 
+-        * country: The name of the GeoIP country data file.  Defaults to
+-            'GeoIP.dat'; overrides the GEOIP_COUNTRY settings attribute.
++        * country: The name of the GeoIP country data file. Overrides
++            the GEOIP_COUNTRY settings attribute. If neither is set,
++            defaults to 'GeoIP.dat'
+ 
+-        * city: The name of the GeoIP city data file.  Defaults to
+-            'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
++        * city: The name of the GeoIP city data file. Overrides the
++            GEOIP_CITY settings attribute. If neither is set, defaults
++            to 'GeoIPCity.dat'.
+         """
+         # Checking the given cache option.
+         if cache in self.cache_options:
+@@ -84,8 +87,7 @@ class GeoIP(object):
+ 
+         # Getting the GeoIP data path.
+         if not path:
+-            path = GEOIP_SETTINGS.get('GEOIP_PATH', None)
+-            if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.')
++            path = GEOIP_SETTINGS.get('GEOIP_PATH', '/usr/share/GeoIP')
+         if not isinstance(path, basestring):
+             raise TypeError('Invalid path type: %s' % type(path).__name__)
+ 
+@@ -98,7 +100,7 @@ class GeoIP(object):
+                 self._country = GeoIP_open(country_db, cache)
+                 self._country_file = country_db
+ 
+-            city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
++            city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoIPCity.dat'))
+             if os.path.isfile(city_db):
+                 self._city = GeoIP_open(city_db, cache)
+                 self._city_file = city_db
diff -Nru python-django-1.4.5/debian/patches/0004-Use-name-that-won-t-resolve-in-tests.patch python-django-1.4.22/debian/patches/0004-Use-name-that-won-t-resolve-in-tests.patch
--- python-django-1.4.5/debian/patches/0004-Use-name-that-won-t-resolve-in-tests.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0004-Use-name-that-won-t-resolve-in-tests.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,24 @@
+From 498cd0183d326a93626e081168021cf9dd03dc10 Mon Sep 17 00:00:00 2001
+From: Luke Faraone <lfaraone@debian.org>
+Date: Thu, 4 Aug 2016 17:50:26 +1000
+Subject: Use name that won't resolve in tests.
+
+Last-Updated: 2013-08-13
+Forwarded: no
+---
+ tests/modeltests/validation/tests.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py
+index 3078089..dc6d4ef 100644
+--- a/tests/modeltests/validation/tests.py
++++ b/tests/modeltests/validation/tests.py
+@@ -87,7 +87,7 @@ class BaseModelValidationTests(ValidationTestCase):
+ 
+     @verify_exists_urls(existing_urls=())
+     def test_correct_https_url_but_nonexisting(self):
+-        mtv = ModelToValidate(number=10, name='Some Name', url_verify='https://www.example.com/')
++        mtv = ModelToValidate(number=10, name='Some Name', url_verify='https://www.example.invalid/')
+         self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url_verify', [u'This URL appears to be a broken link.'])
+ 
+     def test_text_greater_that_charfields_max_length_raises_erros(self):
diff -Nru python-django-1.4.5/debian/patches/0005-date-leak-1.4.x.diff.patch python-django-1.4.22/debian/patches/0005-date-leak-1.4.x.diff.patch
--- python-django-1.4.5/debian/patches/0005-date-leak-1.4.x.diff.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0005-date-leak-1.4.x.diff.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,67 @@
+From 57b4c3cd7253f12a0c3a2ec1fbc21b3a969cbcac Mon Sep 17 00:00:00 2001
+From: Florian Apolloner <florian@apolloner.eu>
+Date: Wed, 11 Nov 2015 20:10:55 +0100
+Subject: date-leak-1.4.x.diff
+
+commit 8a01c6b53169ee079cb21ac5919fdafcc8c5e172
+
+    [1.7.x] Fixed a settings leak possibility in the date template filter.
+
+    This is a security fix.
+---
+ django/utils/formats.py             | 20 ++++++++++++++++++++
+ tests/regressiontests/i18n/tests.py |  3 +++
+ 2 files changed, 23 insertions(+)
+
+diff --git a/django/utils/formats.py b/django/utils/formats.py
+index e283490..1796b64 100644
+--- a/django/utils/formats.py
++++ b/django/utils/formats.py
+@@ -15,6 +15,24 @@ from django.utils.translation import get_language, to_locale, check_for_language
+ _format_cache = {}
+ _format_modules_cache = {}
+ 
++FORMAT_SETTINGS = frozenset([
++    'DECIMAL_SEPARATOR',
++    'THOUSAND_SEPARATOR',
++    'NUMBER_GROUPING',
++    'FIRST_DAY_OF_WEEK',
++    'MONTH_DAY_FORMAT',
++    'TIME_FORMAT',
++    'DATE_FORMAT',
++    'DATETIME_FORMAT',
++    'SHORT_DATE_FORMAT',
++    'SHORT_DATETIME_FORMAT',
++    'YEAR_MONTH_FORMAT',
++    'DATE_INPUT_FORMATS',
++    'TIME_INPUT_FORMATS',
++    'DATETIME_INPUT_FORMATS',
++])
++
++
+ def reset_format_cache():
+     """Clear any cached formats.
+ 
+@@ -66,6 +84,8 @@ def get_format(format_type, lang=None, use_l10n=None):
+     be localized (or not), overriding the value of settings.USE_L10N.
+     """
+     format_type = smart_str(format_type)
++    if format_type not in FORMAT_SETTINGS:
++        return format_type
+     if use_l10n or (use_l10n is None and settings.USE_L10N):
+         if lang is None:
+             lang = get_language()
+diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
+index 99a55bd..f8398ee 100644
+--- a/tests/regressiontests/i18n/tests.py
++++ b/tests/regressiontests/i18n/tests.py
+@@ -817,6 +817,9 @@ class MiscTests(TestCase):
+                 self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), u'42% stellt 1 Objekt dar')
+                 self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), u'42% stellt 4 Objekte dar')
+ 
++    def test_format_arbitrary_settings(self):
++        self.assertEqual(get_format('DEBUG'), 'DEBUG')
++
+ 
+ class ResolutionOrderI18NTests(TestCase):
+ 
diff -Nru python-django-1.4.5/debian/patches/0006-CVE-2016-2512-Prevented-spoofing-is_safe_url-with-ba.patch python-django-1.4.22/debian/patches/0006-CVE-2016-2512-Prevented-spoofing-is_safe_url-with-ba.patch
--- python-django-1.4.5/debian/patches/0006-CVE-2016-2512-Prevented-spoofing-is_safe_url-with-ba.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0006-CVE-2016-2512-Prevented-spoofing-is_safe_url-with-ba.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,61 @@
+From a7a80759c2f8f1dc9c72171ee128b23adc73ab2d Mon Sep 17 00:00:00 2001
+From: Mark Striemer <mstriemer@mozilla.com>
+Date: Thu, 4 Aug 2016 17:50:28 +1000
+Subject: CVE-2016-2512: Prevented spoofing is_safe_url() with basic auth
+
+Origin: backport, https://github.com/django/django/commit/382ab137312961ad62feb8109d70a5a581fe8350
+Bug-Debian: https://bugs.debian.org/816434
+Forwarded: not-needed
+Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
+Last-Update: 2016-03-12
+Applied-Upstream: 1.8.10
+---
+ django/contrib/auth/tests/views.py | 8 +++++++-
+ django/utils/http.py               | 8 ++++++--
+ 2 files changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
+index 2b72cd4..5c781ed 100644
+--- a/django/contrib/auth/tests/views.py
++++ b/django/contrib/auth/tests/views.py
+@@ -312,7 +312,12 @@ class LoginTest(AuthViewsTestCase):
+                         'ftp://exampel.com',
+                         '///example.com',
+                         '//example.com',
+-                        'javascript:alert("XSS")'):
++                        'javascript:alert("XSS")',
++                        r'http://otherserver\@example.com',
++                        r'http:\\testserver\@example.com',
++                        r'http://testserver\me:pass@example.com',
++                        r'http://testserver\@example.com',
++                        r'http:\\testserver\confirm\me@example.com'):
+ 
+             nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
+                 'url': login_url,
+@@ -335,6 +340,7 @@ class LoginTest(AuthViewsTestCase):
+                          'https://testserver/',
+                          'HTTPS://testserver/',
+                          '//testserver/',
++                         'http://testserver/confirm?email=me@example.com',
+                          '/url%20with%20spaces/'):  # see ticket #12534
+             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
+                 'url': login_url,
+diff --git a/django/utils/http.py b/django/utils/http.py
+index b8c81a8..f44639c 100644
+--- a/django/utils/http.py
++++ b/django/utils/http.py
+@@ -237,8 +237,12 @@ def is_safe_url(url, host=None):
+         url = url.strip()
+     if not url:
+         return False
+-    # Chrome treats \ completely as /
+-    url = url.replace('\\', '/')
++    # Chrome treats \ completely as / in paths but it could be part of some
++    # basic auth credentials so we need to check both URLs.
++    return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
++
++
++def _is_safe_url(url, host):
+     # Chrome considers any URL with more than two slashes to be absolute, but
+     # urlaprse is not so flexible. Treat any url with three slashes as unsafe.
+     if url.startswith('///'):
diff -Nru python-django-1.4.5/debian/patches/0007-is_safe_url-crashes-with-a-byestring-URL-on-Python-2.patch python-django-1.4.22/debian/patches/0007-is_safe_url-crashes-with-a-byestring-URL-on-Python-2.patch
--- python-django-1.4.5/debian/patches/0007-is_safe_url-crashes-with-a-byestring-URL-on-Python-2.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0007-is_safe_url-crashes-with-a-byestring-URL-on-Python-2.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,64 @@
+From 088e4b079b2b7af4272233fe119696b98dab2a6f Mon Sep 17 00:00:00 2001
+From: Claude Paroz <claude@2xlibre.net>
+Date: Thu, 4 Aug 2016 17:50:29 +1000
+Subject: is_safe_url() crashes with a byestring URL on Python 2
+
+Origin: upstream, https://github.com/django/django/commit/ada7a4aefb9bec4c34667b511022be6057102f98,
+ https://github.com/django/django/commit/beb392b85e71fdd41209d323126181d74090fecb
+Bug: https://code.djangoproject.com/ticket/26308
+Forwarded: not-needed
+Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
+Last-Update: 2016-03-12
+Applied-Upstream: 1.8.11
+---
+ django/utils/http.py                |  6 +++++-
+ tests/regressiontests/utils/http.py | 11 +++++++++++
+ 2 files changed, 16 insertions(+), 1 deletion(-)
+
+diff --git a/django/utils/http.py b/django/utils/http.py
+index f44639c..8185fc4 100644
+--- a/django/utils/http.py
++++ b/django/utils/http.py
+@@ -8,7 +8,7 @@ import unicodedata
+ from email.utils import formatdate
+ 
+ from django.utils.datastructures import MultiValueDict
+-from django.utils.encoding import smart_str, force_unicode
++from django.utils.encoding import smart_str, force_unicode, force_text
+ from django.utils.functional import allow_lazy
+ 
+ ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"')
+@@ -237,6 +237,10 @@ def is_safe_url(url, host=None):
+         url = url.strip()
+     if not url:
+         return False
++    try:
++         url = force_text(url)
++    except UnicodeDecodeError:
++        return False
+     # Chrome treats \ completely as / in paths but it could be part of some
+     # basic auth credentials so we need to check both URLs.
+     return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
+diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
+index 8245a7e..37aaf3e 100644
+--- a/tests/regressiontests/utils/http.py
++++ b/tests/regressiontests/utils/http.py
+@@ -1,3 +1,5 @@
++# -*- encoding: utf-8 -*-
++from __future__ import unicode_literals
+ import sys
+ 
+ from django.utils import http
+@@ -111,3 +113,12 @@ class TestUtilsHttp(unittest.TestCase):
+                      '//testserver/',
+                      '/url%20with%20spaces/'):
+             self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url)
++
++        # Check binary URLs, regression tests for #26308
++        self.assertTrue(
++            http.is_safe_url(b'https://testserver/', host='testserver'),
++            "binary URLs should be allowed on Python 2"
++        )
++        self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver'))
++        self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver'))
++        self.assertFalse(http.is_safe_url('àview'.encode('latin-1'), host='testserver'))
diff -Nru python-django-1.4.5/debian/patches/0008-CVE-2016-2513-Fixed-user-enumeration-timing-attack-d.patch python-django-1.4.22/debian/patches/0008-CVE-2016-2513-Fixed-user-enumeration-timing-attack-d.patch
--- python-django-1.4.5/debian/patches/0008-CVE-2016-2513-Fixed-user-enumeration-timing-attack-d.patch	1970-01-01 10:00:00.000000000 +1000
+++ python-django-1.4.22/debian/patches/0008-CVE-2016-2513-Fixed-user-enumeration-timing-attack-d.patch	2016-08-04 17:58:02.000000000 +1000
@@ -0,0 +1,241 @@
+From cd296c0e8a82e71edb2c5fa3edd992b8d46b65fa Mon Sep 17 00:00:00 2001
+From: Florian Apolloner <florian@apolloner.eu>
+Date: Thu, 4 Aug 2016 17:50:30 +1000
+Subject: CVE-2016-2513: Fixed user enumeration timing attack during login
+
+Origin: backport, https://github.com/django/django/commit/f4e6e02f7713a6924d16540be279909ff4091eb6, https://github.com/django/django/commit/7d0d0dbf26a3c0d16e9c2b930fd6d7b89f215946
+Forwarded: not-needed
+Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
+Last-Update: 2016-03-12
+Applied-Upstream: 1.8.10
+---
+ django/contrib/auth/hashers.py | 64 ++++++++++++++++++++++++++++++++++++++++--
+ django/utils/encoding.py       | 36 ++++++++++++++++++++++++
+ docs/topics/auth.txt           | 30 ++++++++++++++++++++
+ 3 files changed, 128 insertions(+), 2 deletions(-)
+
+diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
+index a9dbcc9..f936aa3 100644
+--- a/django/contrib/auth/hashers.py
++++ b/django/contrib/auth/hashers.py
+@@ -1,9 +1,10 @@
+ import hashlib
++import warnings
+ 
+ from django.conf import settings
+ from django.utils import importlib
+ from django.utils.datastructures import SortedDict
+-from django.utils.encoding import smart_str
++from django.utils.encoding import force_bytes, smart_str
+ from django.core.exceptions import ImproperlyConfigured
+ from django.utils.crypto import (
+     pbkdf2, constant_time_compare, get_random_string)
+@@ -47,8 +48,17 @@ def check_password(password, encoded, setter=None, preferred='default'):
+         algorithm = encoded.split('$', 1)[0]
+         hasher = get_hasher(algorithm)
+ 
+-    must_update = hasher.algorithm != preferred.algorithm
++    hasher_changed = hasher.algorithm != preferred.algorithm
++    must_update = hasher_changed or preferred.must_update(encoded)
+     is_correct = hasher.verify(password, encoded)
++
++    # If the hasher didn't change (we don't protect against enumeration if it
++    # does) and the password should get updated, try to close the timing gap
++    # between the work factor of the current encoded password and the default
++    # work factor.
++    if not is_correct and not hasher_changed and must_update:
++        hasher.harden_runtime(password, encoded)
++
+     if setter and is_correct and must_update:
+         setter(raw_password)
+     return is_correct
+@@ -189,6 +199,22 @@ class BasePasswordHasher(object):
+         """
+         raise NotImplementedError()
+ 
++    def must_update(self, encoded):
++        return False
++
++    def harden_runtime(self, password, encoded):
++        """
++        Bridge the runtime gap between the work factor supplied in `encoded`
++        and the work factor suggested by this hasher.
++
++        Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
++        `self.iterations` is 30000, this method should run password through
++        another 10000 iterations of PBKDF2. Similar approaches should exist
++        for any hasher that has a work factor. If not, this method should be
++        defined as a no-op to silence the warning.
++        """
++        warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
++
+ 
+ class PBKDF2PasswordHasher(BasePasswordHasher):
+     """
+@@ -227,6 +253,16 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
+             (_('hash'), mask_hash(hash)),
+         ])
+ 
++    def must_update(self, encoded):
++        algorithm, iterations, salt, hash = encoded.split('$', 3)
++        return int(iterations) != self.iterations
++
++    def harden_runtime(self, password, encoded):
++        algorithm, iterations, salt, hash = encoded.split('$', 3)
++        extra_iterations = self.iterations - int(iterations)
++        if extra_iterations > 0:
++            self.encode(password, salt, extra_iterations)
++
+ 
+ class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
+     """
+@@ -278,6 +314,16 @@ class BCryptPasswordHasher(BasePasswordHasher):
+             (_('checksum'), mask_hash(checksum)),
+         ])
+ 
++    def harden_runtime(self, password, encoded):
++        _, data = encoded.split('$', 1)
++        salt = data[:29]  # Length of the salt in bcrypt.
++        rounds = data.split('$')[2]
++        # work factor is logarithmic, adding one doubles the load.
++        diff = 2**(self.rounds - int(rounds)) - 1
++        while diff > 0:
++            self.encode(password, force_bytes(salt))
++            diff -= 1
++
+ 
+ class SHA1PasswordHasher(BasePasswordHasher):
+     """
+@@ -306,6 +352,10 @@ class SHA1PasswordHasher(BasePasswordHasher):
+             (_('hash'), mask_hash(hash)),
+         ])
+ 
++    def harden_runtime(self, password, encoded):
++        pass
++
++
+ 
+ class MD5PasswordHasher(BasePasswordHasher):
+     """
+@@ -366,6 +416,10 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
+             (_('hash'), mask_hash(hash)),
+         ])
+ 
++    def harden_runtime(self, password, encoded):
++        pass
++
++
+ 
+ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
+     """
+@@ -399,6 +453,10 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
+             (_('hash'), mask_hash(encoded, show=3)),
+         ])
+ 
++    def harden_runtime(self, password, encoded):
++        pass
++
++
+ 
+ class CryptPasswordHasher(BasePasswordHasher):
+     """
+@@ -434,3 +492,5 @@ class CryptPasswordHasher(BasePasswordHasher):
+             (_('hash'), mask_hash(data, show=3)),
+         ])
+ 
++    def harden_runtime(self, password, encoded):
++        pass
+diff --git a/django/utils/encoding.py b/django/utils/encoding.py
+index 2924723..2a3e064 100644
+--- a/django/utils/encoding.py
++++ b/django/utils/encoding.py
+@@ -51,6 +51,42 @@ def is_protected_type(obj):
+         float, Decimal)
+     )
+ 
++def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
++    """
++    Similar to smart_bytes, except that lazy instances are resolved to
++    strings, rather than kept as lazy objects.
++
++    If strings_only is True, don't convert (some) non-string-like objects.
++    """
++    # Handle the common case first for performance reasons.
++    if isinstance(s, bytes):
++        if encoding == 'utf-8':
++            return s
++        else:
++            return s.decode('utf-8', errors).encode(encoding, errors)
++    if strings_only and is_protected_type(s):
++        return s
++    if isinstance(s, six.memoryview):
++        return bytes(s)
++    if isinstance(s, Promise):
++        return six.text_type(s).encode(encoding, errors)
++    if not isinstance(s, six.string_types):
++        try:
++            if six.PY3:
++                return six.text_type(s).encode(encoding)
++            else:
++                return bytes(s)
++        except UnicodeEncodeError:
++            if isinstance(s, Exception):
++                # An Exception subclass containing non-ASCII data that doesn't
++                # know how to print itself properly. We shouldn't raise a
++                # further exception.
++                return b' '.join([force_bytes(arg, encoding, strings_only,
++                        errors) for arg in s])
++            return six.text_type(s).encode(encoding, errors)
++    else:
++        return s.encode(encoding, errors)
++
+ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
+     """
+     Similar to smart_unicode, except that lazy instances are resolved to
+diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
+index 23a4a0c..5dedb54 100644
+--- a/docs/topics/auth.txt
++++ b/docs/topics/auth.txt
+@@ -538,12 +538,42 @@ However, Django can only upgrade passwords that use algorithms mentioned in
+ sure never to *remove* entries from this list. If you do, users using un-
+ mentioned algorithms won't be able to upgrade.
+ 
++Be aware that if all the passwords in your database aren't encoded in the
++default hasher's algorithm, you may be vulnerable to a user enumeration timing
++attack due to a difference between the duration of a login request for a user
++with a password encoded in a non-default algorithm and the duration of a login
++request for a nonexistent user (which runs the default hasher). You may be able
++to mitigate this by upgrading older password hashes.
++
+ .. _sha1: http://en.wikipedia.org/wiki/SHA1
+ .. _pbkdf2: http://en.wikipedia.org/wiki/PBKDF2
+ .. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
+ .. _bcrypt: http://en.wikipedia.org/wiki/Bcrypt
+ .. _py-bcrypt: http://pypi.python.org/pypi/py-bcrypt/
+ 
++.. _write-your-own-password-hasher:
++
++Writing your own hasher
++-----------------------
++
++.. versionadded:: 1.8.10
++
++If you write your own password hasher that contains a work factor such as a
++number of iterations, you should implement a
++``harden_runtime(self, password, encoded)`` method to bridge the runtime gap
++between the work factor supplied in the ``encoded`` password and the default
++work factor of the hasher. This prevents a user enumeration timing attack due
++to  difference between a login request for a user with a password encoded in an
++older number of iterations and a nonexistent user (which runs the default
++hasher's default number of iterations).
++
++Taking PBKDF2 as example, if ``encoded`` contains 20,000 iterations and the
++hasher's default ``iterations`` is 30,000, the method should run ``password``
++through another 10,000 iterations of PBKDF2.
++
++If your hasher doesn't have a work factor, implement the method as a no-op
++(``pass``).
++
+ Anonymous users
+ ---------------
+ 
diff -Nru python-django-1.4.5/debian/patches/02_disable-sources-in-sphinxdoc.diff python-django-1.4.22/debian/patches/02_disable-sources-in-sphinxdoc.diff
--- python-django-1.4.5/debian/patches/02_disable-sources-in-sphinxdoc.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/02_disable-sources-in-sphinxdoc.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,23 +0,0 @@
-Description: Disable creation of _sources directory by Sphinx
- We do this to save some space as the sources of the documentation
- are not really useful in a binary package.
- .
- This is a Debian specific patch.
-Forwarded: not-needed
-Author: Raphaël Hertzog <hertzog@debian.org>
-Origin: vendor
-
---- a/docs/conf.py
-+++ b/docs/conf.py
-@@ -168,7 +168,10 @@
- #html_split_index = False
- 
- # If true, links to the reST sources are added to the pages.
--#html_show_sourcelink = True
-+html_show_sourcelink = False
-+
-+# Do not ship a copy of the sources
-+html_copy_source = False
- 
- # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
- #html_show_sphinx = True
diff -Nru python-django-1.4.5/debian/patches/03_manpage.diff python-django-1.4.22/debian/patches/03_manpage.diff
--- python-django-1.4.5/debian/patches/03_manpage.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/03_manpage.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,23 +0,0 @@
-Description: Update manual page to refer to django-admin instead of django-admin.py
- Update the manual page to speak of django-admin instead of
- django-admin.py as that's the name used by the Debian package.
- .
- This is a Debian specific patch.
-Forwarded: not-needed
-Author: Brett Parker <iDunno@sommitrealweird.co.uk>
-Origin: vendor
-
---- a/docs/man/django-admin.1
-+++ b/docs/man/django-admin.1
-@@ -1,8 +1,8 @@
--.TH "django-admin.py" "1" "March 2008" "Django Project" ""
-+.TH "django-admin" "1" "March 2008" "Django Project" ""
- .SH "NAME"
--django\-admin.py \- Utility script for the Django Web framework
-+django\-admin \- Utility script for the Django Web framework
- .SH "SYNOPSIS"
--.B django\-admin.py
-+.B django\-admin
- .I <action>
- .B [options]
- .sp
diff -Nru python-django-1.4.5/debian/patches/06_use_debian_geoip_database_as_default.diff python-django-1.4.22/debian/patches/06_use_debian_geoip_database_as_default.diff
--- python-django-1.4.5/debian/patches/06_use_debian_geoip_database_as_default.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/06_use_debian_geoip_database_as_default.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,59 +0,0 @@
-Description: Use Debian GeoIP database path as default
- Default to Debian standard path for GeoIP directory and for GeoIP city
- file. Avoids the need to declare them in each project.
- .
- This is a Debian specific patch.
-Bug-Debian: http://bugs.debian.org/645094
-Forwarded: not-needed
-Author: Tapio Rantala <tapio.rantala@iki.fi>
-
---- a/django/contrib/gis/geoip/base.py
-+++ b/django/contrib/gis/geoip/base.py
-@@ -61,7 +61,8 @@
-         * path: Base directory to where GeoIP data is located or the full path
-             to where the city or country data files (*.dat) are located.
-             Assumes that both the city and country data sets are located in
--            this directory; overrides the GEOIP_PATH settings attribute.
-+            this directory. Overrides the GEOIP_PATH settings attribute.
-+            If neither is set, defaults to '/usr/share/GeoIP'.
- 
-         * cache: The cache settings when opening up the GeoIP datasets,
-             and may be an integer in (0, 1, 2, 4, 8) corresponding to
-@@ -70,11 +71,13 @@
-             settings,  respectively.  Defaults to 0, meaning that the data is read
-             from the disk.
- 
--        * country: The name of the GeoIP country data file.  Defaults to
--            'GeoIP.dat'; overrides the GEOIP_COUNTRY settings attribute.
--
--        * city: The name of the GeoIP city data file.  Defaults to
--            'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
-+        * country: The name of the GeoIP country data file. Overrides
-+            the GEOIP_COUNTRY settings attribute. If neither is set,
-+            defaults to 'GeoIP.dat'
-+
-+        * city: The name of the GeoIP city data file. Overrides the
-+            GEOIP_CITY settings attribute. If neither is set, defaults
-+            to 'GeoIPCity.dat'.
-         """
-         # Checking the given cache option.
-         if cache in self.cache_options:
-@@ -84,8 +87,7 @@
- 
-         # Getting the GeoIP data path.
-         if not path:
--            path = GEOIP_SETTINGS.get('GEOIP_PATH', None)
--            if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.')
-+            path = GEOIP_SETTINGS.get('GEOIP_PATH', '/usr/share/GeoIP')
-         if not isinstance(path, basestring):
-             raise TypeError('Invalid path type: %s' % type(path).__name__)
- 
-@@ -98,7 +100,7 @@
-                 self._country = GeoIP_open(country_db, cache)
-                 self._country_file = country_db
- 
--            city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
-+            city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoIPCity.dat'))
-             if os.path.isfile(city_db):
-                 self._city = GeoIP_open(city_db, cache)
-                 self._city_file = city_db
diff -Nru python-django-1.4.5/debian/patches/2601.patch python-django-1.4.22/debian/patches/2601.patch
--- python-django-1.4.5/debian/patches/2601.patch	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/2601.patch	1970-01-01 10:00:00.000000000 +1000
@@ -1,75 +0,0 @@
-From: Preston Timmons <prestontimmons@gfa.org>
-Origin: https://github.com/django/django/pull/2601/
-Last-Updated: 2014-06-22
-Subject: Fixed #22486: Reverse raises AttributeError on partial functions.
-
-Create the lookup_str from the original function whenever a partial
-is provided as an argument to a url pattern.
----
- django/core/urlresolvers.py        |  4 ++++
- tests/urlpatterns_reverse/urls.py  |  6 +++++-
- tests/urlpatterns_reverse/views.py | 10 ++++++++++
- 3 files changed, 19 insertions(+), 1 deletion(-)
-
---- a/django/core/urlresolvers.py
-+++ b/django/core/urlresolvers.py
-@@ -8,6 +8,7 @@
- """
- 
- import re
-+import functools
- from threading import local
- 
- from django.http import Http404
-@@ -248,6 +249,9 @@
-                 self._callback_strs.add(pattern._callback_str)
-             elif hasattr(pattern, '_callback'):
-                 callback = pattern._callback
-+                if isinstance(callback, functools.partial):
-+                    callback = callback.func
-+
-                 if not hasattr(callback, '__name__'):
-                     lookup_str = callback.__module__ + "." + callback.__class__.__name__
-                 else:
---- a/tests/regressiontests/urlpatterns_reverse/urls.py
-+++ b/tests/regressiontests/urlpatterns_reverse/urls.py
-@@ -2,7 +2,7 @@
- 
- from django.conf.urls import patterns, url, include
- 
--from .views import empty_view, absolute_kwargs_view
-+from .views import empty_view, empty_view_partial, empty_view_wrapped, absolute_kwargs_view
- 
- 
- other_patterns = patterns('',
-@@ -53,6 +53,10 @@
-             include('regressiontests.urlpatterns_reverse.included_urls')),
-     url('', include('regressiontests.urlpatterns_reverse.extra_urls')),
- 
-+    # Partials should be fine.
-+    url(r'^partial/', empty_view_partial, name="partial"),
-+    url(r'^partial_wrapped/', empty_view_wrapped, name="partial_wrapped"),
-+
-     # This is non-reversible, but we shouldn't blow up when parsing it.
-     url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"),
- 
---- a/tests/regressiontests/urlpatterns_reverse/views.py
-+++ b/tests/regressiontests/urlpatterns_reverse/views.py
-@@ -1,3 +1,5 @@
-+from functools import partial, update_wrapper
-+
- from django.http import HttpResponse
- from django.views.generic import RedirectView
- from django.core.urlresolvers import reverse_lazy
-@@ -40,3 +42,11 @@
- 
- def bad_view(request, *args, **kwargs):
-     raise ValueError("I don't think I'm getting good value for this view")
-+
-+
-+empty_view_partial = partial(empty_view, template_name="template.html")
-+
-+
-+empty_view_wrapped = update_wrapper(
-+    partial(empty_view, template_name="template.html"), empty_view,
-+)
diff -Nru python-django-1.4.5/debian/patches/admin-data-leak-1.4.diff python-django-1.4.22/debian/patches/admin-data-leak-1.4.diff
--- python-django-1.4.5/debian/patches/admin-data-leak-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/admin-data-leak-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,115 +0,0 @@
-commit 027bd348642007617518379f8b02546abacaa6e0
-Author: Simon Charette <charette.s@gmail.com>
-Date:   Mon Aug 11 15:36:16 2014 -0400
-
-    [1.4.x] Prevented data leakage in contrib.admin via query string manipulation.
-    
-    This is a security fix. Disclosure following shortly.
-
---- /dev/null
-+++ b/django/contrib/admin/exceptions.py
-@@ -0,0 +1,6 @@
-+from django.core.exceptions import SuspiciousOperation
-+
-+
-+class DisallowedModelAdminToField(SuspiciousOperation):
-+    """Invalid to_field was passed to admin view via URL query string"""
-+    pass
---- a/django/contrib/admin/options.py
-+++ b/django/contrib/admin/options.py
-@@ -269,6 +269,24 @@
-         clean_lookup = LOOKUP_SEP.join(parts)
-         return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
- 
-+    def to_field_allowed(self, request, to_field):
-+        opts = self.model._meta
-+
-+        try:
-+            field = opts.get_field(to_field)
-+        except FieldDoesNotExist:
-+            return False
-+
-+        # Make sure at least one of the models registered for this site
-+        # references this field.
-+        registered_models = self.admin_site._registry
-+        for related_object in opts.get_all_related_objects():
-+            if (related_object.model in registered_models and
-+                    field == related_object.field.rel.get_related_field()):
-+                return True
-+
-+        return False
-+
-     def has_add_permission(self, request):
-         """
-         Returns True if the given request has permission to add an object.
---- a/django/contrib/admin/views/main.py
-+++ b/django/contrib/admin/views/main.py
-@@ -10,6 +10,7 @@
- from django.utils.http import urlencode
- 
- from django.contrib.admin import FieldListFilter
-+from django.contrib.admin.exceptions import DisallowedModelAdminToField
- from django.contrib.admin.options import IncorrectLookupParameters
- from django.contrib.admin.util import (quote, get_fields_from_path,
-     lookup_needs_distinct, prepare_lookup_value)
-@@ -56,7 +57,10 @@
-             self.page_num = 0
-         self.show_all = ALL_VAR in request.GET
-         self.is_popup = IS_POPUP_VAR in request.GET
--        self.to_field = request.GET.get(TO_FIELD_VAR)
-+        to_field = request.GET.get(TO_FIELD_VAR)
-+        if to_field and not model_admin.to_field_allowed(request, to_field):
-+            raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
-+        self.to_field = to_field
-         self.params = dict(request.GET.items())
-         if PAGE_VAR in self.params:
-             del self.params[PAGE_VAR]
---- a/tests/regressiontests/admin_views/tests.py
-+++ b/tests/regressiontests/admin_views/tests.py
-@@ -13,11 +13,12 @@
- from django.core.urlresolvers import reverse
- # Register auth models with the admin.
- from django.contrib import admin
-+from django.contrib.admin.exceptions import DisallowedModelAdminToField
- from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
- from django.contrib.admin.models import LogEntry, DELETION
- from django.contrib.admin.sites import LOGIN_FORM_KEY
- from django.contrib.admin.util import quote
--from django.contrib.admin.views.main import IS_POPUP_VAR
-+from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
- from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
- from django.contrib.auth import REDIRECT_FIELD_NAME
- from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
-@@ -572,6 +573,19 @@
-         response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
-         self.assertEqual(response.status_code, 200)
- 
-+    def test_disallowed_to_field(self):
-+        with self.assertRaises(DisallowedModelAdminToField):
-+            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'})
-+
-+        # Specifying a field that is not refered by any other model registered
-+        # to this admin site should raise an exception.
-+        with self.assertRaises(DisallowedModelAdminToField):
-+            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'})
-+
-+        # Specifying a field referenced by another model should be allowed.
-+        response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'})
-+        self.assertEqual(response.status_code, 200)
-+
-     def test_allowed_filtering_15103(self):
-         """
-         Regressions test for ticket 15103 - filtering on fields defined in a
-@@ -2061,10 +2075,9 @@
-         """Ensure that the to_field GET parameter is preserved when a search
-         is performed. Refs #10918.
-         """
--        from django.contrib.admin.views.main import TO_FIELD_VAR
--        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
-+        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
-         self.assertContains(response, "\n1 user\n")
--        self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
-+        self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
- 
-     def test_exact_matches(self):
-         response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
diff -Nru python-django-1.4.5/debian/patches/cache-csrf-1.4.x.patch python-django-1.4.22/debian/patches/cache-csrf-1.4.x.patch
--- python-django-1.4.5/debian/patches/cache-csrf-1.4.x.patch	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/cache-csrf-1.4.x.patch	1970-01-01 10:00:00.000000000 +1000
@@ -1,113 +0,0 @@
-Author: Aymeric Augustin <aymeric.augustin@m4x.org>
-Origin: upstream
-Date: Sat Mar 29 14:48:15 2014 +0100
-Subject: Prevent leaking the CSRF token through caching.
-    
-Django includes both a caching framework and a system for preventing
-cross-site request forgery (CSRF) attacks. The CSRF-protection system
-is based on a random nonce sent to the client in a cookie which must
-be sent by the client on future requests, and in forms a hidden value
-which must be submitted back with the form.
-
-The caching framework includes an option to cache responses to
-anonymous (i.e., unauthenticated) clients.
-
-When the first anonymous request to a given page was by a client which
-did not have a CSRF cookie, the cache framework will also cache the
-CSRF cookie, and serve the same nonce to other anonymous clients who
-do not have a CSRF cookie. This allows an attacker to obtain a valid
-CSRF cookie value and perform attacks which bypass the check for the
-cookie.
-
-To remedy this, the caching framework will no longer cache such
-responses. The heuristic for this will be:
-
-1. If the incoming request did not submit any cookies, and
-
-2. The response did send one or more cookies, and
-
-3. The ``Vary: Cookie`` header is set on the response, then the
-   response will not be cached.
-
---- a/django/middleware/cache.py
-+++ b/django/middleware/cache.py
-@@ -50,7 +50,8 @@
- 
- from django.conf import settings
- from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
--from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
-+from django.utils.cache import (get_cache_key, get_max_age, has_vary_header,
-+    learn_cache_key, patch_response_headers)
- 
- 
- class UpdateCacheMiddleware(object):
-@@ -93,8 +94,15 @@
-         if not self._should_update_cache(request, response):
-             # We don't need to update the cache, just return.
-             return response
-+
-         if not response.status_code == 200:
-             return response
-+
-+        # Don't cache responses that set a user-specific (and maybe security
-+        # sensitive) cookie in response to a cookie-less request.
-+        if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
-+            return response
-+
-         # Try to get the timeout from the "max-age" section of the "Cache-
-         # Control" header before reverting to using the default cache_timeout
-         # length.
---- a/tests/regressiontests/cache/tests.py
-+++ b/tests/regressiontests/cache/tests.py
-@@ -17,10 +17,12 @@
- from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
- from django.core.cache.backends.base import (CacheKeyWarning,
-     InvalidCacheBackendError)
-+from django.core.context_processors import csrf
- from django.db import router
- from django.http import HttpResponse, HttpRequest, QueryDict
- from django.middleware.cache import (FetchFromCacheMiddleware,
-     UpdateCacheMiddleware, CacheMiddleware)
-+from django.middleware.csrf import CsrfViewMiddleware
- from django.template import Template
- from django.template.response import TemplateResponse
- from django.test import TestCase, TransactionTestCase, RequestFactory
-@@ -1418,6 +1420,10 @@
-     return HttpResponse('Hello World %s' % value)
- 
- 
-+def csrf_view(request):
-+    return HttpResponse(csrf(request)['csrf_token'])
-+
-+
- class CacheMiddlewareTest(TestCase):
- 
-     def setUp(self):
-@@ -1635,6 +1641,27 @@
-         response = other_with_timeout_view(request, '18')
-         self.assertEqual(response.content, 'Hello World 18')
- 
-+    def test_sensitive_cookie_not_cached(self):
-+        """
-+        Django must prevent caching of responses that set a user-specific (and
-+        maybe security sensitive) cookie in response to a cookie-less request.
-+        """
-+        csrf_middleware = CsrfViewMiddleware()
-+        cache_middleware = CacheMiddleware()
-+
-+        request = self.factory.get('/view/')
-+        self.assertIsNone(cache_middleware.process_request(request))
-+
-+        csrf_middleware.process_view(request, csrf_view, (), {})
-+
-+        response = csrf_view(request)
-+
-+        response = csrf_middleware.process_response(request, response)
-+        response = cache_middleware.process_response(request, response)
-+
-+        # Inserting a CSRF cookie in a cookie-less request prevented caching.
-+        self.assertIsNone(cache_middleware.process_request(request))
-+
- CacheMiddlewareTest = override_settings(
-         CACHE_MIDDLEWARE_ALIAS='other',
-         CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix',
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-0219.diff python-django-1.4.22/debian/patches/CVE-2015-0219.diff
--- python-django-1.4.5/debian/patches/CVE-2015-0219.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-0219.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,105 +0,0 @@
-commit 4f6fffc1dc429f1ad428ecf8e6620739e8837450
-Author: Carl Meyer <carl@oddbird.net>
-Date:   Wed Sep 10 11:06:19 2014 -0600
-
-    [1.4.x] Stripped headers containing underscores to prevent spoofing in WSGI environ.
-    
-    This is a security fix. Disclosure following shortly.
-    
-    Thanks to Jedediah Smith for the report.
-
-diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
-index 8d4ceab..0ec5f98 100644
---- a/django/core/servers/basehttp.py
-+++ b/django/core/servers/basehttp.py
-@@ -199,6 +199,17 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
- 
-         sys.stderr.write(msg)
- 
-+    def get_environ(self):
-+        # Strip all headers with underscores in the name before constructing
-+        # the WSGI environ. This prevents header-spoofing based on ambiguity
-+        # between underscores and dashes both normalized to underscores in WSGI
-+        # env vars. Nginx and Apache 2.4+ both do this as well.
-+        for k, v in self.headers.items():
-+            if '_' in k:
-+                del self.headers[k]
-+
-+        return super(WSGIRequestHandler, self).get_environ()
-+
- 
- class AdminMediaHandler(handlers.StaticFilesHandler):
-     """
-diff --git a/tests/regressiontests/servers/servers/test_basehttp.py b/tests/regressiontests/servers/servers/test_basehttp.py
-new file mode 100644
-index 0000000..6bca608
---- /dev/null
-+++ b/tests/regressiontests/servers/servers/test_basehttp.py
-@@ -0,0 +1,67 @@
-+import sys
-+
-+from django.core.servers.basehttp import WSGIRequestHandler
-+from django.test import TestCase
-+from django.utils.six import BytesIO, StringIO
-+
-+
-+class Stub(object):
-+    def __init__(self, **kwargs):
-+        self.__dict__.update(kwargs)
-+
-+
-+class WSGIRequestHandlerTestCase(TestCase):
-+
-+    def test_strips_underscore_headers(self):
-+        """WSGIRequestHandler ignores headers containing underscores.
-+
-+        This follows the lead of nginx and Apache 2.4, and is to avoid
-+        ambiguity between dashes and underscores in mapping to WSGI environ,
-+        which can have security implications.
-+        """
-+        def test_app(environ, start_response):
-+            """A WSGI app that just reflects its HTTP environ."""
-+            start_response('200 OK', [])
-+            http_environ_items = sorted(
-+                '%s:%s' % (k, v) for k, v in environ.items()
-+                if k.startswith('HTTP_')
-+            )
-+            yield (','.join(http_environ_items)).encode('utf-8')
-+
-+        rfile = BytesIO()
-+        rfile.write(b"GET / HTTP/1.0\r\n")
-+        rfile.write(b"Some-Header: good\r\n")
-+        rfile.write(b"Some_Header: bad\r\n")
-+        rfile.write(b"Other_Header: bad\r\n")
-+        rfile.seek(0)
-+
-+        # WSGIRequestHandler closes the output file; we need to make this a
-+        # no-op so we can still read its contents.
-+        class UnclosableBytesIO(BytesIO):
-+            def close(self):
-+                pass
-+
-+        wfile = UnclosableBytesIO()
-+
-+        def makefile(mode, *a, **kw):
-+            if mode == 'rb':
-+                return rfile
-+            elif mode == 'wb':
-+                return wfile
-+
-+        request = Stub(makefile=makefile)
-+        server = Stub(base_environ={}, get_app=lambda: test_app)
-+
-+        # We don't need to check stderr, but we don't want it in test output
-+        old_stderr = sys.stderr
-+        sys.stderr = StringIO()
-+        try:
-+            # instantiating a handler runs the request as side effect
-+            WSGIRequestHandler(request, '192.168.0.2', server)
-+        finally:
-+            sys.stderr = old_stderr
-+
-+        wfile.seek(0)
-+        body = list(wfile.readlines())[-1]
-+
-+        self.assertEqual(body, b'HTTP_SOME_HEADER:good')
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-0219-fix.diff python-django-1.4.22/debian/patches/CVE-2015-0219-fix.diff
--- python-django-1.4.5/debian/patches/CVE-2015-0219-fix.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-0219-fix.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,224 +0,0 @@
-From e60557c249d33e100008cc30890cde2daeb677bb Mon Sep 17 00:00:00 2001
-From: Tim Graham <timograham@gmail.com>
-Date: Wed, 28 Jan 2015 12:03:05 -0500
-Subject: [PATCH] [1.4.x] Fixed #24238 -- Removed unused
- WSGIRequestHandler.get_environ()
-
-Also moved the test as it wasn't running.
----
- django/core/servers/basehttp.py                    | 33 -----------
- .../servers/servers/test_basehttp.py               | 67 ----------------------
- tests/regressiontests/servers/tests.py             | 67 +++++++++++++++++++++-
- 3 files changed, 66 insertions(+), 101 deletions(-)
- delete mode 100644 tests/regressiontests/servers/servers/test_basehttp.py
-
-diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
-index 0ec5f98..d570657 100644
---- a/django/core/servers/basehttp.py
-+++ b/django/core/servers/basehttp.py
-@@ -138,39 +138,6 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
-         self.style = color_style()
-         super(WSGIRequestHandler, self).__init__(*args, **kwargs)
- 
--    def get_environ(self):
--        env = self.server.base_environ.copy()
--        env['SERVER_PROTOCOL'] = self.request_version
--        env['REQUEST_METHOD'] = self.command
--        if '?' in self.path:
--            path,query = self.path.split('?',1)
--        else:
--            path,query = self.path,''
--
--        env['PATH_INFO'] = urllib.unquote(path)
--        env['QUERY_STRING'] = query
--        env['REMOTE_ADDR'] = self.client_address[0]
--
--        if self.headers.typeheader is None:
--            env['CONTENT_TYPE'] = self.headers.type
--        else:
--            env['CONTENT_TYPE'] = self.headers.typeheader
--
--        length = self.headers.getheader('content-length')
--        if length:
--            env['CONTENT_LENGTH'] = length
--
--        for h in self.headers.headers:
--            k,v = h.split(':',1)
--            k=k.replace('-','_').upper(); v=v.strip()
--            if k in env:
--                continue                    # skip content length, type,etc.
--            if 'HTTP_'+k in env:
--                env['HTTP_'+k] += ','+v     # comma-separate multiple headers
--            else:
--                env['HTTP_'+k] = v
--        return env
--
-     def log_message(self, format, *args):
-         # Don't bother logging requests for admin images or the favicon.
-         if (self.path.startswith(self.admin_media_prefix)
-diff --git a/tests/regressiontests/servers/servers/test_basehttp.py b/tests/regressiontests/servers/servers/test_basehttp.py
-deleted file mode 100644
-index 6bca608..0000000
---- a/tests/regressiontests/servers/servers/test_basehttp.py
-+++ /dev/null
-@@ -1,67 +0,0 @@
--import sys
--
--from django.core.servers.basehttp import WSGIRequestHandler
--from django.test import TestCase
--from django.utils.six import BytesIO, StringIO
--
--
--class Stub(object):
--    def __init__(self, **kwargs):
--        self.__dict__.update(kwargs)
--
--
--class WSGIRequestHandlerTestCase(TestCase):
--
--    def test_strips_underscore_headers(self):
--        """WSGIRequestHandler ignores headers containing underscores.
--
--        This follows the lead of nginx and Apache 2.4, and is to avoid
--        ambiguity between dashes and underscores in mapping to WSGI environ,
--        which can have security implications.
--        """
--        def test_app(environ, start_response):
--            """A WSGI app that just reflects its HTTP environ."""
--            start_response('200 OK', [])
--            http_environ_items = sorted(
--                '%s:%s' % (k, v) for k, v in environ.items()
--                if k.startswith('HTTP_')
--            )
--            yield (','.join(http_environ_items)).encode('utf-8')
--
--        rfile = BytesIO()
--        rfile.write(b"GET / HTTP/1.0\r\n")
--        rfile.write(b"Some-Header: good\r\n")
--        rfile.write(b"Some_Header: bad\r\n")
--        rfile.write(b"Other_Header: bad\r\n")
--        rfile.seek(0)
--
--        # WSGIRequestHandler closes the output file; we need to make this a
--        # no-op so we can still read its contents.
--        class UnclosableBytesIO(BytesIO):
--            def close(self):
--                pass
--
--        wfile = UnclosableBytesIO()
--
--        def makefile(mode, *a, **kw):
--            if mode == 'rb':
--                return rfile
--            elif mode == 'wb':
--                return wfile
--
--        request = Stub(makefile=makefile)
--        server = Stub(base_environ={}, get_app=lambda: test_app)
--
--        # We don't need to check stderr, but we don't want it in test output
--        old_stderr = sys.stderr
--        sys.stderr = StringIO()
--        try:
--            # instantiating a handler runs the request as side effect
--            WSGIRequestHandler(request, '192.168.0.2', server)
--        finally:
--            sys.stderr = old_stderr
--
--        wfile.seek(0)
--        body = list(wfile.readlines())[-1]
--
--        self.assertEqual(body, b'HTTP_SOME_HEADER:good')
-diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
-index d237c83..e0a66f6 100644
---- a/tests/regressiontests/servers/tests.py
-+++ b/tests/regressiontests/servers/tests.py
-@@ -2,6 +2,7 @@
- Tests for django.core.servers.
- """
- import os
-+import sys
- from urlparse import urljoin
- import urllib2
- 
-@@ -10,8 +11,10 @@ from django.conf import settings
- from django.core.exceptions import ImproperlyConfigured
- from django.test import TestCase, LiveServerTestCase
- from django.core.handlers.wsgi import WSGIHandler
--from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException
-+from django.core.servers.basehttp import (
-+    AdminMediaHandler, WSGIRequestHandler, WSGIServerException)
- from django.test.utils import override_settings
-+from django.utils.six import BytesIO, StringIO
- 
- from .models import Person
- 
-@@ -213,3 +216,65 @@ class LiveServerDatabase(LiveServerBase):
-         self.urlopen('/create_model_instance/')
-         names = [person.name for person in Person.objects.all()]
-         self.assertEquals(names, ['jane', 'robert', 'emily'])
-+
-+
-+class Stub(object):
-+    def __init__(self, **kwargs):
-+        self.__dict__.update(kwargs)
-+
-+
-+class WSGIRequestHandlerTestCase(TestCase):
-+
-+    def test_strips_underscore_headers(self):
-+        """WSGIRequestHandler ignores headers containing underscores.
-+
-+        This follows the lead of nginx and Apache 2.4, and is to avoid
-+        ambiguity between dashes and underscores in mapping to WSGI environ,
-+        which can have security implications.
-+        """
-+        def test_app(environ, start_response):
-+            """A WSGI app that just reflects its HTTP environ."""
-+            start_response('200 OK', [])
-+            http_environ_items = sorted(
-+                '%s:%s' % (k, v) for k, v in environ.items()
-+                if k.startswith('HTTP_')
-+            )
-+            yield (','.join(http_environ_items)).encode('utf-8')
-+
-+        rfile = BytesIO()
-+        rfile.write("GET / HTTP/1.0\r\n")
-+        rfile.write("Some-Header: good\r\n")
-+        rfile.write("Some_Header: bad\r\n")
-+        rfile.write("Other_Header: bad\r\n")
-+        rfile.seek(0)
-+
-+        # WSGIRequestHandler closes the output file; we need to make this a
-+        # no-op so we can still read its contents.
-+        class UnclosableBytesIO(BytesIO):
-+            def close(self):
-+                pass
-+
-+        wfile = UnclosableBytesIO()
-+
-+        def makefile(mode, *a, **kw):
-+            if mode == 'rb':
-+                return rfile
-+            elif mode == 'wb':
-+                return wfile
-+
-+        request = Stub(makefile=makefile)
-+        server = Stub(base_environ={}, get_app=lambda: test_app)
-+
-+        # We don't need to check stderr, but we don't want it in test output
-+        old_stderr = sys.stderr
-+        sys.stderr = StringIO()
-+        try:
-+            # instantiating a handler runs the request as side effect
-+            WSGIRequestHandler(request, '192.168.0.2', server)
-+        finally:
-+            sys.stderr = old_stderr
-+
-+        wfile.seek(0)
-+        body = list(wfile.readlines())[-1]
-+
-+        self.assertEqual(body, 'HTTP_SOME_HEADER:good')
--- 
-2.1.4
-
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-0220.diff python-django-1.4.22/debian/patches/CVE-2015-0220.diff
--- python-django-1.4.5/debian/patches/CVE-2015-0220.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-0220.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,52 +0,0 @@
-commit 4c241f1b710da6419d9dca160e80b23b82db7758
-Author: Tim Graham <timograham@gmail.com>
-Date:   Wed Dec 3 16:14:00 2014 -0500
-
-    [1.4.x] Fixed is_safe_url() to handle leading whitespace.
-    
-    This is a security fix. Disclosure following shortly.
-
-diff --git a/django/utils/http.py b/django/utils/http.py
-index 2d40489..e69a92b 100644
---- a/django/utils/http.py
-+++ b/django/utils/http.py
-@@ -234,6 +234,7 @@ def is_safe_url(url, host=None):
-     """
-     if not url:
-         return False
-+    url = url.strip()
-     # Chrome treats \ completely as /
-     url = url.replace('\\', '/')
-     # Chrome considers any URL with more than two slashes to be absolute, but
-diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
-index 802b3fa..3ec237a 100644
---- a/tests/regressiontests/utils/http.py
-+++ b/tests/regressiontests/utils/http.py
-@@ -64,7 +64,7 @@ class TestUtilsHttp(unittest.TestCase):
-         # bad input
-         for n in [-1, sys.maxint+1, '1', 'foo', {1:2}, (1,2,3)]:
-             self.assertRaises(ValueError, http.int_to_base36, n)
--        
-+
-         for n in ['#', ' ']:
-             self.assertRaises(ValueError, http.base36_to_int, n)
- 
-@@ -73,7 +73,7 @@ class TestUtilsHttp(unittest.TestCase):
- 
-         # non-integer input
-         self.assertRaises(TypeError, http.int_to_base36, 3.141)
--        
-+
-         # more explicit output testing
-         for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]:
-             self.assertEqual(http.int_to_base36(n), b36)
-@@ -97,7 +97,8 @@ class TestUtilsHttp(unittest.TestCase):
-                         'http:/\//example.com',
-                         'http:\/example.com',
-                         'http:/\example.com',
--                        'javascript:alert("XSS")'):
-+                        'javascript:alert("XSS")'
-+                        '\njavascript:alert(x)'):
-             self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
-         for good_url in ('/view/?param=http://example.com',
-                      '/view/?param=https://example.com',
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-0221.diff python-django-1.4.22/debian/patches/CVE-2015-0221.diff
--- python-django-1.4.5/debian/patches/CVE-2015-0221.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-0221.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,100 +0,0 @@
-commit d020da6646c5142bc092247d218a3d1ce3e993f7
-Author: Tim Graham <timograham@gmail.com>
-Date:   Tue Dec 9 15:32:03 2014 -0500
-
-    [1.4.x] Prevented views.static.serve() from using large memory on large files.
-    
-    This is a security fix. Disclosure following shortly.
-
-diff --git a/django/views/static.py b/django/views/static.py
-index ed23779..7677d7b 100644
---- a/django/views/static.py
-+++ b/django/views/static.py
-@@ -16,6 +16,9 @@ from django.template import loader, Template, Context, TemplateDoesNotExist
- from django.utils.http import http_date, parse_http_date
- from django.utils.translation import ugettext as _, ugettext_noop
- 
-+STREAM_CHUNK_SIZE = 4096
-+
-+
- def serve(request, path, document_root=None, show_indexes=False):
-     """
-     Serve static files below a given point in the directory structure.
-@@ -59,8 +62,8 @@ def serve(request, path, document_root=None, show_indexes=False):
-     if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
-                               statobj.st_mtime, statobj.st_size):
-         return HttpResponseNotModified(mimetype=mimetype)
--    with open(fullpath, 'rb') as f:
--        response = HttpResponse(f.read(), mimetype=mimetype)
-+    f = open(fullpath, 'rb')
-+    response = HttpResponse(iter(lambda: f.read(STREAM_CHUNK_SIZE), ''), mimetype=mimetype)
-     response["Last-Modified"] = http_date(statobj.st_mtime)
-     if stat.S_ISREG(statobj.st_mode):
-         response["Content-Length"] = statobj.st_size
-diff --git a/tests/regressiontests/views/media/long-line.txt b/tests/regressiontests/views/media/long-line.txt
-new file mode 100644
-index 0000000..b4e1948
---- /dev/null
-+++ b/tests/regressiontests/views/media/long-line.txt
-@@ -0,0 +1 @@
-+lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua hic tempora est veritatis culpa fugiat doloribus fugit in sed harum veniam porro eveniet maxime labore assumenda non illum possimus aut vero laudantium cum magni numquam dolorem explicabo quidem quasi nesciunt ipsum deleniti facilis neque similique nisi ad magnam accusamus quae provident dolor ab atque modi laboriosam fuga suscipit ea beatae ipsam consequatur saepe dolore nulla error quo iusto expedita nemo commodi aspernatur aliquam enim reiciendis rerum necessitatibus recusandae sint amet placeat temporibus autem iste deserunt esse dolores reprehenderit doloremque pariatur velit maiores repellat dignissimos asperiores aperiam alias a corporis id praesentium voluptatibus soluta voluptatem sit molestiae quas odio facere nostrum laborum incidunt eaque nihil ullam rem mollitia at cumque iure tenetur tempore totam repudiandae quisquam quod architecto officia vitae consectetur cupiditate molestias delectus voluptates earum et impedit quibusdam odit sequi perferendis eius perspiciatis eos quam quaerat officiis sunt ratione consequuntur quia quis obcaecati repellendus exercitationem vel minima libero blanditiis eligendi minus dicta voluptas excepturi nam eum inventore voluptatum ducimus sapiente dolorum itaque ipsa qui omnis debitis voluptate quos aliquid accusantium ex illo corrupti ut adipisci natus animi distinctio optio nobis unde similique excepturi vero culpa molestias fugit dolorum non amet iure inventore nihil suscipit explicabo veritatis officiis distinctio nesciunt saepe incidunt reprehenderit porro vitae cumque alias ut deleniti expedita ratione odio magnam eligendi a nostrum laborum minus esse sit libero quaerat qui id illo voluptates soluta neque odit dolore consectetur ducimus nulla est nisi impedit quia sapiente ullam temporibus ipsam repudiandae delectus fugiat blanditiis maxime voluptatibus aspernatur ea ipsum quisquam sunt eius ipsa accusantium enim corporis earum sed sequi dicta accusamus dignissimos illum pariatur quos aut reiciendis obcaecati perspiciatis consequuntur nam modi praesentium cum repellat possimus iste atque quidem architecto recusandae harum eaque sint quae optio voluptate quod quasi beatae magni necessitatibus facilis aperiam repellendus nemo aliquam et quibusdam debitis itaque cupiditate laboriosam unde tempora commodi laudantium in placeat ad vel maiores aliquid hic tempore provident quas officia adipisci rem corrupti iusto natus eum rerum at ex quam eveniet totam dolor assumenda error eos doloribus labore fuga facere deserunt ab dolores consequatur veniam animi exercitationem asperiores mollitia minima numquam voluptatem voluptatum nobis molestiae voluptas omnis velit quis quo tenetur perferendis autem dolorem doloremque sequi vitae laudantium magnam quae adipisci expedita doloribus minus perferendis vero animi at quos iure facere nihil veritatis consectetur similique porro tenetur nobis fugiat quo ducimus qui soluta maxime placeat error sunt ullam quaerat provident eos minima ab harum ratione inventore unde sint dolorum deserunt veniam laborum quasi suscipit facilis eveniet voluptatibus est ipsum sapiente omnis vel repellat perspiciatis illo voluptate aliquid magni alias modi odit ea a voluptatem reiciendis recusandae mollitia eius distinctio amet atque voluptates obcaecati deleniti eligendi commodi debitis dolore laboriosam nam illum pariatur earum exercitationem velit in quas explicabo fugit asperiores itaque quam sit dolorem beatae quod cumque necessitatibus tempora dolores hic aperiam ex tempore ut neque maiores ad dicta voluptatum eum officia assumenda reprehenderit nisi cum molestiae et iusto quidem consequuntur repellendus saepe corrupti numquam culpa rerum incidunt dolor impedit iste sed non praesentium ipsam consequatur eaque possimus quia quibusdam excepturi aspernatur voluptas quisquam autem molestias aliquam corporis delectus nostrum labore nesciunt blanditiis quis enim accusamus nulla architecto fuga natus ipsa repudiandae cupiditate temporibus aut libero optio id officiis esse dignissimos odio totam doloremque accusantium nemo rem repudiandae aliquam accusamus autem minima reiciendis debitis quis ut ducimus quas dolore ratione neque velit repellat natus est error ea nam consequuntur rerum excepturi aspernatur quaerat cumque voluptatibus rem quasi eos unde architecto animi sunt veritatis delectus nulla at iusto repellendus dolorum obcaecati commodi earum assumenda quisquam cum officiis modi ab tempora harum vitae voluptatem explicabo alias maxime nostrum iure consectetur incidunt laudantium distinctio deleniti iste facere fugit libero illo nobis expedita perferendis labore similique beatae sint dicta dignissimos sapiente dolor soluta perspiciatis aut ad illum facilis totam necessitatibus eveniet temporibus reprehenderit quidem fugiat magni dolorem doloribus quibusdam eligendi fuga quae recusandae eum amet dolores asperiores voluptas inventore officia sit vel id vero nihil optio nisi magnam deserunt odit corrupti adipisci aliquid odio enim pariatur cupiditate suscipit voluptatum corporis porro mollitia eaque quia non quod consequatur ipsa nesciunt itaque exercitationem molestias molestiae atque in numquam quo ipsam nemo ex tempore ipsum saepe esse sed veniam a voluptates placeat accusantium quos laboriosam voluptate provident hic sequi quam doloremque eius impedit omnis possimus laborum tenetur praesentium et minus ullam blanditiis culpa qui aperiam maiores quidem numquam nulla
-diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py
-index 3088a86..278eaf5 100644
---- a/tests/regressiontests/views/tests/static.py
-+++ b/tests/regressiontests/views/tests/static.py
-@@ -7,6 +7,7 @@ from django.conf import settings
- from django.conf.urls.static import static
- from django.test import TestCase
- from django.http import HttpResponseNotModified
-+from django.views.static import STREAM_CHUNK_SIZE
- 
- from .. import urls
- from ..urls import media_dir
-@@ -29,10 +30,19 @@ class StaticTests(TestCase):
-         for filename in media_files:
-             response = self.client.get('/views/%s/%s' % (self.prefix, filename))
-             file_path = path.join(media_dir, filename)
--            self.assertEqual(open(file_path).read(), response.content)
--            self.assertEqual(len(response.content), int(response['Content-Length']))
-+            content = response.content
-+            self.assertEqual(open(file_path).read(), content)
-+            self.assertEqual(len(content), int(response['Content-Length']))
-             self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
- 
-+    def test_chunked(self):
-+        "The static view should stream files in chunks to avoid large memory usage"
-+        response = self.client.get('/views/%s/%s' % (self.prefix, 'long-line.txt'))
-+        first_chunk = iter(response).next()
-+        self.assertEqual(len(first_chunk), STREAM_CHUNK_SIZE)
-+        second_chunk = response.next()
-+        self.assertEqual(len(second_chunk), 1451)
-+
-     def test_unknown_mime_type(self):
-         response = self.client.get('/views/%s/file.unknown' % self.prefix)
-         self.assertEqual('application/octet-stream', response['Content-Type'])
-@@ -71,9 +81,9 @@ class StaticTests(TestCase):
-         response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
-                                    HTTP_IF_MODIFIED_SINCE=invalid_date)
-         file = open(path.join(media_dir, file_name))
--        self.assertEqual(file.read(), response.content)
--        self.assertEqual(len(response.content),
--                          int(response['Content-Length']))
-+        content = response.content
-+        self.assertEqual(file.read(), content)
-+        self.assertEqual(len(content), int(response['Content-Length']))
- 
-     def test_invalid_if_modified_since2(self):
-         """Handle even more bogus If-Modified-Since values gracefully
-@@ -86,9 +96,9 @@ class StaticTests(TestCase):
-         response = self.client.get('/views/%s/%s' % (self.prefix, file_name),
-                                    HTTP_IF_MODIFIED_SINCE=invalid_date)
-         file = open(path.join(media_dir, file_name))
--        self.assertEqual(file.read(), response.content)
--        self.assertEqual(len(response.content),
--                          int(response['Content-Length']))
-+        content = response.content
-+        self.assertEqual(file.read(), content)
-+        self.assertEqual(len(content), int(response['Content-Length']))
- 
- 
- class StaticHelperTest(StaticTests):
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-0221-regression-fix.diff python-django-1.4.22/debian/patches/CVE-2015-0221-regression-fix.diff
--- python-django-1.4.5/debian/patches/CVE-2015-0221-regression-fix.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-0221-regression-fix.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,138 +0,0 @@
-commit 1e39d0f6280abf34c7719db5e7ed1c333f5e5919
-Author: Benjamin Richter <richter.benjamin@gmail.com>
-Date:   Sun Jan 25 23:22:46 2015 +0100
-
-    [1.4.x] Fixed #24158 -- Allowed GZipMiddleware to work with streaming responses
-    
-    Backport of django.utils.text.compress_sequence and fix for
-    django.middleware.gzip.GZipMiddleware when using iterators as
-    response.content.
-
-diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py
-index 69f938c..eb4d8bf 100644
---- a/django/middleware/gzip.py
-+++ b/django/middleware/gzip.py
-@@ -1,6 +1,6 @@
- import re
- 
--from django.utils.text import compress_string
-+from django.utils.text import compress_string, compress_sequence
- from django.utils.cache import patch_vary_headers
- 
- re_accepts_gzip = re.compile(r'\bgzip\b')
-@@ -12,8 +12,9 @@ class GZipMiddleware(object):
-     on the Accept-Encoding header.
-     """
-     def process_response(self, request, response):
-+        # The response object can tell us whether content is a string or an iterable
-         # It's not worth attempting to compress really short responses.
--        if len(response.content) < 200:
-+        if not response._base_content_is_iter and len(response.content) < 200:
-             return response
- 
-         patch_vary_headers(response, ('Accept-Encoding',))
-@@ -32,15 +33,23 @@ class GZipMiddleware(object):
-         if not re_accepts_gzip.search(ae):
-             return response
- 
--        # Return the compressed content only if it's actually shorter.
--        compressed_content = compress_string(response.content)
--        if len(compressed_content) >= len(response.content):
--            return response
-+        # The response object can tell us whether content is a string or an iterable
-+        if response._base_content_is_iter:
-+            # If the response content is iterable we don't know the length, so delete the header.
-+            del response['Content-Length']
-+            # Wrap the response content in a streaming gzip iterator (direct access to inner response._container)
-+            response.content = compress_sequence(response._container)
-+        else:
-+            # Return the compressed content only if it's actually shorter.
-+            compressed_content = compress_string(response.content)
-+            if len(compressed_content) >= len(response.content):
-+                return response
-+            response.content = compressed_content
-+            response['Content-Length'] = str(len(response.content))
- 
-         if response.has_header('ETag'):
-             response['ETag'] = re.sub('"$', ';gzip"', response['ETag'])
- 
--        response.content = compressed_content
-         response['Content-Encoding'] = 'gzip'
--        response['Content-Length'] = str(len(response.content))
-+
-         return response
-diff --git a/django/utils/text.py b/django/utils/text.py
-index eaafb96..8e43dc9 100644
---- a/django/utils/text.py
-+++ b/django/utils/text.py
-@@ -286,6 +286,39 @@ def compress_string(s):
- 
- ustring_re = re.compile(u"([\u0080-\uffff])")
- 
-+# Backported from django 1.5
-+class StreamingBuffer(object):
-+    def __init__(self):
-+        self.vals = []
-+
-+    def write(self, val):
-+        self.vals.append(val)
-+
-+    def read(self):
-+        ret = ''.join(self.vals)
-+        self.vals = []
-+        return ret
-+
-+    def flush(self):
-+        return
-+
-+    def close(self):
-+        return
-+
-+# Backported from django 1.5
-+# Like compress_string, but for iterators of strings.
-+def compress_sequence(sequence):
-+    buf = StreamingBuffer()
-+    zfile = GzipFile(mode='wb', compresslevel=6, fileobj=buf)
-+    # Output headers...
-+    yield buf.read()
-+    for item in sequence:
-+        zfile.write(item)
-+        zfile.flush()
-+        yield buf.read()
-+    zfile.close()
-+    yield buf.read()
-+
- def javascript_quote(s, quote_double_quotes=False):
- 
-     def fix(match):
-diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
-index 138ee50..87b19fb 100644
---- a/tests/regressiontests/middleware/tests.py
-+++ b/tests/regressiontests/middleware/tests.py
-@@ -514,6 +514,7 @@ class GZipMiddlewareTest(TestCase):
-     short_string = "This string is too short to be worth compressing."
-     compressible_string = 'a' * 500
-     uncompressible_string = ''.join(chr(random.randint(0, 255)) for _ in xrange(500))
-+    iterator_as_content = iter(compressible_string)
- 
-     def setUp(self):
-         self.req = HttpRequest()
-@@ -589,6 +590,18 @@ class GZipMiddlewareTest(TestCase):
-         self.assertEqual(r.content, self.uncompressible_string)
-         self.assertEqual(r.get('Content-Encoding'), None)
- 
-+    def test_streaming_compression(self):
-+        """
-+        Tests that iterators as response content return a compressed stream without consuming
-+        the whole response.content while doing so.
-+        See #24158.
-+        """
-+        self.resp.content = self.iterator_as_content
-+        r = GZipMiddleware().process_response(self.req, self.resp)
-+        self.assertEqual(self.decompress(''.join(r.content)), self.compressible_string)
-+        self.assertEqual(r.get('Content-Encoding'), 'gzip')
-+        self.assertEqual(r.get('Content-Length'), None)
-+
- 
- class ETagGZipMiddlewareTest(TestCase):
-     """
diff -Nru python-django-1.4.5/debian/patches/CVE-2015-2317.diff python-django-1.4.22/debian/patches/CVE-2015-2317.diff
--- python-django-1.4.5/debian/patches/CVE-2015-2317.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2015-2317.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,63 +0,0 @@
-From 2342693b31f740a422abf7267c53b4e7bc487c1b Mon Sep 17 00:00:00 2001
-From: Tim Graham <timograham@gmail.com>
-Date: Mon, 9 Mar 2015 20:05:13 -0400
-Subject: [PATCH] [1.4.x] Made is_safe_url() reject URLs that start with
- control characters.
-
-This is a security fix; disclosure to follow shortly.
----
- django/utils/http.py                |  9 ++++++++-
- docs/releases/1.4.20.txt            | 19 +++++++++++++++++++
- tests/regressiontests/utils/http.py |  4 +++-
- 3 files changed, 30 insertions(+), 2 deletions(-)
-
-Index: python-django-1.4.5/django/utils/http.py
-===================================================================
---- python-django-1.4.5.orig/django/utils/http.py	2015-03-19 03:34:08.000000000 +0000
-+++ python-django-1.4.5/django/utils/http.py	2015-03-19 05:14:23.805231220 +0000
-@@ -4,6 +4,7 @@
- import sys
- import urllib
- import urlparse
-+import unicodedata
- from email.utils import formatdate
- 
- from django.utils.datastructures import MultiValueDict
-@@ -232,9 +233,10 @@
- 
-     Always returns ``False`` on an empty url.
-     """
-+    if url is not None:
-+        url = url.strip()
-     if not url:
-         return False
--    url = url.strip()
-     # Chrome treats \ completely as /
-     url = url.replace('\\', '/')
-     # Chrome considers any URL with more than two slashes to be absolute, but
-@@ -248,5 +250,10 @@
-     # allow this syntax.
-     if not url_info[1] and url_info[0]:
-         return False
-+    # Forbid URLs that start with control characters. Some browsers (like
-+    # Chrome) ignore quite a few control characters at the start of a
-+    # URL and might consider the URL as scheme relative.
-+    if unicodedata.category(unicode(url[0]))[0] == 'C':
-+        return False
-     return (not url_info[1] or url_info[1] == host) and \
-         (not url_info[0] or url_info[0] in ['http', 'https'])
-Index: python-django-1.4.5/tests/regressiontests/utils/http.py
-===================================================================
---- python-django-1.4.5.orig/tests/regressiontests/utils/http.py	2015-03-19 03:34:08.000000000 +0000
-+++ python-django-1.4.5/tests/regressiontests/utils/http.py	2015-03-19 05:14:23.805231220 +0000
-@@ -98,7 +98,9 @@
-                         'http:\/example.com',
-                         'http:/\example.com',
-                         'javascript:alert("XSS")'
--                        '\njavascript:alert(x)'):
-+                        '\njavascript:alert(x)',
-+                        '\x08//example.com',
-+                        '\n'):
-             self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
-         for good_url in ('/view/?param=http://example.com',
-                      '/view/?param=https://example.com',
diff -Nru python-django-1.4.5/debian/patches/CVE-2016-2512.diff python-django-1.4.22/debian/patches/CVE-2016-2512.diff
--- python-django-1.4.5/debian/patches/CVE-2016-2512.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2016-2512.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,55 +0,0 @@
-Description: CVE-2016-2512: Prevented spoofing is_safe_url() with basic auth
-Origin: backport, https://github.com/django/django/commit/382ab137312961ad62feb8109d70a5a581fe8350
-Bug-Debian: https://bugs.debian.org/816434
-Forwarded: not-needed
-Author: Mark Striemer <mstriemer@mozilla.com>
-Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
-Last-Update: 2016-03-12
-Applied-Upstream: 1.8.10
-
----
- django/utils/http.py           |  8 ++++++--
- tests/utils_tests/test_http.py | 12 ++++++++++++
- 3 files changed, 34 insertions(+), 2 deletions(-)
-
---- a/django/utils/http.py
-+++ b/django/utils/http.py
-@@ -237,8 +237,12 @@ def is_safe_url(url, host=None):
-         url = url.strip()
-     if not url:
-         return False
--    # Chrome treats \ completely as /
--    url = url.replace('\\', '/')
-+    # Chrome treats \ completely as / in paths but it could be part of some
-+    # basic auth credentials so we need to check both URLs.
-+    return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
-+
-+
-+def _is_safe_url(url, host):
-     # Chrome considers any URL with more than two slashes to be absolute, but
-     # urlaprse is not so flexible. Treat any url with three slashes as unsafe.
-     if url.startswith('///'):
---- a/django/contrib/auth/tests/views.py
-+++ b/django/contrib/auth/tests/views.py
-@@ -312,7 +312,12 @@ class LoginTest(AuthViewsTestCase):
-                         'ftp://exampel.com',
-                         '///example.com',
-                         '//example.com',
--                        'javascript:alert("XSS")'):
-+                        'javascript:alert("XSS")',
-+                        r'http://otherserver\@example.com',
-+                        r'http:\\testserver\@example.com',
-+                        r'http://testserver\me:pass@example.com',
-+                        r'http://testserver\@example.com',
-+                        r'http:\\testserver\confirm\me@example.com'):
- 
-             nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
-                 'url': login_url,
-@@ -335,6 +340,7 @@ class LoginTest(AuthViewsTestCase):
-                          'https://testserver/',
-                          'HTTPS://testserver/',
-                          '//testserver/',
-+                         'http://testserver/confirm?email=me@example.com',
-                          '/url%20with%20spaces/'):  # see ticket #12534
-             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-                 'url': login_url,
diff -Nru python-django-1.4.5/debian/patches/CVE-2016-2512-regression.diff python-django-1.4.22/debian/patches/CVE-2016-2512-regression.diff
--- python-django-1.4.5/debian/patches/CVE-2016-2512-regression.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2016-2512-regression.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,53 +0,0 @@
-Description: is_safe_url() crashes with a byestring URL on Python 2
-Origin: upstream, https://github.com/django/django/commit/ada7a4aefb9bec4c34667b511022be6057102f98,
- https://github.com/django/django/commit/beb392b85e71fdd41209d323126181d74090fecb
-Bug: https://code.djangoproject.com/ticket/26308
-Forwarded: not-needed
-Author: Claude Paroz <claude@2xlibre.net>
-Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
-Last-Update: 2016-03-12
-Applied-Upstream: 1.8.11
-
---- a/django/utils/http.py
-+++ b/django/utils/http.py
-@@ -8,7 +8,7 @@ import unicodedata
- from email.utils import formatdate
- 
- from django.utils.datastructures import MultiValueDict
--from django.utils.encoding import smart_str, force_unicode
-+from django.utils.encoding import smart_str, force_unicode, force_text
- from django.utils.functional import allow_lazy
- 
- ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"')
-@@ -237,6 +237,10 @@ def is_safe_url(url, host=None):
-         url = url.strip()
-     if not url:
-         return False
-+    try:
-+         url = force_text(url)
-+    except UnicodeDecodeError:
-+        return False
-     # Chrome treats \ completely as / in paths but it could be part of some
-     # basic auth credentials so we need to check both URLs.
-     return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
---- a/tests/regressiontests/utils/http.py
-+++ b/tests/regressiontests/utils/http.py
-@@ -1,3 +1,5 @@
-+# -*- encoding: utf-8 -*-
-+from __future__ import unicode_literals
- import sys
- 
- from django.utils import http
-@@ -111,3 +113,12 @@ class TestUtilsHttp(unittest.TestCase):
-                      '//testserver/',
-                      '/url%20with%20spaces/'):
-             self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url)
-+
-+        # Check binary URLs, regression tests for #26308
-+        self.assertTrue(
-+            http.is_safe_url(b'https://testserver/', host='testserver'),
-+            "binary URLs should be allowed on Python 2"
-+        )
-+        self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver'))
-+        self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver'))
-+        self.assertFalse(http.is_safe_url('àview'.encode('latin-1'), host='testserver'))
diff -Nru python-django-1.4.5/debian/patches/CVE-2016-2513.diff python-django-1.4.22/debian/patches/CVE-2016-2513.diff
--- python-django-1.4.5/debian/patches/CVE-2016-2513.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/CVE-2016-2513.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,230 +0,0 @@
-Description: CVE-2016-2513: Fixed user enumeration timing attack during login
-Origin: backport, https://github.com/django/django/commit/f4e6e02f7713a6924d16540be279909ff4091eb6, https://github.com/django/django/commit/7d0d0dbf26a3c0d16e9c2b930fd6d7b89f215946
-Forwarded: not-needed
-Author: Florian Apolloner <florian@apolloner.eu>
-Reviewed-by: Salvatore Bonaccorso <carnil@debian.org>
-Last-Update: 2016-03-12
-Applied-Upstream: 1.8.10
-
----
---- a/django/contrib/auth/hashers.py
-+++ b/django/contrib/auth/hashers.py
-@@ -1,10 +1,11 @@
- import functools
- import hashlib
-+import warnings
- 
- from django.conf import settings
- from django.utils import importlib
- from django.utils.datastructures import SortedDict
--from django.utils.encoding import smart_str
-+from django.utils.encoding import force_bytes, smart_str
- from django.core.exceptions import ImproperlyConfigured
- from django.utils.crypto import (
-     pbkdf2, constant_time_compare, get_random_string)
-@@ -56,8 +57,17 @@ def check_password(password, encoded, se
-         algorithm = encoded.split('$', 1)[0]
-         hasher = get_hasher(algorithm)
- 
--    must_update = hasher.algorithm != preferred.algorithm
-+    hasher_changed = hasher.algorithm != preferred.algorithm
-+    must_update = hasher_changed or preferred.must_update(encoded)
-     is_correct = hasher.verify(password, encoded)
-+
-+    # If the hasher didn't change (we don't protect against enumeration if it
-+    # does) and the password should get updated, try to close the timing gap
-+    # between the work factor of the current encoded password and the default
-+    # work factor.
-+    if not is_correct and not hasher_changed and must_update:
-+        hasher.harden_runtime(password, encoded)
-+
-     if setter and is_correct and must_update:
-         setter(raw_password)
-     return is_correct
-@@ -198,6 +208,22 @@ class BasePasswordHasher(object):
-         """
-         raise NotImplementedError()
- 
-+    def must_update(self, encoded):
-+        return False
-+
-+    def harden_runtime(self, password, encoded):
-+        """
-+        Bridge the runtime gap between the work factor supplied in `encoded`
-+        and the work factor suggested by this hasher.
-+
-+        Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
-+        `self.iterations` is 30000, this method should run password through
-+        another 10000 iterations of PBKDF2. Similar approaches should exist
-+        for any hasher that has a work factor. If not, this method should be
-+        defined as a no-op to silence the warning.
-+        """
-+        warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
-+
- 
- class PBKDF2PasswordHasher(BasePasswordHasher):
-     """
-@@ -238,6 +264,16 @@ class PBKDF2PasswordHasher(BasePasswordH
-             (_('hash'), mask_hash(hash)),
-         ])
- 
-+    def must_update(self, encoded):
-+        algorithm, iterations, salt, hash = encoded.split('$', 3)
-+        return int(iterations) != self.iterations
-+
-+    def harden_runtime(self, password, encoded):
-+        algorithm, iterations, salt, hash = encoded.split('$', 3)
-+        extra_iterations = self.iterations - int(iterations)
-+        if extra_iterations > 0:
-+            self.encode(password, salt, extra_iterations)
-+
- 
- class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
-     """
-@@ -291,6 +327,16 @@ class BCryptPasswordHasher(BasePasswordH
-             (_('checksum'), mask_hash(checksum)),
-         ])
- 
-+    def harden_runtime(self, password, encoded):
-+        _, data = encoded.split('$', 1)
-+        salt = data[:29]  # Length of the salt in bcrypt.
-+        rounds = data.split('$')[2]
-+        # work factor is logarithmic, adding one doubles the load.
-+        diff = 2**(self.rounds - int(rounds)) - 1
-+        while diff > 0:
-+            self.encode(password, force_bytes(salt))
-+            diff -= 1
-+
- 
- class SHA1PasswordHasher(BasePasswordHasher):
-     """
-@@ -321,6 +367,10 @@ class SHA1PasswordHasher(BasePasswordHas
-             (_('hash'), mask_hash(hash)),
-         ])
- 
-+    def harden_runtime(self, password, encoded):
-+        pass
-+
-+
- 
- class MD5PasswordHasher(BasePasswordHasher):
-     """
-@@ -351,6 +401,10 @@ class MD5PasswordHasher(BasePasswordHash
-             (_('hash'), mask_hash(hash)),
-         ])
- 
-+    def harden_runtime(self, password, encoded):
-+        pass
-+
-+
- 
- class UnsaltedMD5PasswordHasher(BasePasswordHasher):
-     """
-@@ -384,6 +438,10 @@ class UnsaltedMD5PasswordHasher(BasePass
-             (_('hash'), mask_hash(encoded, show=3)),
-         ])
- 
-+    def harden_runtime(self, password, encoded):
-+        pass
-+
-+
- 
- class CryptPasswordHasher(BasePasswordHasher):
-     """
-@@ -420,3 +478,6 @@ class CryptPasswordHasher(BasePasswordHa
-             (_('salt'), salt),
-             (_('hash'), mask_hash(data, show=3)),
-         ])
-+
-+    def harden_runtime(self, password, encoded):
-+        pass
---- a/docs/topics/auth.txt
-+++ b/docs/topics/auth.txt
-@@ -527,12 +527,42 @@ However, Django can only upgrade passwor
- sure never to *remove* entries from this list. If you do, users using un-
- mentioned algorithms won't be able to upgrade.
- 
-+Be aware that if all the passwords in your database aren't encoded in the
-+default hasher's algorithm, you may be vulnerable to a user enumeration timing
-+attack due to a difference between the duration of a login request for a user
-+with a password encoded in a non-default algorithm and the duration of a login
-+request for a nonexistent user (which runs the default hasher). You may be able
-+to mitigate this by upgrading older password hashes.
-+
- .. _sha1: http://en.wikipedia.org/wiki/SHA1
- .. _pbkdf2: http://en.wikipedia.org/wiki/PBKDF2
- .. _nist: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
- .. _bcrypt: http://en.wikipedia.org/wiki/Bcrypt
- .. _py-bcrypt: http://pypi.python.org/pypi/py-bcrypt/
- 
-+.. _write-your-own-password-hasher:
-+
-+Writing your own hasher
-+-----------------------
-+
-+.. versionadded:: 1.8.10
-+
-+If you write your own password hasher that contains a work factor such as a
-+number of iterations, you should implement a
-+``harden_runtime(self, password, encoded)`` method to bridge the runtime gap
-+between the work factor supplied in the ``encoded`` password and the default
-+work factor of the hasher. This prevents a user enumeration timing attack due
-+to  difference between a login request for a user with a password encoded in an
-+older number of iterations and a nonexistent user (which runs the default
-+hasher's default number of iterations).
-+
-+Taking PBKDF2 as example, if ``encoded`` contains 20,000 iterations and the
-+hasher's default ``iterations`` is 30,000, the method should run ``password``
-+through another 10,000 iterations of PBKDF2.
-+
-+If your hasher doesn't have a work factor, implement the method as a no-op
-+(``pass``).
-+
- Anonymous users
- ---------------
- 
---- a/django/utils/encoding.py
-+++ b/django/utils/encoding.py
-@@ -51,6 +51,42 @@ def is_protected_type(obj):
-         float, Decimal)
-     )
- 
-+def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
-+    """
-+    Similar to smart_bytes, except that lazy instances are resolved to
-+    strings, rather than kept as lazy objects.
-+
-+    If strings_only is True, don't convert (some) non-string-like objects.
-+    """
-+    # Handle the common case first for performance reasons.
-+    if isinstance(s, bytes):
-+        if encoding == 'utf-8':
-+            return s
-+        else:
-+            return s.decode('utf-8', errors).encode(encoding, errors)
-+    if strings_only and is_protected_type(s):
-+        return s
-+    if isinstance(s, six.memoryview):
-+        return bytes(s)
-+    if isinstance(s, Promise):
-+        return six.text_type(s).encode(encoding, errors)
-+    if not isinstance(s, six.string_types):
-+        try:
-+            if six.PY3:
-+                return six.text_type(s).encode(encoding)
-+            else:
-+                return bytes(s)
-+        except UnicodeEncodeError:
-+            if isinstance(s, Exception):
-+                # An Exception subclass containing non-ASCII data that doesn't
-+                # know how to print itself properly. We shouldn't raise a
-+                # further exception.
-+                return b' '.join([force_bytes(arg, encoding, strings_only,
-+                        errors) for arg in s])
-+            return six.text_type(s).encode(encoding, errors)
-+    else:
-+        return s.encode(encoding, errors)
-+
- def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
-     """
-     Similar to smart_unicode, except that lazy instances are resolved to
diff -Nru python-django-1.4.5/debian/patches/date-leak-1.4.x.diff python-django-1.4.22/debian/patches/date-leak-1.4.x.diff
--- python-django-1.4.5/debian/patches/date-leak-1.4.x.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/date-leak-1.4.x.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,56 +0,0 @@
-commit 8a01c6b53169ee079cb21ac5919fdafcc8c5e172
-Author: Florian Apolloner <florian@apolloner.eu>
-Date:   Wed Nov 11 20:10:55 2015 +0100
-
-    [1.7.x] Fixed a settings leak possibility in the date template filter.
-    
-    This is a security fix.
-
---- a/django/utils/formats.py
-+++ b/django/utils/formats.py
-@@ -15,6 +15,24 @@
- _format_cache = {}
- _format_modules_cache = {}
- 
-+FORMAT_SETTINGS = frozenset([
-+    'DECIMAL_SEPARATOR',
-+    'THOUSAND_SEPARATOR',
-+    'NUMBER_GROUPING',
-+    'FIRST_DAY_OF_WEEK',
-+    'MONTH_DAY_FORMAT',
-+    'TIME_FORMAT',
-+    'DATE_FORMAT',
-+    'DATETIME_FORMAT',
-+    'SHORT_DATE_FORMAT',
-+    'SHORT_DATETIME_FORMAT',
-+    'YEAR_MONTH_FORMAT',
-+    'DATE_INPUT_FORMATS',
-+    'TIME_INPUT_FORMATS',
-+    'DATETIME_INPUT_FORMATS',
-+])
-+
-+
- def reset_format_cache():
-     """Clear any cached formats.
- 
-@@ -66,6 +84,8 @@
-     be localized (or not), overriding the value of settings.USE_L10N.
-     """
-     format_type = smart_str(format_type)
-+    if format_type not in FORMAT_SETTINGS:
-+        return format_type
-     if use_l10n or (use_l10n is None and settings.USE_L10N):
-         if lang is None:
-             lang = get_language()
---- a/tests/regressiontests/i18n/tests.py
-+++ b/tests/regressiontests/i18n/tests.py
-@@ -817,6 +817,9 @@
-                 self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 1})), u'42% stellt 1 Objekt dar')
-                 self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), u'42% stellt 4 Objekte dar')
- 
-+    def test_format_arbitrary_settings(self):
-+        self.assertEqual(get_format('DEBUG'), 'DEBUG')
-+
- 
- class ResolutionOrderI18NTests(TestCase):
- 
diff -Nru python-django-1.4.5/debian/patches/drop_fix_ie_for_vary_1_4.diff python-django-1.4.22/debian/patches/drop_fix_ie_for_vary_1_4.diff
--- python-django-1.4.5/debian/patches/drop_fix_ie_for_vary_1_4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/drop_fix_ie_for_vary_1_4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,148 +0,0 @@
-Origin: upstream
-Last-Update: 2014-05-14
-Subject: Caches may  be allowed to store and serve private data (CVE-2014-1418)
-
-In certain situations, Django may allow caches to store private data
-related to a particular session and then serve that data to requests
-with a different session, or no session at all. This can both lead to
-information disclosure, and can be a vector for cache poisoning.
-
-When using Django sessions, Django will set a ``Vary: Cookie`` header
-to ensure caches do not serve cached data to requests from other
-sessions. However, older versions of Internet Explorer (most likely
-only Internet Explorer 6, and Internet Explorer 7 if run on Windows XP
-or Windows Server 2003) are unable to handle the ``Vary`` header in
-combination with many content types. Therefore, Django would remove
-the header if the request was made by Internet Explorer.
-
-To remedy this, the special behavior for these older Internet
-Explorer versions has been removed, and the ``Vary`` header is no
-longer stripped from the response. In addition, modifications to the
-``Cache-Control`` header for all Internet Explorer requests with a
-``Content-Disposition`` header, have also been removed as they were
-found to have similar issues.
-
---- a/django/core/handlers/base.py
-+++ b/django/core/handlers/base.py
-@@ -14,8 +14,6 @@
-     response_fixes = [
-         http.fix_location_header,
-         http.conditional_content_removal,
--        http.fix_IE_for_attach,
--        http.fix_IE_for_vary,
-     ]
- 
-     def __init__(self):
---- a/django/http/utils.py
-+++ b/django/http/utils.py
-@@ -31,57 +31,3 @@
-     if request.method == 'HEAD':
-         response.content = ''
-     return response
--
--def fix_IE_for_attach(request, response):
--    """
--    This function will prevent Django from serving a Content-Disposition header
--    while expecting the browser to cache it (only when the browser is IE). This
--    leads to IE not allowing the client to download.
--    """
--    useragent = request.META.get('HTTP_USER_AGENT', '').upper()
--    if 'MSIE' not in useragent and 'CHROMEFRAME' not in useragent:
--        return response
--
--    offending_headers = ('no-cache', 'no-store')
--    if response.has_header('Content-Disposition'):
--        try:
--            del response['Pragma']
--        except KeyError:
--            pass
--        if response.has_header('Cache-Control'):
--            cache_control_values = [value.strip() for value in
--                    response['Cache-Control'].split(',')
--                    if value.strip().lower() not in offending_headers]
--
--            if not len(cache_control_values):
--                del response['Cache-Control']
--            else:
--                response['Cache-Control'] = ', '.join(cache_control_values)
--
--    return response
--
--def fix_IE_for_vary(request, response):
--    """
--    This function will fix the bug reported at
--    http://support.microsoft.com/kb/824847/en-us?spid=8722&sid=global
--    by clearing the Vary header whenever the mime-type is not safe
--    enough for Internet Explorer to handle.  Poor thing.
--    """
--    useragent = request.META.get('HTTP_USER_AGENT', '').upper()
--    if 'MSIE' not in useragent and 'CHROMEFRAME' not in useragent:
--        return response
--
--    # These mime-types that are decreed "Vary-safe" for IE:
--    safe_mime_types = ('text/html', 'text/plain', 'text/sgml')
--
--    # The first part of the Content-Type field will be the MIME type,
--    # everything after ';', such as character-set, can be ignored.
--    mime_type = response.get('Content-Type', '').partition(';')[0]
--    if mime_type not in safe_mime_types:
--        try:
--            del response['Vary']
--        except KeyError:
--            pass
--
--    return response
--
---- a/tests/regressiontests/utils/http.py
-+++ b/tests/regressiontests/utils/http.py
-@@ -56,50 +56,6 @@
-         ]
-         self.assertTrue(result in acceptable_results)
- 
--    def test_fix_IE_for_vary(self):
--        """
--        Regression for #16632.
--
--        `fix_IE_for_vary` shouldn't crash when there's no Content-Type header.
--        """
--
--        # functions to generate responses
--        def response_with_unsafe_content_type():
--            r = HttpResponse(content_type="text/unsafe")
--            r['Vary'] = 'Cookie'
--            return r
--
--        def no_content_response_with_unsafe_content_type():
--            # 'Content-Type' always defaulted, so delete it
--            r = response_with_unsafe_content_type()
--            del r['Content-Type']
--            return r
--
--        # request with & without IE user agent
--        rf = RequestFactory()
--        request = rf.get('/')
--        ie_request = rf.get('/', HTTP_USER_AGENT='MSIE')
--
--        # not IE, unsafe_content_type
--        response = response_with_unsafe_content_type()
--        utils.fix_IE_for_vary(request, response)
--        self.assertTrue('Vary' in response)
--
--        # IE, unsafe_content_type
--        response = response_with_unsafe_content_type()
--        utils.fix_IE_for_vary(ie_request, response)
--        self.assertFalse('Vary' in response)
--
--        # not IE, no_content
--        response = no_content_response_with_unsafe_content_type()
--        utils.fix_IE_for_vary(request, response)
--        self.assertTrue('Vary' in response)
--
--        # IE, no_content
--        response = no_content_response_with_unsafe_content_type()
--        utils.fix_IE_for_vary(ie_request, response)
--        self.assertFalse('Vary' in response)
--
-     def test_base36(self):
-         # reciprocity works
-         for n in [0, 1, 1000, 1000000, sys.maxint]:
diff -Nru python-django-1.4.5/debian/patches/file-upload-1.4.diff python-django-1.4.22/debian/patches/file-upload-1.4.diff
--- python-django-1.4.5/debian/patches/file-upload-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/file-upload-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,247 +0,0 @@
-commit 30042d475bf084c6723c6217a21598d9247a9c41
-Author: Tim Graham <timograham@gmail.com>
-Date:   Fri Aug 8 10:20:08 2014 -0400
-
-    [1.4.x] Fixed #23157 -- Removed O(n) algorithm when uploading duplicate file names.
-    
-    This is a security fix. Disclosure following shortly.
-
-Debian note: This patch also includes the minimum backport of additional
-functionality to django.utils.six to support this patch, but does not change
-any functionality therein.
-
---- a/django/core/files/storage.py
-+++ b/django/core/files/storage.py
-@@ -1,13 +1,13 @@
- import os
- import errno
- import urlparse
--import itertools
- from datetime import datetime
- 
- from django.conf import settings
- from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
- from django.core.files import locks, File
- from django.core.files.move import file_move_safe
-+from django.utils.crypto import get_random_string
- from django.utils.encoding import force_unicode, filepath_to_uri
- from django.utils.functional import LazyObject
- from django.utils.importlib import import_module
-@@ -63,13 +63,12 @@
-         """
-         dir_name, file_name = os.path.split(name)
-         file_root, file_ext = os.path.splitext(file_name)
--        # If the filename already exists, add an underscore and a number (before
--        # the file extension, if one exists) to the filename until the generated
--        # filename doesn't exist.
--        count = itertools.count(1)
-+        # If the filename already exists, add an underscore and a random 7
-+        # character alphanumeric string (before the file extension, if one
-+        # exists) to the filename until the generated filename doesn't exist.
-         while self.exists(name):
-             # file_ext includes the dot.
--            name = os.path.join(dir_name, "%s_%s%s" % (file_root, count.next(), file_ext))
-+            name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
- 
-         return name
- 
---- a/docs/howto/custom-file-storage.txt
-+++ b/docs/howto/custom-file-storage.txt
-@@ -86,5 +86,13 @@
- will have already cleaned to a filename valid for the storage system, according
- to the ``get_valid_name()`` method described above.
- 
--The code provided on ``Storage`` simply appends ``"_1"``, ``"_2"``, etc. to the
--filename until it finds one that's available in the destination directory.
-+.. versionchanged:: 1.4.14
-+
-+    If a file with ``name`` already exists, an underscore plus a random 7
-+    character alphanumeric string is appended to the filename before the
-+    extension.
-+
-+    Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
-+    etc.) was appended to the filename until an avaible name in the destination
-+    directory was found. A malicious user could exploit this deterministic
-+    algorithm to create a denial-of-service attack.
---- a/docs/ref/files/storage.txt
-+++ b/docs/ref/files/storage.txt
-@@ -18,7 +18,7 @@
- .. function:: get_storage_class([import_path=None])
- 
-     Returns a class or module which implements the storage API.
--    
-+
-     When called without the ``import_path`` parameter ``get_storage_class``
-     will return the current default storage system as defined by
-     :setting:`DEFAULT_FILE_STORAGE`. If ``import_path`` is provided,
-@@ -35,9 +35,9 @@
-     basic file storage on a local filesystem. It inherits from
-     :class:`~django.core.files.storage.Storage` and provides implementations
-     for all the public methods thereof.
--    
-+
-     .. note::
--    
-+
-         The :class:`FileSystemStorage.delete` method will not raise
-         raise an exception if the given file name does not exist.
- 
-@@ -85,6 +85,16 @@
-         available for new content to be written to on the target storage
-         system.
- 
-+        .. versionchanged:: 1.4.14
-+
-+        If a file with ``name`` already exists, an underscore plus a random 7
-+        character alphanumeric string is appended to the filename before the
-+        extension.
-+
-+        Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
-+        etc.) was appended to the filename until an avaible name in the
-+        destination directory was found. A malicious user could exploit this
-+        deterministic algorithm to create a denial-of-service attack.
- 
-     .. method:: get_valid_name(name)
- 
---- a/tests/modeltests/files/tests.py
-+++ b/tests/modeltests/files/tests.py
-@@ -8,10 +8,14 @@
- from django.core.files.base import ContentFile
- from django.core.files.uploadedfile import SimpleUploadedFile
- from django.test import TestCase
-+from django.utils import six
- 
- from .models import Storage, temp_storage, temp_storage_location
- 
- 
-+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
-+
-+
- class FileTests(TestCase):
-     def tearDown(self):
-         shutil.rmtree(temp_storage_location)
-@@ -57,27 +61,28 @@
-         # Save another file with the same name.
-         obj2 = Storage()
-         obj2.normal.save("django_test.txt", ContentFile("more content"))
--        self.assertEqual(obj2.normal.name, "tests/django_test_1.txt")
-+        obj2_name = obj2.normal.name
-+        six.assertRegex(self, obj2_name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
-         self.assertEqual(obj2.normal.size, 12)
- 
-         # Push the objects into the cache to make sure they pickle properly
-         cache.set("obj1", obj1)
-         cache.set("obj2", obj2)
--        self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt")
-+        six.assertRegex(self, cache.get("obj2").normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
- 
-         # Deleting an object does not delete the file it uses.
-         obj2.delete()
-         obj2.normal.save("django_test.txt", ContentFile("more content"))
--        self.assertEqual(obj2.normal.name, "tests/django_test_2.txt")
-+        self.assertNotEqual(obj2_name, obj2.normal.name)
-+        six.assertRegex(self, obj2.normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
- 
-         # Multiple files with the same name get _N appended to them.
--        objs = [Storage() for i in range(3)]
-+        objs = [Storage() for i in range(2)]
-         for o in objs:
-             o.normal.save("multiple_files.txt", ContentFile("Same Content"))
--        self.assertEqual(
--            [o.normal.name for o in objs],
--            ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"]
--        )
-+        names = [o.normal.name for o in objs]
-+        self.assertEqual(names[0], "tests/multiple_files.txt")
-+        six.assertRegex(self, names[1], "tests/multiple_files_%s.txt" % FILE_SUFFIX_REGEX)
-         for o in objs:
-             o.delete()
- 
---- a/tests/regressiontests/file_storage/tests.py
-+++ b/tests/regressiontests/file_storage/tests.py
-@@ -23,7 +23,7 @@
- from django.core.files.storage import FileSystemStorage, get_storage_class
- from django.core.files.uploadedfile import UploadedFile
- from django.test import SimpleTestCase
--from django.utils import unittest
-+from django.utils import six, unittest
- 
- # Try to import PIL in either of the two ways it can end up installed.
- # Checking for the existence of Image is enough for CPython, but
-@@ -37,6 +37,9 @@
-         Image = None
- 
- 
-+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
-+
-+
- class GetStorageClassTests(SimpleTestCase):
- 
-     def test_get_filesystem_storage(self):
-@@ -417,10 +420,9 @@
-         self.thread.start()
-         name = self.save_file('conflict')
-         self.thread.join()
--        self.assertTrue(self.storage.exists('conflict'))
--        self.assertTrue(self.storage.exists('conflict_1'))
--        self.storage.delete('conflict')
--        self.storage.delete('conflict_1')
-+        files = sorted(os.listdir(self.storage_dir))
-+        self.assertEqual(files[0], 'conflict')
-+        six.assertRegex(self, files[1], 'conflict_%s' % FILE_SUFFIX_REGEX)
- 
- class FileStoragePermissions(unittest.TestCase):
-     def setUp(self):
-@@ -457,9 +459,10 @@
-         self.storage.save('dotted.path/test', ContentFile("1"))
-         self.storage.save('dotted.path/test', ContentFile("2"))
- 
-+        files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path')))
-         self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
--        self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
--        self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
-+        self.assertEqual(files[0], 'test')
-+        six.assertRegex(self, files[1], 'test_%s' % FILE_SUFFIX_REGEX)
- 
-     def test_first_character_dot(self):
-         """
-@@ -472,10 +475,12 @@
-         self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
-         # Before 2.6, a leading dot was treated as an extension, and so
-         # underscore gets added to beginning instead of end.
-+        files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path')))
-+        self.assertEqual(files[0], '.test')
-         if sys.version_info < (2, 6):
--            self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
-+            six.assertRegex(self, files[1], '_%s.test' % FILE_SUFFIX_REGEX)
-         else:
--            self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
-+            six.assertRegex(self, files[1], '.test_%s' % FILE_SUFFIX_REGEX)
- 
- class DimensionClosingBug(unittest.TestCase):
-     """
---- a/django/utils/six.py
-+++ b/django/utils/six.py
-@@ -370,13 +370,22 @@
- 
- if PY3:
-     _iterlists = "lists"
-+    _assertRaisesRegex = "assertRaisesRegex"
-+    _assertRegex = "assertRegex"
- else:
-+    _assertRaisesRegex = "assertRaisesRegexp"
-     _iterlists = "iterlists"
-+    _assertRegex = "assertRegexpMatches"
- 
- def iterlists(d):
-     """Return an iterator over the values of a MultiValueDict."""
-     return getattr(d, _iterlists)()
- 
-+def assertRaisesRegex(self, *args, **kwargs):
-+    return getattr(self, _assertRaisesRegex)(*args, **kwargs)
-+
-+def assertRegex(self, *args, **kwargs):
-+    return getattr(self, _assertRegex)(*args, **kwargs)
- 
- add_move(MovedModule("_dummy_thread", "dummy_thread"))
- add_move(MovedModule("_thread", "thread"))
diff -Nru python-django-1.4.5/debian/patches/FTBFS-exception-in-servers-tests-tear-down.patch python-django-1.4.22/debian/patches/FTBFS-exception-in-servers-tests-tear-down.patch
--- python-django-1.4.5/debian/patches/FTBFS-exception-in-servers-tests-tear-down.patch	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/FTBFS-exception-in-servers-tests-tear-down.patch	1970-01-01 10:00:00.000000000 +1000
@@ -1,19 +0,0 @@
-Description: Fix Exception in servers tests tear down
-Origin: upstream, https://github.com/django/django/commit/cf114cffea5482a32064de86c61417d511c2edca
-Bug: https://code.djangoproject.com/ticket/19903
-Bug-Debian: http://bugs.debian.org/722483
-Forwarded: not-needed
-Author: Salvatore Bonaccorso <carnil@debian.org>
-Last-Update: 2014-05-05
-
---- a/tests/regressiontests/servers/tests.py
-+++ b/tests/regressiontests/servers/tests.py
-@@ -150,6 +150,8 @@
-             raise Exception("The line above should have raised an exception")
-         except exception:
-             pass
-+        finally:
-+            super(LiveServerAddress, cls).tearDownClass()
- 
-     def test_test_test(self):
-         # Intentionally empty method so that the test is picked up by the
diff -Nru python-django-1.4.5/debian/patches/is_safe_url_1_4.diff python-django-1.4.22/debian/patches/is_safe_url_1_4.diff
--- python-django-1.4.5/debian/patches/is_safe_url_1_4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/is_safe_url_1_4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,122 +0,0 @@
-Origin: upstream
-Last-Update: 2014-05-14
-Subject: Malformed URLs from user input incorrectly validated (CVE-2014-3730)
-
-The validation for redirects did not correctly validate some malformed
-URLs, which are accepted by some browsers. This allows a user to be
-redirected to an unsafe URL unexpectedly.
-
-Django relies on user input in some cases (e.g.
-:func:`django.contrib.auth.views.login`, ``django.contrib.comments``,
-and :doc:`i18n </topics/i18n/index>`) to redirect the user to an "on
-success" URL. The security checks for these redirects (namely
-``django.util.http.is_safe_url()``) did not correctly validate some
-malformed URLs, such as `http:\\\\\\djangoproject.com`, which are
-accepted by some browsers with more liberal URL parsing.
-
-To remedy this, the validation in ``is_safe_url()`` has been tightened
-to be able to handle and correctly validate these malformed URLs.
-
---- a/django/contrib/auth/tests/views.py
-+++ b/django/contrib/auth/tests/views.py
-@@ -307,8 +307,10 @@
- 
-         # Those URLs should not pass the security check
-         for bad_url in ('http://example.com',
-+                        'http:///example.com',
-                         'https://example.com',
-                         'ftp://exampel.com',
-+                        '///example.com',
-                         '//example.com',
-                         'javascript:alert("XSS")'):
- 
-@@ -330,8 +332,8 @@
-                          '/view/?param=https://example.com',
-                          '/view?param=ftp://exampel.com',
-                          'view/?param=//example.com',
--                         'https:///',
--                         'HTTPS:///',
-+                         'https://testserver/',
-+                         'HTTPS://testserver/',
-                          '//testserver/',
-                          '/url%20with%20spaces/'):  # see ticket #12534
-             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-@@ -467,8 +469,10 @@
- 
-         # Those URLs should not pass the security check
-         for bad_url in ('http://example.com',
-+                        'http:///example.com',
-                         'https://example.com',
-                         'ftp://exampel.com',
-+                        '///example.com',
-                         '//example.com',
-                         'javascript:alert("XSS")'):
-             nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
-@@ -488,8 +492,8 @@
-                          '/view/?param=https://example.com',
-                          '/view?param=ftp://exampel.com',
-                          'view/?param=//example.com',
--                         'https:///',
--                         'HTTPS:///',
-+                         'https://testserver/',
-+                         'HTTPS://testserver/',
-                          '//testserver/',
-                          '/url%20with%20spaces/'):  # see ticket #12534
-             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
---- a/django/utils/http.py
-+++ b/django/utils/http.py
-@@ -234,6 +234,18 @@
-     """
-     if not url:
-         return False
-+    # Chrome treats \ completely as /
-+    url = url.replace('\\', '/')
-+    # Chrome considers any URL with more than two slashes to be absolute, but
-+    # urlaprse is not so flexible. Treat any url with three slashes as unsafe.
-+    if url.startswith('///'):
-+        return False
-     url_info = urlparse.urlparse(url)
-+    # Forbid URLs like http:///example.com - with a scheme, but without a hostname.
-+    # In that URL, example.com is not the hostname but, a path component. However,
-+    # Chrome will still consider example.com to be the hostname, so we must not
-+    # allow this syntax.
-+    if not url_info[1] and url_info[0]:
-+        return False
-     return (not url_info[1] or url_info[1] == host) and \
-         (not url_info[0] or url_info[0] in ['http', 'https'])
---- a/tests/regressiontests/utils/http.py
-+++ b/tests/regressiontests/utils/http.py
-@@ -122,3 +122,33 @@
-         for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]:
-             self.assertEqual(http.int_to_base36(n), b36)
-             self.assertEqual(http.base36_to_int(b36), n)
-+
-+    def test_is_safe_url(self):
-+        for bad_url in ('http://example.com',
-+                        'http:///example.com',
-+                        'https://example.com',
-+                        'ftp://exampel.com',
-+                        r'\\example.com',
-+                        r'\\\example.com',
-+                        r'/\\/example.com',
-+                        r'\\\example.com',
-+                        r'\\example.com',
-+                        r'\\//example.com',
-+                        r'/\/example.com',
-+                        r'\/example.com',
-+                        r'/\example.com',
-+                        'http:///example.com',
-+                        'http:/\//example.com',
-+                        'http:\/example.com',
-+                        'http:/\example.com',
-+                        'javascript:alert("XSS")'):
-+            self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
-+        for good_url in ('/view/?param=http://example.com',
-+                     '/view/?param=https://example.com',
-+                     '/view?param=ftp://exampel.com',
-+                     'view/?param=//example.com',
-+                     'https://testserver/',
-+                     'HTTPS://testserver/',
-+                     '//testserver/',
-+                     '/url%20with%20spaces/'):
-+            self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url)
diff -Nru python-django-1.4.5/debian/patches/is_safe_url-1.4.diff python-django-1.4.22/debian/patches/is_safe_url-1.4.diff
--- python-django-1.4.5/debian/patches/is_safe_url-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/is_safe_url-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,84 +0,0 @@
-Origin: upstream
-Last-Update: 2013-08-08
-Subject: Possible XSS via ``is_safe_url``
-
-A common pattern in Django applications is for a view to accept, via
-querystring parameter, a URL to redirect to upon successful completion
-of the view's processing. This pattern is used in code bundled with
-Django itself; for example, the ``login`` view in
-``django.contrib.auth.views``, which accepts such a parameter to
-determine where to send a user following successful login.
-
-A utility function -- ``django.utils.http.is_safe_url()`` -- is
-provided and used to validate that this URL is on the current host
-(either via fully-qualified or relative URL), so as to avoid
-potentially dangerous redirects from maliciously-constructed
-querystrings.
-
-The ``is_safe_url()`` function works as intended for HTTP and HTTPS
-URLs, but due to the manner in which it parses the URL, will permit
-redirects to other schemes, such as ``javascript:``. While the Django
-project is unaware of any demonstrated ability to perform cross-site
-scripting attacks via this mechanism, the potential for such is
-sufficient to trigger a security response.
-
-To remedy this issue, the ``is_safe_url()`` function will be modified
-to properly recognize and reject URLs which specify a scheme other
-than HTTP or HTTPS.
-
---- a/django/contrib/auth/tests/views.py
-+++ b/django/contrib/auth/tests/views.py
-@@ -309,7 +309,8 @@
-         for bad_url in ('http://example.com',
-                         'https://example.com',
-                         'ftp://exampel.com',
--                        '//example.com'):
-+                        '//example.com',
-+                        'javascript:alert("XSS")'):
- 
-             nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
-                 'url': login_url,
-@@ -330,6 +331,7 @@
-                          '/view?param=ftp://exampel.com',
-                          'view/?param=//example.com',
-                          'https:///',
-+                         'HTTPS:///',
-                          '//testserver/',
-                          '/url%20with%20spaces/'):  # see ticket #12534
-             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-@@ -467,7 +469,8 @@
-         for bad_url in ('http://example.com',
-                         'https://example.com',
-                         'ftp://exampel.com',
--                        '//example.com'):
-+                        '//example.com',
-+                        'javascript:alert("XSS")'):
-             nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
-                 'url': logout_url,
-                 'next': REDIRECT_FIELD_NAME,
-@@ -486,6 +489,7 @@
-                          '/view?param=ftp://exampel.com',
-                          'view/?param=//example.com',
-                          'https:///',
-+                         'HTTPS:///',
-                          '//testserver/',
-                          '/url%20with%20spaces/'):  # see ticket #12534
-             safe_url = '%(url)s?%(next)s=%(good_url)s' % {
---- a/django/utils/http.py
-+++ b/django/utils/http.py
-@@ -228,11 +228,12 @@
- def is_safe_url(url, host=None):
-     """
-     Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
--    a different host).
-+    a different host and uses a safe scheme).
- 
-     Always returns ``False`` on an empty url.
-     """
-     if not url:
-         return False
--    netloc = urlparse.urlparse(url)[1]
--    return not netloc or netloc == host
-+    url_info = urlparse.urlparse(url)
-+    return (not url_info[1] or url_info[1] == host) and \
-+        (not url_info[0] or url_info[0] in ['http', 'https'])
diff -Nru python-django-1.4.5/debian/patches/mysql-typecast-1.4.x.diff python-django-1.4.22/debian/patches/mysql-typecast-1.4.x.diff
--- python-django-1.4.5/debian/patches/mysql-typecast-1.4.x.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/mysql-typecast-1.4.x.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,265 +0,0 @@
-Origin: upstream
-Last-Update: 2014-04-18
-Subject: MySQL typecasting protection
-
-The MySQL database is known to "typecast" on certain queries; for
-example, when querying a table which contains string values, but using
-a query which filters based on an integer value, MySQL will first
-silently coerce the strings to integers, and return a result based on
-that.
-
-Django's model field classes are aware of their own types, and most
-such classes perform explicit conversion of query arguments to the
-correct database-level type before querying. However, three model
-field classes did not correctly convert their arguments:
-
-* ``FilePathField``
-
-* ``GenericIPAddressField``
-
-* ``IPAddressField``
-
-These three fields have been updated to convert their arguments to the
-correct types before querying.
-
-Additionally, developers of custom model fields are now warned via
-documentation to ensure their custom field classes will perform
-appropriate type conversions, and users of the ``raw()`` and
-``extra()`` query methods -- which allow the developer to supply raw
-SQL or SQL fragments -- will be advised to ensure they perform
-appropriate manual type conversions prior to executing queries.
-
---- a/django/db/models/fields/__init__.py
-+++ b/django/db/models/fields/__init__.py
-@@ -911,6 +911,12 @@
-         kwargs['max_length'] = kwargs.get('max_length', 100)
-         Field.__init__(self, verbose_name, name, **kwargs)
- 
-+    def get_prep_value(self, value):
-+        value = super(FilePathField, self).get_prep_value(value)
-+        if value is None:
-+            return None
-+        return smart_unicode(value)
-+
-     def formfield(self, **kwargs):
-         defaults = {
-             'path': self.path,
-@@ -1010,6 +1016,12 @@
-         kwargs['max_length'] = 15
-         Field.__init__(self, *args, **kwargs)
- 
-+    def get_prep_value(self, value):
-+        value = super(IPAddressField, self).get_prep_value(value)
-+        if value is None:
-+            return None
-+        return smart_unicode(value)
-+
-     def get_internal_type(self):
-         return "IPAddressField"
- 
-@@ -1047,12 +1059,14 @@
-         return value or None
- 
-     def get_prep_value(self, value):
-+        if value is None:
-+            return value
-         if value and ':' in value:
-             try:
-                 return clean_ipv6_address(value, self.unpack_ipv4)
-             except exceptions.ValidationError:
-                 pass
--        return value
-+        return smart_unicode(value)
- 
-     def formfield(self, **kwargs):
-         defaults = {'form_class': forms.GenericIPAddressField}
---- a/docs/howto/custom-model-fields.txt
-+++ b/docs/howto/custom-model-fields.txt
-@@ -482,6 +482,16 @@
-             return ''.join([''.join(l) for l in (value.north,
-                     value.east, value.south, value.west)])
- 
-+.. warning::
-+
-+    If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT``
-+    types for MySQL, you must make sure that :meth:`.get_prep_value`
-+    always returns a string type. MySQL performs flexible and unexpected
-+    matching when a query is performed on these types and the provided
-+    value is an integer, which can cause queries to include unexpected
-+    objects in their results. This problem cannot occur if you always
-+    return a string type from :meth:`.get_prep_value`.
-+
- Converting query values to database values
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 
---- a/docs/ref/databases.txt
-+++ b/docs/ref/databases.txt
-@@ -432,6 +432,22 @@
- statement. If ``select_for_update()`` is used with ``nowait=True`` then a
- ``DatabaseError`` will be raised.
- 
-+Automatic typecasting can cause unexpected results
-+--------------------------------------------------
-+
-+When performing a query on a string type, but with an integer value, MySQL will
-+coerce the types of all values in the table to an integer before performing the
-+comparison. If your table contains the values ``'abc'``, ``'def'`` and you
-+query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1``
-+will match the value ``'abc1'``. Therefore, string type fields included in Django
-+will always cast the value to a string before using it in a query.
-+
-+If you implement custom model fields that inherit from :class:`~django.db.models.Field`
-+directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use
-+:meth:`extra() <django.db.models.query.QuerySet.extra>` or
-+:meth:`raw() <django.db.models.Manager.raw>`, you should ensure that you
-+perform the appropriate typecasting.
-+
- .. _sqlite-notes:
- 
- SQLite notes
---- a/docs/ref/models/querysets.txt
-+++ b/docs/ref/models/querysets.txt
-@@ -1041,6 +1041,16 @@
- 
-       Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
- 
-+.. warning::
-+
-+    If you are performing queries on MySQL, note that MySQL's silent type coercion
-+    may cause unexpected results when mixing types. If you query on a string
-+    type column, but with an integer value, MySQL will coerce the types of all values
-+    in the table to an integer before performing the comparison. For example, if your
-+    table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
-+    both rows will match. To prevent this, perform the correct typecasting
-+    before using the value in a query.
-+
- defer
- ~~~~~
- 
---- a/docs/topics/db/sql.txt
-+++ b/docs/topics/db/sql.txt
-@@ -69,6 +69,16 @@
-     database, but does nothing to enforce that. If the query does not
-     return rows, a (possibly cryptic) error will result.
- 
-+.. warning::
-+
-+    If you are performing queries on MySQL, note that MySQL's silent type coercion
-+    may cause unexpected results when mixing types. If you query on a string
-+    type column, but with an integer value, MySQL will coerce the types of all values
-+    in the table to an integer before performing the comparison. For example, if your
-+    table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
-+    both rows will match. To prevent this, perform the correct typecasting
-+    before using the value in a query.
-+
- Mapping query fields to model fields
- ------------------------------------
- 
---- a/tests/regressiontests/model_fields/tests.py
-+++ b/tests/regressiontests/model_fields/tests.py
-@@ -6,8 +6,15 @@
- from django import test
- from django import forms
- from django.core.exceptions import ValidationError
-+from django.db.models.fields import (
-+    AutoField, BigIntegerField, BooleanField, CharField,
-+    CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField,
-+    EmailField, FilePathField, FloatField, IntegerField, IPAddressField,
-+    GenericIPAddressField, NullBooleanField, PositiveIntegerField,
-+    PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
-+    TimeField, URLField)
- from django.db import models
--from django.db.models.fields.files import FieldFile
-+from django.db.models.fields.files import FileField, ImageField, FieldFile
- from django.utils import unittest
- 
- from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
-@@ -373,3 +380,88 @@
-         field = d._meta.get_field('myfile')
-         field.save_form_data(d, 'else.txt')
-         self.assertEqual(d.myfile, 'else.txt')
-+
-+
-+class PrepValueTest(test.TestCase):
-+    def test_AutoField(self):
-+        self.assertIsInstance(AutoField(primary_key=True).get_prep_value(1), int)
-+
-+    def test_BigIntegerField(self):
-+        self.assertIsInstance(BigIntegerField().get_prep_value(long(9999999999999999999)), long)
-+
-+    def test_BooleanField(self):
-+        self.assertIsInstance(BooleanField().get_prep_value(True), bool)
-+
-+    def test_CharField(self):
-+        self.assertIsInstance(CharField().get_prep_value(''), str)
-+        self.assertIsInstance(CharField().get_prep_value(0), unicode)
-+
-+    def test_CommaSeparatedIntegerField(self):
-+        self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value('1,2'), str)
-+        self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(0), unicode)
-+
-+    def test_DateField(self):
-+        self.assertIsInstance(DateField().get_prep_value(datetime.date.today()), datetime.date)
-+
-+    def test_DateTimeField(self):
-+        self.assertIsInstance(DateTimeField().get_prep_value(datetime.datetime.now()), datetime.datetime)
-+
-+    def test_DecimalField(self):
-+        self.assertIsInstance(DecimalField().get_prep_value(Decimal('1.2')), Decimal)
-+
-+    def test_EmailField(self):
-+        self.assertIsInstance(EmailField().get_prep_value('mailbox@domain.com'), str)
-+
-+    def test_FileField(self):
-+        self.assertIsInstance(FileField().get_prep_value('filename.ext'), unicode)
-+        self.assertIsInstance(FileField().get_prep_value(0), unicode)
-+
-+    def test_FilePathField(self):
-+        self.assertIsInstance(FilePathField().get_prep_value('tests.py'), unicode)
-+        self.assertIsInstance(FilePathField().get_prep_value(0), unicode)
-+
-+    def test_FloatField(self):
-+        self.assertIsInstance(FloatField().get_prep_value(1.2), float)
-+
-+    def test_ImageField(self):
-+        self.assertIsInstance(ImageField().get_prep_value('filename.ext'), unicode)
-+
-+    def test_IntegerField(self):
-+        self.assertIsInstance(IntegerField().get_prep_value(1), int)
-+
-+    def test_IPAddressField(self):
-+        self.assertIsInstance(IPAddressField().get_prep_value('127.0.0.1'), unicode)
-+        self.assertIsInstance(IPAddressField().get_prep_value(0), unicode)
-+
-+    def test_GenericIPAddressField(self):
-+        self.assertIsInstance(GenericIPAddressField().get_prep_value('127.0.0.1'), unicode)
-+        self.assertIsInstance(GenericIPAddressField().get_prep_value(0), unicode)
-+
-+    def test_NullBooleanField(self):
-+        self.assertIsInstance(NullBooleanField().get_prep_value(True), bool)
-+
-+    def test_PositiveIntegerField(self):
-+        self.assertIsInstance(PositiveIntegerField().get_prep_value(1), int)
-+
-+    def test_PositiveSmallIntegerField(self):
-+        self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(1), int)
-+
-+    def test_SlugField(self):
-+        self.assertIsInstance(SlugField().get_prep_value('slug'), str)
-+        self.assertIsInstance(SlugField().get_prep_value(0), unicode)
-+
-+    def test_SmallIntegerField(self):
-+        self.assertIsInstance(SmallIntegerField().get_prep_value(1), int)
-+
-+    def test_TextField(self):
-+        self.assertIsInstance(TextField().get_prep_value('Abc'), str)
-+        self.assertIsInstance(TextField().get_prep_value(0), unicode)
-+
-+    def test_TimeField(self):
-+        self.assertIsInstance(
-+            TimeField().get_prep_value(datetime.datetime.now().time()),
-+            datetime.time)
-+
-+    def test_URLField(self):
-+        self.assertIsInstance(URLField().get_prep_value('http://domain.com'), str)
-+
diff -Nru python-django-1.4.5/debian/patches/newlines-1.4.x.diff python-django-1.4.22/debian/patches/newlines-1.4.x.diff
--- python-django-1.4.5/debian/patches/newlines-1.4.x.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/newlines-1.4.x.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,145 +0,0 @@
-commit 55fa3c3f68a07b737290b1e25b1ed0664fff6a96
-Author: Tim Graham <timograham@gmail.com>
-Date:   Fri Jun 12 13:49:31 2015 -0400
-
-    [1.4.x] Prevented newlines from being accepted in some validators.
-    
-    This is a security fix; disclosure to follow shortly.
-    
-    Thanks to Sjoerd Job Postmus for the report and draft patch.
-
-Index: python-django-1.4.5/django/core/validators.py
-===================================================================
---- python-django-1.4.5.orig/django/core/validators.py
-+++ python-django-1.4.5/django/core/validators.py
-@@ -50,7 +50,7 @@ class URLValidator(RegexValidator):
-         r'localhost|' #localhost...
-         r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
-         r'(?::\d+)?' # optional port
--        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
-+        r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
- 
-     def __init__(self, verify_exists=False,
-                  validator_user_agent=URL_VALIDATOR_USER_AGENT):
-@@ -133,11 +133,16 @@ class URLValidator(RegexValidator):
-                 raise broken_error
- 
- 
-+integer_validator = RegexValidator(
-+    re.compile('^-?\d+\Z'),
-+    message=_('Enter a valid integer.'),
-+    code='invalid',
-+)
-+
-+
- def validate_integer(value):
--    try:
--        int(value)
--    except (ValueError, TypeError):
--        raise ValidationError('')
-+    return integer_validator(value)
-+
- 
- class EmailValidator(RegexValidator):
- 
-@@ -160,14 +165,14 @@ email_re = re.compile(
-     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
-     # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
-     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
--    r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)'  # domain
--    r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
-+    r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?\Z)'  # domain
-+    r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
- validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid')
- 
--slug_re = re.compile(r'^[-\w]+$')
-+slug_re = re.compile(r'^[-\w]+\Z')
- validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
- 
--ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
-+ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z')
- validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
- 
- def validate_ipv6_address(value):
-@@ -205,7 +210,7 @@ def ip_address_validators(protocol, unpa
-         raise ValueError("The protocol '%s' is unknown. Supported: %s"
-                          % (protocol, ip_address_validator_map.keys()))
- 
--comma_separated_int_list_re = re.compile('^[\d,]+$')
-+comma_separated_int_list_re = re.compile('^[\d,]+\Z')
- validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
- 
- 
-@@ -249,4 +254,3 @@ class MaxLengthValidator(BaseValidator):
-     clean   = lambda self, x: len(x)
-     message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
-     code = 'max_length'
--
-Index: python-django-1.4.5/tests/modeltests/validators/tests.py
-===================================================================
---- python-django-1.4.5.orig/tests/modeltests/validators/tests.py
-+++ python-django-1.4.5/tests/modeltests/validators/tests.py
-@@ -12,13 +12,16 @@ NOW = datetime.now()
- 
- TEST_DATA = (
-     # (validator, value, expected),
-+    # (validator, value, expected),
-     (validate_integer, '42', None),
-     (validate_integer, '-42', None),
-     (validate_integer, -42, None),
--    (validate_integer, -42.5, None),
- 
-+    (validate_integer, -42.5, ValidationError),
-     (validate_integer, None, ValidationError),
-     (validate_integer, 'a', ValidationError),
-+    (validate_integer, '\n42', ValidationError),
-+    (validate_integer, '42\n', ValidationError),
- 
-     (validate_email, 'email@here.com', None),
-     (validate_email, 'weirder-email@here.and.there.com', None),
-@@ -33,6 +36,11 @@ TEST_DATA = (
-     # Quoted-string format (CR not allowed)
-     (validate_email, '"\\\011"@here.com', None),
-     (validate_email, '"\\\012"@here.com', ValidationError),
-+    # Trailing newlines in username or domain not allowed
-+    (validate_email, 'a@b.com\n', ValidationError),
-+    (validate_email, 'a\n@b.com', ValidationError),
-+    (validate_email, '"test@test"\n@example.com', ValidationError),
-+    (validate_email, 'a@[127.0.0.1]\n', ValidationError),
- 
-     (validate_slug, 'slug-ok', None),
-     (validate_slug, 'longer-slug-still-ok', None),
-@@ -45,6 +53,7 @@ TEST_DATA = (
-     (validate_slug, 'some@mail.com', ValidationError),
-     (validate_slug, '你好', ValidationError),
-     (validate_slug, '\n', ValidationError),
-+    (validate_slug, 'trailing-newline\n', ValidationError),
- 
-     (validate_ipv4_address, '1.1.1.1', None),
-     (validate_ipv4_address, '255.0.0.0', None),
-@@ -54,6 +63,7 @@ TEST_DATA = (
-     (validate_ipv4_address, '25.1.1.', ValidationError),
-     (validate_ipv4_address, '25,1,1,1', ValidationError),
-     (validate_ipv4_address, '25.1 .1.1', ValidationError),
-+    (validate_ipv4_address, '1.1.1.1\n', ValidationError),
- 
-     # validate_ipv6_address uses django.utils.ipv6, which
-     # is tested in much greater detail in it's own testcase
-@@ -87,6 +97,7 @@ TEST_DATA = (
-     (validate_comma_separated_integer_list, '', ValidationError),
-     (validate_comma_separated_integer_list, 'a,b,c', ValidationError),
-     (validate_comma_separated_integer_list, '1, 2, 3', ValidationError),
-+    (validate_comma_separated_integer_list, '1,2,3\n', ValidationError),
- 
-     (MaxValueValidator(10), 10, None),
-     (MaxValueValidator(10), -10, None),
-@@ -138,6 +149,9 @@ TEST_DATA = (
-     (URLValidator(), 'http://-invalid.com', ValidationError),
-     (URLValidator(), 'http://inv-.alid-.com', ValidationError),
-     (URLValidator(), 'http://inv-.-alid.com', ValidationError),
-+    # Trailing newlines not accepted
-+    (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
-+    (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
- 
-     (BaseValidator(True), True, None),
-     (BaseValidator(True), False, ValidationError),
diff -Nru python-django-1.4.5/debian/patches/password-2.5-compat.diff python-django-1.4.22/debian/patches/password-2.5-compat.diff
--- python-django-1.4.5/debian/patches/password-2.5-compat.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/password-2.5-compat.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,81 +0,0 @@
-Last-updated: 2013-09-14
-Origin: https://github.com/django/django/commit/6903d1690a92aa040adfb0c8eb37cf62e4206714
-Author: Russell Keith-Magee
-Description: Removed usage of b"" string syntax for Python 2.5 compatibility.
-
---- a/django/contrib/auth/tests/hashers.py
-+++ b/django/contrib/auth/tests/hashers.py
-@@ -33,7 +33,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-         )
- 
-     def test_pkbdf2(self):
-@@ -47,7 +47,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             "seasalt",
-             "pbkdf2_sha256",
-         )
-@@ -63,7 +63,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             "seasalt",
-             "sha1",
-         )
-@@ -79,7 +79,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             "seasalt",
-             "md5",
-         )
-@@ -99,7 +99,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             "",
-             "unsalted_md5",
-         )
-@@ -115,7 +115,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             "seasalt",
-             "crypt",
-         )
-@@ -131,7 +131,7 @@
-         self.assertRaises(
-             ValueError,
-             make_password,
--            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-             hasher="bcrypt",
-         )
- 
-@@ -154,8 +154,8 @@
-         def encode(s, password, salt):
-             return True
- 
--        self.assertTrue(encode(None, b"1234", b"1234"))
--        self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
-+        self.assertTrue(encode(None, "1234", "1234"))
-+        self.assertRaises(ValueError, encode, None, "1234567890A", "1234")
- 
-     def test_low_level_pkbdf2(self):
-         hasher = PBKDF2PasswordHasher()
diff -Nru python-django-1.4.5/debian/patches/password-dos.diff python-django-1.4.22/debian/patches/password-dos.diff
--- python-django-1.4.5/debian/patches/password-dos.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/password-dos.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,371 +0,0 @@
-Origin: https://github.com/django/django/commit/3f3d887a6844ec2db743fee64c9e53e04d39a368#L1L349
-Date: 2013-09-15
-Description: Ensure that passwords are never long enough for a DoS.
- * Limit the password length to 4096 bytes
- * Password hashers will raise a ValueError
- * django.contrib.auth forms will fail validation
- * Document in release notes that this is a backwards incompatible change
-
-Thanks to Josh Wright for the report, and Donald Stufft for the patch.
-
-Some changes were stripped from this patch since they also apply fixes to
-UnsaltedSHA1PasswordHasher which is not shipped in the version of Django
-in Debian.
-
---- a/django/contrib/auth/forms.py
-+++ b/django/contrib/auth/forms.py
-@@ -8,7 +8,10 @@
- 
- from django.contrib.auth import authenticate
- from django.contrib.auth.models import User
--from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
-+from django.contrib.auth.hashers import (
-+    MAXIMUM_PASSWORD_LENGTH, UNUSABLE_PASSWORD,
-+    is_password_usable, get_hasher
-+)
- from django.contrib.auth.tokens import default_token_generator
- from django.contrib.sites.models import get_current_site
- 
-@@ -70,10 +73,11 @@
-             'invalid': _("This value may contain only letters, numbers and "
-                          "@/./+/-/_ characters.")})
-     password1 = forms.CharField(label=_("Password"),
--        widget=forms.PasswordInput)
-+        widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH)
-     password2 = forms.CharField(label=_("Password confirmation"),
-         widget=forms.PasswordInput,
--        help_text = _("Enter the same password as above, for verification."))
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+        help_text=_("Enter the same password as above, for verification."))
- 
-     class Meta:
-         model = User
-@@ -137,7 +141,11 @@
-     username/password logins.
-     """
-     username = forms.CharField(label=_("Username"), max_length=30)
--    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
-+    password = forms.CharField(
-+        label=_("Password"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
- 
-     error_messages = {
-         'invalid_login': _("Please enter a correct username and password. "
-@@ -250,10 +258,16 @@
-     error_messages = {
-         'password_mismatch': _("The two password fields didn't match."),
-     }
--    new_password1 = forms.CharField(label=_("New password"),
--                                    widget=forms.PasswordInput)
--    new_password2 = forms.CharField(label=_("New password confirmation"),
--                                    widget=forms.PasswordInput)
-+    new_password1 = forms.CharField(
-+        label=_("New password"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
-+    new_password2 = forms.CharField(
-+        label=_("New password confirmation"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
- 
-     def __init__(self, user, *args, **kwargs):
-         self.user = user
-@@ -284,8 +298,11 @@
-         'password_incorrect': _("Your old password was entered incorrectly. "
-                                 "Please enter it again."),
-     })
--    old_password = forms.CharField(label=_("Old password"),
--                                   widget=forms.PasswordInput)
-+    old_password = forms.CharField(
-+        label=_("Old password"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
- 
-     def clean_old_password(self):
-         """
-@@ -307,10 +324,16 @@
-     error_messages = {
-         'password_mismatch': _("The two password fields didn't match."),
-     }
--    password1 = forms.CharField(label=_("Password"),
--                                widget=forms.PasswordInput)
--    password2 = forms.CharField(label=_("Password (again)"),
--                                widget=forms.PasswordInput)
-+    password1 = forms.CharField(
-+        label=_("Password"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
-+    password2 = forms.CharField(
-+        label=_("Password (again)"),
-+        widget=forms.PasswordInput,
-+        max_length=MAXIMUM_PASSWORD_LENGTH,
-+    )
- 
-     def __init__(self, user, *args, **kwargs):
-         self.user = user
---- a/django/contrib/auth/hashers.py
-+++ b/django/contrib/auth/hashers.py
-@@ -1,3 +1,4 @@
-+import functools
- import hashlib
- 
- from django.conf import settings
-@@ -11,10 +12,23 @@
- 
- 
- UNUSABLE_PASSWORD = '!'  # This will never be a valid encoded hash
-+MAXIMUM_PASSWORD_LENGTH = 4096  # The maximum length a password can be to prevent DoS
- HASHERS = None  # lazily loaded from PASSWORD_HASHERS
- PREFERRED_HASHER = None  # defaults to first item in PASSWORD_HASHERS
- 
- 
-+def password_max_length(max_length):
-+    def inner(fn):
-+        @functools.wraps(fn)
-+        def wrapper(self, password, *args, **kwargs):
-+            if len(password) > max_length:
-+                raise ValueError("Invalid password; Must be less than or equal"
-+                                 " to %d bytes" % max_length)
-+            return fn(self, password, *args, **kwargs)
-+        return wrapper
-+    return inner
-+
-+
- def is_password_usable(encoded):
-     return (encoded is not None and encoded != UNUSABLE_PASSWORD)
- 
-@@ -197,6 +211,7 @@
-     iterations = 10000
-     digest = hashlib.sha256
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt, iterations=None):
-         assert password
-         assert salt and '$' not in salt
-@@ -206,6 +221,7 @@
-         hash = hash.encode('base64').strip()
-         return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         algorithm, iterations, salt, hash = encoded.split('$', 3)
-         assert algorithm == self.algorithm
-@@ -251,11 +267,13 @@
-         bcrypt = self._load_library()
-         return bcrypt.gensalt(self.rounds)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt):
-         bcrypt = self._load_library()
-         data = bcrypt.hashpw(password, salt)
-         return "%s$%s" % (self.algorithm, data)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         algorithm, data = encoded.split('$', 1)
-         assert algorithm == self.algorithm
-@@ -280,12 +298,14 @@
-     """
-     algorithm = "sha1"
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt):
-         assert password
-         assert salt and '$' not in salt
-         hash = hashlib.sha1(salt + password).hexdigest()
-         return "%s$%s$%s" % (self.algorithm, salt, hash)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         algorithm, salt, hash = encoded.split('$', 2)
-         assert algorithm == self.algorithm
-@@ -308,12 +328,14 @@
-     """
-     algorithm = "md5"
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt):
-         assert password
-         assert salt and '$' not in salt
-         hash = hashlib.md5(salt + password).hexdigest()
-         return "%s$%s$%s" % (self.algorithm, salt, hash)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         algorithm, salt, hash = encoded.split('$', 2)
-         assert algorithm == self.algorithm
-@@ -344,15 +366,18 @@
-     def salt(self):
-         return ''
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt):
-         return hashlib.md5(password).hexdigest()
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         if len(encoded) == 37 and encoded.startswith('md5$$'):
-             encoded = encoded[5:]
-         encoded_2 = self.encode(password, '')
-         return constant_time_compare(encoded, encoded_2)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def safe_summary(self, encoded):
-         return SortedDict([
-             (_('algorithm'), self.algorithm),
-@@ -372,6 +397,7 @@
-     def salt(self):
-         return get_random_string(2)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def encode(self, password, salt):
-         crypt = self._load_library()
-         assert len(salt) == 2
-@@ -379,6 +405,7 @@
-         # we don't need to store the salt, but Django used to do this
-         return "%s$%s$%s" % (self.algorithm, '', data)
- 
-+    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
-     def verify(self, password, encoded):
-         crypt = self._load_library()
-         algorithm, salt, data = encoded.split('$', 2)
-@@ -393,4 +420,3 @@
-             (_('salt'), salt),
-             (_('hash'), mask_hash(data, show=3)),
-         ])
--
---- a/django/contrib/auth/tests/hashers.py
-+++ b/django/contrib/auth/tests/hashers.py
-@@ -1,7 +1,8 @@
- from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
- from django.contrib.auth.hashers import (is_password_usable, 
-     check_password, make_password, PBKDF2PasswordHasher, load_hashers,
--    PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD)
-+    PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD,
-+    MAXIMUM_PASSWORD_LENGTH, password_max_length)
- from django.utils import unittest
- from django.utils.unittest import skipUnless
- from django.test.utils import override_settings
-@@ -28,6 +29,12 @@
-         self.assertTrue(is_password_usable(encoded))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+        )
- 
-     def test_pkbdf2(self):
-         encoded = make_password('letmein', 'seasalt', 'pbkdf2_sha256')
-@@ -36,6 +43,14 @@
-         self.assertTrue(is_password_usable(encoded))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "seasalt",
-+            "pbkdf2_sha256",
-+        )
- 
-     def test_sha1(self):
-         encoded = make_password('letmein', 'seasalt', 'sha1')
-@@ -44,6 +59,14 @@
-         self.assertTrue(is_password_usable(encoded))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "seasalt",
-+            "sha1",
-+        )
- 
-     def test_md5(self):
-         encoded = make_password('letmein', 'seasalt', 'md5')
-@@ -52,6 +75,14 @@
-         self.assertTrue(is_password_usable(encoded))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "seasalt",
-+            "md5",
-+        )
- 
-     def test_unsalted_md5(self):
-         encoded = make_password('letmein', 'seasalt', 'unsalted_md5')
-@@ -64,6 +95,14 @@
-         self.assertTrue(is_password_usable(alt_encoded))
-         self.assertTrue(check_password(u'letmein', alt_encoded))
-         self.assertFalse(check_password('letmeinz', alt_encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "",
-+            "unsalted_md5",
-+        )
- 
-     @skipUnless(crypt, "no crypt module to generate password.")
-     def test_crypt(self):
-@@ -72,6 +111,14 @@
-         self.assertTrue(is_password_usable(encoded))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            "seasalt",
-+            "crypt",
-+        )
- 
-     @skipUnless(bcrypt, "py-bcrypt not installed")
-     def test_bcrypt(self):
-@@ -80,6 +127,13 @@
-         self.assertTrue(encoded.startswith('bcrypt$'))
-         self.assertTrue(check_password(u'letmein', encoded))
-         self.assertFalse(check_password('letmeinz', encoded))
-+        # Long password
-+        self.assertRaises(
-+            ValueError,
-+            make_password,
-+            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
-+            hasher="bcrypt",
-+        )
- 
-     def test_unusable(self):
-         encoded = make_password(None)
-@@ -95,6 +149,14 @@
-             make_password('letmein', hasher='lolcat')
-         self.assertRaises(ValueError, doit)
- 
-+    def test_max_password_length_decorator(self):
-+        @password_max_length(10)
-+        def encode(s, password, salt):
-+            return True
-+
-+        self.assertTrue(encode(None, b"1234", b"1234"))
-+        self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
-+
-     def test_low_level_pkbdf2(self):
-         hasher = PBKDF2PasswordHasher()
-         encoded = hasher.encode('letmein', 'seasalt')
diff -Nru python-django-1.4.5/debian/patches/remote-user-1.4.diff python-django-1.4.22/debian/patches/remote-user-1.4.diff
--- python-django-1.4.5/debian/patches/remote-user-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/remote-user-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,88 +0,0 @@
-commit c9e3b9949cd55f090591fbdc4a114fcb8368b6d9
-Author: Preston Holmes <preston@ptone.com>
-Date:   Mon Aug 11 12:04:53 2014 -0400
-
-    [1.4.x] Fixed #23066 -- Modified RemoteUserMiddleware to logout on REMOTE_USE change.
-    
-    This is a security fix. Disclosure following shortly.
-
---- a/django/contrib/auth/middleware.py
-+++ b/django/contrib/auth/middleware.py
-@@ -1,4 +1,5 @@
- from django.contrib import auth
-+from django.contrib.auth.backends import RemoteUserBackend
- from django.core.exceptions import ImproperlyConfigured
- from django.utils.functional import SimpleLazyObject
- 
-@@ -47,9 +48,11 @@
-         try:
-             username = request.META[self.header]
-         except KeyError:
--            # If specified header doesn't exist then return (leaving
--            # request.user set to AnonymousUser by the
--            # AuthenticationMiddleware).
-+            # If specified header doesn't exist then remove any existing
-+            # authenticated remote-user, or return (leaving request.user set to
-+            # AnonymousUser by the AuthenticationMiddleware).
-+            if request.user.is_authenticated():
-+                self._remove_invalid_user(request)
-             return
-         # If the user is already authenticated and that user is the user we are
-         # getting passed in the headers, then the correct user is already
-@@ -57,6 +60,11 @@
-         if request.user.is_authenticated():
-             if request.user.username == self.clean_username(username, request):
-                 return
-+            else:
-+                # An authenticated user is associated with the request, but
-+                # it does not match the authorized user in the header.
-+                self._remove_invalid_user(request)
-+
-         # We are seeing this user for the first time in this session, attempt
-         # to authenticate the user.
-         user = auth.authenticate(remote_user=username)
-@@ -78,3 +86,17 @@
-         except AttributeError: # Backend has no clean_username method.
-             pass
-         return username
-+
-+    def _remove_invalid_user(self, request):
-+        """
-+        Removes the current authenticated user in the request which is invalid
-+        but only if the user is authenticated via the RemoteUserBackend.
-+        """
-+        try:
-+            stored_backend = auth.load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
-+        except ImproperlyConfigured:
-+            # backend failed to load
-+            auth.logout(request)
-+        else:
-+            if isinstance(stored_backend, RemoteUserBackend):
-+                auth.logout(request)
---- a/django/contrib/auth/tests/remote_user.py
-+++ b/django/contrib/auth/tests/remote_user.py
-@@ -95,6 +95,24 @@
-         response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
-         self.assertEqual(default_login, response.context['user'].last_login)
- 
-+    def test_user_switch_forces_new_login(self):
-+        """
-+        Tests that if the username in the header changes between requests
-+        that the original user is logged out
-+        """
-+        User.objects.create(username='knownuser')
-+        # Known user authenticates
-+        response = self.client.get('/remote_user/',
-+                                   **{'REMOTE_USER': self.known_user})
-+        self.assertEqual(response.context['user'].username, 'knownuser')
-+        # During the session, the REMOTE_USER changes to a different user.
-+        response = self.client.get('/remote_user/',
-+                                   **{'REMOTE_USER': "newnewuser"})
-+        # Ensure that the current user is not the prior remote_user
-+        # In backends that create a new user, username is "newnewuser"
-+        # In backends that do not create new users, it is '' (anonymous user)
-+        self.assertNotEqual(response.context['user'].username, 'knownuser')
-+
-     def tearDown(self):
-         """Restores settings to avoid breaking other tests."""
-         settings.MIDDLEWARE_CLASSES = self.curr_middleware
diff -Nru python-django-1.4.5/debian/patches/reverse-1.4.diff python-django-1.4.22/debian/patches/reverse-1.4.diff
--- python-django-1.4.5/debian/patches/reverse-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/reverse-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,41 +0,0 @@
-commit c2fe73133b62a1d9e8f7a6b43966570b14618d7e
-Author: Florian Apolloner <florian@apolloner.eu>
-Date:   Thu Jul 17 21:59:28 2014 +0200
-
-    [1.4.x] Prevented reverse() from generating URLs pointing to other hosts.
-    
-    This is a security fix. Disclosure following shortly.
-
---- a/django/core/urlresolvers.py
-+++ b/django/core/urlresolvers.py
-@@ -406,6 +406,8 @@
-                     unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
-                     candidate = (prefix_norm + result) % unicode_kwargs
-                 if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
-+                    if candidate.startswith('//'):
-+                        candidate = '/%%2F%s' % candidate[2:]
-                     return candidate
-         # lookup_view can be URL label, or dotted path, or callable, Any of
-         # these can be passed in at the top, but callables are not friendly in
---- a/tests/regressiontests/urlpatterns_reverse/tests.py
-+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
-@@ -142,6 +142,9 @@
-     ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}),
-     ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}),
-     ('defaults', NoReverseMatch, [], {'arg2': 1}),
-+
-+    # Security tests
-+    ('security', '/%2Fexample.com/security/', ['/example.com'], {}),
- )
- 
- class NoURLPatternsTests(TestCase):
---- a/tests/regressiontests/urlpatterns_reverse/urls.py
-+++ b/tests/regressiontests/urlpatterns_reverse/urls.py
-@@ -71,4 +71,7 @@
-     (r'defaults_view2/(?P<arg1>\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'),
- 
-     url('^includes/', include(other_patterns)),
-+
-+    # Security tests
-+    url('(.+)/security/$', empty_view, name='security'),
- )
diff -Nru python-django-1.4.5/debian/patches/reverse-execution-1.4.x.patch python-django-1.4.22/debian/patches/reverse-execution-1.4.x.patch
--- python-django-1.4.5/debian/patches/reverse-execution-1.4.x.patch	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/reverse-execution-1.4.x.patch	1970-01-01 10:00:00.000000000 +1000
@@ -1,174 +0,0 @@
-Origin: upstream
-Last-Update: 2014-04-18
-Subject: Unexpected code execution using ``reverse()``
-
-Django's URL handling is based on a mapping of regex patterns
-(representing the URLs) to callable views, and Django's own processing
-consists of matching a requested URL against those patterns to
-determine the appropriate view to invoke.
-
-Django also provides a convenience function --
-``django.core.urlresolvers.reverse()`` -- which performs this process
-in the opposite direction. The ``reverse()`` function takes
-information about a view, and returns a URL which would invoke that
-view. Use of ``reverse()`` is encouraged for application developers,
-as the output of ``reverse()`` is always based on the current URL
-patterns, meaning developers do not need to change other code when
-making changes to URLs.
-
-One argument signature for ``reverse()`` is to pass a dotted Python
-path to the desired view. In this situation, Django will import the
-module indicated by that dotted path as part of generating the
-rsulting URL. If such a module has import-time side effects, those
-side effects will occur.
-
-Thus it is possible for an attacker to cause unexpected code
-execution, given the following conditions:
-
-1. One or more views are present which construct a URL based on user
-   input (commonly, a "next" parameter in a querystring indicating
-   where to redirect upon successful completion of an action).
-
-2. One or more modules known to an attacker to exist on the server's
-   Python import path, which perform code execution with side effects
-   on importing.
-
-To remedy this, ``reverse()`` will now only accept and import dotted
-paths based on the view-containing modules listed in the project's URL
-pattern configuration, so as to ensure that only modules the developer
-intended to be imported in this fashion can or will be imported.
-
---- a/django/core/urlresolvers.py
-+++ b/django/core/urlresolvers.py
-@@ -230,6 +230,10 @@
-         self._reverse_dict = {}
-         self._namespace_dict = {}
-         self._app_dict = {}
-+        # set of dotted paths to all functions and classes that are used in
-+        # urlpatterns
-+        self._callback_strs = set()
-+        self._populated = False
- 
-     def __repr__(self):
-         return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
-@@ -240,6 +244,15 @@
-         apps = {}
-         language_code = get_language()
-         for pattern in reversed(self.url_patterns):
-+            if hasattr(pattern, '_callback_str'):
-+                self._callback_strs.add(pattern._callback_str)
-+            elif hasattr(pattern, '_callback'):
-+                callback = pattern._callback
-+                if not hasattr(callback, '__name__'):
-+                    lookup_str = callback.__module__ + "." + callback.__class__.__name__
-+                else:
-+                    lookup_str = callback.__module__ + "." + callback.__name__
-+                self._callback_strs.add(lookup_str)
-             p_pattern = pattern.regex.pattern
-             if p_pattern.startswith('^'):
-                 p_pattern = p_pattern[1:]
-@@ -260,6 +273,7 @@
-                         namespaces[namespace] = (p_pattern + prefix, sub_pattern)
-                     for app_name, namespace_list in pattern.app_dict.items():
-                         apps.setdefault(app_name, []).extend(namespace_list)
-+                    self._callback_strs.update(pattern._callback_strs)
-             else:
-                 bits = normalize(p_pattern)
-                 lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
-@@ -268,6 +282,7 @@
-         self._reverse_dict[language_code] = lookups
-         self._namespace_dict[language_code] = namespaces
-         self._app_dict[language_code] = apps
-+        self._populated = True
- 
-     @property
-     def reverse_dict(self):
-@@ -356,8 +371,13 @@
-     def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
-         if args and kwargs:
-             raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
-+
-+        if not self._populated:
-+            self._populate()
-+
-         try:
--            lookup_view = get_callable(lookup_view, True)
-+            if lookup_view in self._callback_strs:
-+                lookup_view = get_callable(lookup_view, True)
-         except (ImportError, AttributeError), e:
-             raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
-         possibilities = self.reverse_dict.getlist(lookup_view)
---- /dev/null
-+++ b/tests/regressiontests/urlpatterns_reverse/nonimported_module.py
-@@ -0,0 +1,3 @@
-+def view(request):
-+    """Stub view"""
-+    pass
---- a/tests/regressiontests/urlpatterns_reverse/tests.py
-+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
-@@ -1,8 +1,11 @@
-+# -*- coding: utf-8 -*-
- """
- Unit tests for reverse URL lookups.
- """
- from __future__ import absolute_import
- 
-+import sys
-+
- from django.conf import settings
- from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
- from django.core.urlresolvers import (reverse, resolve, NoReverseMatch,
-@@ -267,6 +270,25 @@
-         self.assertEqual(res['Location'], '/foo/')
-         res = redirect('http://example.com/')
-         self.assertEqual(res['Location'], 'http://example.com/')
-+        # Assert that we can redirect using UTF-8 strings
-+        res = redirect('/æøå/abc/')
-+        self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5/abc/')
-+        # Assert that no imports are attempted when dealing with a relative path
-+        # (previously, the below would resolve in a UnicodeEncodeError from __import__ )
-+        res = redirect('/æøå.abc/')
-+        self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5.abc/')
-+        res = redirect('os.path')
-+        self.assertEqual(res['Location'], 'os.path')
-+
-+    def test_no_illegal_imports(self):
-+        # modules that are not listed in urlpatterns should not be importable
-+        redirect("urlpatterns_reverse.nonimported_module.view")
-+        self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
-+
-+    def test_reverse_by_path_nested(self):
-+        # Views that are added to urlpatterns using include() should be
-+        # reversable by doted path.
-+        self.assertEqual(reverse('regressiontests.urlpatterns_reverse.views.nested_view'), '/includes/nested_path/')
- 
-     def test_redirect_view_object(self):
-         from .views import absolute_kwargs_view
-@@ -510,4 +532,3 @@
-         self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/')
-         self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
-         self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
--
---- a/tests/regressiontests/urlpatterns_reverse/urls.py
-+++ b/tests/regressiontests/urlpatterns_reverse/urls.py
-@@ -7,6 +7,7 @@
- 
- other_patterns = patterns('',
-     url(r'non_path_include/$', empty_view, name='non_path_include'),
-+    url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'),
- )
- 
- urlpatterns = patterns('',
---- a/tests/regressiontests/urlpatterns_reverse/views.py
-+++ b/tests/regressiontests/urlpatterns_reverse/views.py
-@@ -16,6 +16,10 @@
- def defaults_view(request, arg1, arg2):
-     pass
- 
-+def nested_view(request):
-+    pass
-+
-+
- def erroneous_view(request):
-     import non_existent
- 
diff -Nru python-django-1.4.5/debian/patches/series python-django-1.4.22/debian/patches/series
--- python-django-1.4.5/debian/patches/series	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/series	2016-08-04 17:58:02.000000000 +1000
@@ -1,32 +1,8 @@
-02_disable-sources-in-sphinxdoc.diff
-03_manpage.diff
-06_use_debian_geoip_database_as_default.diff
-is_safe_url-1.4.diff
-uri-fix.diff
-ssi-tag-1.4.diff
-password-dos.diff
-password-2.5-compat.diff
-reverse-execution-1.4.x.patch
-cache-csrf-1.4.x.patch
-mysql-typecast-1.4.x.diff
-2601.patch
-is_safe_url_1_4.diff
-drop_fix_ie_for_vary_1_4.diff
-FTBFS-exception-in-servers-tests-tear-down.patch
-admin-data-leak-1.4.diff
-file-upload-1.4.diff
-remote-user-1.4.diff
-reverse-1.4.diff
-CVE-2015-0219.diff
-CVE-2015-0219-fix.diff
-CVE-2015-0220.diff
-CVE-2015-0221.diff
-CVE-2015-0221-regression-fix.diff
-CVE-2015-2317.diff
-session-1.4.x.diff
-newlines-1.4.x.diff
-session-store-1.4.x.diff
-date-leak-1.4.x.diff
-CVE-2016-2512.diff
-CVE-2016-2512-regression.diff
-CVE-2016-2513.diff
+0001-Disable-creation-of-_sources-directory-by-Sphinx.patch
+0002-Update-manual-page-to-refer-to-django-admin-instead-.patch
+0003-Use-Debian-GeoIP-database-path-as-default.patch
+0004-Use-name-that-won-t-resolve-in-tests.patch
+0005-date-leak-1.4.x.diff.patch
+0006-CVE-2016-2512-Prevented-spoofing-is_safe_url-with-ba.patch
+0007-is_safe_url-crashes-with-a-byestring-URL-on-Python-2.patch
+0008-CVE-2016-2513-Fixed-user-enumeration-timing-attack-d.patch
diff -Nru python-django-1.4.5/debian/patches/session-1.4.x.diff python-django-1.4.22/debian/patches/session-1.4.x.diff
--- python-django-1.4.5/debian/patches/session-1.4.x.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/session-1.4.x.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,159 +0,0 @@
-commit cc0777db672f6edd44a6a3f5b4684bb3a71d11d3
-Author: Carl Meyer <carl@oddbird.net>
-Date:   Wed Jun 10 15:45:20 2015 -0600
-
-    [1.4.x] Fixed #19324 -- Avoided creating a session record when loading the session.
-    
-    The session record is now only created if/when the session is modified. This
-    prevents a potential DoS via creation of many empty session records.
-    
-    This is a security fix; disclosure to follow shortly.
-
-Index: python-django-1.4.5/django/contrib/sessions/backends/cache.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/cache.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/cache.py
-@@ -25,7 +25,7 @@ class SessionStore(SessionBase):
-             session_data = None
-         if session_data is not None:
-             return session_data
--        self.create()
-+        self._session_key = None
-         return {}
- 
-     def create(self):
-@@ -45,6 +45,8 @@ class SessionStore(SessionBase):
-         raise RuntimeError("Unable to create a new session key.")
- 
-     def save(self, must_create=False):
-+        if self.session_key is None:
-+            return self.create()
-         if must_create:
-             func = self._cache.add
-         else:
-@@ -56,7 +58,7 @@ class SessionStore(SessionBase):
-             raise CreateError
- 
-     def exists(self, session_key):
--        return (KEY_PREFIX + session_key) in self._cache
-+        return session_key and (KEY_PREFIX + session_key) in self._cache
- 
-     def delete(self, session_key=None):
-         if session_key is None:
-Index: python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/cached_db.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
-@@ -30,11 +30,12 @@ class SessionStore(DBStore):
-             data = None
-         if data is None:
-             data = super(SessionStore, self).load()
--            cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
-+            if self.session_key:
-+                cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
-         return data
- 
-     def exists(self, session_key):
--        if (KEY_PREFIX + session_key) in cache:
-+        if session_key and (KEY_PREFIX + session_key) in cache:
-             return True
-         return super(SessionStore, self).exists(session_key)
- 
-Index: python-django-1.4.5/django/contrib/sessions/backends/db.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/db.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/db.py
-@@ -20,7 +20,7 @@ class SessionStore(SessionBase):
-             )
-             return self.decode(force_unicode(s.session_data))
-         except (Session.DoesNotExist, SuspiciousOperation):
--            self.create()
-+            self._session_key = None
-             return {}
- 
-     def exists(self, session_key):
-@@ -37,7 +37,6 @@ class SessionStore(SessionBase):
-                 # Key wasn't unique. Try again.
-                 continue
-             self.modified = True
--            self._session_cache = {}
-             return
- 
-     def save(self, must_create=False):
-@@ -47,6 +46,8 @@ class SessionStore(SessionBase):
-         create a *new* entry (as opposed to possibly updating an existing
-         entry).
-         """
-+        if self.session_key is None:
-+            return self.create()
-         obj = Session(
-             session_key=self._get_or_create_session_key(),
-             session_data=self.encode(self._get_session(no_load=must_create)),
-Index: python-django-1.4.5/django/contrib/sessions/backends/file.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/file.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/file.py
-@@ -56,11 +56,11 @@ class SessionStore(SessionBase):
-                     try:
-                         session_data = self.decode(file_data)
-                     except (EOFError, SuspiciousOperation):
--                        self.create()
-+                        self._session_key = None
-             finally:
-                 session_file.close()
-         except IOError:
--            self.create()
-+            self._session_key = None
-         return session_data
- 
-     def create(self):
-@@ -71,10 +71,11 @@ class SessionStore(SessionBase):
-             except CreateError:
-                 continue
-             self.modified = True
--            self._session_cache = {}
-             return
- 
-     def save(self, must_create=False):
-+        if self.session_key is None:
-+            return self.create()
-         # Get the session data now, before we start messing
-         # with the file it is stored within.
-         session_data = self._get_session(no_load=must_create)
-Index: python-django-1.4.5/django/contrib/sessions/tests.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/tests.py
-+++ python-django-1.4.5/django/contrib/sessions/tests.py
-@@ -162,6 +162,11 @@ class SessionTestsMixin(object):
-         self.assertNotEqual(self.session.session_key, prev_key)
-         self.assertEqual(self.session.items(), prev_data)
- 
-+    def test_save_doesnt_clear_data(self):
-+        self.session['a'] = 'b'
-+        self.session.save()
-+        self.assertEqual(self.session['a'], 'b')
-+
-     def test_invalid_key(self):
-         # Submitting an invalid session key (either by guessing, or if the db has
-         # removed the key) results in a new key being generated.
-@@ -256,6 +261,20 @@ class SessionTestsMixin(object):
-         encoded = self.session.encode(data)
-         self.assertEqual(self.session.decode(encoded), data)
- 
-+    def test_session_load_does_not_create_record(self):
-+        """
-+        Loading an unknown session key does not create a session record.
-+
-+        Creating session records on load is a DOS vulnerability.
-+        """
-+        if self.backend is CookieSession:
-+            raise unittest.SkipTest("Cookie backend doesn't have an external store to create records in.")
-+        session = self.backend('deadbeef')
-+        session.load()
-+
-+        self.assertFalse(session.exists(session.session_key))
-+        # provided unknown key was cycled, not reused
-+        self.assertNotEqual(session.session_key, 'deadbeef')
- 
- class DatabaseSessionTests(SessionTestsMixin, TestCase):
- 
diff -Nru python-django-1.4.5/debian/patches/session-store-1.4.x.diff python-django-1.4.22/debian/patches/session-store-1.4.x.diff
--- python-django-1.4.5/debian/patches/session-store-1.4.x.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/session-store-1.4.x.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,238 +0,0 @@
-commit 9dfb04b1709e015eff82c5b647f1f0019dc3958b
-Author: Tim Graham <timograham@gmail.com>
-Date:   Wed Aug 5 17:44:48 2015 -0400
-
-    [1.4.x] Fixed DoS possiblity in contrib.auth.views.logout()
-    
-    Refs #20936 -- When logging out/ending a session, don't create a new, empty session.
-    
-    Previously, when logging out, the existing session was overwritten by a
-    new sessionid instead of deleting the session altogether.
-    
-    This behavior added overhead by creating a new session record in
-    whichever backend was in use: db, cache, etc.
-    
-    This extra session is unnecessary at the time since no session data is
-    meant to be preserved when explicitly logging out.
-    
-    Backport of 393c0e24223c701edeb8ce7dc9d0f852f0c081ad,
-    088579638b160f3716dc81d194be70c72743593f, and
-    2dee853ed4def42b7ef1b3b472b395055543cc00 from master
-    
-    Thanks Florian Apolloner and Carl Meyer for review.
-    
-    This is a security fix.
-
-Index: python-django-1.4.5/django/contrib/sessions/backends/base.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/base.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/base.py
-@@ -128,6 +128,13 @@ class SessionBase(object):
-         self.accessed = True
-         self.modified = True
- 
-+    def is_empty(self):
-+        "Returns True when there is no session_key and the session is empty"
-+        try:
-+            return not bool(self._session_key) and not self._session_cache
-+        except AttributeError:
-+            return True
-+
-     def _get_new_session_key(self):
-         "Returns session key that isn't being used."
-         # Todo: move to 0-9a-z charset in 1.5
-@@ -230,7 +237,7 @@ class SessionBase(object):
-         """
-         self.clear()
-         self.delete()
--        self.create()
-+        self._session_key = None
- 
-     def cycle_key(self):
-         """
-Index: python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/backends/cached_db.py
-+++ python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
-@@ -58,4 +58,4 @@ class SessionStore(DBStore):
-         """
-         self.clear()
-         self.delete(self.session_key)
--        self.create()
-+        self._session_key = None
-Index: python-django-1.4.5/django/contrib/sessions/middleware.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/middleware.py
-+++ python-django-1.4.5/django/contrib/sessions/middleware.py
-@@ -14,30 +14,38 @@ class SessionMiddleware(object):
-     def process_response(self, request, response):
-         """
-         If request.session was modified, or if the configuration is to save the
--        session every time, save the changes and set a session cookie.
-+        session every time, save the changes and set a session cookie or delete
-+        the session cookie if the session has been emptied.
-         """
-         try:
-             accessed = request.session.accessed
-             modified = request.session.modified
-+            empty = request.session.is_empty()
-         except AttributeError:
-             pass
-         else:
--            if accessed:
--                patch_vary_headers(response, ('Cookie',))
--            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
--                if request.session.get_expire_at_browser_close():
--                    max_age = None
--                    expires = None
--                else:
--                    max_age = request.session.get_expiry_age()
--                    expires_time = time.time() + max_age
--                    expires = cookie_date(expires_time)
--                # Save the session data and refresh the client cookie.
--                request.session.save()
--                response.set_cookie(settings.SESSION_COOKIE_NAME,
--                        request.session.session_key, max_age=max_age,
--                        expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
--                        path=settings.SESSION_COOKIE_PATH,
--                        secure=settings.SESSION_COOKIE_SECURE or None,
--                        httponly=settings.SESSION_COOKIE_HTTPONLY or None)
-+            # First check if we need to delete this cookie.
-+            # The session should be deleted only if the session is entirely empty
-+            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
-+                response.delete_cookie(settings.SESSION_COOKIE_NAME,
-+                    domain=settings.SESSION_COOKIE_DOMAIN)
-+            else:
-+                if accessed:
-+                    patch_vary_headers(response, ('Cookie',))
-+                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
-+                    if request.session.get_expire_at_browser_close():
-+                        max_age = None
-+                        expires = None
-+                    else:
-+                        max_age = request.session.get_expiry_age()
-+                        expires_time = time.time() + max_age
-+                        expires = cookie_date(expires_time)
-+                    # Save the session data and refresh the client cookie.
-+                    request.session.save()
-+                    response.set_cookie(settings.SESSION_COOKIE_NAME,
-+                            request.session.session_key, max_age=max_age,
-+                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
-+                            path=settings.SESSION_COOKIE_PATH,
-+                            secure=settings.SESSION_COOKIE_SECURE or None,
-+                            httponly=settings.SESSION_COOKIE_HTTPONLY or None)
-         return response
-Index: python-django-1.4.5/django/contrib/sessions/tests.py
-===================================================================
---- python-django-1.4.5.orig/django/contrib/sessions/tests.py
-+++ python-django-1.4.5/django/contrib/sessions/tests.py
-@@ -150,6 +150,7 @@ class SessionTestsMixin(object):
-         self.session.flush()
-         self.assertFalse(self.session.exists(prev_key))
-         self.assertNotEqual(self.session.session_key, prev_key)
-+        self.assertIsNone(self.session.session_key)
-         self.assertTrue(self.session.modified)
-         self.assertTrue(self.session.accessed)
- 
-@@ -432,6 +433,75 @@ class SessionMiddlewareTests(unittest.Te
-         self.assertNotIn('httponly',
-                          str(response.cookies[settings.SESSION_COOKIE_NAME]))
- 
-+    def test_session_delete_on_end(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Before deleting, there has to be an existing cookie
-+        request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # Check that the cookie was deleted, not recreated.
-+        # A deleted cookie header looks like:
-+        #  Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
-+        self.assertEqual(
-+            'Set-Cookie: %s=; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
-+            'Max-Age=0; Path=/' % settings.SESSION_COOKIE_NAME,
-+            str(response.cookies[settings.SESSION_COOKIE_NAME])
-+        )
-+
-+    @override_settings(SESSION_COOKIE_DOMAIN='.example.local')
-+    def test_session_delete_on_end_with_custom_domain(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Before deleting, there has to be an existing cookie
-+        request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # Check that the cookie was deleted, not recreated.
-+        # A deleted cookie header with a custom domain looks like:
-+        #  Set-Cookie: sessionid=; Domain=.example.local;
-+        #              expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
-+        self.assertEqual(
-+            'Set-Cookie: %s=; Domain=.example.local; expires=Thu, '
-+            '01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/' % (
-+                settings.SESSION_COOKIE_NAME,
-+            ),
-+            str(response.cookies[settings.SESSION_COOKIE_NAME])
-+        )
-+
-+    def test_flush_empty_without_session_cookie_doesnt_set_cookie(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # A cookie should not be set.
-+        self.assertEqual(response.cookies, {})
-+        # The session is accessed so "Vary: Cookie" should be set.
-+        self.assertEqual(response['Vary'], 'Cookie')
-+
- 
- class CookieSessionTests(SessionTestsMixin, TestCase):
- 
-Index: python-django-1.4.5/docs/topics/http/sessions.txt
-===================================================================
---- python-django-1.4.5.orig/docs/topics/http/sessions.txt
-+++ python-django-1.4.5/docs/topics/http/sessions.txt
-@@ -197,12 +197,17 @@ You can edit it multiple times.
- 
-     .. method:: flush
- 
--      Delete the current session data from the session and regenerate the
--      session key value that is sent back to the user in the cookie. This is
--      used if you want to ensure that the previous session data can't be
--      accessed again from the user's browser (for example, the
-+      Deletes the current session data from the session and deletes the session
-+      cookie. This is used if you want to ensure that the previous session data
-+      can't be accessed again from the user's browser (for example, the
-       :func:`django.contrib.auth.logout()` function calls it).
- 
-+      .. versionchanged:: 1.4.22
-+
-+          Deletion of the session cookie was added. Previously, the behavior
-+          was to regenerate the session key value that was sent back to the
-+          user in the cookie, but this was a denial-of-service vulnerability.
-+
-     .. method:: set_test_cookie
- 
-       Sets a test cookie to determine whether the user's browser supports
diff -Nru python-django-1.4.5/debian/patches/ssi-tag-1.4.diff python-django-1.4.22/debian/patches/ssi-tag-1.4.diff
--- python-django-1.4.5/debian/patches/ssi-tag-1.4.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/ssi-tag-1.4.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,80 +0,0 @@
-Origin: upstream
-Last-Update: 2013-09-03
-Subject: directory traversal with ``ssi`` template tag
-
-Django's template language includes two methods of including and
-rendering one template inside another:
-
-1. The ``{% include %}`` tag takes a template name, and uses Django's
-   template loading mechanism (which is restricted to the directories
-   specified in the ``TEMPLATE_DIRS`` setting, as with any other
-   normal template load in Django).
-
-2. The ``{% ssi %}`` tag, which takes a file path and includes that
-   file's contents (optionally parsing and rendering it as a
-   template).
-
-Since the ``ssi`` tag is not restricted to ``TEMPLATE_DIRS``, it
-represents a security risk; the setting ``ALLOWED_INCLUDE_ROOTS`` thus
-is required, and specifies filesystem locations from which ``ssi`` may
-read files.
-
-To remedy this, the ``ssi`` tag will now use Python's
-``os.path.abspath`` to determine the absolute path of the file, and
-whether it is actually located within a directory permitted by
-``ALLOWED_INCLUDE_ROOTS``.
-
---- a/django/template/defaulttags.py
-+++ b/django/template/defaulttags.py
-@@ -1,5 +1,6 @@
- """Default tags used by the template system, available to all templates."""
- 
-+import os
- import sys
- import re
- from datetime import datetime
-@@ -309,6 +310,7 @@
-         return ''
- 
- def include_is_allowed(filepath):
-+    filepath = os.path.abspath(filepath)
-     for root in settings.ALLOWED_INCLUDE_ROOTS:
-         if filepath.startswith(root):
-             return True
---- a/tests/regressiontests/templates/tests.py
-+++ b/tests/regressiontests/templates/tests.py
-@@ -1764,3 +1764,34 @@
-             template.Template('{% include "child" only %}').render(ctx),
-             'none'
-         )
-+
-+
-+class SSITests(unittest.TestCase):
-+    def setUp(self):
-+        self.this_dir = os.path.dirname(os.path.abspath(__file__))
-+        self.ssi_dir = os.path.join(self.this_dir, "templates", "first")
-+
-+    def render_ssi(self, path):
-+        # the path must exist for the test to be reliable
-+        self.assertTrue(os.path.exists(path))
-+        return template.Template('{%% load ssi from future %%}{%% ssi "%s" %%}' % path).render(Context())
-+
-+    def test_allowed_paths(self):
-+        acceptable_path = os.path.join(self.ssi_dir, "..", "first", "test.html")
-+        with override_settings(ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)):
-+            self.assertEqual(self.render_ssi(acceptable_path), 'First template\n')
-+
-+    def test_relative_include_exploit(self):
-+        """
-+        May not bypass ALLOWED_INCLUDE_ROOTS with relative paths
-+
-+        e.g. if ALLOWED_INCLUDE_ROOTS = ("/var/www",), it should not be
-+        possible to do {% ssi "/var/www/../../etc/passwd" %}
-+        """
-+        disallowed_paths = [
-+            os.path.join(self.ssi_dir, "..", "ssi_include.html"),
-+            os.path.join(self.ssi_dir, "..", "second", "test.html"),
-+        ]
-+        with override_settings(ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)):
-+            for path in disallowed_paths:
-+                self.assertEqual(self.render_ssi(path), '')
diff -Nru python-django-1.4.5/debian/patches/uri-fix.diff python-django-1.4.22/debian/patches/uri-fix.diff
--- python-django-1.4.5/debian/patches/uri-fix.diff	2016-03-26 00:39:46.000000000 +1100
+++ python-django-1.4.22/debian/patches/uri-fix.diff	1970-01-01 10:00:00.000000000 +1000
@@ -1,15 +0,0 @@
-Author: Luke Faraone <lfaraone@debian.org>
-Last-Updated: 2013-08-13
-Forwarded: no
-Subject: Use name that won't resolve in tests.
---- a/tests/modeltests/validation/tests.py
-+++ b/tests/modeltests/validation/tests.py
-@@ -86,7 +86,7 @@
-         self.assertEqual(None, mtv.full_clean()) # This will fail if there's no Internet connection
- 
-     def test_correct_https_url_but_nonexisting(self):
--        mtv = ModelToValidate(number=10, name='Some Name', url_verify='https://www.example.com/')
-+        mtv = ModelToValidate(number=10, name='Some Name', url_verify='https://www.example.invalid/')
-         self.assertFieldFailsValidationWithMessage(mtv.full_clean, 'url_verify', [u'This URL appears to be a broken link.'])
- 
-     def test_text_greater_that_charfields_max_length_raises_erros(self):

Reply to: