diff -r ddff866d820d Lib/http/cookies.py --- a/Lib/http/cookies.py Thu Jul 18 02:31:21 2013 +0200 +++ b/Lib/http/cookies.py Thu Jul 18 12:58:46 2013 -0700 @@ -337,6 +337,7 @@ "httponly" : "httponly", "version" : "Version", } + _flags = ["httponly", "secure"] def __init__(self): # Set defaults @@ -344,7 +345,10 @@ # Set default attributes for key in self._reserved: - dict.__setitem__(self, key, "") + if key in self._flags: + dict.__setitem__(self, key, False) + else: + dict.__setitem__(self, key, "") def __setitem__(self, K, V): K = K.lower() @@ -409,10 +413,9 @@ append("%s=%s" % (self._reserved[key], _getdate(value))) elif key == "max-age" and isinstance(value, int): append("%s=%d" % (self._reserved[key], value)) - elif key == "secure": - append(str(self._reserved[key])) - elif key == "httponly": - append(str(self._reserved[key])) + elif key in self._flags: + if value: + append(str(self._reserved[key])) else: append("%s=%s" % (self._reserved[key], value)) @@ -429,23 +432,34 @@ # result, the parsing rules here are less strict. # -_LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" -_CookiePattern = re.compile(r""" - (?x) # This is a verbose pattern - (?P # Start of group 'key' - """ + _LegalCharsPatt + r"""+? # Any word of at least one letter - ) # End of group 'key' - \s*=\s* # Equal Sign - (?P # Start of group 'val' - "(?:[^\\"]|\\.)*" # Any doublequoted string - | # or - \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr - | # or - """ + _LegalCharsPatt + r"""* # Any word or empty string - ) # End of group 'val' - \s*;? # Probably ending in a semi-colon +_LegalCharsPattern = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" +_AttributeSeparatorPattern = re.compile(r'''((?:[^;"']|"[^"]*"|'[^']*')+)''') +_KeyValuePattern = re.compile(r""" + (?x) # This is a verbose pattern + \s* + (?P # Start of group 'key' + """ + _LegalCharsPattern + r"""+? # Any word of at least one letter + ) # End of group 'key' + \s* + =\s* # Equal Sign + (?P # Start of group 'val' + "(?:[^\\"]|\\.)*" # Any doublequoted string + | # or + \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr + | # or + """ + _LegalCharsPattern + r"""* # Any word or empty string, but no equal sign + ) # End of group 'val' + \s*$ """, re.ASCII) # May be removed if safe. +_KeyOnlyPattern = re.compile(r""" + (?x) # This is a verbose pattern + \s* + (?P # Start of group 'key' + """ + _LegalCharsPattern + r"""+? # Any word of at least one letter + ) # End of group 'key' + \s*$ + """, re.ASCII) # May be removed if safe. # At long last, here is the cookie class. Using this class is almost just like # using a dictionary. See this module's docstring for example usage. @@ -525,20 +539,23 @@ self[key] = value return - def __parse_string(self, str, patt=_CookiePattern): - i = 0 # Our starting point - n = len(str) # Length of string + def __parse_string(self, str): + pattern = _AttributeSeparatorPattern + parts = pattern.split(str)[1::2] M = None # current morsel - while 0 <= i < n: + for part in parts: # Start looking for a cookie - match = patt.search(str, i) - if not match: - # No more cookies - break - - key, value = match.group("key"), match.group("val") - i = match.end(0) + match = _KeyValuePattern.search(part) + if match: + key, value = match.group("key"), match.group("val") + else: + match = _KeyOnlyPattern.search(part) + if match: + key, value = match.group("key"), None + else: + # No more cookies + break # Parse the key, value in case it's metainfo if key[0] == "$": @@ -549,7 +566,10 @@ M[key[1:]] = value elif key.lower() in Morsel._reserved: if M: - M[key] = _unquote(value) + if key in Morsel._flags and value is None: + M[key] = True + else: + M[key] = _unquote(value) else: rval, cval = self.value_decode(value) self.__set(key, rval, cval) diff -r ddff866d820d Lib/test/test_http_cookies.py --- a/Lib/test/test_http_cookies.py Thu Jul 18 02:31:21 2013 +0200 +++ b/Lib/test/test_http_cookies.py Thu Jul 18 12:58:46 2013 -0700 @@ -20,14 +20,14 @@ def test_basic(self): cases = [ {'data': 'chips=ahoy; vienna=finger', - 'dict': {'chips':'ahoy', 'vienna':'finger'}, - 'repr': "", - 'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'}, + 'dict': {'chips':'ahoy', 'vienna':'finger'}, + 'repr': "", + 'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'}, {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"', - 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'}, - 'repr': '''''', - 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'}, + 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'}, + 'repr': '''''', + 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'}, # Check illegal cookies that have an '=' char in an unquoted value {'data': 'keebler=E=mc2', @@ -78,6 +78,22 @@ """) + def test_flag_deserialization(self): + """ + Ensure that the 'httponly' and 'secure' flags are correctly + deserialized. + Refs #16611. + """ + C = cookies.SimpleCookie() + C.load('eggs=scrambled; httponly; secure; Path=/bacon') + self.assertEqual(C['eggs']['httponly'], True) + self.assertEqual(C['eggs']['secure'], True) + + C = cookies.SimpleCookie() + C.load('eggs=scrambled; Path=/bacon') + self.assertEqual(C['eggs']['httponly'], False) + self.assertEqual(C['eggs']['secure'], False) + def test_extended_encode(self): # Issue 9824: some browsers don't follow the standard; we now # encode , and ; to keep them from tripping up.