Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/AsyncEventSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,13 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
}

void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
request->send(new AsyncEventSourceResponse(this));
AsyncWebServerResponse *response = new (std::nothrow) AsyncEventSourceResponse(this);
if (!response) {
async_ws_log_e("Failed to allocate");
request->abort();
return;
}
request->send(response);
}

// list iteration protected by caller's lock
Expand Down Expand Up @@ -416,6 +422,14 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) : _
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
String out;
_assembleHead(out, request->version());
if (!_headLength) {
// Head assembly ran out of memory: fail the stream instead of sending a
// corrupted head.
async_ws_log_e("Failed to allocate");
_state = RESPONSE_FAILED;
request->client()->close();
return;
}
uint32_t lastId = 0;
if (request->hasHeader(T_Last_Event_ID)) {
lastId = strtoul(request->getHeader(T_Last_Event_ID)->value().c_str(), nullptr, 10);
Expand Down
37 changes: 29 additions & 8 deletions src/AsyncWebHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@

#include <ESPAsyncWebServer.h>

#include <utility>

const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
AsyncWebHeader header;
// Both a malformed line and an allocation failure yield an empty (invalid) header
(void)tryParse(data, header);
return header;
}

AsyncWebHeader::ParseStatus AsyncWebHeader::tryParse(const char *data, AsyncWebHeader &out) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
// In HTTP/1.X, a header is a case-insensitive name followed by a colon, then optional whitespace which will be ignored, and finally by its value
if (!data) {
return AsyncWebHeader(); // nullptr
return ParseStatus::Invalid; // nullptr
}
if (data[0] == '\0') {
return AsyncWebHeader(); // empty string
return ParseStatus::Invalid; // empty string
}
if (strchr(data, '\n') || strchr(data, '\r')) {
return AsyncWebHeader(); // Invalid header format
return ParseStatus::Invalid; // Invalid header format
}
const char *colon = strchr(data, ':');
if (!colon) {
return AsyncWebHeader(); // separator not found
return ParseStatus::Invalid; // separator not found
}
if (colon == data) {
return AsyncWebHeader(); // Header name cannot be empty
return ParseStatus::Invalid; // Header name cannot be empty
}
const char *startOfValue = colon + 1; // Skip the colon
// RFC 7230 §3.2.3: OWS (optional whitespace) = *( SP / HTAB )
Expand All @@ -29,7 +38,19 @@ const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
startOfValue++;
}
String name;
name.reserve(colon - data);
name.concat(data, colon - data);
return AsyncWebHeader(name, String(startOfValue));
if (!name.reserve(colon - data) || !name.concat(data, colon - data)) {
return ParseStatus::NoMemory;
}
String value(startOfValue);
// A copy of a non-empty value can never be empty: Arduino String leaves
// length() == 0 only when the heap buffer could not be allocated (its
// c_str() is then null on ESP32, which downstream header consumers such as
// atoi() and strncasecmp() would dereference). Reject the header so the
// caller can fail the request.
if (*startOfValue && !value.length()) {
return ParseStatus::NoMemory;
}
out._name = std::move(name);
out._value = std::move(value);
return ParseStatus::Ok;
}
57 changes: 40 additions & 17 deletions src/AsyncWebSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <cstdio>
#include <cstring>
#include <memory>
#include <new>
#include <utility>
#include <cstdarg>

Expand Down Expand Up @@ -859,18 +860,18 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) {
return 0;
}

char *buffer = new char[len + 1];
std::unique_ptr<char[]> buffer(new (std::nothrow) char[len + 1]);

if (!buffer) {
async_ws_log_e("Failed to allocate");
return 0;
}

va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
len = vsnprintf(buffer.get(), len + 1, format, arg);
va_end(arg);

bool enqueued = text(buffer, len);
delete[] buffer;
bool enqueued = text(buffer.get(), len);
return enqueued ? len : 0;
}

Expand All @@ -885,18 +886,18 @@ size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
return 0;
}

char *buffer = new char[len + 1];
std::unique_ptr<char[]> buffer(new (std::nothrow) char[len + 1]);

if (!buffer) {
async_ws_log_e("Failed to allocate");
return 0;
}

va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
len = vsnprintf_P(buffer.get(), len + 1, formatP, arg);
va_end(arg);

bool enqueued = text(buffer, len);
delete[] buffer;
bool enqueued = text(buffer.get(), len);
return enqueued ? len : 0;
}
#endif
Expand Down Expand Up @@ -1368,18 +1369,18 @@ size_t AsyncWebSocket::printfAll(const char *format, ...) {
return 0;
}

char *buffer = new char[len + 1];
std::unique_ptr<char[]> buffer(new (std::nothrow) char[len + 1]);

if (!buffer) {
async_ws_log_e("Failed to allocate");
return 0;
}

va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
len = vsnprintf(buffer.get(), len + 1, format, arg);
va_end(arg);

AsyncWebSocket::SendStatus status = textAll(buffer, len);
delete[] buffer;
AsyncWebSocket::SendStatus status = textAll(buffer.get(), len);
return status == DISCARDED ? 0 : len;
}

Expand All @@ -1406,18 +1407,18 @@ size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
return 0;
}

char *buffer = new char[len + 1];
std::unique_ptr<char[]> buffer(new (std::nothrow) char[len + 1]);

if (!buffer) {
async_ws_log_e("Failed to allocate");
return 0;
}

