From 304815228ae5f76df6b0effd3670e77982e0c588 Mon Sep 17 00:00:00 2001 From: Rodrigo Gomes da Silva Date: Fri, 14 Aug 2026 21:16:56 -0300 Subject: [PATCH] fix(http): don't let a non-iterable header buffer crash end_headers advertise_connection_close() guarded its two attribute reads but not the dedup scan over _headers_buffer. A stub whose buffer is truthy and non-iterable (a bare Mock) raised TypeError from inside end_headers(), which _handle_write's generic except would swallow into a spurious 500 -- the same failure class as the missing close_connection guard. --- api/helpers.py | 9 +++++++-- tests/test_rejected_write_connection_close.py | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/helpers.py b/api/helpers.py index 2337bb70..79a6c8f3 100644 --- a/api/helpers.py +++ b/api/helpers.py @@ -236,11 +236,16 @@ def advertise_connection_close(handler) -> None: Both attribute reads are defensive: partially-built handler stubs in the test suite carry neither ``close_connection`` nor ``_headers_buffer``, and - header emission must not depend on their presence. + header emission must not depend on their presence. The buffer is also + type-checked before it is scanned -- a stub whose ``_headers_buffer`` is a + truthy non-iterable (a bare ``Mock``) would otherwise raise ``TypeError`` + from inside ``end_headers()``, which ``_handle_write`` would swallow into a + spurious 500. """ if not getattr(handler, 'close_connection', False): return - for raw in getattr(handler, '_headers_buffer', None) or (): + buffered = getattr(handler, '_headers_buffer', None) + for raw in buffered if isinstance(buffered, (list, tuple)) else (): if raw[:11].lower() == b'connection:': return handler.send_header('Connection', 'close') diff --git a/tests/test_rejected_write_connection_close.py b/tests/test_rejected_write_connection_close.py index bd7ddb4f..a1bfc9a5 100644 --- a/tests/test_rejected_write_connection_close.py +++ b/tests/test_rejected_write_connection_close.py @@ -228,6 +228,26 @@ def test_end_headers_on_handler_stub_without_close_connection(monkeypatch): assert "Connection" not in dict(sent), "nothing was armed, so nothing to advertise" +def test_advertise_close_tolerates_a_non_iterable_header_buffer(): + """The dedup scan must be as defensive as the two attribute reads. + + A bare ``Mock`` answers every attribute, so ``_headers_buffer`` comes back + truthy but non-iterable. Iterating it unguarded raised + ``TypeError: 'Mock' object is not iterable`` from inside ``end_headers()``, + which ``_handle_write``'s generic ``except`` turns into a spurious 500 — + the same crash-inside-end_headers class as the missing + ``close_connection`` guard above. + """ + from unittest.mock import Mock + + from api.helpers import advertise_connection_close + + handler = Mock() + advertise_connection_close(handler) # must not raise + + handler.send_header.assert_called_once_with("Connection", "close") + + def test_check_auth_or_close_on_handler_stub_without_close_connection(monkeypatch): """check_auth_or_close must not invent close_connection on a stub.