+#endif
+#include "ESPAsyncWebServer.h"
+
+DNSServer dnsServer;
+AsyncWebServer server(80);
+
+class CaptiveRequestHandler : public AsyncWebHandler {
+ public:
+ CaptiveRequestHandler() {}
+ virtual ~CaptiveRequestHandler() {}
+
+ bool canHandle(__unused AsyncWebServerRequest* request) {
+ // request->addInterestingHeader("ANY");
+ return true;
+ }
+
+ void handleRequest(AsyncWebServerRequest* request) {
+ AsyncResponseStream* response = request->beginResponseStream("text/html");
+ response->print("Captive Portal");
+ response->print("This is out captive portal front page.
");
+ response->printf("You were trying to reach: http://%s%s
", request->host().c_str(), request->url().c_str());
+ response->printf("Try opening this link instead
", WiFi.softAPIP().toString().c_str());
+ response->print("");
+ request->send(response);
+ }
+};
+
+bool hit1 = false;
+bool hit2 = false;
+
+void setup() {
+ Serial.begin(115200);
+
+ server
+ .on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ Serial.println("Captive portal request...");
+ Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
+ Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
+#if ESP_IDF_VERSION_MAJOR >= 5
+ Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
+ Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
+#endif
+ Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
+ Serial.println(WiFi.localIP() == request->client()->localIP());
+ Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
+ request->send(200, "text/plain", "This is the captive portal");
+ hit1 = true;
+ })
+ .setFilter(ON_AP_FILTER);
+
+ server
+ .on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ Serial.println("Website request...");
+ Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
+ Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
+#if ESP_IDF_VERSION_MAJOR >= 5
+ Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
+ Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
+#endif
+ Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
+ Serial.println(WiFi.localIP() == request->client()->localIP());
+ Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
+ request->send(200, "text/plain", "This is the website");
+ hit2 = true;
+ })
+ .setFilter(ON_STA_FILTER);
+
+ // assert(WiFi.softAP("esp-captive-portal"));
+ // dnsServer.start(53, "*", WiFi.softAPIP());
+ // server.begin();
+ // Serial.println("Captive portal started!");
+
+ // while (!hit1) {
+ // dnsServer.processNextRequest();
+ // yield();
+ // }
+ // delay(1000); // Wait for the client to process the response
+
+ // Serial.println("Captive portal opened, stopping it and connecting to WiFi...");
+ // dnsServer.stop();
+ // WiFi.softAPdisconnect();
+
+ WiFi.persistent(false);
+ WiFi.begin("IoT");
+ while (WiFi.status() != WL_CONNECTED) {
+ delay(500);
+ }
+ Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString());
+ server.begin();
+
+ // while (!hit2) {
+ // delay(10);
+ // }
+ // delay(1000); // Wait for the client to process the response
+ // ESP.restart();
+}
+
+void loop() {
+}
diff --git a/lib/ESP Async WebServer/examples/SimpleServer/SimpleServer.ino b/lib/ESP Async WebServer/examples/SimpleServer/SimpleServer.ino
new file mode 100644
index 0000000..2472674
--- /dev/null
+++ b/lib/ESP Async WebServer/examples/SimpleServer/SimpleServer.ino
@@ -0,0 +1,77 @@
+//
+// A simple server implementation showing how to:
+// * serve static messages
+// * read GET and POST parameters
+// * handle missing pages / 404s
+//
+
+#include
+#ifdef ESP32
+ #include
+ #include
+#elif defined(ESP8266)
+ #include
+ #include
+#elif defined(TARGET_RP2040)
+ #include
+ #include
+#endif
+#include
+
+AsyncWebServer server(80);
+
+const char* ssid = "YOUR_SSID";
+const char* password = "YOUR_PASSWORD";
+
+const char* PARAM_MESSAGE = "message";
+
+void notFound(AsyncWebServerRequest* request) {
+ request->send(404, "text/plain", "Not found");
+}
+
+void setup() {
+
+ Serial.begin(115200);
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid, password);
+ if (WiFi.waitForConnectResult() != WL_CONNECTED) {
+ Serial.printf("WiFi Failed!\n");
+ return;
+ }
+
+ Serial.print("IP Address: ");
+ Serial.println(WiFi.localIP());
+
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world");
+ });
+
+ // Send a GET request to /get?message=
+ server.on("/get", HTTP_GET, [](AsyncWebServerRequest* request) {
+ String message;
+ if (request->hasParam(PARAM_MESSAGE)) {
+ message = request->getParam(PARAM_MESSAGE)->value();
+ } else {
+ message = "No message sent";
+ }
+ request->send(200, "text/plain", "Hello, GET: " + message);
+ });
+
+ // Send a POST request to /post with a form field message set to
+ server.on("/post", HTTP_POST, [](AsyncWebServerRequest* request) {
+ String message;
+ if (request->hasParam(PARAM_MESSAGE, true)) {
+ message = request->getParam(PARAM_MESSAGE, true)->value();
+ } else {
+ message = "No message sent";
+ }
+ request->send(200, "text/plain", "Hello, POST: " + message);
+ });
+
+ server.onNotFound(notFound);
+
+ server.begin();
+}
+
+void loop() {
+}
\ No newline at end of file
diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamConcat.h b/lib/ESP Async WebServer/examples/StreamFiles/StreamConcat.h
new file mode 100644
index 0000000..c1e1927
--- /dev/null
+++ b/lib/ESP Async WebServer/examples/StreamFiles/StreamConcat.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include
+
+class StreamConcat : public Stream {
+ public:
+ StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {}
+
+ size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; }
+ size_t write(__unused uint8_t c) override { return 0; }
+ void flush() override {}
+
+ int available() override { return _s1->available() + _s2->available(); }
+
+ int read() override {
+ int c = _s1->read();
+ return c != -1 ? c : _s2->read();
+ }
+
+#if defined(TARGET_RP2040)
+ size_t readBytes(char* buffer, size_t length) {
+#else
+ size_t readBytes(char* buffer, size_t length) override {
+#endif
+ size_t count = _s1->readBytes(buffer, length);
+ return count > 0 ? count : _s2->readBytes(buffer, length);
+ }
+
+ int peek() override {
+ int c = _s1->peek();
+ return c != -1 ? c : _s2->peek();
+ }
+
+ private:
+ Stream* _s1;
+ Stream* _s2;
+};
diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamFiles.ino b/lib/ESP Async WebServer/examples/StreamFiles/StreamFiles.ino
new file mode 100644
index 0000000..2a2c1b6
--- /dev/null
+++ b/lib/ESP Async WebServer/examples/StreamFiles/StreamFiles.ino
@@ -0,0 +1,84 @@
+#include
+#include
+#ifdef ESP32
+ #include
+ #include
+#elif defined(ESP8266)
+ #include
+ #include
+#elif defined(TARGET_RP2040)
+ #include
+ #include
+#endif
+#include "StreamConcat.h"
+#include "StreamString.h"
+#include
+#include
+
+DNSServer dnsServer;
+AsyncWebServer server(80);
+
+void setup() {
+ Serial.begin(115200);
+
+ LittleFS.begin();
+
+ WiFi.mode(WIFI_AP);
+ WiFi.softAP("esp-captive");
+ dnsServer.start(53, "*", WiFi.softAPIP());
+
+ File file1 = LittleFS.open("/header.html", "w");
+ file1.print("ESP Captive Portal");
+ file1.close();
+
+ File file2 = LittleFS.open("/body.html", "w");
+ file2.print("Welcome to ESP Captive Portal
");
+ file2.close();
+
+ File file3 = LittleFS.open("/footer.html", "w");
+ file3.print("");
+ file3.close();
+
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ File header = LittleFS.open("/header.html", "r");
+ File body = LittleFS.open("/body.html", "r");
+ StreamConcat stream1(&header, &body);
+
+ StreamString content;
+#if defined(TARGET_RP2040)
+ content.printf("FreeHeap: %d", rp2040.getFreeHeap());
+#else
+ content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
+#endif
+ StreamConcat stream2 = StreamConcat(&stream1, &content);
+
+ File footer = LittleFS.open("/footer.html", "r");
+ StreamConcat stream3 = StreamConcat(&stream2, &footer);
+
+ request->send(stream3, "text/html", stream3.available());
+ header.close();
+ body.close();
+ footer.close();
+ });
+
+ server.onNotFound([](AsyncWebServerRequest* request) {
+ request->send(404, "text/plain", "Not found");
+ });
+
+ server.begin();
+}
+
+uint32_t last = 0;
+
+void loop() {
+ // dnsServer.processNextRequest();
+
+ if (millis() - last > 2000) {
+#if defined(TARGET_RP2040)
+ Serial.printf("FreeHeap: %d", rp2040.getFreeHeap());
+#else
+ Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
+#endif
+ last = millis();
+ }
+}
\ No newline at end of file
diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamString.h b/lib/ESP Async WebServer/examples/StreamFiles/StreamString.h
new file mode 100644
index 0000000..a6e0655
--- /dev/null
+++ b/lib/ESP Async WebServer/examples/StreamFiles/StreamString.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+
+class StreamString : public Stream {
+ public:
+ size_t write(const uint8_t* p, size_t n) override { return _buffer.concat(reinterpret_cast(p), n) ? n : 0; }
+ size_t write(uint8_t c) override { return _buffer.concat(static_cast(c)) ? 1 : 0; }
+ void flush() override {}
+
+ int available() override { return static_cast(_buffer.length()); }
+
+ int read() override {
+ if (_buffer.length() == 0)
+ return -1;
+ char c = _buffer[0];
+ _buffer.remove(0, 1);
+ return c;
+ }
+
+#if defined(TARGET_RP2040)
+ size_t readBytes(char* buffer, size_t length) {
+#else
+ size_t readBytes(char* buffer, size_t length) override {
+#endif
+ if (length > _buffer.length())
+ length = _buffer.length();
+ // Don't use _str.ToCharArray() because it inserts a terminator
+ memcpy(buffer, _buffer.c_str(), length);
+ _buffer.remove(0, static_cast(length));
+ return length;
+ }
+
+ int peek() override { return _buffer.length() > 0 ? _buffer[0] : -1; }
+
+ const String& buffer() const { return _buffer; }
+
+ private:
+ String _buffer;
+};
diff --git a/lib/ESP Async WebServer/library.json b/lib/ESP Async WebServer/library.json
new file mode 100644
index 0000000..4e089ba
--- /dev/null
+++ b/lib/ESP Async WebServer/library.json
@@ -0,0 +1,64 @@
+{
+ "name": "ESP Async WebServer",
+ "version": "3.0.6",
+ "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
+ "keywords": "http,async,websocket,webserver",
+ "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mathieucarbou/ESPAsyncWebServer.git"
+ },
+ "authors": [
+ {
+ "name": "Hristo Gochkov"
+ },
+ {
+ "name": "Mathieu Carbou",
+ "maintainer": true
+ }
+ ],
+ "license": "LGPL-3.0",
+ "frameworks": "arduino",
+ "platforms": [
+ "espressif32",
+ "espressif8266",
+ "raspberrypi"
+ ],
+ "dependencies": [
+ {
+ "owner": "mathieucarbou",
+ "name": "Async TCP",
+ "version": "^3.1.4",
+ "platforms": "espressif32"
+ },
+ {
+ "owner": "esphome",
+ "name": "ESPAsyncTCP-esphome",
+ "version": "^2.0.0",
+ "platforms": "espressif8266"
+ },
+ {
+ "name": "Hash",
+ "platforms": "espressif8266"
+ },
+ {
+ "owner": "khoih-prog",
+ "name": "AsyncTCP_RP2040W",
+ "version": "^1.2.0",
+ "platforms": "raspberrypi"
+ }
+ ],
+ "export": {
+ "include": [
+ "examples",
+ "src",
+ "library.json",
+ "library.properties",
+ "LICENSE",
+ "README.md"
+ ]
+ },
+ "build": {
+ "libCompatMode": "strict"
+ }
+}
\ No newline at end of file
diff --git a/lib/ESP Async WebServer/library.properties b/lib/ESP Async WebServer/library.properties
new file mode 100644
index 0000000..e0265c3
--- /dev/null
+++ b/lib/ESP Async WebServer/library.properties
@@ -0,0 +1,10 @@
+name=ESP Async WebServer
+version=3.0.6
+author=Me-No-Dev
+maintainer=Mathieu Carbou
+sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
+paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc
+category=Other
+url=https://github.com/mathieucarbou/ESPAsyncWebServer
+architectures=*
+license=LGPL-3.0
diff --git a/lib/ESP Async WebServer/src/AsyncEventSource.cpp b/lib/ESP Async WebServer/src/AsyncEventSource.cpp
new file mode 100644
index 0000000..4cafdae
--- /dev/null
+++ b/lib/ESP Async WebServer/src/AsyncEventSource.cpp
@@ -0,0 +1,402 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "Arduino.h"
+#if defined(ESP32)
+ #include
+#endif
+#include "AsyncEventSource.h"
+
+static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ String ev;
+
+ if (reconnect) {
+ ev += F("retry: ");
+ ev += reconnect;
+ ev += F("\r\n");
+ }
+
+ if (id) {
+ ev += F("id: ");
+ ev += String(id);
+ ev += F("\r\n");
+ }
+
+ if (event != NULL) {
+ ev += F("event: ");
+ ev += String(event);
+ ev += F("\r\n");
+ }
+
+ if (message != NULL) {
+ size_t messageLen = strlen(message);
+ char* lineStart = (char*)message;
+ char* lineEnd;
+ do {
+ char* nextN = strchr(lineStart, '\n');
+ char* nextR = strchr(lineStart, '\r');
+ if (nextN == NULL && nextR == NULL) {
+ size_t llen = ((char*)message + messageLen) - lineStart;
+ char* ldata = (char*)malloc(llen + 1);
+ if (ldata != NULL) {
+ memcpy(ldata, lineStart, llen);
+ ldata[llen] = 0;
+ ev += F("data: ");
+ ev += ldata;
+ ev += F("\r\n\r\n");
+ free(ldata);
+ }
+ lineStart = (char*)message + messageLen;
+ } else {
+ char* nextLine = NULL;
+ if (nextN != NULL && nextR != NULL) {
+ if (nextR < nextN) {
+ lineEnd = nextR;
+ if (nextN == (nextR + 1))
+ nextLine = nextN + 1;
+ else
+ nextLine = nextR + 1;
+ } else {
+ lineEnd = nextN;
+ if (nextR == (nextN + 1))
+ nextLine = nextR + 1;
+ else
+ nextLine = nextN + 1;
+ }
+ } else if (nextN != NULL) {
+ lineEnd = nextN;
+ nextLine = nextN + 1;
+ } else {
+ lineEnd = nextR;
+ nextLine = nextR + 1;
+ }
+
+ size_t llen = lineEnd - lineStart;
+ char* ldata = (char*)malloc(llen + 1);
+ if (ldata != NULL) {
+ memcpy(ldata, lineStart, llen);
+ ldata[llen] = 0;
+ ev += F("data: ");
+ ev += ldata;
+ ev += F("\r\n");
+ free(ldata);
+ }
+ lineStart = nextLine;
+ if (lineStart == ((char*)message + messageLen))
+ ev += F("\r\n");
+ }
+ } while (lineStart < ((char*)message + messageLen));
+ }
+
+ return ev;
+}
+
+// Message
+
+AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len)
+ : _data(nullptr), _len(len), _sent(0), _acked(0) {
+ _data = (uint8_t*)malloc(_len + 1);
+ if (_data == nullptr) {
+ _len = 0;
+ } else {
+ memcpy(_data, data, len);
+ _data[_len] = 0;
+ }
+}
+
+AsyncEventSourceMessage::~AsyncEventSourceMessage() {
+ if (_data != NULL)
+ free(_data);
+}
+
+size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
+ (void)time;
+ // If the whole message is now acked...
+ if (_acked + len > _len) {
+ // Return the number of extra bytes acked (they will be carried on to the next message)
+ const size_t extra = _acked + len - _len;
+ _acked = _len;
+ return extra;
+ }
+ // Return that no extra bytes left.
+ _acked += len;
+ return 0;
+}
+
+// This could also return void as the return value is not used.
+// Leaving as-is for compatibility...
+size_t AsyncEventSourceMessage::send(AsyncClient* client) {
+ if (_sent >= _len) {
+ return 0;
+ }
+ const size_t len_to_send = _len - _sent;
+ auto position = reinterpret_cast(_data + _sent);
+ const size_t sent_now = client->write(position, len_to_send);
+ _sent += sent_now;
+ return sent_now;
+}
+
+// Client
+
+AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) {
+ _client = request->client();
+ _server = server;
+ _lastId = 0;
+ if (request->hasHeader(F("Last-Event-ID")))
+ _lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str());
+
+ _client->setRxTimeout(0);
+ _client->onError(NULL, NULL);
+ _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
+ _client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
+ _client->onData(NULL, NULL);
+ _client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
+ _client->onDisconnect([this](void* r, AsyncClient* c) { ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
+
+ _server->_addClient(this);
+ delete request;
+}
+
+AsyncEventSourceClient::~AsyncEventSourceClient() {
+#ifdef ESP32
+ std::lock_guard lock(_lockmq);
+#endif
+ _messageQueue.clear();
+ close();
+}
+
+void AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
+#ifdef ESP32
+ // length() is not thread-safe, thus acquiring the lock before this call..
+ std::lock_guard lock(_lockmq);
+#endif
+
+ if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
+#ifdef ESP8266
+ ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
+#elif defined(ESP32)
+ log_e("Too many messages queued: deleting message");
+#endif
+ return;
+ }
+
+ _messageQueue.emplace_back(message, len);
+ // runqueue trigger when new messages added
+ if (_client->canSend()) {
+ _runQueue();
+ }
+}
+
+void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard lock(_lockmq);
+#endif
+ while (len && _messageQueue.size()) {
+ len = _messageQueue.front().ack(len, time);
+ if (_messageQueue.front().finished())
+ _messageQueue.pop_front();
+ }
+ _runQueue();
+}
+
+void AsyncEventSourceClient::_onPoll() {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard lock(_lockmq);
+#endif
+ if (_messageQueue.size()) {
+ _runQueue();
+ }
+}
+
+void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
+ _client->close(true);
+}
+
+void AsyncEventSourceClient::_onDisconnect() {
+ _client = NULL;
+ _server->_handleDisconnect(this);
+}
+
+void AsyncEventSourceClient::close() {
+ if (_client != NULL)
+ _client->close();
+}
+
+void AsyncEventSourceClient::write(const char* message, size_t len) {
+ if (!connected())
+ return;
+ _queueMessage(message, len);
+}
+
+void AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ if (!connected())
+ return;
+ String ev = generateEventMessage(message, event, id, reconnect);
+ _queueMessage(ev.c_str(), ev.length());
+}
+
+size_t AsyncEventSourceClient::packetsWaiting() const {
+#ifdef ESP32
+ std::lock_guard lock(_lockmq);
+#endif
+ return _messageQueue.size();
+}
+
+void AsyncEventSourceClient::_runQueue() {
+ // Calls to this private method now already protected by _lockmq acquisition
+ // so no extra call of _lockmq.lock() here..
+ for (auto& i : _messageQueue) {
+ if (!i.sent())
+ i.send(_client);
+ }
+}
+
+// Handler
+void AsyncEventSource::onConnect(ArEventHandlerFunction cb) {
+ _connectcb = cb;
+}
+
+void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
+ _authorizeConnectHandler = cb;
+}
+
+void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
+ if (!client)
+ return;
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ _clients.emplace_back(client);
+ if (_connectcb)
+ _connectcb(client);
+}
+
+void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ for (auto i = _clients.begin(); i != _clients.end(); ++i) {
+ if (i->get() == client)
+ _clients.erase(i);
+ }
+}
+
+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
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ for (const auto& c : _clients) {
+ if (c->connected())
+ c->close();
+ }
+}
+
+// pmb fix
+size_t AsyncEventSource::avgPacketsWaiting() const {
+ size_t aql = 0;
+ uint32_t nConnectedClients = 0;
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ if (!_clients.size())
+ return 0;
+
+ for (const auto& c : _clients) {
+ if (c->connected()) {
+ aql += c->packetsWaiting();
+ ++nConnectedClients;
+ }
+ }
+ return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
+}
+
+void AsyncEventSource::send(
+ const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ String ev = generateEventMessage(message, event, id, reconnect);
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ for (const auto& c : _clients) {
+ if (c->connected()) {
+ c->write(ev.c_str(), ev.length());
+ }
+ }
+}
+
+size_t AsyncEventSource::count() const {
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ size_t n_clients{0};
+ for (const auto& i : _clients)
+ if (i->connected())
+ ++n_clients;
+
+ return n_clients;
+}
+
+bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) {
+ if (request->method() != HTTP_GET || !request->url().equals(_url)) {
+ return false;
+ }
+ request->addInterestingHeader(F("Last-Event-ID"));
+ request->addInterestingHeader("Cookie");
+ return true;
+}
+
+void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
+ if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
+ return request->requestAuthentication();
+ }
+ if (_authorizeConnectHandler != NULL) {
+ if (!_authorizeConnectHandler(request)) {
+ return request->send(401);
+ }
+ }
+ request->send(new AsyncEventSourceResponse(this));
+}
+
+// Response
+
+AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
+ _server = server;
+ _code = 200;
+ _contentType = F("text/event-stream");
+ _sendContentLength = false;
+ addHeader(F("Cache-Control"), F("no-cache"));
+ addHeader(F("Connection"), F("keep-alive"));
+}
+
+void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
+ String out = _assembleHead(request->version());
+ request->client()->write(out.c_str(), _headLength);
+ _state = RESPONSE_WAIT_ACK;
+}
+
+size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
+ if (len) {
+ new AsyncEventSourceClient(request, _server);
+ }
+ return 0;
+}
diff --git a/lib/ESP Async WebServer/src/AsyncEventSource.h b/lib/ESP Async WebServer/src/AsyncEventSource.h
new file mode 100644
index 0000000..0289ebf
--- /dev/null
+++ b/lib/ESP Async WebServer/src/AsyncEventSource.h
@@ -0,0 +1,158 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef ASYNCEVENTSOURCE_H_
+#define ASYNCEVENTSOURCE_H_
+
+#include
+#include
+#ifdef ESP32
+ #include
+ #include
+ #ifndef SSE_MAX_QUEUED_MESSAGES
+ #define SSE_MAX_QUEUED_MESSAGES 32
+ #endif
+#elif defined(ESP8266)
+ #include
+ #ifndef SSE_MAX_QUEUED_MESSAGES
+ #define SSE_MAX_QUEUED_MESSAGES 8
+ #endif
+#elif defined(TARGET_RP2040)
+ #include
+ #ifndef SSE_MAX_QUEUED_MESSAGES
+ #define SSE_MAX_QUEUED_MESSAGES 32
+ #endif
+#endif
+
+#include
+
+#ifdef ESP8266
+ #include
+ #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
+ #include <../src/Hash.h>
+ #endif
+#endif
+
+#ifndef DEFAULT_MAX_SSE_CLIENTS
+ #ifdef ESP32
+ #define DEFAULT_MAX_SSE_CLIENTS 8
+ #else
+ #define DEFAULT_MAX_SSE_CLIENTS 4
+ #endif
+#endif
+
+class AsyncEventSource;
+class AsyncEventSourceResponse;
+class AsyncEventSourceClient;
+using ArEventHandlerFunction = std::function;
+using ArAuthorizeConnectHandler = std::function;
+
+class AsyncEventSourceMessage {
+ private:
+ uint8_t* _data;
+ size_t _len;
+ size_t _sent;
+ // size_t _ack;
+ size_t _acked;
+
+ public:
+ AsyncEventSourceMessage(const char* data, size_t len);
+ ~AsyncEventSourceMessage();
+ size_t ack(size_t len, uint32_t time __attribute__((unused)));
+ size_t send(AsyncClient* client);
+ bool finished() { return _acked == _len; }
+ bool sent() { return _sent == _len; }
+};
+
+class AsyncEventSourceClient {
+ private:
+ AsyncClient* _client;
+ AsyncEventSource* _server;
+ uint32_t _lastId;
+ std::list _messageQueue;
+#ifdef ESP32
+ mutable std::mutex _lockmq;
+#endif
+ void _queueMessage(const char* message, size_t len);
+ void _runQueue();
+
+ public:
+ AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
+ ~AsyncEventSourceClient();
+
+ AsyncClient* client() { return _client; }
+ void close();
+ void write(const char* message, size_t len);
+ void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
+ bool connected() const { return (_client != NULL) && _client->connected(); }
+ uint32_t lastId() const { return _lastId; }
+ size_t packetsWaiting() const;
+
+ // system callbacks (do not call)
+ void _onAck(size_t len, uint32_t time);
+ void _onPoll();
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+};
+
+class AsyncEventSource : public AsyncWebHandler {
+ private:
+ String _url;
+ std::list> _clients;
+#ifdef ESP32
+ // Same as for individual messages, protect mutations of _clients list
+ // since simultaneous access from different tasks is possible
+ mutable std::mutex _client_queue_lock;
+#endif
+ ArEventHandlerFunction _connectcb{nullptr};
+ ArAuthorizeConnectHandler _authorizeConnectHandler;
+
+ public:
+ AsyncEventSource(const String& url) : _url(url){};
+ ~AsyncEventSource() { close(); };
+
+ const char* url() const { return _url.c_str(); }
+ void close();
+ void onConnect(ArEventHandlerFunction cb);
+ void authorizeConnect(ArAuthorizeConnectHandler cb);
+ void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
+ // number of clients connected
+ size_t count() const;
+ size_t avgPacketsWaiting() const;
+
+ // system callbacks (do not call)
+ void _addClient(AsyncEventSourceClient* client);
+ void _handleDisconnect(AsyncEventSourceClient* client);
+ virtual bool canHandle(AsyncWebServerRequest* request) override final;
+ virtual void handleRequest(AsyncWebServerRequest* request) override final;
+};
+
+class AsyncEventSourceResponse : public AsyncWebServerResponse {
+ private:
+ String _content;
+ AsyncEventSource* _server;
+
+ public:
+ AsyncEventSourceResponse(AsyncEventSource* server);
+ void _respond(AsyncWebServerRequest* request);
+ size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
+ bool _sourceValid() const { return true; }
+};
+
+#endif /* ASYNCEVENTSOURCE_H_ */
diff --git a/lib/ESP Async WebServer/src/AsyncJson.h b/lib/ESP Async WebServer/src/AsyncJson.h
new file mode 100644
index 0000000..b85a938
--- /dev/null
+++ b/lib/ESP Async WebServer/src/AsyncJson.h
@@ -0,0 +1,281 @@
+// AsyncJson.h
+/*
+ Async Response to use with ArduinoJson and AsyncWebServer
+ Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
+
+ Example of callback in use
+
+ server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
+
+ AsyncJsonResponse * response = new AsyncJsonResponse();
+ JsonObject& root = response->getRoot();
+ root["key1"] = "key number one";
+ JsonObject& nested = root.createNestedObject("nested");
+ nested["key1"] = "key number one";
+
+ response->setLength();
+ request->send(response);
+ });
+
+ --------------------
+
+ Async Request to use with ArduinoJson and AsyncWebServer
+ Written by Arsène von Wyss (avonwyss)
+
+ Example
+
+ AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
+ handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
+ JsonObject& jsonObj = json.as();
+ // ...
+ });
+ server.addHandler(handler);
+
+*/
+#ifndef ASYNC_JSON_H_
+#define ASYNC_JSON_H_
+#include
+#include
+#include
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
+ #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
+ #endif
+#endif
+
+constexpr const char* JSON_MIMETYPE = "application/json";
+
+/*
+ * Json Response
+ * */
+
+class ChunkPrint : public Print {
+ private:
+ uint8_t* _destination;
+ size_t _to_skip;
+ size_t _to_write;
+ size_t _pos;
+
+ public:
+ ChunkPrint(uint8_t* destination, size_t from, size_t len)
+ : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
+ virtual ~ChunkPrint() {}
+ size_t write(uint8_t c) {
+ if (_to_skip > 0) {
+ _to_skip--;
+ return 1;
+ } else if (_to_write > 0) {
+ _to_write--;
+ _destination[_pos++] = c;
+ return 1;
+ }
+ return 0;
+ }
+ size_t write(const uint8_t* buffer, size_t size) {
+ return this->Print::write(buffer, size);
+ }
+};
+
+class AsyncJsonResponse : public AsyncAbstractResponse {
+ protected:
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ DynamicJsonBuffer _jsonBuffer;
+#elif ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument _jsonBuffer;
+#else
+ JsonDocument _jsonBuffer;
+#endif
+
+ JsonVariant _root;
+ bool _isValid;
+
+ public:
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ AsyncJsonResponse(bool isArray = false) : _isValid{false} {
+ _code = 200;
+ _contentType = JSON_MIMETYPE;
+ if (isArray)
+ _root = _jsonBuffer.createArray();
+ else
+ _root = _jsonBuffer.createObject();
+ }
+#elif ARDUINOJSON_VERSION_MAJOR == 6
+ AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
+ _code = 200;
+ _contentType = JSON_MIMETYPE;
+ if (isArray)
+ _root = _jsonBuffer.createNestedArray();
+ else
+ _root = _jsonBuffer.createNestedObject();
+ }
+#else
+ AsyncJsonResponse(bool isArray = false) : _isValid{false} {
+ _code = 200;
+ _contentType = JSON_MIMETYPE;
+ if (isArray)
+ _root = _jsonBuffer.add();
+ else
+ _root = _jsonBuffer.add();
+ }
+#endif
+
+ JsonVariant& getRoot() { return _root; }
+ bool _sourceValid() const { return _isValid; }
+ size_t setLength() {
+
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measureLength();
+#else
+ _contentLength = measureJson(_root);
+#endif
+
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+ }
+
+ size_t getSize() const { return _jsonBuffer.size(); }
+
+#if ARDUINOJSON_VERSION_MAJOR >= 6
+ bool overflowed() const { return _jsonBuffer.overflowed(); }
+#endif
+
+ size_t _fillBuffer(uint8_t* data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.printTo(dest);
+#else
+ serializeJson(_root, dest);
+#endif
+ return len;
+ }
+};
+
+class PrettyAsyncJsonResponse : public AsyncJsonResponse {
+ public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
+#else
+ PrettyAsyncJsonResponse(bool isArray = false) : AsyncJsonResponse{isArray} {}
+#endif
+ size_t setLength() {
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measurePrettyLength();
+#else
+ _contentLength = measureJsonPretty(_root);
+#endif
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+ }
+ size_t _fillBuffer(uint8_t* data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.prettyPrintTo(dest);
+#else
+ serializeJsonPretty(_root, dest);
+#endif
+ return len;
+ }
+};
+
+typedef std::function ArJsonRequestHandlerFunction;
+
+class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
+ private:
+ protected:
+ const String _uri;
+ WebRequestMethodComposite _method;
+ ArJsonRequestHandlerFunction _onRequest;
+ size_t _contentLength;
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ const size_t maxJsonBufferSize;
+#endif
+ size_t _maxContentLength;
+
+ public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
+ : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
+#else
+ AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
+ : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
+#endif
+
+ void setMethod(WebRequestMethodComposite method) { _method = method; }
+ void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
+ void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
+
+ virtual bool canHandle(AsyncWebServerRequest* request) override final {
+ if (!_onRequest)
+ return false;
+
+ WebRequestMethodComposite request_method = request->method();
+ if (!(_method & request_method))
+ return false;
+
+ if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
+ return false;
+
+ if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(JSON_MIMETYPE))
+ return false;
+
+ request->addInterestingHeader("ANY");
+ return true;
+ }
+
+ virtual void handleRequest(AsyncWebServerRequest* request) override final {
+ if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+ if (_onRequest) {
+ if (request->method() == HTTP_GET) {
+ JsonVariant json;
+ _onRequest(request, json);
+ return;
+ } else if (request->_tempObject != NULL) {
+
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ DynamicJsonBuffer jsonBuffer;
+ JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
+ if (json.success()) {
+#elif ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+#else
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+#endif
+
+ _onRequest(request, json);
+ return;
+ }
+ }
+ request->send(_contentLength > _maxContentLength ? 413 : 400);
+ } else {
+ request->send(500);
+ }
+ }
+ virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {
+ }
+ virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final {
+ if (_onRequest) {
+ _contentLength = total;
+ if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
+ request->_tempObject = malloc(total);
+ }
+ if (request->_tempObject != NULL) {
+ memcpy((uint8_t*)(request->_tempObject) + index, data, len);
+ }
+ }
+ }
+ virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; }
+};
+#endif
diff --git a/lib/ESP Async WebServer/src/AsyncWebSocket.cpp b/lib/ESP Async WebServer/src/AsyncWebSocket.cpp
new file mode 100644
index 0000000..e978afc
--- /dev/null
+++ b/lib/ESP Async WebServer/src/AsyncWebSocket.cpp
@@ -0,0 +1,1197 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "AsyncWebSocket.h"
+#include "Arduino.h"
+
+#include
+
+#include
+
+#if defined(ESP32)
+ #if ESP_IDF_VERSION_MAJOR < 5
+ #include "./port/SHA1Builder.h"
+ #else
+ #include
+ #endif
+ #include
+#elif defined(TARGET_RP2040) || defined(ESP8266)
+ #include
+#endif
+
+#define MAX_PRINTF_LEN 64
+
+size_t webSocketSendFrameWindow(AsyncClient* client) {
+ if (!client->canSend())
+ return 0;
+ size_t space = client->space();
+ if (space < 9)
+ return 0;
+ return space - 8;
+}
+
+size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool mask, uint8_t* data, size_t len) {
+ if (!client->canSend()) {
+ // Serial.println("SF 1");
+ return 0;
+ }
+ size_t space = client->space();
+ if (space < 2) {
+ // Serial.println("SF 2");
+ return 0;
+ }
+ uint8_t mbuf[4] = {0, 0, 0, 0};
+ uint8_t headLen = 2;
+ if (len && mask) {
+ headLen += 4;
+ mbuf[0] = rand() % 0xFF;
+ mbuf[1] = rand() % 0xFF;
+ mbuf[2] = rand() % 0xFF;
+ mbuf[3] = rand() % 0xFF;
+ }
+ if (len > 125)
+ headLen += 2;
+ if (space < headLen) {
+ // Serial.println("SF 2");
+ return 0;
+ }
+ space -= headLen;
+
+ if (len > space)
+ len = space;
+
+ uint8_t* buf = (uint8_t*)malloc(headLen);
+ if (buf == NULL) {
+ // os_printf("could not malloc %u bytes for frame header\n", headLen);
+ // Serial.println("SF 3");
+ return 0;
+ }
+
+ buf[0] = opcode & 0x0F;
+ if (final)
+ buf[0] |= 0x80;
+ if (len < 126)
+ buf[1] = len & 0x7F;
+ else {
+ buf[1] = 126;
+ buf[2] = (uint8_t)((len >> 8) & 0xFF);
+ buf[3] = (uint8_t)(len & 0xFF);
+ }
+ if (len && mask) {
+ buf[1] |= 0x80;
+ memcpy(buf + (headLen - 4), mbuf, 4);
+ }
+ if (client->add((const char*)buf, headLen) != headLen) {
+ // os_printf("error adding %lu header bytes\n", headLen);
+ free(buf);
+ // Serial.println("SF 4");
+ return 0;
+ }
+ free(buf);
+
+ if (len) {
+ if (len && mask) {
+ size_t i;
+ for (i = 0; i < len; i++)
+ data[i] = data[i] ^ mbuf[i % 4];
+ }
+ if (client->add((const char*)data, len) != len) {
+ // os_printf("error adding %lu data bytes\n", len);
+ // Serial.println("SF 5");
+ return 0;
+ }
+ }
+ if (!client->send()) {
+ // os_printf("error sending frame: %lu\n", headLen+len);
+ // Serial.println("SF 6");
+ return 0;
+ }
+ // Serial.println("SF");
+ return len;
+}
+
+/*
+ * AsyncWebSocketMessageBuffer
+ */
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size)
+ : _buffer(std::make_shared>(size)) {
+ if (_buffer->capacity() < size) {
+ _buffer->reserve(size);
+ } else {
+ std::memcpy(_buffer->data(), data, size);
+ }
+}
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
+ : _buffer(std::make_shared>(size)) {
+ if (_buffer->capacity() < size) {
+ _buffer->reserve(size);
+ }
+}
+
+bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
+ if (_buffer->capacity() >= size)
+ return true;
+ _buffer->reserve(size);
+ return _buffer->capacity() >= size;
+}
+
+/*
+ * Control Frame
+ */
+
+class AsyncWebSocketControl {
+ private:
+ uint8_t _opcode;
+ uint8_t* _data;
+ size_t _len;
+ bool _mask;
+ bool _finished;
+
+ public:
+ AsyncWebSocketControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false)
+ : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
+ if (data == NULL)
+ _len = 0;
+ if (_len) {
+ if (_len > 125)
+ _len = 125;
+
+ _data = (uint8_t*)malloc(_len);
+
+ if (_data == NULL)
+ _len = 0;
+ else
+ memcpy(_data, data, len);
+ } else
+ _data = NULL;
+ }
+
+ virtual ~AsyncWebSocketControl() {
+ if (_data != NULL)
+ free(_data);
+ }
+
+ virtual bool finished() const { return _finished; }
+ uint8_t opcode() { return _opcode; }
+ uint8_t len() { return _len + 2; }
+ size_t send(AsyncClient* client) {
+ _finished = true;
+ return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
+ }
+};
+
+/*
+ * AsyncWebSocketMessage Message
+ */
+
+AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer},
+ _opcode(opcode & 0x07),
+ _mask{mask},
+ _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {
+}
+
+void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
+ (void)time;
+ _acked += len;
+ if (_sent >= _WSbuffer->size() && _acked >= _ack) {
+ _status = WS_MSG_SENT;
+ }
+ // ets_printf("A: %u\n", len);
+}
+
+size_t AsyncWebSocketMessage::send(AsyncClient* client) {
+ if (_status != WS_MSG_SENDING)
+ return 0;
+ if (_acked < _ack) {
+ return 0;
+ }
+ if (_sent == _WSbuffer->size()) {
+ if (_acked == _ack)
+ _status = WS_MSG_SENT;
+ return 0;
+ }
+ if (_sent > _WSbuffer->size()) {
+ _status = WS_MSG_ERROR;
+ // ets_printf("E: %u > %u\n", _sent, _WSbuffer->length());
+ return 0;
+ }
+
+ size_t toSend = _WSbuffer->size() - _sent;
+ size_t window = webSocketSendFrameWindow(client);
+
+ if (window < toSend) {
+ toSend = window;
+ }
+
+ _sent += toSend;
+ _ack += toSend + ((toSend < 126) ? 2 : 4) + (_mask * 4);
+
+ // ets_printf("W: %u %u\n", _sent - toSend, toSend);
+
+ bool final = (_sent == _WSbuffer->size());
+ uint8_t* dPtr = (uint8_t*)(_WSbuffer->data() + (_sent - toSend));
+ uint8_t opCode = (toSend && _sent == toSend) ? _opcode : (uint8_t)WS_CONTINUATION;
+
+ size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
+ _status = WS_MSG_SENDING;
+ if (toSend && sent != toSend) {
+ // ets_printf("E: %u != %u\n", toSend, sent);
+ _sent -= (toSend - sent);
+ _ack -= (toSend - sent);
+ }
+ // ets_printf("S: %u %u\n", _sent, sent);
+ return sent;
+}
+
+/*
+ * Async WebSocket Client
+ */
+const char* AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
+const size_t AWSC_PING_PAYLOAD_LEN = 22;
+
+AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server)
+ : _tempObject(NULL) {
+ _client = request->client();
+ _server = server;
+ _clientId = _server->_getNextId();
+ _status = WS_CONNECTED;
+ _pstate = 0;
+ _lastMessageTime = millis();
+ _keepAlivePeriod = 0;
+ _client->setRxTimeout(0);
+ _client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
+ _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
+ _client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
+ _client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
+ _client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
+ _client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
+ _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0);
+ delete request;
+ memset(&_pinfo, 0, sizeof(_pinfo));
+}
+
+AsyncWebSocketClient::~AsyncWebSocketClient() {
+ {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ _messageQueue.clear();
+ _controlQueue.clear();
+ }
+ _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
+}
+
+void AsyncWebSocketClient::_clearQueue() {
+ while (!_messageQueue.empty() && _messageQueue.front().finished())
+ _messageQueue.pop_front();
+}
+
+void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
+ _lastMessageTime = millis();
+
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+
+ if (!_controlQueue.empty()) {
+ auto& head = _controlQueue.front();
+ if (head.finished()) {
+ len -= head.len();
+ if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
+ _controlQueue.pop_front();
+ _status = WS_DISCONNECTED;
+ if (_client)
+ _client->close(true);
+ return;
+ }
+ _controlQueue.pop_front();
+ }
+ }
+
+ if (len && !_messageQueue.empty()) {
+ _messageQueue.front().ack(len, time);
+ }
+
+ _clearQueue();
+
+ _runQueue();
+}
+
+void AsyncWebSocketClient::_onPoll() {
+ if (!_client)
+ return;
+
+#ifdef ESP32
+ std::unique_lock lock(_lock);
+#endif
+ if (_client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
+ _runQueue();
+ } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) {
+#ifdef ESP32
+ lock.unlock();
+#endif
+ ping((uint8_t*)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
+ }
+}
+
+void AsyncWebSocketClient::_runQueue() {
+ // all calls to this method MUST be protected by a mutex lock!
+ if (!_client)
+ return;
+
+ _clearQueue();
+
+ if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
+ _controlQueue.front().send(_client);
+ } else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
+ _messageQueue.front().send(_client);
+ }
+}
+
+bool AsyncWebSocketClient::queueIsFull() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ size_t size = _messageQueue.size();
+ ;
+ return (size >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
+}
+
+size_t AsyncWebSocketClient::queueLen() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+
+ return _messageQueue.size() + _controlQueue.size();
+}
+
+bool AsyncWebSocketClient::canSend() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
+}
+
+void AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, size_t len, bool mask) {
+ if (!_client)
+ return;
+
+ {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ _controlQueue.emplace_back(opcode, data, len, mask);
+ }
+
+ if (_client && _client->canSend())
+ _runQueue();
+}
+
+void AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
+ if (!_client || buffer->size() == 0 || _status != WS_CONNECTED)
+ return;
+
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
+ if (closeWhenFull) {
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: closing connection");
+#endif
+ _status = WS_DISCONNECTED;
+ if (_client)
+ _client->close(true);
+ } else {
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: discarding new message");
+#endif
+ }
+ return;
+ } else {
+ _messageQueue.emplace_back(buffer, opcode, mask);
+ }
+
+ if (_client && _client->canSend())
+ _runQueue();
+}
+
+void AsyncWebSocketClient::close(uint16_t code, const char* message) {
+ if (_status != WS_CONNECTED)
+ return;
+
+ if (code) {
+ uint8_t packetLen = 2;
+ if (message != NULL) {
+ size_t mlen = strlen(message);
+ if (mlen > 123)
+ mlen = 123;
+ packetLen += mlen;
+ }
+ char* buf = (char*)malloc(packetLen);
+ if (buf != NULL) {
+ buf[0] = (uint8_t)(code >> 8);
+ buf[1] = (uint8_t)(code & 0xFF);
+ if (message != NULL) {
+ memcpy(buf + 2, message, packetLen - 2);
+ }
+ _queueControl(WS_DISCONNECT, (uint8_t*)buf, packetLen);
+ free(buf);
+ return;
+ }
+ }
+ _queueControl(WS_DISCONNECT);
+}
+
+void AsyncWebSocketClient::ping(const uint8_t* data, size_t len) {
+ if (_status == WS_CONNECTED)
+ _queueControl(WS_PING, data, len);
+}
+
+void AsyncWebSocketClient::_onError(int8_t) {
+ // Serial.println("onErr");
+}
+
+void AsyncWebSocketClient::_onTimeout(uint32_t time) {
+ // Serial.println("onTime");
+ (void)time;
+ _client->close(true);
+}
+
+void AsyncWebSocketClient::_onDisconnect() {
+ // Serial.println("onDis");
+ _client = NULL;
+}
+
+void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
+ // Serial.println("onData");
+ _lastMessageTime = millis();
+ uint8_t* data = (uint8_t*)pbuf;
+ while (plen > 0) {
+ if (!_pstate) {
+ const uint8_t* fdata = data;
+ _pinfo.index = 0;
+ _pinfo.final = (fdata[0] & 0x80) != 0;
+ _pinfo.opcode = fdata[0] & 0x0F;
+ _pinfo.masked = (fdata[1] & 0x80) != 0;
+ _pinfo.len = fdata[1] & 0x7F;
+ data += 2;
+ plen -= 2;
+ if (_pinfo.len == 126) {
+ _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
+ data += 2;
+ plen -= 2;
+ } else if (_pinfo.len == 127) {
+ _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
+ data += 8;
+ plen -= 8;
+ }
+
+ if (_pinfo.masked) {
+ memcpy(_pinfo.mask, data, 4);
+ data += 4;
+ plen -= 4;
+ }
+ }
+
+ const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
+ const auto datalast = data[datalen];
+
+ if (_pinfo.masked) {
+ for (size_t i = 0; i < datalen; i++)
+ data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
+ }
+
+ if ((datalen + _pinfo.index) < _pinfo.len) {
+ _pstate = 1;
+
+ if (_pinfo.index == 0) {
+ if (_pinfo.opcode) {
+ _pinfo.message_opcode = _pinfo.opcode;
+ _pinfo.num = 0;
+ }
+ }
+ if (datalen > 0)
+ _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, (uint8_t*)data, datalen);
+
+ _pinfo.index += datalen;
+ } else if ((datalen + _pinfo.index) == _pinfo.len) {
+ _pstate = 0;
+ if (_pinfo.opcode == WS_DISCONNECT) {
+ if (datalen) {
+ uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
+ char* reasonString = (char*)(data + 2);
+ if (reasonCode > 1001) {
+ _server->_handleEvent(this, WS_EVT_ERROR, (void*)&reasonCode, (uint8_t*)reasonString, strlen(reasonString));
+ }
+ }
+ if (_status == WS_DISCONNECTING) {
+ _status = WS_DISCONNECTED;
+ _client->close(true);
+ } else {
+ _status = WS_DISCONNECTING;
+ _client->ackLater();
+ _queueControl(WS_DISCONNECT, data, datalen);
+ }
+ } else if (_pinfo.opcode == WS_PING) {
+ _queueControl(WS_PONG, data, datalen);
+ } else if (_pinfo.opcode == WS_PONG) {
+ if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
+ _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen);
+ } else if (_pinfo.opcode < 8) { // continuation or text/binary frame
+ _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, data, datalen);
+ if (_pinfo.final)
+ _pinfo.num = 0;
+ else
+ _pinfo.num += 1;
+ }
+ } else {
+ // os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
+ // what should we do?
+ break;
+ }
+
+ // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
+ if (datalen > 0)
+ data[datalen] = datalast;
+
+ data += datalen;
+ plen -= datalen;
+ }
+}
+
+size_t AsyncWebSocketClient::printf(const char* format, ...) {
+ va_list arg;
+ va_start(arg, format);
+ char* temp = new char[MAX_PRINTF_LEN];
+ if (!temp) {
+ va_end(arg);
+ return 0;
+ }
+ char* buffer = temp;
+ size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
+ va_end(arg);
+
+ if (len > (MAX_PRINTF_LEN - 1)) {
+ buffer = new char[len + 1];
+ if (!buffer) {
+ delete[] temp;
+ return 0;
+ }
+ va_start(arg, format);
+ vsnprintf(buffer, len + 1, format, arg);
+ va_end(arg);
+ }
+ text(buffer, len);
+ if (buffer != temp) {
+ delete[] buffer;
+ }
+ delete[] temp;
+ return len;
+}
+
+#ifndef ESP32
+size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
+ va_list arg;
+ va_start(arg, formatP);
+ char* temp = new char[MAX_PRINTF_LEN];
+ if (!temp) {
+ va_end(arg);
+ return 0;
+ }
+ char* buffer = temp;
+ size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
+ va_end(arg);
+
+ if (len > (MAX_PRINTF_LEN - 1)) {
+ buffer = new char[len + 1];
+ if (!buffer) {
+ delete[] temp;
+ return 0;
+ }
+ va_start(arg, formatP);
+ vsnprintf_P(buffer, len + 1, formatP, arg);
+ va_end(arg);
+ }
+ text(buffer, len);
+ if (buffer != temp) {
+ delete[] buffer;
+ }
+ delete[] temp;
+ return len;
+}
+#endif
+
+namespace {
+ AsyncWebSocketSharedBuffer makeSharedBuffer(const uint8_t* message, size_t len) {
+ auto buffer = std::make_shared>(len);
+ std::memcpy(buffer->data(), message, len);
+ return buffer;
+ }
+}
+
+void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ text(std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+
+void AsyncWebSocketClient::text(AsyncWebSocketSharedBuffer buffer) {
+ _queueMessage(buffer);
+}
+
+void AsyncWebSocketClient::text(const uint8_t* message, size_t len) {
+ text(makeSharedBuffer(message, len));
+}
+
+void AsyncWebSocketClient::text(const char* message, size_t len) {
+ text((const uint8_t*)message, len);
+}
+
+void AsyncWebSocketClient::text(const char* message) {
+ text(message, strlen(message));
+}
+
+void AsyncWebSocketClient::text(const String& message) {
+ text(message.c_str(), message.length());
+}
+
+#ifndef ESP32
+void AsyncWebSocketClient::text(const __FlashStringHelper* data) {
+ PGM_P p = reinterpret_cast(data);
+
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
+ }
+
+ char* message = (char*)malloc(n + 1);
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ text(message, n);
+ free(message);
+ }
+}
+#endif // ESP32
+
+void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ binary(std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+
+void AsyncWebSocketClient::binary(AsyncWebSocketSharedBuffer buffer) {
+ _queueMessage(buffer, WS_BINARY);
+}
+
+void AsyncWebSocketClient::binary(const uint8_t* message, size_t len) {
+ binary(makeSharedBuffer(message, len));
+}
+
+void AsyncWebSocketClient::binary(const char* message, size_t len) {
+ binary((const uint8_t*)message, len);
+}
+
+void AsyncWebSocketClient::binary(const char* message) {
+ binary(message, strlen(message));
+}
+
+void AsyncWebSocketClient::binary(const String& message) {
+ binary(message.c_str(), message.length());
+}
+
+#ifndef ESP32
+void AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) {
+ PGM_P p = reinterpret_cast(data);
+ char* message = (char*)malloc(len);
+ if (message) {
+ memcpy_P(message, p, len);
+ binary(message, len);
+ free(message);
+ }
+}
+#endif
+IPAddress AsyncWebSocketClient::remoteIP() const {
+ if (!_client)
+ return IPAddress((uint32_t)0U);
+
+ return _client->remoteIP();
+}
+
+uint16_t AsyncWebSocketClient::remotePort() const {
+ if (!_client)
+ return 0;
+
+ return _client->remotePort();
+}
+
+/*
+ * Async Web Socket - Each separate socket location
+ */
+
+void AsyncWebSocket::_handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
+ if (_eventHandler != NULL) {
+ _eventHandler(this, client, type, arg, data, len);
+ }
+}
+
+AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request) {
+ _clients.emplace_back(request, this);
+ return &_clients.back();
+}
+
+bool AsyncWebSocket::availableForWriteAll() {
+ return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); });
+}
+
+bool AsyncWebSocket::availableForWrite(uint32_t id) {
+ const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; });
+ if (iter == std::end(_clients))
+ return true;
+ return !iter->queueIsFull();
+}
+
+size_t AsyncWebSocket::count() const {
+ return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; });
+}
+
+AsyncWebSocketClient* AsyncWebSocket::client(uint32_t id) {
+ const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; });
+ if (iter == std::end(_clients))
+ return nullptr;
+
+ return &(*iter);
+}
+
+void AsyncWebSocket::close(uint32_t id, uint16_t code, const char* message) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->close(code, message);
+}
+
+void AsyncWebSocket::closeAll(uint16_t code, const char* message) {
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED)
+ c.close(code, message);
+}
+
+void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
+ if (count() > maxClients)
+ _clients.front().close();
+
+ for (auto iter = std::begin(_clients); iter != std::end(_clients);) {
+ if (iter->shouldBeDeleted())
+ iter = _clients.erase(iter);
+ else
+ iter++;
+ }
+}
+
+void AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->ping(data, len);
+}
+
+void AsyncWebSocket::pingAll(const uint8_t* data, size_t len) {
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED)
+ c.ping(data, len);
+}
+
+void AsyncWebSocket::text(uint32_t id, const uint8_t* message, size_t len) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->text(makeSharedBuffer(message, len));
+}
+void AsyncWebSocket::text(uint32_t id, const char* message, size_t len) {
+ text(id, (const uint8_t*)message, len);
+}
+void AsyncWebSocket::text(uint32_t id, const char* message) {
+ text(id, message, strlen(message));
+}
+void AsyncWebSocket::text(uint32_t id, const String& message) {
+ text(id, message.c_str(), message.length());
+}
+#ifndef ESP32
+void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) {
+ PGM_P p = reinterpret_cast(data);
+
+ size_t n = 0;
+ while (true) {
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
+ }
+
+ char* message = (char*)malloc(n + 1);
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ text(id, message, n);
+ free(message);
+ }
+}
+#endif // ESP32
+void AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ text(id, std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+void AsyncWebSocket::text(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->text(buffer);
+}
+
+void AsyncWebSocket::textAll(const uint8_t* message, size_t len) {
+ textAll(makeSharedBuffer(message, len));
+}
+void AsyncWebSocket::textAll(const char* message, size_t len) {
+ textAll((const uint8_t*)message, len);
+}
+void AsyncWebSocket::textAll(const char* message) {
+ textAll(message, strlen(message));
+}
+void AsyncWebSocket::textAll(const String& message) {
+ textAll(message.c_str(), message.length());
+}
+#ifndef ESP32
+void AsyncWebSocket::textAll(const __FlashStringHelper* data) {
+ PGM_P p = reinterpret_cast(data);
+
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
+ }
+
+ char* message = (char*)malloc(n + 1);
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ textAll(message, n);
+ free(message);
+ }
+}
+#endif // ESP32
+void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ textAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+
+void AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED)
+ c.text(buffer);
+}
+
+void AsyncWebSocket::binary(uint32_t id, const uint8_t* message, size_t len) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->binary(makeSharedBuffer(message, len));
+}
+void AsyncWebSocket::binary(uint32_t id, const char* message, size_t len) {
+ binary(id, (const uint8_t*)message, len);
+}
+void AsyncWebSocket::binary(uint32_t id, const char* message) {
+ binary(id, message, strlen(message));
+}
+void AsyncWebSocket::binary(uint32_t id, const String& message) {
+ binary(id, message.c_str(), message.length());
+}
+#ifndef ESP32
+void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper* data, size_t len) {
+ PGM_P p = reinterpret_cast(data);
+ char* message = (char*)malloc(len);
+ if (message) {
+ memcpy_P(message, p, len);
+ binary(id, message, len);
+ free(message);
+ }
+}
+#endif // ESP32
+void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ binary(id, std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->binary(buffer);
+}
+
+void AsyncWebSocket::binaryAll(const uint8_t* message, size_t len) {
+ binaryAll(makeSharedBuffer(message, len));
+}
+void AsyncWebSocket::binaryAll(const char* message, size_t len) {
+ binaryAll((const uint8_t*)message, len);
+}
+void AsyncWebSocket::binaryAll(const char* message) {
+ binaryAll(message, strlen(message));
+}
+void AsyncWebSocket::binaryAll(const String& message) {
+ binaryAll(message.c_str(), message.length());
+}
+#ifndef ESP32
+void AsyncWebSocket::binaryAll(const __FlashStringHelper* data, size_t len) {
+ PGM_P p = reinterpret_cast(data);
+ char* message = (char*)malloc(len);
+ if (message) {
+ memcpy_P(message, p, len);
+ binaryAll(message, len);
+ free(message);
+ }
+}
+#endif // ESP32
+void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer* buffer) {
+ if (buffer) {
+ binaryAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+}
+void AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED)
+ c.binary(buffer);
+}
+
+size_t AsyncWebSocket::printf(uint32_t id, const char* format, ...) {
+ AsyncWebSocketClient* c = client(id);
+ if (c) {
+ va_list arg;
+ va_start(arg, format);
+ size_t len = c->printf(format, arg);
+ va_end(arg);
+ return len;
+ }
+ return 0;
+}
+
+size_t AsyncWebSocket::printfAll(const char* format, ...) {
+ va_list arg;
+ char* temp = new char[MAX_PRINTF_LEN];
+ if (!temp)
+ return 0;
+
+ va_start(arg, format);
+ size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
+ va_end(arg);
+ delete[] temp;
+
+ AsyncWebSocketSharedBuffer buffer = std::make_shared>(len);
+
+ va_start(arg, format);
+ vsnprintf((char*)buffer->data(), len + 1, format, arg);
+ va_end(arg);
+
+ textAll(buffer);
+ return len;
+}
+
+#ifndef ESP32
+size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) {
+ AsyncWebSocketClient* c = client(id);
+ if (c != NULL) {
+ va_list arg;
+ va_start(arg, formatP);
+ size_t len = c->printf_P(formatP, arg);
+ va_end(arg);
+ return len;
+ }
+ return 0;
+}
+
+size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
+ va_list arg;
+ char* temp = new char[MAX_PRINTF_LEN];
+ if (!temp)
+ return 0;
+
+ va_start(arg, formatP);
+ size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
+ va_end(arg);
+ delete[] temp;
+
+ AsyncWebSocketSharedBuffer buffer = std::make_shared>(len + 1);
+
+ va_start(arg, formatP);
+ vsnprintf_P((char*)buffer->data(), len + 1, formatP, arg);
+ va_end(arg);
+
+ textAll(buffer);
+ return len;
+}
+#endif
+
+const char __WS_STR_CONNECTION[] PROGMEM = {"Connection"};
+const char __WS_STR_UPGRADE[] PROGMEM = {"Upgrade"};
+const char __WS_STR_ORIGIN[] PROGMEM = {"Origin"};
+const char __WS_STR_COOKIE[] PROGMEM = {"Cookie"};
+const char __WS_STR_VERSION[] PROGMEM = {"Sec-WebSocket-Version"};
+const char __WS_STR_KEY[] PROGMEM = {"Sec-WebSocket-Key"};
+const char __WS_STR_PROTOCOL[] PROGMEM = {"Sec-WebSocket-Protocol"};
+const char __WS_STR_ACCEPT[] PROGMEM = {"Sec-WebSocket-Accept"};
+const char __WS_STR_UUID[] PROGMEM = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"};
+
+#define WS_STR_UUID_LEN 36
+
+#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
+#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
+#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
+#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
+#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
+#define WS_STR_KEY FPSTR(__WS_STR_KEY)
+#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
+#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT)
+#define WS_STR_UUID FPSTR(__WS_STR_UUID)
+
+bool AsyncWebSocket::canHandle(AsyncWebServerRequest* request) {
+ if (!_enabled)
+ return false;
+
+ if (request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS))
+ return false;
+
+ request->addInterestingHeader(WS_STR_CONNECTION);
+ request->addInterestingHeader(WS_STR_UPGRADE);
+ request->addInterestingHeader(WS_STR_ORIGIN);
+ request->addInterestingHeader(WS_STR_COOKIE);
+ request->addInterestingHeader(WS_STR_VERSION);
+ request->addInterestingHeader(WS_STR_KEY);
+ request->addInterestingHeader(WS_STR_PROTOCOL);
+ return true;
+}
+
+void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
+ if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) {
+ request->send(400);
+ return;
+ }
+ if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
+ return request->requestAuthentication();
+ }
+ if (_handshakeHandler != nullptr) {
+ if (!_handshakeHandler(request)) {
+ request->send(401);
+ return;
+ }
+ }
+ const AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
+ if (version->value().toInt() != 13) {
+ AsyncWebServerResponse* response = request->beginResponse(400);
+ response->addHeader(WS_STR_VERSION, F("13"));
+ request->send(response);
+ return;
+ }
+ const AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
+ AsyncWebServerResponse* response = new AsyncWebSocketResponse(key->value(), this);
+ if (request->hasHeader(WS_STR_PROTOCOL)) {
+ const AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL);
+ // ToDo: check protocol
+ response->addHeader(WS_STR_PROTOCOL, protocol->value());
+ }
+ request->send(response);
+}
+
+AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(size_t size) {
+ AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size);
+ if (buffer->length() != size) {
+ delete buffer;
+ return nullptr;
+ } else {
+ return buffer;
+ }
+}
+
+AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(const uint8_t* data, size_t size) {
+ AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size);
+ if (buffer->length() != size) {
+ delete buffer;
+ return nullptr;
+ } else {
+ return buffer;
+ }
+}
+
+/*
+ * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
+ * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480
+ */
+
+AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket* server) {
+ _server = server;
+ _code = 101;
+ _sendContentLength = false;
+
+ uint8_t hash[20];
+ char buffer[33];
+
+#if defined(ESP8266) || defined(TARGET_RP2040)
+ sha1(key + WS_STR_UUID, hash);
+#else
+ String k;
+ k.reserve(key.length() + WS_STR_UUID_LEN);
+ k.concat(key);
+ k.concat(WS_STR_UUID);
+ SHA1Builder sha1;
+ sha1.begin();
+ sha1.add((const uint8_t*)k.c_str(), k.length());
+ sha1.calculate();
+ sha1.getBytes(hash);
+#endif
+ base64_encodestate _state;
+ base64_init_encodestate(&_state);
+ int len = base64_encode_block((const char*)hash, 20, buffer, &_state);
+ len = base64_encode_blockend((buffer + len), &_state);
+ addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
+ addHeader(WS_STR_UPGRADE, F("websocket"));
+ addHeader(WS_STR_ACCEPT, buffer);
+}
+
+void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
+ if (_state == RESPONSE_FAILED) {
+ request->client()->close(true);
+ return;
+ }
+ String out(_assembleHead(request->version()));
+ request->client()->write(out.c_str(), _headLength);
+ _state = RESPONSE_WAIT_ACK;
+}
+
+size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
+ (void)time;
+
+ if (len)
+ _server->_newClient(request);
+
+ return 0;
+}
diff --git a/lib/ESP Async WebServer/src/AsyncWebSocket.h b/lib/ESP Async WebServer/src/AsyncWebSocket.h
new file mode 100644
index 0000000..77bf7ee
--- /dev/null
+++ b/lib/ESP Async WebServer/src/AsyncWebSocket.h
@@ -0,0 +1,390 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef ASYNCWEBSOCKET_H_
+#define ASYNCWEBSOCKET_H_
+
+#include
+#ifdef ESP32
+ #include
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 32
+ #endif
+#elif defined(ESP8266)
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 8
+ #endif
+#elif defined(TARGET_RP2040)
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 32
+ #endif
+#endif
+
+#include
+
+#include
+#include
+#include
+
+#ifdef ESP8266
+ #include
+ #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
+ #include <../src/Hash.h>
+ #endif
+#endif
+
+#ifndef DEFAULT_MAX_WS_CLIENTS
+ #ifdef ESP32
+ #define DEFAULT_MAX_WS_CLIENTS 8
+ #else
+ #define DEFAULT_MAX_WS_CLIENTS 4
+ #endif
+#endif
+
+using AsyncWebSocketSharedBuffer = std::shared_ptr>;
+
+class AsyncWebSocket;
+class AsyncWebSocketResponse;
+class AsyncWebSocketClient;
+class AsyncWebSocketControl;
+
+typedef struct {
+ /** Message type as defined by enum AwsFrameType.
+ * Note: Applications will only see WS_TEXT and WS_BINARY.
+ * All other types are handled by the library. */
+ uint8_t message_opcode;
+ /** Frame number of a fragmented message. */
+ uint32_t num;
+ /** Is this the last frame in a fragmented message ?*/
+ uint8_t final;
+ /** Is this frame masked? */
+ uint8_t masked;
+ /** Message type as defined by enum AwsFrameType.
+ * This value is the same as message_opcode for non-fragmented
+ * messages, but may also be WS_CONTINUATION in a fragmented message. */
+ uint8_t opcode;
+ /** Length of the current frame.
+ * This equals the total length of the message if num == 0 && final == true */
+ uint64_t len;
+ /** Mask key */
+ uint8_t mask[4];
+ /** Offset of the data inside the current frame. */
+ uint64_t index;
+} AwsFrameInfo;
+
+typedef enum { WS_DISCONNECTED,
+ WS_CONNECTED,
+ WS_DISCONNECTING } AwsClientStatus;
+typedef enum { WS_CONTINUATION,
+ WS_TEXT,
+ WS_BINARY,
+ WS_DISCONNECT = 0x08,
+ WS_PING,
+ WS_PONG } AwsFrameType;
+typedef enum { WS_MSG_SENDING,
+ WS_MSG_SENT,
+ WS_MSG_ERROR } AwsMessageStatus;
+typedef enum { WS_EVT_CONNECT,
+ WS_EVT_DISCONNECT,
+ WS_EVT_PONG,
+ WS_EVT_ERROR,
+ WS_EVT_DATA } AwsEventType;
+
+class AsyncWebSocketMessageBuffer {
+ friend AsyncWebSocket;
+ friend AsyncWebSocketClient;
+
+ private:
+ AsyncWebSocketSharedBuffer _buffer;
+
+ public:
+ AsyncWebSocketMessageBuffer() {}
+ explicit AsyncWebSocketMessageBuffer(size_t size);
+ AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
+ //~AsyncWebSocketMessageBuffer();
+ bool reserve(size_t size);
+ uint8_t* get() { return _buffer->data(); }
+ size_t length() const { return _buffer->size(); }
+};
+
+class AsyncWebSocketMessage {
+ private:
+ AsyncWebSocketSharedBuffer _WSbuffer;
+ uint8_t _opcode{WS_TEXT};
+ bool _mask{false};
+ AwsMessageStatus _status{WS_MSG_ERROR};
+ size_t _sent{};
+ size_t _ack{};
+ size_t _acked{};
+
+ public:
+ AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
+
+ bool finished() const { return _status != WS_MSG_SENDING; }
+ bool betweenFrames() const { return _acked == _ack; }
+
+ void ack(size_t len, uint32_t time);
+ size_t send(AsyncClient* client);
+};
+
+class AsyncWebSocketClient {
+ private:
+ AsyncClient* _client;
+ AsyncWebSocket* _server;
+ uint32_t _clientId;
+ AwsClientStatus _status;
+#ifdef ESP32
+ mutable std::mutex _lock;
+#endif
+ std::deque _controlQueue;
+ std::deque _messageQueue;
+ bool closeWhenFull = true;
+
+ uint8_t _pstate;
+ AwsFrameInfo _pinfo;
+
+ uint32_t _lastMessageTime;
+ uint32_t _keepAlivePeriod;
+
+ void _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
+ void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
+ void _runQueue();
+ void _clearQueue();
+
+ public:
+ void* _tempObject;
+
+ AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
+ ~AsyncWebSocketClient();
+
+ // client id increments for the given server
+ uint32_t id() const { return _clientId; }
+ AwsClientStatus status() const { return _status; }
+ AsyncClient* client() { return _client; }
+ const AsyncClient* client() const { return _client; }
+ AsyncWebSocket* server() { return _server; }
+ const AsyncWebSocket* server() const { return _server; }
+ AwsFrameInfo const& pinfo() const { return _pinfo; }
+
+ // - If "true" (default), the connection will be closed if the message queue is full.
+ // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
+ // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
+ // and so on, causing a resource exhaustion.
+ //
+ // - If "false", the incoming message will be discarded if the queue is full.
+ // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
+ // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
+ //
+ // - In any case, when the queue is full, a message is logged.
+ // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
+ //
+ // Usage:
+ // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
+ //
+ // Use cases:,
+ // - if using websocket to send logging messages, maybe some loss is acceptable.
+ // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
+ void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
+ bool willCloseClientOnQueueFull() const { return closeWhenFull; }
+
+ IPAddress remoteIP() const;
+ uint16_t remotePort() const;
+
+ bool shouldBeDeleted() const { return !_client; }
+
+ // control frames
+ void close(uint16_t code = 0, const char* message = NULL);
+ void ping(const uint8_t* data = NULL, size_t len = 0);
+
+ // set auto-ping period in seconds. disabled if zero (default)
+ void keepAlivePeriod(uint16_t seconds) {
+ _keepAlivePeriod = seconds * 1000;
+ }
+ uint16_t keepAlivePeriod() {
+ return (uint16_t)(_keepAlivePeriod / 1000);
+ }
+
+ // data packets
+ void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
+ bool queueIsFull() const;
+ size_t queueLen() const;
+
+ size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
+#ifndef ESP32
+ size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
+#endif
+
+ void text(AsyncWebSocketSharedBuffer buffer);
+ void text(const uint8_t* message, size_t len);
+ void text(const char* message, size_t len);
+ void text(const char* message);
+ void text(const String& message);
+#ifndef ESP32
+ void text(const __FlashStringHelper* message);
+#endif // ESP32
+ void text(AsyncWebSocketMessageBuffer* buffer);
+
+ void binary(AsyncWebSocketSharedBuffer buffer);
+ void binary(const uint8_t* message, size_t len);
+ void binary(const char* message, size_t len);
+ void binary(const char* message);
+ void binary(const String& message);
+#ifndef ESP32
+ void binary(const __FlashStringHelper* message, size_t len);
+#endif // ESP32
+ void binary(AsyncWebSocketMessageBuffer* buffer);
+
+ bool canSend() const;
+
+ // system callbacks (do not call)
+ void _onAck(size_t len, uint32_t time);
+ void _onError(int8_t);
+ void _onPoll();
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+ void _onData(void* pbuf, size_t plen);
+};
+
+using AwsHandshakeHandler = std::function;
+using AwsEventHandler = std::function;
+
+// WebServer Handler implementation that plays the role of a socket server
+class AsyncWebSocket : public AsyncWebHandler {
+ private:
+ String _url;
+ std::list _clients;
+ uint32_t _cNextId;
+ AwsEventHandler _eventHandler{nullptr};
+ AwsHandshakeHandler _handshakeHandler;
+ bool _enabled;
+#ifdef ESP32
+ mutable std::mutex _lock;
+#endif
+
+ public:
+ explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
+ AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
+ ~AsyncWebSocket(){};
+ const char* url() const { return _url.c_str(); }
+ void enable(bool e) { _enabled = e; }
+ bool enabled() const { return _enabled; }
+ bool availableForWriteAll();
+ bool availableForWrite(uint32_t id);
+
+ size_t count() const;
+ AsyncWebSocketClient* client(uint32_t id);
+ bool hasClient(uint32_t id) { return client(id) != nullptr; }
+
+ void close(uint32_t id, uint16_t code = 0, const char* message = NULL);
+ void closeAll(uint16_t code = 0, const char* message = NULL);
+ void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
+
+ void ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
+ void pingAll(const uint8_t* data = NULL, size_t len = 0); // done
+
+ void text(uint32_t id, const uint8_t* message, size_t len);
+ void text(uint32_t id, const char* message, size_t len);
+ void text(uint32_t id, const char* message);
+ void text(uint32_t id, const String& message);
+#ifndef ESP32
+ void text(uint32_t id, const __FlashStringHelper* message);
+#endif // ESP32
+ void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
+ void text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
+
+ void textAll(const uint8_t* message, size_t len);
+ void textAll(const char* message, size_t len);
+ void textAll(const char* message);
+ void textAll(const String& message);
+#ifndef ESP32
+ void textAll(const __FlashStringHelper* message);
+#endif // ESP32
+ void textAll(AsyncWebSocketMessageBuffer* buffer);
+ void textAll(AsyncWebSocketSharedBuffer buffer);
+
+ void binary(uint32_t id, const uint8_t* message, size_t len);
+ void binary(uint32_t id, const char* message, size_t len);
+ void binary(uint32_t id, const char* message);
+ void binary(uint32_t id, const String& message);
+#ifndef ESP32
+ void binary(uint32_t id, const __FlashStringHelper* message, size_t len);
+#endif // ESP32
+ void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
+ void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
+
+ void binaryAll(const uint8_t* message, size_t len);
+ void binaryAll(const char* message, size_t len);
+ void binaryAll(const char* message);
+ void binaryAll(const String& message);
+#ifndef ESP32
+ void binaryAll(const __FlashStringHelper* message, size_t len);
+#endif // ESP32
+ void binaryAll(AsyncWebSocketMessageBuffer* buffer);
+ void binaryAll(AsyncWebSocketSharedBuffer buffer);
+
+ size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
+ size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
+
+#ifndef ESP32
+ size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
+ size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
+#endif
+
+ // event listener
+ void onEvent(AwsEventHandler handler) {
+ _eventHandler = handler;
+ }
+
+ // Handshake Handler
+ void handleHandshake(AwsHandshakeHandler handler) {
+ _handshakeHandler = handler;
+ }
+
+ // system callbacks (do not call)
+ uint32_t _getNextId() { return _cNextId++; }
+ AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
+ void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
+ virtual bool canHandle(AsyncWebServerRequest* request) override final;
+ virtual void handleRequest(AsyncWebServerRequest* request) override final;
+
+ // messagebuffer functions/objects.
+ AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
+ AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
+
+ const std::list& getClients() const { return _clients; }
+};
+
+// WebServer response to authenticate the socket and detach the tcp client from the web server request
+class AsyncWebSocketResponse : public AsyncWebServerResponse {
+ private:
+ String _content;
+ AsyncWebSocket* _server;
+
+ public:
+ AsyncWebSocketResponse(const String& key, AsyncWebSocket* server);
+ void _respond(AsyncWebServerRequest* request);
+ size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
+ bool _sourceValid() const { return true; }
+};
+
+#endif /* ASYNCWEBSOCKET_H_ */
diff --git a/lib/ESP Async WebServer/src/ESPAsyncWebServer.h b/lib/ESP Async WebServer/src/ESPAsyncWebServer.h
new file mode 100644
index 0000000..77c5de7
--- /dev/null
+++ b/lib/ESP Async WebServer/src/ESPAsyncWebServer.h
@@ -0,0 +1,652 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef _ESPAsyncWebServer_H_
+#define _ESPAsyncWebServer_H_
+
+#include "Arduino.h"
+
+#include "FS.h"
+#include
+#include
+#include
+
+#ifdef ESP32
+ #include
+ #include
+#elif defined(ESP8266)
+ #include
+ #include
+#elif defined(TARGET_RP2040)
+ #include
+ #include
+ #include
+ #include
+#else
+ #error Platform not supported
+#endif
+
+#define ASYNCWEBSERVER_VERSION "3.0.6"
+#define ASYNCWEBSERVER_VERSION_MAJOR 3
+#define ASYNCWEBSERVER_VERSION_MINOR 0
+#define ASYNCWEBSERVER_VERSION_REVISION 6
+#define ASYNCWEBSERVER_FORK_mathieucarbou
+
+#ifdef ASYNCWEBSERVER_REGEX
+ #define ASYNCWEBSERVER_REGEX_ATTRIBUTE
+#else
+ #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
+#endif
+
+class AsyncWebServer;
+class AsyncWebServerRequest;
+class AsyncWebServerResponse;
+class AsyncWebHeader;
+class AsyncWebParameter;
+class AsyncWebRewrite;
+class AsyncWebHandler;
+class AsyncStaticWebHandler;
+class AsyncCallbackWebHandler;
+class AsyncResponseStream;
+
+#if defined (TARGET_RP2040)
+ typedef enum http_method WebRequestMethod;
+#else
+ #ifndef WEBSERVER_H
+ typedef enum {
+ HTTP_GET = 0b00000001,
+ HTTP_POST = 0b00000010,
+ HTTP_DELETE = 0b00000100,
+ HTTP_PUT = 0b00001000,
+ HTTP_PATCH = 0b00010000,
+ HTTP_HEAD = 0b00100000,
+ HTTP_OPTIONS = 0b01000000,
+ HTTP_ANY = 0b01111111,
+ } WebRequestMethod;
+ #endif
+#endif
+
+#ifndef HAVE_FS_FILE_OPEN_MODE
+namespace fs {
+ class FileOpenMode {
+ public:
+ static const char* read;
+ static const char* write;
+ static const char* append;
+ };
+};
+#else
+ #include "FileOpenMode.h"
+#endif
+
+// if this value is returned when asked for data, packet will not be sent and you will be asked for data again
+#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
+
+typedef uint8_t WebRequestMethodComposite;
+typedef std::function ArDisconnectHandler;
+
+/*
+ * PARAMETER :: Chainable object to hold GET/POST and FILE parameters
+ * */
+
+class AsyncWebParameter {
+ private:
+ String _name;
+ String _value;
+ size_t _size;
+ bool _isForm;
+ bool _isFile;
+
+ public:
+ AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
+ const String& name() const { return _name; }
+ const String& value() const { return _value; }
+ size_t size() const { return _size; }
+ bool isPost() const { return _isForm; }
+ bool isFile() const { return _isFile; }
+};
+
+/*
+ * HEADER :: Chainable object to hold the headers
+ * */
+
+class AsyncWebHeader {
+ private:
+ String _name;
+ String _value;
+
+ public:
+ AsyncWebHeader() = default;
+ AsyncWebHeader(const AsyncWebHeader&) = default;
+
+ AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {}
+ AsyncWebHeader(const String& data) {
+ if (!data)
+ return;
+ int index = data.indexOf(':');
+ if (index < 0)
+ return;
+ _name = data.substring(0, index);
+ _value = data.substring(index + 2);
+ }
+
+ AsyncWebHeader& operator=(const AsyncWebHeader&) = default;
+
+ const String& name() const { return _name; }
+ const String& value() const { return _value; }
+ String toString() const { return _name + F(": ") + _value + F("\r\n"); }
+};
+
+/*
+ * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
+ * */
+
+typedef enum { RCT_NOT_USED = -1,
+ RCT_DEFAULT = 0,
+ RCT_HTTP,
+ RCT_WS,
+ RCT_EVENT,
+ RCT_MAX } RequestedConnectionType;
+
+typedef std::function AwsResponseFiller;
+typedef std::function AwsTemplateProcessor;
+
+class AsyncWebServerRequest {
+ using File = fs::File;
+ using FS = fs::FS;
+ friend class AsyncWebServer;
+ friend class AsyncCallbackWebHandler;
+
+ private:
+ AsyncClient* _client;
+ AsyncWebServer* _server;
+ AsyncWebHandler* _handler;
+ AsyncWebServerResponse* _response;
+ std::vector _interestingHeaders;
+ ArDisconnectHandler _onDisconnectfn;
+
+ String _temp;
+ uint8_t _parseState;
+
+ uint8_t _version;
+ WebRequestMethodComposite _method;
+ String _url;
+ String _host;
+ String _contentType;
+ String _boundary;
+ String _authorization;
+ RequestedConnectionType _reqconntype;
+ void _removeNotInterestingHeaders();
+ bool _isDigest;
+ bool _isMultipart;
+ bool _isPlainPost;
+ bool _expectingContinue;
+ size_t _contentLength;
+ size_t _parsedLength;
+
+ std::list _headers;
+ std::list _params;
+ std::vector _pathParams;
+
+ uint8_t _multiParseState;
+ uint8_t _boundaryPosition;
+ size_t _itemStartIndex;
+ size_t _itemSize;
+ String _itemName;
+ String _itemFilename;
+ String _itemType;
+ String _itemValue;
+ uint8_t* _itemBuffer;
+ size_t _itemBufferIndex;
+ bool _itemIsFile;
+
+ void _onPoll();
+ void _onAck(size_t len, uint32_t time);
+ void _onError(int8_t error);
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+ void _onData(void* buf, size_t len);
+
+ void _addPathParam(const char* param);
+
+ bool _parseReqHead();
+ bool _parseReqHeader();
+ void _parseLine();
+ void _parsePlainPostChar(uint8_t data);
+ void _parseMultipartPostByte(uint8_t data, bool last);
+ void _addGetParams(const String& params);
+
+ void _handleUploadStart();
+ void _handleUploadByte(uint8_t data, bool last);
+ void _handleUploadEnd();
+
+ public:
+ File _tempFile;
+ void* _tempObject;
+
+ AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
+ ~AsyncWebServerRequest();
+
+ AsyncClient* client() { return _client; }
+ uint8_t version() const { return _version; }
+ WebRequestMethodComposite method() const { return _method; }
+ const String& url() const { return _url; }
+ const String& host() const { return _host; }
+ const String& contentType() const { return _contentType; }
+ size_t contentLength() const { return _contentLength; }
+ bool multipart() const { return _isMultipart; }
+
+#ifndef ESP8266
+ const char* methodToString() const;
+ const char* requestedConnTypeToString() const;
+#else
+ const __FlashStringHelper* methodToString() const;
+ const __FlashStringHelper* requestedConnTypeToString() const;
+#endif
+
+ RequestedConnectionType requestedConnType() const { return _reqconntype; }
+ bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
+ void onDisconnect(ArDisconnectHandler fn);
+
+ // hash is the string representation of:
+ // base64(user:pass) for basic or
+ // user:realm:md5(user:realm:pass) for digest
+ bool authenticate(const char* hash);
+ bool authenticate(const char* username, const char* password, const char* realm = NULL, bool passwordIsHash = false);
+ void requestAuthentication(const char* realm = NULL, bool isDigest = true);
+
+ void setHandler(AsyncWebHandler* handler) { _handler = handler; }
+
+ /**
+ * @brief add header to collect from a response
+ *
+ * @param name
+ */
+ void addInterestingHeader(const char* name);
+ void addInterestingHeader(const String& name) { return addInterestingHeader(name.c_str()); };
+
+ /**
+ * @brief issue 302 redirect response
+ *
+ * @param url
+ */
+ void redirect(const char* url);
+ void redirect(const String& url) { return redirect(url.c_str()); };
+
+ void send(AsyncWebServerResponse* response);
+ void send(int code, const String& contentType = String(), const String& content = String());
+ void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
+ void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
+ void send(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ void send(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
+ void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+
+ [[deprecated("Replaced by send(...)")]]
+ void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
+ send(code, contentType, content, len, callback);
+ }
+ [[deprecated("Replaced by send(...)")]]
+ void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
+ send(code, contentType, content, callback);
+ }
+
+ AsyncWebServerResponse* beginResponse(int code, const String& contentType = String(), const String& content = String());
+ AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
+ AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
+ AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
+ AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460);
+
+
+ [[deprecated("Replaced by beginResponse(...)")]]
+ AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
+ return beginResponse(code, contentType, content, len, callback);
+ }
+ [[deprecated("Replaced by beginResponse(...)")]]
+ AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
+ return beginResponse(code, contentType, content, callback);
+ }
+
+ size_t headers() const; // get header count
+
+ // check if header exists
+ bool hasHeader(const char* name) const;
+ bool hasHeader(const String& name) const { return hasHeader(name.c_str()); };
+#ifdef ESP8266
+ bool hasHeader(const __FlashStringHelper* data) const; // check if header exists
+#endif
+
+ const AsyncWebHeader* getHeader(const char* name) const;
+ const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); };
+#ifdef ESP8266
+ const AsyncWebHeader* getHeader(const __FlashStringHelper* data) const;
+#endif
+ const AsyncWebHeader* getHeader(size_t num) const;
+
+ size_t params() const; // get arguments count
+ bool hasParam(const String& name, bool post = false, bool file = false) const;
+ bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const;
+
+ /**
+ * @brief Get the Request parameter by name
+ *
+ * @param name
+ * @param post
+ * @param file
+ * @return const AsyncWebParameter*
+ */
+ const AsyncWebParameter* getParam(const char* name, bool post = false, bool file = false) const;
+
+ const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
+#ifdef ESP8266
+ const AsyncWebParameter* getParam(const __FlashStringHelper* data, bool post, bool file) const;
+#endif
+
+ /**
+ * @brief Get request parameter by number
+ * i.e., n-th parameter
+ * @param num
+ * @return const AsyncWebParameter*
+ */
+ const AsyncWebParameter* getParam(size_t num) const;
+
+ size_t args() const { return params(); } // get arguments count
+
+ // get request argument value by name
+ const String& arg(const char* name) const;
+ // get request argument value by name
+ const String& arg(const String& name) const { return arg(name.c_str()); };
+#ifdef ESP8266
+ const String& arg(const __FlashStringHelper* data) const; // get request argument value by F(name)
+#endif
+ const String& arg(size_t i) const; // get request argument value by number
+ const String& argName(size_t i) const; // get request argument name by number
+ bool hasArg(const char* name) const; // check if argument exists
+ bool hasArg(const String& name) const { return hasArg(name.c_str()); };
+#ifdef ESP8266
+ bool hasArg(const __FlashStringHelper* data) const; // check if F(argument) exists
+#endif
+
+ const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
+
+ // get request header value by name
+ const String& header(const char* name) const;
+ const String& header(const String& name) const { return header(name.c_str()); };
+
+#ifdef ESP8266
+ const String& header(const __FlashStringHelper* data) const; // get request header value by F(name)
+#endif
+
+ const String& header(size_t i) const; // get request header value by number
+ const String& headerName(size_t i) const; // get request header name by number
+
+ String urlDecode(const String& text) const;
+};
+
+/*
+ * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
+ * */
+
+using ArRequestFilterFunction = std::function;
+
+bool ON_STA_FILTER(AsyncWebServerRequest* request);
+
+bool ON_AP_FILTER(AsyncWebServerRequest* request);
+
+/*
+ * REWRITE :: One instance can be handle any Request (done by the Server)
+ * */
+
+class AsyncWebRewrite {
+ protected:
+ String _from;
+ String _toUrl;
+ String _params;
+ ArRequestFilterFunction _filter{nullptr};
+
+ public:
+ AsyncWebRewrite(const char* from, const char* to) : _from(from), _toUrl(to) {
+ int index = _toUrl.indexOf('?');
+ if (index > 0) {
+ _params = _toUrl.substring(index + 1);
+ _toUrl = _toUrl.substring(0, index);
+ }
+ }
+ virtual ~AsyncWebRewrite() {}
+ AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) {
+ _filter = fn;
+ return *this;
+ }
+ bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); }
+ const String& from(void) const { return _from; }
+ const String& toUrl(void) const { return _toUrl; }
+ const String& params(void) const { return _params; }
+ virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); }
+};
+
+/*
+ * HANDLER :: One instance can be attached to any Request (done by the Server)
+ * */
+
+class AsyncWebHandler {
+ protected:
+ ArRequestFilterFunction _filter{nullptr};
+ String _username;
+ String _password;
+
+ public:
+ AsyncWebHandler() {}
+ AsyncWebHandler& setFilter(ArRequestFilterFunction fn) {
+ _filter = fn;
+ return *this;
+ }
+ AsyncWebHandler& setAuthentication(const char* username, const char* password) {
+ _username = username;
+ _password = password;
+ return *this;
+ };
+ AsyncWebHandler& setAuthentication(const String& username, const String& password) {
+ _username = username;
+ _password = password;
+ return *this;
+ };
+ bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
+ virtual ~AsyncWebHandler() {}
+ virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) {
+ return false;
+ }
+ virtual void handleRequest(AsyncWebServerRequest* request __attribute__((unused))) {}
+ virtual void handleUpload(AsyncWebServerRequest* request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))) {}
+ virtual void handleBody(AsyncWebServerRequest* request __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {}
+ virtual bool isRequestHandlerTrivial() { return true; }
+};
+
+/*
+ * RESPONSE :: One instance is created for each Request (attached by the Handler)
+ * */
+
+typedef enum {
+ RESPONSE_SETUP,
+ RESPONSE_HEADERS,
+ RESPONSE_CONTENT,
+ RESPONSE_WAIT_ACK,
+ RESPONSE_END,
+ RESPONSE_FAILED
+} WebResponseState;
+
+class AsyncWebServerResponse {
+ protected:
+ int _code;
+ std::list _headers;
+ String _contentType;
+ size_t _contentLength;
+ bool _sendContentLength;
+ bool _chunked;
+ size_t _headLength;
+ size_t _sentLength;
+ size_t _ackedLength;
+ size_t _writtenLength;
+ WebResponseState _state;
+ const char* _responseCodeToString(int code);
+
+ public:
+ static const __FlashStringHelper* responseCodeToString(int code);
+
+ public:
+ AsyncWebServerResponse();
+ virtual ~AsyncWebServerResponse();
+ virtual void setCode(int code);
+ virtual void setContentLength(size_t len);
+ virtual void setContentType(const String& type);
+ virtual void addHeader(const String& name, const String& value);
+ virtual String _assembleHead(uint8_t version);
+ virtual bool _started() const;
+ virtual bool _finished() const;
+ virtual bool _failed() const;
+ virtual bool _sourceValid() const;
+ virtual void _respond(AsyncWebServerRequest* request);
+ virtual size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
+};
+
+/*
+ * SERVER :: One instance
+ * */
+
+typedef std::function ArRequestHandlerFunction;
+typedef std::function ArUploadHandlerFunction;
+typedef std::function ArBodyHandlerFunction;
+
+class AsyncWebServer {
+ protected:
+ AsyncServer _server;
+ std::list> _rewrites;
+ std::list> _handlers;
+ AsyncCallbackWebHandler* _catchAllHandler;
+
+ public:
+ AsyncWebServer(uint16_t port);
+ ~AsyncWebServer();
+
+ void begin();
+ void end();
+
+#if ASYNC_TCP_SSL_ENABLED
+ void onSslFileRequest(AcSSlFileHandler cb, void* arg);
+ void beginSecure(const char* cert, const char* private_key_file, const char* password);
+#endif
+
+ AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
+
+ /**
+ * @brief (compat) Add url rewrite rule by pointer
+ * a deep copy of the pounter object will be created,
+ * it is up to user to manage further lifetime of the object in argument
+ *
+ * @param rewrite pointer to rewrite object to copy setting from
+ * @return AsyncWebRewrite& reference to a newly created rewrite rule
+ */
+ AsyncWebRewrite& addRewrite(std::shared_ptr rewrite);
+
+ /**
+ * @brief add url rewrite rule
+ *
+ * @param from
+ * @param to
+ * @return AsyncWebRewrite&
+ */
+ AsyncWebRewrite& rewrite(const char* from, const char* to);
+
+ /**
+ * @brief (compat) remove rewrite rule via referenced object
+ * this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any
+ * it's a compat method, better use `removeRewrite(const char* from, const char* to)`
+ * @param rewrite
+ * @return true
+ * @return false
+ */
+ bool removeRewrite(AsyncWebRewrite* rewrite);
+
+ /**
+ * @brief remove rewrite rule
+ *
+ * @param from
+ * @param to
+ * @return true
+ * @return false
+ */
+ bool removeRewrite(const char* from, const char* to);
+
+ AsyncWebHandler& addHandler(AsyncWebHandler* handler);
+ bool removeHandler(AsyncWebHandler* handler);
+
+ AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
+ AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
+ AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
+ AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
+
+ AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
+
+ void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
+ void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads
+ void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
+
+ void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
+
+ void _handleDisconnect(AsyncWebServerRequest* request);
+ void _attachHandler(AsyncWebServerRequest* request);
+ void _rewriteRequest(AsyncWebServerRequest* request);
+};
+
+class DefaultHeaders {
+ using headers_t = std::list;
+ headers_t _headers;
+
+ public:
+ DefaultHeaders() = default;
+
+ using ConstIterator = headers_t::const_iterator;
+
+ void addHeader(const String& name, const String& value) {
+ _headers.emplace_back(name, value);
+ }
+
+ ConstIterator begin() const { return _headers.begin(); }
+ ConstIterator end() const { return _headers.end(); }
+
+ DefaultHeaders(DefaultHeaders const&) = delete;
+ DefaultHeaders& operator=(DefaultHeaders const&) = delete;
+
+ static DefaultHeaders& Instance() {
+ static DefaultHeaders instance;
+ return instance;
+ }
+};
+
+#include "AsyncEventSource.h"
+#include "AsyncWebSocket.h"
+#include "WebHandlerImpl.h"
+#include "WebResponseImpl.h"
+
+#endif /* _AsyncWebServer_H_ */
diff --git a/lib/ESP Async WebServer/src/ESP_Async_WebServer.h b/lib/ESP Async WebServer/src/ESP_Async_WebServer.h
new file mode 100644
index 0000000..ca6a112
--- /dev/null
+++ b/lib/ESP Async WebServer/src/ESP_Async_WebServer.h
@@ -0,0 +1,2 @@
+// to please Arduino Lint
+#include "ESPAsyncWebServer.h"
diff --git a/lib/ESP Async WebServer/src/WebAuthentication.cpp b/lib/ESP Async WebServer/src/WebAuthentication.cpp
new file mode 100644
index 0000000..abd74b8
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebAuthentication.cpp
@@ -0,0 +1,245 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "WebAuthentication.h"
+#include
+#if defined(ESP32) || defined(TARGET_RP2040)
+ #include
+#else
+ #include "md5.h"
+#endif
+
+// Basic Auth hash = base64("username:password")
+
+bool checkBasicAuthentication(const char* hash, const char* username, const char* password) {
+ if (username == NULL || password == NULL || hash == NULL)
+ return false;
+
+ size_t toencodeLen = strlen(username) + strlen(password) + 1;
+ size_t encodedLen = base64_encode_expected_len(toencodeLen);
+ if (strlen(hash) != encodedLen)
+// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
+#ifdef ARDUINO_ARCH_ESP32
+ if (strlen(hash) != encodedLen)
+#else
+ if (strlen(hash) != encodedLen - 1)
+#endif
+ return false;
+
+ char* toencode = new char[toencodeLen + 1];
+ if (toencode == NULL) {
+ return false;
+ }
+ char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
+ if (encoded == NULL) {
+ delete[] toencode;
+ return false;
+ }
+ sprintf_P(toencode, PSTR("%s:%s"), username, password);
+ if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) {
+ delete[] toencode;
+ delete[] encoded;
+ return true;
+ }
+ delete[] toencode;
+ delete[] encoded;
+ return false;
+}
+
+static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
+#if defined(ESP32) || defined(TARGET_RP2040)
+ MD5Builder md5;
+ md5.begin();
+ md5.add(data, len);
+ md5.calculate();
+ md5.getChars(output);
+#else
+ md5_context_t _ctx;
+
+ uint8_t* _buf = (uint8_t*)malloc(16);
+ if (_buf == NULL)
+ return false;
+ memset(_buf, 0x00, 16);
+
+ MD5Init(&_ctx);
+ MD5Update(&_ctx, data, len);
+ MD5Final(_buf, &_ctx);
+
+ for (uint8_t i = 0; i < 16; i++) {
+ sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
+ }
+
+ free(_buf);
+#endif
+ return true;
+}
+
+static String genRandomMD5() {
+#ifdef ESP8266
+ uint32_t r = RANDOM_REG32;
+#else
+ uint32_t r = rand();
+#endif
+ char* out = (char*)malloc(33);
+ if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
+ return emptyString;
+ String res = String(out);
+ free(out);
+ return res;
+}
+
+static String stringMD5(const String& in) {
+ char* out = (char*)malloc(33);
+ if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
+ return emptyString;
+ String res = String(out);
+ free(out);
+ return res;
+}
+
+String generateDigestHash(const char* username, const char* password, const char* realm) {
+ if (username == NULL || password == NULL || realm == NULL) {
+ return emptyString;
+ }
+ char* out = (char*)malloc(33);
+ String res = String(username);
+ res += ':';
+ res.concat(realm);
+ res += ':';
+ String in = res;
+ in.concat(password);
+ if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
+ return emptyString;
+ res.concat(out);
+ free(out);
+ return res;
+}
+
+String requestDigestAuthentication(const char* realm) {
+ String header = F("realm=\"");
+ if (realm == NULL)
+ header.concat(F("asyncesp"));
+ else
+ header.concat(realm);
+ header.concat(F("\", qop=\"auth\", nonce=\""));
+ header.concat(genRandomMD5());
+ header.concat(F("\", opaque=\""));
+ header.concat(genRandomMD5());
+ header += '"';
+ return header;
+}
+
+#ifndef ESP8266
+bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
+#else
+bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
+#endif
+{
+ if (username == NULL || password == NULL || header == NULL || method == NULL) {
+ // os_printf("AUTH FAIL: missing requred fields\n");
+ return false;
+ }
+
+ String myHeader(header);
+ int nextBreak = myHeader.indexOf(',');
+ if (nextBreak < 0) {
+ // os_printf("AUTH FAIL: no variables\n");
+ return false;
+ }
+
+ String myUsername;
+ String myRealm;
+ String myNonce;
+ String myUri;
+ String myResponse;
+ String myQop;
+ String myNc;
+ String myCnonce;
+
+ myHeader += F(", ");
+ do {
+ String avLine(myHeader.substring(0, nextBreak));
+ avLine.trim();
+ myHeader = myHeader.substring(nextBreak + 1);
+ nextBreak = myHeader.indexOf(',');
+
+ int eqSign = avLine.indexOf('=');
+ if (eqSign < 0) {
+ // os_printf("AUTH FAIL: no = sign\n");
+ return false;
+ }
+ String varName(avLine.substring(0, eqSign));
+ avLine = avLine.substring(eqSign + 1);
+ if (avLine.startsWith(String('"'))) {
+ avLine = avLine.substring(1, avLine.length() - 1);
+ }
+
+ if (varName.equals(F("username"))) {
+ if (!avLine.equals(username)) {
+ // os_printf("AUTH FAIL: username\n");
+ return false;
+ }
+ myUsername = avLine;
+ } else if (varName.equals(F("realm"))) {
+ if (realm != NULL && !avLine.equals(realm)) {
+ // os_printf("AUTH FAIL: realm\n");
+ return false;
+ }
+ myRealm = avLine;
+ } else if (varName.equals(F("nonce"))) {
+ if (nonce != NULL && !avLine.equals(nonce)) {
+ // os_printf("AUTH FAIL: nonce\n");
+ return false;
+ }
+ myNonce = avLine;
+ } else if (varName.equals(F("opaque"))) {
+ if (opaque != NULL && !avLine.equals(opaque)) {
+ // os_printf("AUTH FAIL: opaque\n");
+ return false;
+ }
+ } else if (varName.equals(F("uri"))) {
+ if (uri != NULL && !avLine.equals(uri)) {
+ // os_printf("AUTH FAIL: uri\n");
+ return false;
+ }
+ myUri = avLine;
+ } else if (varName.equals(F("response"))) {
+ myResponse = avLine;
+ } else if (varName.equals(F("qop"))) {
+ myQop = avLine;
+ } else if (varName.equals(F("nc"))) {
+ myNc = avLine;
+ } else if (varName.equals(F("cnonce"))) {
+ myCnonce = avLine;
+ }
+ } while (nextBreak > 0);
+
+ String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + password);
+ String ha2 = String(method) + ':' + myUri;
+ String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
+
+ if (myResponse.equals(stringMD5(response))) {
+ // os_printf("AUTH SUCCESS\n");
+ return true;
+ }
+
+ // os_printf("AUTH FAIL: password\n");
+ return false;
+}
diff --git a/lib/ESP Async WebServer/src/WebAuthentication.h b/lib/ESP Async WebServer/src/WebAuthentication.h
new file mode 100644
index 0000000..d519777
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebAuthentication.h
@@ -0,0 +1,39 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef WEB_AUTHENTICATION_H_
+#define WEB_AUTHENTICATION_H_
+
+#include "Arduino.h"
+
+bool checkBasicAuthentication(const char* header, const char* username, const char* password);
+String requestDigestAuthentication(const char* realm);
+
+bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
+
+#ifdef ESP8266
+bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
+#endif
+
+// for storing hashed versions on the device that can be authenticated against
+String generateDigestHash(const char* username, const char* password, const char* realm);
+
+#endif
diff --git a/lib/ESP Async WebServer/src/WebHandlerImpl.h b/lib/ESP Async WebServer/src/WebHandlerImpl.h
new file mode 100644
index 0000000..22757d7
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebHandlerImpl.h
@@ -0,0 +1,155 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
+#define ASYNCWEBSERVERHANDLERIMPL_H_
+
+#include
+#ifdef ASYNCWEBSERVER_REGEX
+ #include
+#endif
+
+#include "stddef.h"
+#include
+
+class AsyncStaticWebHandler : public AsyncWebHandler {
+ using File = fs::File;
+ using FS = fs::FS;
+
+ private:
+ bool _getFile(AsyncWebServerRequest* request);
+ bool _fileExists(AsyncWebServerRequest* request, const String& path);
+ uint8_t _countBits(const uint8_t value) const;
+
+ protected:
+ FS _fs;
+ String _uri;
+ String _path;
+ String _default_file;
+ String _cache_control;
+ String _last_modified;
+ AwsTemplateProcessor _callback;
+ bool _isDir;
+ bool _gzipFirst;
+ uint8_t _gzipStats;
+
+ public:
+ AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
+ virtual bool canHandle(AsyncWebServerRequest* request) override final;
+ virtual void handleRequest(AsyncWebServerRequest* request) override final;
+ AsyncStaticWebHandler& setIsDir(bool isDir);
+ AsyncStaticWebHandler& setDefaultFile(const char* filename);
+ AsyncStaticWebHandler& setCacheControl(const char* cache_control);
+ AsyncStaticWebHandler& setLastModified(const char* last_modified);
+ AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
+#ifdef ESP8266
+ AsyncStaticWebHandler& setLastModified(time_t last_modified);
+ AsyncStaticWebHandler& setLastModified(); // sets to current time. Make sure sntp is runing and time is updated
+#endif
+ AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {
+ _callback = newCallback;
+ return *this;
+ }
+};
+
+class AsyncCallbackWebHandler : public AsyncWebHandler {
+ private:
+ protected:
+ String _uri;
+ WebRequestMethodComposite _method;
+ ArRequestHandlerFunction _onRequest;
+ ArUploadHandlerFunction _onUpload;
+ ArBodyHandlerFunction _onBody;
+ bool _isRegex;
+
+ public:
+ AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
+ void setUri(const String& uri) {
+ _uri = uri;
+ _isRegex = uri.startsWith("^") && uri.endsWith("$");
+ }
+ void setMethod(WebRequestMethodComposite method) { _method = method; }
+ void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
+ void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
+ void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
+
+ virtual bool canHandle(AsyncWebServerRequest* request) override final {
+
+ if (!_onRequest)
+ return false;
+
+ if (!(_method & request->method()))
+ return false;
+
+#ifdef ASYNCWEBSERVER_REGEX
+ if (_isRegex) {
+ std::regex pattern(_uri.c_str());
+ std::smatch matches;
+ std::string s(request->url().c_str());
+ if (std::regex_search(s, matches, pattern)) {
+ for (size_t i = 1; i < matches.size(); ++i) { // start from 1
+ request->_addPathParam(matches[i].str().c_str());
+ }
+ } else {
+ return false;
+ }
+ } else
+#endif
+ if (_uri.length() && _uri.startsWith("/*.")) {
+ String uriTemplate = String(_uri);
+ uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
+ if (!request->url().endsWith(uriTemplate))
+ return false;
+ } else if (_uri.length() && _uri.endsWith("*")) {
+ String uriTemplate = String(_uri);
+ uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
+ if (!request->url().startsWith(uriTemplate))
+ return false;
+ } else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
+ return false;
+
+ request->addInterestingHeader("ANY");
+ return true;
+ }
+
+ virtual void handleRequest(AsyncWebServerRequest* request) override final {
+ if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+ if (_onRequest)
+ _onRequest(request);
+ else
+ request->send(500);
+ }
+ virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final {
+ if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+ if (_onUpload)
+ _onUpload(request, filename, index, data, len, final);
+ }
+ virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final {
+ if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+ if (_onBody)
+ _onBody(request, data, len, index, total);
+ }
+ virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; }
+};
+
+#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
diff --git a/lib/ESP Async WebServer/src/WebHandlers.cpp b/lib/ESP Async WebServer/src/WebHandlers.cpp
new file mode 100644
index 0000000..d740d86
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebHandlers.cpp
@@ -0,0 +1,247 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "ESPAsyncWebServer.h"
+#include "WebHandlerImpl.h"
+
+AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
+ : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
+ // Ensure leading '/'
+ if (_uri.length() == 0 || _uri[0] != '/')
+ _uri = String('/') + _uri;
+ if (_path.length() == 0 || _path[0] != '/')
+ _path = String('/') + _path;
+
+ // If path ends with '/' we assume a hint that this is a directory to improve performance.
+ // However - if it does not end with '/' we, can't assume a file, path can still be a directory.
+ _isDir = _path[_path.length() - 1] == '/';
+
+ // Remove the trailing '/' so we can handle default file
+ // Notice that root will be "" not "/"
+ if (_uri[_uri.length() - 1] == '/')
+ _uri = _uri.substring(0, _uri.length() - 1);
+ if (_path[_path.length() - 1] == '/')
+ _path = _path.substring(0, _path.length() - 1);
+
+ // Reset stats
+ _gzipFirst = false;
+ _gzipStats = 0xF8;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
+ _isDir = isDir;
+ return *this;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
+ _default_file = String(filename);
+ return *this;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
+ _cache_control = String(cache_control);
+ return *this;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
+ _last_modified = last_modified;
+ return *this;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
+ auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
+ char format[strlen_P(formatP) + 1];
+ strcpy_P(format, formatP);
+
+ char result[30];
+ strftime(result, sizeof(result), format, last_modified);
+ return setLastModified((const char*)result);
+}
+
+#ifdef ESP8266
+AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
+ return setLastModified((struct tm*)gmtime(&last_modified));
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
+ time_t last_modified;
+ if (time(&last_modified) == 0) // time is not yet set
+ return *this;
+ return setLastModified(last_modified);
+}
+#endif
+bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) {
+ if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
+ return false;
+ }
+ if (_getFile(request)) {
+ // We interested in "If-Modified-Since" header to check if file was modified
+ if (_last_modified.length())
+ request->addInterestingHeader(F("If-Modified-Since"));
+
+ if (_cache_control.length())
+ request->addInterestingHeader(F("If-None-Match"));
+
+ return true;
+ }
+
+ return false;
+}
+
+bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) {
+ // Remove the found uri
+ String path = request->url().substring(_uri.length());
+
+ // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
+ bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
+
+ path = _path + path;
+
+ // Do we have a file or .gz file
+ if (!canSkipFileCheck && _fileExists(request, path))
+ return true;
+
+ // Can't handle if not default file
+ if (_default_file.length() == 0)
+ return false;
+
+ // Try to add default file, ensure there is a trailing '/' ot the path.
+ if (path.length() == 0 || path[path.length() - 1] != '/')
+ path += String('/');
+ path += _default_file;
+
+ return _fileExists(request, path);
+}
+
+#ifdef ESP32
+ #define FILE_IS_REAL(f) (f == true && !f.isDirectory())
+#else
+ #define FILE_IS_REAL(f) (f == true)
+#endif
+
+bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) {
+ bool fileFound = false;
+ bool gzipFound = false;
+
+ String gzip = path + F(".gz");
+
+ if (_gzipFirst) {
+ if (_fs.exists(gzip)) {
+ request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
+ gzipFound = FILE_IS_REAL(request->_tempFile);
+ }
+ if (!gzipFound) {
+ if (_fs.exists(path)) {
+ request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ }
+ }
+ } else {
+ if (_fs.exists(path)) {
+ request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ }
+ if (!fileFound) {
+ if (_fs.exists(gzip)) {
+ request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
+ gzipFound = FILE_IS_REAL(request->_tempFile);
+ }
+ }
+ }
+
+ bool found = fileFound || gzipFound;
+
+ if (found) {
+ // Extract the file name from the path and keep it in _tempObject
+ size_t pathLen = path.length();
+ char* _tempPath = (char*)malloc(pathLen + 1);
+ snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
+ request->_tempObject = (void*)_tempPath;
+
+ // Calculate gzip statistic
+ _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
+ if (_gzipStats == 0x00)
+ _gzipFirst = false; // All files are not gzip
+ else if (_gzipStats == 0xFF)
+ _gzipFirst = true; // All files are gzip
+ else
+ _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
+ }
+
+ return found;
+}
+
+uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
+ uint8_t w = value;
+ uint8_t n;
+ for (n = 0; w != 0; n++)
+ w &= w - 1;
+ return n;
+}
+
+void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
+ // Get the filename from request->_tempObject and free it
+ String filename = String((char*)request->_tempObject);
+ free(request->_tempObject);
+ request->_tempObject = NULL;
+ if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+
+ if (request->_tempFile == true) {
+ time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
+ // set etag to lastmod timestamp if available, otherwise to size
+ String etag;
+ if (lw) {
+ setLastModified(gmtime(&lw));
+#if defined(TARGET_RP2040)
+ // time_t == long long int
+ const size_t len = 1 + 8 * sizeof(time_t);
+ char buf[len];
+ char* ret = lltoa(lw, buf, len, 10);
+ etag = ret ? String(ret) : String(request->_tempFile.size());
+#else
+ etag = String(lw);
+#endif
+ } else {
+ etag = String(request->_tempFile.size());
+ }
+ if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
+ request->_tempFile.close();
+ request->send(304); // Not modified
+ } else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
+ request->_tempFile.close();
+ AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified
+ response->addHeader(F("Cache-Control"), _cache_control);
+ response->addHeader(F("ETag"), etag);
+ request->send(response);
+ } else {
+ AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
+ if (_last_modified.length())
+ response->addHeader(F("Last-Modified"), _last_modified);
+ if (_cache_control.length()) {
+ response->addHeader(F("Cache-Control"), _cache_control);
+ response->addHeader(F("ETag"), etag);
+ }
+ request->send(response);
+ }
+ } else {
+ request->send(404);
+ }
+}
diff --git a/lib/ESP Async WebServer/src/WebRequest.cpp b/lib/ESP Async WebServer/src/WebRequest.cpp
new file mode 100644
index 0000000..2331b13
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebRequest.cpp
@@ -0,0 +1,1028 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "ESPAsyncWebServer.h"
+#include "WebAuthentication.h"
+#include "WebResponseImpl.h"
+
+#ifndef ESP8266
+ #define os_strlen strlen
+#endif
+
+#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
+
+enum { PARSE_REQ_START,
+ PARSE_REQ_HEADERS,
+ PARSE_REQ_BODY,
+ PARSE_REQ_END,
+ PARSE_REQ_FAIL };
+
+AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
+ : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(0), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _isDigest(false), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
+ c->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
+ c->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
+ c->onDisconnect([](void* r, AsyncClient* c) { AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
+ c->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
+ c->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
+ c->onPoll([](void* r, AsyncClient* c) { (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
+}
+
+AsyncWebServerRequest::~AsyncWebServerRequest() {
+ _headers.clear();
+
+ _pathParams.clear();
+
+ _interestingHeaders.clear();
+
+ if (_response != NULL) {
+ delete _response;
+ }
+
+ if (_tempObject != NULL) {
+ free(_tempObject);
+ }
+
+ if (_tempFile) {
+ _tempFile.close();
+ }
+
+ if (_itemBuffer) {
+ free(_itemBuffer);
+ }
+}
+
+void AsyncWebServerRequest::_onData(void* buf, size_t len) {
+ size_t i = 0;
+ while (true) {
+
+ if (_parseState < PARSE_REQ_BODY) {
+ // Find new line in buf
+ char* str = (char*)buf;
+ for (i = 0; i < len; i++) {
+ if (str[i] == '\n') {
+ break;
+ }
+ }
+ if (i == len) { // No new line, just add the buffer in _temp
+ char ch = str[len - 1];
+ str[len - 1] = 0;
+ _temp.reserve(_temp.length() + len);
+ _temp.concat(str);
+ _temp.concat(ch);
+ } else { // Found new line - extract it and parse
+ str[i] = 0; // Terminate the string at the end of the line.
+ _temp.concat(str);
+ _temp.trim();
+ _parseLine();
+ if (++i < len) {
+ // Still have more buffer to process
+ buf = str + i;
+ len -= i;
+ continue;
+ }
+ }
+ } else if (_parseState == PARSE_REQ_BODY) {
+ // A handler should be already attached at this point in _parseLine function.
+ // If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
+ const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
+ if (_isMultipart) {
+ if (needParse) {
+ size_t i;
+ for (i = 0; i < len; i++) {
+ _parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1);
+ _parsedLength++;
+ }
+ } else
+ _parsedLength += len;
+ } else {
+ if (_parsedLength == 0) {
+ if (_contentType.startsWith(F("application/x-www-form-urlencoded"))) {
+ _isPlainPost = true;
+ } else if (_contentType == F("text/plain") && __is_param_char(((char*)buf)[0])) {
+ size_t i = 0;
+ while (i < len && __is_param_char(((char*)buf)[i++]))
+ ;
+ if (i < len && ((char*)buf)[i - 1] == '=') {
+ _isPlainPost = true;
+ }
+ }
+ }
+ if (!_isPlainPost) {
+ // check if authenticated before calling the body
+ if (_handler)
+ _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
+ _parsedLength += len;
+ } else if (needParse) {
+ size_t i;
+ for (i = 0; i < len; i++) {
+ _parsedLength++;
+ _parsePlainPostChar(((uint8_t*)buf)[i]);
+ }
+ } else {
+ _parsedLength += len;
+ }
+ }
+ if (_parsedLength == _contentLength) {
+ _parseState = PARSE_REQ_END;
+ // check if authenticated before calling handleRequest and request auth instead
+ if (_handler)
+ _handler->handleRequest(this);
+ else
+ send(501);
+ }
+ }
+ break;
+ }
+}
+
+void AsyncWebServerRequest::_removeNotInterestingHeaders() {
+ if (std::any_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [](const String& str) { return str.equalsIgnoreCase(F("ANY")); }))
+ return; // nothing to do
+
+ for (auto iter = std::begin(_headers); iter != std::end(_headers);) {
+ const auto name = iter->name();
+
+ if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [&name](const String& str) { return str.equalsIgnoreCase(name); }))
+ iter = _headers.erase(iter);
+ else
+ iter++;
+ }
+}
+
+void AsyncWebServerRequest::_onPoll() {
+ // os_printf("p\n");
+ if (_response != NULL && _client != NULL && _client->canSend()) {
+ if (!_response->_finished()) {
+ _response->_ack(this, 0, 0);
+ } else {
+ AsyncWebServerResponse* r = _response;
+ _response = NULL;
+ delete r;
+
+ _client->close();
+ }
+ }
+}
+
+void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) {
+ // os_printf("a:%u:%u\n", len, time);
+ if (_response != NULL) {
+ if (!_response->_finished()) {
+ _response->_ack(this, len, time);
+ } else if (_response->_finished()) {
+ AsyncWebServerResponse* r = _response;
+ _response = NULL;
+ delete r;
+
+ _client->close();
+ }
+ }
+}
+
+void AsyncWebServerRequest::_onError(int8_t error) {
+ (void)error;
+}
+
+void AsyncWebServerRequest::_onTimeout(uint32_t time) {
+ (void)time;
+ // os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
+ _client->close();
+}
+
+void AsyncWebServerRequest::onDisconnect(ArDisconnectHandler fn) {
+ _onDisconnectfn = fn;
+}
+
+void AsyncWebServerRequest::_onDisconnect() {
+ // os_printf("d\n");
+ if (_onDisconnectfn) {
+ _onDisconnectfn();
+ }
+ _server->_handleDisconnect(this);
+}
+
+void AsyncWebServerRequest::_addPathParam(const char* p) {
+ _pathParams.emplace_back(p);
+}
+
+void AsyncWebServerRequest::_addGetParams(const String& params) {
+ size_t start = 0;
+ while (start < params.length()) {
+ int end = params.indexOf('&', start);
+ if (end < 0)
+ end = params.length();
+ int equal = params.indexOf('=', start);
+ if (equal < 0 || equal > end)
+ equal = end;
+ String name(params.substring(start, equal));
+ String value(equal + 1 < end ? params.substring(equal + 1, end) : String());
+ _params.emplace_back(urlDecode(name), urlDecode(value));
+ start = end + 1;
+ }
+}
+
+bool AsyncWebServerRequest::_parseReqHead() {
+ // Split the head into method, url and version
+ int index = _temp.indexOf(' ');
+ String m = _temp.substring(0, index);
+ index = _temp.indexOf(' ', index + 1);
+ String u = _temp.substring(m.length() + 1, index);
+ _temp = _temp.substring(index + 1);
+
+ if (m == F("GET")) {
+ _method = HTTP_GET;
+ } else if (m == F("POST")) {
+ _method = HTTP_POST;
+ } else if (m == F("DELETE")) {
+ _method = HTTP_DELETE;
+ } else if (m == F("PUT")) {
+ _method = HTTP_PUT;
+ } else if (m == F("PATCH")) {
+ _method = HTTP_PATCH;
+ } else if (m == F("HEAD")) {
+ _method = HTTP_HEAD;
+ } else if (m == F("OPTIONS")) {
+ _method = HTTP_OPTIONS;
+ }
+
+ String g;
+ index = u.indexOf('?');
+ if (index > 0) {
+ g = u.substring(index + 1);
+ u = u.substring(0, index);
+ }
+ _url = urlDecode(u);
+ _addGetParams(g);
+
+ if (!_temp.startsWith(F("HTTP/1.0")))
+ _version = 1;
+
+ _temp = String();
+ return true;
+}
+
+bool strContains(const String& src, const String& find, bool mindcase = true) {
+ int pos = 0, i = 0;
+ const int slen = src.length();
+ const int flen = find.length();
+
+ if (slen < flen)
+ return false;
+ while (pos <= (slen - flen)) {
+ for (i = 0; i < flen; i++) {
+ if (mindcase) {
+ if (src[pos + i] != find[i])
+ i = flen + 1; // no match
+ } else if (tolower(src[pos + i]) != tolower(find[i])) {
+ i = flen + 1; // no match
+ }
+ }
+ if (i == flen)
+ return true;
+ pos++;
+ }
+ return false;
+}
+
+bool AsyncWebServerRequest::_parseReqHeader() {
+ int index = _temp.indexOf(':');
+ if (index) {
+ String name = _temp.substring(0, index);
+ String value = _temp.substring(index + 2);
+ if (name.equalsIgnoreCase("Host")) {
+ _host = value;
+ } else if (name.equalsIgnoreCase(F("Content-Type"))) {
+ _contentType = value.substring(0, value.indexOf(';'));
+ if (value.startsWith(F("multipart/"))) {
+ _boundary = value.substring(value.indexOf('=') + 1);
+ _boundary.replace(String('"'), String());
+ _isMultipart = true;
+ }
+ } else if (name.equalsIgnoreCase(F("Content-Length"))) {
+ _contentLength = atoi(value.c_str());
+ } else if (name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")) {
+ _expectingContinue = true;
+ } else if (name.equalsIgnoreCase(F("Authorization"))) {
+ if (value.length() > 5 && value.substring(0, 5).equalsIgnoreCase(F("Basic"))) {
+ _authorization = value.substring(6);
+ } else if (value.length() > 6 && value.substring(0, 6).equalsIgnoreCase(F("Digest"))) {
+ _isDigest = true;
+ _authorization = value.substring(7);
+ }
+ } else {
+ if (name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))) {
+ // WebSocket request can be uniquely identified by header: [Upgrade: websocket]
+ _reqconntype = RCT_WS;
+ } else {
+ if (name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)) {
+ // WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
+ _reqconntype = RCT_EVENT;
+ }
+ }
+ }
+ _headers.emplace_back(name, value);
+ }
+ _temp = String();
+ return true;
+}
+
+void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
+ if (data && (char)data != '&')
+ _temp += (char)data;
+ if (!data || (char)data == '&' || _parsedLength == _contentLength) {
+ String name = F("body");
+ String value = _temp;
+ if (!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0) {
+ name = _temp.substring(0, _temp.indexOf('='));
+ value = _temp.substring(_temp.indexOf('=') + 1);
+ }
+ _params.emplace_back(urlDecode(name), urlDecode(value), true);
+ _temp = String();
+ }
+}
+
+void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
+ _itemBuffer[_itemBufferIndex++] = data;
+
+ if (last || _itemBufferIndex == 1460) {
+ // check if authenticated before calling the upload
+ if (_handler)
+ _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
+ _itemBufferIndex = 0;
+ }
+}
+
+enum {
+ EXPECT_BOUNDARY,
+ PARSE_HEADERS,
+ WAIT_FOR_RETURN1,
+ EXPECT_FEED1,
+ EXPECT_DASH1,
+ EXPECT_DASH2,
+ BOUNDARY_OR_DATA,
+ DASH3_OR_RETURN2,
+ EXPECT_FEED2,
+ PARSING_FINISHED,
+ PARSE_ERROR
+};
+
+void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
+#define itemWriteByte(b) \
+ do { \
+ _itemSize++; \
+ if (_itemIsFile) \
+ _handleUploadByte(b, last); \
+ else \
+ _itemValue += (char)(b); \
+ } while (0)
+
+ if (!_parsedLength) {
+ _multiParseState = EXPECT_BOUNDARY;
+ _temp = String();
+ _itemName = String();
+ _itemFilename = String();
+ _itemType = String();
+ }
+
+ if (_multiParseState == WAIT_FOR_RETURN1) {
+ if (data != '\r') {
+ itemWriteByte(data);
+ } else {
+ _multiParseState = EXPECT_FEED1;
+ }
+ } else if (_multiParseState == EXPECT_BOUNDARY) {
+ if (_parsedLength < 2 && data != '-') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data) {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 2 == _boundary.length() && data != '\r') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 3 == _boundary.length()) {
+ if (data != '\n') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ }
+ _multiParseState = PARSE_HEADERS;
+ _itemIsFile = false;
+ }
+ } else if (_multiParseState == PARSE_HEADERS) {
+ if ((char)data != '\r' && (char)data != '\n')
+ _temp += (char)data;
+ if ((char)data == '\n') {
+ if (_temp.length()) {
+ if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))) {
+ _itemType = _temp.substring(14);
+ _itemIsFile = true;
+ } else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
+ _temp = _temp.substring(_temp.indexOf(';') + 2);
+ while (_temp.indexOf(';') > 0) {
+ String name = _temp.substring(0, _temp.indexOf('='));
+ String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
+ if (name == F("name")) {
+ _itemName = nameVal;
+ } else if (name == F("filename")) {
+ _itemFilename = nameVal;
+ _itemIsFile = true;
+ }
+ _temp = _temp.substring(_temp.indexOf(';') + 2);
+ }
+ String name = _temp.substring(0, _temp.indexOf('='));
+ String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
+ if (name == F("name")) {
+ _itemName = nameVal;
+ } else if (name == F("filename")) {
+ _itemFilename = nameVal;
+ _itemIsFile = true;
+ }
+ }
+ _temp = String();
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ // value starts from here
+ _itemSize = 0;
+ _itemStartIndex = _parsedLength;
+ _itemValue = String();
+ if (_itemIsFile) {
+ if (_itemBuffer)
+ free(_itemBuffer);
+ _itemBuffer = (uint8_t*)malloc(1460);
+ if (_itemBuffer == NULL) {
+ _multiParseState = PARSE_ERROR;
+ return;
+ }
+ _itemBufferIndex = 0;
+ }
+ }
+ }
+ } else if (_multiParseState == EXPECT_FEED1) {
+ if (data != '\n') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = EXPECT_DASH1;
+ }
+ } else if (_multiParseState == EXPECT_DASH1) {
+ if (data != '-') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = EXPECT_DASH2;
+ }
+ } else if (_multiParseState == EXPECT_DASH2) {
+ if (data != '-') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = BOUNDARY_OR_DATA;
+ _boundaryPosition = 0;
+ }
+ } else if (_multiParseState == BOUNDARY_OR_DATA) {
+ if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data) {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t i;
+ for (i = 0; i < _boundaryPosition; i++)
+ itemWriteByte(_boundary.c_str()[i]);
+ _parseMultipartPostByte(data, last);
+ } else if (_boundaryPosition == _boundary.length() - 1) {
+ _multiParseState = DASH3_OR_RETURN2;
+ if (!_itemIsFile) {
+ _params.emplace_back(_itemName, _itemValue, true);
+ } else {
+ if (_itemSize) {
+ // check if authenticated before calling the upload
+ if (_handler)
+ _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
+ _itemBufferIndex = 0;
+ _params.emplace_back(_itemName, _itemFilename, true, true, _itemSize);
+ }
+ free(_itemBuffer);
+ _itemBuffer = NULL;
+ }
+
+ } else {
+ _boundaryPosition++;
+ }
+ } else if (_multiParseState == DASH3_OR_RETURN2) {
+ if (data == '-' && (_contentLength - _parsedLength - 4) != 0) {
+ // os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
+ _contentLength = _parsedLength + 4; // lets close the request gracefully
+ }
+ if (data == '\r') {
+ _multiParseState = EXPECT_FEED2;
+ } else if (data == '-' && _contentLength == (_parsedLength + 4)) {
+ _multiParseState = PARSING_FINISHED;
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t i;
+ for (i = 0; i < _boundary.length(); i++)
+ itemWriteByte(_boundary.c_str()[i]);
+ _parseMultipartPostByte(data, last);
+ }
+ } else if (_multiParseState == EXPECT_FEED2) {
+ if (data == '\n') {
+ _multiParseState = PARSE_HEADERS;
+ _itemIsFile = false;
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t i;
+ for (i = 0; i < _boundary.length(); i++)
+ itemWriteByte(_boundary.c_str()[i]);
+ itemWriteByte('\r');
+ _parseMultipartPostByte(data, last);
+ }
+ }
+}
+
+void AsyncWebServerRequest::_parseLine() {
+ if (_parseState == PARSE_REQ_START) {
+ if (!_temp.length()) {
+ _parseState = PARSE_REQ_FAIL;
+ _client->close();
+ } else {
+ _parseReqHead();
+ _parseState = PARSE_REQ_HEADERS;
+ }
+ return;
+ }
+
+ if (_parseState == PARSE_REQ_HEADERS) {
+ if (!_temp.length()) {
+ // end of headers
+ _server->_rewriteRequest(this);
+ _server->_attachHandler(this);
+ _removeNotInterestingHeaders();
+ if (_expectingContinue) {
+ String response = F("HTTP/1.1 100 Continue\r\n\r\n");
+ _client->write(response.c_str(), response.length());
+ }
+ // check handler for authentication
+ if (_contentLength) {
+ _parseState = PARSE_REQ_BODY;
+ } else {
+ _parseState = PARSE_REQ_END;
+ if (_handler)
+ _handler->handleRequest(this);
+ else
+ send(501);
+ }
+ } else
+ _parseReqHeader();
+ }
+}
+
+size_t AsyncWebServerRequest::headers() const {
+ return _headers.size();
+}
+
+bool AsyncWebServerRequest::hasHeader(const char* name) const {
+ for (const auto& h : _headers) {
+ if (h.name().equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef ESP8266
+bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const {
+ return hasHeader(String(data));
+}
+#endif
+
+const AsyncWebHeader* AsyncWebServerRequest::getHeader(const char* name) const {
+ auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
+
+ return (iter == std::end(_headers)) ? nullptr : &(*iter);
+}
+
+#ifdef ESP8266
+const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper* data) const {
+ PGM_P p = reinterpret_cast(data);
+ size_t n = strlen_P(p);
+ char* name = (char*)malloc(n + 1);
+ if (name) {
+ strcpy_P(name, p);
+ const AsyncWebHeader* result = getHeader(String(name));
+ free(name);
+ return result;
+ } else {
+ return nullptr;
+ }
+}
+#endif
+
+const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
+ if (num >= _headers.size())
+ return nullptr;
+ return &(*std::next(_headers.cbegin(), num));
+}
+
+size_t AsyncWebServerRequest::params() const {
+ return _params.size();
+}
+
+bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
+ for (const auto& p : _params) {
+ if (p.name() == name && p.isPost() == post && p.isFile() == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AsyncWebServerRequest::hasParam(const __FlashStringHelper* data, bool post, bool file) const {
+ return hasParam(String(data).c_str(), post, file);
+}
+
+const AsyncWebParameter* AsyncWebServerRequest::getParam(const char* name, bool post, bool file) const {
+ for (const auto& p : _params) {
+ if (p.name() == name && p.isPost() == post && p.isFile() == file) {
+ return &p;
+ }
+ }
+ return nullptr;
+}
+
+#ifdef ESP8266
+const AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper* data, bool post, bool file) const {
+ return getParam(String(data), post, file);
+}
+#endif
+
+const AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
+ if (num >= _params.size())
+ return nullptr;
+ return &(*std::next(_params.cbegin(), num));
+}
+
+void AsyncWebServerRequest::addInterestingHeader(const char* name) {
+ if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [&name](const String& str) { return str.equalsIgnoreCase(name); }))
+ _interestingHeaders.emplace_back(name);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content) {
+ return new AsyncBasicResponse(code, contentType, content);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) {
+ return new AsyncProgmemResponse(code, contentType, content, len, callback);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) {
+ return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen_P(content), callback);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) {
+ if (fs.exists(path) || (!download && fs.exists(path + F(".gz"))))
+ return new AsyncFileResponse(fs, path, contentType, download, callback);
+ return NULL;
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) {
+ if (content == true)
+ return new AsyncFileResponse(content, path, contentType, download, callback);
+ return NULL;
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) {
+ return new AsyncStreamResponse(stream, contentType, len, callback);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
+ return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
+}
+
+AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
+ if (_version)
+ return new AsyncChunkedResponse(contentType, callback, templateCallback);
+ return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
+}
+
+AsyncResponseStream* AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize) {
+ return new AsyncResponseStream(contentType, bufferSize);
+}
+
+void AsyncWebServerRequest::send(AsyncWebServerResponse* response) {
+ _response = response;
+ if (_response == NULL) {
+ _client->close(true);
+ _onDisconnect();
+ return;
+ }
+ if (!_response->_sourceValid()) {
+ delete response;
+ _response = NULL;
+ send(500);
+ } else {
+ _client->setRxTimeout(0);
+ _response->_respond(this);
+ }
+}
+
+void AsyncWebServerRequest::send(int code, const String& contentType, const String& content) {
+ send(beginResponse(code, contentType, content));
+}
+
+void AsyncWebServerRequest::send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) {
+ send(beginResponse(code, contentType, content, len, callback));
+}
+
+void AsyncWebServerRequest::send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) {
+ send(beginResponse(code, contentType, content, callback));
+}
+
+void AsyncWebServerRequest::send(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) {
+ if (fs.exists(path) || (!download && fs.exists(path + F(".gz")))) {
+ send(beginResponse(fs, path, contentType, download, callback));
+ } else
+ send(404);
+}
+
+void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) {
+ if (content == true) {
+ send(beginResponse(content, path, contentType, download, callback));
+ } else
+ send(404);
+}
+
+void AsyncWebServerRequest::send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) {
+ send(beginResponse(stream, contentType, len, callback));
+}
+
+void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
+ send(beginResponse(contentType, len, callback, templateCallback));
+}
+
+void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
+ send(beginChunkedResponse(contentType, callback, templateCallback));
+}
+
+void AsyncWebServerRequest::redirect(const char* url) {
+ AsyncWebServerResponse* response = beginResponse(302);
+ response->addHeader(F("Location"), url);
+ send(response);
+}
+
+bool AsyncWebServerRequest::authenticate(const char* username, const char* password, const char* realm, bool passwordIsHash) {
+ if (_authorization.length()) {
+ if (_isDigest)
+ return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
+ else if (!passwordIsHash)
+ return checkBasicAuthentication(_authorization.c_str(), username, password);
+ else
+ return _authorization.equals(password);
+ }
+ return false;
+}
+
+bool AsyncWebServerRequest::authenticate(const char* hash) {
+ if (!_authorization.length() || hash == NULL)
+ return false;
+
+ if (_isDigest) {
+ String hStr = String(hash);
+ int separator = hStr.indexOf(':');
+ if (separator <= 0)
+ return false;
+ String username = hStr.substring(0, separator);
+ hStr = hStr.substring(separator + 1);
+ separator = hStr.indexOf(':');
+ if (separator <= 0)
+ return false;
+ String realm = hStr.substring(0, separator);
+ hStr = hStr.substring(separator + 1);
+ return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
+ }
+
+ return (_authorization.equals(hash));
+}
+
+void AsyncWebServerRequest::requestAuthentication(const char* realm, bool isDigest) {
+ AsyncWebServerResponse* r = beginResponse(401);
+ if (!isDigest && realm == NULL) {
+ r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
+ } else if (!isDigest) {
+ String header = F("Basic realm=\"");
+ header.concat(realm);
+ header += '"';
+ r->addHeader(F("WWW-Authenticate"), header);
+ } else {
+ String header = F("Digest ");
+ header.concat(requestDigestAuthentication(realm));
+ r->addHeader(F("WWW-Authenticate"), header);
+ }
+ send(r);
+}
+
+bool AsyncWebServerRequest::hasArg(const char* name) const {
+ for (const auto& arg : _params) {
+ if (arg.name() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef ESP8266
+bool AsyncWebServerRequest::hasArg(const __FlashStringHelper* data) const {
+ return hasArg(String(data).c_str());
+}
+#endif
+
+const String& AsyncWebServerRequest::arg(const char* name) const {
+ for (const auto& arg : _params) {
+ if (arg.name() == name) {
+ return arg.value();
+ }
+ }
+ return emptyString;
+}
+
+#ifdef ESP8266
+const String& AsyncWebServerRequest::arg(const __FlashStringHelper* data) const {
+ return arg(String(data).c_str());
+}
+#endif
+
+const String& AsyncWebServerRequest::arg(size_t i) const {
+ return getParam(i)->value();
+}
+
+const String& AsyncWebServerRequest::argName(size_t i) const {
+ return getParam(i)->name();
+}
+
+const String& AsyncWebServerRequest::pathArg(size_t i) const {
+ return i < _pathParams.size() ? _pathParams[i] : emptyString;
+}
+
+const String& AsyncWebServerRequest::header(const char* name) const {
+ const AsyncWebHeader* h = getHeader(name);
+ return h ? h->value() : emptyString;
+}
+
+#ifdef ESP8266
+const String& AsyncWebServerRequest::header(const __FlashStringHelper* data) const {
+ return header(String(data).c_str());
+};
+#endif
+
+const String& AsyncWebServerRequest::header(size_t i) const {
+ const AsyncWebHeader* h = getHeader(i);
+ return h ? h->value() : emptyString;
+}
+
+const String& AsyncWebServerRequest::headerName(size_t i) const {
+ const AsyncWebHeader* h = getHeader(i);
+ return h ? h->name() : emptyString;
+}
+
+String AsyncWebServerRequest::urlDecode(const String& text) const {
+ char temp[] = "0x00";
+ unsigned int len = text.length();
+ unsigned int i = 0;
+ String decoded;
+ decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
+ while (i < len) {
+ char decodedChar;
+ char encodedChar = text.charAt(i++);
+ if ((encodedChar == '%') && (i + 1 < len)) {
+ temp[2] = text.charAt(i++);
+ temp[3] = text.charAt(i++);
+ decodedChar = strtol(temp, NULL, 16);
+ } else if (encodedChar == '+') {
+ decodedChar = ' ';
+ } else {
+ decodedChar = encodedChar; // normal ascii char
+ }
+ decoded.concat(decodedChar);
+ }
+ return decoded;
+}
+
+#ifndef ESP8266
+const char* AsyncWebServerRequest::methodToString() const {
+ if (_method == HTTP_ANY)
+ return "ANY";
+ if (_method & HTTP_GET)
+ return "GET";
+ if (_method & HTTP_POST)
+ return "POST";
+ if (_method & HTTP_DELETE)
+ return "DELETE";
+ if (_method & HTTP_PUT)
+ return "PUT";
+ if (_method & HTTP_PATCH)
+ return "PATCH";
+ if (_method & HTTP_HEAD)
+ return "HEAD";
+ if (_method & HTTP_OPTIONS)
+ return "OPTIONS";
+ return "UNKNOWN";
+}
+
+const char* AsyncWebServerRequest::requestedConnTypeToString() const {
+ switch (_reqconntype) {
+ case RCT_NOT_USED:
+ return "RCT_NOT_USED";
+ case RCT_DEFAULT:
+ return "RCT_DEFAULT";
+ case RCT_HTTP:
+ return "RCT_HTTP";
+ case RCT_WS:
+ return "RCT_WS";
+ case RCT_EVENT:
+ return "RCT_EVENT";
+ default:
+ return "ERROR";
+ }
+}
+#endif
+
+#ifdef ESP8266
+const __FlashStringHelper* AsyncWebServerRequest::methodToString() const {
+ if (_method == HTTP_ANY)
+ return F("ANY");
+ else if (_method & HTTP_GET)
+ return F("GET");
+ else if (_method & HTTP_POST)
+ return F("POST");
+ else if (_method & HTTP_DELETE)
+ return F("DELETE");
+ else if (_method & HTTP_PUT)
+ return F("PUT");
+ else if (_method & HTTP_PATCH)
+ return F("PATCH");
+ else if (_method & HTTP_HEAD)
+ return F("HEAD");
+ else if (_method & HTTP_OPTIONS)
+ return F("OPTIONS");
+ return F("UNKNOWN");
+}
+
+const __FlashStringHelper* AsyncWebServerRequest::requestedConnTypeToString() const {
+ switch (_reqconntype) {
+ case RCT_NOT_USED:
+ return F("RCT_NOT_USED");
+ case RCT_DEFAULT:
+ return F("RCT_DEFAULT");
+ case RCT_HTTP:
+ return F("RCT_HTTP");
+ case RCT_WS:
+ return F("RCT_WS");
+ case RCT_EVENT:
+ return F("RCT_EVENT");
+ default:
+ return F("ERROR");
+ }
+}
+#endif
+
+bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) {
+ bool res = false;
+ if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype))
+ res = true;
+ if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype))
+ res = true;
+ if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype))
+ res = true;
+ return res;
+}
diff --git a/lib/ESP Async WebServer/src/WebResponseImpl.h b/lib/ESP Async WebServer/src/WebResponseImpl.h
new file mode 100644
index 0000000..26ec223
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebResponseImpl.h
@@ -0,0 +1,148 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
+#define ASYNCWEBSERVERRESPONSEIMPL_H_
+
+#ifdef Arduino_h
+ // arduino is not compatible with std::vector
+ #undef min
+ #undef max
+#endif
+#include
+#include
+
+// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
+
+class AsyncBasicResponse : public AsyncWebServerResponse {
+ private:
+ String _content;
+
+ public:
+ AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String());
+ void _respond(AsyncWebServerRequest* request);
+ size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
+ bool _sourceValid() const { return true; }
+};
+
+class AsyncAbstractResponse : public AsyncWebServerResponse {
+ private:
+ String _head;
+ // Data is inserted into cache at begin().
+ // This is inefficient with vector, but if we use some other container,
+ // we won't be able to access it as contiguous array of bytes when reading from it,
+ // so by gaining performance in one place, we'll lose it in another.
+ std::vector _cache;
+ size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
+ size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
+
+ protected:
+ AwsTemplateProcessor _callback;
+
+ public:
+ AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
+ void _respond(AsyncWebServerRequest* request);
+ size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
+ bool _sourceValid() const { return false; }
+ virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
+};
+
+#ifndef TEMPLATE_PLACEHOLDER
+ #define TEMPLATE_PLACEHOLDER '%'
+#endif
+
+#define TEMPLATE_PARAM_NAME_LENGTH 32
+class AsyncFileResponse : public AsyncAbstractResponse {
+ using File = fs::File;
+ using FS = fs::FS;
+
+ private:
+ File _content;
+ String _path;
+ void _setContentType(const String& path);
+
+ public:
+ AsyncFileResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ AsyncFileResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
+ ~AsyncFileResponse();
+ bool _sourceValid() const { return !!(_content); }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+};
+
+class AsyncStreamResponse : public AsyncAbstractResponse {
+ private:
+ Stream* _content;
+
+ public:
+ AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
+ bool _sourceValid() const { return !!(_content); }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+};
+
+class AsyncCallbackResponse : public AsyncAbstractResponse {
+ private:
+ AwsResponseFiller _content;
+ size_t _filledLength;
+
+ public:
+ AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ bool _sourceValid() const { return !!(_content); }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+};
+
+class AsyncChunkedResponse : public AsyncAbstractResponse {
+ private:
+ AwsResponseFiller _content;
+ size_t _filledLength;
+
+ public:
+ AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ bool _sourceValid() const { return !!(_content); }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+};
+
+class AsyncProgmemResponse : public AsyncAbstractResponse {
+ private:
+ const uint8_t* _content;
+ size_t _readLength;
+
+ public:
+ AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
+ bool _sourceValid() const { return true; }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+};
+
+class cbuf;
+
+class AsyncResponseStream : public AsyncAbstractResponse, public Print {
+ private:
+ std::unique_ptr _content;
+
+ public:
+ AsyncResponseStream(const String& contentType, size_t bufferSize);
+ ~AsyncResponseStream();
+ bool _sourceValid() const { return (_state < RESPONSE_END); }
+ virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
+ size_t write(const uint8_t* data, size_t len);
+ size_t write(uint8_t data);
+ using Print::write;
+};
+
+#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
diff --git a/lib/ESP Async WebServer/src/WebResponses.cpp b/lib/ESP Async WebServer/src/WebResponses.cpp
new file mode 100644
index 0000000..1fd41fa
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebResponses.cpp
@@ -0,0 +1,751 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "ESPAsyncWebServer.h"
+#include "WebResponseImpl.h"
+#include "cbuf.h"
+
+// Since ESP8266 does not link memchr by default, here's its implementation.
+void* memchr(void* ptr, int ch, size_t count) {
+ unsigned char* p = static_cast(ptr);
+ while (count--)
+ if (*p++ == static_cast(ch))
+ return --p;
+ return nullptr;
+}
+
+/*
+ * Abstract Response
+ * */
+const char* AsyncWebServerResponse::_responseCodeToString(int code) {
+ return reinterpret_cast(responseCodeToString(code));
+}
+
+const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) {
+ switch (code) {
+ case 100:
+ return F("Continue");
+ case 101:
+ return F("Switching Protocols");
+ case 200:
+ return F("OK");
+ case 201:
+ return F("Created");
+ case 202:
+ return F("Accepted");
+ case 203:
+ return F("Non-Authoritative Information");
+ case 204:
+ return F("No Content");
+ case 205:
+ return F("Reset Content");
+ case 206:
+ return F("Partial Content");
+ case 300:
+ return F("Multiple Choices");
+ case 301:
+ return F("Moved Permanently");
+ case 302:
+ return F("Found");
+ case 303:
+ return F("See Other");
+ case 304:
+ return F("Not Modified");
+ case 305:
+ return F("Use Proxy");
+ case 307:
+ return F("Temporary Redirect");
+ case 400:
+ return F("Bad Request");
+ case 401:
+ return F("Unauthorized");
+ case 402:
+ return F("Payment Required");
+ case 403:
+ return F("Forbidden");
+ case 404:
+ return F("Not Found");
+ case 405:
+ return F("Method Not Allowed");
+ case 406:
+ return F("Not Acceptable");
+ case 407:
+ return F("Proxy Authentication Required");
+ case 408:
+ return F("Request Time-out");
+ case 409:
+ return F("Conflict");
+ case 410:
+ return F("Gone");
+ case 411:
+ return F("Length Required");
+ case 412:
+ return F("Precondition Failed");
+ case 413:
+ return F("Request Entity Too Large");
+ case 414:
+ return F("Request-URI Too Large");
+ case 415:
+ return F("Unsupported Media Type");
+ case 416:
+ return F("Requested range not satisfiable");
+ case 417:
+ return F("Expectation Failed");
+ case 500:
+ return F("Internal Server Error");
+ case 501:
+ return F("Not Implemented");
+ case 502:
+ return F("Bad Gateway");
+ case 503:
+ return F("Service Unavailable");
+ case 504:
+ return F("Gateway Time-out");
+ case 505:
+ return F("HTTP Version not supported");
+ default:
+ return F("");
+ }
+}
+
+AsyncWebServerResponse::AsyncWebServerResponse()
+ : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
+ for (const auto& header : DefaultHeaders::Instance()) {
+ _headers.emplace_back(header);
+ }
+}
+
+AsyncWebServerResponse::~AsyncWebServerResponse() = default;
+
+void AsyncWebServerResponse::setCode(int code) {
+ if (_state == RESPONSE_SETUP)
+ _code = code;
+}
+
+void AsyncWebServerResponse::setContentLength(size_t len) {
+ if (_state == RESPONSE_SETUP)
+ _contentLength = len;
+}
+
+void AsyncWebServerResponse::setContentType(const String& type) {
+ if (_state == RESPONSE_SETUP)
+ _contentType = type;
+}
+
+void AsyncWebServerResponse::addHeader(const String& name, const String& value) {
+ _headers.emplace_back(name, value);
+}
+
+String AsyncWebServerResponse::_assembleHead(uint8_t version) {
+ if (version) {
+ addHeader(F("Accept-Ranges"), F("none"));
+ if (_chunked)
+ addHeader(F("Transfer-Encoding"), F("chunked"));
+ }
+ String out = String();
+ int bufSize = 300;
+ char buf[bufSize];
+
+ snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
+ out.concat(buf);
+
+ if (_sendContentLength) {
+ snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
+ out.concat(buf);
+ }
+ if (_contentType.length()) {
+ snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
+ out.concat(buf);
+ }
+
+ for (const auto& header : _headers) {
+ snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
+ out.concat(buf);
+ }
+ _headers.clear();
+
+ out.concat(F("\r\n"));
+ _headLength = out.length();
+ return out;
+}
+
+bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
+bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
+bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
+bool AsyncWebServerResponse::_sourceValid() const { return false; }
+void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) {
+ _state = RESPONSE_END;
+ request->client()->close();
+}
+size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
+ (void)request;
+ (void)len;
+ (void)time;
+ return 0;
+}
+
+/*
+ * String/Code Response
+ * */
+AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content) {
+ _code = code;
+ _content = content;
+ _contentType = contentType;
+ if (_content.length()) {
+ _contentLength = _content.length();
+ if (!_contentType.length())
+ _contentType = F("text/plain");
+ }
+ addHeader(F("Connection"), F("close"));
+}
+
+void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
+ _state = RESPONSE_HEADERS;
+ String out = _assembleHead(request->version());
+ size_t outLen = out.length();
+ size_t space = request->client()->space();
+ if (!_contentLength && space >= outLen) {
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_WAIT_ACK;
+ } else if (_contentLength && space >= outLen + _contentLength) {
+ out += _content;
+ outLen += _contentLength;
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_WAIT_ACK;
+ } else if (space && space < outLen) {
+ String partial = out.substring(0, space);
+ _content = out.substring(space) + _content;
+ _contentLength += outLen - space;
+ _writtenLength += request->client()->write(partial.c_str(), partial.length());
+ _state = RESPONSE_CONTENT;
+ } else if (space > outLen && space < (outLen + _contentLength)) {
+ size_t shift = space - outLen;
+ outLen += shift;
+ _sentLength += shift;
+ out += _content.substring(0, shift);
+ _content = _content.substring(shift);
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_CONTENT;
+ } else {
+ _content = out + _content;
+ _contentLength += outLen;
+ _state = RESPONSE_CONTENT;
+ }
+}
+
+size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
+ (void)time;
+ _ackedLength += len;
+ if (_state == RESPONSE_CONTENT) {
+ size_t available = _contentLength - _sentLength;
+ size_t space = request->client()->space();
+ // we can fit in this packet
+ if (space > available) {
+ _writtenLength += request->client()->write(_content.c_str(), available);
+ _content = String();
+ _state = RESPONSE_WAIT_ACK;
+ return available;
+ }
+ // send some data, the rest on ack
+ String out = _content.substring(0, space);
+ _content = _content.substring(space);
+ _sentLength += space;
+ _writtenLength += request->client()->write(out.c_str(), space);
+ return space;
+ } else if (_state == RESPONSE_WAIT_ACK) {
+ if (_ackedLength >= _writtenLength) {
+ _state = RESPONSE_END;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Abstract Response
+ * */
+
+AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
+ // In case of template processing, we're unable to determine real response size
+ if (callback) {
+ _contentLength = 0;
+ _sendContentLength = false;
+ _chunked = true;
+ }
+}
+
+void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
+ addHeader(F("Connection"), F("close"));
+ _head = _assembleHead(request->version());
+ _state = RESPONSE_HEADERS;
+ _ack(request, 0, 0);
+}
+
+size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
+ (void)time;
+ if (!_sourceValid()) {
+ _state = RESPONSE_FAILED;
+ request->client()->close();
+ return 0;
+ }
+ _ackedLength += len;
+ size_t space = request->client()->space();
+
+ size_t headLen = _head.length();
+ if (_state == RESPONSE_HEADERS) {
+ if (space >= headLen) {
+ _state = RESPONSE_CONTENT;
+ space -= headLen;
+ } else {
+ String out = _head.substring(0, space);
+ _head = _head.substring(space);
+ _writtenLength += request->client()->write(out.c_str(), out.length());
+ return out.length();
+ }
+ }
+
+ if (_state == RESPONSE_CONTENT) {
+ size_t outLen;
+ if (_chunked) {
+ if (space <= 8) {
+ return 0;
+ }
+ outLen = space;
+ } else if (!_sendContentLength) {
+ outLen = space;
+ } else {
+ outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
+ }
+
+ uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
+ if (!buf) {
+ // os_printf("_ack malloc %d failed\n", outLen+headLen);
+ return 0;
+ }
+
+ if (headLen) {
+ memcpy(buf, _head.c_str(), _head.length());
+ }
+
+ size_t readLen = 0;
+
+ if (_chunked) {
+ // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
+ // See RFC2616 sections 2, 3.6.1.
+ readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
+ if (readLen == RESPONSE_TRY_AGAIN) {
+ free(buf);
+ return 0;
+ }
+ outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen;
+ while (outLen < headLen + 4)
+ buf[outLen++] = ' ';
+ buf[outLen++] = '\r';
+ buf[outLen++] = '\n';
+ outLen += readLen;
+ buf[outLen++] = '\r';
+ buf[outLen++] = '\n';
+ } else {
+ readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
+ if (readLen == RESPONSE_TRY_AGAIN) {
+ free(buf);
+ return 0;
+ }
+ outLen = readLen + headLen;
+ }
+
+ if (headLen) {
+ _head = String();
+ }
+
+ if (outLen) {
+ _writtenLength += request->client()->write((const char*)buf, outLen);
+ }
+
+ if (_chunked) {
+ _sentLength += readLen;
+ } else {
+ _sentLength += outLen - headLen;
+ }
+
+ free(buf);
+
+ if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
+ _state = RESPONSE_WAIT_ACK;
+ }
+ return outLen;
+
+ } else if (_state == RESPONSE_WAIT_ACK) {
+ if (!_sendContentLength || _ackedLength >= _writtenLength) {
+ _state = RESPONSE_END;
+ if (!_chunked && !_sendContentLength)
+ request->client()->close(true);
+ }
+ }
+ return 0;
+}
+
+size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) {
+ // If we have something in cache, copy it to buffer
+ const size_t readFromCache = std::min(len, _cache.size());
+ if (readFromCache) {
+ memcpy(data, _cache.data(), readFromCache);
+ _cache.erase(_cache.begin(), _cache.begin() + readFromCache);
+ }
+ // If we need to read more...
+ const size_t needFromFile = len - readFromCache;
+ const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
+ return readFromCache + readFromContent;
+}
+
+size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
+ if (!_callback)
+ return _fillBuffer(data, len);
+
+ const size_t originalLen = len;
+ len = _readDataFromCacheOrContent(data, len);
+ // Now we've read 'len' bytes, either from cache or from file
+ // Search for template placeholders
+ uint8_t* pTemplateStart = data;
+ while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
+ uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
+ // temporary buffer to hold parameter name
+ uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
+ String paramName;
+ // If closing placeholder is found:
+ if (pTemplateEnd) {
+ // prepare argument to callback
+ const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
+ if (paramNameLength) {
+ memcpy(buf, pTemplateStart + 1, paramNameLength);
+ buf[paramNameLength] = 0;
+ paramName = String(reinterpret_cast(buf));
+ } else { // double percent sign encountered, this is single percent sign escaped.
+ // remove the 2nd percent sign
+ memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
+ len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
+ ++pTemplateStart;
+ }
+ } else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
+ memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
+ const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
+ if (readFromCacheOrContent) {
+ pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
+ if (pTemplateEnd) {
+ // prepare argument to callback
+ *pTemplateEnd = 0;
+ paramName = String(reinterpret_cast(buf));
+ // Copy remaining read-ahead data into cache
+ _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
+ pTemplateEnd = &data[len - 1];
+ } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
+ {
+ // but first, store read file data in cache
+ _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
+ ++pTemplateStart;
+ }
+ } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
+ ++pTemplateStart;
+ } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
+ ++pTemplateStart;
+ if (paramName.length()) {
+ // call callback and replace with result.
+ // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
+ // Data after pTemplateEnd may need to be moved.
+ // The first byte of data after placeholder is located at pTemplateEnd + 1.
+ // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
+ const String paramValue(_callback(paramName));
+ const char* pvstr = paramValue.c_str();
+ const unsigned int pvlen = paramValue.length();
+ const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1));
+ // make room for param value
+ // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
+ if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
+ _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
+ // 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
+ memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
+ len = originalLen; // fix issue with truncated data, not sure if it has any side effects
+ } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
+ // 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
+ // Move the entire data after the placeholder
+ memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
+ // 3. replace placeholder with actual value
+ memcpy(pTemplateStart, pvstr, numBytesCopied);
+ // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
+ if (numBytesCopied < pvlen) {
+ _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
+ } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
+ // there is some free room, fill it from cache
+ const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
+ const size_t totalFreeRoom = originalLen - len + roomFreed;
+ len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
+ } else { // result is copied fully; it is longer than placeholder text
+ const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
+ len = std::min(len + roomTaken, originalLen);
+ }
+ }
+ } // while(pTemplateStart)
+ return len;
+}
+
+/*
+ * File Response
+ * */
+
+AsyncFileResponse::~AsyncFileResponse() {
+ if (_content)
+ _content.close();
+}
+
+void AsyncFileResponse::_setContentType(const String& path) {
+#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
+ extern const __FlashStringHelper* getContentType(const String& path);
+ _contentType = getContentType(path);
+#else
+ if (path.endsWith(F(".html")))
+ _contentType = F("text/html");
+ else if (path.endsWith(F(".htm")))
+ _contentType = F("text/html");
+ else if (path.endsWith(F(".css")))
+ _contentType = F("text/css");
+ else if (path.endsWith(F(".json")))
+ _contentType = F("application/json");
+ else if (path.endsWith(F(".js")))
+ _contentType = F("application/javascript");
+ else if (path.endsWith(F(".png")))
+ _contentType = F("image/png");
+ else if (path.endsWith(F(".gif")))
+ _contentType = F("image/gif");
+ else if (path.endsWith(F(".jpg")))
+ _contentType = F("image/jpeg");
+ else if (path.endsWith(F(".ico")))
+ _contentType = F("image/x-icon");
+ else if (path.endsWith(F(".svg")))
+ _contentType = F("image/svg+xml");
+ else if (path.endsWith(F(".eot")))
+ _contentType = F("font/eot");
+ else if (path.endsWith(F(".woff")))
+ _contentType = F("font/woff");
+ else if (path.endsWith(F(".woff2")))
+ _contentType = F("font/woff2");
+ else if (path.endsWith(F(".ttf")))
+ _contentType = F("font/ttf");
+ else if (path.endsWith(F(".xml")))
+ _contentType = F("text/xml");
+ else if (path.endsWith(F(".pdf")))
+ _contentType = F("application/pdf");
+ else if (path.endsWith(F(".zip")))
+ _contentType = F("application/zip");
+ else if (path.endsWith(F(".gz")))
+ _contentType = F("application/x-gzip");
+ else
+ _contentType = F("text/plain");
+#endif
+}
+
+AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _path = path;
+
+ if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) {
+ _path = _path + F(".gz");
+ addHeader(F("Content-Encoding"), F("gzip"));
+ _callback = nullptr; // Unable to process zipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = fs.open(_path, fs::FileOpenMode::read);
+ _contentLength = _content.size();
+
+ if (contentType.length() == 0)
+ _setContentType(path);
+ else
+ _contentType = contentType;
+
+ int filenameStart = path.lastIndexOf('/') + 1;
+ char buf[26 + path.length() - filenameStart];
+ char* filename = (char*)path.c_str() + filenameStart;
+
+ if (download) {
+ // set filename and force download
+ snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
+ } else {
+ // set filename and force rendering
+ snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ }
+ addHeader(F("Content-Disposition"), buf);
+}
+
+AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _path = path;
+
+ if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) {
+ addHeader(F("Content-Encoding"), F("gzip"));
+ _callback = nullptr; // Unable to process gzipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = content;
+ _contentLength = _content.size();
+
+ if (contentType.length() == 0)
+ _setContentType(path);
+ else
+ _contentType = contentType;
+
+ int filenameStart = path.lastIndexOf('/') + 1;
+ char buf[26 + path.length() - filenameStart];
+ char* filename = (char*)path.c_str() + filenameStart;
+
+ if (download) {
+ snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
+ } else {
+ snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ }
+ addHeader(F("Content-Disposition"), buf);
+}
+
+size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
+ return _content.read(data, len);
+}
+
+/*
+ * Stream Response
+ * */
+
+AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _content = &stream;
+ _contentLength = len;
+ _contentType = contentType;
+}
+
+size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
+ size_t available = _content->available();
+ size_t outLen = (available > len) ? len : available;
+ size_t i;
+ for (i = 0; i < outLen; i++)
+ data[i] = _content->read();
+ return outLen;
+}
+
+/*
+ * Callback Response
+ * */
+
+AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
+ _code = 200;
+ _content = callback;
+ _contentLength = len;
+ if (!len)
+ _sendContentLength = false;
+ _contentType = contentType;
+ _filledLength = 0;
+}
+
+size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
+ size_t ret = _content(data, len, _filledLength);
+ if (ret != RESPONSE_TRY_AGAIN) {
+ _filledLength += ret;
+ }
+ return ret;
+}
+
+/*
+ * Chunked Response
+ * */
+
+AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
+ _code = 200;
+ _content = callback;
+ _contentLength = 0;
+ _contentType = contentType;
+ _sendContentLength = false;
+ _chunked = true;
+ _filledLength = 0;
+}
+
+size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
+ size_t ret = _content(data, len, _filledLength);
+ if (ret != RESPONSE_TRY_AGAIN) {
+ _filledLength += ret;
+ }
+ return ret;
+}
+
+/*
+ * Progmem Response
+ * */
+
+AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
+ _code = code;
+ _content = content;
+ _contentType = contentType;
+ _contentLength = len;
+ _readLength = 0;
+}
+
+size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
+ size_t left = _contentLength - _readLength;
+ if (left > len) {
+ memcpy_P(data, _content + _readLength, len);
+ _readLength += len;
+ return len;
+ }
+ memcpy_P(data, _content + _readLength, left);
+ _readLength += left;
+ return left;
+}
+
+/*
+ * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
+ * */
+
+AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) {
+ _code = 200;
+ _contentLength = 0;
+ _contentType = contentType;
+ _content = std::unique_ptr(new cbuf(bufferSize)); // std::make_unique(bufferSize);
+}
+
+AsyncResponseStream::~AsyncResponseStream() = default;
+
+size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
+ return _content->read((char*)buf, maxLen);
+}
+
+size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
+ if (_started())
+ return 0;
+
+ if (len > _content->room()) {
+ size_t needed = len - _content->room();
+ _content->resizeAdd(needed);
+ }
+ size_t written = _content->write((const char*)data, len);
+ _contentLength += written;
+ return written;
+}
+
+size_t AsyncResponseStream::write(uint8_t data) {
+ return write(&data, 1);
+}
diff --git a/lib/ESP Async WebServer/src/WebServer.cpp b/lib/ESP Async WebServer/src/WebServer.cpp
new file mode 100644
index 0000000..9d78e02
--- /dev/null
+++ b/lib/ESP Async WebServer/src/WebServer.cpp
@@ -0,0 +1,217 @@
+/*
+ Asynchronous WebServer library for Espressif MCUs
+
+ Copyright (c) 2016 Hristo Gochkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include "ESPAsyncWebServer.h"
+#include "WebHandlerImpl.h"
+
+bool ON_STA_FILTER(AsyncWebServerRequest* request) {
+ return WiFi.localIP() == request->client()->localIP();
+}
+
+bool ON_AP_FILTER(AsyncWebServerRequest* request) {
+ return WiFi.localIP() != request->client()->localIP();
+}
+
+#ifndef HAVE_FS_FILE_OPEN_MODE
+const char* fs::FileOpenMode::read = "r";
+const char* fs::FileOpenMode::write = "w";
+const char* fs::FileOpenMode::append = "a";
+#endif
+
+AsyncWebServer::AsyncWebServer(uint16_t port)
+ : _server(port) {
+ _catchAllHandler = new AsyncCallbackWebHandler();
+ if (_catchAllHandler == NULL)
+ return;
+ _server.onClient([](void* s, AsyncClient* c) {
+ if (c == NULL)
+ return;
+ c->setRxTimeout(3);
+ AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
+ if (r == NULL) {
+ c->close(true);
+ c->free();
+ delete c;
+ }
+ },
+ this);
+}
+
+AsyncWebServer::~AsyncWebServer() {
+ reset();
+ end();
+ if (_catchAllHandler)
+ delete _catchAllHandler;
+}
+
+AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr rewrite) {
+ _rewrites.emplace_back(rewrite);
+ return *_rewrites.back().get();
+}
+
+AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
+ _rewrites.emplace_back(rewrite);
+ return *_rewrites.back().get();
+}
+
+bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
+ return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
+}
+
+bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
+ for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
+ if (r->get()->from() == from && r->get()->toUrl() == to) {
+ _rewrites.erase(r);
+ return true;
+ }
+ }
+ return false;
+}
+
+AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
+ _rewrites.emplace_back(std::make_shared(from, to));
+ return *_rewrites.back().get();
+}
+
+AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
+ _handlers.emplace_back(handler);
+ return *(_handlers.back().get());
+}
+
+bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
+ for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
+ if (i->get() == handler) {
+ _handlers.erase(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+void AsyncWebServer::begin() {
+ _server.setNoDelay(true);
+ _server.begin();
+}
+
+void AsyncWebServer::end() {
+ _server.end();
+}
+
+#if ASYNC_TCP_SSL_ENABLED
+void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
+ _server.onSslFileRequest(cb, arg);
+}
+
+void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
+ _server.beginSecure(cert, key, password);
+}
+#endif
+
+void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
+ delete request;
+}
+
+void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
+ for (const auto& r : _rewrites) {
+ if (r->match(request)) {
+ request->_url = r->toUrl();
+ request->_addGetParams(r->params());
+ }
+ }
+}
+
+void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
+ for (auto& h : _handlers) {
+ if (h->filter(request) && h->canHandle(request)) {
+ request->setHandler(h.get());
+ return;
+ }
+ }
+
+ request->addInterestingHeader(F("ANY"));
+ request->setHandler(_catchAllHandler);
+}
+
+AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
+ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
+ handler->setUri(uri);
+ handler->setMethod(method);
+ handler->onRequest(onRequest);
+ handler->onUpload(onUpload);
+ handler->onBody(onBody);
+ addHandler(handler);
+ return *handler;
+}
+
+AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) {
+ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
+ handler->setUri(uri);
+ handler->setMethod(method);
+ handler->onRequest(onRequest);
+ handler->onUpload(onUpload);
+ addHandler(handler);
+ return *handler;
+}
+
+AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {
+ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
+ handler->setUri(uri);
+ handler->setMethod(method);
+ handler->onRequest(onRequest);
+ addHandler(handler);
+ return *handler;
+}
+
+AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) {
+ AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
+ handler->setUri(uri);
+ handler->onRequest(onRequest);
+ addHandler(handler);
+ return *handler;
+}
+
+AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) {
+ AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
+ addHandler(handler);
+ return *handler;
+}
+
+void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
+ _catchAllHandler->onRequest(fn);
+}
+
+void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
+ _catchAllHandler->onUpload(fn);
+}
+
+void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
+ _catchAllHandler->onBody(fn);
+}
+
+void AsyncWebServer::reset() {
+ _rewrites.clear();
+ _handlers.clear();
+
+ if (_catchAllHandler != NULL) {
+ _catchAllHandler->onRequest(NULL);
+ _catchAllHandler->onUpload(NULL);
+ _catchAllHandler->onBody(NULL);
+ }
+}
diff --git a/lib/ESP Async WebServer/src/port/SHA1Builder.cpp b/lib/ESP Async WebServer/src/port/SHA1Builder.cpp
new file mode 100644
index 0000000..901fb80
--- /dev/null
+++ b/lib/ESP Async WebServer/src/port/SHA1Builder.cpp
@@ -0,0 +1,284 @@
+/*
+ * FIPS-180-1 compliant SHA-1 implementation
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
+ */
+
+#include
+#if ESP_IDF_VERSION_MAJOR < 5
+
+#include "SHA1Builder.h"
+
+// 32-bit integer manipulation macros (big endian)
+
+#ifndef GET_UINT32_BE
+#define GET_UINT32_BE(n, b, i) \
+ { (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
+#endif
+
+#ifndef PUT_UINT32_BE
+#define PUT_UINT32_BE(n, b, i) \
+ { \
+ (b)[(i)] = (uint8_t)((n) >> 24); \
+ (b)[(i) + 1] = (uint8_t)((n) >> 16); \
+ (b)[(i) + 2] = (uint8_t)((n) >> 8); \
+ (b)[(i) + 3] = (uint8_t)((n)); \
+ }
+#endif
+
+// Constants
+
+static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Private methods
+
+void SHA1Builder::process(const uint8_t *data) {
+ uint32_t temp, W[16], A, B, C, D, E;
+
+ GET_UINT32_BE(W[0], data, 0);
+ GET_UINT32_BE(W[1], data, 4);
+ GET_UINT32_BE(W[2], data, 8);
+ GET_UINT32_BE(W[3], data, 12);
+ GET_UINT32_BE(W[4], data, 16);
+ GET_UINT32_BE(W[5], data, 20);
+ GET_UINT32_BE(W[6], data, 24);
+ GET_UINT32_BE(W[7], data, 28);
+ GET_UINT32_BE(W[8], data, 32);
+ GET_UINT32_BE(W[9], data, 36);
+ GET_UINT32_BE(W[10], data, 40);
+ GET_UINT32_BE(W[11], data, 44);
+ GET_UINT32_BE(W[12], data, 48);
+ GET_UINT32_BE(W[13], data, 52);
+ GET_UINT32_BE(W[14], data, 56);
+ GET_UINT32_BE(W[15], data, 60);
+
+#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
+
+#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
+
+#define sha1_P(a, b, c, d, e, x) \
+ { \
+ e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
+ b = sha1_S(b, 30); \
+ }
+
+ A = state[0];
+ B = state[1];
+ C = state[2];
+ D = state[3];
+ E = state[4];
+
+#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
+#define sha1_K 0x5A827999
+
+ sha1_P(A, B, C, D, E, W[0]);
+ sha1_P(E, A, B, C, D, W[1]);
+ sha1_P(D, E, A, B, C, W[2]);
+ sha1_P(C, D, E, A, B, W[3]);
+ sha1_P(B, C, D, E, A, W[4]);
+ sha1_P(A, B, C, D, E, W[5]);
+ sha1_P(E, A, B, C, D, W[6]);
+ sha1_P(D, E, A, B, C, W[7]);
+ sha1_P(C, D, E, A, B, W[8]);
+ sha1_P(B, C, D, E, A, W[9]);
+ sha1_P(A, B, C, D, E, W[10]);
+ sha1_P(E, A, B, C, D, W[11]);
+ sha1_P(D, E, A, B, C, W[12]);
+ sha1_P(C, D, E, A, B, W[13]);
+ sha1_P(B, C, D, E, A, W[14]);
+ sha1_P(A, B, C, D, E, W[15]);
+ sha1_P(E, A, B, C, D, sha1_R(16));
+ sha1_P(D, E, A, B, C, sha1_R(17));
+ sha1_P(C, D, E, A, B, sha1_R(18));
+ sha1_P(B, C, D, E, A, sha1_R(19));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0x6ED9EBA1
+
+ sha1_P(A, B, C, D, E, sha1_R(20));
+ sha1_P(E, A, B, C, D, sha1_R(21));
+ sha1_P(D, E, A, B, C, sha1_R(22));
+ sha1_P(C, D, E, A, B, sha1_R(23));
+ sha1_P(B, C, D, E, A, sha1_R(24));
+ sha1_P(A, B, C, D, E, sha1_R(25));
+ sha1_P(E, A, B, C, D, sha1_R(26));
+ sha1_P(D, E, A, B, C, sha1_R(27));
+ sha1_P(C, D, E, A, B, sha1_R(28));
+ sha1_P(B, C, D, E, A, sha1_R(29));
+ sha1_P(A, B, C, D, E, sha1_R(30));
+ sha1_P(E, A, B, C, D, sha1_R(31));
+ sha1_P(D, E, A, B, C, sha1_R(32));
+ sha1_P(C, D, E, A, B, sha1_R(33));
+ sha1_P(B, C, D, E, A, sha1_R(34));
+ sha1_P(A, B, C, D, E, sha1_R(35));
+ sha1_P(E, A, B, C, D, sha1_R(36));
+ sha1_P(D, E, A, B, C, sha1_R(37));
+ sha1_P(C, D, E, A, B, sha1_R(38));
+ sha1_P(B, C, D, E, A, sha1_R(39));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
+#define sha1_K 0x8F1BBCDC
+
+ sha1_P(A, B, C, D, E, sha1_R(40));
+ sha1_P(E, A, B, C, D, sha1_R(41));
+ sha1_P(D, E, A, B, C, sha1_R(42));
+ sha1_P(C, D, E, A, B, sha1_R(43));
+ sha1_P(B, C, D, E, A, sha1_R(44));
+ sha1_P(A, B, C, D, E, sha1_R(45));
+ sha1_P(E, A, B, C, D, sha1_R(46));
+ sha1_P(D, E, A, B, C, sha1_R(47));
+ sha1_P(C, D, E, A, B, sha1_R(48));
+ sha1_P(B, C, D, E, A, sha1_R(49));
+ sha1_P(A, B, C, D, E, sha1_R(50));
+ sha1_P(E, A, B, C, D, sha1_R(51));
+ sha1_P(D, E, A, B, C, sha1_R(52));
+ sha1_P(C, D, E, A, B, sha1_R(53));
+ sha1_P(B, C, D, E, A, sha1_R(54));
+ sha1_P(A, B, C, D, E, sha1_R(55));
+ sha1_P(E, A, B, C, D, sha1_R(56));
+ sha1_P(D, E, A, B, C, sha1_R(57));
+ sha1_P(C, D, E, A, B, sha1_R(58));
+ sha1_P(B, C, D, E, A, sha1_R(59));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0xCA62C1D6
+
+ sha1_P(A, B, C, D, E, sha1_R(60));
+ sha1_P(E, A, B, C, D, sha1_R(61));
+ sha1_P(D, E, A, B, C, sha1_R(62));
+ sha1_P(C, D, E, A, B, sha1_R(63));
+ sha1_P(B, C, D, E, A, sha1_R(64));
+ sha1_P(A, B, C, D, E, sha1_R(65));
+ sha1_P(E, A, B, C, D, sha1_R(66));
+ sha1_P(D, E, A, B, C, sha1_R(67));
+ sha1_P(C, D, E, A, B, sha1_R(68));
+ sha1_P(B, C, D, E, A, sha1_R(69));
+ sha1_P(A, B, C, D, E, sha1_R(70));
+ sha1_P(E, A, B, C, D, sha1_R(71));
+ sha1_P(D, E, A, B, C, sha1_R(72));
+ sha1_P(C, D, E, A, B, sha1_R(73));
+ sha1_P(B, C, D, E, A, sha1_R(74));
+ sha1_P(A, B, C, D, E, sha1_R(75));
+ sha1_P(E, A, B, C, D, sha1_R(76));
+ sha1_P(D, E, A, B, C, sha1_R(77));
+ sha1_P(C, D, E, A, B, sha1_R(78));
+ sha1_P(B, C, D, E, A, sha1_R(79));
+
+#undef sha1_K
+#undef sha1_F
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+}
+
+// Public methods
+
+void SHA1Builder::begin(void) {
+ total[0] = 0;
+ total[1] = 0;
+
+ state[0] = 0x67452301;
+ state[1] = 0xEFCDAB89;
+ state[2] = 0x98BADCFE;
+ state[3] = 0x10325476;
+ state[4] = 0xC3D2E1F0;
+
+ memset(buffer, 0x00, sizeof(buffer));
+ memset(hash, 0x00, sizeof(hash));
+}
+
+void SHA1Builder::add(const uint8_t *data, size_t len) {
+ size_t fill;
+ uint32_t left;
+
+ if (len == 0) {
+ return;
+ }
+
+ left = total[0] & 0x3F;
+ fill = 64 - left;
+
+ total[0] += (uint32_t)len;
+ total[0] &= 0xFFFFFFFF;
+
+ if (total[0] < (uint32_t)len) {
+ total[1]++;
+ }
+
+ if (left && len >= fill) {
+ memcpy((void *)(buffer + left), data, fill);
+ process(buffer);
+ data += fill;
+ len -= fill;
+ left = 0;
+ }
+
+ while (len >= 64) {
+ process(data);
+ data += 64;
+ len -= 64;
+ }
+
+ if (len > 0) {
+ memcpy((void *)(buffer + left), data, len);
+ }
+}
+
+void SHA1Builder::calculate(void) {
+ uint32_t last, padn;
+ uint32_t high, low;
+ uint8_t msglen[8];
+
+ high = (total[0] >> 29) | (total[1] << 3);
+ low = (total[0] << 3);
+
+ PUT_UINT32_BE(high, msglen, 0);
+ PUT_UINT32_BE(low, msglen, 4);
+
+ last = total[0] & 0x3F;
+ padn = (last < 56) ? (56 - last) : (120 - last);
+
+ add((uint8_t *)sha1_padding, padn);
+ add(msglen, 8);
+
+ PUT_UINT32_BE(state[0], hash, 0);
+ PUT_UINT32_BE(state[1], hash, 4);
+ PUT_UINT32_BE(state[2], hash, 8);
+ PUT_UINT32_BE(state[3], hash, 12);
+ PUT_UINT32_BE(state[4], hash, 16);
+}
+
+void SHA1Builder::getBytes(uint8_t *output) {
+ memcpy(output, hash, SHA1_HASH_SIZE);
+}
+
+#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/lib/ESP Async WebServer/src/port/SHA1Builder.h b/lib/ESP Async WebServer/src/port/SHA1Builder.h
new file mode 100644
index 0000000..da9a77a
--- /dev/null
+++ b/lib/ESP Async WebServer/src/port/SHA1Builder.h
@@ -0,0 +1,39 @@
+// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SHA1Builder_h
+#define SHA1Builder_h
+
+#include
+#include
+
+#define SHA1_HASH_SIZE 20
+
+class SHA1Builder {
+ private:
+ uint32_t total[2]; /* number of bytes processed */
+ uint32_t state[5]; /* intermediate digest state */
+ unsigned char buffer[64]; /* data block being processed */
+ uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
+
+ void process(const uint8_t* data);
+
+ public:
+ void begin();
+ void add(const uint8_t* data, size_t len);
+ void calculate();
+ void getBytes(uint8_t* output);
+};
+
+#endif // SHA1Builder_h
diff --git a/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-core-esp8266.sh b/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-core-esp8266.sh
deleted file mode 100644
index 048cd02..0000000
--- a/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-core-esp8266.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-echo "Installing ESP8266 Arduino Core ..."
-script_init_path="$PWD"
-mkdir -p "$ARDUINO_USR_PATH/hardware/esp8266com"
-cd "$ARDUINO_USR_PATH/hardware/esp8266com"
-
-echo "Installing Python Serial ..."
-pip install pyserial > /dev/null
-
-if [ "$OS_IS_WINDOWS" == "1" ]; then
- echo "Installing Python Requests ..."
- pip install requests > /dev/null
-fi
-
-echo "Cloning Core Repository ..."
-git clone https://github.com/esp8266/Arduino.git esp8266 > /dev/null 2>&1
-
-echo "Updating submodules ..."
-cd esp8266
-git submodule update --init --recursive > /dev/null 2>&1
-
-echo "Installing Platform Tools ..."
-cd tools
-python get.py > /dev/null
-cd $script_init_path
-
-echo "ESP8266 Arduino has been installed in '$ARDUINO_USR_PATH/hardware/esp8266com'"
-echo ""
diff --git a/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-ide.sh b/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-ide.sh
deleted file mode 100644
index 7e268b1..0000000
--- a/lib/ESPAsyncTCP-esphome/.github/scripts/install-arduino-ide.sh
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/bin/bash
-
-#OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64
-#OSTYPE: 'msys', ARCH: 'x86_64' => win32
-#OSTYPE: 'darwin18', ARCH: 'i386' => macos
-
-OSBITS=`arch`
-if [[ "$OSTYPE" == "linux"* ]]; then
- export OS_IS_LINUX="1"
- ARCHIVE_FORMAT="tar.xz"
- if [[ "$OSBITS" == "i686" ]]; then
- OS_NAME="linux32"
- elif [[ "$OSBITS" == "x86_64" ]]; then
- OS_NAME="linux64"
- elif [[ "$OSBITS" == "armv7l" || "$OSBITS" == "aarch64" ]]; then
- OS_NAME="linuxarm"
- else
- OS_NAME="$OSTYPE-$OSBITS"
- echo "Unknown OS '$OS_NAME'"
- exit 1
- fi
-elif [[ "$OSTYPE" == "darwin"* ]]; then
- export OS_IS_MACOS="1"
- ARCHIVE_FORMAT="zip"
- OS_NAME="macosx"
-elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
- export OS_IS_WINDOWS="1"
- ARCHIVE_FORMAT="zip"
- OS_NAME="windows"
-else
- OS_NAME="$OSTYPE-$OSBITS"
- echo "Unknown OS '$OS_NAME'"
- exit 1
-fi
-export OS_NAME
-
-ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp"
-ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp"
-
-if [ "$OS_IS_MACOS" == "1" ]; then
- export ARDUINO_IDE_PATH="/Applications/Arduino.app/Contents/Java"
- export ARDUINO_USR_PATH="$HOME/Documents/Arduino"
-elif [ "$OS_IS_WINDOWS" == "1" ]; then
- export ARDUINO_IDE_PATH="$HOME/arduino_ide"
- export ARDUINO_USR_PATH="$HOME/Documents/Arduino"
-else
- export ARDUINO_IDE_PATH="$HOME/arduino_ide"
- export ARDUINO_USR_PATH="$HOME/Arduino"
-fi
-
-if [ ! -d "$ARDUINO_IDE_PATH" ]; then
- echo "Installing Arduino IDE on $OS_NAME ..."
- echo "Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ..."
- if [ "$OS_IS_LINUX" == "1" ]; then
- wget -O "arduino.$ARCHIVE_FORMAT" "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1
- echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..."
- tar xf "arduino.$ARCHIVE_FORMAT" > /dev/null
- mv arduino-nightly "$ARDUINO_IDE_PATH"
- else
- curl -o "arduino.$ARCHIVE_FORMAT" -L "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1
- echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..."
- unzip "arduino.$ARCHIVE_FORMAT" > /dev/null
- if [ "$OS_IS_MACOS" == "1" ]; then
- mv "Arduino.app" "/Applications/Arduino.app"
- else
- mv arduino-nightly "$ARDUINO_IDE_PATH"
- fi
- fi
- rm -rf "arduino.$ARCHIVE_FORMAT"
-
- mkdir -p "$ARDUINO_USR_PATH/libraries"
- mkdir -p "$ARDUINO_USR_PATH/hardware"
-
- echo "Arduino IDE Installed in '$ARDUINO_IDE_PATH'"
- echo ""
-fi
-
-function build_sketch(){ # build_sketch [extra-options]
- if [ "$#" -lt 2 ]; then
- echo "ERROR: Illegal number of parameters"
- echo "USAGE: build_sketch [extra-options]"
- return 1
- fi
-
- local fqbn="$1"
- local sketch="$2"
- local xtra_opts="$3"
- local win_opts=""
- if [ "$OS_IS_WINDOWS" == "1" ]; then
- local ctags_version=`ls "$ARDUINO_IDE_PATH/tools-builder/ctags/"`
- local preprocessor_version=`ls "$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/"`
- win_opts="-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version"
- fi
-
- echo ""
- echo "Compiling '"$(basename "$sketch")"' ..."
- mkdir -p "$ARDUINO_BUILD_DIR"
- mkdir -p "$ARDUINO_CACHE_DIR"
- $ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \
- -fqbn=$fqbn \
- -warnings="all" \
- -tools "$ARDUINO_IDE_PATH/tools-builder" \
- -tools "$ARDUINO_IDE_PATH/tools" \
- -built-in-libraries "$ARDUINO_IDE_PATH/libraries" \
- -hardware "$ARDUINO_IDE_PATH/hardware" \
- -hardware "$ARDUINO_USR_PATH/hardware" \
- -libraries "$ARDUINO_USR_PATH/libraries" \
- -build-cache "$ARDUINO_CACHE_DIR" \
- -build-path "$ARDUINO_BUILD_DIR" \
- $win_opts $xtra_opts "$sketch"
-}
-
-function count_sketches() # count_sketches
-{
- local examples="$1"
- rm -rf sketches.txt
- if [ ! -d "$examples" ]; then
- touch sketches.txt
- return 0
- fi
- local sketches=$(find $examples -name *.ino)
- local sketchnum=0
- for sketch in $sketches; do
- local sketchdir=$(dirname $sketch)
- local sketchdirname=$(basename $sketchdir)
- local sketchname=$(basename $sketch)
- if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then
- continue
- fi;
- if [[ -f "$sketchdir/.test.skip" ]]; then
- continue
- fi
- echo $sketch >> sketches.txt
- sketchnum=$(($sketchnum + 1))
- done
- return $sketchnum
-}
-
-function build_sketches() # build_sketches [extra-options]
-{
- local fqbn=$1
- local examples=$2
- local chunk_idex=$3
- local chunks_num=$4
- local xtra_opts=$5
-
- if [ "$#" -lt 2 ]; then
- echo "ERROR: Illegal number of parameters"
- echo "USAGE: build_sketches [ ] [extra-options]"
- return 1
- fi
-
- if [ "$#" -lt 4 ]; then
- chunk_idex="0"
- chunks_num="1"
- xtra_opts=$3
- fi
-
- if [ "$chunks_num" -le 0 ]; then
- echo "ERROR: Chunks count must be positive number"
- return 1
- fi
- if [ "$chunk_idex" -ge "$chunks_num" ]; then
- echo "ERROR: Chunk index must be less than chunks count"
- return 1
- fi
-
- set +e
- count_sketches "$examples"
- local sketchcount=$?
- set -e
- local sketches=$(cat sketches.txt)
- rm -rf sketches.txt
-
- local chunk_size=$(( $sketchcount / $chunks_num ))
- local all_chunks=$(( $chunks_num * $chunk_size ))
- if [ "$all_chunks" -lt "$sketchcount" ]; then
- chunk_size=$(( $chunk_size + 1 ))
- fi
-
- local start_index=$(( $chunk_idex * $chunk_size ))
- if [ "$sketchcount" -le "$start_index" ]; then
- echo "Skipping job"
- return 0
- fi
-
- local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size ))
- if [ "$end_index" -gt "$sketchcount" ]; then
- end_index=$sketchcount
- fi
-
- local start_num=$(( $start_index + 1 ))
- echo "Found $sketchcount Sketches";
- echo "Chunk Count : $chunks_num"
- echo "Chunk Size : $chunk_size"
- echo "Start Sketch: $start_num"
- echo "End Sketch : $end_index"
-
- local sketchnum=0
- for sketch in $sketches; do
- local sketchdir=$(dirname $sketch)
- local sketchdirname=$(basename $sketchdir)
- local sketchname=$(basename $sketch)
- if [ "${sketchdirname}.ino" != "$sketchname" ] \
- || [ -f "$sketchdir/.test.skip" ]; then
- continue
- fi
- sketchnum=$(($sketchnum + 1))
- if [ "$sketchnum" -le "$start_index" ] \
- || [ "$sketchnum" -gt "$end_index" ]; then
- continue
- fi
- build_sketch "$fqbn" "$sketch" "$xtra_opts"
- local result=$?
- if [ $result -ne 0 ]; then
- return $result
- fi
- done
- return 0
-}
diff --git a/lib/ESPAsyncTCP-esphome/.github/scripts/install-platformio.sh b/lib/ESPAsyncTCP-esphome/.github/scripts/install-platformio.sh
deleted file mode 100644
index 61c94fe..0000000
--- a/lib/ESPAsyncTCP-esphome/.github/scripts/install-platformio.sh
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/bin/bash
-
-echo "Installing Python Wheel ..."
-pip install wheel > /dev/null 2>&1
-
-echo "Installing PlatformIO ..."
-pip install -U platformio > /dev/null 2>&1
-
-echo "PlatformIO has been installed"
-echo ""
-
-
-function build_pio_sketch(){ # build_pio_sketch
- if [ "$#" -lt 2 ]; then
- echo "ERROR: Illegal number of parameters"
- echo "USAGE: build_pio_sketch "
- return 1
- fi
-
- local board="$1"
- local sketch="$2"
- local sketch_dir=$(dirname "$sketch")
- echo ""
- echo "Compiling '"$(basename "$sketch")"' ..."
- python -m platformio ci -l '.' --board "$board" "$sketch_dir" --project-option="board_build.partitions = huge_app.csv"
-}
-
-function count_sketches() # count_sketches