The DoS mitigation added in db389a730 wraps each message-body read in
``select()`` so a stalled client cannot pin the server forever. The
existing transport tests route through MockLspTransport, whose
``BytesIO`` raises ``UnsupportedOperation`` on ``fileno()`` -- so
``_read_body_with_timeout`` always falls through the BytesIO fast
path and the actual ``select()``-based mitigation code (the part
that runs in production) is never executed. That left ~19 new lines
uncovered, dragging total coverage below the 96.5% floor and
failing the coverage gate.
Two new scenarios drive the helper through an ``os.pipe()`` whose
read fd satisfies ``fileno()``, so ``use_select`` is True and the
real DoS-protection path runs:
* ``timeout=0.0`` makes the deadline already past on the first
iteration, exercising the ``if timeout <= 0:`` early-exit warning.
* ``timeout=0.05`` lets ``select.select()`` run and time out with no
ready descriptors, exercising the ``if not ready:`` warning.
Both paths log ``lsp.transport.read_timeout`` and return ``None``,
matching the production behaviour the helper was added to provide.
ISSUES CLOSED: #5566
Resolves the DoS vulnerability in LspServer._read_message() where a
malicious client could send a valid Content-Length header but stall
without delivering the body, blocking the server indefinitely.
Changes:
- Add MESSAGE_READ_TIMEOUT constant (default: 30 seconds)
- Add _read_body_with_timeout() method using select() for timeout enforcement
- Add read_timeout constructor parameter for configurability
- Replace blocking self._input.read(content_length) with timeout-based read
- Remove the SEC: comment that documented the vulnerability
- Add BDD tests covering timeout behavior and constant export
The fix uses select() for streams with a real file descriptor (e.g.
sys.stdin) and falls back to direct blocking read for in-memory streams
(e.g. io.BytesIO used in tests), preserving full test compatibility.
Closes#7083