--- python-django-1.1.1.orig/debian/control +++ python-django-1.1.1/debian/control @@ -0,0 +1,57 @@ +Source: python-django +Section: python +Priority: optional +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Chris Lamb +Uploaders: Debian Python Modules Team , Raphaël Hertzog +Standards-Version: 3.8.3 +Build-Depends: debhelper (>= 7.0.50), python-support, quilt (>= 0.46-7~), python (>= 2.5) | python-sqlite +Build-Depends-Indep: python-sphinx, libjs-jquery +Homepage: http://www.djangoproject.com/ +Vcs-Svn: svn://svn.debian.org/python-modules/packages/python-django/trunk/ +Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/python-django/trunk/ + +Package: python-django +Architecture: all +Depends: ${misc:Depends}, ${python:Depends} +Suggests: python-psycopg2, python-psycopg, python-mysqldb, python-flup, python-sqlite +Description: High-level Python web development framework + Django is a high-level web application framework that loosely follows the + model-view-controller design pattern. + . + Python's equivalent to Ruby on Rails, Django lets you build complex + data-driven websites quickly and easily - Django focuses on automating as much + as possible and adhering to the "Don't Repeat Yourself" (DRY) principle. + . + Django additionally emphasizes reusability and "pluggability" of components; + many generic third-party "applications" are available to enhance projects or + to simply to reduce development time even further. + . + Notable features include: + * An object-relational mapper (ORM) + * Automatic admin interface + * Elegant URL dispatcher + * Form serialization and validation system + * Templating system + * Lightweight, standalone web server for development and testing + * Internationalization support + * Testing framework and client + +Package: python-django-doc +Section: doc +Architecture: all +Depends: ${misc:Depends} +Recommends: libjs-jquery +Description: High-level Python web development framework (documentation) + Django is a high-level web application framework that loosely follows the + model-view-controller design pattern. + . + Python's equivalent to Ruby on Rails, Django lets you build complex + data-driven websites quickly and easily - Django focuses on automating as much + as possible and adhering to the "Don't Repeat Yourself" (DRY) principle. + . + Django additionally emphasizes reusability and "pluggability" of components; + many generic third-party "applications" are available to enhance projects or + to simply to reduce development time even further. + . + This package contains the HTML documentation and example projects. --- python-django-1.1.1.orig/debian/python-django-doc.docs +++ python-django-1.1.1/debian/python-django-doc.docs @@ -0,0 +1 @@ +docs.debian/_build/html --- python-django-1.1.1.orig/debian/pycompat +++ python-django-1.1.1/debian/pycompat @@ -0,0 +1 @@ +2 --- python-django-1.1.1.orig/debian/python-django.install +++ python-django-1.1.1/debian/python-django.install @@ -0,0 +1,2 @@ +usr/ +extras/django_bash_completion etc/bash_completion.d/ --- python-django-1.1.1.orig/debian/python-django-doc.examples +++ python-django-1.1.1/debian/python-django-doc.examples @@ -0,0 +1 @@ +examples/* --- python-django-1.1.1.orig/debian/changelog +++ python-django-1.1.1/debian/changelog @@ -0,0 +1,732 @@ +python-django (1.1.1-2ubuntu1.14) lucid-security; urgency=medium + + * SECURITY UPDATE: WSGI header spoofing via underscore/dash conflation + - debian/patches/CVE-2015-0219.patch: strip headers with underscores in + django/core/servers/basehttp.py, added test to + tests/regressiontests/servers/tests.py. + - CVE-2015-0219 + * SECURITY UPDATE: Mitigated possible XSS attack via user-supplied + redirect URLs + - debian/patches/CVE-2015-0220.patch: filter url in + django/utils/http.py. + - CVE-2015-0220 + * SECURITY UPDATE: Denial-of-service attack against + django.views.static.serve + - debian/patches/CVE-2015-0221.patch: limit large files in + django/views/static.py, added test to + tests/regressiontests/views/media/long-line.txt, + tests/regressiontests/views/tests/static.py. + - CVE-2015-0221 + + -- Marc Deslauriers Tue, 13 Jan 2015 08:14:45 -0500 + +python-django (1.1.1-2ubuntu1.13) lucid-security; urgency=medium + + * SECURITY UPDATE: incorrect url validation in core.urlresolvers.reverse + - debian/patches/CVE-2014-0480.patch: prevent reverse() from generating + URLs pointing to other hosts in django/core/urlresolvers.py, added + tests to tests/regressiontests/urlpatterns_reverse/{tests,urls}.py. + - CVE-2014-0480 + * SECURITY UPDATE: denial of service via file upload handling + - debian/patches/CVE-2014-0481.patch: remove O(n) algorithm in + django/core/files/storage.py, updated docs in + docs/howto/custom-file-storage.txt, added tests to + tests/modeltests/files/models.py, + tests/regressiontests/file_storage/tests.py, backport + get_random_string() to django/utils/crypto.py. + - CVE-2014-0481 + * SECURITY UPDATE: web session hijack via REMOTE_USER header + - debian/patches/CVE-2014-0482.patch: modified RemoteUserMiddleware to + logout on REMOTE_USE change in django/contrib/auth/middleware.py, + added test to django/contrib/auth/tests/remote_user.py. + - CVE-2014-0482 + * SECURITY UPDATE: data leak in contrib.admin via query string manipulation + - debian/patches/CVE-2014-0483.patch: validate to_field in + django/contrib/admin/{options,exceptions}.py, + django/contrib/admin/views/main.py, added tests to + tests/regressiontests/admin_views/tests.py. + - debian/patches/CVE-2014-0483-bug23329.patch: regression fix in + django/contrib/admin/options.py, added tests to + tests/regressiontests/admin_views/{models,tests}.py. + - debian/patches/CVE-2014-0483-bug23431.patch: regression fix in + django/contrib/admin/options.py, added tests to + tests/regressiontests/admin_views/{models,tests}.py. + - CVE-2014-0483 + * debian/patches/fix_invalid_link_ftbfs.patch: remove test causing FTBFS. + + -- Marc Deslauriers Wed, 10 Sep 2014 13:07:32 -0400 + +python-django (1.1.1-2ubuntu1.12) lucid-security; urgency=medium + + * SECURITY UPDATE: cache coherency problems in old Internet Explorer + compatibility functions lead to loss of privacy and cache poisoning + attacks. (LP: #1317663) + - debian/patches/drop_fix_ie_for_vary_1_4.diff: remove fix_IE_for_vary() + and fix_IE_for_attach() functions so Cache-Control and Vary headers are + no longer modified. This may introduce some regressions for IE 6 and IE 7 + users. Patch from upstream. + - CVE-2014-1418 + + -- Seth Arnold Wed, 14 May 2014 11:24:15 -0700 + +python-django (1.1.1-2ubuntu1.11) lucid-security; urgency=medium + + * SECURITY REGRESSION: security fix regression when a view is a partial + (LP: #1311433) + - debian/patches/CVE-2014-0472-regression.patch: create the lookup_str + from the original function whenever a partial is provided as an + argument to a url pattern in django/core/urlresolvers.py, + added tests to tests/regressiontests/urlpatterns_reverse/urls.py, + tests/regressiontests/urlpatterns_reverse/views.py. + - CVE-2014-0472 + + -- Marc Deslauriers Tue, 22 Apr 2014 23:20:22 -0400 + +python-django (1.1.1-2ubuntu1.10) lucid-security; urgency=medium + + * SECURITY UPDATE: unexpected code execution using reverse() + (LP: #1309779) + - debian/patches/CVE-2014-0472.patch: added filtering to + django/core/urlresolvers.py, added tests to + tests/regressiontests/urlpatterns_reverse/nonimported_module.py, + tests/regressiontests/urlpatterns_reverse/tests.py, + tests/regressiontests/urlpatterns_reverse/urls.py, + tests/regressiontests/urlpatterns_reverse/views.py. + - CVE-2014-0472 + * SECURITY UPDATE: caching of anonymous pages could reveal CSRF token + (LP: #1309782) + - debian/patches/CVE-2014-0473.patch: don't cache responses with a + cookie in django/middleware/cache.py, backport has_vary_header() to + django/utils/cache.py. + - CVE-2014-0473 + * SECURITY UPDATE: MySQL typecasting issue (LP: #1309784) + - debian/patches/CVE-2014-0474.patch: convert arguments to correct + type in django/db/models/fields/__init__.py, added tests to + tests/regressiontests/model_fields/tests.py. + - CVE-2014-0474 + + -- Marc Deslauriers Sat, 19 Apr 2014 11:21:00 -0400 + +python-django (1.1.1-2ubuntu1.9) lucid-security; urgency=low + + * SECURITY UPDATE: denial of service via long passwords (LP: #1225784) + - debian/patches/CVE-2013-1443.patch: enforce a maximum password length + in django/contrib/auth/forms.py, django/contrib/auth/models.py, + django/contrib/auth/tests/basic.py. + - CVE-2013-1443 + * SECURITY UPDATE: directory traversal with ssi template tag + - debian/patches/CVE-2013-4315.patch: properly check absolute path in + django/template/defaulttags.py, + tests/regressiontests/templates/tests.py, + tests/regressiontests/templates/templates/*. + - CVE-2013-4315 + * SECURITY UPDATE: possible XSS via is_safe_url + - debian/patches/security-is_safe_url.patch: properly reject URLs which + specify a scheme other then HTTP or HTTPS. + - https://www.djangoproject.com/weblog/2013/aug/13/security-releases-issued/ + - No CVE number + + -- Marc Deslauriers Fri, 20 Sep 2013 09:33:23 -0400 + +python-django (1.1.1-2ubuntu1.8) lucid-security; urgency=low + + * SECURITY UPDATE: host header poisoning (LP: #1089337) + - debian/patches/fix_get_host.patch: tighten host header validation in + django/http/__init__.py, add tests to + tests/regressiontests/requests/tests.py. + - https://www.djangoproject.com/weblog/2012/dec/10/security/ + - No CVE number + * SECURITY UPDATE: redirect poisoning (LP: #1089337) + - debian/patches/fix_redirect_poisoning.patch: tighten validation in + django/contrib/auth/views.py, + django/contrib/comments/views/comments.py, + django/contrib/comments/views/moderation.py, + django/contrib/comments/views/utils.py, django/utils/http.py, + django/views/i18n.py, add tests to + tests/regressiontests/comment_tests/tests/comment_view_tests.py, + tests/regressiontests/comment_tests/tests/moderation_view_tests.py, + tests/regressiontests/views/tests/i18n.py. + - https://www.djangoproject.com/weblog/2012/dec/10/security/ + - No CVE number + * SECURITY UPDATE: host header poisoning (LP: #1130445) + - debian/patches/add_allowed_hosts.patch: add new ALLOWED_HOSTS setting + to django/conf/global_settings.py, + django/conf/project_template/settings.py, + django/http/__init__.py, django/test/utils.py, add docs to + docs/ref/settings.txt, add tests to + tests/regressiontests/requests/tests.py, backport required function + to django/utils/functional.py. + - https://www.djangoproject.com/weblog/2013/feb/19/security/ + - No CVE number + * SECURITY UPDATE: XML attacks (LP: #1130445) + - debian/patches/CVE-2013-166x.patch: forbid DTDs, entity expansion, + and external entities/DTDs in + django/core/serializers/xml_serializer.py, add tests to + tests/regressiontests/serializers_regress/tests.py. + - https://www.djangoproject.com/weblog/2013/feb/19/security/ + - CVE-2013-1664 + - CVE-2013-1665 + * SECURITY UPDATE: Data leakage via admin history log (LP: #1130445) + - debian/patches/CVE-2013-0305.patch: add permission checks to history + view in django/contrib/admin/options.py, add tests to + tests/regressiontests/admin_views/tests.py. + - https://www.djangoproject.com/weblog/2013/feb/19/security/ + - CVE-2013-0305 + * SECURITY UPDATE: Formset denial-of-service (LP: #1130445) + - debian/patches/CVE-2013-0306.patch: limit maximum number of forms in + django/forms/formsets.py, add docs to docs/topics/forms/formsets.txt. + - https://www.djangoproject.com/weblog/2013/feb/19/security/ + - CVE-2013-0306 + + -- Marc Deslauriers Mon, 04 Mar 2013 14:08:31 -0500 + +python-django (1.1.1-2ubuntu1.7) lucid-security; urgency=low + + * Add additional tests for CVE-2012-4520 + - debian/patches/CVE-2012-4520-additional-tests.diff: add various poisoned + host header test material + * Don't fail self-tests if MANAGERS or ADMINS is defined in settings.py + - debian/patches/lp1080204.diff: Isolate poisoned_http_host tests from 500 + - https://code.djangoproject.com/ticket/19172 + - LP: #1080204 + + -- Jamie Strandboge Mon, 19 Nov 2012 15:20:21 -0600 + +python-django (1.1.1-2ubuntu1.6) lucid-security; urgency=low + + * SECURITY UPDATE: fix Host header poisoning + - debian/patches/CVE-2012-4520.diff: adjust HttpRequest.get_host() to + raise django.core.exceptions.SuspiciousOperation if Host headers contain + potentially dangerous content. + - CVE-2012-4520 + - LP: #1068486 + + -- Jamie Strandboge Fri, 09 Nov 2012 16:16:26 -0600 + +python-django (1.1.1-2ubuntu1.5) lucid-security; urgency=low + + * SECURITY UPDATE: Cross-site scripting in authentication views + (LP: #1031733) + - debian/patches/16_fix_cross_site_scripting_in_authentication.diff: + fix unsafe redirects indjango/http/__init__.py. Patch backported from + Debian Squeeze and fixed for python 2.4 compatibility. + - CVE-2012-3442 + * SECURITY UPDATE: Denial-of-service in image validation (LP: #1031733) + - debian/patches/17_fix_dos_in_image_validation.diff: call verify() + immediately after the constructor in django/forms/fields.py. + - CVE-2012-3443 + * SECURITY UPDATE: Denial-of-service via get_image_dimensions() + (LP: #1031733) + - debian/patches/18_fix_dos_via_get_image_dimensions.diff: don't limit + chunk size in django/core/files/images.py. + - CVE-2012-3444 + + -- Marc Deslauriers Thu, 06 Sep 2012 09:56:37 -0400 + +python-django (1.1.1-2ubuntu1.4) lucid-security; urgency=low + + * SECURITY UPDATE: session manipulation when using django.contrib.sessions + with memory-based sessions and caching + - debian/patches/CVE-2011-4136.patch: use namespace of cache to store keys + for session instead of root namespace + - CVE-2011-4136 + * SECURITY UPDATE: potential denial of service and information disclosure in + URLField + - debian/patches/CVE-2011-4137+4138.patch: set verify_exists to False by + default and use a timeout if available. + - CVE-2011-4137, CVE-2011-4138 + * SECURITY UPDATE: potential cache-poisoning via crafted Host header + - debian/patches/CVE-2011-4139.patch: ignore X-Forwarded-Host header by + default when constructing full URLs + - CVE-2011-4139 + * More information on these issues can be found at: + https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued/ + + -- Jamie Strandboge Wed, 07 Dec 2011 16:02:57 -0600 + +python-django (1.1.1-2ubuntu1.3) lucid-security; urgency=low + + * SECURITY UPDATE: flaw in CSRF handling (LP: #719031) + - debian/patches/10_CVE-2011-0696.diff: apply full CSRF validation to all + requests, regardless of apparent AJAX origin. This is technically + backwards-incompatible, but the security risks have been judged to + outweigh the compatibility concerns in this case. See the Django project + notes for more information: + http://www.djangoproject.com/weblog/2011/feb/08/security/ + - CVE-2011-0696 + * SECURITY UPDATE: potential XSS in file field rendering + - debian/patches/11_CVE-2011-0697.diff: properly escape URL in + django/contrib/admin/widgets.py + - CVE-2011-0697 + + -- Jamie Strandboge Tue, 15 Feb 2011 17:11:08 -0600 + +python-django (1.1.1-2ubuntu1.2) lucid-security; urgency=low + + * SECURITY UPDATE: information leak in admin interface + - debian/patches/08_security_admin_infoleak.diff: validate querystring + lookup arguments either specify only fields on the model being viewed, + or cross relations which have been explicitly whitelisted. + - CVE-2010-4534 + * SECURITY UPDATE: + - debian/patches/09_security_pasword_reset_dos.diff: adjust + base36_to_int() function in django.utils.http will now validate the + length of its input; on input longer than 13 digits (sufficient to + base36-encode any 64-bit integer), it will now raise ValueError. + Additionally, the default URL patterns for django.contrib.auth will now + enforce a maximum length on the relevant parameters. + - CVE-2010-4535 + + -- Jamie Strandboge Mon, 03 Jan 2011 11:31:57 -0600 + +python-django (1.1.1-2ubuntu1) lucid; urgency=low + + * Fix django test client cookie handling (LP: #513719) + + -- Elliot Murphy Fri, 29 Jan 2010 13:01:27 -0500 + +python-django (1.1.1-2) unstable; urgency=low + + * Remove embedded "decimal" code copy and use system version instead. The + "doctest" code copy cannot be removed as parts of Django depend on modified + behaviour. (Closes: #555419) + * Fix FTBFS in November by applying patch from upstream bug #12125. + (Closes: #555931) + * Fix FTBFS under Python 2.6.3 by applying patch from upstream bug #11993. + (Closes: #555969) + + -- Chris Lamb Tue, 01 Dec 2009 23:46:22 +0000 + +python-django (1.1.1-1) unstable; urgency=high + + * New upstream security release - fixes pathological regular expression + backtracking performance in URL and email fields which can be used as part + of a denial of service attack. + * Set Maintainer: to myself with thanks to Brett Parker. + * Bump versioned build dependency on quilt to help backporters. + (Closes: #547955) + + -- Chris Lamb Sat, 10 Oct 2009 10:17:52 +0100 + +python-django (1.1-4) unstable; urgency=low + + * Sourceful upload to drop dependency on Python 2.4. + + -- Chris Lamb Mon, 24 Aug 2009 08:16:11 +0100 + +python-django (1.1-3) unstable; urgency=low + + * Disable regression tests that require an internet connection. Patch by + Krzysztof Klimonda . (Closes: #542996) + * Bump Standards-Version to 3.8.3. + + -- Chris Lamb Sun, 23 Aug 2009 18:13:18 +0100 + +python-django (1.1-2) unstable; urgency=low + + * Run testsuite on build. + * Use "--with quilt" over specifying $(QUILT_STAMPFN)/unpatch dependencies. + * Override clean target correctly. + + -- Chris Lamb Fri, 14 Aug 2009 08:06:29 +0100 + +python-django (1.1-1) unstable; urgency=low + + * New upstream release. + * Merge from experimental: + - Ship FastCGI initscript and /etc/default file in python-django's examples + directory (Closes: #538863) + - Drop "05_10539-sphinx06-compatibility.diff"; it has been applied + upstream. + - Bump Standards-Version to 3.8.2. + + -- Chris Lamb Wed, 29 Jul 2009 11:26:28 +0200 + +python-django (1.0.2-7) unstable; urgency=low + + * Fix compatibility with Python 2.6 and Python transitions in general. + Thanks to Krzysztof Klimonda . + + -- Chris Lamb Sat, 16 May 2009 00:09:47 +0100 + +python-django (1.0.2-6) unstable; urgency=low + + * Backport patch from to fix + FTBFS when using python-sphinx >= 0.6. (Closes: #527492) + + -- Chris Lamb Sun, 10 May 2009 22:11:09 +0100 + +python-django (1.0.2-5) unstable; urgency=low + + * Fix issue where newly created projects do not have their manage.py file + executable. + + -- Chris Lamb Thu, 26 Mar 2009 23:42:14 +0000 + +python-django (1.0.2-4) unstable; urgency=low + + * Programatically replace most references to "django-admin.py" with + "django-admin" in the generated documentation. (Closes: #519937) + * Bump Standards-Version to 3.8.1; no changes. + + -- Chris Lamb Tue, 24 Mar 2009 00:50:26 +0000 + +python-django (1.0.2-3) unstable; urgency=low + + * Split documentation into a separate python-django-doc package due to size + (approximately 6Mb). + + -- Chris Lamb Tue, 10 Mar 2009 21:13:57 +0000 + +python-django (1.0.2-2) unstable; urgency=low + + * Don't rely on the internal layout of python-support. (Closes: #517052) + * Move to debhelper-based packaging for operational clarity: + - Remove bashisms from binary-post-install. + - Use quilt instead of simple-patchsys.mk and adjust existing patches so + that we can apply with -p1 for the "quilt" source package type. + * Adjust Build-Depends: + - Bump debhelper requirement 7.0.50 for override_* feature. + - Drop cdbs, python-dev and python-setuptools requirement. + - Just Build-Depend on `python', not `python-dev'. + - Drop versions on Build-Depends where they are satisfied in current + oldstable (ie. etch). + * debian/control: + - Add python-sqlite to Suggests. + - Remove repeated 'Priority' line in binary package stanza. + - Update crufty long and short descriptions. + - Add ${misc:Depends} in binary stanza for debhelper-using package. + + -- Chris Lamb Sun, 08 Mar 2009 06:01:59 +0000 + +python-django (1.0.2-1) unstable; urgency=low + + [ Chris Lamb ] + * New upstream bugfix release. Closes: #505783 + * Add myself to Uploaders with ACK from Brett. + + [ David Spreen ] + * Remove python-pysqlite2 from Recommends because Python 2.5 includes + sqlite library used by Django. Closes: 497886 + + [ Sandro Tosi ] + * debian/control + - switch Vcs-Browser field to viewsvn + + -- Chris Lamb Wed, 19 Nov 2008 21:31:00 +0000 + +python-django (1.0-1) unstable; urgency=low + + [ David Spreen ] + * New _stable_ upstream release. + + [ Raphael Hertzog ] + * This version fixes the latest security issue: + http://www.djangoproject.com/weblog/2008/sep/02/security/ + Closes: #497765 + * Don't include source files of documentation in the binary package, + keep only the HTML version. + * Updated README.Debian with information about the switch from 0.96 to + 1.0. + * Remove execute right on /etc/bash_completion.d/django_bash_completion + * Add debian/patches/04_hyphen-manpage.diff to fix a lintian message + (hyphen-used-as-minus-sign usr/share/man/man1/django-admin.1.gz:156). + * Don't compress javascript files. + * Add libjs-jquery to Recommends since it's used by the HTML + documentation. + + -- Raphael Hertzog Thu, 04 Sep 2008 08:33:32 +0200 + +python-django (1.0~beta2+ds-1) unstable; urgency=low + + * Bumping up upstream version to push sources into unstable. + (Thanks to Raphael Hertzog). + + -- David Spreen Sat, 30 Aug 2008 20:56:09 -0700 + +python-django (1.0~beta2-3) unstable; urgency=low + + [ David Spreen ] + * Updated the copyright information to include copyright and + licenses for individual contributions. + * Added the documentation to the main python-django package: + * debian/python-django.install + - Added installation of html documentation. + * debian/python-django.doc-base + - Added. + * debian/control + - Added Build-Depends-Indep on python-sphinx and libjs-jquery. + * debian/rules + - Readded code to build documentation. + - Readded code to link to libjs-jquery. + * debian/NEWS + - Fixed format. + - Added more comprehensive list of changes and references to + local documentation as well as the wiki pages for + backwards-incompatible changes. + * debian/python-django.docs + - Removed docs/*.txt since those are templates for the + generated docs now included with doc-base. + + -- David Spreen Fri, 29 Aug 2008 09:20:45 -0700 + +python-django (1.0~beta2-2) unstable; urgency=low + + [ David Spreen ] + * Removed all -doc related files temporarily to push beta2 into + unstable for extensive testing. The -doc package will be + readded once this package is in unstable as recommended in + http://lists.debian.org/debian-release/2008/08/msg01475.html. + * debian/python-django-doc.install + - Removed. + * debian/python-django-doc.doc-base + - Removed. + * debian/python-django-doc.examples + - Moved to python-django.examples. + * debian/rules + - Removed python-doc related build and post-installation. + * debian/control + - Removed binary package python-django-doc. + - Removed Build-Depends-Indep on python-sphinx and libjs-jquery. + * debian/python-django.install: + - Removed multiple package related issues. + + -- David Spreen Thu, 28 Aug 2008 20:15:21 -0700 + +python-django (1.0~beta2-1) experimental; urgency=low + + [ David Spreen ] + * The `hooray for the documentation' release! + * New upstream beta release. + * debian/control + - Updated standards version. + - Added python-sphinx and libjs-jquery. + - Added python-django-doc package depending on libjs-jquery. + * debian/docs + - Moved to debian/python-django.docs. + * debian/install + - Moved to debian/python-django.install. + * debian/manpages + - Moved to debian/python-django.manpages. + * debian/examples + - Moved to debian/python-django-doc.examples + * debian/README.Debian + - Moved to debian/python-django.README.Debian + * debian/python-django-doc.doc-base: + - Added doc-base file for the documentation. + * debian/python-django-doc.install: + - Added install file for sphinx generated documentation. + * debian/rules: + - Added code to generate documentation with sphinx and + replace convenience file of jquery.js with the respective + symlink to libjs-jquery. + + -- David Spreen Thu, 28 Aug 2008 10:22:29 -0700 + +python-django (1.0~beta1-1) experimental; urgency=low + + [ David Spreen ] + * New upstream beta release. Closes: #492956 + * debian/control: Added myself to Uploaders field. + * debian/watch: Added mangling for filename and version. Old watch file would + name the download 'tarball'. Also added mangling to handle alpha and beta + versioning. + * Drop debian/patches/01_add_shebang.diff as this has been fixed upstream. + * Drop debian/patches/02_bash_completion.diff as this has been committed + upstream http://code.djangoproject.com/ticket/7268. + * debian/control: Added python-flup to the Suggest field. Closes: #488123 + * debian/patches/03_manpage.diff: Adapted patch to new upstream version. + + [ Jan Dittberner ] + * add debian/watch file. + + -- David Spreen Fri, 15 Aug 2008 16:05:07 -0700 + +python-django (0.97~svn7534-1) experimental; urgency=low + + * New upstream snapshot. Closes: #409565, #481051 + - Include an XSS security fix (CVE-2008-2302). Closes: #481164 + * Drop debian/patches/04_pg_version_fix.diff as another fix + has been committed upstream (see http://code.djangoproject.com/ticket/6433 + and http://code.djangoproject.com/changeset/7415). + * Add some headers to the remaining patches. + + -- Raphael Hertzog Mon, 19 May 2008 23:41:50 +0200 + +python-django (0.97~svn7189-1) experimental; urgency=low + + * New upstream snapshot including bash completion fix + Closes: #450913 + + -- Brett Parker Sun, 02 Mar 2008 12:59:03 +0000 + +python-django (0.97~svn7047-2) experimental; urgency=low + + [ Brett Parker ] + * Patch for postgresql version issue with 8.3 beta/rc releases + Closes: #462058 + + [ Raphael Hertzog ] + * Updated Standards-Version to 3.7.3. + * Adjusted build-dependency on python-setuptools to strip the -1 part. + + -- Brett Parker Wed, 6 Feb 2008 15:15:37 +0000 + +python-django (0.97~svn7047-1) experimental; urgency=low + + * New upstream snapshot (rev 7047) + - tarball prepared by Gabriel Falcão Gonçalves de Moura + + + -- Gustavo Noronha Silva Tue, 29 Jan 2008 10:54:47 -0200 + +python-django (0.97~svn6996-1) experimental; urgency=low + + * New upstream snapshot + * debian/control: + - added myself to Uploaders + + -- Gustavo Noronha Silva Sat, 05 Jan 2008 20:53:23 -0200 + +python-django (0.97~svn6668-2) UNRELEASED; urgency=low + + [ Raphael Hertzog ] + * Install examples with dh_installexamples instead of dh_installdocs + (change done by Ubuntu) as empty files are kept. + + [ Sandro Tosi ] + * debian/control + - uniforming Vcs-Browser field + + -- Raphael Hertzog Mon, 17 Dec 2007 09:09:16 +0100 + +python-django (0.97~svn6668-1) experimental; urgency=low + + * New SVN snapshot (rev 6668) + - Auth system delegations + - Apps can now have thier own management commands + - Fix for CVE-2007-5712 remote denial of service + Closes: #448838 + * Fix missing upstream info in changelog + Closes: #450659 + + -- Brett Parker Sun, 11 Nov 2007 10:15:55 +0000 + +python-django (0.96+svn6373-1) experimental; urgency=low + + [ Raphael Hertzog ] + * New SVN snapshot (rev 6373, a few days after the last Django sprint). + * Note: The version 0.96+svn6034-1 never got uploaded. + * Rename XS-Vcs* fields to Vcs-* since they are now supported by dpkg. + + [ Piotr Ożarowski ] + * XS-Vcs-Browser and Homepage fields added + + -- Raphael Hertzog Thu, 04 Oct 2007 14:59:01 +0200 + +python-django (0.96+svn6034-1) experimental; urgency=low + + [ Brett Parker] + * New SVN snapshot (rev 6034). + * validate and runserver commands now display the number of errors + (returning back to previous functionality). + * Small documentation fixes + * assertRedirects handling for paths with get data + * start{project,app} no make sure files created are writable + * Add man page for django-admin to the debian package + + -- Brett Parker Sat, 8 Sep 2007 10:37:00 +0100 + +python-django (0.96+svn6020-1) experimental; urgency=low + + * New SVN snapshot (rev 6020). + + -- Raphael Hertzog Sun, 26 Aug 2007 18:16:08 +0200 + +python-django (0.96+svn5779-1) experimental; urgency=low + + * SVN snapshot (rev 5779) packaged to experimental as many interesting + Django applications rely on newer unreleased features. + + -- Raphael Hertzog Tue, 31 Jul 2007 13:40:18 +0200 + +python-django (0.96-1) unstable; urgency=low + + [ Brett Parker ] + * New upstream release - introduces some backwards incompatible changes, see + README.Debian or the backwards incompatible changes page at + http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges + * Add documentation from upstream to /usr/share/doc/python-django + Closes: #411249 + * Install the bash completion file from extras in to + /etc/bash_completion.d/django_bash_completion + Closes: #414399 + * Egg support dropped as it's been dropped by upstream. + + -- Brett Parker Sun, 25 Mar 2007 19:18:39 +0100 + +python-django (0.95.1-1) unstable; urgency=low + + [ Brett Parker ] + * New upstream minor release for security bugs: + - http://www.djangoproject.com/weblog/2007/jan/21/0951/ + - Fixes a small security vulnerability in the script Django's + internationalization system uses to compile translation files + (changeset 4360 in the "0.95-bugfixes" branch). + - fix for a bug in Django's authentication middleware which could cause + apparent "caching" of a logged-in user (changeset 4361). + - patch which disables debugging mode in the flup FastCGI package Django + uses to launch its FastCGI server, which prevents tracebacks from + bubbling up during production use (changeset 4363). + Closes: #407786, #407607 + * Sets Recommends to python-psycopg and moves other database engines to + the Suggests field. + + [ Raphael Hertzog ] + * Use python-pysqlite2 as default database engine in Recommends. Others are + in Suggests. Closes: #403761 + * Add python-psycopg2 in Suggests. Closes: #407489 + + -- Raphael Hertzog Sun, 21 Jan 2007 17:45:50 +0100 + +python-django (0.95-3) unstable; urgency=low + + * Integrate 2 upstream changesets: + - http://code.djangoproject.com/changeset/3754 as + debian/patches/04_sec_fix_auth.diff + Fixes a possible case of mis-authentication due to bad caching. + Closes: #407521 + - http://code.djangoproject.com/changeset/3592 as + debian/patches/03_sec_fix_compile-messages.diff + Fixes an (unlikely) arbitrary command execution if the user is blindly + running compile-messages.py on a untrusted set of *.po files. + Closes: #407519 + + -- Raphael Hertzog Sat, 16 Dec 2006 15:13:29 +0100 + +python-django (0.95-2) unstable; urgency=low + + [ Piotr Ozarowski ] + * Added XS-Vcs-Svn field + + [ Brett Parker ] + * Made manage.py get a shebang with the version of python + used when running django-admin (closes: #401616) + * Created a convenience /usr/lib/python-django/bin symlink. + + [ Raphael Hertzog ] + * Adapted Brett's work to better fit my views of the packaging. + + -- Raphael Hertzog Sat, 16 Dec 2006 11:03:20 +0100 + +python-django (0.95-1) unstable; urgency=low + + [ Brett Parker ] + * 0.95 release - initial packaging + + [ Raphael Hertzog ] + * Fix recommends: s/python-sqlite/python-pysqlite2/ + * Add debian/pyversions to ensure that we have at least python 2.3 (and to + work around bug #391689 of python-support). + + -- Raphael Hertzog Mon, 9 Oct 2006 12:10:27 +0200 --- python-django-1.1.1.orig/debian/compat +++ python-django-1.1.1/debian/compat @@ -0,0 +1 @@ +7 --- python-django-1.1.1.orig/debian/python-django.README.Debian +++ python-django-1.1.1/debian/python-django.README.Debian @@ -0,0 +1,178 @@ +0.96 -> 1.0 +=========== + +Django 1.0 has a number of backwards-incompatible changes from Django +0.96. If you have apps written against Django 0.96 that you need to port, +see the detailed porting guide: +/usr/share/doc/python-django/html/releases/1.0-porting-guide.html +or +http://docs.djangoproject.com/en/dev/releases/1.0-porting-guide/ + +You can also find a complete list of of backwards incompatible changes +here: +http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges + +0.95 -> 0.96 +============ + +Information here has been gathered from: + http://www.djangoproject.com/documentation/release_notes_0.96/ +and + http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges + +Backwards Incompatible Changes +------------------------------ + + Database constraint names changed + ================================= + + As of [3512], the format of the constraint names Django generates for + foreign key references changed slightly. These names are only used + sometimes, when it is not possible to put the reference directly on the + affected column, so this is not always visible. + + The effect of this change is that manage.py reset app_name and similar + commands may generate SQL with invalid constraint names and thus generate + an error when run against the database (the database server will complain + about the constraint not existing). To fix this, you will need to tweak the + output of manage.py sqlreset app_name to match the correct constraint names + and pass the results to the database server manually. + + Backslash escaping changed + ========================== + + As of [3552], the Django database API now escapes backslashes given as + query parameters. If you have any database API code that match backslashes, + and it was working before (despite the broken escaping), you'll have to + change your code to "unescape" the slashes one level. + + For example, this used to work: + + # Code that matches a single backslash + MyModel.objects.filter(text__contains='\\\\') + + But it should be rewritten as this: + + # Code that matches a single backslash + MyModel.objects.filter(text__contains='\\') + + Removed ENABLE_PSYCO setting + ============================ + + As of [3877], the ENABLE_PSYCO setting no longer exists. If your settings + file includes ENABLE_PSYCO, nothing will break per se, but it just won't do + anything. If you want to use Psyco with Django, write some custom + middleware that activates Psyco. + + Changed Admin.manager option to more flexible hook + ================================================== + + As of [4342], the manager option to class Admin no longer exists. This + option was undocumented, but we're mentioning the change here in case you + used it. In favor of this option, class Admin may now define one of these + methods: + + * queryset() + * queryset_add() + * queryset_change() + + These give you much more flexibility. + + Note that this change was made to the NewformsAdminBranch. (We initially + called the new method change_list_queryset, but this was changed in [4584] + to be more flexible.) The change will not be made to trunk until that + branch is merged to trunk. + + Changed prepopulate_from to be defined in the Admin class, + not database field classes ¶ + ========================================================== + + As of [4446], the prepopulate_from option to database fields no + longer exists. It's been discontinued in favor of the new + prepopulated_fields option on class Admin. The new + prepopulated_fields option, if given, should be a dictionary + mapping field names to lists/tuples of field names. Here's an + example comparing old syntax and new syntax: + + # OLD: + class MyModel(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name')) + + class Admin: + pass + + # NEW: + class MyModel(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + slug = models.CharField(maxlength=60) + + class Admin: + prepopulated_fields = {'slug': ('first_name', 'last_name')} + + Moved admin doc views into django.contrib.admindocs + ==================================================== + + As of [4585], the documentation views for the Django admin site were moved + into a new package, django.contrib.admindocs. + + The admin docs, which aren't documented very well, were located at docs/ in + the admin site. They're also linked-to by the "Documentation" link in the + upper right of default admin templates. + + Because we've moved the doc views, you now have to activate admin docs + explicitly. Do this by adding the following line to your URLconf: + + (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + Note that this change was made to the NewformsAdminBranch. The change will + not be made to trunk until that branch is merged to trunk. + + Enforcing MySQLdb version + ========================= + + As of [4724], Django will raise an error if you try to use the MySQL + backend with a MySQLdb ( MySQL python module) version earlier than 1.2.1p2. + There were significant, production-related bugs in earlier versions, so we + have upgraded the minimum requirement. + + In [4767], a mysql_old backend was added, that is identical to the original + mysql backend prior to the change in [4724]. This backend can be used if + upgrading the MySQLdb module is not immediately possible, however, it is + deprecated and no further development will be done on it. + +New Features +------------ + + New forms library + ================= + + The new forms library has been merged from the new forms branch in to + django.newforms in 0.96, the next revision will replace django.forms with + django.newforms, the current forms library is already copied to + django.oldforms to make the transition easier - it's advised to either + upgrade your forms code to the newforms library or to change your imports + as follows: + + from django import forms + becomes + from django import oldforms as forms + + URLconf improvements + ==================== + + It's now possible to use imported views in the urlconf rather than a string + representing the view to call. + + Test framework + ============== + + Now possible to write tests based on doctest and unittest + + Admin area changes + ================== + + Changes to the user adding and updating views so that you don't need to + worry about hashed passwords. --- python-django-1.1.1.orig/debian/python-django.manpages +++ python-django-1.1.1/debian/python-django.manpages @@ -0,0 +1 @@ +docs/man/django-admin.1 --- python-django-1.1.1.orig/debian/python-django.docs +++ python-django-1.1.1/debian/python-django.docs @@ -0,0 +1,2 @@ +README +AUTHORS --- python-django-1.1.1.orig/debian/python-django-doc.doc-base +++ python-django-1.1.1/debian/python-django-doc.doc-base @@ -0,0 +1,20 @@ +Document: python-django-doc +Title: Python Django Documentation +Author: Django Software Foundation +Abstract: This documentation gives an introduction + to Django and its contributed packages like its automatic + admin and the user authentication applications. +Section: Programming/Python + +Format: HTML +Index: /usr/share/doc/python-django-doc/html/index.html +Files: /usr/share/doc/python-django-doc/html/*.html + /usr/share/doc/python-django-doc/html/faq/*.html + /usr/share/doc/python-django-doc/html/howto/*.html + /usr/share/doc/python-django-doc/html/internals/*.html + /usr/share/doc/python-django-doc/html/intro/*.html + /usr/share/doc/python-django-doc/html/misc/*.html + /usr/share/doc/python-django-doc/html/obsolete/*.html + /usr/share/doc/python-django-doc/html/ref/*.html + /usr/share/doc/python-django-doc/html/releases/*.html + /usr/share/doc/python-django-doc/html/topics/*.html --- python-django-1.1.1.orig/debian/NEWS +++ python-django-1.1.1/debian/NEWS @@ -0,0 +1,63 @@ +python-django (1.0~beta2-3) unstable; urgency=low + + The transition from Django 0.96.2 to Django 1.0 means that your Django software + will probably have to be updated. A comprehensive list of changes since Django + 0.96 is available at the Django Wiki[1]. If you are upgrading from 0.95 + (Sarge), you should refer to this page[2] as well. + + The Debian package now also includes the full Django documentation in HTML + format[3] + + The following incomplete list of major changes is taken from the release notes + of 1.0alpha1[4], 1.0alpha2[5], 1.0beta1[6] ad 1.0beta2[7]. For more changes + see those release notes or [1]. + + Refactored admin application (newforms-admin) + The Django administrative interface (django.contrib.admin) has been + completely refactored; admin definitions are now completely decoupled from + model definitions (no more class Admin declaration in models!), rewritten + to use Django’s new form-handling library (introduced in the 0.96 release + as django.newforms, and now available as simply django.forms) and redesigned + with extensibility and customization in mind. Full documentation for the + admin application is available online in the official Django + documentation[8]. + + INSERT/UPDATE distinction + Although Django’s default behavior of having a model’s save() method + automatically determine whether to perform an INSERT or an UPDATE at + the SQL level is suitable for the majority of cases, there are occasional + situations where forcing one or the other is useful. As a result, models + can now support an additional parameter to save() which can force a specific + operation. Consult the database API documentation[9] for details and important + notes about appropriate use of this parameter. + + Split CacheMiddleware + Django’s CacheMiddleware has been split into three classes: CacheMiddleware + itself still exists and retains all of its previous functionality, but it + is now built from two separate middleware classes which handle the two parts + of caching (inserting into and reading from the cache) separately, offering + additional flexibility for situations where combining these functions into a + single middleware posed problems. Full details, including updated notes on + appropriate use, are in the caching documentation[10]. + + Refactored django.contrib.comments + As part of a Google Summer of Code project, Thejaswi Puthraya carried out + a major rewrite and refactoring of Django’s bundled comment system, greatly + increasing its flexibility and customizability. Full documentation[11] is + available, as well as an upgrade guide[12] if you were using the previous + incarnation of the comments application. + + [1] http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges + [2] http://code.djangoproject.com/wiki/OlderBackwardsIncompatibleChanges + [3] /usr/share/doc/python-django/html/index.html + [4] http://docs.djangoproject.com/en/dev/releases/1.0-alpha-1/#releases-1-0-alpha-1 + [5] http://docs.djangoproject.com/en/dev/releases/1.0-alpha-2/#releases-1-0-alpha-2 + [6] http://docs.djangoproject.com/en/dev/releases/1.0-beta/#releases-1-0-beta + [7] http://docs.djangoproject.com/en/dev/releases/1.0-beta-2/ + [8] /usr/share/doc/python-django/html/ref/contrib/admin.html + [9] /usr/share/doc/python-django/html/ref/models/index.html + [10] /usr/share/doc/python-django/html/topics/cache.html + [11] /usr/share/doc/python-django/html/ref/contrib/comments/index.html + [12] /usr/share/doc/python-django/html/ref/contrib/comments/upgrade.htm + + -- David Spreen Fri, 28 Aug 2008 09:10:16 -0700 --- python-django-1.1.1.orig/debian/copyright +++ python-django-1.1.1/debian/copyright @@ -0,0 +1,312 @@ +This package was debianized by Brett Parker with +the assistance of Raphael Hertzog , Gustavo Noronha Silva +, David Spreen and the Debian Python +Modules Team . + +The upstream source is available from . + +Main Django Code Licence: +========================= +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Individual copyright holders stated in Main Django License are listed +in /usr/share/doc/python-django/AUTHORS.gz. + +PyDispatcher Licence (django/dispatch/*): +========================================= + + Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + The name of Patrick K. O'Brien, or the name of any Contributor, + may not be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + +License of django/utils/simplejson/* +==================================== + +Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +License for django/utils/functional.py and django/utils/_decimal.py +(License taken from Python 2.5) +======================================================================= + +functional.py: +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation + +decimal.py: +Copyright (c) 2004 Python Software Foundation + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative +version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + +django/utils/autoreload.py +=========================== + +Portions Copyright (c) 2004 CherryPy Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Some code taken from Ian Bicking's Paste which is released under the MIT +License: + +Copyright (c) 2008 Ian Bicking + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +django/contrib/gis/geos/* and django/contrib/gis/gdal/* +======================================================== +Copyright (c) 2007, Justin Bronn + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of GEOSGeometry nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +django/contrib/gis/measure.py: +=============================== +Copyright (c) 2007, Robert Coup + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Distance nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +docs/_static/reset-fonts-grids.css: +==================================== +Copyright (c) 2008, Yahoo! Inc. All rights reserved. + +Software License Agreement (BSD License), downloaded from + on Friday, Aug 29 2008 + +Copyright (c) 2006, Yahoo! Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or +without modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Yahoo! Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Yahoo! Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. --- python-django-1.1.1.orig/debian/watch +++ python-django-1.1.1/debian/watch @@ -0,0 +1,3 @@ +version=3 +opts=filenamemangle=s/.*\/download\/(.*)\/tarball\//Django-$1.tar.gz/,uversionmangle=s/-(alpha|beta)-/~$1/ \ + http://www.djangoproject.com/download/ (.+)/tarball/ --- python-django-1.1.1.orig/debian/pyversions +++ python-django-1.1.1/debian/pyversions @@ -0,0 +1 @@ +2.3- --- python-django-1.1.1.orig/debian/rules +++ python-django-1.1.1/debian/rules @@ -0,0 +1,57 @@ +#!/usr/bin/make -f + +include /usr/share/python/python.mk + +PREFIX = debian/python-django +DJANGO_DIR = $(PREFIX)/$(call py_libdir_sh,`pyversions -d`)/django + +%: + dh --with quilt $@ + +override_dh_auto_clean: + rm -rf docs.debian testproject + dh_auto_clean + +override_dh_auto_build: + dh_auto_build + + # Build the HTML documentation. + # We programmatically replace most instances of django-admin.py with + # django-admin and remove the source files from the target _build. + cp -r docs docs.debian + find docs.debian -type f -print0 | xargs -0r perl -pi -e 's|(?. +# +# Changed: Jannis Leidel +# +# Joost Cassee +# +# +# Version: @(#)fastcgi 0.3 05-Aug-2008 joost AT cassee.net +# + +set -e + +#### CONFIGURATION (override in /etc/default/django) + +# django project names/directories +DJANGO_SITES="" + +# path to the directory with your django projects +SITES_PATH=/var/lib/django + +# path to the directory for socket and pid files +RUNFILES_PATH=/var/run/django + +# please make sure this is NOT root +# local user prefered, www-data accepted +RUN_AS=www-data + +# maximum requests before fast-cgi process respawns +# (a.k.a. get killed and let live) +MAXREQUESTS=1000 + +#### END CONFIGURATION + +# Include defaults if available +if [ -f /etc/default/django ] ; then + . /etc/default/django +fi + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DESC="Django FastCGI servers" +NAME=$0 +SCRIPTNAME=/etc/init.d/$NAME +mkdir -p $RUNFILES_PATH +chown -R $RUN_AS:$RUN_AS $RUNFILES_PATH + +# +# Function that starts the daemon/service. +# +d_start() +{ + # Starting all Django FastCGI processes + # PORT=$PORT_START + for SITE in $DJANGO_SITES + do + echo -n ", $SITE" + if [ -f $RUNFILES_PATH/$SITE.pid ]; then + echo -n " already running" + else + start-stop-daemon --start --quiet \ + --pidfile $RUNFILES_PATH/$SITE.pid \ + --chuid $RUN_AS --exec /usr/bin/env -- python \ + $SITES_PATH/$SITE/manage.py runfcgi \ + protocol=fcgi method=threaded maxrequests=$MAXREQUESTS \ + socket=$RUNFILES_PATH/$SITE.socket \ + pidfile=$RUNFILES_PATH/$SITE.pid + chmod 400 $RUNFILES_PATH/$SITE.pid + fi + sleep 1 + done +} + +# +# Function that stops the daemon/service. +# +d_stop() { + # Killing all Django FastCGI processes running + for SITE in $DJANGO_SITES + do + echo -n ", $SITE" + start-stop-daemon --stop --quiet --pidfile $RUNFILES_PATH/$SITE.pid \ + || echo -n " not running" + if [ -f $RUNFILES_PATH/$SITE.pid ]; then + rm -f $RUNFILES_PATH/$SITE.pid + fi + sleep 1 + done +} + +ACTION="$1" +case "$ACTION" in + start) + echo -n "Starting $DESC: $NAME" + d_start + echo "." + ;; + + stop) + echo -n "Stopping $DESC: $NAME" + d_stop + echo "." + ;; + + restart|force-reload) + echo -n "Restarting $DESC: $NAME" + d_stop + sleep 2 + d_start + echo "." + ;; + + *) + echo "Usage: $NAME {start|stop|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0 --- python-django-1.1.1.orig/debian/patches/CVE-2014-0473.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0473.patch @@ -0,0 +1,56 @@ +Description: fix caching of anonymous pages could reveal CSRF token +Origin: backport of commit 77723a0f22c647d9ed01326a804af1423e0e3a14 +Author: Aymeric Augustin +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309782 + +Index: python-django-1.1.1/django/middleware/cache.py +=================================================================== +--- python-django-1.1.1.orig/django/middleware/cache.py 2014-04-19 13:07:23.086407461 -0400 ++++ python-django-1.1.1/django/middleware/cache.py 2014-04-19 13:07:23.082407461 -0400 +@@ -50,7 +50,8 @@ + + from django.conf import settings + from django.core.cache import cache +-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): + """ +@@ -77,8 +78,15 @@ + # HTTPMiddleware, which throws the body of a HEAD-request + # away before this middleware gets a chance to cache it. + 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. +Index: python-django-1.1.1/django/utils/cache.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/cache.py 2009-04-01 14:19:32.000000000 -0400 ++++ python-django-1.1.1/django/utils/cache.py 2014-04-19 13:09:59.370409995 -0400 +@@ -137,6 +137,16 @@ + if newheader.lower() not in existing_headers] + response['Vary'] = ', '.join(vary_headers + additional_headers) + ++def has_vary_header(response, header_query): ++ """ ++ Checks to see if the response has a given header name in its Vary header. ++ """ ++ if not response.has_header('Vary'): ++ return False ++ vary_headers = cc_delim_re.split(response['Vary']) ++ existing_headers = set([header.lower() for header in vary_headers]) ++ return header_query.lower() in existing_headers ++ + def _generate_cache_key(request, headerlist, key_prefix): + """Returns a cache key from the headers given in the header list.""" + ctx = md5_constructor() --- python-django-1.1.1.orig/debian/patches/CVE-2014-0472-regression.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0472-regression.patch @@ -0,0 +1,84 @@ +Backport of: + +From b63ae5c60619a257ad57cf6043e71f681283e47b Mon Sep 17 00:00:00 2001 +From: Preston Timmons +Date: Tue, 22 Apr 2014 20:19:46 +0000 +Subject: [PATCH] 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(-) + +Index: python-django-1.1.1/django/core/urlresolvers.py +=================================================================== +--- python-django-1.1.1.orig/django/core/urlresolvers.py 2014-04-22 23:18:58.648360929 -0400 ++++ python-django-1.1.1/django/core/urlresolvers.py 2014-04-22 23:18:58.644360929 -0400 +@@ -7,6 +7,7 @@ + (view_function, function_args, function_kwargs) + """ + ++import functools + import re + + from django.http import Http404 +@@ -170,6 +171,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: +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/urls.py 2014-04-22 23:18:58.648360929 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py 2014-04-22 23:18:58.644360929 -0400 +@@ -1,5 +1,5 @@ + from django.conf.urls.defaults import * +-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('', + url(r'non_path_include/$', empty_view, name='non_path_include'), +@@ -48,6 +48,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"), + +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/views.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/views.py 2014-04-22 23:18:58.648360929 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/views.py 2014-04-22 23:19:29.164361423 -0400 +@@ -1,3 +1,5 @@ ++from functools import partial, update_wrapper ++ + def empty_view(request, *args, **kwargs): + pass + +@@ -7,5 +9,13 @@ + def absolute_kwargs_view(request, arg1=1, arg2=2): + pass + ++ ++empty_view_partial = partial(empty_view, template_name="template.html") ++ ++ ++empty_view_wrapped = update_wrapper( ++ partial(empty_view, template_name="template.html"), empty_view, ++) ++ + def nested_view(request): + pass --- python-django-1.1.1.orig/debian/patches/CVE-2011-4137+4138.patch +++ python-django-1.1.1/debian/patches/CVE-2011-4137+4138.patch @@ -0,0 +1,42 @@ +Origin: backport, https://code.djangoproject.com/changeset/16766 +Description: Altered the behavior of URLField to avoid a potential DOS vector, + and to avoid potential leakage of local filesystem data. +Index: python-django-1.1.1/django/db/models/fields/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/db/models/fields/__init__.py 2009-04-12 00:32:23.000000000 -0500 ++++ python-django-1.1.1/django/db/models/fields/__init__.py 2011-12-07 16:00:14.000000000 -0600 +@@ -888,7 +888,7 @@ + return super(TimeField, self).formfield(**defaults) + + class URLField(CharField): +- def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): ++ def __init__(self, verbose_name=None, name=None, verify_exists=False, **kwargs): + kwargs['max_length'] = kwargs.get('max_length', 200) + self.verify_exists = verify_exists + CharField.__init__(self, verbose_name, name, **kwargs) +Index: python-django-1.1.1/docs/ref/models/fields.txt +=================================================================== +--- python-django-1.1.1.orig/docs/ref/models/fields.txt 2009-09-23 18:41:35.000000000 -0500 ++++ python-django-1.1.1/docs/ref/models/fields.txt 2011-12-07 16:00:14.000000000 -0600 +@@ -730,7 +730,7 @@ + ``URLField`` + ------------ + +-.. class:: URLField([verify_exists=True, max_length=200, **options]) ++.. class:: URLField([verify_exists=False, max_length=200, **options]) + + A :class:`CharField` for a URL. Has one extra optional argument: + +@@ -742,6 +742,12 @@ + a url being serverd by the same server will hang. + This should not be a problem for multithreaded servers. + ++ ++.. versionchanged:: 1.2 ++ ++ The default value of ``verify_exists`` has been changed to ++ ``False``. This argument should not be set to ``True`` because it ++ has security and performance problems. + The admin represents this as an ```` (a single-line input). + + Like all :class:`CharField` subclasses, :class:`URLField` takes the optional --- python-django-1.1.1.orig/debian/patches/02-embedded_code_copies.diff +++ python-django-1.1.1/debian/patches/02-embedded_code_copies.diff @@ -0,0 +1,3088 @@ + + Use the system version of the decimal module. + + -- Chris Lamb Tue, 01 Dec 2009 22:43:48 +0000 + +--- a/django/utils/_decimal.py 2009-12-01 22:47:42.000000000 +0000 ++++ b/django/utils/_decimal.py 2009-12-01 22:48:42.000000000 +0000 +@@ -1,3079 +1 @@ +-# Copyright (c) 2004 Python Software Foundation. +-# All rights reserved. +- +-# Written by Eric Price +-# and Facundo Batista +-# and Raymond Hettinger +-# and Aahz +-# and Tim Peters +- +-# This module is currently Py2.3 compatible and should be kept that way +-# unless a major compelling advantage arises. IOW, 2.3 compatibility is +-# strongly preferred, but not guaranteed. +- +-# Also, this module should be kept in sync with the latest updates of +-# the IBM specification as it evolves. Those updates will be treated +-# as bug fixes (deviation from the spec is a compatibility, usability +-# bug) and will be backported. At this point the spec is stabilizing +-# and the updates are becoming fewer, smaller, and less significant. +- +-""" +-This is a Py2.3 implementation of decimal floating point arithmetic based on +-the General Decimal Arithmetic Specification: +- +- www2.hursley.ibm.com/decimal/decarith.html +- +-and IEEE standard 854-1987: +- +- www.cs.berkeley.edu/~ejr/projects/754/private/drafts/854-1987/dir.html +- +-Decimal floating point has finite precision with arbitrarily large bounds. +- +-The purpose of the module is to support arithmetic using familiar +-"schoolhouse" rules and to avoid the some of tricky representation +-issues associated with binary floating point. The package is especially +-useful for financial applications or for contexts where users have +-expectations that are at odds with binary floating point (for instance, +-in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 instead +-of the expected Decimal("0.00") returned by decimal floating point). +- +-Here are some examples of using the decimal module: +- +->>> from decimal import * +->>> setcontext(ExtendedContext) +->>> Decimal(0) +-Decimal("0") +->>> Decimal("1") +-Decimal("1") +->>> Decimal("-.0123") +-Decimal("-0.0123") +->>> Decimal(123456) +-Decimal("123456") +->>> Decimal("123.45e12345678901234567890") +-Decimal("1.2345E+12345678901234567892") +->>> Decimal("1.33") + Decimal("1.27") +-Decimal("2.60") +->>> Decimal("12.34") + Decimal("3.87") - Decimal("18.41") +-Decimal("-2.20") +->>> dig = Decimal(1) +->>> print dig / Decimal(3) +-0.333333333 +->>> getcontext().prec = 18 +->>> print dig / Decimal(3) +-0.333333333333333333 +->>> print dig.sqrt() +-1 +->>> print Decimal(3).sqrt() +-1.73205080756887729 +->>> print Decimal(3) ** 123 +-4.85192780976896427E+58 +->>> inf = Decimal(1) / Decimal(0) +->>> print inf +-Infinity +->>> neginf = Decimal(-1) / Decimal(0) +->>> print neginf +--Infinity +->>> print neginf + inf +-NaN +->>> print neginf * inf +--Infinity +->>> print dig / 0 +-Infinity +->>> getcontext().traps[DivisionByZero] = 1 +->>> print dig / 0 +-Traceback (most recent call last): +- ... +- ... +- ... +-DivisionByZero: x / 0 +->>> c = Context() +->>> c.traps[InvalidOperation] = 0 +->>> print c.flags[InvalidOperation] +-0 +->>> c.divide(Decimal(0), Decimal(0)) +-Decimal("NaN") +->>> c.traps[InvalidOperation] = 1 +->>> print c.flags[InvalidOperation] +-1 +->>> c.flags[InvalidOperation] = 0 +->>> print c.flags[InvalidOperation] +-0 +->>> print c.divide(Decimal(0), Decimal(0)) +-Traceback (most recent call last): +- ... +- ... +- ... +-InvalidOperation: 0 / 0 +->>> print c.flags[InvalidOperation] +-1 +->>> c.flags[InvalidOperation] = 0 +->>> c.traps[InvalidOperation] = 0 +->>> print c.divide(Decimal(0), Decimal(0)) +-NaN +->>> print c.flags[InvalidOperation] +-1 +->>> +-""" +- +-__all__ = [ +- # Two major classes +- 'Decimal', 'Context', +- +- # Contexts +- 'DefaultContext', 'BasicContext', 'ExtendedContext', +- +- # Exceptions +- 'DecimalException', 'Clamped', 'InvalidOperation', 'DivisionByZero', +- 'Inexact', 'Rounded', 'Subnormal', 'Overflow', 'Underflow', +- +- # Constants for use in setting up contexts +- 'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING', +- 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', +- +- # Functions for manipulating contexts +- 'setcontext', 'getcontext' +-] +- +-import copy as _copy +- +-#Rounding +-ROUND_DOWN = 'ROUND_DOWN' +-ROUND_HALF_UP = 'ROUND_HALF_UP' +-ROUND_HALF_EVEN = 'ROUND_HALF_EVEN' +-ROUND_CEILING = 'ROUND_CEILING' +-ROUND_FLOOR = 'ROUND_FLOOR' +-ROUND_UP = 'ROUND_UP' +-ROUND_HALF_DOWN = 'ROUND_HALF_DOWN' +- +-#Rounding decision (not part of the public API) +-NEVER_ROUND = 'NEVER_ROUND' # Round in division (non-divmod), sqrt ONLY +-ALWAYS_ROUND = 'ALWAYS_ROUND' # Every operation rounds at end. +- +-#Errors +- +-class DecimalException(ArithmeticError): +- """Base exception class. +- +- Used exceptions derive from this. +- If an exception derives from another exception besides this (such as +- Underflow (Inexact, Rounded, Subnormal) that indicates that it is only +- called if the others are present. This isn't actually used for +- anything, though. +- +- handle -- Called when context._raise_error is called and the +- trap_enabler is set. First argument is self, second is the +- context. More arguments can be given, those being after +- the explanation in _raise_error (For example, +- context._raise_error(NewError, '(-x)!', self._sign) would +- call NewError().handle(context, self._sign).) +- +- To define a new exception, it should be sufficient to have it derive +- from DecimalException. +- """ +- def handle(self, context, *args): +- pass +- +- +-class Clamped(DecimalException): +- """Exponent of a 0 changed to fit bounds. +- +- This occurs and signals clamped if the exponent of a result has been +- altered in order to fit the constraints of a specific concrete +- representation. This may occur when the exponent of a zero result would +- be outside the bounds of a representation, or when a large normal +- number would have an encoded exponent that cannot be represented. In +- this latter case, the exponent is reduced to fit and the corresponding +- number of zero digits are appended to the coefficient ("fold-down"). +- """ +- +- +-class InvalidOperation(DecimalException): +- """An invalid operation was performed. +- +- Various bad things cause this: +- +- Something creates a signaling NaN +- -INF + INF +- 0 * (+-)INF +- (+-)INF / (+-)INF +- x % 0 +- (+-)INF % x +- x._rescale( non-integer ) +- sqrt(-x) , x > 0 +- 0 ** 0 +- x ** (non-integer) +- x ** (+-)INF +- An operand is invalid +- """ +- def handle(self, context, *args): +- if args: +- if args[0] == 1: #sNaN, must drop 's' but keep diagnostics +- return Decimal( (args[1]._sign, args[1]._int, 'n') ) +- return NaN +- +-class ConversionSyntax(InvalidOperation): +- """Trying to convert badly formed string. +- +- This occurs and signals invalid-operation if an string is being +- converted to a number and it does not conform to the numeric string +- syntax. The result is [0,qNaN]. +- """ +- +- def handle(self, context, *args): +- return (0, (0,), 'n') #Passed to something which uses a tuple. +- +-class DivisionByZero(DecimalException, ZeroDivisionError): +- """Division by 0. +- +- This occurs and signals division-by-zero if division of a finite number +- by zero was attempted (during a divide-integer or divide operation, or a +- power operation with negative right-hand operand), and the dividend was +- not zero. +- +- The result of the operation is [sign,inf], where sign is the exclusive +- or of the signs of the operands for divide, or is 1 for an odd power of +- -0, for power. +- """ +- +- def handle(self, context, sign, double = None, *args): +- if double is not None: +- return (Infsign[sign],)*2 +- return Infsign[sign] +- +-class DivisionImpossible(InvalidOperation): +- """Cannot perform the division adequately. +- +- This occurs and signals invalid-operation if the integer result of a +- divide-integer or remainder operation had too many digits (would be +- longer than precision). The result is [0,qNaN]. +- """ +- +- def handle(self, context, *args): +- return (NaN, NaN) +- +-class DivisionUndefined(InvalidOperation, ZeroDivisionError): +- """Undefined result of division. +- +- This occurs and signals invalid-operation if division by zero was +- attempted (during a divide-integer, divide, or remainder operation), and +- the dividend is also zero. The result is [0,qNaN]. +- """ +- +- def handle(self, context, tup=None, *args): +- if tup is not None: +- return (NaN, NaN) #for 0 %0, 0 // 0 +- return NaN +- +-class Inexact(DecimalException): +- """Had to round, losing information. +- +- This occurs and signals inexact whenever the result of an operation is +- not exact (that is, it needed to be rounded and any discarded digits +- were non-zero), or if an overflow or underflow condition occurs. The +- result in all cases is unchanged. +- +- The inexact signal may be tested (or trapped) to determine if a given +- operation (or sequence of operations) was inexact. +- """ +- pass +- +-class InvalidContext(InvalidOperation): +- """Invalid context. Unknown rounding, for example. +- +- This occurs and signals invalid-operation if an invalid context was +- detected during an operation. This can occur if contexts are not checked +- on creation and either the precision exceeds the capability of the +- underlying concrete representation or an unknown or unsupported rounding +- was specified. These aspects of the context need only be checked when +- the values are required to be used. The result is [0,qNaN]. +- """ +- +- def handle(self, context, *args): +- return NaN +- +-class Rounded(DecimalException): +- """Number got rounded (not necessarily changed during rounding). +- +- This occurs and signals rounded whenever the result of an operation is +- rounded (that is, some zero or non-zero digits were discarded from the +- coefficient), or if an overflow or underflow condition occurs. The +- result in all cases is unchanged. +- +- The rounded signal may be tested (or trapped) to determine if a given +- operation (or sequence of operations) caused a loss of precision. +- """ +- pass +- +-class Subnormal(DecimalException): +- """Exponent < Emin before rounding. +- +- This occurs and signals subnormal whenever the result of a conversion or +- operation is subnormal (that is, its adjusted exponent is less than +- Emin, before any rounding). The result in all cases is unchanged. +- +- The subnormal signal may be tested (or trapped) to determine if a given +- or operation (or sequence of operations) yielded a subnormal result. +- """ +- pass +- +-class Overflow(Inexact, Rounded): +- """Numerical overflow. +- +- This occurs and signals overflow if the adjusted exponent of a result +- (from a conversion or from an operation that is not an attempt to divide +- by zero), after rounding, would be greater than the largest value that +- can be handled by the implementation (the value Emax). +- +- The result depends on the rounding mode: +- +- For round-half-up and round-half-even (and for round-half-down and +- round-up, if implemented), the result of the operation is [sign,inf], +- where sign is the sign of the intermediate result. For round-down, the +- result is the largest finite number that can be represented in the +- current precision, with the sign of the intermediate result. For +- round-ceiling, the result is the same as for round-down if the sign of +- the intermediate result is 1, or is [0,inf] otherwise. For round-floor, +- the result is the same as for round-down if the sign of the intermediate +- result is 0, or is [1,inf] otherwise. In all cases, Inexact and Rounded +- will also be raised. +- """ +- +- def handle(self, context, sign, *args): +- if context.rounding in (ROUND_HALF_UP, ROUND_HALF_EVEN, +- ROUND_HALF_DOWN, ROUND_UP): +- return Infsign[sign] +- if sign == 0: +- if context.rounding == ROUND_CEILING: +- return Infsign[sign] +- return Decimal((sign, (9,)*context.prec, +- context.Emax-context.prec+1)) +- if sign == 1: +- if context.rounding == ROUND_FLOOR: +- return Infsign[sign] +- return Decimal( (sign, (9,)*context.prec, +- context.Emax-context.prec+1)) +- +- +-class Underflow(Inexact, Rounded, Subnormal): +- """Numerical underflow with result rounded to 0. +- +- This occurs and signals underflow if a result is inexact and the +- adjusted exponent of the result would be smaller (more negative) than +- the smallest value that can be handled by the implementation (the value +- Emin). That is, the result is both inexact and subnormal. +- +- The result after an underflow will be a subnormal number rounded, if +- necessary, so that its exponent is not less than Etiny. This may result +- in 0 with the sign of the intermediate result and an exponent of Etiny. +- +- In all cases, Inexact, Rounded, and Subnormal will also be raised. +- """ +- +-# List of public traps and flags +-_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded, +- Underflow, InvalidOperation, Subnormal] +- +-# Map conditions (per the spec) to signals +-_condition_map = {ConversionSyntax:InvalidOperation, +- DivisionImpossible:InvalidOperation, +- DivisionUndefined:InvalidOperation, +- InvalidContext:InvalidOperation} +- +-##### Context Functions ####################################### +- +-# The getcontext() and setcontext() function manage access to a thread-local +-# current context. Py2.4 offers direct support for thread locals. If that +-# is not available, use threading.currentThread() which is slower but will +-# work for older Pythons. If threads are not part of the build, create a +-# mock threading object with threading.local() returning the module namespace. +- +-try: +- import threading +-except ImportError: +- # Python was compiled without threads; create a mock object instead +- import sys +- class MockThreading: +- def local(self, sys=sys): +- return sys.modules[__name__] +- threading = MockThreading() +- del sys, MockThreading +- +-try: +- threading.local +- +-except AttributeError: +- +- #To fix reloading, force it to create a new context +- #Old contexts have different exceptions in their dicts, making problems. +- if hasattr(threading.currentThread(), '__decimal_context__'): +- del threading.currentThread().__decimal_context__ +- +- def setcontext(context): +- """Set this thread's context to context.""" +- if context in (DefaultContext, BasicContext, ExtendedContext): +- context = context.copy() +- context.clear_flags() +- threading.currentThread().__decimal_context__ = context +- +- def getcontext(): +- """Returns this thread's context. +- +- If this thread does not yet have a context, returns +- a new context and sets this thread's context. +- New contexts are copies of DefaultContext. +- """ +- try: +- return threading.currentThread().__decimal_context__ +- except AttributeError: +- context = Context() +- threading.currentThread().__decimal_context__ = context +- return context +- +-else: +- +- local = threading.local() +- if hasattr(local, '__decimal_context__'): +- del local.__decimal_context__ +- +- def getcontext(_local=local): +- """Returns this thread's context. +- +- If this thread does not yet have a context, returns +- a new context and sets this thread's context. +- New contexts are copies of DefaultContext. +- """ +- try: +- return _local.__decimal_context__ +- except AttributeError: +- context = Context() +- _local.__decimal_context__ = context +- return context +- +- def setcontext(context, _local=local): +- """Set this thread's context to context.""" +- if context in (DefaultContext, BasicContext, ExtendedContext): +- context = context.copy() +- context.clear_flags() +- _local.__decimal_context__ = context +- +- del threading, local # Don't contaminate the namespace +- +- +-##### Decimal class ########################################### +- +-class Decimal(object): +- """Floating point class for decimal arithmetic.""" +- +- __slots__ = ('_exp','_int','_sign', '_is_special') +- # Generally, the value of the Decimal instance is given by +- # (-1)**_sign * _int * 10**_exp +- # Special values are signified by _is_special == True +- +- # We're immutable, so use __new__ not __init__ +- def __new__(cls, value="0", context=None): +- """Create a decimal point instance. +- +- >>> Decimal('3.14') # string input +- Decimal("3.14") +- >>> Decimal((0, (3, 1, 4), -2)) # tuple input (sign, digit_tuple, exponent) +- Decimal("3.14") +- >>> Decimal(314) # int or long +- Decimal("314") +- >>> Decimal(Decimal(314)) # another decimal instance +- Decimal("314") +- """ +- +- self = object.__new__(cls) +- self._is_special = False +- +- # From an internal working value +- if isinstance(value, _WorkRep): +- self._sign = value.sign +- self._int = tuple(map(int, str(value.int))) +- self._exp = int(value.exp) +- return self +- +- # From another decimal +- if isinstance(value, Decimal): +- self._exp = value._exp +- self._sign = value._sign +- self._int = value._int +- self._is_special = value._is_special +- return self +- +- # From an integer +- if isinstance(value, (int,long)): +- if value >= 0: +- self._sign = 0 +- else: +- self._sign = 1 +- self._exp = 0 +- self._int = tuple(map(int, str(abs(value)))) +- return self +- +- # tuple/list conversion (possibly from as_tuple()) +- if isinstance(value, (list,tuple)): +- if len(value) != 3: +- raise ValueError, 'Invalid arguments' +- if value[0] not in (0,1): +- raise ValueError, 'Invalid sign' +- for digit in value[1]: +- if not isinstance(digit, (int,long)) or digit < 0: +- raise ValueError, "The second value in the tuple must be composed of non negative integer elements." +- +- self._sign = value[0] +- self._int = tuple(value[1]) +- if value[2] in ('F','n','N'): +- self._exp = value[2] +- self._is_special = True +- else: +- self._exp = int(value[2]) +- return self +- +- if isinstance(value, float): +- raise TypeError("Cannot convert float to Decimal. " + +- "First convert the float to a string") +- +- # Other argument types may require the context during interpretation +- if context is None: +- context = getcontext() +- +- # From a string +- # REs insist on real strings, so we can too. +- if isinstance(value, basestring): +- if _isinfinity(value): +- self._exp = 'F' +- self._int = (0,) +- self._is_special = True +- if _isinfinity(value) == 1: +- self._sign = 0 +- else: +- self._sign = 1 +- return self +- if _isnan(value): +- sig, sign, diag = _isnan(value) +- self._is_special = True +- if len(diag) > context.prec: #Diagnostic info too long +- self._sign, self._int, self._exp = \ +- context._raise_error(ConversionSyntax) +- return self +- if sig == 1: +- self._exp = 'n' #qNaN +- else: #sig == 2 +- self._exp = 'N' #sNaN +- self._sign = sign +- self._int = tuple(map(int, diag)) #Diagnostic info +- return self +- try: +- self._sign, self._int, self._exp = _string2exact(value) +- except ValueError: +- self._is_special = True +- self._sign, self._int, self._exp = context._raise_error(ConversionSyntax) +- return self +- +- raise TypeError("Cannot convert %r to Decimal" % value) +- +- def _isnan(self): +- """Returns whether the number is not actually one. +- +- 0 if a number +- 1 if NaN +- 2 if sNaN +- """ +- if self._is_special: +- exp = self._exp +- if exp == 'n': +- return 1 +- elif exp == 'N': +- return 2 +- return 0 +- +- def _isinfinity(self): +- """Returns whether the number is infinite +- +- 0 if finite or not a number +- 1 if +INF +- -1 if -INF +- """ +- if self._exp == 'F': +- if self._sign: +- return -1 +- return 1 +- return 0 +- +- def _check_nans(self, other = None, context=None): +- """Returns whether the number is not actually one. +- +- if self, other are sNaN, signal +- if self, other are NaN return nan +- return 0 +- +- Done before operations. +- """ +- +- self_is_nan = self._isnan() +- if other is None: +- other_is_nan = False +- else: +- other_is_nan = other._isnan() +- +- if self_is_nan or other_is_nan: +- if context is None: +- context = getcontext() +- +- if self_is_nan == 2: +- return context._raise_error(InvalidOperation, 'sNaN', +- 1, self) +- if other_is_nan == 2: +- return context._raise_error(InvalidOperation, 'sNaN', +- 1, other) +- if self_is_nan: +- return self +- +- return other +- return 0 +- +- def __nonzero__(self): +- """Is the number non-zero? +- +- 0 if self == 0 +- 1 if self != 0 +- """ +- if self._is_special: +- return 1 +- return sum(self._int) != 0 +- +- def __cmp__(self, other, context=None): +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- return 1 # Comparison involving NaN's always reports self > other +- +- # INF = INF +- return cmp(self._isinfinity(), other._isinfinity()) +- +- if not self and not other: +- return 0 #If both 0, sign comparison isn't certain. +- +- #If different signs, neg one is less +- if other._sign < self._sign: +- return -1 +- if self._sign < other._sign: +- return 1 +- +- self_adjusted = self.adjusted() +- other_adjusted = other.adjusted() +- if self_adjusted == other_adjusted and \ +- self._int + (0,)*(self._exp - other._exp) == \ +- other._int + (0,)*(other._exp - self._exp): +- return 0 #equal, except in precision. ([0]*(-x) = []) +- elif self_adjusted > other_adjusted and self._int[0] != 0: +- return (-1)**self._sign +- elif self_adjusted < other_adjusted and other._int[0] != 0: +- return -((-1)**self._sign) +- +- # Need to round, so make sure we have a valid context +- if context is None: +- context = getcontext() +- +- context = context._shallow_copy() +- rounding = context._set_rounding(ROUND_UP) #round away from 0 +- +- flags = context._ignore_all_flags() +- res = self.__sub__(other, context=context) +- +- context._regard_flags(*flags) +- +- context.rounding = rounding +- +- if not res: +- return 0 +- elif res._sign: +- return -1 +- return 1 +- +- def __eq__(self, other): +- if not isinstance(other, (Decimal, int, long)): +- return NotImplemented +- return self.__cmp__(other) == 0 +- +- def __ne__(self, other): +- if not isinstance(other, (Decimal, int, long)): +- return NotImplemented +- return self.__cmp__(other) != 0 +- +- def compare(self, other, context=None): +- """Compares one to another. +- +- -1 => a < b +- 0 => a = b +- 1 => a > b +- NaN => one is NaN +- Like __cmp__, but returns Decimal instances. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- #compare(NaN, NaN) = NaN +- if (self._is_special or other and other._is_special): +- ans = self._check_nans(other, context) +- if ans: +- return ans +- +- return Decimal(self.__cmp__(other, context)) +- +- def __hash__(self): +- """x.__hash__() <==> hash(x)""" +- # Decimal integers must hash the same as the ints +- # Non-integer decimals are normalized and hashed as strings +- # Normalization assures that hast(100E-1) == hash(10) +- if self._is_special: +- if self._isnan(): +- raise TypeError('Cannot hash a NaN value.') +- return hash(str(self)) +- i = int(self) +- if self == Decimal(i): +- return hash(i) +- assert self.__nonzero__() # '-0' handled by integer case +- return hash(str(self.normalize())) +- +- def as_tuple(self): +- """Represents the number as a triple tuple. +- +- To show the internals exactly as they are. +- """ +- return (self._sign, self._int, self._exp) +- +- def __repr__(self): +- """Represents the number as an instance of Decimal.""" +- # Invariant: eval(repr(d)) == d +- return 'Decimal("%s")' % str(self) +- +- def __str__(self, eng = 0, context=None): +- """Return string representation of the number in scientific notation. +- +- Captures all of the information in the underlying representation. +- """ +- +- if self._is_special: +- if self._isnan(): +- minus = '-'*self._sign +- if self._int == (0,): +- info = '' +- else: +- info = ''.join(map(str, self._int)) +- if self._isnan() == 2: +- return minus + 'sNaN' + info +- return minus + 'NaN' + info +- if self._isinfinity(): +- minus = '-'*self._sign +- return minus + 'Infinity' +- +- if context is None: +- context = getcontext() +- +- tmp = map(str, self._int) +- numdigits = len(self._int) +- leftdigits = self._exp + numdigits +- if eng and not self: #self = 0eX wants 0[.0[0]]eY, not [[0]0]0eY +- if self._exp < 0 and self._exp >= -6: #short, no need for e/E +- s = '-'*self._sign + '0.' + '0'*(abs(self._exp)) +- return s +- #exp is closest mult. of 3 >= self._exp +- exp = ((self._exp - 1)// 3 + 1) * 3 +- if exp != self._exp: +- s = '0.'+'0'*(exp - self._exp) +- else: +- s = '0' +- if exp != 0: +- if context.capitals: +- s += 'E' +- else: +- s += 'e' +- if exp > 0: +- s += '+' #0.0e+3, not 0.0e3 +- s += str(exp) +- s = '-'*self._sign + s +- return s +- if eng: +- dotplace = (leftdigits-1)%3+1 +- adjexp = leftdigits -1 - (leftdigits-1)%3 +- else: +- adjexp = leftdigits-1 +- dotplace = 1 +- if self._exp == 0: +- pass +- elif self._exp < 0 and adjexp >= 0: +- tmp.insert(leftdigits, '.') +- elif self._exp < 0 and adjexp >= -6: +- tmp[0:0] = ['0'] * int(-leftdigits) +- tmp.insert(0, '0.') +- else: +- if numdigits > dotplace: +- tmp.insert(dotplace, '.') +- elif numdigits < dotplace: +- tmp.extend(['0']*(dotplace-numdigits)) +- if adjexp: +- if not context.capitals: +- tmp.append('e') +- else: +- tmp.append('E') +- if adjexp > 0: +- tmp.append('+') +- tmp.append(str(adjexp)) +- if eng: +- while tmp[0:1] == ['0']: +- tmp[0:1] = [] +- if len(tmp) == 0 or tmp[0] == '.' or tmp[0].lower() == 'e': +- tmp[0:0] = ['0'] +- if self._sign: +- tmp.insert(0, '-') +- +- return ''.join(tmp) +- +- def to_eng_string(self, context=None): +- """Convert to engineering-type string. +- +- Engineering notation has an exponent which is a multiple of 3, so there +- are up to 3 digits left of the decimal place. +- +- Same rules for when in exponential and when as a value as in __str__. +- """ +- return self.__str__(eng=1, context=context) +- +- def __neg__(self, context=None): +- """Returns a copy with the sign switched. +- +- Rounds, if it has reason. +- """ +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- if not self: +- # -Decimal('0') is Decimal('0'), not Decimal('-0') +- sign = 0 +- elif self._sign: +- sign = 0 +- else: +- sign = 1 +- +- if context is None: +- context = getcontext() +- if context._rounding_decision == ALWAYS_ROUND: +- return Decimal((sign, self._int, self._exp))._fix(context) +- return Decimal( (sign, self._int, self._exp)) +- +- def __pos__(self, context=None): +- """Returns a copy, unless it is a sNaN. +- +- Rounds the number (if more then precision digits) +- """ +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- sign = self._sign +- if not self: +- # + (-0) = 0 +- sign = 0 +- +- if context is None: +- context = getcontext() +- +- if context._rounding_decision == ALWAYS_ROUND: +- ans = self._fix(context) +- else: +- ans = Decimal(self) +- ans._sign = sign +- return ans +- +- def __abs__(self, round=1, context=None): +- """Returns the absolute value of self. +- +- If the second argument is 0, do not round. +- """ +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- if not round: +- if context is None: +- context = getcontext() +- context = context._shallow_copy() +- context._set_rounding_decision(NEVER_ROUND) +- +- if self._sign: +- ans = self.__neg__(context=context) +- else: +- ans = self.__pos__(context=context) +- +- return ans +- +- def __add__(self, other, context=None): +- """Returns self + other. +- +- -INF + INF (or the reverse) cause InvalidOperation errors. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if context is None: +- context = getcontext() +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- return ans +- +- if self._isinfinity(): +- #If both INF, same sign => same as both, opposite => error. +- if self._sign != other._sign and other._isinfinity(): +- return context._raise_error(InvalidOperation, '-INF + INF') +- return Decimal(self) +- if other._isinfinity(): +- return Decimal(other) #Can't both be infinity here +- +- shouldround = context._rounding_decision == ALWAYS_ROUND +- +- exp = min(self._exp, other._exp) +- negativezero = 0 +- if context.rounding == ROUND_FLOOR and self._sign != other._sign: +- #If the answer is 0, the sign should be negative, in this case. +- negativezero = 1 +- +- if not self and not other: +- sign = min(self._sign, other._sign) +- if negativezero: +- sign = 1 +- return Decimal( (sign, (0,), exp)) +- if not self: +- exp = max(exp, other._exp - context.prec-1) +- ans = other._rescale(exp, watchexp=0, context=context) +- if shouldround: +- ans = ans._fix(context) +- return ans +- if not other: +- exp = max(exp, self._exp - context.prec-1) +- ans = self._rescale(exp, watchexp=0, context=context) +- if shouldround: +- ans = ans._fix(context) +- return ans +- +- op1 = _WorkRep(self) +- op2 = _WorkRep(other) +- op1, op2 = _normalize(op1, op2, shouldround, context.prec) +- +- result = _WorkRep() +- if op1.sign != op2.sign: +- # Equal and opposite +- if op1.int == op2.int: +- if exp < context.Etiny(): +- exp = context.Etiny() +- context._raise_error(Clamped) +- return Decimal((negativezero, (0,), exp)) +- if op1.int < op2.int: +- op1, op2 = op2, op1 +- #OK, now abs(op1) > abs(op2) +- if op1.sign == 1: +- result.sign = 1 +- op1.sign, op2.sign = op2.sign, op1.sign +- else: +- result.sign = 0 +- #So we know the sign, and op1 > 0. +- elif op1.sign == 1: +- result.sign = 1 +- op1.sign, op2.sign = (0, 0) +- else: +- result.sign = 0 +- #Now, op1 > abs(op2) > 0 +- +- if op2.sign == 0: +- result.int = op1.int + op2.int +- else: +- result.int = op1.int - op2.int +- +- result.exp = op1.exp +- ans = Decimal(result) +- if shouldround: +- ans = ans._fix(context) +- return ans +- +- __radd__ = __add__ +- +- def __sub__(self, other, context=None): +- """Return self + (-other)""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context=context) +- if ans: +- return ans +- +- # -Decimal(0) = Decimal(0), which we don't want since +- # (-0 - 0 = -0 + (-0) = -0, but -0 + 0 = 0.) +- # so we change the sign directly to a copy +- tmp = Decimal(other) +- tmp._sign = 1-tmp._sign +- +- return self.__add__(tmp, context=context) +- +- def __rsub__(self, other, context=None): +- """Return other + (-self)""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- tmp = Decimal(self) +- tmp._sign = 1 - tmp._sign +- return other.__add__(tmp, context=context) +- +- def _increment(self, round=1, context=None): +- """Special case of add, adding 1eExponent +- +- Since it is common, (rounding, for example) this adds +- (sign)*one E self._exp to the number more efficiently than add. +- +- For example: +- Decimal('5.624e10')._increment() == Decimal('5.625e10') +- """ +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- return Decimal(self) # Must be infinite, and incrementing makes no difference +- +- L = list(self._int) +- L[-1] += 1 +- spot = len(L)-1 +- while L[spot] == 10: +- L[spot] = 0 +- if spot == 0: +- L[0:0] = [1] +- break +- L[spot-1] += 1 +- spot -= 1 +- ans = Decimal((self._sign, L, self._exp)) +- +- if context is None: +- context = getcontext() +- if round and context._rounding_decision == ALWAYS_ROUND: +- ans = ans._fix(context) +- return ans +- +- def __mul__(self, other, context=None): +- """Return self * other. +- +- (+-) INF * 0 (or its reverse) raise InvalidOperation. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if context is None: +- context = getcontext() +- +- resultsign = self._sign ^ other._sign +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- return ans +- +- if self._isinfinity(): +- if not other: +- return context._raise_error(InvalidOperation, '(+-)INF * 0') +- return Infsign[resultsign] +- +- if other._isinfinity(): +- if not self: +- return context._raise_error(InvalidOperation, '0 * (+-)INF') +- return Infsign[resultsign] +- +- resultexp = self._exp + other._exp +- shouldround = context._rounding_decision == ALWAYS_ROUND +- +- # Special case for multiplying by zero +- if not self or not other: +- ans = Decimal((resultsign, (0,), resultexp)) +- if shouldround: +- #Fixing in case the exponent is out of bounds +- ans = ans._fix(context) +- return ans +- +- # Special case for multiplying by power of 10 +- if self._int == (1,): +- ans = Decimal((resultsign, other._int, resultexp)) +- if shouldround: +- ans = ans._fix(context) +- return ans +- if other._int == (1,): +- ans = Decimal((resultsign, self._int, resultexp)) +- if shouldround: +- ans = ans._fix(context) +- return ans +- +- op1 = _WorkRep(self) +- op2 = _WorkRep(other) +- +- ans = Decimal( (resultsign, map(int, str(op1.int * op2.int)), resultexp)) +- if shouldround: +- ans = ans._fix(context) +- +- return ans +- __rmul__ = __mul__ +- +- def __div__(self, other, context=None): +- """Return self / other.""" +- return self._divide(other, context=context) +- __truediv__ = __div__ +- +- def _divide(self, other, divmod = 0, context=None): +- """Return a / b, to context.prec precision. +- +- divmod: +- 0 => true division +- 1 => (a //b, a%b) +- 2 => a //b +- 3 => a%b +- +- Actually, if divmod is 2 or 3 a tuple is returned, but errors for +- computing the other value are not raised. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- if divmod in (0, 1): +- return NotImplemented +- return (NotImplemented, NotImplemented) +- +- if context is None: +- context = getcontext() +- +- sign = self._sign ^ other._sign +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- if divmod: +- return (ans, ans) +- return ans +- +- if self._isinfinity() and other._isinfinity(): +- if divmod: +- return (context._raise_error(InvalidOperation, +- '(+-)INF // (+-)INF'), +- context._raise_error(InvalidOperation, +- '(+-)INF % (+-)INF')) +- return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF') +- +- if self._isinfinity(): +- if divmod == 1: +- return (Infsign[sign], +- context._raise_error(InvalidOperation, 'INF % x')) +- elif divmod == 2: +- return (Infsign[sign], NaN) +- elif divmod == 3: +- return (Infsign[sign], +- context._raise_error(InvalidOperation, 'INF % x')) +- return Infsign[sign] +- +- if other._isinfinity(): +- if divmod: +- return (Decimal((sign, (0,), 0)), Decimal(self)) +- context._raise_error(Clamped, 'Division by infinity') +- return Decimal((sign, (0,), context.Etiny())) +- +- # Special cases for zeroes +- if not self and not other: +- if divmod: +- return context._raise_error(DivisionUndefined, '0 / 0', 1) +- return context._raise_error(DivisionUndefined, '0 / 0') +- +- if not self: +- if divmod: +- otherside = Decimal(self) +- otherside._exp = min(self._exp, other._exp) +- return (Decimal((sign, (0,), 0)), otherside) +- exp = self._exp - other._exp +- if exp < context.Etiny(): +- exp = context.Etiny() +- context._raise_error(Clamped, '0e-x / y') +- if exp > context.Emax: +- exp = context.Emax +- context._raise_error(Clamped, '0e+x / y') +- return Decimal( (sign, (0,), exp) ) +- +- if not other: +- if divmod: +- return context._raise_error(DivisionByZero, 'divmod(x,0)', +- sign, 1) +- return context._raise_error(DivisionByZero, 'x / 0', sign) +- +- #OK, so neither = 0, INF or NaN +- +- shouldround = context._rounding_decision == ALWAYS_ROUND +- +- #If we're dividing into ints, and self < other, stop. +- #self.__abs__(0) does not round. +- if divmod and (self.__abs__(0, context) < other.__abs__(0, context)): +- +- if divmod == 1 or divmod == 3: +- exp = min(self._exp, other._exp) +- ans2 = self._rescale(exp, context=context, watchexp=0) +- if shouldround: +- ans2 = ans2._fix(context) +- return (Decimal( (sign, (0,), 0) ), +- ans2) +- +- elif divmod == 2: +- #Don't round the mod part, if we don't need it. +- return (Decimal( (sign, (0,), 0) ), Decimal(self)) +- +- op1 = _WorkRep(self) +- op2 = _WorkRep(other) +- op1, op2, adjust = _adjust_coefficients(op1, op2) +- res = _WorkRep( (sign, 0, (op1.exp - op2.exp)) ) +- if divmod and res.exp > context.prec + 1: +- return context._raise_error(DivisionImpossible) +- +- prec_limit = 10 ** context.prec +- while 1: +- while op2.int <= op1.int: +- res.int += 1 +- op1.int -= op2.int +- if res.exp == 0 and divmod: +- if res.int >= prec_limit and shouldround: +- return context._raise_error(DivisionImpossible) +- otherside = Decimal(op1) +- frozen = context._ignore_all_flags() +- +- exp = min(self._exp, other._exp) +- otherside = otherside._rescale(exp, context=context, watchexp=0) +- context._regard_flags(*frozen) +- if shouldround: +- otherside = otherside._fix(context) +- return (Decimal(res), otherside) +- +- if op1.int == 0 and adjust >= 0 and not divmod: +- break +- if res.int >= prec_limit and shouldround: +- if divmod: +- return context._raise_error(DivisionImpossible) +- shouldround=1 +- # Really, the answer is a bit higher, so adding a one to +- # the end will make sure the rounding is right. +- if op1.int != 0: +- res.int *= 10 +- res.int += 1 +- res.exp -= 1 +- +- break +- res.int *= 10 +- res.exp -= 1 +- adjust += 1 +- op1.int *= 10 +- op1.exp -= 1 +- +- if res.exp == 0 and divmod and op2.int > op1.int: +- #Solves an error in precision. Same as a previous block. +- +- if res.int >= prec_limit and shouldround: +- return context._raise_error(DivisionImpossible) +- otherside = Decimal(op1) +- frozen = context._ignore_all_flags() +- +- exp = min(self._exp, other._exp) +- otherside = otherside._rescale(exp, context=context) +- +- context._regard_flags(*frozen) +- +- return (Decimal(res), otherside) +- +- ans = Decimal(res) +- if shouldround: +- ans = ans._fix(context) +- return ans +- +- def __rdiv__(self, other, context=None): +- """Swaps self/other and returns __div__.""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- return other.__div__(self, context=context) +- __rtruediv__ = __rdiv__ +- +- def __divmod__(self, other, context=None): +- """ +- (self // other, self % other) +- """ +- return self._divide(other, 1, context) +- +- def __rdivmod__(self, other, context=None): +- """Swaps self/other and returns __divmod__.""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- return other.__divmod__(self, context=context) +- +- def __mod__(self, other, context=None): +- """ +- self % other +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- return ans +- +- if self and not other: +- return context._raise_error(InvalidOperation, 'x % 0') +- +- return self._divide(other, 3, context)[1] +- +- def __rmod__(self, other, context=None): +- """Swaps self/other and returns __mod__.""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- return other.__mod__(self, context=context) +- +- def remainder_near(self, other, context=None): +- """ +- Remainder nearest to 0- abs(remainder-near) <= other/2 +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- ans = self._check_nans(other, context) +- if ans: +- return ans +- if self and not other: +- return context._raise_error(InvalidOperation, 'x % 0') +- +- if context is None: +- context = getcontext() +- # If DivisionImpossible causes an error, do not leave Rounded/Inexact +- # ignored in the calling function. +- context = context._shallow_copy() +- flags = context._ignore_flags(Rounded, Inexact) +- #keep DivisionImpossible flags +- (side, r) = self.__divmod__(other, context=context) +- +- if r._isnan(): +- context._regard_flags(*flags) +- return r +- +- context = context._shallow_copy() +- rounding = context._set_rounding_decision(NEVER_ROUND) +- +- if other._sign: +- comparison = other.__div__(Decimal(-2), context=context) +- else: +- comparison = other.__div__(Decimal(2), context=context) +- +- context._set_rounding_decision(rounding) +- context._regard_flags(*flags) +- +- s1, s2 = r._sign, comparison._sign +- r._sign, comparison._sign = 0, 0 +- +- if r < comparison: +- r._sign, comparison._sign = s1, s2 +- #Get flags now +- self.__divmod__(other, context=context) +- return r._fix(context) +- r._sign, comparison._sign = s1, s2 +- +- rounding = context._set_rounding_decision(NEVER_ROUND) +- +- (side, r) = self.__divmod__(other, context=context) +- context._set_rounding_decision(rounding) +- if r._isnan(): +- return r +- +- decrease = not side._iseven() +- rounding = context._set_rounding_decision(NEVER_ROUND) +- side = side.__abs__(context=context) +- context._set_rounding_decision(rounding) +- +- s1, s2 = r._sign, comparison._sign +- r._sign, comparison._sign = 0, 0 +- if r > comparison or decrease and r == comparison: +- r._sign, comparison._sign = s1, s2 +- context.prec += 1 +- if len(side.__add__(Decimal(1), context=context)._int) >= context.prec: +- context.prec -= 1 +- return context._raise_error(DivisionImpossible)[1] +- context.prec -= 1 +- if self._sign == other._sign: +- r = r.__sub__(other, context=context) +- else: +- r = r.__add__(other, context=context) +- else: +- r._sign, comparison._sign = s1, s2 +- +- return r._fix(context) +- +- def __floordiv__(self, other, context=None): +- """self // other""" +- return self._divide(other, 2, context)[0] +- +- def __rfloordiv__(self, other, context=None): +- """Swaps self/other and returns __floordiv__.""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- return other.__floordiv__(self, context=context) +- +- def __float__(self): +- """Float representation.""" +- return float(str(self)) +- +- def __int__(self): +- """Converts self to an int, truncating if necessary.""" +- if self._is_special: +- if self._isnan(): +- context = getcontext() +- return context._raise_error(InvalidContext) +- elif self._isinfinity(): +- raise OverflowError, "Cannot convert infinity to long" +- if self._exp >= 0: +- s = ''.join(map(str, self._int)) + '0'*self._exp +- else: +- s = ''.join(map(str, self._int))[:self._exp] +- if s == '': +- s = '0' +- sign = '-'*self._sign +- return int(sign + s) +- +- def __long__(self): +- """Converts to a long. +- +- Equivalent to long(int(self)) +- """ +- return long(self.__int__()) +- +- def _fix(self, context): +- """Round if it is necessary to keep self within prec precision. +- +- Rounds and fixes the exponent. Does not raise on a sNaN. +- +- Arguments: +- self - Decimal instance +- context - context used. +- """ +- if self._is_special: +- return self +- if context is None: +- context = getcontext() +- prec = context.prec +- ans = self._fixexponents(context) +- if len(ans._int) > prec: +- ans = ans._round(prec, context=context) +- ans = ans._fixexponents(context) +- return ans +- +- def _fixexponents(self, context): +- """Fix the exponents and return a copy with the exponent in bounds. +- Only call if known to not be a special value. +- """ +- folddown = context._clamp +- Emin = context.Emin +- ans = self +- ans_adjusted = ans.adjusted() +- if ans_adjusted < Emin: +- Etiny = context.Etiny() +- if ans._exp < Etiny: +- if not ans: +- ans = Decimal(self) +- ans._exp = Etiny +- context._raise_error(Clamped) +- return ans +- ans = ans._rescale(Etiny, context=context) +- #It isn't zero, and exp < Emin => subnormal +- context._raise_error(Subnormal) +- if context.flags[Inexact]: +- context._raise_error(Underflow) +- else: +- if ans: +- #Only raise subnormal if non-zero. +- context._raise_error(Subnormal) +- else: +- Etop = context.Etop() +- if folddown and ans._exp > Etop: +- context._raise_error(Clamped) +- ans = ans._rescale(Etop, context=context) +- else: +- Emax = context.Emax +- if ans_adjusted > Emax: +- if not ans: +- ans = Decimal(self) +- ans._exp = Emax +- context._raise_error(Clamped) +- return ans +- context._raise_error(Inexact) +- context._raise_error(Rounded) +- return context._raise_error(Overflow, 'above Emax', ans._sign) +- return ans +- +- def _round(self, prec=None, rounding=None, context=None): +- """Returns a rounded version of self. +- +- You can specify the precision or rounding method. Otherwise, the +- context determines it. +- """ +- +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- if self._isinfinity(): +- return Decimal(self) +- +- if context is None: +- context = getcontext() +- +- if rounding is None: +- rounding = context.rounding +- if prec is None: +- prec = context.prec +- +- if not self: +- if prec <= 0: +- dig = (0,) +- exp = len(self._int) - prec + self._exp +- else: +- dig = (0,) * prec +- exp = len(self._int) + self._exp - prec +- ans = Decimal((self._sign, dig, exp)) +- context._raise_error(Rounded) +- return ans +- +- if prec == 0: +- temp = Decimal(self) +- temp._int = (0,)+temp._int +- prec = 1 +- elif prec < 0: +- exp = self._exp + len(self._int) - prec - 1 +- temp = Decimal( (self._sign, (0, 1), exp)) +- prec = 1 +- else: +- temp = Decimal(self) +- +- numdigits = len(temp._int) +- if prec == numdigits: +- return temp +- +- # See if we need to extend precision +- expdiff = prec - numdigits +- if expdiff > 0: +- tmp = list(temp._int) +- tmp.extend([0] * expdiff) +- ans = Decimal( (temp._sign, tmp, temp._exp - expdiff)) +- return ans +- +- #OK, but maybe all the lost digits are 0. +- lostdigits = self._int[expdiff:] +- if lostdigits == (0,) * len(lostdigits): +- ans = Decimal( (temp._sign, temp._int[:prec], temp._exp - expdiff)) +- #Rounded, but not Inexact +- context._raise_error(Rounded) +- return ans +- +- # Okay, let's round and lose data +- +- this_function = getattr(temp, self._pick_rounding_function[rounding]) +- #Now we've got the rounding function +- +- if prec != context.prec: +- context = context._shallow_copy() +- context.prec = prec +- ans = this_function(prec, expdiff, context) +- context._raise_error(Rounded) +- context._raise_error(Inexact, 'Changed in rounding') +- +- return ans +- +- _pick_rounding_function = {} +- +- def _round_down(self, prec, expdiff, context): +- """Also known as round-towards-0, truncate.""" +- return Decimal( (self._sign, self._int[:prec], self._exp - expdiff) ) +- +- def _round_half_up(self, prec, expdiff, context, tmp = None): +- """Rounds 5 up (away from 0)""" +- +- if tmp is None: +- tmp = Decimal( (self._sign,self._int[:prec], self._exp - expdiff)) +- if self._int[prec] >= 5: +- tmp = tmp._increment(round=0, context=context) +- if len(tmp._int) > prec: +- return Decimal( (tmp._sign, tmp._int[:-1], tmp._exp + 1)) +- return tmp +- +- def _round_half_even(self, prec, expdiff, context): +- """Round 5 to even, rest to nearest.""" +- +- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff)) +- half = (self._int[prec] == 5) +- if half: +- for digit in self._int[prec+1:]: +- if digit != 0: +- half = 0 +- break +- if half: +- if self._int[prec-1] & 1 == 0: +- return tmp +- return self._round_half_up(prec, expdiff, context, tmp) +- +- def _round_half_down(self, prec, expdiff, context): +- """Round 5 down""" +- +- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff)) +- half = (self._int[prec] == 5) +- if half: +- for digit in self._int[prec+1:]: +- if digit != 0: +- half = 0 +- break +- if half: +- return tmp +- return self._round_half_up(prec, expdiff, context, tmp) +- +- def _round_up(self, prec, expdiff, context): +- """Rounds away from 0.""" +- tmp = Decimal( (self._sign, self._int[:prec], self._exp - expdiff) ) +- for digit in self._int[prec:]: +- if digit != 0: +- tmp = tmp._increment(round=1, context=context) +- if len(tmp._int) > prec: +- return Decimal( (tmp._sign, tmp._int[:-1], tmp._exp + 1)) +- else: +- return tmp +- return tmp +- +- def _round_ceiling(self, prec, expdiff, context): +- """Rounds up (not away from 0 if negative.)""" +- if self._sign: +- return self._round_down(prec, expdiff, context) +- else: +- return self._round_up(prec, expdiff, context) +- +- def _round_floor(self, prec, expdiff, context): +- """Rounds down (not towards 0 if negative)""" +- if not self._sign: +- return self._round_down(prec, expdiff, context) +- else: +- return self._round_up(prec, expdiff, context) +- +- def __pow__(self, n, modulo = None, context=None): +- """Return self ** n (mod modulo) +- +- If modulo is None (default), don't take it mod modulo. +- """ +- n = _convert_other(n) +- if n is NotImplemented: +- return n +- +- if context is None: +- context = getcontext() +- +- if self._is_special or n._is_special or n.adjusted() > 8: +- #Because the spot << doesn't work with really big exponents +- if n._isinfinity() or n.adjusted() > 8: +- return context._raise_error(InvalidOperation, 'x ** INF') +- +- ans = self._check_nans(n, context) +- if ans: +- return ans +- +- if not n._isinteger(): +- return context._raise_error(InvalidOperation, 'x ** (non-integer)') +- +- if not self and not n: +- return context._raise_error(InvalidOperation, '0 ** 0') +- +- if not n: +- return Decimal(1) +- +- if self == Decimal(1): +- return Decimal(1) +- +- sign = self._sign and not n._iseven() +- n = int(n) +- +- if self._isinfinity(): +- if modulo: +- return context._raise_error(InvalidOperation, 'INF % x') +- if n > 0: +- return Infsign[sign] +- return Decimal( (sign, (0,), 0) ) +- +- #with ludicrously large exponent, just raise an overflow and return inf. +- if not modulo and n > 0 and (self._exp + len(self._int) - 1) * n > context.Emax \ +- and self: +- +- tmp = Decimal('inf') +- tmp._sign = sign +- context._raise_error(Rounded) +- context._raise_error(Inexact) +- context._raise_error(Overflow, 'Big power', sign) +- return tmp +- +- elength = len(str(abs(n))) +- firstprec = context.prec +- +- if not modulo and firstprec + elength + 1 > DefaultContext.Emax: +- return context._raise_error(Overflow, 'Too much precision.', sign) +- +- mul = Decimal(self) +- val = Decimal(1) +- context = context._shallow_copy() +- context.prec = firstprec + elength + 1 +- if n < 0: +- #n is a long now, not Decimal instance +- n = -n +- mul = Decimal(1).__div__(mul, context=context) +- +- spot = 1 +- while spot <= n: +- spot <<= 1 +- +- spot >>= 1 +- #Spot is the highest power of 2 less than n +- while spot: +- val = val.__mul__(val, context=context) +- if val._isinfinity(): +- val = Infsign[sign] +- break +- if spot & n: +- val = val.__mul__(mul, context=context) +- if modulo is not None: +- val = val.__mod__(modulo, context=context) +- spot >>= 1 +- context.prec = firstprec +- +- if context._rounding_decision == ALWAYS_ROUND: +- return val._fix(context) +- return val +- +- def __rpow__(self, other, context=None): +- """Swaps self/other and returns __pow__.""" +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- return other.__pow__(self, context=context) +- +- def normalize(self, context=None): +- """Normalize- strip trailing 0s, change anything equal to 0 to 0e0""" +- +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- dup = self._fix(context) +- if dup._isinfinity(): +- return dup +- +- if not dup: +- return Decimal( (dup._sign, (0,), 0) ) +- end = len(dup._int) +- exp = dup._exp +- while dup._int[end-1] == 0: +- exp += 1 +- end -= 1 +- return Decimal( (dup._sign, dup._int[:end], exp) ) +- +- +- def quantize(self, exp, rounding=None, context=None, watchexp=1): +- """Quantize self so its exponent is the same as that of exp. +- +- Similar to self._rescale(exp._exp) but with error checking. +- """ +- if self._is_special or exp._is_special: +- ans = self._check_nans(exp, context) +- if ans: +- return ans +- +- if exp._isinfinity() or self._isinfinity(): +- if exp._isinfinity() and self._isinfinity(): +- return self #if both are inf, it is OK +- if context is None: +- context = getcontext() +- return context._raise_error(InvalidOperation, +- 'quantize with one INF') +- return self._rescale(exp._exp, rounding, context, watchexp) +- +- def same_quantum(self, other): +- """Test whether self and other have the same exponent. +- +- same as self._exp == other._exp, except NaN == sNaN +- """ +- if self._is_special or other._is_special: +- if self._isnan() or other._isnan(): +- return self._isnan() and other._isnan() and True +- if self._isinfinity() or other._isinfinity(): +- return self._isinfinity() and other._isinfinity() and True +- return self._exp == other._exp +- +- def _rescale(self, exp, rounding=None, context=None, watchexp=1): +- """Rescales so that the exponent is exp. +- +- exp = exp to scale to (an integer) +- rounding = rounding version +- watchexp: if set (default) an error is returned if exp is greater +- than Emax or less than Etiny. +- """ +- if context is None: +- context = getcontext() +- +- if self._is_special: +- if self._isinfinity(): +- return context._raise_error(InvalidOperation, 'rescale with an INF') +- +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- if watchexp and (context.Emax < exp or context.Etiny() > exp): +- return context._raise_error(InvalidOperation, 'rescale(a, INF)') +- +- if not self: +- ans = Decimal(self) +- ans._int = (0,) +- ans._exp = exp +- return ans +- +- diff = self._exp - exp +- digits = len(self._int) + diff +- +- if watchexp and digits > context.prec: +- return context._raise_error(InvalidOperation, 'Rescale > prec') +- +- tmp = Decimal(self) +- tmp._int = (0,) + tmp._int +- digits += 1 +- +- if digits < 0: +- tmp._exp = -digits + tmp._exp +- tmp._int = (0,1) +- digits = 1 +- tmp = tmp._round(digits, rounding, context=context) +- +- if tmp._int[0] == 0 and len(tmp._int) > 1: +- tmp._int = tmp._int[1:] +- tmp._exp = exp +- +- tmp_adjusted = tmp.adjusted() +- if tmp and tmp_adjusted < context.Emin: +- context._raise_error(Subnormal) +- elif tmp and tmp_adjusted > context.Emax: +- return context._raise_error(InvalidOperation, 'rescale(a, INF)') +- return tmp +- +- def to_integral(self, rounding=None, context=None): +- """Rounds to the nearest integer, without raising inexact, rounded.""" +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- if self._exp >= 0: +- return self +- if context is None: +- context = getcontext() +- flags = context._ignore_flags(Rounded, Inexact) +- ans = self._rescale(0, rounding, context=context) +- context._regard_flags(flags) +- return ans +- +- def sqrt(self, context=None): +- """Return the square root of self. +- +- Uses a converging algorithm (Xn+1 = 0.5*(Xn + self / Xn)) +- Should quadratically approach the right answer. +- """ +- if self._is_special: +- ans = self._check_nans(context=context) +- if ans: +- return ans +- +- if self._isinfinity() and self._sign == 0: +- return Decimal(self) +- +- if not self: +- #exponent = self._exp / 2, using round_down. +- #if self._exp < 0: +- # exp = (self._exp+1) // 2 +- #else: +- exp = (self._exp) // 2 +- if self._sign == 1: +- #sqrt(-0) = -0 +- return Decimal( (1, (0,), exp)) +- else: +- return Decimal( (0, (0,), exp)) +- +- if context is None: +- context = getcontext() +- +- if self._sign == 1: +- return context._raise_error(InvalidOperation, 'sqrt(-x), x > 0') +- +- tmp = Decimal(self) +- +- expadd = tmp._exp // 2 +- if tmp._exp & 1: +- tmp._int += (0,) +- tmp._exp = 0 +- else: +- tmp._exp = 0 +- +- context = context._shallow_copy() +- flags = context._ignore_all_flags() +- firstprec = context.prec +- context.prec = 3 +- if tmp.adjusted() & 1 == 0: +- ans = Decimal( (0, (8,1,9), tmp.adjusted() - 2) ) +- ans = ans.__add__(tmp.__mul__(Decimal((0, (2,5,9), -2)), +- context=context), context=context) +- ans._exp -= 1 + tmp.adjusted() // 2 +- else: +- ans = Decimal( (0, (2,5,9), tmp._exp + len(tmp._int)- 3) ) +- ans = ans.__add__(tmp.__mul__(Decimal((0, (8,1,9), -3)), +- context=context), context=context) +- ans._exp -= 1 + tmp.adjusted() // 2 +- +- #ans is now a linear approximation. +- +- Emax, Emin = context.Emax, context.Emin +- context.Emax, context.Emin = DefaultContext.Emax, DefaultContext.Emin +- +- half = Decimal('0.5') +- +- maxp = firstprec + 2 +- rounding = context._set_rounding(ROUND_HALF_EVEN) +- while 1: +- context.prec = min(2*context.prec - 2, maxp) +- ans = half.__mul__(ans.__add__(tmp.__div__(ans, context=context), +- context=context), context=context) +- if context.prec == maxp: +- break +- +- #round to the answer's precision-- the only error can be 1 ulp. +- context.prec = firstprec +- prevexp = ans.adjusted() +- ans = ans._round(context=context) +- +- #Now, check if the other last digits are better. +- context.prec = firstprec + 1 +- # In case we rounded up another digit and we should actually go lower. +- if prevexp != ans.adjusted(): +- ans._int += (0,) +- ans._exp -= 1 +- +- +- lower = ans.__sub__(Decimal((0, (5,), ans._exp-1)), context=context) +- context._set_rounding(ROUND_UP) +- if lower.__mul__(lower, context=context) > (tmp): +- ans = ans.__sub__(Decimal((0, (1,), ans._exp)), context=context) +- +- else: +- upper = ans.__add__(Decimal((0, (5,), ans._exp-1)),context=context) +- context._set_rounding(ROUND_DOWN) +- if upper.__mul__(upper, context=context) < tmp: +- ans = ans.__add__(Decimal((0, (1,), ans._exp)),context=context) +- +- ans._exp += expadd +- +- context.prec = firstprec +- context.rounding = rounding +- ans = ans._fix(context) +- +- rounding = context._set_rounding_decision(NEVER_ROUND) +- if not ans.__mul__(ans, context=context) == self: +- # Only rounded/inexact if here. +- context._regard_flags(flags) +- context._raise_error(Rounded) +- context._raise_error(Inexact) +- else: +- #Exact answer, so let's set the exponent right. +- #if self._exp < 0: +- # exp = (self._exp +1)// 2 +- #else: +- exp = self._exp // 2 +- context.prec += ans._exp - exp +- ans = ans._rescale(exp, context=context) +- context.prec = firstprec +- context._regard_flags(flags) +- context.Emax, context.Emin = Emax, Emin +- +- return ans._fix(context) +- +- def max(self, other, context=None): +- """Returns the larger value. +- +- like max(self, other) except if one is not a number, returns +- NaN (and signals if one is sNaN). Also rounds. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- # if one operand is a quiet NaN and the other is number, then the +- # number is always returned +- sn = self._isnan() +- on = other._isnan() +- if sn or on: +- if on == 1 and sn != 2: +- return self +- if sn == 1 and on != 2: +- return other +- return self._check_nans(other, context) +- +- ans = self +- c = self.__cmp__(other) +- if c == 0: +- # if both operands are finite and equal in numerical value +- # then an ordering is applied: +- # +- # if the signs differ then max returns the operand with the +- # positive sign and min returns the operand with the negative sign +- # +- # if the signs are the same then the exponent is used to select +- # the result. +- if self._sign != other._sign: +- if self._sign: +- ans = other +- elif self._exp < other._exp and not self._sign: +- ans = other +- elif self._exp > other._exp and self._sign: +- ans = other +- elif c == -1: +- ans = other +- +- if context is None: +- context = getcontext() +- if context._rounding_decision == ALWAYS_ROUND: +- return ans._fix(context) +- return ans +- +- def min(self, other, context=None): +- """Returns the smaller value. +- +- like min(self, other) except if one is not a number, returns +- NaN (and signals if one is sNaN). Also rounds. +- """ +- other = _convert_other(other) +- if other is NotImplemented: +- return other +- +- if self._is_special or other._is_special: +- # if one operand is a quiet NaN and the other is number, then the +- # number is always returned +- sn = self._isnan() +- on = other._isnan() +- if sn or on: +- if on == 1 and sn != 2: +- return self +- if sn == 1 and on != 2: +- return other +- return self._check_nans(other, context) +- +- ans = self +- c = self.__cmp__(other) +- if c == 0: +- # if both operands are finite and equal in numerical value +- # then an ordering is applied: +- # +- # if the signs differ then max returns the operand with the +- # positive sign and min returns the operand with the negative sign +- # +- # if the signs are the same then the exponent is used to select +- # the result. +- if self._sign != other._sign: +- if other._sign: +- ans = other +- elif self._exp > other._exp and not self._sign: +- ans = other +- elif self._exp < other._exp and self._sign: +- ans = other +- elif c == 1: +- ans = other +- +- if context is None: +- context = getcontext() +- if context._rounding_decision == ALWAYS_ROUND: +- return ans._fix(context) +- return ans +- +- def _isinteger(self): +- """Returns whether self is an integer""" +- if self._exp >= 0: +- return True +- rest = self._int[self._exp:] +- return rest == (0,)*len(rest) +- +- def _iseven(self): +- """Returns 1 if self is even. Assumes self is an integer.""" +- if self._exp > 0: +- return 1 +- return self._int[-1+self._exp] & 1 == 0 +- +- def adjusted(self): +- """Return the adjusted exponent of self""" +- try: +- return self._exp + len(self._int) - 1 +- #If NaN or Infinity, self._exp is string +- except TypeError: +- return 0 +- +- # support for pickling, copy, and deepcopy +- def __reduce__(self): +- return (self.__class__, (str(self),)) +- +- def __copy__(self): +- if type(self) == Decimal: +- return self # I'm immutable; therefore I am my own clone +- return self.__class__(str(self)) +- +- def __deepcopy__(self, memo): +- if type(self) == Decimal: +- return self # My components are also immutable +- return self.__class__(str(self)) +- +-##### Context class ########################################### +- +- +-# get rounding method function: +-rounding_functions = [name for name in Decimal.__dict__.keys() if name.startswith('_round_')] +-for name in rounding_functions: +- #name is like _round_half_even, goes to the global ROUND_HALF_EVEN value. +- globalname = name[1:].upper() +- val = globals()[globalname] +- Decimal._pick_rounding_function[val] = name +- +-del name, val, globalname, rounding_functions +- +-class Context(object): +- """Contains the context for a Decimal instance. +- +- Contains: +- prec - precision (for use in rounding, division, square roots..) +- rounding - rounding type. (how you round) +- _rounding_decision - ALWAYS_ROUND, NEVER_ROUND -- do you round? +- traps - If traps[exception] = 1, then the exception is +- raised when it is caused. Otherwise, a value is +- substituted in. +- flags - When an exception is caused, flags[exception] is incremented. +- (Whether or not the trap_enabler is set) +- Should be reset by user of Decimal instance. +- Emin - Minimum exponent +- Emax - Maximum exponent +- capitals - If 1, 1*10^1 is printed as 1E+1. +- If 0, printed as 1e1 +- _clamp - If 1, change exponents if too high (Default 0) +- """ +- +- def __init__(self, prec=None, rounding=None, +- traps=None, flags=None, +- _rounding_decision=None, +- Emin=None, Emax=None, +- capitals=None, _clamp=0, +- _ignored_flags=None): +- if flags is None: +- flags = [] +- if _ignored_flags is None: +- _ignored_flags = [] +- if not isinstance(flags, dict): +- flags = dict([(s,s in flags) for s in _signals]) +- del s +- if traps is not None and not isinstance(traps, dict): +- traps = dict([(s,s in traps) for s in _signals]) +- del s +- for name, val in locals().items(): +- if val is None: +- setattr(self, name, _copy.copy(getattr(DefaultContext, name))) +- else: +- setattr(self, name, val) +- del self.self +- +- def __repr__(self): +- """Show the current context.""" +- s = [] +- s.append('Context(prec=%(prec)d, rounding=%(rounding)s, Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' % vars(self)) +- s.append('flags=[' + ', '.join([f.__name__ for f, v in self.flags.items() if v]) + ']') +- s.append('traps=[' + ', '.join([t.__name__ for t, v in self.traps.items() if v]) + ']') +- return ', '.join(s) + ')' +- +- def clear_flags(self): +- """Reset all flags to zero""" +- for flag in self.flags: +- self.flags[flag] = 0 +- +- def _shallow_copy(self): +- """Returns a shallow copy from self.""" +- nc = Context(self.prec, self.rounding, self.traps, self.flags, +- self._rounding_decision, self.Emin, self.Emax, +- self.capitals, self._clamp, self._ignored_flags) +- return nc +- +- def copy(self): +- """Returns a deep copy from self.""" +- nc = Context(self.prec, self.rounding, self.traps.copy(), self.flags.copy(), +- self._rounding_decision, self.Emin, self.Emax, +- self.capitals, self._clamp, self._ignored_flags) +- return nc +- __copy__ = copy +- +- def _raise_error(self, condition, explanation = None, *args): +- """Handles an error +- +- If the flag is in _ignored_flags, returns the default response. +- Otherwise, it increments the flag, then, if the corresponding +- trap_enabler is set, it reaises the exception. Otherwise, it returns +- the default value after incrementing the flag. +- """ +- error = _condition_map.get(condition, condition) +- if error in self._ignored_flags: +- #Don't touch the flag +- return error().handle(self, *args) +- +- self.flags[error] += 1 +- if not self.traps[error]: +- #The errors define how to handle themselves. +- return condition().handle(self, *args) +- +- # Errors should only be risked on copies of the context +- #self._ignored_flags = [] +- raise error, explanation +- +- def _ignore_all_flags(self): +- """Ignore all flags, if they are raised""" +- return self._ignore_flags(*_signals) +- +- def _ignore_flags(self, *flags): +- """Ignore the flags, if they are raised""" +- # Do not mutate-- This way, copies of a context leave the original +- # alone. +- self._ignored_flags = (self._ignored_flags + list(flags)) +- return list(flags) +- +- def _regard_flags(self, *flags): +- """Stop ignoring the flags, if they are raised""" +- if flags and isinstance(flags[0], (tuple,list)): +- flags = flags[0] +- for flag in flags: +- self._ignored_flags.remove(flag) +- +- def __hash__(self): +- """A Context cannot be hashed.""" +- # We inherit object.__hash__, so we must deny this explicitly +- raise TypeError, "Cannot hash a Context." +- +- def Etiny(self): +- """Returns Etiny (= Emin - prec + 1)""" +- return int(self.Emin - self.prec + 1) +- +- def Etop(self): +- """Returns maximum exponent (= Emax - prec + 1)""" +- return int(self.Emax - self.prec + 1) +- +- def _set_rounding_decision(self, type): +- """Sets the rounding decision. +- +- Sets the rounding decision, and returns the current (previous) +- rounding decision. Often used like: +- +- context = context._shallow_copy() +- # That so you don't change the calling context +- # if an error occurs in the middle (say DivisionImpossible is raised). +- +- rounding = context._set_rounding_decision(NEVER_ROUND) +- instance = instance / Decimal(2) +- context._set_rounding_decision(rounding) +- +- This will make it not round for that operation. +- """ +- +- rounding = self._rounding_decision +- self._rounding_decision = type +- return rounding +- +- def _set_rounding(self, type): +- """Sets the rounding type. +- +- Sets the rounding type, and returns the current (previous) +- rounding type. Often used like: +- +- context = context.copy() +- # so you don't change the calling context +- # if an error occurs in the middle. +- rounding = context._set_rounding(ROUND_UP) +- val = self.__sub__(other, context=context) +- context._set_rounding(rounding) +- +- This will make it round up for that operation. +- """ +- rounding = self.rounding +- self.rounding= type +- return rounding +- +- def create_decimal(self, num='0'): +- """Creates a new Decimal instance but using self as context.""" +- d = Decimal(num, context=self) +- return d._fix(self) +- +- #Methods +- def abs(self, a): +- """Returns the absolute value of the operand. +- +- If the operand is negative, the result is the same as using the minus +- operation on the operand. Otherwise, the result is the same as using +- the plus operation on the operand. +- +- >>> ExtendedContext.abs(Decimal('2.1')) +- Decimal("2.1") +- >>> ExtendedContext.abs(Decimal('-100')) +- Decimal("100") +- >>> ExtendedContext.abs(Decimal('101.5')) +- Decimal("101.5") +- >>> ExtendedContext.abs(Decimal('-101.5')) +- Decimal("101.5") +- """ +- return a.__abs__(context=self) +- +- def add(self, a, b): +- """Return the sum of the two operands. +- +- >>> ExtendedContext.add(Decimal('12'), Decimal('7.00')) +- Decimal("19.00") +- >>> ExtendedContext.add(Decimal('1E+2'), Decimal('1.01E+4')) +- Decimal("1.02E+4") +- """ +- return a.__add__(b, context=self) +- +- def _apply(self, a): +- return str(a._fix(self)) +- +- def compare(self, a, b): +- """Compares values numerically. +- +- If the signs of the operands differ, a value representing each operand +- ('-1' if the operand is less than zero, '0' if the operand is zero or +- negative zero, or '1' if the operand is greater than zero) is used in +- place of that operand for the comparison instead of the actual +- operand. +- +- The comparison is then effected by subtracting the second operand from +- the first and then returning a value according to the result of the +- subtraction: '-1' if the result is less than zero, '0' if the result is +- zero or negative zero, or '1' if the result is greater than zero. +- +- >>> ExtendedContext.compare(Decimal('2.1'), Decimal('3')) +- Decimal("-1") +- >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.1')) +- Decimal("0") +- >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.10')) +- Decimal("0") +- >>> ExtendedContext.compare(Decimal('3'), Decimal('2.1')) +- Decimal("1") +- >>> ExtendedContext.compare(Decimal('2.1'), Decimal('-3')) +- Decimal("1") +- >>> ExtendedContext.compare(Decimal('-3'), Decimal('2.1')) +- Decimal("-1") +- """ +- return a.compare(b, context=self) +- +- def divide(self, a, b): +- """Decimal division in a specified context. +- +- >>> ExtendedContext.divide(Decimal('1'), Decimal('3')) +- Decimal("0.333333333") +- >>> ExtendedContext.divide(Decimal('2'), Decimal('3')) +- Decimal("0.666666667") +- >>> ExtendedContext.divide(Decimal('5'), Decimal('2')) +- Decimal("2.5") +- >>> ExtendedContext.divide(Decimal('1'), Decimal('10')) +- Decimal("0.1") +- >>> ExtendedContext.divide(Decimal('12'), Decimal('12')) +- Decimal("1") +- >>> ExtendedContext.divide(Decimal('8.00'), Decimal('2')) +- Decimal("4.00") +- >>> ExtendedContext.divide(Decimal('2.400'), Decimal('2.0')) +- Decimal("1.20") +- >>> ExtendedContext.divide(Decimal('1000'), Decimal('100')) +- Decimal("10") +- >>> ExtendedContext.divide(Decimal('1000'), Decimal('1')) +- Decimal("1000") +- >>> ExtendedContext.divide(Decimal('2.40E+6'), Decimal('2')) +- Decimal("1.20E+6") +- """ +- return a.__div__(b, context=self) +- +- def divide_int(self, a, b): +- """Divides two numbers and returns the integer part of the result. +- +- >>> ExtendedContext.divide_int(Decimal('2'), Decimal('3')) +- Decimal("0") +- >>> ExtendedContext.divide_int(Decimal('10'), Decimal('3')) +- Decimal("3") +- >>> ExtendedContext.divide_int(Decimal('1'), Decimal('0.3')) +- Decimal("3") +- """ +- return a.__floordiv__(b, context=self) +- +- def divmod(self, a, b): +- return a.__divmod__(b, context=self) +- +- def max(self, a,b): +- """max compares two values numerically and returns the maximum. +- +- If either operand is a NaN then the general rules apply. +- Otherwise, the operands are compared as as though by the compare +- operation. If they are numerically equal then the left-hand operand +- is chosen as the result. Otherwise the maximum (closer to positive +- infinity) of the two operands is chosen as the result. +- +- >>> ExtendedContext.max(Decimal('3'), Decimal('2')) +- Decimal("3") +- >>> ExtendedContext.max(Decimal('-10'), Decimal('3')) +- Decimal("3") +- >>> ExtendedContext.max(Decimal('1.0'), Decimal('1')) +- Decimal("1") +- >>> ExtendedContext.max(Decimal('7'), Decimal('NaN')) +- Decimal("7") +- """ +- return a.max(b, context=self) +- +- def min(self, a,b): +- """min compares two values numerically and returns the minimum. +- +- If either operand is a NaN then the general rules apply. +- Otherwise, the operands are compared as as though by the compare +- operation. If they are numerically equal then the left-hand operand +- is chosen as the result. Otherwise the minimum (closer to negative +- infinity) of the two operands is chosen as the result. +- +- >>> ExtendedContext.min(Decimal('3'), Decimal('2')) +- Decimal("2") +- >>> ExtendedContext.min(Decimal('-10'), Decimal('3')) +- Decimal("-10") +- >>> ExtendedContext.min(Decimal('1.0'), Decimal('1')) +- Decimal("1.0") +- >>> ExtendedContext.min(Decimal('7'), Decimal('NaN')) +- Decimal("7") +- """ +- return a.min(b, context=self) +- +- def minus(self, a): +- """Minus corresponds to unary prefix minus in Python. +- +- The operation is evaluated using the same rules as subtract; the +- operation minus(a) is calculated as subtract('0', a) where the '0' +- has the same exponent as the operand. +- +- >>> ExtendedContext.minus(Decimal('1.3')) +- Decimal("-1.3") +- >>> ExtendedContext.minus(Decimal('-1.3')) +- Decimal("1.3") +- """ +- return a.__neg__(context=self) +- +- def multiply(self, a, b): +- """multiply multiplies two operands. +- +- If either operand is a special value then the general rules apply. +- Otherwise, the operands are multiplied together ('long multiplication'), +- resulting in a number which may be as long as the sum of the lengths +- of the two operands. +- +- >>> ExtendedContext.multiply(Decimal('1.20'), Decimal('3')) +- Decimal("3.60") +- >>> ExtendedContext.multiply(Decimal('7'), Decimal('3')) +- Decimal("21") +- >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('0.8')) +- Decimal("0.72") +- >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('-0')) +- Decimal("-0.0") +- >>> ExtendedContext.multiply(Decimal('654321'), Decimal('654321')) +- Decimal("4.28135971E+11") +- """ +- return a.__mul__(b, context=self) +- +- def normalize(self, a): +- """normalize reduces an operand to its simplest form. +- +- Essentially a plus operation with all trailing zeros removed from the +- result. +- +- >>> ExtendedContext.normalize(Decimal('2.1')) +- Decimal("2.1") +- >>> ExtendedContext.normalize(Decimal('-2.0')) +- Decimal("-2") +- >>> ExtendedContext.normalize(Decimal('1.200')) +- Decimal("1.2") +- >>> ExtendedContext.normalize(Decimal('-120')) +- Decimal("-1.2E+2") +- >>> ExtendedContext.normalize(Decimal('120.00')) +- Decimal("1.2E+2") +- >>> ExtendedContext.normalize(Decimal('0.00')) +- Decimal("0") +- """ +- return a.normalize(context=self) +- +- def plus(self, a): +- """Plus corresponds to unary prefix plus in Python. +- +- The operation is evaluated using the same rules as add; the +- operation plus(a) is calculated as add('0', a) where the '0' +- has the same exponent as the operand. +- +- >>> ExtendedContext.plus(Decimal('1.3')) +- Decimal("1.3") +- >>> ExtendedContext.plus(Decimal('-1.3')) +- Decimal("-1.3") +- """ +- return a.__pos__(context=self) +- +- def power(self, a, b, modulo=None): +- """Raises a to the power of b, to modulo if given. +- +- The right-hand operand must be a whole number whose integer part (after +- any exponent has been applied) has no more than 9 digits and whose +- fractional part (if any) is all zeros before any rounding. The operand +- may be positive, negative, or zero; if negative, the absolute value of +- the power is used, and the left-hand operand is inverted (divided into +- 1) before use. +- +- If the increased precision needed for the intermediate calculations +- exceeds the capabilities of the implementation then an Invalid operation +- condition is raised. +- +- If, when raising to a negative power, an underflow occurs during the +- division into 1, the operation is not halted at that point but +- continues. +- +- >>> ExtendedContext.power(Decimal('2'), Decimal('3')) +- Decimal("8") +- >>> ExtendedContext.power(Decimal('2'), Decimal('-3')) +- Decimal("0.125") +- >>> ExtendedContext.power(Decimal('1.7'), Decimal('8')) +- Decimal("69.7575744") +- >>> ExtendedContext.power(Decimal('Infinity'), Decimal('-2')) +- Decimal("0") +- >>> ExtendedContext.power(Decimal('Infinity'), Decimal('-1')) +- Decimal("0") +- >>> ExtendedContext.power(Decimal('Infinity'), Decimal('0')) +- Decimal("1") +- >>> ExtendedContext.power(Decimal('Infinity'), Decimal('1')) +- Decimal("Infinity") +- >>> ExtendedContext.power(Decimal('Infinity'), Decimal('2')) +- Decimal("Infinity") +- >>> ExtendedContext.power(Decimal('-Infinity'), Decimal('-2')) +- Decimal("0") +- >>> ExtendedContext.power(Decimal('-Infinity'), Decimal('-1')) +- Decimal("-0") +- >>> ExtendedContext.power(Decimal('-Infinity'), Decimal('0')) +- Decimal("1") +- >>> ExtendedContext.power(Decimal('-Infinity'), Decimal('1')) +- Decimal("-Infinity") +- >>> ExtendedContext.power(Decimal('-Infinity'), Decimal('2')) +- Decimal("Infinity") +- >>> ExtendedContext.power(Decimal('0'), Decimal('0')) +- Decimal("NaN") +- """ +- return a.__pow__(b, modulo, context=self) +- +- def quantize(self, a, b): +- """Returns a value equal to 'a' (rounded) and having the exponent of 'b'. +- +- The coefficient of the result is derived from that of the left-hand +- operand. It may be rounded using the current rounding setting (if the +- exponent is being increased), multiplied by a positive power of ten (if +- the exponent is being decreased), or is unchanged (if the exponent is +- already equal to that of the right-hand operand). +- +- Unlike other operations, if the length of the coefficient after the +- quantize operation would be greater than precision then an Invalid +- operation condition is raised. This guarantees that, unless there is an +- error condition, the exponent of the result of a quantize is always +- equal to that of the right-hand operand. +- +- Also unlike other operations, quantize will never raise Underflow, even +- if the result is subnormal and inexact. +- +- >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.001')) +- Decimal("2.170") +- >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.01')) +- Decimal("2.17") +- >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.1')) +- Decimal("2.2") +- >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+0')) +- Decimal("2") +- >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+1')) +- Decimal("0E+1") +- >>> ExtendedContext.quantize(Decimal('-Inf'), Decimal('Infinity')) +- Decimal("-Infinity") +- >>> ExtendedContext.quantize(Decimal('2'), Decimal('Infinity')) +- Decimal("NaN") +- >>> ExtendedContext.quantize(Decimal('-0.1'), Decimal('1')) +- Decimal("-0") +- >>> ExtendedContext.quantize(Decimal('-0'), Decimal('1e+5')) +- Decimal("-0E+5") +- >>> ExtendedContext.quantize(Decimal('+35236450.6'), Decimal('1e-2')) +- Decimal("NaN") +- >>> ExtendedContext.quantize(Decimal('-35236450.6'), Decimal('1e-2')) +- Decimal("NaN") +- >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-1')) +- Decimal("217.0") +- >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-0')) +- Decimal("217") +- >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e+1')) +- Decimal("2.2E+2") +- >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e+2')) +- Decimal("2E+2") +- """ +- return a.quantize(b, context=self) +- +- def remainder(self, a, b): +- """Returns the remainder from integer division. +- +- The result is the residue of the dividend after the operation of +- calculating integer division as described for divide-integer, rounded to +- precision digits if necessary. The sign of the result, if non-zero, is +- the same as that of the original dividend. +- +- This operation will fail under the same conditions as integer division +- (that is, if integer division on the same two operands would fail, the +- remainder cannot be calculated). +- +- >>> ExtendedContext.remainder(Decimal('2.1'), Decimal('3')) +- Decimal("2.1") +- >>> ExtendedContext.remainder(Decimal('10'), Decimal('3')) +- Decimal("1") +- >>> ExtendedContext.remainder(Decimal('-10'), Decimal('3')) +- Decimal("-1") +- >>> ExtendedContext.remainder(Decimal('10.2'), Decimal('1')) +- Decimal("0.2") +- >>> ExtendedContext.remainder(Decimal('10'), Decimal('0.3')) +- Decimal("0.1") +- >>> ExtendedContext.remainder(Decimal('3.6'), Decimal('1.3')) +- Decimal("1.0") +- """ +- return a.__mod__(b, context=self) +- +- def remainder_near(self, a, b): +- """Returns to be "a - b * n", where n is the integer nearest the exact +- value of "x / b" (if two integers are equally near then the even one +- is chosen). If the result is equal to 0 then its sign will be the +- sign of a. +- +- This operation will fail under the same conditions as integer division +- (that is, if integer division on the same two operands would fail, the +- remainder cannot be calculated). +- +- >>> ExtendedContext.remainder_near(Decimal('2.1'), Decimal('3')) +- Decimal("-0.9") +- >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('6')) +- Decimal("-2") +- >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('3')) +- Decimal("1") +- >>> ExtendedContext.remainder_near(Decimal('-10'), Decimal('3')) +- Decimal("-1") +- >>> ExtendedContext.remainder_near(Decimal('10.2'), Decimal('1')) +- Decimal("0.2") +- >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('0.3')) +- Decimal("0.1") +- >>> ExtendedContext.remainder_near(Decimal('3.6'), Decimal('1.3')) +- Decimal("-0.3") +- """ +- return a.remainder_near(b, context=self) +- +- def same_quantum(self, a, b): +- """Returns True if the two operands have the same exponent. +- +- The result is never affected by either the sign or the coefficient of +- either operand. +- +- >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.001')) +- False +- >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.01')) +- True +- >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('1')) +- False +- >>> ExtendedContext.same_quantum(Decimal('Inf'), Decimal('-Inf')) +- True +- """ +- return a.same_quantum(b) +- +- def sqrt(self, a): +- """Returns the square root of a non-negative number to context precision. +- +- If the result must be inexact, it is rounded using the round-half-even +- algorithm. +- +- >>> ExtendedContext.sqrt(Decimal('0')) +- Decimal("0") +- >>> ExtendedContext.sqrt(Decimal('-0')) +- Decimal("-0") +- >>> ExtendedContext.sqrt(Decimal('0.39')) +- Decimal("0.624499800") +- >>> ExtendedContext.sqrt(Decimal('100')) +- Decimal("10") +- >>> ExtendedContext.sqrt(Decimal('1')) +- Decimal("1") +- >>> ExtendedContext.sqrt(Decimal('1.0')) +- Decimal("1.0") +- >>> ExtendedContext.sqrt(Decimal('1.00')) +- Decimal("1.0") +- >>> ExtendedContext.sqrt(Decimal('7')) +- Decimal("2.64575131") +- >>> ExtendedContext.sqrt(Decimal('10')) +- Decimal("3.16227766") +- >>> ExtendedContext.prec +- 9 +- """ +- return a.sqrt(context=self) +- +- def subtract(self, a, b): +- """Return the difference between the two operands. +- +- >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.07')) +- Decimal("0.23") +- >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.30')) +- Decimal("0.00") +- >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('2.07')) +- Decimal("-0.77") +- """ +- return a.__sub__(b, context=self) +- +- def to_eng_string(self, a): +- """Converts a number to a string, using scientific notation. +- +- The operation is not affected by the context. +- """ +- return a.to_eng_string(context=self) +- +- def to_sci_string(self, a): +- """Converts a number to a string, using scientific notation. +- +- The operation is not affected by the context. +- """ +- return a.__str__(context=self) +- +- def to_integral(self, a): +- """Rounds to an integer. +- +- When the operand has a negative exponent, the result is the same +- as using the quantize() operation using the given operand as the +- left-hand-operand, 1E+0 as the right-hand-operand, and the precision +- of the operand as the precision setting, except that no flags will +- be set. The rounding mode is taken from the context. +- +- >>> ExtendedContext.to_integral(Decimal('2.1')) +- Decimal("2") +- >>> ExtendedContext.to_integral(Decimal('100')) +- Decimal("100") +- >>> ExtendedContext.to_integral(Decimal('100.0')) +- Decimal("100") +- >>> ExtendedContext.to_integral(Decimal('101.5')) +- Decimal("102") +- >>> ExtendedContext.to_integral(Decimal('-101.5')) +- Decimal("-102") +- >>> ExtendedContext.to_integral(Decimal('10E+5')) +- Decimal("1.0E+6") +- >>> ExtendedContext.to_integral(Decimal('7.89E+77')) +- Decimal("7.89E+77") +- >>> ExtendedContext.to_integral(Decimal('-Inf')) +- Decimal("-Infinity") +- """ +- return a.to_integral(context=self) +- +-class _WorkRep(object): +- __slots__ = ('sign','int','exp') +- # sign: 0 or 1 +- # int: int or long +- # exp: None, int, or string +- +- def __init__(self, value=None): +- if value is None: +- self.sign = None +- self.int = 0 +- self.exp = None +- elif isinstance(value, Decimal): +- self.sign = value._sign +- cum = 0 +- for digit in value._int: +- cum = cum * 10 + digit +- self.int = cum +- self.exp = value._exp +- else: +- # assert isinstance(value, tuple) +- self.sign = value[0] +- self.int = value[1] +- self.exp = value[2] +- +- def __repr__(self): +- return "(%r, %r, %r)" % (self.sign, self.int, self.exp) +- +- __str__ = __repr__ +- +- +- +-def _normalize(op1, op2, shouldround = 0, prec = 0): +- """Normalizes op1, op2 to have the same exp and length of coefficient. +- +- Done during addition. +- """ +- # Yes, the exponent is a long, but the difference between exponents +- # must be an int-- otherwise you'd get a big memory problem. +- numdigits = int(op1.exp - op2.exp) +- if numdigits < 0: +- numdigits = -numdigits +- tmp = op2 +- other = op1 +- else: +- tmp = op1 +- other = op2 +- +- +- if shouldround and numdigits > prec + 1: +- # Big difference in exponents - check the adjusted exponents +- tmp_len = len(str(tmp.int)) +- other_len = len(str(other.int)) +- if numdigits > (other_len + prec + 1 - tmp_len): +- # If the difference in adjusted exps is > prec+1, we know +- # other is insignificant, so might as well put a 1 after the precision. +- # (since this is only for addition.) Also stops use of massive longs. +- +- extend = prec + 2 - tmp_len +- if extend <= 0: +- extend = 1 +- tmp.int *= 10 ** extend +- tmp.exp -= extend +- other.int = 1 +- other.exp = tmp.exp +- return op1, op2 +- +- tmp.int *= 10 ** numdigits +- tmp.exp -= numdigits +- return op1, op2 +- +-def _adjust_coefficients(op1, op2): +- """Adjust op1, op2 so that op2.int * 10 > op1.int >= op2.int. +- +- Returns the adjusted op1, op2 as well as the change in op1.exp-op2.exp. +- +- Used on _WorkRep instances during division. +- """ +- adjust = 0 +- #If op1 is smaller, make it larger +- while op2.int > op1.int: +- op1.int *= 10 +- op1.exp -= 1 +- adjust += 1 +- +- #If op2 is too small, make it larger +- while op1.int >= (10 * op2.int): +- op2.int *= 10 +- op2.exp -= 1 +- adjust -= 1 +- +- return op1, op2, adjust +- +-##### Helper Functions ######################################## +- +-def _convert_other(other): +- """Convert other to Decimal. +- +- Verifies that it's ok to use in an implicit construction. +- """ +- if isinstance(other, Decimal): +- return other +- if isinstance(other, (int, long)): +- return Decimal(other) +- return NotImplemented +- +-_infinity_map = { +- 'inf' : 1, +- 'infinity' : 1, +- '+inf' : 1, +- '+infinity' : 1, +- '-inf' : -1, +- '-infinity' : -1 +-} +- +-def _isinfinity(num): +- """Determines whether a string or float is infinity. +- +- +1 for negative infinity; 0 for finite ; +1 for positive infinity +- """ +- num = str(num).lower() +- return _infinity_map.get(num, 0) +- +-def _isnan(num): +- """Determines whether a string or float is NaN +- +- (1, sign, diagnostic info as string) => NaN +- (2, sign, diagnostic info as string) => sNaN +- 0 => not a NaN +- """ +- num = str(num).lower() +- if not num: +- return 0 +- +- #get the sign, get rid of trailing [+-] +- sign = 0 +- if num[0] == '+': +- num = num[1:] +- elif num[0] == '-': #elif avoids '+-nan' +- num = num[1:] +- sign = 1 +- +- if num.startswith('nan'): +- if len(num) > 3 and not num[3:].isdigit(): #diagnostic info +- return 0 +- return (1, sign, num[3:].lstrip('0')) +- if num.startswith('snan'): +- if len(num) > 4 and not num[4:].isdigit(): +- return 0 +- return (2, sign, num[4:].lstrip('0')) +- return 0 +- +- +-##### Setup Specific Contexts ################################ +- +-# The default context prototype used by Context() +-# Is mutable, so that new contexts can have different default values +- +-DefaultContext = Context( +- prec=28, rounding=ROUND_HALF_EVEN, +- traps=[DivisionByZero, Overflow, InvalidOperation], +- flags=[], +- _rounding_decision=ALWAYS_ROUND, +- Emax=999999999, +- Emin=-999999999, +- capitals=1 +-) +- +-# Pre-made alternate contexts offered by the specification +-# Don't change these; the user should be able to select these +-# contexts and be able to reproduce results from other implementations +-# of the spec. +- +-BasicContext = Context( +- prec=9, rounding=ROUND_HALF_UP, +- traps=[DivisionByZero, Overflow, InvalidOperation, Clamped, Underflow], +- flags=[], +-) +- +-ExtendedContext = Context( +- prec=9, rounding=ROUND_HALF_EVEN, +- traps=[], +- flags=[], +-) +- +- +-##### Useful Constants (internal use only) #################### +- +-#Reusable defaults +-Inf = Decimal('Inf') +-negInf = Decimal('-Inf') +- +-#Infsign[sign] is infinity w/ that sign +-Infsign = (Inf, negInf) +- +-NaN = Decimal('NaN') +- +- +-##### crud for parsing strings ################################# +-import re +- +-# There's an optional sign at the start, and an optional exponent +-# at the end. The exponent has an optional sign and at least one +-# digit. In between, must have either at least one digit followed +-# by an optional fraction, or a decimal point followed by at least +-# one digit. Yuck. +- +-_parser = re.compile(r""" +-# \s* +- (?P[-+])? +- ( +- (?P\d+) (\. (?P\d*))? +- | +- \. (?P\d+) +- ) +- ([eE](?P[-+]? \d+))? +-# \s* +- $ +-""", re.VERBOSE).match #Uncomment the \s* to allow leading or trailing spaces. +- +-del re +- +-# return sign, n, p s.t. float string value == -1**sign * n * 10**p exactly +- +-def _string2exact(s): +- m = _parser(s) +- if m is None: +- raise ValueError("invalid literal for Decimal: %r" % s) +- +- if m.group('sign') == "-": +- sign = 1 +- else: +- sign = 0 +- +- exp = m.group('exp') +- if exp is None: +- exp = 0 +- else: +- exp = int(exp) +- +- intpart = m.group('int') +- if intpart is None: +- intpart = "" +- fracpart = m.group('onlyfrac') +- else: +- fracpart = m.group('frac') +- if fracpart is None: +- fracpart = "" +- +- exp -= len(fracpart) +- +- mantissa = intpart + fracpart +- tmp = map(int, mantissa) +- backup = tmp +- while tmp and tmp[0] == 0: +- del tmp[0] +- +- # It's a zero +- if not tmp: +- if backup: +- return (sign, tuple(backup), exp) +- return (sign, (0,), exp) +- mantissa = tuple(tmp) +- +- return (sign, mantissa, exp) +- +- +-if __name__ == '__main__': +- import doctest, sys +- doctest.testmod(sys.modules[__name__]) ++from decimal import * --- python-django-1.1.1.orig/debian/patches/CVE-2013-166x.patch +++ python-django-1.1.1/debian/patches/CVE-2013-166x.patch @@ -0,0 +1,154 @@ +Backport of: + +From d19a27066b2247102e65412aa66917aff0091112 Mon Sep 17 00:00:00 2001 +From: Carl Meyer +Date: Mon, 11 Feb 2013 21:54:53 -0700 +Subject: [PATCH] [1.3.x] Restrict the XML deserializer to prevent network and + entity-expansion DoS attacks. + +This is a security fix. Disclosure and advisory coming shortly. + + +Index: python-django-1.1.1/django/core/serializers/xml_serializer.py +=================================================================== +--- python-django-1.1.1.orig/django/core/serializers/xml_serializer.py 2013-03-04 18:40:59.312449724 -0500 ++++ python-django-1.1.1/django/core/serializers/xml_serializer.py 2013-03-04 18:40:59.304449724 -0500 +@@ -8,6 +8,8 @@ + from django.utils.xmlutils import SimplerXMLGenerator + from django.utils.encoding import smart_unicode + from xml.dom import pulldom ++from xml.sax import handler ++from xml.sax.expatreader import ExpatParser as _ExpatParser + + class Serializer(base.Serializer): + """ +@@ -122,7 +124,11 @@ + + def __init__(self, stream_or_string, **options): + super(Deserializer, self).__init__(stream_or_string, **options) +- self.event_stream = pulldom.parse(self.stream) ++ self.event_stream = pulldom.parse(self.stream, self._make_parser()) ++ ++ def _make_parser(self): ++ """Create a hardened XML parser (no custom/external entities).""" ++ return DefusedExpatParser() + + def next(self): + for event, node in self.event_stream: +@@ -234,3 +240,87 @@ + pass + return u"".join(inner_text) + ++# Below code based on Christian Heimes' defusedxml ++ ++ ++class DefusedExpatParser(_ExpatParser): ++ """ ++ An expat parser hardened against XML bomb attacks. ++ ++ Forbids DTDs, external entity references ++ ++ """ ++ def __init__(self, *args, **kwargs): ++ _ExpatParser.__init__(self, *args, **kwargs) ++ self.setFeature(handler.feature_external_ges, False) ++ self.setFeature(handler.feature_external_pes, False) ++ ++ def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): ++ raise DTDForbidden(name, sysid, pubid) ++ ++ def entity_decl(self, name, is_parameter_entity, value, base, ++ sysid, pubid, notation_name): ++ raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) ++ ++ def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): ++ # expat 1.2 ++ raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) ++ ++ def external_entity_ref_handler(self, context, base, sysid, pubid): ++ raise ExternalReferenceForbidden(context, base, sysid, pubid) ++ ++ def reset(self): ++ _ExpatParser.reset(self) ++ parser = self._parser ++ parser.StartDoctypeDeclHandler = self.start_doctype_decl ++ parser.EntityDeclHandler = self.entity_decl ++ parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl ++ parser.ExternalEntityRefHandler = self.external_entity_ref_handler ++ ++ ++class DefusedXmlException(ValueError): ++ """Base exception.""" ++ def __repr__(self): ++ return str(self) ++ ++ ++class DTDForbidden(DefusedXmlException): ++ """Document type definition is forbidden.""" ++ def __init__(self, name, sysid, pubid): ++ self.name = name ++ self.sysid = sysid ++ self.pubid = pubid ++ ++ def __str__(self): ++ tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" ++ return tpl.format(self.name, self.sysid, self.pubid) ++ ++ ++class EntitiesForbidden(DefusedXmlException): ++ """Entity definition is forbidden.""" ++ def __init__(self, name, value, base, sysid, pubid, notation_name): ++ super(EntitiesForbidden, self).__init__() ++ self.name = name ++ self.value = value ++ self.base = base ++ self.sysid = sysid ++ self.pubid = pubid ++ self.notation_name = notation_name ++ ++ def __str__(self): ++ tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" ++ return tpl.format(self.name, self.sysid, self.pubid) ++ ++ ++class ExternalReferenceForbidden(DefusedXmlException): ++ """Resolving an external reference is forbidden.""" ++ def __init__(self, context, base, sysid, pubid): ++ super(ExternalReferenceForbidden, self).__init__() ++ self.context = context ++ self.base = base ++ self.sysid = sysid ++ self.pubid = pubid ++ ++ def __str__(self): ++ tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" ++ return tpl.format(self.sysid, self.pubid) +Index: python-django-1.1.1/tests/regressiontests/serializers_regress/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/serializers_regress/tests.py 2013-03-04 18:40:59.312449724 -0500 ++++ python-django-1.1.1/tests/regressiontests/serializers_regress/tests.py 2013-03-04 18:41:25.348450391 -0500 +@@ -15,6 +15,7 @@ + from django.core import serializers + from django.db import transaction + from django.core import management ++from django.core.serializers.xml_serializer import DTDForbidden + from django.conf import settings + + from models import * +@@ -439,3 +440,16 @@ + setattr(SerializerTests, 'test_'+format+'_serializer_fields', curry(fieldsTest, format)) + if format != 'python': + setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(streamTest, format)) ++ ++class XmlDeserializerSecurityTests(unittest.TestCase): ++ ++ def test_no_dtd(self): ++ """ ++ The XML deserializer shouldn't allow a DTD. ++ ++ This is the most straightforward way to prevent all entity definitions ++ and avoid both external entities and entity-expansion attacks. ++ ++ """ ++ xml = '' ++ self.assertRaises(DTDForbidden, serializers.deserialize('xml', xml).next) --- python-django-1.1.1.orig/debian/patches/CVE-2013-1443.patch +++ python-django-1.1.1/debian/patches/CVE-2013-1443.patch @@ -0,0 +1,109 @@ +Description: fix denial of service via long passwords +Origin: based on password-dos.diff by Luke Faraone +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=723043 +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1225784 + +Index: python-django-1.1.1/django/contrib/auth/forms.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/forms.py 2013-09-20 09:27:39.732159139 -0400 ++++ python-django-1.1.1/django/contrib/auth/forms.py 2013-09-20 09:29:13.336156563 -0400 +@@ -1,4 +1,4 @@ +-from django.contrib.auth.models import User ++from django.contrib.auth.models import User, MAXIMUM_PASSWORD_LENGTH + from django.contrib.auth import authenticate + from django.contrib.auth.tokens import default_token_generator + from django.contrib.sites.models import Site +@@ -14,8 +14,9 @@ + username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$', + help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."), + error_message = _("This value must contain only letters, numbers and underscores.")) +- password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) +- password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput) ++ password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH) ++ password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput, ++ max_length=MAXIMUM_PASSWORD_LENGTH) + + class Meta: + model = User +@@ -57,7 +58,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, ++ ) + + def __init__(self, request=None, *args, **kwargs): + """ +@@ -140,8 +145,8 @@ + A form that lets a user change set his/her password without + entering the old password + """ +- 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 +@@ -166,7 +171,7 @@ + A form that lets a user change his/her password by entering + their old password. + """ +- 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): + """ +@@ -182,8 +187,8 @@ + """ + A form used to change the password of a user in the admin interface. + """ +- 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 +Index: python-django-1.1.1/django/contrib/auth/models.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/models.py 2013-09-20 09:27:39.732159139 -0400 ++++ python-django-1.1.1/django/contrib/auth/models.py 2013-09-20 09:29:30.960156078 -0400 +@@ -10,6 +10,7 @@ + from django.utils.hashcompat import md5_constructor, sha_constructor + from django.utils.translation import ugettext_lazy as _ + ++MAXIMUM_PASSWORD_LENGTH = 4096 # The maximum length a password can be to prevent DoS + UNUSABLE_PASSWORD = '!' # This will never be a valid hash + + try: +@@ -22,6 +23,9 @@ + Returns a string of the hexdigest of the given plaintext password and salt + using the given algorithm ('md5', 'sha1' or 'crypt'). + """ ++ if len(raw_password) > MAXIMUM_PASSWORD_LENGTH: ++ raise ValueError("Invalid password; Must be less than or equal" ++ " to %d bytes" % MAXIMUM_PASSWORD_LENGTH) + raw_password, salt = smart_str(raw_password), smart_str(salt) + if algorithm == 'crypt': + try: +Index: python-django-1.1.1/django/contrib/auth/tests/basic.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/tests/basic.py 2013-09-20 09:27:39.732159139 -0400 ++++ python-django-1.1.1/django/contrib/auth/tests/basic.py 2013-09-20 09:30:16.824154816 -0400 +@@ -14,6 +14,11 @@ + False + >>> u.has_usable_password() + False ++>>> u.set_password("a"*4100) ++Traceback (most recent call last): ++ ... ++ValueError: Invalid password; Must be less than or equal to 4096 bytes ++ + >>> u2 = User.objects.create_user('testuser2', 'test2@example.com') + >>> u2.has_usable_password() + False --- python-django-1.1.1.orig/debian/patches/CVE-2014-0482.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0482.patch @@ -0,0 +1,99 @@ +Backport of: + +From c9e3b9949cd55f090591fbdc4a114fcb8368b6d9 Mon Sep 17 00:00:00 2001 +From: Preston Holmes +Date: Mon, 11 Aug 2014 12:04:53 -0400 +Subject: [PATCH] [1.4.x] Fixed #23066 -- Modified RemoteUserMiddleware to + logout on REMOTE_USE change. + +This is a security fix. Disclosure following shortly. +--- + django/contrib/auth/middleware.py | 28 +++++++++++++++++++++++++--- + django/contrib/auth/tests/remote_user.py | 18 ++++++++++++++++++ + docs/releases/1.4.14.txt | 9 +++++++++ + 3 files changed, 52 insertions(+), 3 deletions(-) + +Index: python-django-1.3.1/django/contrib/auth/middleware.py +=================================================================== +--- python-django-1.3.1.orig/django/contrib/auth/middleware.py 2014-09-09 14:22:24.380826742 -0400 ++++ python-django-1.3.1/django/contrib/auth/middleware.py 2014-09-09 14:22:24.376826742 -0400 +@@ -1,4 +1,5 @@ + from django.contrib import auth ++from django.contrib.auth.backends import RemoteUserBackend + from django.core.exceptions import ImproperlyConfigured + + +@@ -48,9 +49,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 +@@ -58,6 +61,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) +@@ -79,3 +87,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) +Index: python-django-1.3.1/django/contrib/auth/tests/remote_user.py +=================================================================== +--- python-django-1.3.1.orig/django/contrib/auth/tests/remote_user.py 2014-09-09 14:22:24.380826742 -0400 ++++ python-django-1.3.1/django/contrib/auth/tests/remote_user.py 2014-09-09 14:22:24.376826742 -0400 +@@ -92,6 +92,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 --- python-django-1.1.1.orig/debian/patches/CVE-2013-0305.patch +++ python-django-1.1.1/debian/patches/CVE-2013-0305.patch @@ -0,0 +1,66 @@ +Backport of: + +From d3a45e10c8ac8268899999129daa27652ec0da35 Mon Sep 17 00:00:00 2001 +From: Carl Meyer +Date: Mon, 4 Feb 2013 16:57:59 -0700 +Subject: [PATCH] [1.3.x] Checked object permissions on admin history view. + +This is a security fix. Disclosure and advisory coming shortly. + +Patch by Russell Keith-Magee. + +Index: python-django-1.1.1/django/contrib/admin/options.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/options.py 2013-03-04 19:18:08.856506811 -0500 ++++ python-django-1.1.1/django/contrib/admin/options.py 2013-03-04 19:18:08.852506811 -0500 +@@ -1070,15 +1070,21 @@ + def history_view(self, request, object_id, extra_context=None): + "The 'history' admin view for this model." + from django.contrib.admin.models import LogEntry ++ # First check if the user can see this history. + model = self.model ++ obj = get_object_or_404(model, pk=unquote(object_id)) ++ ++ if not self.has_change_permission(request, obj): ++ raise PermissionDenied ++ ++ # Then get the history for this object. + opts = model._meta + app_label = opts.app_label + action_list = LogEntry.objects.filter( + object_id = object_id, + content_type__id__exact = ContentType.objects.get_for_model(model).id + ).select_related().order_by('action_time') +- # If no history was found, see whether this object even exists. +- obj = get_object_or_404(model, pk=object_id) ++ + context = { + 'title': _('Change history: %s') % force_unicode(obj), + 'action_list': action_list, +Index: python-django-1.1.1/tests/regressiontests/admin_views/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/tests.py 2013-03-04 19:18:08.856506811 -0500 ++++ python-django-1.1.1/tests/regressiontests/admin_views/tests.py 2013-03-04 19:18:45.692507755 -0500 +@@ -533,6 +533,22 @@ + 'Plural error message not found in response to post with multiple errors.') + self.client.get('/test_admin/admin/logout/') + ++ def testHistoryView(self): ++ """History view should restrict access.""" ++ ++ # add user shoud not be able to view the list of article or change any of them ++ self.client.get('/test_admin/admin/') ++ self.client.post('/test_admin/admin/', self.adduser_login) ++ response = self.client.get('/test_admin/admin/admin_views/article/1/history/') ++ self.assertEqual(response.status_code, 403) ++ self.client.get('/test_admin/admin/logout/') ++ ++ # change user can view all items and edit them ++ self.client.get('/test_admin/admin/') ++ self.client.post('/test_admin/admin/', self.changeuser_login) ++ response = self.client.get('/test_admin/admin/admin_views/article/1/history/') ++ self.assertEqual(response.status_code, 200) ++ + def testCustomModelAdminTemplates(self): + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.super_login) --- python-django-1.1.1.orig/debian/patches/CVE-2014-0483-bug23329.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0483-bug23329.patch @@ -0,0 +1,100 @@ +Backport of: + +From 4685026840f0e2b895f980b6a33ad1b282aa7852 Mon Sep 17 00:00:00 2001 +From: Simon Charette +Date: Thu, 21 Aug 2014 11:55:23 -0400 +Subject: [PATCH] [1.4.x] Fixed #23329 -- Allowed inherited and m2m fields to + be referenced in the admin. + +Thanks to Trac alias Markush2010 and ross for the detailed reports. + +Backport of 3cbb759 from master +--- + django/contrib/admin/options.py | 10 ++++++---- + docs/releases/1.4.15.txt | 13 +++++++++++++ + docs/releases/index.txt | 1 + + tests/regressiontests/admin_views/admin.py | 5 ++++- + tests/regressiontests/admin_views/models.py | 18 ++++++++++++++++++ + tests/regressiontests/admin_views/tests.py | 9 +++++++++ + 6 files changed, 51 insertions(+), 5 deletions(-) + create mode 100644 docs/releases/1.4.15.txt + +Index: python-django-1.1.1/django/contrib/admin/options.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/options.py 2014-09-10 14:46:56.858879042 -0400 ++++ python-django-1.1.1/django/contrib/admin/options.py 2014-09-10 14:46:56.850879042 -0400 +@@ -300,11 +300,13 @@ + return False + + # Make sure at least one of the models registered for this site +- # references this field. ++ # references this field through a FK or a M2M relationship. + 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()): ++ for related_object in (opts.get_all_related_objects() + ++ opts.get_all_related_many_to_many_objects()): ++ related_model = related_object.model ++ if (any(issubclass(model, related_model) for model in registered_models) and ++ related_object.field.rel.get_related_field() == field): + return True + + return False +Index: python-django-1.1.1/tests/regressiontests/admin_views/models.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/models.py 2014-09-10 14:46:56.858879042 -0400 ++++ python-django-1.1.1/tests/regressiontests/admin_views/models.py 2014-09-10 14:47:29.134879225 -0400 +@@ -427,6 +427,22 @@ + class CollectorAdmin(admin.ModelAdmin): + inlines = [WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, FancyDoodadInline, CategoryInline] + ++# Models for #23329 ++class ReferencedByParent(models.Model): ++ pass ++ ++ ++class ParentWithFK(models.Model): ++ fk = models.ForeignKey(ReferencedByParent) ++ ++ ++class ChildOfReferer(ParentWithFK): ++ pass ++ ++ ++class M2MReference(models.Model): ++ ref = models.ManyToManyField('self') ++ + admin.site.register(Article, ArticleAdmin) + admin.site.register(CustomArticle, CustomArticleAdmin) + admin.site.register(Section, save_as=True, inlines=[ArticleInline]) +@@ -450,6 +466,9 @@ + admin.site.register(Recommender) + admin.site.register(Collector, CollectorAdmin) + admin.site.register(Category, CategoryAdmin) ++admin.site.register(ReferencedByParent) ++admin.site.register(ChildOfReferer) ++admin.site.register(M2MReference) + + # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. + # That way we cover all four cases: +Index: python-django-1.1.1/tests/regressiontests/admin_views/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/tests.py 2014-09-10 14:46:56.858879042 -0400 ++++ python-django-1.1.1/tests/regressiontests/admin_views/tests.py 2014-09-10 14:48:19.918879511 -0400 +@@ -259,6 +259,15 @@ + response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + ++ # Specifying a field referenced by another model though a m2m should be allowed. ++ response = self.client.get("/test_admin/admin/admin_views/m2mreference/", {TO_FIELD_VAR: 'id'}) ++ self.assertEqual(response.status_code, 200) ++ ++ # Specifying a field that is not refered by any other model directly registered ++ # to this admin site but registered through inheritance should be allowed. ++ response = self.client.get("/test_admin/admin/admin_views/referencedbyparent/", {TO_FIELD_VAR: 'id'}) ++ self.assertEqual(response.status_code, 200) ++ + + class SaveAsTests(TestCase): + fixtures = ['admin-views-users.xml','admin-views-person.xml'] --- python-django-1.1.1.orig/debian/patches/07_test_client_cookie_fix.diff +++ python-django-1.1.1/debian/patches/07_test_client_cookie_fix.diff @@ -0,0 +1,19 @@ +From: James Henstridge +Subject: Fix Django test client cookie handling with latest python2.6 upload +Origin: upstream, http://code.djangoproject.com/changeset/12343 +Bug: http://code.djangoproject.com/ticket/12720 +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/513719 +Reviewed-By: Elliot Murphy +Last-Update: 2010-01-30 + +--- python-django.orig/django/test/client.py 2010-01-29 13:09:25.885960656 -0500 ++++ python-django/django/test/client.py 2010-01-29 13:10:15.622491350 -0500 +@@ -193,7 +193,7 @@ + using the arguments to the request. + """ + environ = { +- 'HTTP_COOKIE': self.cookies, ++ 'HTTP_COOKIE': self.cookies.output(header='', sep='; '), + 'PATH_INFO': '/', + 'QUERY_STRING': '', + 'REMOTE_ADDR': '127.0.0.1', --- python-django-1.1.1.orig/debian/patches/01_disable_url_verify_regression_tests.diff +++ python-django-1.1.1/debian/patches/01_disable_url_verify_regression_tests.diff @@ -0,0 +1,41 @@ +Forwarded-Upstream: not needed +Author: Krzysztof Klimonda +Comment: + Disable regression tests that require an internet connection. + . + This is a Debian specific patch. + +Index: python-django-1.1/tests/regressiontests/forms/fields.py +=================================================================== +--- python-django-1.1.orig/tests/regressiontests/forms/fields.py 2009-08-15 21:03:21.248330080 +0200 ++++ python-django-1.1/tests/regressiontests/forms/fields.py 2009-08-15 21:03:45.709392050 +0200 +@@ -977,29 +977,6 @@ + ... + ValidationError: [u'Enter a valid URL.'] + +-URLField takes an optional verify_exists parameter, which is False by default. +-This verifies that the URL is live on the Internet and doesn't return a 404 or 500: +->>> f = URLField(verify_exists=True) +->>> f.clean('http://www.google.com') # This will fail if there's no Internet connection +-u'http://www.google.com/' +->>> f.clean('http://example') +-Traceback (most recent call last): +-... +-ValidationError: [u'Enter a valid URL.'] +->>> f.clean('http://www.broken.djangoproject.com') # bad domain +-Traceback (most recent call last): +-... +-ValidationError: [u'This URL appears to be a broken link.'] +->>> f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page +-Traceback (most recent call last): +-... +-ValidationError: [u'This URL appears to be a broken link.'] +->>> f = URLField(verify_exists=True, required=False) +->>> f.clean('') +-u'' +->>> f.clean('http://www.google.com') # This will fail if there's no Internet connection +-u'http://www.google.com/' +- + URLField also access min_length and max_length parameters, for convenience. + >>> f = URLField(min_length=15, max_length=20) + >>> f.clean('http://f.com') --- python-django-1.1.1.orig/debian/patches/CVE-2014-0483-bug23431.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0483-bug23431.patch @@ -0,0 +1,118 @@ +Backport of: + +From 065caafa70b6c422f73e364a4c241b6538969d7b Mon Sep 17 00:00:00 2001 +From: Simon Charette +Date: Thu, 4 Sep 2014 17:04:53 -0400 +Subject: [PATCH] [1.4.x] Fixed #23431 -- Allowed inline and hidden references + to admin fields. + +This fixes a regression introduced by the 53ff096982 security fix. + +Thanks to @a1tus for the report and Tim for the review. + +refs #23329. + +Backport of 342ccbd from master +--- + django/contrib/admin/options.py | 13 +++++++++++-- + docs/releases/1.4.16.txt | 13 +++++++++++++ + docs/releases/index.txt | 1 + + tests/regressiontests/admin_views/admin.py | 13 ++++++++++++- + tests/regressiontests/admin_views/models.py | 12 ++++++++++++ + tests/regressiontests/admin_views/tests.py | 7 ++++++- + 6 files changed, 55 insertions(+), 4 deletions(-) + create mode 100644 docs/releases/1.4.16.txt + +Index: python-django-1.1.1/django/contrib/admin/options.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/options.py 2014-09-10 14:49:03.902879759 -0400 ++++ python-django-1.1.1/django/contrib/admin/options.py 2014-09-10 14:49:03.894879759 -0400 +@@ -292,6 +292,10 @@ + media = property(_media) + + def to_field_allowed(self, request, to_field): ++ """ ++ Returns True if the model associated with this admin should be ++ allowed to be referenced by the specified field. ++ """ + opts = self.model._meta + + try: +@@ -301,8 +305,13 @@ + + # Make sure at least one of the models registered for this site + # references this field through a FK or a M2M relationship. +- registered_models = self.admin_site._registry +- for related_object in (opts.get_all_related_objects() + ++ registered_models = set() ++ for model, admin in self.admin_site._registry.items(): ++ registered_models.add(model) ++ for inline in admin.inlines: ++ registered_models.add(inline.model) ++ ++ for related_object in (opts.get_all_related_objects() + + opts.get_all_related_many_to_many_objects()): + related_model = related_object.model + if (any(issubclass(model, related_model) for model in registered_models) and +Index: python-django-1.1.1/tests/regressiontests/admin_views/models.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/models.py 2014-09-10 14:49:03.902879759 -0400 ++++ python-django-1.1.1/tests/regressiontests/admin_views/models.py 2014-09-10 14:49:03.894879759 -0400 +@@ -443,6 +443,26 @@ + class M2MReference(models.Model): + ref = models.ManyToManyField('self') + ++# Models for #23431 ++class ReferencedByInline(models.Model): ++ pass ++ ++ ++class InlineReference(models.Model): ++ fk = models.ForeignKey(ReferencedByInline, related_name='hidden+') ++ ++ ++class InlineReferer(models.Model): ++ refs = models.ManyToManyField(InlineReference) ++ ++class InlineReferenceInline(admin.TabularInline): ++ model = InlineReference ++ ++ ++class InlineRefererAdmin(admin.ModelAdmin): ++ inlines = [InlineReferenceInline] ++ ++ + admin.site.register(Article, ArticleAdmin) + admin.site.register(CustomArticle, CustomArticleAdmin) + admin.site.register(Section, save_as=True, inlines=[ArticleInline]) +@@ -469,6 +489,8 @@ + admin.site.register(ReferencedByParent) + admin.site.register(ChildOfReferer) + admin.site.register(M2MReference) ++admin.site.register(ReferencedByInline) ++admin.site.register(InlineReferer, InlineRefererAdmin) + + # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. + # That way we cover all four cases: +Index: python-django-1.1.1/tests/regressiontests/admin_views/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/tests.py 2014-09-10 14:49:03.902879759 -0400 ++++ python-django-1.1.1/tests/regressiontests/admin_views/tests.py 2014-09-10 14:49:51.582880029 -0400 +@@ -263,11 +263,16 @@ + response = self.client.get("/test_admin/admin/admin_views/m2mreference/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + +- # Specifying a field that is not refered by any other model directly registered ++ # #23329 - Specifying a field that is not refered by any other model directly registered + # to this admin site but registered through inheritance should be allowed. + response = self.client.get("/test_admin/admin/admin_views/referencedbyparent/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + ++ # #23431 - Specifying a field that is only refered to by a inline of a registered ++ # model should be allowed. ++ response = self.client.get("/test_admin/admin/admin_views/referencedbyinline/", {TO_FIELD_VAR: 'id'}) ++ self.assertEqual(response.status_code, 200) ++ + + class SaveAsTests(TestCase): + fixtures = ['admin-views-users.xml','admin-views-person.xml'] --- python-django-1.1.1.orig/debian/patches/fix_invalid_link_ftbfs.patch +++ python-django-1.1.1/debian/patches/fix_invalid_link_ftbfs.patch @@ -0,0 +1,19 @@ +Description: remove test causing FTBFS +Author: Marc Deslauriers +Forwarded: not needed + +Index: python-django-1.1.1/tests/regressiontests/forms/error_messages.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/forms/error_messages.py 2009-09-11 06:47:40.000000000 -0400 ++++ python-django-1.1.1/tests/regressiontests/forms/error_messages.py 2014-09-10 13:26:30.642851805 -0400 +@@ -238,10 +238,6 @@ + Traceback (most recent call last): + ... + ValidationError: [u'INVALID'] +->>> f.clean('http://www.broken.djangoproject.com') +-Traceback (most recent call last): +-... +-ValidationError: [u'INVALID LINK'] + + # BooleanField ################################################################ + --- python-django-1.1.1.orig/debian/patches/CVE-2015-0219.patch +++ python-django-1.1.1/debian/patches/CVE-2015-0219.patch @@ -0,0 +1,107 @@ +Description: fix WSGI header spoofing via underscore/dash conflation +Origin: backported from patch provided by Carl Meyer + +Index: python-django-1.1.1/django/core/servers/basehttp.py +=================================================================== +--- python-django-1.1.1.orig/django/core/servers/basehttp.py 2015-01-13 10:15:25.447565593 -0500 ++++ python-django-1.1.1/django/core/servers/basehttp.py 2015-01-13 10:15:25.443565561 -0500 +@@ -560,6 +560,14 @@ + BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + + 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] ++ + env = self.server.base_environ.copy() + env['SERVER_PROTOCOL'] = self.request_version + env['REQUEST_METHOD'] = self.command +Index: python-django-1.1.1/tests/regressiontests/servers/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/servers/tests.py 2015-01-13 10:15:25.447565593 -0500 ++++ python-django-1.1.1/tests/regressiontests/servers/tests.py 2015-01-13 10:15:25.443565561 -0500 +@@ -3,11 +3,14 @@ + """ + + import os ++import sys ++from StringIO import StringIO + + import django + from django.test import TestCase + from django.core.handlers.wsgi import WSGIHandler + from django.core.servers.basehttp import AdminMediaHandler ++from django.core.servers.basehttp import WSGIRequestHandler + + + class AdminMediaHandlerTests(TestCase): +@@ -65,3 +68,65 @@ + continue + self.fail('URL: %s should have caused a ValueError exception.' + % url) ++ ++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 = StringIO() ++ 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(StringIO): ++ 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') ++ --- python-django-1.1.1.orig/debian/patches/08_security_admin_infoleak.diff +++ python-django-1.1.1/debian/patches/08_security_admin_infoleak.diff @@ -0,0 +1,146 @@ +Origin: http://code.djangoproject.com/changeset/15035 +Description: Information leakage in Django administrative interface + (CVE-2010-4534) + +Index: python-django-1.1.1/django/contrib/admin/options.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/options.py 2011-01-03 11:29:23.000000000 -0600 ++++ python-django-1.1.1/django/contrib/admin/options.py 2011-01-03 11:29:27.000000000 -0600 +@@ -8,7 +8,9 @@ + from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict + from django.core.exceptions import PermissionDenied + from django.db import models, transaction +-from django.db.models.fields import BLANK_CHOICE_DASH ++from django.db.models.related import RelatedObject ++from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist ++from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS + from django.http import Http404, HttpResponse, HttpResponseRedirect + from django.shortcuts import get_object_or_404, render_to_response + from django.utils.datastructures import SortedDict +@@ -172,6 +174,30 @@ + return None + declared_fieldsets = property(_declared_fieldsets) + ++ def lookup_allowed(self, lookup): ++ parts = lookup.split(LOOKUP_SEP) ++ ++ # Last term in lookup is a query term (__exact, __startswith etc) ++ # This term can be ignored. ++ if len(parts) > 1 and parts[-1] in QUERY_TERMS: ++ parts.pop() ++ ++ # Special case -- foo__id__exact and foo__id queries are implied ++ # if foo has been specificially included in the lookup list; so ++ # drop __id if it is the last part. ++ if len(parts) > 1 and parts[-1] == self.model._meta.pk.name: ++ parts.pop() ++ ++ try: ++ self.model._meta.get_field_by_name(parts[0]) ++ except FieldDoesNotExist: ++ # Lookups on non-existants fields are ok, since they're ignored ++ # later. ++ return True ++ else: ++ clean_lookup = LOOKUP_SEP.join(parts) ++ return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy ++ + class ModelAdmin(BaseModelAdmin): + "Encapsulates all admin options and functionality for a given model." + __metaclass__ = forms.MediaDefiningClass +Index: python-django-1.1.1/django/contrib/admin/views/main.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/views/main.py 2011-01-03 11:29:23.000000000 -0600 ++++ python-django-1.1.1/django/contrib/admin/views/main.py 2011-01-03 11:29:27.000000000 -0600 +@@ -1,6 +1,7 @@ + from django.contrib.admin.filterspecs import FilterSpec + from django.contrib.admin.options import IncorrectLookupParameters + from django.contrib.admin.util import quote ++from django.core.exceptions import SuspiciousOperation + from django.core.paginator import Paginator, InvalidPage + from django.db import models + from django.db.models.query import QuerySet +@@ -185,13 +186,18 @@ + if key.endswith('__in'): + lookup_params[key] = value.split(',') + ++ if not self.model_admin.lookup_allowed(key): ++ raise SuspiciousOperation( ++ "Filtering by %s not allowed" % key ++ ) ++ + # Apply lookup parameters from the query string. + try: + qs = qs.filter(**lookup_params) + # Naked except! Because we don't have any other way of validating "params". + # They might be invalid if the keyword arguments are incorrect, or if the + # values are not in the correct type, so we might get FieldError, ValueError, +- # ValicationError, or ? from a custom field that raises yet something else ++ # ValicationError, or ? from a custom field that raises yet something else + # when handed impossible data. + except: + raise IncorrectLookupParameters +Index: python-django-1.1.1/tests/regressiontests/admin_views/models.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/models.py 2011-01-03 11:29:23.000000000 -0600 ++++ python-django-1.1.1/tests/regressiontests/admin_views/models.py 2011-01-03 11:29:27.000000000 -0600 +@@ -5,7 +5,7 @@ + from django.db import models + from django.contrib import admin + from django.core.mail import EmailMessage +- ++from django.contrib.auth.models import User + class Section(models.Model): + """ + A simple section that links to articles, to test linking to related items +@@ -82,7 +82,7 @@ + + class ArticleAdmin(admin.ModelAdmin): + list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') +- list_filter = ('date',) ++ list_filter = ('date', 'section') + + def changelist_view(self, request): + "Test that extra_context works" +@@ -185,6 +185,13 @@ + def __unicode__(self): + return self.name + ++class Album(models.Model): ++ owner = models.ForeignKey(User) ++ title = models.CharField(max_length=30) ++ ++class AlbumAdmin(admin.ModelAdmin): ++ list_filter = ['title'] ++ + class Account(models.Model): + """ + A simple, generic account encapsulating the information shared by all +@@ -456,3 +463,4 @@ + admin.site.register(Book, inlines=[ChapterInline]) + admin.site.register(Promo) + admin.site.register(ChapterXtra1) ++admin.site.register(Album, AlbumAdmin) +Index: python-django-1.1.1/tests/regressiontests/admin_views/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/tests.py 2011-01-03 11:29:23.000000000 -0600 ++++ python-django-1.1.1/tests/regressiontests/admin_views/tests.py 2011-01-03 11:29:27.000000000 -0600 +@@ -3,6 +3,7 @@ + import re + import datetime + from django.core.files import temp as tempfile ++from django.core.exceptions import SuspiciousOperation + from django.test import TestCase + from django.contrib.auth.models import User, Permission + from django.contrib.contenttypes.models import ContentType +@@ -678,6 +679,10 @@ + should_contain = """

