Commit 93a135d1 authored by Collin Anderson's avatar Collin Anderson Committed by Tim Graham
Browse files

Fixed #26158 -- Rewrote http.parse_cookie() to better match browsers.

parent e7e5d9b3
Loading
Loading
Loading
Loading
+16 −13
Original line number Diff line number Diff line
@@ -57,18 +57,21 @@ else:


def parse_cookie(cookie):
    if cookie == '':
        return {}
    if not isinstance(cookie, http_cookies.BaseCookie):
        try:
            c = SimpleCookie()
            c.load(cookie)
        except http_cookies.CookieError:
            # Invalid cookie
            return {}
    else:
        c = cookie
    """
    Return a dictionary parsed from a `Cookie:` header string.
    """
    cookiedict = {}
    for key in c.keys():
        cookiedict[key] = c.get(key).value
    if six.PY2:
        cookie = force_str(cookie)
    for chunk in cookie.split(str(';')):
        if str('=') in chunk:
            key, val = chunk.split(str('='), 1)
        else:
            # Assume an empty name per
            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
            key, val = str(''), chunk
        key, val = key.strip(), val.strip()
        if key or val:
            # unquote using Python's algorithm.
            cookiedict[key] = http_cookies._unquote(val)
    return cookiedict
+4 −0
Original line number Diff line number Diff line
@@ -354,6 +354,10 @@ Requests and Responses
  :attr:`~django.http.HttpRequest.content_params` attributes which are
  parsed from the ``CONTENT_TYPE`` header.

* The parser for ``request.COOKIES`` is simplified to better match the behavior
  of browsers. ``request.COOKIES`` may now contain cookies that are invalid
  according to :rfc:`6265` but are possible to set via ``document.cookie``.

Serialization
~~~~~~~~~~~~~

+50 −0
Original line number Diff line number Diff line
@@ -676,6 +676,8 @@ class CookieTests(unittest.TestCase):
        c2 = SimpleCookie()
        c2.load(c.output()[12:])
        self.assertEqual(c['test'].value, c2['test'].value)
        c3 = parse_cookie(c.output()[12:])
        self.assertEqual(c['test'].value, c3['test'])

    def test_decode_2(self):
        """
@@ -686,6 +688,8 @@ class CookieTests(unittest.TestCase):
        c2 = SimpleCookie()
        c2.load(c.output()[12:])
        self.assertEqual(c['test'].value, c2['test'].value)
        c3 = parse_cookie(c.output()[12:])
        self.assertEqual(c['test'].value, c3['test'])

    def test_nonstandard_keys(self):
        """
@@ -699,6 +703,52 @@ class CookieTests(unittest.TestCase):
        """
        self.assertIn('good_cookie', parse_cookie('a:=b; a:=c; good_cookie=yes').keys())

    def test_python_cookies(self):
        """
        Test cases copied from Python's Lib/test/test_http_cookies.py
        """
        self.assertEqual(parse_cookie('chips=ahoy; vienna=finger'), {'chips': 'ahoy', 'vienna': 'finger'})
        # Here parse_cookie() differs from Python's cookie parsing in that it
        # treats all semicolons as delimiters, even within quotes.
        self.assertEqual(
            parse_cookie('keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'),
            {'keebler': '"E=mc2', 'L': '\\"Loves\\"', 'fudge': '\\012', '': '"'}
        )
        # Illegal cookies that have an '=' char in an unquoted value.
        self.assertEqual(parse_cookie('keebler=E=mc2'), {'keebler': 'E=mc2'})
        # Cookies with ':' character in their name.
        self.assertEqual(parse_cookie('key:term=value:term'), {'key:term': 'value:term'})
        # Cookies with '[' and ']'.
        self.assertEqual(parse_cookie('a=b; c=[; d=r; f=h'), {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'})

    def test_cookie_edgecases(self):
        # Cookies that RFC6265 allows.
        self.assertEqual(parse_cookie('a=b; Domain=example.com'), {'a': 'b', 'Domain': 'example.com'})
        # parse_cookie() has historically kept only the last cookie with the
        # same name.
        self.assertEqual(parse_cookie('a=b; h=i; a=c'), {'a': 'c', 'h': 'i'})

    def test_invalid_cookies(self):
        """
        Cookie strings that go against RFC6265 but browsers will send if set
        via document.cookie.
        """
        # Chunks without an equals sign appear as unnamed values per
        # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
        self.assertIn('django_language', parse_cookie('abc=def; unnamed; django_language=en').keys())
        # Even a double quote may be an unamed value.
        self.assertEqual(parse_cookie('a=b; "; c=d'), {'a': 'b', '': '"', 'c': 'd'})
        # Spaces in names and values, and an equals sign in values.
        self.assertEqual(parse_cookie('a b c=d e = f; gh=i'), {'a b c': 'd e = f', 'gh': 'i'})
        # More characters the spec forbids.
        self.assertEqual(parse_cookie('a   b,c<>@:/[]?{}=d  "  =e,f g'), {'a   b,c<>@:/[]?{}': 'd  "  =e,f g'})
        # Unicode characters. The spec only allows ASCII.
        self.assertEqual(parse_cookie('saint=André Bessette'), {'saint': force_str('André Bessette')})
        # Browsers don't send extra whitespace or semicolons in Cookie headers,
        # but parse_cookie() should parse whitespace the same way
        # document.cookie parses whitespace.
        self.assertEqual(parse_cookie('  =  b  ;  ;  =  ;   c  =  ;  '), {'': 'b', 'c': ''})

    def test_httponly_after_load(self):
        """
        Test that we can use httponly attribute on cookies that we load
+0 −4
Original line number Diff line number Diff line
@@ -10,7 +10,6 @@ from django.core.exceptions import SuspiciousOperation
from django.core.handlers.wsgi import LimitedStream, WSGIRequest
from django.http import (
    HttpRequest, HttpResponse, RawPostDataException, UnreadablePostError,
    parse_cookie,
)
from django.test import RequestFactory, SimpleTestCase, override_settings
from django.test.client import FakePayload
@@ -183,9 +182,6 @@ class RequestsTests(SimpleTestCase):
        request = WSGIRequest({'PATH_INFO': wsgi_str("/سلام/"), 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')})
        self.assertEqual(request.path, "/سلام/")

    def test_parse_cookie(self):
        self.assertEqual(parse_cookie('invalid@key=true'), {})

    def test_httprequest_location(self):
        request = HttpRequest()
        self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"),