Commit 42b5e4fe authored by Tim Graham's avatar Tim Graham
Browse files

Fixed #23730 -- Moved support for SimpleCookie HIGHEST_PROTOCOL pickling to http.cookie.

This fix is necessary for Python 3.5 compatibility (refs #23763).

Thanks Berker Peksag for review.
parent 4e9a6c94
Loading
Loading
Loading
Loading
+19 −1
Original line number Diff line number Diff line
from __future__ import unicode_literals
import sys

from django.utils.encoding import force_str
from django.utils import six
@@ -15,12 +16,29 @@ try:
except http_cookies.CookieError:
    _cookie_allows_colon_in_names = False

if _cookie_encodes_correctly and _cookie_allows_colon_in_names:
# Cookie pickling bug is fixed in Python 2.7.9 and Python 3.4.3+
# http://bugs.python.org/issue22775
cookie_pickles_properly = (
    (sys.version_info[:2] == (2, 7) and sys.version_info >= (2, 7, 9)) or
    sys.version_info >= (3, 4, 3)
)

if _cookie_encodes_correctly and _cookie_allows_colon_in_names and cookie_pickles_properly:
    SimpleCookie = http_cookies.SimpleCookie
else:
    Morsel = http_cookies.Morsel

    class SimpleCookie(http_cookies.SimpleCookie):
        if not cookie_pickles_properly:
            def __setitem__(self, key, value):
                # Apply the fix from http://bugs.python.org/issue22775 where
                # it's not fixed in Python itself
                if isinstance(value, Morsel):
                    # allow assignment of constructed Morsels (e.g. for pickling)
                    dict.__setitem__(self, key, value)
                else:
                    super(SimpleCookie, self).__setitem__(key, value)

        if not _cookie_encodes_correctly:
            def value_encode(self, val):
                # Some browsers do not support quoted-string from RFC 2109,
+0 −11
Original line number Diff line number Diff line
@@ -206,17 +206,6 @@ class HttpResponseBase(six.Iterator):
    def __getitem__(self, header):
        return self._headers[header.lower()][1]

    def __getstate__(self):
        # SimpleCookie is not pickleable with pickle.HIGHEST_PROTOCOL, so we
        # serialize to a string instead
        state = self.__dict__.copy()
        state['cookies'] = str(state['cookies'])
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.cookies = SimpleCookie(self.cookies)

    def has_header(self, header):
        """Case-insensitive check for a header."""
        return header.lower() in self._headers
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ class SimpleTemplateResponse(HttpResponse):
        rendered, and that the pickled state only includes rendered
        data, not the data used to construct the response.
        """
        obj_dict = super(SimpleTemplateResponse, self).__getstate__()
        obj_dict = self.__dict__.copy()
        if not self._is_rendered:
            raise ContentNotRenderedError('The response content must be '
                                          'rendered before it can be pickled.')
+14 −2
Original line number Diff line number Diff line
@@ -631,7 +631,7 @@ class CookieTests(unittest.TestCase):
        c = SimpleCookie()
        c['test'] = "An,awkward;value"
        c2 = SimpleCookie()
        c2.load(c.output())
        c2.load(c.output()[12:])
        self.assertEqual(c['test'].value, c2['test'].value)

    def test_decode_2(self):
@@ -641,7 +641,7 @@ class CookieTests(unittest.TestCase):
        c = SimpleCookie()
        c['test'] = b"\xf0"
        c2 = SimpleCookie()
        c2.load(c.output())
        c2.load(c.output()[12:])
        self.assertEqual(c['test'].value, c2['test'].value)

    def test_nonstandard_keys(self):
@@ -678,3 +678,15 @@ class CookieTests(unittest.TestCase):
        r = HttpResponse()
        r.set_cookie("a:.b/", 1)
        self.assertEqual(len(r.cookies.bad_cookies), 1)

    def test_pickle(self):
        rawdata = 'Customer="WILE_E_COYOTE"; Path=/acme; Version=1'
        expected_output = 'Set-Cookie: %s' % rawdata

        C = SimpleCookie()
        C.load(rawdata)
        self.assertEqual(C.output(), expected_output)

        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            C1 = pickle.loads(pickle.dumps(C, protocol=proto))
            self.assertEqual(C1.output(), expected_output)