From 42c31f6bf036efd93c0311bc1f524b1553c20489 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 24 Jan 2011 20:35:46 +0000 Subject: [PATCH] Rationalised CompatCookie/SimpleCookie into single SimpleCookie class with all fixes. Since upstream Python has fixed the encoding bug (see http://bugs.python.org/issue9824), we don't want a separate class for this bug fix, or several layers for the different fixes. git-svn-id: http://code.djangoproject.com/svn/django/trunk@15298 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/messages/storage/cookie.py | 6 +- django/http/__init__.py | 118 +++++++++++--------- docs/internals/deprecation.txt | 3 + docs/releases/1.3.txt | 8 ++ tests/regressiontests/httpwrappers/tests.py | 12 +- 5 files changed, 84 insertions(+), 63 deletions(-) diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index ffbfce157e..b4ab98d725 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -1,7 +1,7 @@ from django.conf import settings from django.contrib.messages import constants from django.contrib.messages.storage.base import BaseStorage, Message -from django.http import CompatCookie +from django.http import SimpleCookie from django.utils import simplejson as json from django.utils.crypto import salted_hmac, constant_time_compare @@ -88,9 +88,9 @@ class CookieStorage(BaseStorage): unstored_messages = [] encoded_data = self._encode(messages) if self.max_cookie_size: - # data is going to be stored eventually by CompatCookie, which + # data is going to be stored eventually by SimpleCookie, which # adds it's own overhead, which we must account for. - cookie = CompatCookie() # create outside the loop + cookie = SimpleCookie() # create outside the loop def stored_length(val): return len(cookie.value_encode(val)[1]) diff --git a/django/http/__init__.py b/django/http/__init__.py index 90c55c6319..64b03deb3d 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -21,38 +21,76 @@ except ImportError: # PendingDeprecationWarning from cgi import parse_qsl +import Cookie # httponly support exists in Python 2.6's Cookie library, # but not in Python 2.4 or 2.5. -import Cookie -if Cookie.Morsel._reserved.has_key('httponly'): +_morsel_supports_httponly = Cookie.Morsel._reserved.has_key('httponly') +# Some versions of Python 2.7 and later won't need this encoding bug fix: +_cookie_encodes_correctly = Cookie.SimpleCookie().value_encode(';') == (';', '"\\073"') + +if _morsel_supports_httponly and _cookie_encodes_correctly: SimpleCookie = Cookie.SimpleCookie else: - class Morsel(Cookie.Morsel): - def __setitem__(self, K, V): - K = K.lower() - if K == "httponly": - if V: - # The superclass rejects httponly as a key, - # so we jump to the grandparent. - super(Cookie.Morsel, self).__setitem__(K, V) - else: - super(Morsel, self).__setitem__(K, V) + if not _morsel_supports_httponly: + class Morsel(Cookie.Morsel): + def __setitem__(self, K, V): + K = K.lower() + if K == "httponly": + if V: + # The superclass rejects httponly as a key, + # so we jump to the grandparent. + super(Cookie.Morsel, self).__setitem__(K, V) + else: + super(Morsel, self).__setitem__(K, V) - def OutputString(self, attrs=None): - output = super(Morsel, self).OutputString(attrs) - if "httponly" in self: - output += "; httponly" - return output + def OutputString(self, attrs=None): + output = super(Morsel, self).OutputString(attrs) + if "httponly" in self: + output += "; httponly" + return output class SimpleCookie(Cookie.SimpleCookie): - def __set(self, key, real_value, coded_value): - M = self.get(key, Morsel()) - M.set(key, real_value, coded_value) - dict.__setitem__(self, key, M) + if not _morsel_supports_httponly: + def __set(self, key, real_value, coded_value): + M = self.get(key, Morsel()) + M.set(key, real_value, coded_value) + dict.__setitem__(self, key, M) - def __setitem__(self, key, value): - rval, cval = self.value_encode(value) - self.__set(key, rval, cval) + def __setitem__(self, key, value): + rval, cval = self.value_encode(value) + self.__set(key, rval, cval) + + if not _cookie_encodes_correctly: + def value_encode(self, val): + # Some browsers do not support quoted-string from RFC 2109, + # including some versions of Safari and Internet Explorer. + # These browsers split on ';', and some versions of Safari + # are known to split on ', '. Therefore, we encode ';' and ',' + + # SimpleCookie already does the hard work of encoding and decoding. + # It uses octal sequences like '\\012' for newline etc. + # and non-ASCII chars. We just make use of this mechanism, to + # avoid introducing two encoding schemes which would be confusing + # and especially awkward for javascript. + + # NB, contrary to Python docs, value_encode returns a tuple containing + # (real val, encoded_val) + val, encoded = super(SimpleCookie, self).value_encode(val) + + encoded = encoded.replace(";", "\\073").replace(",","\\054") + # If encoded now contains any quoted chars, we need double quotes + # around the whole string. + if "\\" in encoded and not encoded.startswith('"'): + encoded = '"' + encoded + '"' + + return val, encoded + +class CompatCookie(SimpleCookie): + def __init__(self, *args, **kwargs): + super(CompatCookie, self).__init__(*args, **kwargs) + import warnings + warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.", + PendingDeprecationWarning) from django.utils.datastructures import MultiValueDict, ImmutableList from django.utils.encoding import smart_str, iri_to_uri, force_unicode @@ -389,40 +427,12 @@ class QueryDict(MultiValueDict): for v in list_]) return '&'.join(output) -class CompatCookie(SimpleCookie): - """ - Cookie class that handles some issues with browser compatibility. - """ - def value_encode(self, val): - # Some browsers do not support quoted-string from RFC 2109, - # including some versions of Safari and Internet Explorer. - # These browsers split on ';', and some versions of Safari - # are known to split on ', '. Therefore, we encode ';' and ',' - - # SimpleCookie already does the hard work of encoding and decoding. - # It uses octal sequences like '\\012' for newline etc. - # and non-ASCII chars. We just make use of this mechanism, to - # avoid introducing two encoding schemes which would be confusing - # and especially awkward for javascript. - - # NB, contrary to Python docs, value_encode returns a tuple containing - # (real val, encoded_val) - val, encoded = super(CompatCookie, self).value_encode(val) - - encoded = encoded.replace(";", "\\073").replace(",","\\054") - # If encoded now contains any quoted chars, we need double quotes - # around the whole string. - if "\\" in encoded and not encoded.startswith('"'): - encoded = '"' + encoded + '"' - - return val, encoded - def parse_cookie(cookie): if cookie == '': return {} if not isinstance(cookie, Cookie.BaseCookie): try: - c = CompatCookie() + c = SimpleCookie() c.load(cookie) except Cookie.CookieError: # Invalid cookie @@ -460,7 +470,7 @@ class HttpResponse(object): else: self._container = [content] self._is_string = True - self.cookies = CompatCookie() + self.cookies = SimpleCookie() if status: self.status_code = status diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 4613b7e3ee..9bb0289c68 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -155,6 +155,9 @@ their deprecation, as per the :ref:`Django deprecation policy a :class:`~django.contrib.gis.geos.GEOSException` when called on a geometry with no SRID value. + * :class:`~django.http.CompatCookie` will be removed in favour of + :class:`~django.http.SimpleCookie`. + * 2.0 * ``django.views.defaults.shortcut()``. This function has been moved to ``django.contrib.contenttypes.views.shortcut()`` as part of the diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 1904e9526d..89e8b58585 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -609,3 +609,11 @@ Previously this field's ``clean()`` method accepted a second, gender, argument which allowed stronger validation checks to be made, however since this argument could never actually be passed from the Django form machinery it is now pending deprecation. + +``CompatCookie`` +~~~~~~~~~~~~~~~~ + +Previously, ``django.http`` exposed an undocumented ``CompatCookie`` class, +which was a bug-fix wrapper around the standard library ``SimpleCookie``. As the +fixes are moving upstream, this is now deprecated - you should use ``from +django.http import SimpleCookie`` instead. diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index b983bca8ab..69b052f5cd 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -1,7 +1,7 @@ import copy import pickle -from django.http import QueryDict, HttpResponse, CompatCookie, BadHeaderError +from django.http import QueryDict, HttpResponse, SimpleCookie, BadHeaderError from django.utils import unittest class QueryDictTests(unittest.TestCase): @@ -250,7 +250,7 @@ class CookieTests(unittest.TestCase): # Python 2.4 compatibility note: Python 2.4's cookie implementation # always returns Set-Cookie headers terminating in semi-colons. # That's not the bug this test is looking for, so ignore it. - c = CompatCookie() + c = SimpleCookie() c['test'] = "An,awkward;value" self.assert_(";" not in c.output().rstrip(';')) # IE compat self.assert_("," not in c.output().rstrip(';')) # Safari compat @@ -259,9 +259,9 @@ class CookieTests(unittest.TestCase): """ Test that we can still preserve semi-colons and commas """ - c = CompatCookie() + c = SimpleCookie() c['test'] = "An,awkward;value" - c2 = CompatCookie() + c2 = SimpleCookie() c2.load(c.output()) self.assertEqual(c['test'].value, c2['test'].value) @@ -269,8 +269,8 @@ class CookieTests(unittest.TestCase): """ Test that we haven't broken normal encoding """ - c = CompatCookie() + c = SimpleCookie() c['test'] = "\xf0" - c2 = CompatCookie() + c2 = SimpleCookie() c2.load(c.output()) self.assertEqual(c['test'].value, c2['test'].value)