--- python-django-1.1.1.orig/debian/changelog +++ python-django-1.1.1/debian/changelog @@ -0,0 +1,623 @@ +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/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/pyversions +++ python-django-1.1.1/debian/pyversions @@ -0,0 +1 @@ +2.3- --- 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.examples +++ python-django-1.1.1/debian/python-django.examples @@ -0,0 +1 @@ +debian/contrib/* --- 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/compat +++ python-django-1.1.1/debian/compat @@ -0,0 +1 @@ +7 --- 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|(? 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/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/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/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/pycompat +++ python-django-1.1.1/debian/pycompat @@ -0,0 +1 @@ +2 --- 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/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/contrib/default +++ python-django-1.1.1/debian/contrib/default @@ -0,0 +1,16 @@ +# django project names/directories +DJANGO_SITES="myapp myapp2 myapp3" + +# path to the directory with your django projects +#SITES_PATH=/home/django/projects + +# path to the directory for socket and pid files +RUNFILES_PATH=$SITES_PATH/run + +# please make sure this is NOT root +# local user prefered, www-data accepted +RUN_AS=django + +# maximum requests before fast-cgi process respawns +# (a.k.a. get killed and let live) +MAXREQUESTS=100 --- python-django-1.1.1.orig/debian/contrib/initscript +++ python-django-1.1.1/debian/contrib/initscript @@ -0,0 +1,131 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: FastCGI servers for Django +# Required-Start: networking +# Required-Stop: networking +# Default-Start: 2 3 4 5 +# Default-Stop: S 0 1 6 +# Short-Description: Start FastCGI servers with Django. +# Description: Django, in order to operate with FastCGI, must be started +# in a very specific way with manage.py. This must be done +# for each Django web server that has to run. +### END INIT INFO +# +# Author: Guillermo Fernandez Castellanos +# . +# +# 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-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/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-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/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-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-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/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-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/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/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/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/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/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/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/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-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/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/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/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 + --- 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/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/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/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/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/series +++ python-django-1.1.1/debian/patches/series @@ -0,0 +1,29 @@ +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 --- 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/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/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)