diff --git a/django/http/cookie.py b/django/http/cookie.py index 7084c87766..3bd9065d3a 100644 --- a/django/http/cookie.py +++ b/django/http/cookie.py @@ -1,4 +1,5 @@ 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, diff --git a/django/http/response.py b/django/http/response.py index 9e8280a307..3edf10d1e8 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -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 diff --git a/django/template/response.py b/django/template/response.py index f43a4e0f53..53d2795868 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -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.') diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index 4e705e2aeb..cc324b76cd 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -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)