From 68b2df83111c4cb55980dbca8f7d9c07bd65e5c0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 09:55:59 +0300 Subject: [PATCH 1/2] Write 0 in the expiration time field for session cookies curl and Wget ignore the line if this field is empty. --- Lib/http/cookiejar.py | 4 ++- Lib/test/test_http_cookiejar.py | 29 +++++++++++++++++++ ...6-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst | 3 ++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index 81526cd544acfb..144db91a0f0317 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -2109,7 +2109,9 @@ def save(self, filename=None, ignore_discard=False, ignore_expires=False): if cookie.expires is not None: expires = str(cookie.expires) else: - expires = "" + # curl and Wget use 0 for session cookies, and ignore + # the line if this field is empty. + 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 7c31cff56000b8..218181f1185365 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -2048,6 +2048,35 @@ def test_session_cookies(self): # we didn't have session cookies in the first place self.assertNotEqual(counter["session_before"], 0) + def test_save_session_cookies(self): + # Session cookies are saved with 0 in the expiration time field, + # as curl and Wget do (gh-61366). + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + expires = int(time.time() + 3600) + c = MozillaCookieJar() + c.set_cookie(Cookie(0, "perm", "bar", None, False, + "www.foo.com", True, False, "/", False, False, + expires, False, None, None, {})) + c.set_cookie(Cookie(0, "session", "bar", None, False, + "www.foo.com", True, False, "/", False, False, + None, True, None, None, {})) + c.save(filename, ignore_discard=True) + + saved = {} + with open(filename) as f: + for line in f: + if line.strip() and not line.startswith("#"): + fields = line.split("\t") + saved[fields[5]] = fields[4] + self.assertEqual(saved, {"perm": str(expires), "session": "0"}) + + # The saved file can be read back. + c = MozillaCookieJar() + c.revert(filename, ignore_discard=True) + self.assertEqual(sorted(cookie.name for cookie in c), + ["perm", "session"]) + 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-61366). diff --git a/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst b/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst new file mode 100644 index 00000000000000..9a6b634311b232 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst @@ -0,0 +1,3 @@ +:meth:`http.cookiejar.MozillaCookieJar.save` now writes ``0`` in the +expiration time field for session cookies, as curl and Wget do. Previously +this field was left empty, and such lines were ignored by curl and Wget. From 990501db303dc8fd5950f255eedb8081893473ea Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 12 Aug 2026 11:54:23 +0300 Subject: [PATCH 2/2] Update 2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst --- .../next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst b/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst index 9a6b634311b232..662d55f05abda6 100644 --- a/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst +++ b/Misc/NEWS.d/next/Library/2026-08-12-09-30-00.gh-issue-61366.Wr7cKm.rst @@ -1,3 +1,3 @@ -:meth:`http.cookiejar.MozillaCookieJar.save` now writes ``0`` in the +:meth:`!http.cookiejar.MozillaCookieJar.save` now writes ``0`` in the expiration time field for session cookies, as curl and Wget do. Previously this field was left empty, and such lines were ignored by curl and Wget.