bpo-38976: Add support for HTTP Only flag in MozillaCookieJar (#17471)
Add support for HTTP Only flag in MozillaCookieJar Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
parent
a3c4ceffe6
commit
16ee68da6e
|
@ -50,10 +50,18 @@ def _debug(*args):
|
||||||
logger = logging.getLogger("http.cookiejar")
|
logger = logging.getLogger("http.cookiejar")
|
||||||
return logger.debug(*args)
|
return logger.debug(*args)
|
||||||
|
|
||||||
|
HTTPONLY_ATTR = "HTTPOnly"
|
||||||
|
HTTPONLY_PREFIX = "#HttpOnly_"
|
||||||
DEFAULT_HTTP_PORT = str(http.client.HTTP_PORT)
|
DEFAULT_HTTP_PORT = str(http.client.HTTP_PORT)
|
||||||
|
NETSCAPE_MAGIC_RGX = re.compile("#( Netscape)? HTTP Cookie File")
|
||||||
MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
|
MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar "
|
||||||
"instance initialised with one)")
|
"instance initialised with one)")
|
||||||
|
NETSCAPE_HEADER_TEXT = """\
|
||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# http://curl.haxx.se/rfc/cookie_spec.html
|
||||||
|
# This is a generated file! Do not edit.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def _warn_unhandled_exception():
|
def _warn_unhandled_exception():
|
||||||
# There are a few catch-all except: statements in this module, for
|
# There are a few catch-all except: statements in this module, for
|
||||||
|
@ -2004,19 +2012,11 @@ class MozillaCookieJar(FileCookieJar):
|
||||||
header by default (Mozilla can cope with that).
|
header by default (Mozilla can cope with that).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
magic_re = re.compile("#( Netscape)? HTTP Cookie File")
|
|
||||||
header = """\
|
|
||||||
# Netscape HTTP Cookie File
|
|
||||||
# http://curl.haxx.se/rfc/cookie_spec.html
|
|
||||||
# This is a generated file! Do not edit.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _really_load(self, f, filename, ignore_discard, ignore_expires):
|
def _really_load(self, f, filename, ignore_discard, ignore_expires):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
magic = f.readline()
|
if not NETSCAPE_MAGIC_RGX.match(f.readline()):
|
||||||
if not self.magic_re.search(magic):
|
|
||||||
raise LoadError(
|
raise LoadError(
|
||||||
"%r does not look like a Netscape format cookies file" %
|
"%r does not look like a Netscape format cookies file" %
|
||||||
filename)
|
filename)
|
||||||
|
@ -2024,8 +2024,17 @@ class MozillaCookieJar(FileCookieJar):
|
||||||
try:
|
try:
|
||||||
while 1:
|
while 1:
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
|
rest = {}
|
||||||
|
|
||||||
if line == "": break
|
if line == "": break
|
||||||
|
|
||||||
|
# httponly is a cookie flag as defined in rfc6265
|
||||||
|
# when encoded in a netscape cookie file,
|
||||||
|
# the line is prepended with "#HttpOnly_"
|
||||||
|
if line.startswith(HTTPONLY_PREFIX):
|
||||||
|
rest[HTTPONLY_ATTR] = ""
|
||||||
|
line = line[len(HTTPONLY_PREFIX):]
|
||||||
|
|
||||||
# last field may be absent, so keep any trailing tab
|
# last field may be absent, so keep any trailing tab
|
||||||
if line.endswith("\n"): line = line[:-1]
|
if line.endswith("\n"): line = line[:-1]
|
||||||
|
|
||||||
|
@ -2063,7 +2072,7 @@ class MozillaCookieJar(FileCookieJar):
|
||||||
discard,
|
discard,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
{})
|
rest)
|
||||||
if not ignore_discard and c.discard:
|
if not ignore_discard and c.discard:
|
||||||
continue
|
continue
|
||||||
if not ignore_expires and c.is_expired(now):
|
if not ignore_expires and c.is_expired(now):
|
||||||
|
@ -2083,16 +2092,17 @@ class MozillaCookieJar(FileCookieJar):
|
||||||
else: raise ValueError(MISSING_FILENAME_TEXT)
|
else: raise ValueError(MISSING_FILENAME_TEXT)
|
||||||
|
|
||||||
with open(filename, "w") as f:
|
with open(filename, "w") as f:
|
||||||
f.write(self.header)
|
f.write(NETSCAPE_HEADER_TEXT)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
for cookie in self:
|
for cookie in self:
|
||||||
|
domain = cookie.domain
|
||||||
if not ignore_discard and cookie.discard:
|
if not ignore_discard and cookie.discard:
|
||||||
continue
|
continue
|
||||||
if not ignore_expires and cookie.is_expired(now):
|
if not ignore_expires and cookie.is_expired(now):
|
||||||
continue
|
continue
|
||||||
if cookie.secure: secure = "TRUE"
|
if cookie.secure: secure = "TRUE"
|
||||||
else: secure = "FALSE"
|
else: secure = "FALSE"
|
||||||
if cookie.domain.startswith("."): initial_dot = "TRUE"
|
if domain.startswith("."): initial_dot = "TRUE"
|
||||||
else: initial_dot = "FALSE"
|
else: initial_dot = "FALSE"
|
||||||
if cookie.expires is not None:
|
if cookie.expires is not None:
|
||||||
expires = str(cookie.expires)
|
expires = str(cookie.expires)
|
||||||
|
@ -2107,7 +2117,9 @@ class MozillaCookieJar(FileCookieJar):
|
||||||
else:
|
else:
|
||||||
name = cookie.name
|
name = cookie.name
|
||||||
value = cookie.value
|
value = cookie.value
|
||||||
|
if cookie.has_nonstandard_attr(HTTPONLY_ATTR):
|
||||||
|
domain = HTTPONLY_PREFIX + domain
|
||||||
f.write(
|
f.write(
|
||||||
"\t".join([cookie.domain, initial_dot, cookie.path,
|
"\t".join([domain, initial_dot, cookie.path,
|
||||||
secure, expires, name, value])+
|
secure, expires, name, value])+
|
||||||
"\n")
|
"\n")
|
||||||
|
|
|
@ -1773,6 +1773,10 @@ class LWPCookieTests(unittest.TestCase):
|
||||||
interact_netscape(c, "http://www.foo.com/",
|
interact_netscape(c, "http://www.foo.com/",
|
||||||
"fooc=bar; Domain=www.foo.com; %s" % expires)
|
"fooc=bar; Domain=www.foo.com; %s" % expires)
|
||||||
|
|
||||||
|
for cookie in c:
|
||||||
|
if cookie.name == "foo1":
|
||||||
|
cookie.set_nonstandard_attr("HTTPOnly", "")
|
||||||
|
|
||||||
def save_and_restore(cj, ignore_discard):
|
def save_and_restore(cj, ignore_discard):
|
||||||
try:
|
try:
|
||||||
cj.save(ignore_discard=ignore_discard)
|
cj.save(ignore_discard=ignore_discard)
|
||||||
|
@ -1787,6 +1791,7 @@ class LWPCookieTests(unittest.TestCase):
|
||||||
new_c = save_and_restore(c, True)
|
new_c = save_and_restore(c, True)
|
||||||
self.assertEqual(len(new_c), 6) # none discarded
|
self.assertEqual(len(new_c), 6) # none discarded
|
||||||
self.assertIn("name='foo1', value='bar'", repr(new_c))
|
self.assertIn("name='foo1', value='bar'", repr(new_c))
|
||||||
|
self.assertIn("rest={'HTTPOnly': ''}", repr(new_c))
|
||||||
|
|
||||||
new_c = save_and_restore(c, False)
|
new_c = save_and_restore(c, False)
|
||||||
self.assertEqual(len(new_c), 4) # 2 of them discarded on save
|
self.assertEqual(len(new_c), 4) # 2 of them discarded on save
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
The :mod:`http.cookiejar` module now supports the parsing of cookies in CURL-style cookiejar files through MozillaCookieJar
|
||||||
|
on all platforms. Previously, such cookie entries would be silently ignored when loading a cookiejar with such entries.
|
||||||
|
|
||||||
|
Additionally, the HTTP Only attribute is persisted in the object, and will be correctly written to file if the MozillaCookieJar object is subsequently dumped.
|
Loading…
Reference in New Issue