From a3f4402853b7beb7a89cc88a1500818f94f8d313 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 23 Jan 2010 23:25:41 +0000 Subject: [PATCH] [1.1.X] Fixed #12470 - encoding of comma and semi-colon in cookies. This is a backport of r12282 from trunk. The original bug was about CookieStorage, which does not exist in 1.1.X, but the fix involved the underlying cookie storage. The change fixes other bugs with cookies for Internet Explorer and Safari, hence it is backported. It could, however, also cause backwards incompatibilities with existing javascript that may parse cookie values that contain commas or semi-colons, and, very rarely, with existing cookie values manipulated server side. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@12283 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/__init__.py | 32 +++++++++++++++++-- tests/regressiontests/httpwrappers/tests.py | 35 ++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index 7b0c469c51..6353637a95 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -248,12 +248,40 @@ class QueryDict(MultiValueDict): output.extend([urlencode({k: smart_str(v, self.encoding)}) 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, BaseCookie): try: - c = SimpleCookie() + c = CompatCookie() c.load(cookie) except CookieError: # Invalid cookie @@ -288,7 +316,7 @@ class HttpResponse(object): else: self._container = [content] self._is_string = True - self.cookies = SimpleCookie() + self.cookies = CompatCookie() if status: self.status_code = status diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 04099be16e..09da4476b4 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -465,7 +465,40 @@ BadHeaderError: Header values can't contain newlines (got 'test\\nstr') [u'1', u'2', u'3', u'4'] """ -from django.http import QueryDict, HttpResponse +from django.http import QueryDict, HttpResponse, CompatCookie +from django.test import TestCase + + +class Cookies(TestCase): + + def test_encode(self): + """ + Test that we don't output tricky characters in encoded value + """ + c = CompatCookie() + c['test'] = "An,awkward;value" + self.assert_(";" not in c.output()) # IE compat + self.assert_("," not in c.output()) # Safari compat + + def test_decode(self): + """ + Test that we can still preserve semi-colons and commas + """ + c = CompatCookie() + c['test'] = "An,awkward;value" + c2 = CompatCookie() + c2.load(c.output()) + self.assertEqual(c['test'].value, c2['test'].value) + + def test_decode_2(self): + """ + Test that we haven't broken normal encoding + """ + c = CompatCookie() + c['test'] = "\xf0" + c2 = CompatCookie() + c2.load(c.output()) + self.assertEqual(c['test'].value, c2['test'].value) if __name__ == "__main__": import doctest