open
https://gitlab.synchro.net/main/sbbs/-/work_items/1275
## Summary
An HTTPS request whose request line (or headers) arrives in more than one TLS record is silently dropped: the session is closed with no response, no error, and no `Request N:` log line. The same request over plain HTTP is served normally.
Observed on Linux, v3.22a Debug, master as of 2026-09-30.
## Reproduction
Scratch web server on loopback (`[Web] Port=18080 TLSPort=18443`, static `index.html`). Python client; the request is `GET /index.html HTTP/1.0\r\nHost: localhost\r\n\r\n`, sent either whole or as the first 5 bytes, a 1 s pause, then the rest:
```
http single -> HTTP/1.1 200 OK
http split -> HTTP/1.1 200 OK
https single -> HTTP/1.1 200 OK
https split -> (no response; server closed the connection)
```
Server log for the failing case has only `Session thread started`, then `Session thread terminated after 0 requests`. No `Request 1:` line, no TLS error at `LogLevel=Debug`.
Any HTTPS client that does not deliver the whole request line in one TLS record hits this: a request typed into `openssl s_client`, or a client that flushes the request line before the headers.
## Cause
`sockreadline()` in `websrvr.cpp` reads the request line one byte at a time via `sess_recv()`. For a TLS session it waits on the raw socket with `socket_readable()` only while `tls_pending` is false; once the first record is readable it sets `tls_pending` and from then on calls `cryptPopData()` directly, which is non-blocking (`CRYPT_OPTION_NET_READTIMEOUT` is 0 for the session).
When the buffered record is drained before a newline has been seen, the next pop returns no data. `sess_recv()` turns that into `-1` (`len == 0` -> `tls_pending = false; len = -1`), and `sockreadline()` treats `-1` on a TLS session as a disconnect: it calls `close_session_socket()` and returns `-1`. The partial line is discarded and the session ends.
The `tls_pending` short-circuit was added so a request body already decrypted in the TLS layer is not waited for on the raw socket (#1169). The missing piece is the other direction: when the TLS layer has nothing buffered and the socket is still open, `sockreadline()` should go back to waiting on the socket (clear `tls_pending` and loop) rather than closing. The same pattern is in `recvbufsocket()`/`get_req()` for the body read and should be checked too.
## Found while
Investigating the web server stop hang of 2026-09-30 (the shutdown() kick commit on master). A test client sending a partial request line over TLS never reached the request-read wait because the session was closed on the second record.
- *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.38-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)