Repository navigation
Fix request processing failures caused by failed allocations - #501
Open
mathieucarbou wants to merge 2 commits into
Open
mathieucarbou wants to merge 2 commits into
mathieucarbou wants to merge 2 commits into
Conversation
Abort the request instead of crashing, corrupting the wire format or silently mis-parsing it when a heap allocation fails. Arduino String leaves a failed string empty, and its c_str() is null on ESP32. Sites that copied header or body data and then passed c_str() to C string functions could crash (issue #500), and sites that stored the copy in request state silently lost or corrupted data. Header parsing: - Add AsyncWebHeader::tryParse() reporting ParseStatus::NoMemory so a failed name/value copy fails the request instead of dropping the header (a lost Content-Length desynchronizes the body parser). - Scan Accept and Transfer-Encoding in place: the lowercased copies are gone, so issue #500 cannot happen and chunked framing is no longer silently missed. - Abort on failed copies into _host, _contentType, _authorization and on failed header-line/body accumulation in the plain and multipart parsers. - Write the 100-continue reply from the literal: the String copy exceeded the small-buffer size and an OOM one silently stalled the client. Responses: - Check every concat() in _assembleHead(); callers now close the connection instead of writing a truncated head (basic, abstract, WebSocket and SSE responses). - AsyncWebSocketResponse flags RESPONSE_FAILED when it cannot build Sec-WebSocket-Accept, and _sourceValid() reflects it, so the upgrade is refused rather than completing with a garbage accept header. Allocation helpers: - Use std::nothrow for response objects created by beginResponse*(), AsyncStaticWebHandler and AsyncEventSource so failures are observable instead of aborting the device; send()/_send() handle a null response. - Replace plain new with malloc in generateBasicHash() and the WebSocket printf helpers, where the null checks were dead code. Static file handler: - Stop and fall through to 404 when the path cannot be built, rather than serving the default or a wrong file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
|
@willmmiles : this is a starting point, not fully tested yet. |
Keep the code self-explanatory: the allocation change does not need an inline comment in each of the call sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mathieucarbou
marked this pull request as ready for review
October 28, 2026 21:35
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #500 and the other places where request processing broke when a heap allocation failed.
An Arduino
Stringwhose allocation fails is left empty, and itsc_str()returns NULL on ESP32. Depending on where that happened, the library crashed, silently mis-parsed the request, corrupted the HTTP wire format, or served the wrong file.Why
Issue #500 reported a crash in
_parseReqHeader(): theAcceptvalue was copied into a newString, the copy failed under heap pressure, andstrstr(NULL, ...)hard-faulted in theasync_tcptask. The same pattern existed in several other places, with different (but equally wrong) outcomes.What changed
Header parsing (
WebRequest.cpp,AsyncWebHeader.cpp)AsyncWebHeader::tryParse()returnsParseStatus::{Ok,Invalid,NoMemory}so a failed name/value copy fails the request rather than silently dropping the header. A droppedContent-LengthorTransfer-Encodingdesynchronizes the body parser.AcceptandTransfer-Encodingare now scanned in place — the lowercased copies are gone entirely, so the Null dereference in_parseReqHeader()when theAcceptheader copy fails to allocate (crash instead of rejecting the request) #500 crash cannot occur and chunked framing can no longer be silently missed._host,_contentTypeand_authorizationabort the request instead of handing handlers corrupt state._onData()and failed byte accumulation in the plain/multipart body parsers abort instead of truncating data.100 Continuereply is written straight from the literal (theStringcopy exceeded the small-buffer size, and an OOM one silently stalled the client).Responses (
WebResponses.cpp,AsyncWebSocket.cpp,AsyncEventSource.cpp)concat()in_assembleHead()is checked; on OOM the buffer is emptied and all four_respond()callers close the connection instead of writing a truncated head.AsyncWebSocketResponsesetsRESPONSE_FAILEDwhen it cannot buildSec-WebSocket-Accept, and_sourceValid()reflects it, so the upgrade is refused instead of completing with a garbage accept header.Allocation helpers
beginResponse*(),AsyncStaticWebHandlerandAsyncEventSourceusestd::nothrow, making their existing null checks live instead of unreachable (plainnewaborts the device on OOM).send()/_send()now handle a null response.generateBasicHash()and the WebSocketprintfhelpers usemallocwhere the null checks were dead code.Static file handler (
WebHandlers.cpp)canHandle()returns false so the request falls through to another handler / 404 instead of serving the default or a wrong file.Known limitation (not addressed here)
std::list::emplace_back(headers, params),std::vector,std::make_shared(WebSocket message buffers) and thecbufallocated insideAsyncResponseStreamstill use throwing-new, so an OOM there aborts the device. Fixing that needs custom allocators/containers and is out of scope for this change.Validation
arduino-3,esp8266,raspberrypi,libretiny,arduino-2-esp-idf-log(core 2.x),no-json,regex,no-chunk-inflight,latest-asynctcp— all pass.arduino-2env itself fails before compiling any source because PlatformIO no longer serves the pinnedtool-esptoolpy@^5.3.0; this happens on unmodifiedmaintoo. Thearduino-2-esp-idf-logenv covers the same core.cpplintwith the CI filter set: 0 errors.codespell: clean. Formatted with the pinnedclang-format18.1.3.Opened as a draft for review.