diff --git a/impit-python/src/cookies.rs b/impit-python/src/cookies.rs index a7dbb079..04002c9a 100644 --- a/impit-python/src/cookies.rs +++ b/impit-python/src/cookies.rs @@ -91,13 +91,18 @@ impl CookieStore for PythonCookieJar { kwargs.set_item("rest", rest).unwrap_or_default(); - let py_cookie = self.cookie_constructor.call(py, (), Some(&kwargs)).unwrap(); - - let args = PyTuple::new(py, vec![py_cookie]).unwrap(); - - self.cookie_jar - .call_method1(py, "set_cookie", args) - .unwrap(); + // Malformed cookies and cookie-jar insertion errors are ignored silently. + let py_cookie = match self.cookie_constructor.call(py, (), Some(&kwargs)) { + Ok(py_cookie) => py_cookie, + Err(_) => continue, + }; + + let args = match PyTuple::new(py, vec![py_cookie]) { + Ok(args) => args, + Err(_) => continue, + }; + + let _ = self.cookie_jar.call_method1(py, "set_cookie", args); } }); } diff --git a/impit-python/test/async_client_test.py b/impit-python/test/async_client_test.py index 8a0b11b5..cb5c837a 100644 --- a/impit-python/test/async_client_test.py +++ b/impit-python/test/async_client_test.py @@ -2,7 +2,7 @@ import json import socket import threading -from http.cookiejar import CookieJar +from http.cookiejar import Cookie, CookieJar from typing import Literal import pytest @@ -192,6 +192,35 @@ async def test_complex_cookies(self, browser: Browser) -> None: # but it's ok - https://www.rfc-editor.org/rfc/rfc6265#section-4.1.2.3 assert cookie.domain == '127.0.0.1' + @pytest.mark.asyncio + async def test_rejected_cookie_is_skipped_not_crashing(self, browser: Browser) -> None: + """Cookies rejected by the cookie jar are skipped instead of aborting the process.""" + + class RejectingCookieJar(CookieJar): + def set_cookie(self, cookie: Cookie) -> None: + if cookie.name == 'bad': + raise ValueError('simulate parsing error') + super().set_cookie(cookie) + + cookies_jar = RejectingCookieJar() + + impit = AsyncClient(browser=browser, cookie_jar=cookies_jar, follow_redirects=True) + + url = get_httpbin_url( + '/response-headers', + query={ + 'set-cookie': [ + 'bad=1; Path=/', + 'good=2; Path=/', + ] + }, + ) + + response = await impit.get(url) + assert response.status_code == 200 + + assert {'good'} == {cookie.name for cookie in cookies_jar} + @pytest.mark.asyncio async def test_cookie_jar_works(self, browser: Browser) -> None: cookies = Cookies({'preset-cookie': '123'}) diff --git a/impit-python/test/basic_client_test.py b/impit-python/test/basic_client_test.py index 7da84e07..f78bffe6 100644 --- a/impit-python/test/basic_client_test.py +++ b/impit-python/test/basic_client_test.py @@ -2,7 +2,7 @@ import socket import threading import time -from http.cookiejar import CookieJar +from http.cookiejar import Cookie, CookieJar from typing import Literal import pytest @@ -203,6 +203,34 @@ def test_complex_cookies(self, browser: Browser) -> None: # but it's ok - https://www.rfc-editor.org/rfc/rfc6265#section-4.1.2.3 assert cookie.domain == '127.0.0.1' + def test_rejected_cookie_is_skipped_not_crashing(self, browser: Browser) -> None: + """Cookies rejected by the cookie jar are skipped instead of aborting the process.""" + + class RejectingCookieJar(CookieJar): + def set_cookie(self, cookie: Cookie) -> None: + if cookie.name == 'bad': + raise ValueError('simulate parsing error') + super().set_cookie(cookie) + + cookies_jar = RejectingCookieJar() + + impit = Client(browser=browser, cookie_jar=cookies_jar, follow_redirects=True) + + url = get_httpbin_url( + '/response-headers', + query={ + 'set-cookie': [ + 'bad=1; Path=/', + 'good=2; Path=/', + ] + }, + ) + + response = impit.get(url) + assert response.status_code == 200 + + assert {'good'} == {cookie.name for cookie in cookies_jar} + def test_cookie_jar_works(self, browser: Browser) -> None: cookies = Cookies({'preset-cookie': '123'}) diff --git a/impit-python/uv.lock b/impit-python/uv.lock index 2ab19201..85b06d06 100644 --- a/impit-python/uv.lock +++ b/impit-python/uv.lock @@ -388,7 +388,7 @@ wheels = [ [[package]] name = "impit" -version = "0.12.0" +version = "0.13.1" source = { editable = "." } [package.dev-dependencies]