diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp index 60fd9273..3e740a31 100644 --- a/src/AsyncEventSource.cpp +++ b/src/AsyncEventSource.cpp @@ -27,7 +27,7 @@ static String generateEventMessage(const char *message, const char *event, uint3 if (!str.reserve(len)) { async_ws_log_e("Failed to allocate"); - return emptyString; + return _emptyString; } if (reconnect) { @@ -191,7 +191,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A } AsyncEventSourceClient::~AsyncEventSourceClient() { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif @@ -200,8 +200,12 @@ AsyncEventSourceClient::~AsyncEventSourceClient() { } bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Protect message queue access (size checks and modifications) which is not thread-safe. + if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { + async_ws_log_e("Event message queue overflow: discard message"); + return false; + } std::lock_guard lock(_lockmq); #endif @@ -231,7 +235,7 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) { } bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif @@ -261,7 +265,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) { } void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif @@ -289,7 +293,7 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t } void AsyncEventSourceClient::_onPoll() { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif @@ -373,7 +377,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) { _connectcb(client); } -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif @@ -386,7 +390,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) { if (_disconnectcb) { _disconnectcb(client); } -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif for (auto i = _clients.begin(); i != _clients.end(); ++i) { @@ -402,7 +406,7 @@ void AsyncEventSource::close() { // While the whole loop is not done, the linked list is locked and so the // iterator should remain valid even when AsyncEventSource::_handleDisconnect() // is called very early -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif for (const auto &c : _clients) { @@ -421,7 +425,7 @@ void AsyncEventSource::close() { size_t AsyncEventSource::avgPacketsWaiting() const { size_t aql = 0; uint32_t nConnectedClients = 0; -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif for (const auto &c : _clients) { @@ -435,7 +439,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const { AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { AsyncEvent_SharedData_t shared_msg = std::make_shared(generateEventMessage(message, event, id, reconnect)); -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif size_t hits = 0; @@ -453,7 +457,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c } size_t AsyncEventSource::count() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_client_queue_lock); #endif size_t n_clients{0}; diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h index 4282fff3..15d39892 100644 --- a/src/AsyncEventSource.h +++ b/src/AsyncEventSource.h @@ -5,7 +5,7 @@ #include -#if defined(ESP32) || defined(LIBRETINY) +#if defined(ESP32) || defined(LIBRETINY) || defined(HOST) #include #ifdef LIBRETINY #ifdef round @@ -136,7 +136,7 @@ class AsyncEventSourceClient { size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer std::list _messageQueue; -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) mutable std::recursive_mutex _lockmq; #endif bool _queueMessage(const char *message, size_t len); @@ -205,7 +205,7 @@ class AsyncEventSourceClient { return _lastId; } size_t packetsWaiting() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lockmq); #endif return _messageQueue.size(); @@ -245,7 +245,7 @@ class AsyncEventSource : public AsyncWebHandler { private: String _url; std::list> _clients; -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) // Same as for individual messages, protect mutations of _clients list // since simultaneous access from different tasks is possible mutable std::recursive_mutex _client_queue_lock; @@ -331,11 +331,11 @@ class AsyncEventSourceResponse : public AsyncWebServerResponse { public: AsyncEventSourceResponse(AsyncEventSource *server); - void _respond(AsyncWebServerRequest *request); + void _respond(AsyncWebServerRequest *request) override; size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override { return 0; }; - bool _sourceValid() const { + bool _sourceValid() const override { return true; } }; diff --git a/src/AsyncWebServerLogging.h b/src/AsyncWebServerLogging.h index f550fd9e..daef2594 100644 --- a/src/AsyncWebServerLogging.h +++ b/src/AsyncWebServerLogging.h @@ -31,6 +31,13 @@ #define async_ws_log_d(format, ...) log_d(format, ##__VA_ARGS__) #define async_ws_log_v(format, ...) log_v(format, ##__VA_ARGS__) +#elif defined(HOST) +#define async_ws_log_e(format, ...) +#define async_ws_log_w(format, ...) +#define async_ws_log_i(format, ...) +#define async_ws_log_d(format, ...) +#define async_ws_log_v(format, ...) + /** * Raspberry Pi Pico specific configurations */ diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp index 1f6adee0..4c3fc78c 100644 --- a/src/AsyncWebSocket.cpp +++ b/src/AsyncWebSocket.cpp @@ -17,6 +17,8 @@ #include #elif defined(LIBRETINY) #include +#elif defined(HOST) +#include "BackPort_SHA1Builder.h" #endif #include @@ -24,6 +26,7 @@ #include #include #include +#include #define STATE_FRAME_START 0 #define STATE_FRAME_MASK 1 @@ -291,7 +294,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncClient *client, AsyncWebSocket * AsyncWebSocketClient::~AsyncWebSocketClient() { { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif _messageQueue.clear(); @@ -309,7 +312,7 @@ void AsyncWebSocketClient::_clearQueue() { void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { _lastMessageTime = millis(); -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::unique_lock lock(_lock); #endif @@ -324,7 +327,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { _status = WS_DISCONNECTED; async_ws_log_v("[%s][%" PRIu32 "] ACK WS_DISCONNECTED", _server->url(), _clientId); if (_client) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) /* Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock. Due to _client->close() shall call the callback function _onDisconnect() @@ -361,13 +364,13 @@ void AsyncWebSocketClient::_onPoll() { return; } -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::unique_lock lock(_lock); #endif if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) { _runQueue(); } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) lock.unlock(); #endif ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); @@ -433,21 +436,21 @@ void AsyncWebSocketClient::_runQueue() { } bool AsyncWebSocketClient::queueIsFull() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED); } size_t AsyncWebSocketClient::queueLen() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif return _messageQueue.size(); } bool AsyncWebSocketClient::canSend() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES; @@ -458,7 +461,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si return false; } -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif @@ -477,7 +480,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint return false; } -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::unique_lock lock(_lock); #endif @@ -486,7 +489,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint _status = WS_DISCONNECTED; if (_client) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) /* Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock. Due to _client->close() shall call the callback function _onDisconnect() @@ -988,7 +991,7 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient *client, AwsEventType typ } AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif _clients.emplace_back(request, this); @@ -1000,7 +1003,7 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) } void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif const auto client_id = client->id(); @@ -1013,7 +1016,7 @@ void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) { } bool AsyncWebSocket::availableForWriteAll() { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) { @@ -1022,7 +1025,7 @@ bool AsyncWebSocket::availableForWriteAll() { } bool AsyncWebSocket::availableForWrite(uint32_t id) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) { @@ -1035,7 +1038,7 @@ bool AsyncWebSocket::availableForWrite(uint32_t id) { } size_t AsyncWebSocket::count() const { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) { @@ -1044,7 +1047,7 @@ size_t AsyncWebSocket::count() const { } AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) { @@ -1064,7 +1067,7 @@ void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) { } void AsyncWebSocket::closeAll(uint16_t code, const char *message) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif for (auto &c : _clients) { @@ -1075,7 +1078,7 @@ void AsyncWebSocket::closeAll(uint16_t code, const char *message) { } void AsyncWebSocket::cleanupClients(uint16_t maxClients) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif const size_t c = count(); @@ -1098,7 +1101,7 @@ bool AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) { } AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif size_t hit = 0; @@ -1209,7 +1212,7 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * } AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif size_t hit = 0; @@ -1301,7 +1304,7 @@ AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer return status; } AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) { -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) std::lock_guard lock(_lock); #endif size_t hit = 0; diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h index ea181c59..bed17b9c 100644 --- a/src/AsyncWebSocket.h +++ b/src/AsyncWebSocket.h @@ -16,6 +16,16 @@ #ifndef WS_MAX_QUEUED_MESSAGES #define WS_MAX_QUEUED_MESSAGES 32 #endif +#elif defined(HOST) +// For compatibility with AsyncWebSocket +#ifndef FPSTR +#define FPSTR (const char *) +#endif + +#include +#ifndef WS_MAX_QUEUED_MESSAGES +#define WS_MAX_QUEUED_MESSAGES 32 +#endif #elif defined(ESP8266) #include #ifndef WS_MAX_QUEUED_MESSAGES @@ -44,7 +54,7 @@ #endif #ifndef DEFAULT_MAX_WS_CLIENTS -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) #define DEFAULT_MAX_WS_CLIENTS 8 #else #define DEFAULT_MAX_WS_CLIENTS 4 @@ -222,7 +232,7 @@ class AsyncWebSocketClient { uint8_t _pstate; uint32_t _lastMessageTime; uint32_t _keepAlivePeriod; -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) mutable std::recursive_mutex _lock; #endif std::deque _controlQueue; @@ -372,7 +382,7 @@ class AsyncWebSocket : public AsyncWebHandler { AwsEventHandler _eventHandler; AwsHandshakeHandler _handshakeHandler; bool _enabled; -#ifdef ESP32 +#if defined(ESP32) || defined(HOST) mutable std::recursive_mutex _lock; #endif @@ -488,11 +498,11 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse { public: AsyncWebSocketResponse(const String &key, AsyncWebSocket *server); - void _respond(AsyncWebServerRequest *request); + void _respond(AsyncWebServerRequest *request) override ; size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override { return 0; }; - bool _sourceValid() const { + bool _sourceValid() const override { return true; } }; diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index f7e222d1..d11b6e02 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -5,7 +5,9 @@ #include #include +#ifndef HOST #include +#endif #include #include @@ -17,6 +19,10 @@ #include #include +#ifndef __unused +#define __unused __attribute__((unused)) +#endif + #if __has_include("ArduinoJson.h") #include @@ -37,6 +43,9 @@ #if defined(ESP32) || defined(LIBRETINY) #include #include +#elif defined(HOST) +#include +#include #elif defined(ESP8266) #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) @@ -374,6 +383,13 @@ typedef std::function AwsTemplateProcessor; using AsyncWebServerRequestPtr = std::weak_ptr; +// The ESP32 Arduino framework defines emptyString but it is not in the +// Arduino Core API. Some other Arduino frameworks define emptyString +// for compatibility, but its implementation is cumbersome due to the +// lack of a convenient place to put the definition. For maximum +// portablity, we just make our own renamed version. +extern const String _emptyString; + class AsyncWebServerRequest { using File = fs::File; using FS = fs::FS; @@ -671,14 +687,14 @@ class AsyncWebServerRequest { AsyncWebServerResponse * beginResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse * - beginResponse(FS &fs, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { + beginResponse(FS &fs, const String &path, const String &contentType = _emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); } AsyncWebServerResponse * beginResponse(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse * - beginResponse(File content, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { + beginResponse(File content, const String &path, const String &contentType = _emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); } @@ -783,11 +799,11 @@ class AsyncWebServerRequest { #endif const String &arg(size_t i) const; // get request argument value by number const String &arg(int i) const { - return i < 0 ? emptyString : arg((size_t)i); + return i < 0 ? _emptyString : arg((size_t)i); }; const String &argName(size_t i) const; // get request argument name by number const String &argName(int i) const { - return i < 0 ? emptyString : argName((size_t)i); + return i < 0 ? _emptyString : argName((size_t)i); }; bool hasArg(const char *name) const; // check if argument exists bool hasArg(const String &name) const { @@ -800,14 +816,14 @@ class AsyncWebServerRequest { #ifdef ASYNCWEBSERVER_REGEX const String &pathArg(size_t i) const { if (i >= _pathParams.size()) { - return emptyString; + return _emptyString; } auto it = _pathParams.begin(); std::advance(it, i); return *it; } const String &pathArg(int i) const { - return i < 0 ? emptyString : pathArg((size_t)i); + return i < 0 ? _emptyString : pathArg((size_t)i); } #else const String &pathArg(size_t i) const __attribute__((error("ERR: pathArg() requires -D ASYNCWEBSERVER_REGEX and only works on regex handlers"))); @@ -826,11 +842,11 @@ class AsyncWebServerRequest { const String &header(size_t i) const; // get request header value by number const String &header(int i) const { - return i < 0 ? emptyString : header((size_t)i); + return i < 0 ? _emptyString : header((size_t)i); }; const String &headerName(size_t i) const; // get request header name by number const String &headerName(int i) const { - return i < 0 ? emptyString : headerName((size_t)i); + return i < 0 ? _emptyString : headerName((size_t)i); }; size_t headers() const; // get header count @@ -888,7 +904,7 @@ class AsyncWebServerRequest { _attributes[name] = value; } void setAttribute(const char *name, bool value) { - _attributes[name] = value ? "1" : emptyString; + _attributes[name] = value ? "1" : _emptyString; } void setAttribute(const char *name, long value) { _attributes[name] = String(value); @@ -904,7 +920,7 @@ class AsyncWebServerRequest { return _attributes.find(name) != _attributes.end(); } - const String &getAttribute(const char *name, const String &defaultValue = emptyString) const; + const String &getAttribute(const char *name, const String &defaultValue = _emptyString) const; bool getAttribute(const char *name, bool defaultValue) const; long getAttribute(const char *name, long defaultValue) const; float getAttribute(const char *name, float defaultValue) const; diff --git a/src/WebAuthentication.cpp b/src/WebAuthentication.cpp index 64509e07..d029f50d 100644 --- a/src/WebAuthentication.cpp +++ b/src/WebAuthentication.cpp @@ -3,9 +3,11 @@ #include "WebAuthentication.h" #include "AsyncWebServerLogging.h" +#include "ESPAsyncWebServer.h" #include -#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) + +#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(HOST) #include #else #include @@ -26,19 +28,19 @@ bool checkBasicAuthentication(const char *hash, const char *username, const char String generateBasicHash(const char *username, const char *password) { if (username == NULL || password == NULL) { - return emptyString; + return _emptyString; } size_t toencodeLen = strlen(username) + strlen(password) + 1; char *toencode = new char[toencodeLen + 1]; if (toencode == NULL) { - return emptyString; + return _emptyString; } char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1]; if (encoded == NULL) { delete[] toencode; - return emptyString; + return _emptyString; } sprintf_P(toencode, PSTR("%s:%s"), username, password); if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) { @@ -49,11 +51,11 @@ String generateBasicHash(const char *username, const char *password) { } delete[] toencode; delete[] encoded; - return emptyString; + return _emptyString; } static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more -#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(HOST) MD5Builder md5; md5.begin(); md5.add(data, len); @@ -90,7 +92,7 @@ String genRandomMD5() { char *out = (char *)malloc(33); if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) { async_ws_log_e("Failed to allocate"); - return emptyString; + return _emptyString; } String res = String(out); free(out); @@ -101,7 +103,7 @@ static String stringMD5(const String &in) { char *out = (char *)malloc(33); if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) { async_ws_log_e("Failed to allocate"); - return emptyString; + return _emptyString; } String res = String(out); free(out); @@ -110,19 +112,19 @@ static String stringMD5(const String &in) { String generateDigestHash(const char *username, const char *password, const char *realm) { if (username == NULL || password == NULL || realm == NULL) { - return emptyString; + return _emptyString; } char *out = (char *)malloc(33); if (out == NULL) { async_ws_log_e("Failed to allocate"); - return emptyString; + return _emptyString; } String in; if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) { async_ws_log_e("Failed to allocate"); free(out); - return emptyString; + return _emptyString; } in.concat(username); @@ -134,7 +136,7 @@ String generateDigestHash(const char *username, const char *password, const char if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) { async_ws_log_e("Failed to allocate"); free(out); - return emptyString; + return _emptyString; } in = String(out); diff --git a/src/WebAuthentication.h b/src/WebAuthentication.h index a4819d60..4712ed58 100644 --- a/src/WebAuthentication.h +++ b/src/WebAuthentication.h @@ -5,6 +5,8 @@ #include "Arduino.h" +using namespace arduino; + bool checkBasicAuthentication(const char *header, const char *username, const char *password); bool checkDigestAuthentication( diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index 698ab006..f67e8c38 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -260,7 +260,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { request->_tempFile.close(); response = new AsyncBasicResponse(304); // Not modified } else { - response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback); + response = new AsyncFileResponse(request->_tempFile, filename, _emptyString, false, _callback); } if (!response) { diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 87593bbd..e148d7ae 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -298,7 +298,7 @@ void AsyncWebServerRequest::_addGetParams(const String ¶ms) { equal = end; } String name = urlDecode(params.substring(start, equal)); - String value = urlDecode(equal + 1 < end ? params.substring(equal + 1, end) : emptyString); + String value = urlDecode(equal + 1 < end ? params.substring(equal + 1, end) : _emptyString); if (name.length()) { _params.emplace_back(name, value); } @@ -336,7 +336,7 @@ bool AsyncWebServerRequest::_parseReqHead() { _version = 1; } - _temp = emptyString; + _temp = _emptyString; return true; } @@ -543,9 +543,10 @@ bool AsyncWebServerRequest::_parseReqHeader() { } _headers.emplace_back(std::move(header)); } -#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) +#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) || defined(HOST) + // Ancient PRI core does not have String::clear() method 8-() - _temp = emptyString; + _temp = _emptyString; #else _temp.clear(); #endif @@ -568,9 +569,9 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { _params.emplace_back(name, urlDecode(value), true); } -#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) +#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) || defined(HOST) // Ancient PRI core does not have String::clear() method 8-() - _temp = emptyString; + _temp = _emptyString; #else _temp.clear(); #endif @@ -615,10 +616,10 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { if (!_parsedLength) { _multiParseState = EXPECT_BOUNDARY; - _temp = emptyString; - _itemName = emptyString; - _itemFilename = emptyString; - _itemType = emptyString; + _temp = _emptyString; + _itemName = _emptyString; + _itemFilename = _emptyString; + _itemType = _emptyString; } if (_multiParseState == WAIT_FOR_RETURN1) { @@ -686,13 +687,13 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { _params.emplace_back(T_filename, _itemFilename, true, true); } } - _temp = emptyString; + _temp = _emptyString; } else { _multiParseState = WAIT_FOR_RETURN1; // value starts from here _itemSize = 0; _itemStartIndex = _parsedLength; - _itemValue = emptyString; + _itemValue = _emptyString; if (_itemIsFile) { if (_itemBuffer) { free(_itemBuffer); @@ -1219,7 +1220,7 @@ const String &AsyncWebServerRequest::arg(const char *name) const { return arg.value(); } } - return emptyString; + return _emptyString; } #ifdef ESP8266 @@ -1238,7 +1239,7 @@ const String &AsyncWebServerRequest::argName(size_t i) const { const String &AsyncWebServerRequest::header(const char *name) const { const AsyncWebHeader *h = getHeader(name); - return h ? h->value() : emptyString; + return h ? h->value() : _emptyString; } #ifdef ESP8266 @@ -1249,12 +1250,12 @@ const String &AsyncWebServerRequest::header(const __FlashStringHelper *data) con const String &AsyncWebServerRequest::header(size_t i) const { const AsyncWebHeader *h = getHeader(i); - return h ? h->value() : emptyString; + return h ? h->value() : _emptyString; } const String &AsyncWebServerRequest::headerName(size_t i) const { const AsyncWebHeader *h = getHeader(i); - return h ? h->name() : emptyString; + return h ? h->name() : _emptyString; } String AsyncWebServerRequest::urlDecode(const String &text) const { @@ -1265,7 +1266,7 @@ String AsyncWebServerRequest::urlDecode(const String &text) const { // Allocate the string internal buffer - never longer from source text if (!decoded.reserve(len)) { async_ws_log_e("Failed to allocate"); - return emptyString; + return _emptyString; } while (i < len) { char decodedChar; diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index 747d4a2b..18eef586 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -37,7 +37,7 @@ class AsyncBasicResponse : public AsyncWebServerResponse { public: explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty); - AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString) + AsyncBasicResponse(int code, const String &contentType, const String &content = _emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {} void _respond(AsyncWebServerRequest *request) final; size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) final { @@ -82,6 +82,8 @@ class AsyncAbstractResponse : public AsyncWebServerResponse { std::unique_ptr > _send_buffer; // buffer data size specifiers size_t _send_buffer_offset{0}, _send_buffer_len{0}; + // track if final chunk terminator has been queued for chunked responses + bool _finalChunkQueued{false}; size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len); size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen); @@ -216,6 +218,10 @@ class AsyncResponseStream : public AsyncAbstractResponse, public Print { return (_state < RESPONSE_END); } size_t _fillBuffer(uint8_t *buf, size_t maxLen) final; +#ifdef ARDUINO_API_VERSION +// ArduinoCore-API does not have Print::printf + size_t printf(const char *format, ...); +#endif size_t write(const uint8_t *data, size_t len); size_t write(uint8_t data); /** diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index e520ea10..f501780f 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -340,6 +340,7 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) { addHeader(T_Connection, T_close, false); _assembleHead(_assembled_headers, request->version()); _state = RESPONSE_HEADERS; + _finalChunkQueued = false; // Reset for new response write_send_buffs(request, 0, 0); } @@ -447,6 +448,11 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s } if (_chunked) { + if (_finalChunkQueued) { + // Final chunk terminator was queued and add()ed to TCP buffer; stop reading content + _state = RESPONSE_WAIT_ACK; + break; + } // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. // See https://datatracker.ietf.org/doc/html/rfc9112#section-7.1 size_t const readLen = @@ -466,8 +472,9 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s _send_buffer_len += readLen + 8; // set buffers's size to match added data _sentLength += readLen; // data is not sent yet, but we won't get a chance to count this later properly for chunked data if (!readLen) { - // last chunk? - _state = RESPONSE_END; + // This is the final chunk terminator ("0\r\n\r\n") + // Don't break - let loop continue so add() pushes it to TCP buffer next iteration + _finalChunkQueued = true; } } } else { @@ -483,13 +490,13 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s if (readLen == 0) { // no more data to send - _state = RESPONSE_END; + _state = RESPONSE_WAIT_ACK; // Wait for ACK instead of ending immediately } else if (readLen != RESPONSE_TRY_AGAIN) { _send_buffer_len += readLen; // set buffers's size to match added data _sentLength += readLen; // data is not sent yet, but we need it to understand that it would be last block if (_sendContentLength && (_sentLength == _contentLength)) { // it was last piece of content - _state = RESPONSE_END; + _state = RESPONSE_WAIT_ACK; // Wait for ACKs to be processed before ending } } } @@ -499,8 +506,10 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s request->client()->send(); _writtenLength += payloadlen; #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT - _in_flight += payloadlen; - --_in_flight_credit; // take a credit + if (payloadlen > 0) { + _in_flight += payloadlen; + --_in_flight_credit; // take a credit only when data is actually in-flight + } #endif if (_send_buffer_len == 0) { // buffer empty, we can release mem, otherwise need to keep it till next run (should not happen under normal conditions) @@ -511,9 +520,18 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s // implicit check if (_state == RESPONSE_WAIT_ACK) { - // we do not need to wait for any acks actually if we won't send any more data, - // connection would be closed gracefully with last piece of data (in AsyncWebServerRequest::_onAck) - _state = RESPONSE_END; + // Only transition to END once all data has been acknowledged + if (_sendContentLength) { + // For fixed-length responses, make sure all content has been ACK'd + if (_ackedLength >= _contentLength) { + _state = RESPONSE_END; + } + } else { + // For streaming/chunked responses, once all buffered data is gone and sent, we're done + if (_send_buffer_len == 0) { + _state = RESPONSE_END; + } + } } return 0; } @@ -923,6 +941,39 @@ size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) { return _content->read((char *)buf, maxLen); } +#ifdef ARDUINO_API_VERSION +// ArduinoCore-API does not have Print::printf +#include +size_t AsyncResponseStream::printf(const char *format, ...) { + char loc_buf[64]; + char *temp = loc_buf; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + int len = vsnprintf(temp, sizeof(loc_buf), format, copy); + va_end(copy); + if (len < 0) { + va_end(arg); + return 0; + } + if (len >= (int)sizeof(loc_buf)) { // comparation of same sign type for the compiler + temp = (char *)malloc(len + 1); + if (temp == NULL) { + va_end(arg); + return 0; + } + len = vsnprintf(temp, len + 1, format, arg); + } + va_end(arg); + len = write((uint8_t *)temp, len); + if (temp != loc_buf) { + free(temp); + } + return len; +} +#endif + size_t AsyncResponseStream::write(const uint8_t *data, size_t len) { if (_started()) { return 0; diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 15fa701a..4108f130 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -7,7 +7,7 @@ #include #include -#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) +#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY) || defined(HOST) #include #elif defined(ESP8266) #include @@ -343,3 +343,5 @@ bool AsyncURIMatcher::matches(AsyncWebServerRequest *request) const { return false; } } + +const String _emptyString("");