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.
This commit is contained in:
Tim Graham 2014-10-31 14:26:27 -04:00
parent 4e9a6c94e6
commit 42b5e4feea
4 changed files with 34 additions and 15 deletions

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils import six from django.utils import six
@ -15,12 +16,29 @@ try:
except http_cookies.CookieError: except http_cookies.CookieError:
_cookie_allows_colon_in_names = False _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 SimpleCookie = http_cookies.SimpleCookie
else: else:
Morsel = http_cookies.Morsel Morsel = http_cookies.Morsel
class SimpleCookie(http_cookies.SimpleCookie): 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: if not _cookie_encodes_correctly:
def value_encode(self, val): def value_encode(self, val):
# Some browsers do not support quoted-string from RFC 2109, # Some browsers do not support quoted-string from RFC 2109,

View File

@ -206,17 +206,6 @@ class HttpResponseBase(six.Iterator):
def __getitem__(self, header): def __getitem__(self, header):
return self._headers[header.lower()][1] 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): def has_header(self, header):
"""Case-insensitive check for a header.""" """Case-insensitive check for a header."""
return header.lower() in self._headers return header.lower() in self._headers

View File

@ -39,7 +39,7 @@ class SimpleTemplateResponse(HttpResponse):
rendered, and that the pickled state only includes rendered rendered, and that the pickled state only includes rendered
data, not the data used to construct the response. 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: if not self._is_rendered:
raise ContentNotRenderedError('The response content must be ' raise ContentNotRenderedError('The response content must be '
'rendered before it can be pickled.') 'rendered before it can be pickled.')

View File

@ -631,7 +631,7 @@ class CookieTests(unittest.TestCase):
c = SimpleCookie() c = SimpleCookie()
c['test'] = "An,awkward;value" c['test'] = "An,awkward;value"
c2 = SimpleCookie() c2 = SimpleCookie()
c2.load(c.output()) c2.load(c.output()[12:])
self.assertEqual(c['test'].value, c2['test'].value) self.assertEqual(c['test'].value, c2['test'].value)
def test_decode_2(self): def test_decode_2(self):
@ -641,7 +641,7 @@ class CookieTests(unittest.TestCase):
c = SimpleCookie() c = SimpleCookie()
c['test'] = b"\xf0" c['test'] = b"\xf0"
c2 = SimpleCookie() c2 = SimpleCookie()
c2.load(c.output()) c2.load(c.output()[12:])
self.assertEqual(c['test'].value, c2['test'].value) self.assertEqual(c['test'].value, c2['test'].value)
def test_nonstandard_keys(self): def test_nonstandard_keys(self):
@ -678,3 +678,15 @@ class CookieTests(unittest.TestCase):
r = HttpResponse() r = HttpResponse()
r.set_cookie("a:.b/", 1) r.set_cookie("a:.b/", 1)
self.assertEqual(len(r.cookies.bad_cookies), 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)