From b961f6bd3cc268757cb08d0fd5fc621836043665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 8 Feb 2019 17:34:01 +0100 Subject: [PATCH 1/6] Make MozillaCookieJar use curl's format for session cookies --- Lib/http/cookiejar.py | 5 ++-- Lib/test/test_http_cookiejar.py | 25 +++++++++++++++++++ .../2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst | 2 ++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index 0ba8200f325a629..0e664b324529dda 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -2033,7 +2033,8 @@ def _really_load(self, f, filename, ignore_discard, ignore_expires): assert domain_specified == initial_dot discard = False - if expires == "": + # curl and Wget set expires to 0 for session cookies. + if expires == "0" or expires == "": expires = None discard = True @@ -2081,7 +2082,7 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False): if cookie.expires is not None: expires = str(cookie.expires) else: - expires = "" + expires = "0" if cookie.value is None: # cookies.txt regards 'Set-Cookie: foo' as a cookie # with no name, whereas http.cookiejar regards it as a diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index 8dbea3325d9b990..1a7657eed960607 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -1689,6 +1689,31 @@ def save_and_restore(cj, ignore_discard): self.assertEqual(len(new_c), 4) # 2 of them discarded on save self.assertIn("name='foo1', value='bar'", repr(new_c)) + # Check compatibility with curl / wget cookiejar format. + # See issue 17164 + try: + expires = int(time.time() + 3600) + with open(filename, "w") as f: + f.write(MozillaCookieJar.header) + f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % + expires) + f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") + f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") + c = MozillaCookieJar() + c.revert(filename) + self.assertEqual(len(c), 1) + c.revert(filename, ignore_discard = True) + self.assertEqual(len(c), 3) + c.save(filename, ignore_discard = True) + with open(filename, "r") as f: + for line in f: + if line == '\n' or line.startswith('#'): + continue + self.assertRegex(line.split('\t')[4], '^\d+$') + finally: + try: os.unlink(filename) + except OSError: pass + def test_netscape_misc(self): # Some additional Netscape cookies tests. c = CookieJar() diff --git a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst new file mode 100644 index 000000000000000..61c09b952e18ce6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst @@ -0,0 +1,2 @@ +`MozillaCookieJar` is now compatible with curl and wget cookiejar file +format. Contributed by Jérémie Detrey. From 6cde09b1148911e8afe7c0ee9458e314fc30bd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Tue, 30 Jul 2019 12:14:18 +0200 Subject: [PATCH 2/6] Fix formatting of What's New entry --- .../next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst index 61c09b952e18ce6..38c2247c18eb91e 100644 --- a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst +++ b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst @@ -1,2 +1,2 @@ -`MozillaCookieJar` is now compatible with curl and wget cookiejar file +:class:`http.cookiejar.MozillaCookieJar` is now compatible with curl and wget cookiejar file format. Contributed by Jérémie Detrey. From 944207e42baa380ecbe7334df9974777b53041c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Tue, 30 Jul 2019 12:16:46 +0200 Subject: [PATCH 3/6] Move test in a new method --- Lib/test/test_http_cookiejar.py | 52 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index 1a7657eed960607..18a7207e0694836 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -1689,31 +1689,6 @@ def save_and_restore(cj, ignore_discard): self.assertEqual(len(new_c), 4) # 2 of them discarded on save self.assertIn("name='foo1', value='bar'", repr(new_c)) - # Check compatibility with curl / wget cookiejar format. - # See issue 17164 - try: - expires = int(time.time() + 3600) - with open(filename, "w") as f: - f.write(MozillaCookieJar.header) - f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % - expires) - f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") - f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") - c = MozillaCookieJar() - c.revert(filename) - self.assertEqual(len(c), 1) - c.revert(filename, ignore_discard = True) - self.assertEqual(len(c), 3) - c.save(filename, ignore_discard = True) - with open(filename, "r") as f: - for line in f: - if line == '\n' or line.startswith('#'): - continue - self.assertRegex(line.split('\t')[4], '^\d+$') - finally: - try: os.unlink(filename) - except OSError: pass - def test_netscape_misc(self): # Some additional Netscape cookies tests. c = CookieJar() @@ -1836,6 +1811,33 @@ def test_session_cookies(self): # we didn't have session cookies in the first place self.assertNotEqual(counter["session_before"], 0) + def test_curl_format(self): + # Check compatibility with curl / wget cookiejar format. + # See issue 17164 + filename = test.support.TESTFN + try: + expires = int(time.time() + 3600) + with open(filename, "w") as f: + f.write(MozillaCookieJar.header) + f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % + expires) + f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") + f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") + c = MozillaCookieJar() + c.revert(filename) + self.assertEqual(len(c), 1) + c.revert(filename, ignore_discard = True) + self.assertEqual(len(c), 3) + c.save(filename, ignore_discard = True) + with open(filename, "r") as f: + for line in f: + if line == '\n' or line.startswith('#'): + continue + self.assertRegex(line.split('\t')[4], '^\d+$') + finally: + try: os.unlink(filename) + except OSError: pass + def test_main(verbose=None): test.support.run_unittest( From c718b44b9c2aabd1fde1e6706314e1014fd8e9fd Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 09:52:27 +0300 Subject: [PATCH 4/6] Fix the test to use the current test support API test.support.TESTFN and MozillaCookieJar.header no longer exist. --- Lib/test/test_http_cookiejar.py | 50 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index dd3f7c0f3e5549d..a5cd37ba6704ab7 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -16,7 +16,7 @@ CookieJar, DefaultCookiePolicy, LWPCookieJar, MozillaCookieJar, LoadError, lwp_cookie_str, DEFAULT_HTTP_PORT, escape_path, reach, is_HDN, domain_match, user_domain_match, request_path, - request_port, request_host) + request_port, request_host, NETSCAPE_HEADER_TEXT) mswindows = (sys.platform == "win32") @@ -2049,31 +2049,29 @@ def test_session_cookies(self): self.assertNotEqual(counter["session_before"], 0) def test_curl_format(self): - # Check compatibility with curl / wget cookiejar format. - # See issue 17164 - filename = test.support.TESTFN - try: - expires = int(time.time() + 3600) - with open(filename, "w") as f: - f.write(MozillaCookieJar.header) - f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % - expires) - f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") - f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") - c = MozillaCookieJar() - c.revert(filename) - self.assertEqual(len(c), 1) - c.revert(filename, ignore_discard = True) - self.assertEqual(len(c), 3) - c.save(filename, ignore_discard = True) - with open(filename, "r") as f: - for line in f: - if line == '\n' or line.startswith('#'): - continue - self.assertRegex(line.split('\t')[4], '^\d+$') - finally: - try: os.unlink(filename) - except OSError: pass + # Check compatibility with the curl and Wget cookie file format, + # which uses 0 for session cookies (gh-61364). + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + expires = int(time.time() + 3600) + with open(filename, "w") as f: + f.write(NETSCAPE_HEADER_TEXT) + f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % expires) + f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") + f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") + + c = MozillaCookieJar() + c.revert(filename) + self.assertEqual(len(c), 1) + c.revert(filename, ignore_discard=True) + self.assertEqual(len(c), 3) + + # Session cookies are saved with 0, not with an empty field. + c.save(filename, ignore_discard=True) + with open(filename) as f: + for line in f: + if line.strip() and not line.startswith('#'): + self.assertRegex(line.split('\t')[4], r'^\d+$') if __name__ == "__main__": From 98873a36b306ec90cf14ea67d0d565111678937c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 09:54:12 +0300 Subject: [PATCH 5/6] Only read the curl format, do not write it Writing 0 instead of an empty field makes the file incompatible with unpatched Python, so it is left for a separate change. --- Lib/http/cookiejar.py | 2 +- Lib/test/test_http_cookiejar.py | 33 ++++++++++--------- .../2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst | 2 -- ...9-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst | 3 ++ 4 files changed, 22 insertions(+), 18 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst create mode 100644 Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index f5631344fdbced8..81526cd544acfb1 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -2109,7 +2109,7 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False): if cookie.expires is not None: expires = str(cookie.expires) else: - expires = "0" + expires = "" if cookie.value is None: # cookies.txt regards 'Set-Cookie: foo' as a cookie # with no name, whereas http.cookiejar regards it as a diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index a5cd37ba6704ab7..1f81a33c9786705 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -2048,30 +2048,33 @@ def test_session_cookies(self): # we didn't have session cookies in the first place self.assertNotEqual(counter["session_before"], 0) - def test_curl_format(self): - # Check compatibility with the curl and Wget cookie file format, - # which uses 0 for session cookies (gh-61364). + def test_load_session_cookies(self): + # curl and Wget write 0 in the expires field for session cookies, + # while we write an empty field. Both should be read (gh-61364). filename = os_helper.TESTFN self.addCleanup(os_helper.unlink, filename) expires = int(time.time() + 3600) with open(filename, "w") as f: f.write(NETSCAPE_HEADER_TEXT) - f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tfoo1\tbar\n" % expires) - f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tfoo2\tbar\n") - f.write("www.foo.com\tFALSE\t/\tFALSE\t\tfoo3\tbar\n") + f.write("www.foo.com\tFALSE\t/\tFALSE\t%u\tperm\tbar\n" % expires) + f.write("www.foo.com\tFALSE\t/\tFALSE\t0\tcurl_session\tbar\n") + f.write("www.foo.com\tFALSE\t/\tFALSE\t\tour_session\tbar\n") c = MozillaCookieJar() c.revert(filename) - self.assertEqual(len(c), 1) - c.revert(filename, ignore_discard=True) - self.assertEqual(len(c), 3) + self.assertEqual([cookie.name for cookie in c], ["perm"]) - # Session cookies are saved with 0, not with an empty field. - c.save(filename, ignore_discard=True) - with open(filename) as f: - for line in f: - if line.strip() and not line.startswith('#'): - self.assertRegex(line.split('\t')[4], r'^\d+$') + c = MozillaCookieJar() + c.revert(filename, ignore_discard=True) + self.assertEqual(sorted(cookie.name for cookie in c), + ["curl_session", "our_session", "perm"]) + for cookie in c: + if cookie.name == "perm": + self.assertEqual(cookie.expires, expires) + self.assertFalse(cookie.discard) + else: + self.assertIsNone(cookie.expires) + self.assertTrue(cookie.discard) if __name__ == "__main__": diff --git a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst deleted file mode 100644 index 38c2247c18eb91e..000000000000000 --- a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.bpo-17164.k6W5Sp.rst +++ /dev/null @@ -1,2 +0,0 @@ -:class:`http.cookiejar.MozillaCookieJar` is now compatible with curl and wget cookiejar file -format. Contributed by Jérémie Detrey. diff --git a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst new file mode 100644 index 000000000000000..2a286a6b62a8f1b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst @@ -0,0 +1,3 @@ +:class:`http.cookiejar.MozillaCookieJar` now reads session cookies written +by curl and Wget, which use ``0`` in the expiration time field. +Contributed by Jérémie Detrey. From ee8e5261d43e31973cce902d846794da6412b5eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 09:58:28 +0300 Subject: [PATCH 6/6] Fix the issue number: bpo-17164 is gh-61366 --- Lib/test/test_http_cookiejar.py | 2 +- ...k6W5Sp.rst => 2019-02-08-17-33-49.gh-issue-61366.k6W5Sp.rst} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Misc/NEWS.d/next/Library/{2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst => 2019-02-08-17-33-49.gh-issue-61366.k6W5Sp.rst} (100%) diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index 1f81a33c9786705..7c31cff56000b83 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -2050,7 +2050,7 @@ def test_session_cookies(self): def test_load_session_cookies(self): # curl and Wget write 0 in the expires field for session cookies, - # while we write an empty field. Both should be read (gh-61364). + # while we write an empty field. Both should be read (gh-61366). filename = os_helper.TESTFN self.addCleanup(os_helper.unlink, filename) expires = int(time.time() + 3600) diff --git a/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst b/Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61366.k6W5Sp.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61364.k6W5Sp.rst rename to Misc/NEWS.d/next/Library/2019-02-08-17-33-49.gh-issue-61366.k6W5Sp.rst