Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Doc/library/http.cookiejar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -716,9 +716,10 @@ Cookies may have additional non-standard cookie-attributes. These may be
accessed using the following methods:


.. method:: Cookie.has_nonstandard_attr(name)
.. method:: Cookie.has_nonstandard_attr(name, case_insensitive=False)

Return ``True`` if cookie has the named cookie-attribute.
Return ``True`` if cookie has the named cookie-attribute. If *case_insensitive*
is true, the name is compared without regard to case.


.. method:: Cookie.get_nonstandard_attr(name, default=None)
Expand Down
9 changes: 6 additions & 3 deletions Lib/http/cookiejar.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _debug(*args):
logger = logging.getLogger("http.cookiejar")
return logger.debug(*args)

HTTPONLY_ATTR = "HTTPOnly"
HTTPONLY_ATTR = "HttpOnly"
HTTPONLY_PREFIX = "#HttpOnly_"
DEFAULT_HTTP_PORT = str(http.client.HTTP_PORT)
NETSCAPE_MAGIC_RGX = re.compile("#( Netscape)? HTTP Cookie File")
Expand Down Expand Up @@ -800,7 +800,10 @@ def __init__(self, version, name, value,

self._rest = copy.copy(rest)

def has_nonstandard_attr(self, name):
def has_nonstandard_attr(self, name, case_insensitive=False):
if case_insensitive:
name = name.lower()
return any(k.lower() == name for k in self._rest)
return name in self._rest
def get_nonstandard_attr(self, name, default=None):
return self._rest.get(name, default)
Expand Down Expand Up @@ -2113,7 +2116,7 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False):
else:
name = cookie.name
value = cookie.value
if cookie.has_nonstandard_attr(HTTPONLY_ATTR):
if cookie.has_nonstandard_attr(HTTPONLY_ATTR, case_insensitive=True):
domain = HTTPONLY_PREFIX + domain
f.write(
"\t".join([domain, initial_dot, cookie.path,
Expand Down
72 changes: 70 additions & 2 deletions Lib/test/test_http_cookiejar.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ def test_ns_parser(self):
# case is preserved
self.assertTrue(cookie.has_nonstandard_attr("blArgh"))
self.assertFalse(cookie.has_nonstandard_attr("blargh"))
self.assertTrue(cookie.has_nonstandard_attr("blargh", case_insensitive=True))

cookie = c._cookies["www.acme.com"]["/"]["ni"]
self.assertEqual(cookie.domain, "www.acme.com")
Expand Down Expand Up @@ -1881,7 +1882,7 @@ def test_mozilla(self):

for cookie in c:
if cookie.name == "foo1":
cookie.set_nonstandard_attr("HTTPOnly", "")
cookie.set_nonstandard_attr("HttpOnly", "")

def save_and_restore(cj, ignore_discard):
try:
Expand All @@ -1896,12 +1897,79 @@ def save_and_restore(cj, ignore_discard):
new_c = save_and_restore(c, True)
self.assertEqual(len(new_c), 6) # none discarded
self.assertIn("name='foo1', value='bar'", repr(new_c))
self.assertIn("rest={'HTTPOnly': ''}", repr(new_c))
self.assertIn("rest={'HttpOnly': ''}", repr(new_c))

new_c = save_and_restore(c, False)
self.assertEqual(len(new_c), 4) # 2 of them discarded on save
self.assertIn("name='foo1', value='bar'", repr(new_c))

def test_mozilla_httponly_prefix(self):
# Save / load Mozilla/Netscape cookie file with HttpOnly prefix.
filename = os_helper.TESTFN

# Load the input file test
c1 = MozillaCookieJar(filename)
one_year_later = int(time.time()) + 365*24*60*60
try:
with open(filename, "w") as f:
f.write("# Netscape HTTP Cookie File\n")
f.write("#HttpOnly_.example.com\tTRUE\t/\tFALSE\t%d\tfoo\tbar\n"
% (one_year_later,))
c1.load()
finally:
os_helper.unlink(filename)

cookie = list(c1)[0]
self.assertIn("HttpOnly", repr(cookie))
self.assertTrue(cookie.has_nonstandard_attr("HttpOnly", case_insensitive=True))
self.assertTrue(cookie.has_nonstandard_attr("HTTPOnly", case_insensitive=True))
self.assertFalse(cookie.has_nonstandard_attr("HTTPOnly"))

# Save and read the output file test
c2 = MozillaCookieJar(filename)
year_plus_one = time.localtime()[0] + 1
expires = "expires=09-Nov-%d 23:12:40 GMT" % (year_plus_one,)
# foo1 has the HttpOnly flag set
interact_netscape(c2, "http://example.com/",
"foo1=bar1; %s; HttpOnly;" % expires)
# foo2 will have the HttpOnly flag set later
interact_netscape(c2, "http://example.com/",
"foo2=bar2; %s;" % expires)
# foo3 will have the HTTPOnly flag set later
interact_netscape(c2, "http://example.com/",
"foo3=bar3; %s;" % expires)
# foo4 does not have the HttpOnly flag set
interact_netscape(c2, "http://example.com/",
"foo4=bar4; %s;" % expires)
# Set flags manually
for cookie in c2:
if cookie.name == "foo2":
cookie.set_nonstandard_attr("HttpOnly", "")
if cookie.name == "foo3":
cookie.set_nonstandard_attr("HTTPOnly", "")

# Save and read the output file
try:
c2.save()
with open(filename, "r") as f:
lines = f.readlines()
finally:
os_helper.unlink(filename)

# Check that the HttpOnly prefix is added to the correct cookies
for key in ["foo1", "foo2", "foo3"]:
with self.subTest(key=key):
matches = [x for x in lines if key in x]
self.assertEqual(len(matches), 1)
self.assertTrue(matches[0].startswith("#HttpOnly_"))

# Check that the HttpOnly prefix is not added to the correct cookies
for key in ["foo4"]:
with self.subTest(key=key):
matches = [x for x in lines if key in x]
self.assertEqual(len(matches), 1)
self.assertFalse(matches[0].startswith("#HttpOnly_"))

def test_netscape_misc(self):
# Some additional Netscape cookies tests.
c = CookieJar()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix handling of the ``#HttpOnly_`` prefix in :class:`http.cookiejar.MozillaCookieJar`.
Loading