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
33 changes: 25 additions & 8 deletions src/AsyncWebHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,31 @@

#include <ESPAsyncWebServer.h>

#include <utility>

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

AsyncWebHeader::ParseResult AsyncWebHeader::tryParse(const char *data) {
// 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 +36,17 @@ 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). Report it so the caller can
// fail the request.
if (*startOfValue && !value.length()) {
return {ParseStatus::NoMemory, {}};
}
return {ParseStatus::Ok, AsyncWebHeader(std::move(name), std::move(value))};
}
64 changes: 39 additions & 25 deletions src/AsyncWebSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,19 +859,17 @@ 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;
return enqueued ? len : 0;
return text(buffer.get(), len) ? len : 0;
}

#ifdef ESP8266
Expand All @@ -885,19 +883,17 @@ 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;
return enqueued ? len : 0;
return text(buffer.get(), len) ? len : 0;
}
#endif

Expand Down Expand Up @@ -1368,19 +1364,17 @@ 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;
return status == DISCARDED ? 0 : len;
return textAll(buffer.get(), len) == DISCARDED ? 0 : len;
}

#ifdef ESP8266
Expand All @@ -1406,19 +1400,17 @@ 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;
return status == DISCARDED ? 0 : len;
return textAll(buffer.get(), len) == DISCARDED ? 0 : len;
}
#endif

Expand Down Expand Up @@ -1462,17 +1454,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 +1512,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 +1560,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
34 changes: 31 additions & 3 deletions src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,39 @@ class AsyncWebHeader {
return _name.length();
}

static const AsyncWebHeader parse(const String &data) {
return parse(data.c_str());
}
// Result of AsyncWebHeader::tryParse()
enum class ParseStatus {
Ok, // header parsed, returned header is valid
Invalid, // malformed header line, returned header is empty
NoMemory, // allocation failure: request must be failed, not skipped
};

/** @brief Outcome of tryParse(): the status and, when Ok, the header. */
struct ParseResult;

/**
* @brief Parse a header line.
*
* 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)
* @return the parse status together with the parsed header
*/
static ParseResult tryParse(const char *data);

static const AsyncWebHeader parse(const String &data);
static const AsyncWebHeader parse(const char *data);
};

/** @brief Outcome of AsyncWebHeader::tryParse(). */
struct AsyncWebHeader::ParseResult {
ParseStatus status;
AsyncWebHeader header;
};

/*
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */
Expand Down Expand Up @@ -595,6 +622,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
27 changes: 14 additions & 13 deletions src/WebAuthentication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

#include "WebAuthentication.h"
#include "AsyncWebServerLogging.h"

#include <libb64/cencode.h>

#include <cstring>
#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 +36,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