Change model with string primary key

""" + self.assertContains(response, should_contain) + ++ def test_disallowed_filtering(self): ++ self.assertRaises(SuspiciousOperation, ++ self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy" ++ ) + + class SecureViewTest(TestCase): + fixtures = ['admin-views-users.xml'] --- python-django-1.1.1.orig/debian/patches/fix_redirect_poisoning.patch +++ python-django-1.1.1/debian/patches/fix_redirect_poisoning.patch @@ -0,0 +1,420 @@ +Backport of: + +From 1515eb46daa0897ba5ad5f0a2db8969255f1b343 Mon Sep 17 00:00:00 2001 +From: Florian Apolloner +Date: Sat, 17 Nov 2012 22:00:53 +0100 +Subject: [PATCH] [1.3.X] Fixed #18856 -- Ensured that redirects can't be + poisoned by malicious users. + +Index: python-django-1.1.1/django/contrib/auth/views.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/views.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/contrib/auth/views.py 2013-03-04 14:45:33.720088038 -0500 +@@ -9,7 +9,7 @@ + from django.contrib.sites.models import Site, RequestSite + from django.http import HttpResponseRedirect, Http404 + from django.template import RequestContext +-from django.utils.http import urlquote, base36_to_int ++from django.utils.http import urlquote, base36_to_int, is_safe_url + from django.utils.translation import ugettext as _ + from django.contrib.auth.models import User + from django.views.decorators.cache import never_cache +@@ -20,8 +20,8 @@ + if request.method == "POST": + form = AuthenticationForm(data=request.POST) + if form.is_valid(): +- # Light security check -- make sure redirect_to isn't garbage. +- if not redirect_to or '//' in redirect_to or ' ' in redirect_to: ++ # Ensure the user-originating redirection url is safe. ++ if not is_safe_url(url=redirect_to, host=request.get_host()): + redirect_to = settings.LOGIN_REDIRECT_URL + from django.contrib.auth import login + login(request, form.get_user()) +@@ -47,17 +47,28 @@ + "Logs out the user and displays 'You are logged out' message." + from django.contrib.auth import logout + logout(request) +- if next_page is None: +- redirect_to = request.REQUEST.get(redirect_field_name, '') +- if redirect_to: +- return HttpResponseRedirect(redirect_to) +- else: +- return render_to_response(template_name, { +- 'title': _('Logged out') +- }, context_instance=RequestContext(request)) +- else: ++ ++ if redirect_field_name in request.REQUEST: ++ next_page = request.REQUEST[redirect_field_name] ++ # Security check -- don't allow redirection to a different host. ++ if not is_safe_url(url=next_page, host=request.get_host()): ++ next_page = request.path ++ ++ if next_page: + # Redirect to this page until the session has been cleared. +- return HttpResponseRedirect(next_page or request.path) ++ return HttpResponseRedirect(next_page) ++ ++ if Site._meta.installed: ++ current_site = Site.objects.get_current() ++ else: ++ current_site = RequestSite(request) ++ context = { ++ 'site': current_site, ++ 'site_name': current_site.name, ++ 'title': _('Logged out') ++ } ++ return render_to_response(template_name, context, ++ context_instance=RequestContext(request)) + + def logout_then_login(request, login_url=None): + "Logs out the user if he is logged in. Then redirects to the log-in page." +Index: python-django-1.1.1/django/contrib/comments/views/comments.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/comments/views/comments.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/contrib/comments/views/comments.py 2013-03-04 14:39:45.320079117 -0500 +@@ -37,9 +37,6 @@ + if not data.get('email', ''): + data["email"] = request.user.email + +- # Check to see if the POST data overrides the view's next argument. +- next = data.get("next", next) +- + # Look up the object we're trying to comment about + ctype = data.get("content_type") + object_pk = data.get("object_pk") +@@ -81,9 +78,9 @@ + ] + return render_to_response( + template_list, { +- "comment" : form.data.get("comment", ""), +- "form" : form, +- "next": next, ++ "comment": form.data.get("comment", ""), ++ "form": form, ++ "next": data.get("next", next), + }, + RequestContext(request, {}) + ) +@@ -114,7 +111,7 @@ + request = request + ) + +- return next_redirect(data, next, comment_done, c=comment._get_pk_val()) ++ return next_redirect(request, next, comment_done, c=comment._get_pk_val()) + + post_comment = require_POST(post_comment) + +Index: python-django-1.1.1/django/contrib/comments/views/moderation.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/comments/views/moderation.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/contrib/comments/views/moderation.py 2013-03-04 14:39:45.320079117 -0500 +@@ -34,7 +34,7 @@ + created = created, + request = request, + ) +- return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk) ++ return next_redirect(request, next, flag_done, c=comment.pk) + + # Render a form on GET + else: +@@ -74,7 +74,7 @@ + created = created, + request = request, + ) +- return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk) ++ return next_redirect(request, next, delete_done, c=comment.pk) + + # Render a form on GET + else: +@@ -117,7 +117,7 @@ + created = created, + request = request, + ) +- return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk) ++ return next_redirect(request, next, approve_done, c=comment.pk) + + # Render a form on GET + else: +Index: python-django-1.1.1/django/contrib/comments/views/utils.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/comments/views/utils.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/contrib/comments/views/utils.py 2013-03-04 14:39:45.320079117 -0500 +@@ -4,14 +4,15 @@ + + import urllib + import textwrap +-from django.http import HttpResponseRedirect + from django.core import urlresolvers ++from django.http import HttpResponseRedirect + from django.shortcuts import render_to_response + from django.template import RequestContext + from django.core.exceptions import ObjectDoesNotExist + from django.contrib import comments ++from django.utils.http import is_safe_url + +-def next_redirect(data, default, default_view, **get_kwargs): ++def next_redirect(request, default, default_view, **get_kwargs): + """ + Handle the "where should I go next?" part of comment views. + +@@ -21,9 +22,10 @@ + + Returns an ``HttpResponseRedirect``. + """ +- next = data.get("next", default) +- if next is None: ++ next = request.POST.get('next', default) ++ if not is_safe_url(url=next, host=request.get_host()): + next = urlresolvers.reverse(default_view) ++ + if get_kwargs: + joiner = ('?' in next) and '&' or '?' + next += joiner + urllib.urlencode(get_kwargs) +Index: python-django-1.1.1/django/utils/http.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/http.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/utils/http.py 2013-03-04 14:39:45.320079117 -0500 +@@ -1,5 +1,6 @@ + import re + import urllib ++import urlparse + from email.Utils import formatdate + + from django.utils.encoding import smart_str, force_unicode +@@ -122,3 +123,14 @@ + """ + return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"') + ++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). ++ ++ Always returns ``False`` on an empty url. ++ """ ++ if not url: ++ return False ++ netloc = urlparse.urlparse(url)[1] ++ return not netloc or netloc == host +Index: python-django-1.1.1/django/views/i18n.py +=================================================================== +--- python-django-1.1.1.orig/django/views/i18n.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/django/views/i18n.py 2013-03-04 14:39:45.320079117 -0500 +@@ -3,6 +3,7 @@ + from django.utils import importlib + from django.utils.translation import check_for_language, activate, to_locale, get_language + from django.utils.text import javascript_quote ++from django.utils.http import is_safe_url + import os + import gettext as gettext_module + +@@ -17,11 +18,11 @@ + redirect to the page in the request (the 'next' parameter) without changing + any state. + """ +- next = request.REQUEST.get('next', None) +- if not next: +- next = request.META.get('HTTP_REFERER', None) +- if not next: +- next = '/' ++ next = request.REQUEST.get('next') ++ if not is_safe_url(url=next, host=request.get_host()): ++ next = request.META.get('HTTP_REFERER') ++ if not is_safe_url(url=next, host=request.get_host()): ++ next = '/' + response = http.HttpResponseRedirect(next) + if request.method == 'POST': + lang_code = request.POST.get('language', None) +Index: python-django-1.1.1/tests/regressiontests/comment_tests/tests/comment_view_tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/comment_tests/tests/comment_view_tests.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/tests/regressiontests/comment_tests/tests/comment_view_tests.py 2013-03-04 14:39:45.320079117 -0500 +@@ -195,6 +195,13 @@ + match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location) + self.failUnless(match != None, "Unexpected redirect location: %s" % location) + ++ data["next"] = "http://badserver/somewhere/else/" ++ data["comment"] = "This is another comment with an unsafe next url" ++ response = self.client.post("/post/", data) ++ location = response["Location"] ++ match = post_redirect_re.match(location) ++ self.assertTrue(match != None, "Unsafe redirection to: %s" % location) ++ + def testCommentDoneView(self): + a = Article.objects.get(pk=1) + data = self.getValidData(a) +Index: python-django-1.1.1/tests/regressiontests/comment_tests/tests/moderation_view_tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/comment_tests/tests/moderation_view_tests.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/tests/regressiontests/comment_tests/tests/moderation_view_tests.py 2013-03-04 14:39:45.320079117 -0500 +@@ -25,6 +25,30 @@ + self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1) + return c + ++ def testFlagPostNext(self): ++ """ ++ POST the flag view, explicitly providing a next url. ++ """ ++ comments = self.createSomeComments() ++ pk = comments[0].pk ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/flag/%d/" % pk, {'next': "/go/here/"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/go/here/?c=1") ++ ++ def testFlagPostUnsafeNext(self): ++ """ ++ POSTing to the flag view with an unsafe next url will ignore the ++ provided url when redirecting. ++ """ ++ comments = self.createSomeComments() ++ pk = comments[0].pk ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/flag/%d/" % pk, ++ {'next': "http://elsewhere/bad"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/flagged/?c=%d" % pk) ++ + def testFlagPostTwice(self): + """Users don't get to flag comments more than once.""" + c = self.testFlagPost() +@@ -44,7 +68,7 @@ + def testFlaggedView(self): + comments = self.createSomeComments() + pk = comments[0].pk +- response = self.client.get("/flagged/", data={"c":pk}) ++ response = self.client.get("/flagged/", data={"c": pk}) + self.assertTemplateUsed(response, "comments/flagged.html") + + def testFlagSignals(self): +@@ -96,6 +120,33 @@ + self.failUnless(c.is_removed) + self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1) + ++ def testDeletePostNext(self): ++ """ ++ POSTing the delete view will redirect to an explicitly provided a next ++ url. ++ """ ++ comments = self.createSomeComments() ++ pk = comments[0].pk ++ makeModerator("normaluser") ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/delete/%d/" % pk, {'next': "/go/here/"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/go/here/?c=1") ++ ++ def testDeletePostUnsafeNext(self): ++ """ ++ POSTing to the delete view with an unsafe next url will ignore the ++ provided url when redirecting. ++ """ ++ comments = self.createSomeComments() ++ pk = comments[0].pk ++ makeModerator("normaluser") ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/delete/%d/" % pk, ++ {'next': "http://elsewhere/bad"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/deleted/?c=%d" % pk) ++ + def testDeleteSignals(self): + def receive(sender, **kwargs): + received_signals.append(kwargs.get('signal')) +@@ -111,13 +162,13 @@ + def testDeletedView(self): + comments = self.createSomeComments() + pk = comments[0].pk +- response = self.client.get("/deleted/", data={"c":pk}) ++ response = self.client.get("/deleted/", data={"c": pk}) + self.assertTemplateUsed(response, "comments/deleted.html") + + class ApproveViewTests(CommentTestCase): + + def testApprovePermissions(self): +- """The delete view should only be accessible to 'moderators'""" ++ """The approve view should only be accessible to 'moderators'""" + comments = self.createSomeComments() + pk = comments[0].pk + self.client.login(username="normaluser", password="normaluser") +@@ -129,7 +180,7 @@ + self.assertEqual(response.status_code, 200) + + def testApprovePost(self): +- """POSTing the delete view should mark the comment as removed""" ++ """POSTing the approve view should mark the comment as removed""" + c1, c2, c3, c4 = self.createSomeComments() + c1.is_public = False; c1.save() + +@@ -141,6 +192,36 @@ + self.failUnless(c.is_public) + self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1) + ++ def testApprovePostNext(self): ++ """ ++ POSTing the approve view will redirect to an explicitly provided a next ++ url. ++ """ ++ c1, c2, c3, c4 = self.createSomeComments() ++ c1.is_public = False; c1.save() ++ ++ makeModerator("normaluser") ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/approve/%d/" % c1.pk, ++ {'next': "/go/here/"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/go/here/?c=1") ++ ++ def testApprovePostUnsafeNext(self): ++ """ ++ POSTing to the approve view with an unsafe next url will ignore the ++ provided url when redirecting. ++ """ ++ c1, c2, c3, c4 = self.createSomeComments() ++ c1.is_public = False; c1.save() ++ ++ makeModerator("normaluser") ++ self.client.login(username="normaluser", password="normaluser") ++ response = self.client.post("/approve/%d/" % c1.pk, ++ {'next': "http://elsewhere/bad"}) ++ self.assertEqual(response["Location"], ++ "http://testserver/approved/?c=%d" % c1.pk) ++ + def testApproveSignals(self): + def receive(sender, **kwargs): + received_signals.append(kwargs.get('signal')) +Index: python-django-1.1.1/tests/regressiontests/views/tests/i18n.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/views/tests/i18n.py 2013-03-04 14:39:45.328079118 -0500 ++++ python-django-1.1.1/tests/regressiontests/views/tests/i18n.py 2013-03-04 14:39:45.320079117 -0500 +@@ -11,13 +11,28 @@ + """ Tests django views in django/views/i18n.py """ + + def test_setlang(self): +- """The set_language view can be used to change the session language""" ++ """ ++ The set_language view can be used to change the session language. ++ ++ The user is redirected to the 'next' argument if provided. ++ """ + for lang_code, lang_name in settings.LANGUAGES: + post_data = dict(language=lang_code, next='/views/') + response = self.client.post('/views/i18n/setlang/', data=post_data) + self.assertRedirects(response, 'http://testserver/views/') + self.assertEquals(self.client.session['django_language'], lang_code) + ++ def test_setlang_unsafe_next(self): ++ """ ++ The set_language view only redirects to the 'next' argument if it is ++ "safe". ++ """ ++ lang_code, lang_name = settings.LANGUAGES[0] ++ post_data = dict(language=lang_code, next='//unsafe/redirection/') ++ response = self.client.post('/views/i18n/setlang/', data=post_data) ++ self.assertEqual(response['Location'], 'http://testserver/') ++ self.assertEqual(self.client.session['django_language'], lang_code) ++ + def test_jsi18n(self): + """The javascript_catalog can be deployed with language settings""" + for lang_code in ['es', 'fr', 'en']: --- python-django-1.1.1.orig/debian/patches/series +++ python-django-1.1.1/debian/patches/series @@ -0,0 +1,44 @@ +01_disable_url_verify_regression_tests.diff +02-embedded_code_copies.diff +03_manpage.diff +04_hyphen-manpage.diff +05_ftbfs_in_november.diff +06_python_2.6.3_regression.diff +07_test_client_cookie_fix.diff +08_security_admin_infoleak.diff +09_security_pasword_reset_dos.diff +10_CVE-2011-0696.diff +11_CVE-2011-0697.diff +CVE-2011-4136.patch +CVE-2011-4137+4138.patch +CVE-2011-4139.patch +16_fix_cross_site_scripting_in_authentication.diff +17_fix_dos_in_image_validation.diff +18_fix_dos_via_get_image_dimensions.diff +CVE-2012-4520.patch +CVE-2012-4520-additional-tests.diff +lp1080204.diff +fix_get_host.patch +fix_redirect_poisoning.patch +add_allowed_hosts.patch +CVE-2013-166x.patch +CVE-2013-0305.patch +CVE-2013-0306.patch +security-is_safe_url.patch +CVE-2013-1443.patch +CVE-2013-4315.patch +CVE-2014-0472.patch +CVE-2014-0473.patch +CVE-2014-0474.patch +CVE-2014-0472-regression.patch +drop_fix_ie_for_vary_1_4.diff +fix_invalid_link_ftbfs.patch +CVE-2014-0480.patch +CVE-2014-0481.patch +CVE-2014-0482.patch +CVE-2014-0483.patch +CVE-2014-0483-bug23329.patch +CVE-2014-0483-bug23431.patch +CVE-2015-0219.patch +CVE-2015-0220.patch +CVE-2015-0221.patch --- python-django-1.1.1.orig/debian/patches/fix_get_host.patch +++ python-django-1.1.1/debian/patches/fix_get_host.patch @@ -0,0 +1,79 @@ +Backport of: + +From 2da4ace0bc1bc1d79bf43b368cb857f6f0cd6b1b Mon Sep 17 00:00:00 2001 +From: Florian Apolloner +Date: Tue, 27 Nov 2012 22:27:14 +0100 +Subject: [PATCH] [1.3.X] Fixed a security issue in get_host. + +Full disclosure and new release forthcoming. +--- + django/http/__init__.py | 4 +++- + tests/regressiontests/requests/tests.py | 11 ++++++++--- + 2 files changed, 11 insertions(+), 4 deletions(-) + +Index: python-django-1.1.1/django/http/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/http/__init__.py 2013-03-04 13:52:59.860007284 -0500 ++++ python-django-1.1.1/django/http/__init__.py 2013-03-04 13:52:59.860007284 -0500 +@@ -21,6 +21,8 @@ + RESERVED_CHARS="!*'();:@&=+$,/?%#[]" + + absolute_http_url_re = re.compile(r"^https?://", re.I) ++host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$") ++ + + class Http404(Exception): + pass +@@ -59,7 +61,7 @@ + host = '%s:%s' % (host, server_port) + + # Disallow potentially poisoned hostnames. +- if set(';/?@&=+$,').intersection(host): ++ if not host_validation_re.match(host.lower()): + raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) + + return host +Index: python-django-1.1.1/tests/regressiontests/requests/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/requests/tests.py 2013-03-04 13:52:59.860007284 -0500 ++++ python-django-1.1.1/tests/regressiontests/requests/tests.py 2013-03-04 13:53:19.020007774 -0500 +@@ -1,3 +1,4 @@ ++# -*- coding: utf-8 -*- + from datetime import datetime, timedelta + import time + import unittest +@@ -110,13 +111,15 @@ + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', ++ 'xn--4ca9at.com', # Punnycode for öäü.com + ] + + poisoned_hosts = [ + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', +- 'example.com:someone@somestie.com:80', +- 'example.com:80/badpath' ++ 'example.com:dr.frankenstein@evil.tld:80', ++ 'example.com:80/badpath', ++ 'example.com: recovermypassword.com', + ] + + for host in legit_hosts: +@@ -187,13 +190,15 @@ + '12.34.56.78:443', + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', ++ 'xn--4ca9at.com', # Punnycode for öäü.com + ] + + poisoned_hosts = [ + 'example.com@evil.tld', + 'example.com:dr.frankenstein@evil.tld', + 'example.com:dr.frankenstein@evil.tld:80', +- 'example.com:80/badpath' ++ 'example.com:80/badpath', ++ 'example.com: recovermypassword.com', + ] + + for host in legit_hosts: --- python-django-1.1.1.orig/debian/patches/16_fix_cross_site_scripting_in_authentication.diff +++ python-django-1.1.1/debian/patches/16_fix_cross_site_scripting_in_authentication.diff @@ -0,0 +1,55 @@ +Description: Fix cross site scripting issue in authentication views +Origin: backport, https://github.com/django/django/commit/4dea4883e6c50d75f215a6b9bcbd95273f57c72d +Forwarded: not needed, it comes from upstream +Bug-Debian: http://bugs.debian.org/683364 + +Index: python-django-1.1.1/django/http/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/http/__init__.py 2012-09-06 09:51:32.000000000 -0400 ++++ python-django-1.1.1/django/http/__init__.py 2012-09-06 09:53:36.104092814 -0400 +@@ -3,13 +3,14 @@ + from Cookie import SimpleCookie, CookieError + from pprint import pformat + from urllib import urlencode +-from urlparse import urljoin ++from urlparse import urljoin, urlparse + try: + # The mod_python version is more efficient, so try importing it first. + from mod_python.util import parse_qsl + except ImportError: + from cgi import parse_qsl + ++from django.core.exceptions import SuspiciousOperation + from django.utils.datastructures import MultiValueDict, ImmutableList + from django.utils.encoding import smart_str, iri_to_uri, force_unicode + from django.http.multipartparser import MultiPartParser +@@ -400,19 +401,21 @@ + raise Exception("This %s instance cannot tell its position" % self.__class__) + return sum([len(chunk) for chunk in self._container]) + +-class HttpResponseRedirect(HttpResponse): +- status_code = 302 ++class HttpResponseRedirectBase(HttpResponse): ++ allowed_schemes = ['http', 'https', 'ftp'] + + def __init__(self, redirect_to): +- HttpResponse.__init__(self) ++ super(HttpResponseRedirectBase, self).__init__() ++ parsed = urlparse(redirect_to) ++ if parsed[0] and parsed[0] not in self.allowed_schemes: ++ raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed[0]) + self['Location'] = redirect_to + +-class HttpResponsePermanentRedirect(HttpResponse): +- status_code = 301 ++class HttpResponseRedirect(HttpResponseRedirectBase): ++ status_code = 302 + +- def __init__(self, redirect_to): +- HttpResponse.__init__(self) +- self['Location'] = redirect_to ++class HttpResponsePermanentRedirect(HttpResponseRedirectBase): ++ status_code = 301 + + class HttpResponseNotModified(HttpResponse): + status_code = 304 --- python-django-1.1.1.orig/debian/patches/add_allowed_hosts.patch +++ python-django-1.1.1/debian/patches/add_allowed_hosts.patch @@ -0,0 +1,391 @@ +Backport of: + +From 27cd872e6e36a81d0bb6f5b8765a1705fecfc253 Mon Sep 17 00:00:00 2001 +From: Carl Meyer +Date: Sat, 9 Feb 2013 12:25:52 -0700 +Subject: [PATCH] [1.3.x] Added ALLOWED_HOSTS setting for HTTP host header + validation. + +This is a security fix; disclosure and advisory coming shortly. + + +Index: python-django-1.1.1/django/conf/global_settings.py +=================================================================== +--- python-django-1.1.1.orig/django/conf/global_settings.py 2013-03-04 14:50:28.300095581 -0500 ++++ python-django-1.1.1/django/conf/global_settings.py 2013-03-04 14:51:11.312096682 -0500 +@@ -29,6 +29,10 @@ + # * Receive x-headers + INTERNAL_IPS = () + ++# Hosts/domain names that are valid for this site. ++# "*" matches anything, ".example.com" matches example.com and all subdomains ++ALLOWED_HOSTS = ['*'] ++ + # Local time zone for this installation. All choices can be found here: + # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all + # systems may support all possibilities). +Index: python-django-1.1.1/django/conf/project_template/settings.py +=================================================================== +--- python-django-1.1.1.orig/django/conf/project_template/settings.py 2009-03-23 19:02:46.000000000 -0400 ++++ python-django-1.1.1/django/conf/project_template/settings.py 2013-03-04 14:51:11.312096682 -0500 +@@ -16,6 +16,10 @@ + DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. + DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + ++# Hosts/domain names that are valid for this site; required if DEBUG is False ++# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts ++ALLOWED_HOSTS = [] ++ + # Local time zone for this installation. Choices can be found here: + # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name + # although not all choices may be available on all operating systems. +Index: python-django-1.1.1/django/http/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/http/__init__.py 2013-03-04 14:50:28.480095586 -0500 ++++ python-django-1.1.1/django/http/__init__.py 2013-03-04 14:51:11.312096682 -0500 +@@ -60,11 +60,15 @@ + if server_port != (self.is_secure() and '443' or '80'): + host = '%s:%s' % (host, server_port) + +- # Disallow potentially poisoned hostnames. +- if not host_validation_re.match(host.lower()): +- raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) +- +- return host ++ if settings.DEBUG: ++ allowed_hosts = ['*'] ++ else: ++ allowed_hosts = settings.ALLOWED_HOSTS ++ if validate_host(host, allowed_hosts): ++ return host ++ else: ++ raise SuspiciousOperation( ++ "Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host) + + def get_full_path(self): + return '' +@@ -475,3 +479,43 @@ + else: + return s + ++def validate_host(host, allowed_hosts): ++ """ ++ Validate the given host header value for this site. ++ ++ Check that the host looks valid and matches a host or host pattern in the ++ given list of ``allowed_hosts``. Any pattern beginning with a period ++ matches a domain and all its subdomains (e.g. ``.example.com`` matches ++ ``example.com`` and any subdomain), ``*`` matches anything, and anything ++ else must match exactly. ++ ++ Return ``True`` for a valid host, ``False`` otherwise. ++ ++ """ ++ # All validation is case-insensitive ++ host = host.lower() ++ ++ # Basic sanity check ++ if not host_validation_re.match(host): ++ return False ++ ++ # Validate only the domain part. ++ if host[-1] == ']': ++ # It's an IPv6 address without a port. ++ domain = host ++ else: ++ domain = host.rsplit(':', 1)[0] ++ ++ for pattern in allowed_hosts: ++ pattern = pattern.lower() ++ match = ( ++ pattern == '*' or ++ pattern.startswith('.') and ( ++ domain.endswith(pattern) or domain == pattern[1:] ++ ) or ++ pattern == domain ++ ) ++ if match: ++ return True ++ ++ return False +Index: python-django-1.1.1/django/test/utils.py +=================================================================== +--- python-django-1.1.1.orig/django/test/utils.py 2009-03-18 06:46:55.000000000 -0400 ++++ python-django-1.1.1/django/test/utils.py 2013-03-04 14:51:11.312096682 -0500 +@@ -60,6 +60,9 @@ + mail.original_SMTPConnection = mail.SMTPConnection + mail.SMTPConnection = TestSMTPConnection + ++ settings._original_allowed_hosts = settings.ALLOWED_HOSTS ++ settings.ALLOWED_HOSTS = ['*'] ++ + mail.outbox = [] + + deactivate() +@@ -77,6 +80,9 @@ + mail.SMTPConnection = mail.original_SMTPConnection + del mail.original_SMTPConnection + ++ settings.ALLOWED_HOSTS = settings._original_allowed_hosts ++ del settings._original_allowed_hosts ++ + del mail.outbox + + +Index: python-django-1.1.1/docs/ref/settings.txt +=================================================================== +--- python-django-1.1.1.orig/docs/ref/settings.txt 2013-03-04 14:50:28.300095581 -0500 ++++ python-django-1.1.1/docs/ref/settings.txt 2013-03-04 14:51:11.312096682 -0500 +@@ -68,6 +68,42 @@ + Note that Django will e-mail *all* of these people whenever an error happens. + See :ref:`howto-error-reporting` for more information. + ++.. setting:: ALLOWED_HOSTS ++ ++ALLOWED_HOSTS ++------------- ++ ++Default: ``['*']`` ++ ++A list of strings representing the host/domain names that this Django site can ++serve. This is a security measure to prevent an attacker from poisoning caches ++and password reset emails with links to malicious hosts by submitting requests ++with a fake HTTP ``Host`` header, which is possible even under many ++seemingly-safe webserver configurations. ++ ++Values in this list can be fully qualified names (e.g. ``'www.example.com'``), ++in which case they will be matched against the request's ``Host`` header ++exactly (case-insensitive, not including port). A value beginning with a period ++can be used as a subdomain wildcard: ``'.example.com'`` will match ++``example.com``, ``www.example.com``, and any other subdomain of ++``example.com``. A value of ``'*'`` will match anything; in this case you are ++responsible to provide your own validation of the ``Host`` header (perhaps in a ++middleware; if so this middleware must be listed first in ++:setting:`MIDDLEWARE_CLASSES`). ++ ++If the ``Host`` header (or ``X-Forwarded-Host`` if ++:setting:`USE_X_FORWARDED_HOST` is enabled) does not match any value in this ++list, the :meth:`django.http.HttpRequest.get_host()` method will raise ++:exc:`~django.core.exceptions.SuspiciousOperation`. ++ ++When :setting:`DEBUG` is ``True`` or when running tests, host validation is ++disabled; any host will be accepted. Thus it's usually only necessary to set it ++in production. ++ ++This validation only applies via :meth:`~django.http.HttpRequest.get_host()`; ++if your code accesses the ``Host`` header directly from ``request.META`` you ++are bypassing this security protection. ++ + .. setting:: ALLOWED_INCLUDE_ROOTS + + ALLOWED_INCLUDE_ROOTS +Index: python-django-1.1.1/tests/regressiontests/requests/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/requests/tests.py 2013-03-04 14:50:28.480095586 -0500 ++++ python-django-1.1.1/tests/regressiontests/requests/tests.py 2013-03-04 14:52:49.568099198 -0500 +@@ -63,17 +63,23 @@ + 'http://www.example.com/path/with:colons') + + def test_http_get_host(self): +- old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ _old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS + try: + settings.USE_X_FORWARDED_HOST = False ++ settings.ALLOWED_HOSTS = [ ++ 'forward.com', 'example.com', 'internal.com', '12.34.56.78', ++ '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com', ++ '.multitenant.com', 'INSENSITIVE.com', ++ ] + + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { +- u'HTTP_X_FORWARDED_HOST': u'forward.com', +- u'HTTP_HOST': u'example.com', +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'HTTP_X_FORWARDED_HOST': 'forward.com', ++ 'HTTP_HOST': 'example.com', ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is ignored. + self.assertEqual(request.get_host(), 'example.com') +@@ -81,25 +87,25 @@ + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { +- u'HTTP_HOST': u'example.com', +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'HTTP_HOST': 'example.com', ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 8042, ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + +@@ -112,6 +118,9 @@ + '[2001:19f0:feee::dead:beef:cafe]', + '[2001:19f0:feee::dead:beef:cafe]:8080', + 'xn--4ca9at.com', # Punnycode for öäü.com ++ 'anything.multitenant.com', ++ 'multitenant.com', ++ 'insensitive.com', + ] + + poisoned_hosts = [ +@@ -120,6 +129,7 @@ + 'example.com:dr.frankenstein@evil.tld:80', + 'example.com:80/badpath', + 'example.com: recovermypassword.com', ++ 'other.com', # not in ALLOWED_HOSTS + ] + + for host in legit_hosts: +@@ -130,29 +140,31 @@ + request.get_host() + + for host in poisoned_hosts: +- def test_host_poisoning(): ++ def _test(): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() +- self.assertRaises(SuspiciousOperation, test_host_poisoning) +- ++ self.assertRaises(SuspiciousOperation, _test) + finally: +- settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST ++ settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS ++ settings.USE_X_FORWARDED_HOST = _old_USE_X_FORWARDED_HOST + + def test_http_get_host_with_x_forwarded_host(self): +- old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ _old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS + try: + settings.USE_X_FORWARDED_HOST = True ++ settings.ALLOWED_HOSTS = ['*'] + + # Check if X_FORWARDED_HOST is provided. + request = HttpRequest() + request.META = { +- u'HTTP_X_FORWARDED_HOST': u'forward.com', +- u'HTTP_HOST': u'example.com', +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'HTTP_X_FORWARDED_HOST': 'forward.com', ++ 'HTTP_HOST': 'example.com', ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + # X_FORWARDED_HOST is obeyed. + self.assertEqual(request.get_host(), 'forward.com') +@@ -160,25 +172,25 @@ + # Check if X_FORWARDED_HOST isn't provided. + request = HttpRequest() + request.META = { +- u'HTTP_HOST': u'example.com', +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'HTTP_HOST': 'example.com', ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'example.com') + + # Check if HTTP_HOST isn't provided. + request = HttpRequest() + request.META = { +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 80, ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 80, + } + self.assertEqual(request.get_host(), 'internal.com') + + # Check if HTTP_HOST isn't provided, and we're on a nonstandard port + request = HttpRequest() + request.META = { +- u'SERVER_NAME': u'internal.com', +- u'SERVER_PORT': 8042, ++ 'SERVER_NAME': 'internal.com', ++ 'SERVER_PORT': 8042, + } + self.assertEqual(request.get_host(), 'internal.com:8042') + +@@ -209,13 +221,30 @@ + request.get_host() + + for host in poisoned_hosts: +- def test_host_poisoning(): ++ def _test(): + request = HttpRequest() + request.META = { + 'HTTP_HOST': host, + } + request.get_host() +- self.assertRaises(SuspiciousOperation, test_host_poisoning) ++ self.assertRaises(SuspiciousOperation, _test) ++ finally: ++ settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS ++ settings.USE_X_FORWARDED_HOST = _old_USE_X_FORWARDED_HOST ++ ++ def test_host_validation_disabled_in_debug_mode(self): ++ """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass.""" ++ _old_DEBUG = settings.DEBUG ++ _old_ALLOWED_HOSTS = settings.ALLOWED_HOSTS ++ try: ++ settings.DEBUG = True ++ settings.ALLOWED_HOSTS = [] + ++ request = HttpRequest() ++ request.META = { ++ 'HTTP_HOST': 'example.com', ++ } ++ self.assertEqual(request.get_host(), 'example.com') + finally: +- settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST ++ settings.DEBUG = _old_DEBUG ++ settings.ALLOWED_HOSTS = _old_ALLOWED_HOSTS +Index: python-django-1.1.1/django/utils/functional.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/functional.py 2009-03-01 23:48:22.000000000 -0500 ++++ python-django-1.1.1/django/utils/functional.py 2013-03-04 16:41:17.344265830 -0500 +@@ -281,6 +281,13 @@ + self._setup() + setattr(self._wrapped, name, value) + ++ def __delattr__(self, name): ++ if name == "_wrapped": ++ raise TypeError("can't delete _wrapped.") ++ if self._wrapped is None: ++ self._setup() ++ delattr(self._wrapped, name) ++ + def _setup(self): + """ + Must be implemented by subclasses to initialise the wrapped object. --- python-django-1.1.1.orig/debian/patches/CVE-2011-4136.patch +++ python-django-1.1.1/debian/patches/CVE-2011-4136.patch @@ -0,0 +1,90 @@ +Origin: https://code.djangoproject.com/changeset/16765 +Description: Corrected an issue which could allow attackers to manipulate + session data using the cache. +Index: python-django-1.2.5/django/contrib/sessions/backends/cached_db.py +=================================================================== +--- python-django-1.2.5.orig/django/contrib/sessions/backends/cached_db.py 2011-11-28 17:44:57.000000000 -0600 ++++ python-django-1.2.5/django/contrib/sessions/backends/cached_db.py 2011-11-28 17:45:01.000000000 -0600 +@@ -6,6 +6,8 @@ + from django.contrib.sessions.backends.db import SessionStore as DBStore + from django.core.cache import cache + ++KEY_PREFIX = "django.contrib.sessions.cached_db" ++ + class SessionStore(DBStore): + """ + Implements cached, database backed sessions. +@@ -15,10 +17,11 @@ + super(SessionStore, self).__init__(session_key) + + def load(self): +- data = cache.get(self.session_key, None) ++ data = cache.get(KEY_PREFIX + self.session_key, None) + if data is None: + data = super(SessionStore, self).load() +- cache.set(self.session_key, data, settings.SESSION_COOKIE_AGE) ++ cache.set(KEY_PREFIX + self.session_key, data, ++ settings.SESSION_COOKIE_AGE) + return data + + def exists(self, session_key): +@@ -26,11 +29,12 @@ + + def save(self, must_create=False): + super(SessionStore, self).save(must_create) +- cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) ++ cache.set(KEY_PREFIX + self.session_key, self._session, ++ settings.SESSION_COOKIE_AGE) + + def delete(self, session_key=None): + super(SessionStore, self).delete(session_key) +- cache.delete(session_key or self.session_key) ++ cache.delete(KEY_PREFIX + (session_key or self.session_key)) + + def flush(self): + """ +Index: python-django-1.2.5/django/contrib/sessions/backends/cache.py +=================================================================== +--- python-django-1.2.5.orig/django/contrib/sessions/backends/cache.py 2011-11-28 17:44:57.000000000 -0600 ++++ python-django-1.2.5/django/contrib/sessions/backends/cache.py 2011-11-28 17:45:01.000000000 -0600 +@@ -1,6 +1,8 @@ + from django.contrib.sessions.backends.base import SessionBase, CreateError + from django.core.cache import cache + ++KEY_PREFIX = "django.contrib.sessions.cache" ++ + class SessionStore(SessionBase): + """ + A cache-based session store. +@@ -10,7 +12,7 @@ + super(SessionStore, self).__init__(session_key) + + def load(self): +- session_data = self._cache.get(self.session_key) ++ session_data = self._cache.get(KEY_PREFIX + self.session_key) + if session_data is not None: + return session_data + self.create() +@@ -37,13 +39,13 @@ + func = self._cache.add + else: + func = self._cache.set +- result = func(self.session_key, self._get_session(no_load=must_create), ++ result = func(KEY_PREFIX + self.session_key, self._get_session(no_load=must_create), + self.get_expiry_age()) + if must_create and not result: + raise CreateError + + def exists(self, session_key): +- if self._cache.has_key(session_key): ++ if self._cache.has_key(KEY_PREFIX + session_key): + return True + return False + +@@ -52,5 +54,5 @@ + if self._session_key is None: + return + session_key = self._session_key +- self._cache.delete(session_key) ++ self._cache.delete(KEY_PREFIX + session_key) + --- python-django-1.1.1.orig/debian/patches/CVE-2015-0220.patch +++ python-django-1.1.1/debian/patches/CVE-2015-0220.patch @@ -0,0 +1,15 @@ +Description: fix mitigated possible XSS attack via user-supplied redirect URLs +Origin: backported from patch provided by Tim Graham + +Index: python-django-1.1.1/django/utils/http.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/http.py 2015-01-13 08:05:33.797990001 -0500 ++++ python-django-1.1.1/django/utils/http.py 2015-01-13 08:07:57.007022592 -0500 +@@ -132,6 +132,7 @@ + """ + if not url: + return False ++ url = url.strip() + 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']) --- python-django-1.1.1.orig/debian/patches/CVE-2014-0483.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0483.patch @@ -0,0 +1,122 @@ +Backport of: + +From 027bd348642007617518379f8b02546abacaa6e0 Mon Sep 17 00:00:00 2001 +From: Simon Charette +Date: Mon, 11 Aug 2014 15:36:16 -0400 +Subject: [PATCH] [1.4.x] Prevented data leakage in contrib.admin via query + string manipulation. + +This is a security fix. Disclosure following shortly. +--- + django/contrib/admin/exceptions.py | 6 ++++++ + django/contrib/admin/options.py | 18 ++++++++++++++++++ + django/contrib/admin/views/main.py | 6 +++++- + docs/releases/1.4.14.txt | 15 +++++++++++++++ + tests/regressiontests/admin_views/tests.py | 21 +++++++++++++++++---- + 5 files changed, 61 insertions(+), 5 deletions(-) + create mode 100644 django/contrib/admin/exceptions.py + +Index: python-django-1.1.1/django/contrib/admin/exceptions.py +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/django/contrib/admin/exceptions.py 2014-09-10 14:38:25.038876154 -0400 +@@ -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 +Index: python-django-1.1.1/django/contrib/admin/options.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/options.py 2014-09-10 14:38:25.042876154 -0400 ++++ python-django-1.1.1/django/contrib/admin/options.py 2014-09-10 14:38:25.038876154 -0400 +@@ -291,6 +291,24 @@ + return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) + media = property(_media) + ++ 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." + opts = self.opts +Index: python-django-1.1.1/django/contrib/admin/views/main.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/views/main.py 2014-09-10 14:38:25.042876154 -0400 ++++ python-django-1.1.1/django/contrib/admin/views/main.py 2014-09-10 14:38:25.038876154 -0400 +@@ -1,4 +1,5 @@ + from django.contrib.admin.filterspecs import FilterSpec ++from django.contrib.admin.exceptions import DisallowedModelAdminToField + from django.contrib.admin.options import IncorrectLookupParameters + from django.contrib.admin.util import quote + from django.core.exceptions import SuspiciousOperation +@@ -55,7 +56,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] +Index: python-django-1.1.1/tests/regressiontests/admin_views/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_views/tests.py 2014-09-10 14:38:25.042876154 -0400 ++++ python-django-1.1.1/tests/regressiontests/admin_views/tests.py 2014-09-10 14:41:46.098877289 -0400 +@@ -5,12 +5,14 @@ + from django.core.files import temp as tempfile + from django.core.exceptions import SuspiciousOperation + from django.test import TestCase ++from django.contrib.admin.exceptions import DisallowedModelAdminToField + from django.contrib.auth.models import User, Permission + from django.contrib.contenttypes.models import ContentType + 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.helpers import ACTION_CHECKBOX_NAME ++from django.contrib.admin.views.main import TO_FIELD_VAR + from django.utils.cache import get_max_age + from django.utils.html import escape + +@@ -242,6 +244,22 @@ + "Changelist filter isn't showing options contained inside a model field 'choices' option named group." + ) + ++ def test_disallowed_to_field(self): ++ self.assertRaises(DisallowedModelAdminToField, 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. ++ self.assertRaises(DisallowedModelAdminToField, 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) ++ ++ + class SaveAsTests(TestCase): + fixtures = ['admin-views-users.xml','admin-views-person.xml'] + --- python-django-1.1.1.orig/debian/patches/03_manpage.diff +++ python-django-1.1.1/debian/patches/03_manpage.diff @@ -0,0 +1,23 @@ +Forwarded-Upstream: not needed +Author: Brett Parker +Comment: + 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. + +--- a/docs/man/django-admin.1 2007-09-08 10:47:27.516890257 +0100 ++++ b/docs/man/django-admin.1 2007-09-08 10:48:01.822845242 +0100 +@@ -1,9 +1,9 @@ +-.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 + .B [options] + .sp + .SH "DESCRIPTION" --- python-django-1.1.1.orig/debian/patches/11_CVE-2011-0697.diff +++ python-django-1.1.1/debian/patches/11_CVE-2011-0697.diff @@ -0,0 +1,42 @@ +Origin: http://code.djangoproject.com/changeset/15472 +Description: Fixed XSS in AdminFileWidget. Added AdminFileWidgetTest() class + and tests +Bug-Ubuntu: https://launchpad.net/bugs/719031 + +Index: python-django-1.1.1/django/contrib/admin/widgets.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/admin/widgets.py 2011-02-15 16:44:55.000000000 -0600 ++++ python-django-1.1.1/django/contrib/admin/widgets.py 2011-02-15 16:44:59.000000000 -0600 +@@ -93,7 +93,7 @@ + output = [] + if value and hasattr(value, "url"): + output.append('%s %s
%s ' % \ +- (_('Currently:'), value.url, value, _('Change:'))) ++ (_('Currently:'), escape(value.url), escape(value), _('Change:'))) + output.append(super(AdminFileWidget, self).render(name, value, attrs)) + return mark_safe(u''.join(output)) + +Index: python-django-1.1.1/tests/regressiontests/admin_widgets/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/admin_widgets/tests.py 2011-02-15 16:44:55.000000000 -0600 ++++ python-django-1.1.1/tests/regressiontests/admin_widgets/tests.py 2011-02-15 16:44:59.000000000 -0600 +@@ -130,3 +130,19 @@ + class OldAdminForeignKeyWidgetChangeList(AdminForeignKeyWidgetChangeList): + urls = 'regressiontests.admin_widgets.urls2' + admin_root = '/deep/down/admin' ++ ++class AdminFileWidgetTest(DjangoTestCase): ++ def test_render_escapes_html(self): ++ class StrangeFieldFile(object): ++ url = "something?chapter=1§=2©=3&lang=en" ++ ++ def __unicode__(self): ++ return u'''something
.jpg''' ++ ++ widget = widgets.AdminFileWidget() ++ field = StrangeFieldFile() ++ output = widget.render('myfile', field) ++ self.assertFalse(field.url in output) ++ self.assertTrue(u'href="something?chapter=1&sect=2&copy=3&lang=en"' in output) ++ self.assertFalse(unicode(field) in output) ++ self.assertTrue(u'something<div onclick="alert('oops')">.jpg' in output) --- python-django-1.1.1.orig/debian/patches/CVE-2012-4520.patch +++ python-django-1.1.1/debian/patches/CVE-2012-4520.patch @@ -0,0 +1,113 @@ +Origin: backport, b45c377f8f488955e0c7069cad3f3dd21910b071 +From b45c377f8f488955e0c7069cad3f3dd21910b071 Mon Sep 17 00:00:00 2001 +From: Preston Holmes +Date: Wed, 17 Oct 2012 14:43:08 -0700 +Subject: [PATCH] Fixed a security issue related to password resets + +Full disclosure and new release are forthcoming + +backport from master +--- + django/contrib/auth/tests/urls.py | 1 + + django/contrib/auth/tests/views.py | 39 ++++++++++++++++++++++++++++++++++++ + django/contrib/auth/views.py | 2 +- + django/http/__init__.py | 5 +++++ + 4 files changed, 46 insertions(+), 1 deletion(-) + +Index: python-django-1.1.1/django/contrib/auth/tests/urls.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/tests/urls.py 2009-05-04 02:05:44.000000000 -0500 ++++ python-django-1.1.1/django/contrib/auth/tests/urls.py 2012-11-09 16:13:37.000000000 -0600 +@@ -14,5 +14,6 @@ + (r'^logout/custom_query/$', 'django.contrib.auth.views.logout', dict(redirect_field_name='follow')), + (r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')), + (r'^remote_user/$', remote_user_auth_view), ++ (r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)), + ) + +Index: python-django-1.1.1/django/contrib/auth/tests/views.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/tests/views.py 2009-04-18 22:41:33.000000000 -0500 ++++ python-django-1.1.1/django/contrib/auth/tests/views.py 2012-11-09 16:12:08.000000000 -0600 +@@ -8,6 +8,7 @@ + from django.contrib.auth.models import User + from django.test import TestCase + from django.core import mail ++from django.core.exceptions import SuspiciousOperation + from django.core.urlresolvers import reverse + + class AuthViewsTestCase(TestCase): +@@ -62,6 +63,44 @@ + self.assertEquals(len(mail.outbox), 1) + self.assert_("http://" in mail.outbox[0].body) + ++ def test_admin_reset(self): ++ "If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override." ++ response = self.client.post('/admin_password_reset/', ++ {'email': 'staffmember@example.com'}, ++ HTTP_HOST='adminsite.com' ++ ) ++ self.assertEqual(response.status_code, 302) ++ self.assertEqual(len(mail.outbox), 1) ++ self.assertTrue("http://adminsite.com" in mail.outbox[0].body) ++ self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) ++ ++ def test_poisoned_http_host(self): ++ "Poisoned HTTP_HOST headers can't be used for reset emails" ++ # This attack is based on the way browsers handle URLs. The colon ++ # should be used to separate the port, but if the URL contains an @, ++ # the colon is interpreted as part of a username for login purposes, ++ # making 'evil.com' the request domain. Since HTTP_HOST is used to ++ # produce a meaningful reset URL, we need to be certain that the ++ # HTTP_HOST header isn't poisoned. This is done as a check when get_host() ++ # is invoked, but we check here as a practical consequence. ++ def test_host_poisoning(): ++ self.client.post('/password_reset/', ++ {'email': 'staffmember@example.com'}, ++ HTTP_HOST='www.example:dr.frankenstein@evil.tld' ++ ) ++ self.assertRaises(SuspiciousOperation, test_host_poisoning) ++ self.assertEqual(len(mail.outbox), 0) ++ ++ def test_poisoned_http_host_admin_site(self): ++ "Poisoned HTTP_HOST headers can't be used for reset emails on admin views" ++ def test_host_poisoning(): ++ self.client.post('/admin_password_reset/', ++ {'email': 'staffmember@example.com'}, ++ HTTP_HOST='www.example:dr.frankenstein@evil.tld' ++ ) ++ self.assertRaises(SuspiciousOperation, test_host_poisoning) ++ self.assertEqual(len(mail.outbox), 0) ++ + def _test_confirm_start(self): + # Start by creating the email + response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) +Index: python-django-1.1.1/django/contrib/auth/views.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/views.py 2009-04-01 12:02:32.000000000 -0500 ++++ python-django-1.1.1/django/contrib/auth/views.py 2012-11-09 16:14:53.000000000 -0600 +@@ -91,7 +91,7 @@ + opts['use_https'] = request.is_secure() + opts['token_generator'] = token_generator + if is_admin_site: +- opts['domain_override'] = request.META['HTTP_HOST'] ++ opts['domain_override'] = domain_override=request.get_host() + else: + opts['email_template_name'] = email_template_name + if not Site._meta.installed: +Index: python-django-1.1.1/django/http/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/http/__init__.py 2012-11-09 16:11:27.000000000 -0600 ++++ python-django-1.1.1/django/http/__init__.py 2012-11-09 16:12:08.000000000 -0600 +@@ -57,6 +57,11 @@ + server_port = str(self.META['SERVER_PORT']) + if server_port != (self.is_secure() and '443' or '80'): + host = '%s:%s' % (host, server_port) ++ ++ # Disallow potentially poisoned hostnames. ++ if set(';/?@&=+$,').intersection(host): ++ raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) ++ + return host + + def get_full_path(self): --- python-django-1.1.1.orig/debian/patches/CVE-2014-0480.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0480.patch @@ -0,0 +1,59 @@ +Backport of: + +From c2fe73133b62a1d9e8f7a6b43966570b14618d7e Mon Sep 17 00:00:00 2001 +From: Florian Apolloner +Date: Thu, 17 Jul 2014 21:59:28 +0200 +Subject: [PATCH] [1.4.x] Prevented reverse() from generating URLs pointing to + other hosts. + +This is a security fix. Disclosure following shortly. +--- + django/core/urlresolvers.py | 2 ++ + docs/releases/1.4.14.txt | 13 +++++++++++++ + tests/regressiontests/urlpatterns_reverse/tests.py | 3 +++ + tests/regressiontests/urlpatterns_reverse/urls.py | 3 +++ + 4 files changed, 21 insertions(+) + +Index: python-django-1.1.1/django/core/urlresolvers.py +=================================================================== +--- python-django-1.1.1.orig/django/core/urlresolvers.py 2014-09-10 12:55:45.226841390 -0400 ++++ python-django-1.1.1/django/core/urlresolvers.py 2014-09-10 12:55:45.222841390 -0400 +@@ -310,6 +310,8 @@ + unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()]) + candidate = result % unicode_kwargs + if re.search(u'^%s' % pattern, candidate, re.UNICODE): ++ if candidate.startswith('/'): ++ candidate = '%%2F%s' % candidate[1:] + 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 +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/tests.py 2014-09-10 12:55:45.226841390 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/tests.py 2014-09-10 12:55:45.222841390 -0400 +@@ -100,8 +100,10 @@ + ('kwargs_view', '/arg_view/10/', [], {'arg1':10}), + ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}), + ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}), +- ('non_path_include', '/includes/non_path_include/', [], {}) ++ ('non_path_include', '/includes/non_path_include/', [], {}), + ++ # Security tests ++ ('security', '/%2Fexample.com/security/', ['/example.com'], {}), + ) + + class URLPatternReverse(TestCase): +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/urls.py 2014-09-10 12:55:45.226841390 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py 2014-09-10 12:55:45.222841390 -0400 +@@ -63,6 +63,9 @@ + + url('^includes/', include(other_patterns)), + ++ # Security tests ++ url('(.+)/security/$', empty_view, name='security'), ++ + ) + + --- python-django-1.1.1.orig/debian/patches/06_python_2.6.3_regression.diff +++ python-django-1.1.1/debian/patches/06_python_2.6.3_regression.diff @@ -0,0 +1,14 @@ +Forwarded-Upstream: http://code.djangoproject.com/changeset/11619 +Comment: + Fix FTBFS under Python 2.6.3; int(Decimal('nan')) raises ValueError instead + of returning None. + +--- ./django/template/defaultfilters.py (revision 10543) ++++ ./django/template/defaultfilters.py (revision 11619) +@@ -163,5 +163,5 @@ + try: + m = int(d) - d +- except (OverflowError, InvalidOperation): ++ except (ValueError, OverflowError, InvalidOperation): + return input_val + --- python-django-1.1.1.orig/debian/patches/CVE-2014-0472.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0472.patch @@ -0,0 +1,141 @@ +Description: fix unexpected code execution using reverse() +Origin: backport of 1.4.x patch provided by upstream +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309779 + +Index: python-django-1.1.1/django/core/urlresolvers.py +=================================================================== +--- python-django-1.1.1.orig/django/core/urlresolvers.py 2014-04-19 11:12:59.682296195 -0400 ++++ python-django-1.1.1/django/core/urlresolvers.py 2014-04-19 11:12:59.678296195 -0400 +@@ -153,6 +153,10 @@ + self._reverse_dict = None + self._namespace_dict = None + self._app_dict = None ++ # 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 '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern) +@@ -162,6 +166,15 @@ + namespaces = {} + apps = {} + 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:] +@@ -182,6 +195,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)) +@@ -189,6 +203,7 @@ + self._reverse_dict = lookups + self._namespace_dict = namespaces + self._app_dict = apps ++ self._populated = True + + def _get_reverse_dict(self): + if self._reverse_dict is None: +@@ -268,8 +283,13 @@ + def reverse(self, lookup_view, *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) +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/nonimported_module.py +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/nonimported_module.py 2014-04-19 11:12:59.678296195 -0400 +@@ -0,0 +1,3 @@ ++def view(request): ++ """Stub view""" ++ pass +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/tests.py 2014-04-19 11:12:59.682296195 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/tests.py 2014-04-19 11:13:41.922296880 -0400 +@@ -1,3 +1,4 @@ ++# -*- coding: utf-8 -*- + """ + Unit tests for reverse URL lookups. + """ +@@ -14,6 +15,8 @@ + ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.no_urls doesn't have any patterns in it + """} + ++import sys ++ + import unittest + + from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404 +@@ -159,6 +162,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'], '/\xc3\xa6\xc3\xb8\xc3\xa5/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'], '/\xc3\xa6\xc3\xb8\xc3\xa5.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.failIf("urlpatterns_reverse.nonimported_module" in 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/') + + + class NamespaceTests(TestCase): +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/urls.py 2014-04-19 11:12:59.682296195 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/urls.py 2014-04-19 11:12:59.678296195 -0400 +@@ -3,6 +3,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('', +Index: python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/views.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/urlpatterns_reverse/views.py 2014-04-19 11:12:59.682296195 -0400 ++++ python-django-1.1.1/tests/regressiontests/urlpatterns_reverse/views.py 2014-04-19 11:13:54.874297090 -0400 +@@ -6,3 +6,6 @@ + + def absolute_kwargs_view(request, arg1=1, arg2=2): + pass ++ ++def nested_view(request): ++ pass --- python-django-1.1.1.orig/debian/patches/CVE-2011-4139.patch +++ python-django-1.1.1/debian/patches/CVE-2011-4139.patch @@ -0,0 +1,281 @@ +Origin: backport, https://code.djangoproject.com/changeset/16764 +Description: Added protection against spoofing of X_FORWARDED_HOST headers. + Also apply the following for the regression patch (and it will make future + updates easier): + - https://code.djangoproject.com/changeset/13930 + - https://code.djangoproject.com/changeset/13949 + +Index: python-django-1.1.1/django/http/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/http/__init__.py 2009-05-08 06:31:36.000000000 -0500 ++++ python-django-1.1.1/django/http/__init__.py 2011-12-07 16:02:04.000000000 -0600 +@@ -45,7 +45,8 @@ + def get_host(self): + """Returns the HTTP host using the environment or request headers.""" + # We try three options, in order of decreasing preference. +- if 'HTTP_X_FORWARDED_HOST' in self.META: ++ if settings.USE_X_FORWARDED_HOST and ( ++ 'HTTP_X_FORWARDED_HOST' in self.META): + host = self.META['HTTP_X_FORWARDED_HOST'] + elif 'HTTP_HOST' in self.META: + host = self.META['HTTP_HOST'] +Index: python-django-1.1.1/django/conf/global_settings.py +=================================================================== +--- python-django-1.1.1.orig/django/conf/global_settings.py 2009-05-26 00:32:52.000000000 -0500 ++++ python-django-1.1.1/django/conf/global_settings.py 2011-12-07 16:02:04.000000000 -0600 +@@ -290,6 +290,8 @@ + DEFAULT_TABLESPACE = '' + DEFAULT_INDEX_TABLESPACE = '' + ++USE_X_FORWARDED_HOST = False ++ + ############## + # MIDDLEWARE # + ############## +Index: python-django-1.1.1/docs/ref/request-response.txt +=================================================================== +--- python-django-1.1.1.orig/docs/ref/request-response.txt 2009-09-13 01:31:17.000000000 -0500 ++++ python-django-1.1.1/docs/ref/request-response.txt 2011-12-07 16:02:04.000000000 -0600 +@@ -193,12 +193,11 @@ + + .. versionadded:: 1.0 + +- Returns the originating host of the request using information from the +- ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If +- they don't provide a value, the method uses a combination of +- ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. +- +- .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ ++ Returns the originating host of the request using information from ++ the ``HTTP_X_FORWARDED_HOST`` (if enabled in the settings) and ++ ``HTTP_HOST`` headers (in that order). If they don't provide a value, ++ the method uses a combination of ``SERVER_NAME`` and ``SERVER_PORT`` as ++ detailed in :pep:`3333`. + + Example: ``"127.0.0.1:8000"`` + +Index: python-django-1.1.1/docs/ref/settings.txt +=================================================================== +--- python-django-1.1.1.orig/docs/ref/settings.txt 2009-06-18 08:33:18.000000000 -0500 ++++ python-django-1.1.1/docs/ref/settings.txt 2011-12-07 16:02:04.000000000 -0600 +@@ -1189,6 +1189,19 @@ + set to ``False``, Django will make some optimizations so as not to load the + internationalization machinery. + ++.. setting:: USE_X_FORWARDED_HOST ++ ++USE_X_FORWARDED_HOST ++-------------------- ++ ++.. versionadded:: 1.3.1 ++ ++Default: ``False`` ++ ++A boolean that specifies whether to use the X-Forwarded-Host header in ++preference to the Host header. This should only be enabled if a proxy ++which sets this header is in use. ++ + .. setting:: YEAR_MONTH_FORMAT + + YEAR_MONTH_FORMAT +Index: python-django-1.1.1/tests/regressiontests/requests/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/requests/tests.py 2008-08-23 12:28:12.000000000 -0500 ++++ python-django-1.1.1/tests/regressiontests/requests/tests.py 2011-12-07 16:02:04.000000000 -0600 +@@ -1,47 +1,149 @@ +-""" +->>> from django.http import HttpRequest +->>> print repr(HttpRequest()) +- +- +->>> from django.core.handlers.wsgi import WSGIRequest +->>> print repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'})) +-, +-POST:, +-COOKIES:{}, +-META:{...}> +- +->>> from django.core.handlers.modpython import ModPythonRequest +->>> class FakeModPythonRequest(ModPythonRequest): +-... def __init__(self, *args, **kwargs): +-... super(FakeModPythonRequest, self).__init__(*args, **kwargs) +-... self._get = self._post = self._meta = self._cookies = {} +->>> class Dummy: +-... def get_options(self): +-... return {} +->>> req = Dummy() +->>> req.uri = 'bogus' +->>> print repr(FakeModPythonRequest(req)) +- +- +->>> from django.http import parse_cookie +->>> parse_cookie('invalid:key=true') +-{} +- +->>> request = HttpRequest() +->>> print request.build_absolute_uri(location="https://www.example.com/asdf") +-https://www.example.com/asdf +->>> request.get_host = lambda: 'www.example.com' +->>> request.path = '' +->>> print request.build_absolute_uri(location="/path/with:colons") +-http://www.example.com/path/with:colons +-""" ++from datetime import datetime, timedelta ++import time ++import unittest ++ ++from django.conf import settings ++from django.http import HttpRequest, HttpResponse, parse_cookie ++from django.core.handlers.wsgi import WSGIRequest ++from django.core.handlers.modpython import ModPythonRequest ++from django.utils.http import cookie_date ++ ++ ++class RequestsTests(unittest.TestCase): ++ ++ def test_httprequest(self): ++ request = HttpRequest() ++ self.assertEqual(request.GET.keys(), []) ++ self.assertEqual(request.POST.keys(), []) ++ self.assertEqual(request.COOKIES.keys(), []) ++ self.assertEqual(request.META.keys(), []) ++ ++ def test_wsgirequest(self): ++ request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'}) ++ self.assertEqual(request.GET.keys(), []) ++ self.assertEqual(request.POST.keys(), []) ++ self.assertEqual(request.COOKIES.keys(), []) ++ self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME'])) ++ self.assertEqual(request.META['PATH_INFO'], 'bogus') ++ self.assertEqual(request.META['REQUEST_METHOD'], 'bogus') ++ self.assertEqual(request.META['SCRIPT_NAME'], '') ++ ++ def test_modpythonrequest(self): ++ class FakeModPythonRequest(ModPythonRequest): ++ def __init__(self, *args, **kwargs): ++ super(FakeModPythonRequest, self).__init__(*args, **kwargs) ++ self._get = self._post = self._meta = self._cookies = {} ++ ++ class Dummy: ++ def get_options(self): ++ return {} ++ ++ req = Dummy() ++ req.uri = 'bogus' ++ request = FakeModPythonRequest(req) ++ self.assertEqual(request.path, 'bogus') ++ self.assertEqual(request.GET.keys(), []) ++ self.assertEqual(request.POST.keys(), []) ++ self.assertEqual(request.COOKIES.keys(), []) ++ self.assertEqual(request.META.keys(), []) ++ ++ def test_parse_cookie(self): ++ self.assertEqual(parse_cookie('invalid:key=true'), {}) ++ ++ def test_httprequest_location(self): ++ request = HttpRequest() ++ self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"), ++ 'https://www.example.com/asdf') ++ ++ request.get_host = lambda: 'www.example.com' ++ request.path = '' ++ self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), ++ 'http://www.example.com/path/with:colons') ++ ++ def test_http_get_host(self): ++ old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ try: ++ settings.USE_X_FORWARDED_HOST = False ++ ++ # Check if X_FORWARDED_HOST is provided. ++ request = HttpRequest() ++ request.META = { ++ u'HTTP_X_FORWARDED_HOST': u'forward.com', ++ u'HTTP_HOST': u'example.com', ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ # X_FORWARDED_HOST is ignored. ++ self.assertEqual(request.get_host(), 'example.com') ++ ++ # Check if X_FORWARDED_HOST isn't provided. ++ request = HttpRequest() ++ request.META = { ++ u'HTTP_HOST': u'example.com', ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ self.assertEqual(request.get_host(), 'example.com') ++ ++ # Check if HTTP_HOST isn't provided. ++ request = HttpRequest() ++ request.META = { ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ self.assertEqual(request.get_host(), 'internal.com') ++ ++ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port ++ request = HttpRequest() ++ request.META = { ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 8042, ++ } ++ self.assertEqual(request.get_host(), 'internal.com:8042') ++ ++ finally: ++ settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST ++ ++ def test_http_get_host_with_x_forwarded_host(self): ++ old_USE_X_FORWARDED_HOST = settings.USE_X_FORWARDED_HOST ++ try: ++ settings.USE_X_FORWARDED_HOST = True ++ ++ # Check if X_FORWARDED_HOST is provided. ++ request = HttpRequest() ++ request.META = { ++ u'HTTP_X_FORWARDED_HOST': u'forward.com', ++ u'HTTP_HOST': u'example.com', ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ # X_FORWARDED_HOST is obeyed. ++ self.assertEqual(request.get_host(), 'forward.com') ++ ++ # Check if X_FORWARDED_HOST isn't provided. ++ request = HttpRequest() ++ request.META = { ++ u'HTTP_HOST': u'example.com', ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ self.assertEqual(request.get_host(), 'example.com') ++ ++ # Check if HTTP_HOST isn't provided. ++ request = HttpRequest() ++ request.META = { ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 80, ++ } ++ self.assertEqual(request.get_host(), 'internal.com') ++ ++ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port ++ request = HttpRequest() ++ request.META = { ++ u'SERVER_NAME': u'internal.com', ++ u'SERVER_PORT': 8042, ++ } ++ self.assertEqual(request.get_host(), 'internal.com:8042') ++ ++ finally: ++ settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST --- python-django-1.1.1.orig/debian/patches/CVE-2012-4520-additional-tests.diff +++ python-django-1.1.1/debian/patches/CVE-2012-4520-additional-tests.diff @@ -0,0 +1,94 @@ +Origin: backport, 6383d2358c1077b16b13eb6e6975d7a200ed7285 +Description: Added missed poisoned host header test material + +Index: python-django-1.1.1/tests/regressiontests/requests/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/requests/tests.py 2012-11-19 17:30:02.000000000 -0600 ++++ python-django-1.1.1/tests/regressiontests/requests/tests.py 2012-11-19 17:30:25.000000000 -0600 +@@ -4,6 +4,7 @@ + + from django.conf import settings + from django.http import HttpRequest, HttpResponse, parse_cookie ++from django.core.exceptions import SuspiciousOperation + from django.core.handlers.wsgi import WSGIRequest + from django.core.handlers.modpython import ModPythonRequest + from django.utils.http import cookie_date +@@ -101,6 +102,39 @@ + } + self.assertEqual(request.get_host(), 'internal.com:8042') + ++ # Poisoned host headers are rejected as suspicious ++ legit_hosts = [ ++ 'example.com', ++ 'example.com:80', ++ '12.34.56.78', ++ '12.34.56.78:443', ++ '[2001:19f0:feee::dead:beef:cafe]', ++ '[2001:19f0:feee::dead:beef:cafe]:8080', ++ ] ++ ++ poisoned_hosts = [ ++ 'example.com@evil.tld', ++ 'example.com:dr.frankenstein@evil.tld', ++ 'example.com:someone@somestie.com:80', ++ 'example.com:80/badpath' ++ ] ++ ++ for host in legit_hosts: ++ request = HttpRequest() ++ request.META = { ++ 'HTTP_HOST': host, ++ } ++ request.get_host() ++ ++ for host in poisoned_hosts: ++ def test_host_poisoning(): ++ request = HttpRequest() ++ request.META = { ++ 'HTTP_HOST': host, ++ } ++ request.get_host() ++ self.assertRaises(SuspiciousOperation, test_host_poisoning) ++ + finally: + settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST + +@@ -145,5 +179,38 @@ + } + self.assertEqual(request.get_host(), 'internal.com:8042') + ++ # Poisoned host headers are rejected as suspicious ++ legit_hosts = [ ++ 'example.com', ++ 'example.com:80', ++ '12.34.56.78', ++ '12.34.56.78:443', ++ '[2001:19f0:feee::dead:beef:cafe]', ++ '[2001:19f0:feee::dead:beef:cafe]:8080', ++ ] ++ ++ poisoned_hosts = [ ++ 'example.com@evil.tld', ++ 'example.com:dr.frankenstein@evil.tld', ++ 'example.com:dr.frankenstein@evil.tld:80', ++ 'example.com:80/badpath' ++ ] ++ ++ for host in legit_hosts: ++ request = HttpRequest() ++ request.META = { ++ 'HTTP_HOST': host, ++ } ++ request.get_host() ++ ++ for host in poisoned_hosts: ++ def test_host_poisoning(): ++ request = HttpRequest() ++ request.META = { ++ 'HTTP_HOST': host, ++ } ++ request.get_host() ++ self.assertRaises(SuspiciousOperation, test_host_poisoning) ++ + finally: + settings.USE_X_FORWARDED_HOST = old_USE_X_FORWARDED_HOST --- python-django-1.1.1.orig/debian/patches/security-is_safe_url.patch +++ python-django-1.1.1/debian/patches/security-is_safe_url.patch @@ -0,0 +1,34 @@ +Backport of: + +From ec67af0bd609c412b76eaa4cc89968a2a8e5ad6a Mon Sep 17 00:00:00 2001 +From: Jacob Kaplan-Moss +Date: Tue, 13 Aug 2013 11:00:13 -0500 +Subject: [PATCH] Fixed is_safe_url() to reject URLs that use a scheme other + than HTTP/S. + +This is a security fix; disclosure to follow shortly. +--- + django/contrib/auth/tests/views.py | 8 ++++++-- + django/utils/http.py | 7 ++++--- + 2 files changed, 10 insertions(+), 5 deletions(-) + +Index: python-django-1.1.1/django/utils/http.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/http.py 2013-09-20 09:25:21.996162930 -0400 ++++ python-django-1.1.1/django/utils/http.py 2013-09-20 09:25:21.992162930 -0400 +@@ -126,11 +126,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']) --- python-django-1.1.1.orig/debian/patches/05_ftbfs_in_november.diff +++ python-django-1.1.1/debian/patches/05_ftbfs_in_november.diff @@ -0,0 +1,13 @@ +Forwarded-Upstream: http://code.djangoproject.com/changeset/11697 +Comment: + Fix FTBFS in November. + +--- ./tests/regressiontests/views/tests/generic/date_based.py (revision 10556) ++++ ./tests/regressiontests/views/tests/generic/date_based.py (revision 11697) +@@ -101,5 +101,5 @@ + now = datetime.now() + prev_month = now.date().replace(day=1) +- if prev_month.month == 11: ++ if prev_month.month == 1: + prev_month = prev_month.replace(year=prev_month.year-1, month=12) + else: --- python-django-1.1.1.orig/debian/patches/04_hyphen-manpage.diff +++ python-django-1.1.1/debian/patches/04_hyphen-manpage.diff @@ -0,0 +1,17 @@ +Forwarded-Upstream: not yet +Author: Raphael Hertzog +Comment: + Fix a lintian I: message about improper usage of minus instead + of hyphen. + +--- a/docs/man/django-admin.1 2008-09-04 08:51:48.000000000 +0200 ++++ b/docs/man/django-admin.1 2008-09-04 08:52:47.000000000 +0200 +@@ -153,7 +153,7 @@ + .TP + .I \-e, \-\-extension=EXTENSION + The file extension(s) to examine (default: ".html", separate multiple +-extensions with commas, or use -e multiple times). ++extensions with commas, or use \-e multiple times). + .TP + .I \-a, \-\-all + Process all available locales when using makemessages..SH "ENVIRONMENT" --- python-django-1.1.1.orig/debian/patches/CVE-2013-4315.patch +++ python-django-1.1.1/debian/patches/CVE-2013-4315.patch @@ -0,0 +1,83 @@ +Description: fix directory traversal with ssi template tag +Origin: backport, https://github.com/django/django/commit/87d2750b39f6f2d54b7047225521a44dcd37e896 +Origin: backport, https://github.com/django/django/commit/3203f684e8e51cbfa1b39d7b6a56e340981ad4d5 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=722605 + +Index: python-django-1.1.1/django/template/defaulttags.py +=================================================================== +--- python-django-1.1.1.orig/django/template/defaulttags.py 2013-09-20 10:52:53.104018424 -0400 ++++ python-django-1.1.1/django/template/defaulttags.py 2013-09-20 10:52:53.096018424 -0400 +@@ -1,5 +1,6 @@ + """Default tags used by the template system, available to all templates.""" + ++import os + import sys + import re + from itertools import cycle as itertools_cycle +@@ -277,6 +278,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 +Index: python-django-1.1.1/tests/regressiontests/templates/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/templates/tests.py 2013-09-20 10:52:53.104018424 -0400 ++++ python-django-1.1.1/tests/regressiontests/templates/tests.py 2013-09-20 10:54:24.476015910 -0400 +@@ -1047,5 +1047,36 @@ + 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x"}, template.TemplateSyntaxError), + } + ++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): ++ from django.template import Context ++ # the path must exist for the test to be reliable ++ self.assertTrue(os.path.exists(path)) ++ return template.Template('{%% ssi %s %%}' % path).render(Context()) ++ ++ def test_allowed_paths(self): ++ acceptable_path = os.path.join(self.ssi_dir, "..", "first", "test.html") ++ 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"), ++ ] ++ settings.ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,) ++ for path in disallowed_paths: ++ self.assertEqual(self.render_ssi(path), '') ++ + if __name__ == "__main__": + unittest.main() +Index: python-django-1.1.1/tests/regressiontests/templates/templates/ssi_include.html +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/tests/regressiontests/templates/templates/ssi_include.html 2013-09-20 10:52:53.096018424 -0400 +@@ -0,0 +1 @@ ++This is for testing an ssi include. {{ test }} +Index: python-django-1.1.1/tests/regressiontests/templates/templates/first/test.html +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/tests/regressiontests/templates/templates/first/test.html 2013-09-20 10:52:53.096018424 -0400 +@@ -0,0 +1 @@ ++First template +Index: python-django-1.1.1/tests/regressiontests/templates/templates/second/test.html +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/tests/regressiontests/templates/templates/second/test.html 2013-09-20 10:52:53.096018424 -0400 +@@ -0,0 +1 @@ ++Second template --- python-django-1.1.1.orig/debian/patches/CVE-2013-0306.patch +++ python-django-1.1.1/debian/patches/CVE-2013-0306.patch @@ -0,0 +1,65 @@ +Backport of: + +From d7094bbce8cb838f3b40f504f198c098ff1cf727 Mon Sep 17 00:00:00 2001 +From: Aymeric Augustin +Date: Tue, 12 Feb 2013 11:22:41 +0100 +Subject: [PATCH] [1.3.x] Added a default limit to the maximum number of forms + in a formset. + +This is a security fix. Disclosure and advisory coming shortly. + +Index: python-django-1.1.1/django/forms/formsets.py +=================================================================== +--- python-django-1.1.1.orig/django/forms/formsets.py 2013-03-04 19:07:05.676489831 -0500 ++++ python-django-1.1.1/django/forms/formsets.py 2013-03-04 19:08:29.824491985 -0500 +@@ -14,6 +14,9 @@ + ORDERING_FIELD_NAME = 'ORDER' + DELETION_FIELD_NAME = 'DELETE' + ++# default maximum number of forms in a formset, to prevent memory exhaustion ++DEFAULT_MAX_NUM = 1000 ++ + class ManagementForm(Form): + """ + ``ManagementForm`` is used to keep track of how many form instances +@@ -84,7 +87,7 @@ + def _construct_forms(self): + # instantiate all the forms and put them in self.forms + self.forms = [] +- for i in xrange(self.total_form_count()): ++ for i in xrange(min(self.total_form_count(), self.absolute_max)): + self.forms.append(self._construct_form(i)) + + def _construct_form(self, i, **kwargs): +@@ -304,9 +307,14 @@ + def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, + can_delete=False, max_num=0): + """Return a FormSet for the given form class.""" ++ if max_num == 0: ++ max_num = DEFAULT_MAX_NUM ++ # hard limit on forms instantiated, to prevent memory-exhaustion attacks ++ # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num ++ absolute_max = max(DEFAULT_MAX_NUM, max_num) + attrs = {'form': form, 'extra': extra, + 'can_order': can_order, 'can_delete': can_delete, +- 'max_num': max_num} ++ 'max_num': max_num, 'absolute_max': absolute_max} + return type(form.__name__ + 'FormSet', (formset,), attrs) + + def all_valid(formsets): +Index: python-django-1.1.1/docs/topics/forms/formsets.txt +=================================================================== +--- python-django-1.1.1.orig/docs/topics/forms/formsets.txt 2013-03-04 19:07:05.676489831 -0500 ++++ python-django-1.1.1/docs/topics/forms/formsets.txt 2013-03-04 19:07:05.672489831 -0500 +@@ -80,8 +80,9 @@ + + + +-A ``max_num`` value of ``0`` (the default) puts no limit on the number forms +-displayed. ++A ``max_num`` value of ``0`` (the default) puts a high limit on the number ++of forms displayed (1000). In practice this is equivalent to no limit. ++ + + Formset validation + ------------------ --- python-django-1.1.1.orig/debian/patches/09_security_pasword_reset_dos.diff +++ python-django-1.1.1/debian/patches/09_security_pasword_reset_dos.diff @@ -0,0 +1,56 @@ +Origin: http://code.djangoproject.com/changeset/15036 +Description: Denial-of-service attack in password-reset mechanism + (CVE-2010-4535) + +Index: python-django-1.1.1/django/contrib/auth/tests/tokens.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/tests/tokens.py 2011-01-03 11:30:57.000000000 -0600 ++++ python-django-1.1.1/django/contrib/auth/tests/tokens.py 2011-01-03 11:31:00.000000000 -0600 +@@ -34,4 +34,9 @@ + >>> p2.check_token(u, tk1) + False + ++This will put a 14-digit base36 timestamp into the token, which is too large. ++>>> tk1 = p0._make_token_with_timestamp(u, 175455491841851871349) ++>>> p0.check_token(u, tk1) ++False ++ + """ +Index: python-django-1.1.1/django/contrib/auth/urls.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/urls.py 2011-01-03 11:30:57.000000000 -0600 ++++ python-django-1.1.1/django/contrib/auth/urls.py 2011-01-03 11:31:00.000000000 -0600 +@@ -1,4 +1,4 @@ +-# These URLs are normally mapped to /admin/urls.py. This URLs file is ++# These URLs are normally mapped to /admin/urls.py. This URLs file is + # provided as a convenience to those who want to deploy these URLs elsewhere. + # This file is also used to provide a reliable view deployment for test purposes. + +@@ -11,7 +11,7 @@ + (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'), + (r'^password_reset/$', 'django.contrib.auth.views.password_reset'), + (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'), +- (r'^reset/(?P[0-9A-Za-z]+)-(?P.+)/$', 'django.contrib.auth.views.password_reset_confirm'), ++ (r'^reset/(?P[0-9A-Za-z]{1,13})-(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'), + (r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'), + ) + +Index: python-django-1.1.1/django/utils/http.py +=================================================================== +--- python-django-1.1.1.orig/django/utils/http.py 2011-01-03 11:30:57.000000000 -0600 ++++ python-django-1.1.1/django/utils/http.py 2011-01-03 11:31:00.000000000 -0600 +@@ -73,8 +73,13 @@ + + def base36_to_int(s): + """ +- Convertd a base 36 string to an integer ++ Converts a base 36 string to an ``int``. To prevent ++ overconsumption of server resources, raises ``ValueError` if the ++ input is longer than 13 base36 digits (13 digits is sufficient to ++ base36-encode any 64-bit integer). + """ ++ if len(s) > 13: ++ raise ValueError("Base36 input too large") + return int(s, 36) + + def int_to_base36(i): --- python-django-1.1.1.orig/debian/patches/18_fix_dos_via_get_image_dimensions.diff +++ python-django-1.1.1/debian/patches/18_fix_dos_via_get_image_dimensions.diff @@ -0,0 +1,28 @@ +Description: Fix denial of service via get_image_dimensions() +Origin: upstream, https://github.com/django/django/commit/9ca0ff6268eeff92d0d0ac2c315d4b6a8e229155/download +Bug-Debian: http://bugs.debian.org/683364 + +Index: python-django-1.1.1/django/core/files/images.py +=================================================================== +--- python-django-1.1.1.orig/django/core/files/images.py 2009-05-11 05:57:19.000000000 -0400 ++++ python-django-1.1.1/django/core/files/images.py 2012-09-06 09:55:50.112096246 -0400 +@@ -39,13 +39,18 @@ + file = open(file_or_path, 'rb') + close = True + try: ++ # Most of the time PIL only needs a small chunk to parse the image and ++ # get the dimensions, but with some TIFF files PIL needs to parse the ++ # whole file. ++ chunk_size = 1024 + while 1: +- data = file.read(1024) ++ data = file.read(chunk_size) + if not data: + break + p.feed(data) + if p.image: + return p.image.size ++ chunk_size = chunk_size*2 + return None + finally: + if close: --- python-django-1.1.1.orig/debian/patches/17_fix_dos_in_image_validation.diff +++ python-django-1.1.1/debian/patches/17_fix_dos_in_image_validation.diff @@ -0,0 +1,33 @@ +Description: Fix denial of service in image validation +Origin: upstream, https://github.com/django/django/commit/b2eb4787a0fff9c9993b78be5c698e85108f3446 +Bug-Debian: http://bugs.debian.org/683364 + +Index: python-django-1.1.1/django/forms/fields.py +=================================================================== +--- python-django-1.1.1.orig/django/forms/fields.py 2009-10-09 16:59:05.000000000 -0400 ++++ python-django-1.1.1/django/forms/fields.py 2012-09-06 09:55:40.140095990 -0400 +@@ -505,20 +505,10 @@ + file = StringIO(data['content']) + + try: +- # load() is the only method that can spot a truncated JPEG, +- # but it cannot be called sanely after verify() +- trial_image = Image.open(file) +- trial_image.load() +- +- # Since we're about to use the file again we have to reset the +- # file object if possible. +- if hasattr(file, 'reset'): +- file.reset() +- +- # verify() is the only method that can spot a corrupt PNG, +- # but it must be called immediately after the constructor +- trial_image = Image.open(file) +- trial_image.verify() ++ # load() could spot a truncated JPEG, but it loads the entire ++ # image in memory, which is a DoS vector. See #3848 and #18520. ++ # verify() must be called immediately after the constructor. ++ Image.open(file).verify() + except ImportError: + # Under PyPy, it is possible to import PIL. However, the underlying + # _imaging C module isn't available, so an ImportError will be --- python-django-1.1.1.orig/debian/patches/CVE-2014-0474.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0474.patch @@ -0,0 +1,146 @@ +Description: fix MySQL typecasting issue +Origin: backport of 1.4.x patch provided by upstream +Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309784 + +Index: python-django-1.1.1/django/db/models/fields/__init__.py +=================================================================== +--- python-django-1.1.1.orig/django/db/models/fields/__init__.py 2014-04-19 12:54:40.050395091 -0400 ++++ python-django-1.1.1/django/db/models/fields/__init__.py 2014-04-19 12:54:40.046395091 -0400 +@@ -427,6 +427,9 @@ + ugettext_lazy("This field cannot be null.")) + return smart_unicode(value) + ++ def get_db_prep_value(self, value): ++ return self.to_python(value) ++ + def formfield(self, **kwargs): + defaults = {'max_length': self.max_length} + defaults.update(kwargs) +@@ -652,6 +655,12 @@ + kwargs['max_length'] = kwargs.get('max_length', 100) + Field.__init__(self, verbose_name, name, **kwargs) + ++ def get_db_prep_value(self, value): ++ value = super(FilePathField, self).get_db_prep_value(value) ++ if value is None: ++ return None ++ return smart_unicode(value) ++ + def formfield(self, **kwargs): + defaults = { + 'path': self.path, +@@ -720,6 +729,12 @@ + kwargs['max_length'] = 15 + Field.__init__(self, *args, **kwargs) + ++ def get_db_prep_value(self, value): ++ value = super(IPAddressField, self).get_db_prep_value(value) ++ if value is None: ++ return None ++ return smart_unicode(value) ++ + def get_internal_type(self): + return "IPAddressField" + +@@ -810,6 +825,11 @@ + def get_internal_type(self): + return "TextField" + ++ def get_db_prep_value(self, value): ++ if isinstance(value, basestring) or value is None: ++ return value ++ return smart_unicode(value) ++ + def formfield(self, **kwargs): + defaults = {'widget': forms.Textarea} + defaults.update(kwargs) +Index: python-django-1.1.1/tests/regressiontests/model_fields/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/model_fields/tests.py 2014-04-19 12:54:40.050395091 -0400 ++++ python-django-1.1.1/tests/regressiontests/model_fields/tests.py 2014-04-19 12:55:06.978395528 -0400 +@@ -5,7 +5,14 @@ + from django import forms + from django.db import models + from django.core.exceptions import ValidationError +- ++from django.db.models.fields import ( ++ AutoField, BooleanField, CharField, ++ CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField, ++ EmailField, FilePathField, FloatField, IntegerField, IPAddressField, ++ NullBooleanField, PositiveIntegerField, ++ PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, ++ TimeField, URLField) ++from django.db.models.fields.files import FileField, ImageField, FieldFile + from models import Foo, Bar, Whiz, BigD, BigS, Image + + try: +@@ -144,3 +151,69 @@ + bs = BigS.objects.create(s = 'slug'*50) + bs = BigS.objects.get(pk=bs.pk) + self.assertEqual(bs.s, 'slug'*50) ++ ++class PrepValueTest(django.test.TestCase): ++ def test_AutoField(self): ++ self.assertEqual(isinstance(AutoField(primary_key=True).get_db_prep_value(1), int), True) ++ ++ def test_BooleanField(self): ++ self.assertEqual(isinstance(BooleanField().get_db_prep_value(True), bool), True) ++ ++ def test_CharField(self): ++ self.assertEqual(isinstance(CharField().get_db_prep_value(''), str), True) ++ self.assertEqual(isinstance(CharField().get_db_prep_value(0), unicode), True) ++ ++ def test_CommaSeparatedIntegerField(self): ++ self.assertEqual(isinstance(CommaSeparatedIntegerField().get_db_prep_value('1,2'), str), True) ++ self.assertEqual(isinstance(CommaSeparatedIntegerField().get_db_prep_value(0), unicode), True) ++ ++ def test_DecimalField(self): ++ self.assertEqual(isinstance(DecimalField().get_db_prep_value(Decimal('1.2')), Decimal), True) ++ ++ def test_EmailField(self): ++ self.assertEqual(isinstance(EmailField().get_db_prep_value('mailbox@domain.com'), str), True) ++ ++ def test_FileField(self): ++ self.assertEqual(isinstance(FileField().get_db_prep_value('filename.ext'), unicode), True) ++ self.assertEqual(isinstance(FileField().get_db_prep_value(0), unicode), True) ++ ++ def test_FilePathField(self): ++ self.assertEqual(isinstance(FilePathField().get_db_prep_value('tests.py'), unicode), True) ++ self.assertEqual(isinstance(FilePathField().get_db_prep_value(0), unicode), True) ++ ++ def test_FloatField(self): ++ self.assertEqual(isinstance(FloatField().get_db_prep_value(1.2), float), True) ++ ++ def test_ImageField(self): ++ self.assertEqual(isinstance(ImageField().get_db_prep_value('filename.ext'), unicode), True) ++ ++ def test_IntegerField(self): ++ self.assertEqual(isinstance(IntegerField().get_db_prep_value(1), int), True) ++ ++ def test_IPAddressField(self): ++ self.assertEqual(isinstance(IPAddressField().get_db_prep_value('127.0.0.1'), unicode), True) ++ self.assertEqual(isinstance(IPAddressField().get_db_prep_value(0), unicode), True) ++ ++ def test_NullBooleanField(self): ++ self.assertEqual(isinstance(NullBooleanField().get_db_prep_value(True), bool), True) ++ ++ def test_PositiveIntegerField(self): ++ self.assertEqual(isinstance(PositiveIntegerField().get_db_prep_value(1), int), True) ++ ++ def test_PositiveSmallIntegerField(self): ++ self.assertEqual(isinstance(PositiveSmallIntegerField().get_db_prep_value(1), int), True) ++ ++ def test_SlugField(self): ++ self.assertEqual(isinstance(SlugField().get_db_prep_value('slug'), str), True) ++ self.assertEqual(isinstance(SlugField().get_db_prep_value(0), unicode), True) ++ ++ def test_SmallIntegerField(self): ++ self.assertEqual(isinstance(SmallIntegerField().get_db_prep_value(1), int), True) ++ ++ def test_TextField(self): ++ self.assertEqual(isinstance(TextField().get_db_prep_value('Abc'), str), True) ++ self.assertEqual(isinstance(TextField().get_db_prep_value(0), unicode), True) ++ ++ def test_URLField(self): ++ self.assertEqual(isinstance(URLField().get_db_prep_value('http://domain.com'), str), True) ++ --- python-django-1.1.1.orig/debian/patches/lp1080204.diff +++ python-django-1.1.1/debian/patches/lp1080204.diff @@ -0,0 +1,35 @@ +Origin: backport, b774c5993cf80000966ae8f04c985116f98ee5ac +Description: Isolated poisoned_http_host tests from 500 handlers +Bug: https://code.djangoproject.com/ticket/19172 +Bug-Ubuntu: https://launchpad.net/bugs/1080204 + +Index: python-django-1.1.1/django/contrib/auth/tests/views.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/auth/tests/views.py 2012-11-19 16:34:14.000000000 -0600 ++++ python-django-1.1.1/django/contrib/auth/tests/views.py 2012-11-19 16:34:14.000000000 -0600 +@@ -89,7 +89,11 @@ + HTTP_HOST='www.example:dr.frankenstein@evil.tld' + ) + self.assertRaises(SuspiciousOperation, test_host_poisoning) +- self.assertEqual(len(mail.outbox), 0) ++ # Skip any 500 handler action (like sending more mail...) ++ # if ADMINS or MANAGERS is defined ++ if settings.ADMINS and len(settings.ADMINS) == 0 and \ ++ settings.MANAGERS and len(settings.MANAGERS) == 0: ++ self.assertEqual(len(mail.outbox), 0) + + def test_poisoned_http_host_admin_site(self): + "Poisoned HTTP_HOST headers can't be used for reset emails on admin views" +@@ -99,7 +103,11 @@ + HTTP_HOST='www.example:dr.frankenstein@evil.tld' + ) + self.assertRaises(SuspiciousOperation, test_host_poisoning) +- self.assertEqual(len(mail.outbox), 0) ++ # Skip any 500 handler action (like sending more mail...) ++ # if ADMINS or MANAGERS is defined ++ if settings.ADMINS and len(settings.ADMINS) == 0 and \ ++ settings.MANAGERS and len(settings.MANAGERS) == 0: ++ self.assertEqual(len(mail.outbox), 0) + + def _test_confirm_start(self): + # Start by creating the email --- python-django-1.1.1.orig/debian/patches/CVE-2015-0221.patch +++ python-django-1.1.1/debian/patches/CVE-2015-0221.patch @@ -0,0 +1,69 @@ +Description: fix denial-of-service attack against django.views.static.serve +Origin: backported from patch provided by Tim Graham + +Index: python-django-1.1.1/django/views/static.py +=================================================================== +--- python-django-1.1.1.orig/django/views/static.py 2015-01-13 08:26:14.150897717 -0500 ++++ python-django-1.1.1/django/views/static.py 2015-01-13 08:26:14.146897688 -0500 +@@ -16,6 +16,9 @@ + from django.template import Template, Context, TemplateDoesNotExist + from django.utils.http import http_date + ++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. +@@ -60,10 +63,10 @@ + statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): + return HttpResponseNotModified() + mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream' +- contents = open(fullpath, 'rb').read() +- response = HttpResponse(contents, mimetype=mimetype) ++ f = open(fullpath, 'rb') ++ response = HttpResponse(iter(lambda: f.read(STREAM_CHUNK_SIZE), ''), mimetype=mimetype) + response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) +- response["Content-Length"] = len(contents) ++ response["Content-Length"] = statobj[stat.ST_SIZE] + return response + + DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ +Index: python-django-1.1.1/tests/regressiontests/views/media/long-line.txt +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/tests/regressiontests/views/media/long-line.txt 2015-01-13 08:26:14.146897688 -0500 +@@ -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 +Index: python-django-1.1.1/tests/regressiontests/views/tests/static.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/views/tests/static.py 2015-01-13 08:26:14.150897717 -0500 ++++ python-django-1.1.1/tests/regressiontests/views/tests/static.py 2015-01-13 08:29:17.608209055 -0500 +@@ -2,6 +2,7 @@ + + from django.test import TestCase + from regressiontests.views.urls import media_dir ++from django.views.static import STREAM_CHUNK_SIZE + + class StaticTests(TestCase): + """Tests django views in django/views/static.py""" +@@ -12,8 +13,17 @@ + for filename in media_files: + response = self.client.get('/views/site_media/%s' % filename) + file = open(path.join(media_dir, filename)) +- self.assertEquals(file.read(), response.content) +- self.assertEquals(len(response.content), int(response['Content-Length'])) ++ content = response.content ++ self.assertEquals(file.read(), content) ++ self.assertEquals(len(content), int(response['Content-Length'])) ++ ++ def test_chunked(self): ++ "The static view should stream files in chunks to avoid large memory usage" ++ response = self.client.get('/views/site_media/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/site_media/file.unknown') --- python-django-1.1.1.orig/debian/patches/drop_fix_ie_for_vary_1_4.diff +++ python-django-1.1.1/debian/patches/drop_fix_ie_for_vary_1_4.diff @@ -0,0 +1,81 @@ +Description: Drop IE6/IE7 compatability functions that introduced cache + coherency errors. Fixes CVE-2014-1418. + +Origin: Original patch from upstream +--- + django/core/handlers/base.py | 2 - + django/http/utils.py | 51 ------------------------------------------- + 2 files changed, 53 deletions(-) + +Index: b/django/core/handlers/base.py +=================================================================== +--- a/django/core/handlers/base.py ++++ b/django/core/handlers/base.py +@@ -10,8 +10,6 @@ + response_fixes = [ + http.fix_location_header, + http.conditional_content_removal, +- http.fix_IE_for_attach, +- http.fix_IE_for_vary, + ] + + def __init__(self): +Index: b/django/http/utils.py +=================================================================== +--- a/django/http/utils.py ++++ b/django/http/utils.py +@@ -31,54 +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. +- """ +- if 'MSIE' not in request.META.get('HTTP_USER_AGENT', '').upper(): +- 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. +- """ +- if 'MSIE' not in request.META.get('HTTP_USER_AGENT', '').upper(): +- 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. +- if response['Content-Type'].split(';')[0] not in safe_mime_types: +- try: +- del response['Vary'] +- except KeyError: +- pass +- +- return response +- --- python-django-1.1.1.orig/debian/patches/CVE-2014-0481.patch +++ python-django-1.1.1/debian/patches/CVE-2014-0481.patch @@ -0,0 +1,249 @@ +Backport of: + +From 30042d475bf084c6723c6217a21598d9247a9c41 Mon Sep 17 00:00:00 2001 +From: Tim Graham +Date: Fri, 8 Aug 2014 10:20:08 -0400 +Subject: [PATCH] [1.4.x] Fixed #23157 -- Removed O(n) algorithm when uploading + duplicate file names. + +This is a security fix. Disclosure following shortly. +--- + django/core/files/storage.py | 11 +++++------ + docs/howto/custom-file-storage.txt | 12 ++++++++++-- + docs/ref/files/storage.txt | 16 +++++++++++++--- + docs/releases/1.4.14.txt | 20 ++++++++++++++++++++ + tests/modeltests/files/tests.py | 21 +++++++++++++-------- + tests/regressiontests/file_storage/tests.py | 23 ++++++++++++++--------- + 6 files changed, 75 insertions(+), 28 deletions(-) + +Index: python-django-1.1.1/django/core/files/storage.py +=================================================================== +--- python-django-1.1.1.orig/django/core/files/storage.py 2014-09-10 14:17:48.090869173 -0400 ++++ python-django-1.1.1/django/core/files/storage.py 2014-09-10 14:17:48.086869173 -0400 +@@ -6,6 +6,7 @@ + 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, smart_str + from django.utils.functional import LazyObject + from django.utils.importlib import import_module +@@ -65,13 +66,13 @@ + """ + dir_name, file_name = os.path.split(name) + file_root, file_ext = os.path.splitext(file_name) +- # If the filename already exists, keep adding an underscore (before the +- # file extension, if one exists) to the filename until the generated +- # filename doesn't exist. ++ # 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_root += '_' + # file_ext includes the dot. +- name = os.path.join(dir_name, file_root + file_ext) ++ name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext)) ++ + return name + + def path(self, name): +Index: python-django-1.1.1/django/utils/crypto.py +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ python-django-1.1.1/django/utils/crypto.py 2014-09-10 14:17:48.086869173 -0400 +@@ -0,0 +1,45 @@ ++""" ++Django's standard crypto functions and utilities. ++""" ++import hashlib ++import time ++ ++# Use the system PRNG if possible ++import random ++try: ++ random = random.SystemRandom() ++ using_sysrandom = True ++except NotImplementedError: ++ import warnings ++ warnings.warn('A secure pseudo-random number generator is not available ' ++ 'on your system. Falling back to Mersenne Twister.') ++ using_sysrandom = False ++ ++from django.conf import settings ++ ++ ++def get_random_string(length=12, ++ allowed_chars='abcdefghijklmnopqrstuvwxyz' ++ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): ++ """ ++ Returns a securely generated random string. ++ ++ The default length of 12 with the a-z, A-Z, 0-9 character set returns ++ a 71-bit value. log_2((26+26+10)^12) =~ 71 bits ++ """ ++ if not using_sysrandom: ++ # This is ugly, and a hack, but it makes things better than ++ # the alternative of predictability. This re-seeds the PRNG ++ # using a value that is hard for an attacker to predict, every ++ # time a random string is required. This may change the ++ # properties of the chosen random sequence slightly, but this ++ # is better than absolute predictability. ++ random.seed( ++ hashlib.sha256( ++ ("%s%s%s" % ( ++ random.getstate(), ++ time.time(), ++ settings.SECRET_KEY)).encode('utf-8') ++ ).digest()) ++ return ''.join([random.choice(allowed_chars) for i in range(length)]) ++ +Index: python-django-1.1.1/docs/howto/custom-file-storage.txt +=================================================================== +--- python-django-1.1.1.orig/docs/howto/custom-file-storage.txt 2014-09-10 14:17:48.090869173 -0400 ++++ python-django-1.1.1/docs/howto/custom-file-storage.txt 2014-09-10 14:17:48.086869173 -0400 +@@ -88,5 +88,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 underscores 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. +Index: python-django-1.1.1/tests/regressiontests/file_storage/tests.py +=================================================================== +--- python-django-1.1.1.orig/tests/regressiontests/file_storage/tests.py 2014-09-10 14:17:48.090869173 -0400 ++++ python-django-1.1.1/tests/regressiontests/file_storage/tests.py 2014-09-10 14:17:48.086869173 -0400 +@@ -1,5 +1,6 @@ + # -*- coding: utf-8 -*- + import os ++import re + import shutil + import sys + import tempfile +@@ -25,6 +26,8 @@ + except ImportError: + Image = None + ++FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' ++ + class FileStorageTests(unittest.TestCase): + storage_class = FileSystemStorage + +@@ -114,6 +117,15 @@ + def tearDown(self): + shutil.rmtree(self.storage_dir) + ++ def assertRegexpMatches(self, text, expected_regexp, msg=None): ++ """Fail the test unless the text matches the regular expression.""" ++ if isinstance(expected_regexp, basestring): ++ expected_regexp = re.compile(expected_regexp) ++ if not expected_regexp.search(text): ++ msg = msg or "Regexp didn't match" ++ msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) ++ raise self.failureException(msg) ++ + def save_file(self, name): + name = self.storage.save(name, SlowFile("Data")) + +@@ -121,10 +133,9 @@ + self.thread.start() + name = self.save_file('conflict') + self.thread.join() +- self.assert_(self.storage.exists('conflict')) +- self.assert_(self.storage.exists('conflict_')) +- self.storage.delete('conflict') +- self.storage.delete('conflict_') ++ files = sorted(os.listdir(self.storage_dir)) ++ self.assertEqual(files[0], 'conflict') ++ self.assertRegexpMatches(files[1], 'conflict_%s' % FILE_SUFFIX_REGEX) + + class FileStoragePermissions(TestCase): + def setUp(self): +@@ -151,6 +162,15 @@ + def tearDown(self): + shutil.rmtree(self.storage_dir) + ++ def assertRegexpMatches(self, text, expected_regexp, msg=None): ++ """Fail the test unless the text matches the regular expression.""" ++ if isinstance(expected_regexp, basestring): ++ expected_regexp = re.compile(expected_regexp) ++ if not expected_regexp.search(text): ++ msg = msg or "Regexp didn't match" ++ msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) ++ raise self.failureException(msg) ++ + def test_directory_with_dot(self): + """Regression test for #9610. + +@@ -161,9 +181,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.failIf(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) +- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) +- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_'))) ++ self.assertEqual(files[0], 'test') ++ self.assertRegexpMatches(files[1], 'test_%s' % FILE_SUFFIX_REGEX) + + def test_first_character_dot(self): + """ +@@ -176,10 +197,12 @@ + self.assert_(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.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_.test'))) ++ self.assertRegexpMatches(files[1], '_%s.test' % FILE_SUFFIX_REGEX) + else: +- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_'))) ++ self.assertRegexpMatches(files[1], '.test_%s' % FILE_SUFFIX_REGEX) + + if Image is not None: + class DimensionClosingBug(TestCase): +Index: python-django-1.1.1/tests/modeltests/files/models.py +=================================================================== +--- python-django-1.1.1.orig/tests/modeltests/files/models.py 2014-09-10 14:17:48.090869173 -0400 ++++ python-django-1.1.1/tests/modeltests/files/models.py 2014-09-10 14:18:57.074869562 -0400 +@@ -92,8 +92,8 @@ + + >>> obj2 = Storage() + >>> obj2.normal.save('django_test.txt', ContentFile('more content')) +->>> obj2.normal +- ++>>> str(obj2.normal)[:-11] ++'tests/django_test_' + >>> obj2.normal.size + 12 + +@@ -101,16 +101,16 @@ + + >>> cache.set('obj1', obj1) + >>> cache.set('obj2', obj2) +->>> cache.get('obj2').normal +- ++>>> str(cache.get('obj2').normal)[:-11] ++'tests/django_test_' + + # Deleting an object deletes the file it uses, if there are no other objects + # still using that file. + + >>> obj2.delete() + >>> obj2.normal.save('django_test.txt', ContentFile('more content')) +->>> obj2.normal +- ++>>> str(obj2.normal)[:-11] ++'tests/django_test_' + + # Default values allow an object to access a single file. + --- python-django-1.1.1.orig/debian/patches/10_CVE-2011-0696.diff +++ python-django-1.1.1/debian/patches/10_CVE-2011-0696.diff @@ -0,0 +1,118 @@ +Origin: http://code.djangoproject.com/changeset/15466 +Description: Don't exempt AJAX from CSRF validation +Bug-Ubuntu: https://launchpad.net/bugs/719031 + +Index: python-django-1.1.1/django/contrib/csrf/middleware.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/csrf/middleware.py 2011-02-15 16:43:55.000000000 -0600 ++++ python-django-1.1.1/django/contrib/csrf/middleware.py 2011-02-15 16:44:00.000000000 -0600 +@@ -37,9 +37,6 @@ + if getattr(callback, 'csrf_exempt', False): + return None + +- if request.is_ajax(): +- return None +- + try: + session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] + except KeyError: +@@ -48,9 +45,12 @@ + + csrf_token = _make_token(session_id) + # check incoming token +- try: +- request_csrf_token = request.POST['csrfmiddlewaretoken'] +- except KeyError: ++ request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') ++ if request_csrf_token == "": ++ # Fall back to X-CSRFToken, to make things easier for AJAX ++ request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') ++ ++ if request_csrf_token == "": + return HttpResponseForbidden(_ERROR_MSG) + + if request_csrf_token != csrf_token: +Index: python-django-1.1.1/django/contrib/csrf/tests.py +=================================================================== +--- python-django-1.1.1.orig/django/contrib/csrf/tests.py 2011-02-15 16:43:55.000000000 -0600 ++++ python-django-1.1.1/django/contrib/csrf/tests.py 2011-02-15 16:44:00.000000000 -0600 +@@ -134,11 +134,11 @@ + req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {}) + self.assertEquals(None, req2) + +- def test_ajax_exemption(self): ++ def test_csrf_token_in_header(self): + """ +- Check that AJAX requests are automatically exempted. ++ Check that we can pass in the token in a header instead of in the form + """ + req = self._get_POST_session_request() +- req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' ++ req.META['HTTP_X_CSRFTOKEN'] = _make_token(self._session_id) + req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) + self.assertEquals(None, req2) +Index: python-django-1.1.1/docs/ref/contrib/csrf.txt +=================================================================== +--- python-django-1.1.1.orig/docs/ref/contrib/csrf.txt 2011-02-15 16:43:55.000000000 -0600 ++++ python-django-1.1.1/docs/ref/contrib/csrf.txt 2011-02-15 16:44:00.000000000 -0600 +@@ -39,6 +39,34 @@ + (previous versions of Django did not provide these two components + of ``CsrfMiddleware`` as described above) + ++AJAX ++---- ++ ++While the above method can be used with AJAX POST requests, it has some ++inconveniences: you have to remember to get the CSRF token from the HTML ++document and pass it in as POST data with every POST request. For this reason, ++there is an alternative method: on each XMLHttpRequest, set a custom ++`X-CSRFToken` header to the value of the CSRF token. This is often easier, ++because many javascript frameworks provide hooks that allow headers to be set on ++every request. In jQuery, you can use the ``beforeSend`` hook as follows: ++ ++.. code-block:: javascript ++ ++ $.ajaxSetup({ ++ beforeSend: function(xhr, settings) { ++ if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { ++ // Only send the token to relative URLs i.e. locally. ++ xhr.setRequestHeader("X-CSRFToken", ++ $("#csrfmiddlewaretoken").val()); ++ } ++ } ++ }); ++ ++Adding this to a javascript file that is included on your site will ensure that ++AJAX POST requests that are made via jQuery will not be caught by the CSRF ++protection. This will only work if you remember to include a form on the page, ++so that the input with id 'csrfmiddlewaretoken' will be found. ++ + Exceptions + ---------- + +@@ -61,10 +89,6 @@ + response post-processing (``CsrfResponseMiddleware``) respectively. + They can be used individually if required. + +-You don't have to worry about doing this for most AJAX views. Any +-request sent with "X-Requested-With: XMLHttpRequest" is automatically +-exempt. (See the next section.) +- + How it works + ============ + +@@ -98,14 +122,6 @@ + pages that are served as 'text/html' or 'application/xml+xhtml' + are modified. + +-The middleware tries to be smart about requests that come in via AJAX. Many +-JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP header; +-these requests are detected and automatically *not* handled by this middleware. +-We can do this safely because, in the context of a browser, the header can only +-be added by using ``XMLHttpRequest``, and browsers already implement a +-same-domain policy for ``XMLHttpRequest``. (Note that this is not secure if you +-don't trust content within the same domain or subdomains.) +- + + .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html +