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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/).

## Unreleased

- Honor `Max-Age` over `Expires` when removing expired session cookies.

## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01)

- Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596))
Expand Down
2 changes: 0 additions & 2 deletions httpie/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ def _max_age_to_expires(cookies, now):

"""
for cookie in cookies:
if 'expires' in cookie:
continue
max_age = cookie.get('max-age')
if max_age and max_age.isdigit():
cookie['expires'] = now + float(max_age)
Expand Down
33 changes: 30 additions & 3 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,41 @@ def test_expired_cookies(self, httpbin):
assert 'cookie1' in updated_session['cookies']
assert 'cookie2' not in updated_session['cookies']

def test_session_keeps_cookie_when_max_age_overrides_expired_date(self, localhost_http_server):
http(
'--session', str(self.session_path),
localhost_http_server + '/cookies/set',
'cookie2==bar; Max-Age=3600; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Path=/',
)
r = http(
'--session', str(self.session_path),
'--print=H',
localhost_http_server + '/cookies',
)
assert 'cookie2=bar' in r

def test_get_expired_cookies_using_max_age(self):
cookies = 'one=two; Max-Age=0; path=/; domain=.tumblr.com; HttpOnly'
expected_expired = [
{'name': 'one', 'path': '/'}
]
assert get_expired_cookies(cookies, now=None) == expected_expired

@pytest.mark.parametrize(
'expires, max_age, expired',
[
('Thu, 01-Jan-1970 00:00:00 GMT', '3600', False),
('Fri, 12 Jun 2020 12:28:55 GMT', '0', True),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a negative Max-Age case here?

RFC 6265 allows negative values such as Max-Age=-1 and treats them as immediately expired. At the moment, _max_age_to_expires() uses max_age.isdigit(), which rejects -1.

For example, when a cookie has Max-Age=-1 alongside a future Expires date, the current implementation leaves the future expiration in place. That means Expires incorrectly wins over Max-Age.

A regression case such as ('Fri, 12 Jun 2020 12:28:55 GMT', '-1', True) with the existing test's now=1000.0 should expose this. We could then handle valid signed integers while continuing to ignore malformed values.

('invalid', '0', True),
('Thu, 01-Jan-1970 00:00:00 GMT', 'invalid', True),
('Fri, 12 Jun 2020 12:28:55 GMT', 'invalid', False),
]
)
def test_max_age_takes_precedence_over_expires(self, expires, max_age, expired):
cookies = f'one=two; Expires={expires}; Max-Age={max_age}; Path=/'
expected = [{'name': 'one', 'path': '/'}] if expired else []
assert get_expired_cookies(cookies, now=1000.0) == expected

@pytest.mark.parametrize(
'cookies, now, expected_expired',
[
Expand All @@ -426,11 +454,10 @@ def test_get_expired_cookies_using_max_age(self):
]
),
(
# Checks we gracefully ignore expires date in invalid format.
# <https://github.com/httpie/cli/issues/963>
# A valid Max-Age still applies when Expires is malformed.
'pfg=; Expires=Sat, 19-Sep-2020 06:58:14 GMT+0000; Max-Age=0; path=/; domain=.tumblr.com; secure; HttpOnly',
None,
[]
[{'name': 'pfg', 'path': '/'}]
),
(
'hello=world; Path=/; Expires=Fri, 12 Jun 2020 12:28:55 GMT; HttpOnly',
Expand Down
Loading