diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py index fda02b7..9ec65ea 100644 --- a/Lib/http/cookies.py +++ b/Lib/http/cookies.py @@ -134,9 +134,9 @@ import string __all__ = ["CookieError", "BaseCookie", "SimpleCookie"] -_nulljoin = ''.join -_semispacejoin = '; '.join -_spacejoin = ' '.join +_nulljoin = b''.join +_semispacejoin = b'; '.join +_spacejoin = b' '.join def _warn_deprecated_setter(setter): import warnings @@ -164,17 +164,19 @@ class CookieError(Exception): # _LegalChars is the list of chars which don't require "'s # _Translator hash-table for fast quoting # -_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:" -_UnescapedChars = _LegalChars + ' ()/<=>?@[]{}' +_LegalChars = string.ascii_letters.encode('ascii') \ + + string.digits.encode('ascii') \ + + b"!#$%&'*+-.^_`|~:" +_UnescapedChars = _LegalChars + b' ()/<=>?@[]{}' -_Translator = {n: '\\%03o' % n - for n in set(range(256)) - set(map(ord, _UnescapedChars))} +_Translator = {n: b'\\%03o' % n + for n in set(range(256)) - set(_UnescapedChars)} _Translator.update({ - ord('"'): '\\"', - ord('\\'): '\\\\', + ord(b'"'): b'\\"', + ord(b'\\'): b'\\\\', }) -_is_legal_key = re.compile('[%s]+' % _LegalChars).fullmatch +_is_legal_key = re.compile(br'[%s]+' % _LegalChars).fullmatch def _quote(str): r"""Quote a string for use in a cookie header. @@ -189,8 +191,8 @@ def _quote(str): return '"' + str.translate(_Translator) + '"' -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") -_QuotePatt = re.compile(r"[\\].") +_OctalPatt = re.compile(br"\\[0-3][0-7][0-7]") +_QuotePatt = re.compile(br"[\\].") def _unquote(str): # If there aren't any doublequotes, @@ -279,17 +281,17 @@ class Morsel(dict): # variant on the left to the appropriate traditional # formatting on the right. _reserved = { - "expires" : "expires", - "path" : "Path", - "comment" : "Comment", - "domain" : "Domain", - "max-age" : "Max-Age", - "secure" : "Secure", - "httponly" : "HttpOnly", - "version" : "Version", + b"expires" : b"expires", + b"path" : b"Path", + b"comment" : b"Comment", + b"domain" : b"Domain", + b"max-age" : b"Max-Age", + b"secure" : b"Secure", + b"httponly" : b"HttpOnly", + b"version" : b"Version", } - _flags = {'secure', 'httponly'} + _flags = {b'secure', b'httponly'} def __init__(self): # Set defaults @@ -297,7 +299,7 @@ class Morsel(dict): # Set default attributes for key in self._reserved: - dict.__setitem__(self, key, "") + dict.__setitem__(self, key, b"") @property def key(self): @@ -396,8 +398,8 @@ class Morsel(dict): self._value = state['value'] self._coded_value = state['coded_value'] - def output(self, attrs=None, header="Set-Cookie:"): - return "%s %s" % (header, self.OutputString(attrs)) + def output(self, attrs=None, header=b"Set-Cookie:"): + return b"%s %s" % (header, self.OutputString(attrs)) __str__ = output @@ -421,26 +423,26 @@ class Morsel(dict): append = result.append # First, the key=value pair - append("%s=%s" % (self.key, self.coded_value)) + append(b"%s=%s" % (self.key, self.coded_value)) # Now add any defined attributes if attrs is None: attrs = self._reserved items = sorted(self.items()) for key, value in items: - if value == "": + if value == b"": continue if key not in attrs: continue - if key == "expires" and isinstance(value, int): - append("%s=%s" % (self._reserved[key], _getdate(value))) - elif key == "max-age" and isinstance(value, int): - append("%s=%d" % (self._reserved[key], value)) + if key == b"expires" and isinstance(value, int): + append(b"%s=%s" % (self._reserved[key], _getdate(value))) + elif key == b"max-age" and isinstance(value, int): + append(b"%s=%d" % (self._reserved[key], value)) elif key in self._flags: if value: append(str(self._reserved[key])) else: - append("%s=%s" % (self._reserved[key], value)) + append(b"%s=%s" % (self._reserved[key], value)) # Return the result return _semispacejoin(result) @@ -455,13 +457,13 @@ class Morsel(dict): # result, the parsing rules here are less strict. # -_LegalKeyChars = r"\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=" -_LegalValueChars = _LegalKeyChars + '\[\]' -_CookiePattern = re.compile(r""" +_LegalKeyChars = br"\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=" +_LegalValueChars = _LegalKeyChars + br'\[\]' +_CookiePattern = re.compile(br""" (?x) # This is a verbose pattern \s* # Optional whitespace at start of cookie (?P # Start of group 'key' - [""" + _LegalKeyChars + r"""]+? # Any word of at least one letter + [""" + _LegalKeyChars + br"""]+? # Any word of at least one letter ) # End of group 'key' ( # Optional group: there may not be a value. \s*=\s* # Equal Sign @@ -470,7 +472,7 @@ _CookiePattern = re.compile(r""" | # or \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr | # or - [""" + _LegalValueChars + r"""]* # Any word or empty string + [""" + _LegalValueChars + br"""]* # Any word or empty string ) # End of group 'val' )? # End of optional value group \s* # Any number of spaces. @@ -521,7 +523,7 @@ class BaseCookie(dict): rval, cval = self.value_encode(value) self.__set(key, rval, cval) - def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"): + def output(self, attrs=None, header=b"Set-Cookie:", sep=b"\015\012"): """Return a string suitable for HTTP.""" result = [] items = sorted(self.items()) @@ -552,17 +554,19 @@ class BaseCookie(dict): is equivalent to calling: map(Cookie.__setitem__, d.keys(), d.values()) """ - if isinstance(rawdata, str): + if isinstance(rawdata, bytes): self.__parse_string(rawdata) - else: + elif hasattr(rawdata, 'items'): # self.update() wouldn't call our custom __setitem__ for key, value in rawdata.items(): self[key] = value + else: + raise TypeError("Please provide a byte string or a dict of byte strings.") return - def __parse_string(self, str, patt=_CookiePattern): + def __parse_string(self, bytestring, patt=_CookiePattern): i = 0 # Our starting point - n = len(str) # Length of string + n = len(bytestring) # Length of the bytestring parsed_items = [] # Parsed (type, key, value) triples morsel_seen = False # A key=value pair was previously encountered @@ -574,7 +578,7 @@ class BaseCookie(dict): # attacks). while 0 <= i < n: # Start looking for a cookie - match = patt.match(str, i) + match = patt.match(bytestring, i) if not match: # No more cookies break diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py index f74ab98..17635d4 100644 --- a/Lib/test/test_http_cookies.py +++ b/Lib/test/test_http_cookies.py @@ -95,7 +95,7 @@ class CookieTests(unittest.TestCase): def test_load_accepts_a_binary_string(self): C = cookies.SimpleCookie() C.load(b'foo=bar') - self.assertEqual(C.output(), 'foo=bar') + self.assertEqual(C.output(), b'Set-Cookie: foo=bar') def test_load_rejects_a_text_string(self): C = cookies.SimpleCookie()