va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
len = vsnprintf_P(buffer.get(), len + 1, formatP, arg);
va_end(arg);

AsyncWebSocket::SendStatus status = textAll(buffer, len);
delete[] buffer;
AsyncWebSocket::SendStatus status = textAll(buffer.get(), len);
return status == DISCARDED ? 0 : len;
}
#endif
Expand Down Expand Up @@ -1462,17 +1463,30 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request) {
const AsyncWebHeader *version = request->getHeader(WS_STR_VERSION);
if (version->value().toInt() != 13) {
AsyncWebServerResponse *response = request->beginResponse(400);
if (!response) {
async_ws_log_e("Failed to allocate");
request->abort();
return;
}
response->addHeader(WS_STR_VERSION, T_13);
request->send(response);
return;
}
const AsyncWebHeader *key = request->getHeader(WS_STR_KEY);
AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
AsyncWebServerResponse *response = new (std::nothrow) AsyncWebSocketResponse(key->value(), this);
if (response == NULL) {
async_ws_log_e("Failed to allocate");
request->abort();
return;
}
if (!response->_sourceValid()) {
// The response could not build the Sec-WebSocket-Accept header (out of
// memory): fail the upgrade instead of sending an invalid handshake.
async_ws_log_e("Failed to allocate");
delete response;
request->abort();
return;
}
if (request->hasHeader(WS_STR_PROTOCOL)) {
const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL);
// ToDo: check protocol
Expand Down Expand Up @@ -1507,6 +1521,7 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket
String k;
if (!k.reserve(key.length() + WS_STR_UUID_LEN)) {
async_ws_log_e("Failed to allocate");
_state = RESPONSE_FAILED;
return;
}
k.concat(key);
Expand Down Expand Up @@ -1554,6 +1569,14 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request) {
);
String out;
_assembleHead(out, request->version());
if (!_headLength) {
// Head assembly ran out of memory: fail the upgrade instead of sending a
// corrupted handshake.
async_ws_log_e("Failed to allocate");
_state = RESPONSE_FAILED;
request->client()->close();
return;
}
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}
Expand Down
4 changes: 3 additions & 1 deletion src/AsyncWebSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse {
return 0;
};
bool _sourceValid() const override {
return true;
// _state is RESPONSE_FAILED when the Sec-WebSocket-Accept computation
// could not allocate (see the constructor).
return _state != RESPONSE_FAILED;
}
};

Expand Down
28 changes: 27 additions & 1 deletion src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,33 @@ class AsyncWebHeader {
return _name.length();
}

// Result of AsyncWebHeader::tryParse()
enum class ParseStatus {
Ok, // header parsed, output valid
Invalid, // malformed header line
NoMemory, // allocation failure: request must be failed, not skipped
};

/**
* @brief Parse a header line into an existing AsyncWebHeader.
*
* Unlike parse(), the output parameter is only overwritten on success, so a
* NoMemory result cannot leave a half-built header behind. A NoMemory
* result must abort the request (see issue #500): a String whose internal
* allocation failed has a null c_str() on ESP32 and an empty one on
* ESP8266, and downstream header consumers would either dereference it or
* silently misparse the request.
*
* @param data raw header line "Name: value" (no CR/LF)
* @param out header to fill on success; left untouched otherwise
* @return ParseStatus Ok, Invalid or NoMemory
*/
static ParseStatus tryParse(const char *data, AsyncWebHeader &out);

static const AsyncWebHeader parse(const String &data) {
return parse(data.c_str());
AsyncWebHeader header;
(void)tryParse(data.c_str(), header);
return header;
}
static const AsyncWebHeader parse(const char *data);
};
Expand Down Expand Up @@ -595,6 +620,7 @@ class AsyncWebServerRequest {

bool _parseReqHead();
bool _parseReqHeader();
void _parseHeader(AsyncWebHeader &&header);
void _parseLine();
void _parsePlainPostChar(uint8_t data);
void _parseMultipartPostByte(uint8_t data, bool last);
Expand Down
25 changes: 12 additions & 13 deletions src/WebAuthentication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "AsyncWebServerLogging.h"
#include <libb64/cencode.h>

#include <memory>
#include <new>

#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(HOST)
#include <MD5Builder.h>
#else
Expand All @@ -31,24 +34,20 @@ String generateBasicHash(const char *username, const char *password) {

size_t toencodeLen = strlen(username) + strlen(password) + 1;

char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) {
std::unique_ptr<char[]> toencode(new (std::nothrow) char[toencodeLen + 1]);
if (!toencode) {
async_ws_log_e("Failed to allocate");
return asyncsrv::emptyString;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) {
delete[] toencode;
std::unique_ptr<char[]> encoded(new (std::nothrow) char[base64_encode_expected_len(toencodeLen) + 1]);
if (!encoded) {
async_ws_log_e("Failed to allocate");
return asyncsrv::emptyString;
}
sprintf_P(toencode, PSTR("%s:%s"), username, password);
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) {
String res = String(encoded);
delete[] toencode;
delete[] encoded;
return res;
sprintf_P(toencode.get(), PSTR("%s:%s"), username, password);
if (base64_encode_chars(toencode.get(), toencodeLen, encoded.get()) > 0) {
return String(encoded.get());
}
delete[] toencode;
delete[] encoded;
return asyncsrv::emptyString;
}

Expand Down
Loading
Loading