While fixing #4281 and #4282 I read through the rest of ext/http and found more than fits in one PR. This is a tracker rather than a single bug: each item below has its own root cause, its own platform surface and its own fix, and I am opening a child issue and a small PR per item as I get to them rather than filing everything at once.
Every item is a code-inspection finding against main at 9d38caf. The affected code path and the conditions that reach it are described per item. Runtime reproduction and regression-test status belong in the child issues, not here. Please push back on anything you consider a deliberate limitation of a test server rather than a defect, and I will drop it.
One further potentially security-sensitive finding was reported privately under the project security policy and is intentionally out of scope here.
Memory safety
Process safety
Nonblocking write handling
Reactor (all platforms)
macOS reactor
Request framing
Build
Minor
Possible follow-up, not a defect
A compile-only target that includes every installed ext header on Linux, macOS and Windows would have caught the build item above, and other installed headers with no in-repo includer are likely in the same position. Happy to propose one if that sounds useful.
I do not expect all of these to be accepted, and I would rather you closed some as working-as-intended than have me open PRs for them. If you use formal sub-issues I am happy for these to be attached that way; I do not have the permission to do it myself.
While fixing #4281 and #4282 I read through the rest of
ext/httpand found more than fits in one PR. This is a tracker rather than a single bug: each item below has its own root cause, its own platform surface and its own fix, and I am opening a child issue and a small PR per item as I get to them rather than filing everything at once.Every item is a code-inspection finding against
mainat9d38caf. The affected code path and the conditions that reach it are described per item. Runtime reproduction and regression-test status belong in the child issues, not here. Please push back on anything you consider a deliberate limitation of a test server rather than a defect, and I will drop it.One further potentially security-sensitive finding was reported privately under the project security policy and is intentionally out of scope here.
Memory safety
processRequest()uses aConnectionafterhandleConnectionClosed()erases it. PR [BUG] Fix use-after-free when an HTTP handler closes the connection #4289 is up.SocketAddr(char const *)on Windows, where the copy loop uses a byte count as an element count. PR [BUG] Make SocketAddr string parsing safe and reject malformed addresses #4292 is up.SocketAddr(char const *)reads uninitialized bytes because the host buffer is terminated at a fixed index. PR [BUG] Make SocketAddr string parsing safe and reject malformed addresses #4292 is up.Process safety
SIGPIPEand terminate the host process. PR [BUG] Stop embedded HTTP server writes from raising SIGPIPE #4294 is up. Worth landing before the fatal send error item below, since otherwise the process can die before that handling runs.Nonblocking write handling
sendMore()performs a singlesend()per readiness event. Winsock permits a nonblockingsend()to succeed having transferred only part of the buffer, and documents that after the firstFD_WRITEan application should keep sending until a send fails withWSAEWOULDBLOCK, because that failure is what arms the nextFD_WRITE. Re-registering the same interest does not help:socket_tools.h#L582only callsWSAEventSelectwhen the flag set actually changes, so a repeataddSocket(Writable | Closed)is a no-op. A short write on a response larger than the send buffer can therefore park inSendingHeadersorSendingBody. Draining until the socket says stop also makes the would-block branch reachable by a test, which it is not today under level-triggered readiness. Credit for this one goes to a reviewer of [BUG] Fix transient would-block handling in the ext/http embedded server #4283.send()error never closes the connection.http_server.h#L344-L347returnstrueon any non would-block error without closing the socket, removing the reactor registration or changing state. On LinuxEPOLLOUTis registered withoutEPOLLET, so the writable callback can be re-entered continuously; on Windows there may simply be no further event. [BUG] Fix transient would-block handling in the ext/http embedded server #4283 fixes the would-block cases and deliberately leaves this one.EINTRis not classified as retryable. An interruptedrecvis treated as a peer close and an interruptedsendleaves the connection stalled.socket_tools.h#L770already handlesEINTRfor the poll loop, so there is a precedent to follow.int.http_server.h#L342castsconn.sendBuffer.size()tointbefore passing it to a wrapper that takesunsigned. The response body comes from the handler, som_maxRequestContentSizedoes not bound it. A body around 4 GiB can narrow to a length of0, which makessend()return0and the buffer stop advancing.Reactor (all platforms)
WSACreateEvent,epoll_ctl(EPOLL_CTL_ADD),WSAEventSelectandepoll_ctl(EPOLL_CTL_MOD), and thekeventadd calls, ignore their return values, while the teardownkeventcalls do check theirs. A failed registration updates the software interest flags but leaves the kernel out of sync silently, so a socket can be waited on for the wrong events or never woken. Credit for this one goes to a reviewer of [BUG] Stop embedded HTTP server writes from raising SIGPIPE #4294.onSocketReadable()and thenonSocketClosed()as separateifs in one iteration, andonSocketReadable()does a singlerecv()of one buffer. ForEPOLLIN | EPOLLHUP(the peer sent more than one buffer and then closed) the reactor reads one buffer and closes; the close removes the socket, so there is no later readable event to drain the rest. The Windows path has the same shape atFD_READthenFD_CLOSE. Bothepolland Winsock allow readable data to remain when a hangup or close is reported. Credit for this one goes to a reviewer of [BUG] Fix transient would-block handling in the ext/http embedded server #4283.macOS reactor
EV_EOFis never processed.socket_tools.h#L567-L570registers bothEVFILT_READandEVFILT_WRITEregardless of the requested flags, and#L627carries a TODO saying flag updates are unsupported, so a socket registered only for reading is woken continually by writability and then does nothing. Separately, in the event loop theEVFILT_READandEVFILT_WRITEbranches each end incontinuewhile theEV_EOF/EV_ERRORhandling sits after them. Since kqueue only reportsEV_EOFas a flag on one of those two filters, that close path, and theLOG_ERROR("Reactor: unhandled kevent!")below it, are unreachable.Request framing
http_server.h#L672-L680, and theContent-Lengthparse at#L440-L451tolerates trailing characters, soContent-Length: 5followed byContent-Length: 999parses as 5.Transfer-Encodingdoes not appear anywhere in the file, so it is neither honored nor rejected. RFC 9112 requires framing to be unambiguous. The trailing-character tolerance is mine from [CODE HEALTH] Fix clang-tidy bugprone-unchecked-string-to-number-conversion warnings #4216, so tightening it means revisiting that decision, and this is the item most likely to be a deliberate test-server simplification rather than a defect.Build
file_http_server.hcannot compile on Windows, becausestd::replaceis applied to aconst std::string &. PR [BUG] Make file_http_server.h compile on Windows #4300 is up.Minor
::tolowerand::toupperreceive a plaincharathttp_server.h#L708,#L724and#L733, which is undefined for negative values.Possible follow-up, not a defect
A compile-only target that includes every installed
extheader on Linux, macOS and Windows would have caught the build item above, and other installed headers with no in-repo includer are likely in the same position. Happy to propose one if that sounds useful.I do not expect all of these to be accepted, and I would rather you closed some as working-as-intended than have me open PRs for them. If you use formal sub-issues I am happy for these to be attached that way; I do not have the permission to do it myself.