+#endif
+#include "ESPAsyncWebServer.h"
+
+DNSServer dnsServer;
+AsyncWebServer server(80);
+
+class CaptiveRequestHandler : public AsyncWebHandler {
+public:
+ CaptiveRequestHandler() {}
+ virtual ~CaptiveRequestHandler() {}
+
+ bool canHandle(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);
+ }
+};
+
+
+void setup(){
+ //your other setup stuff...
+ WiFi.softAP("esp-captive");
+ dnsServer.start(53, "*", WiFi.softAPIP());
+ server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
+ //more handlers...
+ server.begin();
+}
+
+void loop(){
+ dnsServer.processNextRequest();
+}
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino
new file mode 100755
index 0000000..bf42d00
--- /dev/null
+++ b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino
@@ -0,0 +1,221 @@
+#include
+#ifdef ESP32
+#include
+#include
+#include
+#include
+#include
+#elif defined(ESP8266)
+#include
+#include
+#include
+#endif
+#include
+#include
+
+// SKETCH BEGIN
+AsyncWebServer server(80);
+AsyncWebSocket ws("/ws");
+AsyncEventSource events("/events");
+
+void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
+ if(type == WS_EVT_CONNECT){
+ Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
+ client->printf("Hello Client %u :)", client->id());
+ client->ping();
+ } else if(type == WS_EVT_DISCONNECT){
+ Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id());
+ } else if(type == WS_EVT_ERROR){
+ Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
+ } else if(type == WS_EVT_PONG){
+ Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
+ } else if(type == WS_EVT_DATA){
+ AwsFrameInfo * info = (AwsFrameInfo*)arg;
+ String msg = "";
+ if(info->final && info->index == 0 && info->len == len){
+ //the whole message is in a single frame and we got all of it's data
+ Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
+
+ if(info->opcode == WS_TEXT){
+ for(size_t i=0; i < info->len; i++) {
+ msg += (char) data[i];
+ }
+ } else {
+ char buff[3];
+ for(size_t i=0; i < info->len; i++) {
+ sprintf(buff, "%02x ", (uint8_t) data[i]);
+ msg += buff ;
+ }
+ }
+ Serial.printf("%s\n",msg.c_str());
+
+ if(info->opcode == WS_TEXT)
+ client->text("I got your text message");
+ else
+ client->binary("I got your binary message");
+ } else {
+ //message is comprised of multiple frames or the frame is split into multiple packets
+ if(info->index == 0){
+ if(info->num == 0)
+ Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
+ Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
+ }
+
+ Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
+
+ if(info->opcode == WS_TEXT){
+ for(size_t i=0; i < len; i++) {
+ msg += (char) data[i];
+ }
+ } else {
+ char buff[3];
+ for(size_t i=0; i < len; i++) {
+ sprintf(buff, "%02x ", (uint8_t) data[i]);
+ msg += buff ;
+ }
+ }
+ Serial.printf("%s\n",msg.c_str());
+
+ if((info->index + len) == info->len){
+ Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
+ if(info->final){
+ Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
+ if(info->message_opcode == WS_TEXT)
+ client->text("I got your text message");
+ else
+ client->binary("I got your binary message");
+ }
+ }
+ }
+ }
+}
+
+
+const char* ssid = "*******";
+const char* password = "*******";
+const char * hostName = "esp-async";
+const char* http_username = "admin";
+const char* http_password = "admin";
+
+void setup(){
+ Serial.begin(115200);
+ Serial.setDebugOutput(true);
+ WiFi.mode(WIFI_AP_STA);
+ WiFi.softAP(hostName);
+ WiFi.begin(ssid, password);
+ if (WiFi.waitForConnectResult() != WL_CONNECTED) {
+ Serial.printf("STA: Failed!\n");
+ WiFi.disconnect(false);
+ delay(1000);
+ WiFi.begin(ssid, password);
+ }
+
+ //Send OTA events to the browser
+ ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); });
+ ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); });
+ ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
+ char p[32];
+ sprintf(p, "Progress: %u%%\n", (progress/(total/100)));
+ events.send(p, "ota");
+ });
+ ArduinoOTA.onError([](ota_error_t error) {
+ if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota");
+ else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota");
+ else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota");
+ else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota");
+ else if(error == OTA_END_ERROR) events.send("End Failed", "ota");
+ });
+ ArduinoOTA.setHostname(hostName);
+ ArduinoOTA.begin();
+
+ MDNS.addService("http","tcp",80);
+
+ SPIFFS.begin();
+
+ ws.onEvent(onWsEvent);
+ server.addHandler(&ws);
+
+ events.onConnect([](AsyncEventSourceClient *client){
+ client->send("hello!",NULL,millis(),1000);
+ });
+ server.addHandler(&events);
+
+#ifdef ESP32
+ server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
+#elif defined(ESP8266)
+ server.addHandler(new SPIFFSEditor(http_username,http_password));
+#endif
+
+ server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
+ request->send(200, "text/plain", String(ESP.getFreeHeap()));
+ });
+
+ server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
+
+ server.onNotFound([](AsyncWebServerRequest *request){
+ Serial.printf("NOT_FOUND: ");
+ if(request->method() == HTTP_GET)
+ Serial.printf("GET");
+ else if(request->method() == HTTP_POST)
+ Serial.printf("POST");
+ else if(request->method() == HTTP_DELETE)
+ Serial.printf("DELETE");
+ else if(request->method() == HTTP_PUT)
+ Serial.printf("PUT");
+ else if(request->method() == HTTP_PATCH)
+ Serial.printf("PATCH");
+ else if(request->method() == HTTP_HEAD)
+ Serial.printf("HEAD");
+ else if(request->method() == HTTP_OPTIONS)
+ Serial.printf("OPTIONS");
+ else
+ Serial.printf("UNKNOWN");
+ Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
+
+ if(request->contentLength()){
+ Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
+ Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
+ }
+
+ int headers = request->headers();
+ int i;
+ for(i=0;igetHeader(i);
+ Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
+ }
+
+ int params = request->params();
+ for(i=0;igetParam(i);
+ if(p->isFile()){
+ Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
+ } else if(p->isPost()){
+ Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
+ } else {
+ Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
+ }
+ }
+
+ request->send(404);
+ });
+ server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
+ if(!index)
+ Serial.printf("UploadStart: %s\n", filename.c_str());
+ Serial.printf("%s", (const char*)data);
+ if(final)
+ Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len);
+ });
+ server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
+ if(!index)
+ Serial.printf("BodyStart: %u\n", total);
+ Serial.printf("%s", (const char*)data);
+ if(index + len == total)
+ Serial.printf("BodyEnd: %u\n", total);
+ });
+ server.begin();
+}
+
+void loop(){
+ ArduinoOTA.handle();
+ ws.cleanupClients();
+}
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files
new file mode 100755
index 0000000..955397f
--- /dev/null
+++ b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/.exclude.files
@@ -0,0 +1,2 @@
+/*.js.gz
+/.exclude.files
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz
new file mode 100755
index 0000000..7b175c1
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ace.js.gz differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz
new file mode 100755
index 0000000..cf5b49f
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico
new file mode 100755
index 0000000..71b25fe
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/favicon.ico differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm
new file mode 100755
index 0000000..28f47e9
--- /dev/null
+++ b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/index.htm
@@ -0,0 +1,131 @@
+
+
+
+
+
+ WebSocketTester
+
+
+
+
+
+
+ $
+
+
+
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz
new file mode 100755
index 0000000..ebd6fe9
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz
new file mode 100755
index 0000000..26b5353
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz
new file mode 100755
index 0000000..c0451c1
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz differ
diff --git a/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz
new file mode 100755
index 0000000..ec8aa87
Binary files /dev/null and b/components/AsyncWebServer/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz differ
diff --git a/components/AsyncWebServer/examples/regex_patterns/.test.build_flags b/components/AsyncWebServer/examples/regex_patterns/.test.build_flags
new file mode 100755
index 0000000..9ea3bb7
--- /dev/null
+++ b/components/AsyncWebServer/examples/regex_patterns/.test.build_flags
@@ -0,0 +1 @@
+-DASYNCWEBSERVER_REGEX=1
diff --git a/components/AsyncWebServer/examples/regex_patterns/regex_patterns.ino b/components/AsyncWebServer/examples/regex_patterns/regex_patterns.ino
new file mode 100755
index 0000000..fb01306
--- /dev/null
+++ b/components/AsyncWebServer/examples/regex_patterns/regex_patterns.ino
@@ -0,0 +1,77 @@
+//
+// A simple server implementation with regex routes:
+// * serve static messages
+// * read GET and POST parameters
+// * handle missing pages / 404s
+//
+
+// Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support
+
+// For platformio: platformio.ini:
+// build_flags =
+// -DASYNCWEBSERVER_REGEX
+
+// For arduino IDE: create/update platform.local.txt
+// Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt
+// Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt
+//
+// compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1
+
+#include
+#ifdef ESP32
+#include
+#include
+#elif defined(ESP8266)
+#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 /sensor/
+ server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
+ String sensorNumber = request->pathArg(0);
+ request->send(200, "text/plain", "Hello, sensor: " + sensorNumber);
+ });
+
+ // Send a GET request to /sensor//action/
+ server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
+ String sensorNumber = request->pathArg(0);
+ String action = request->pathArg(1);
+ request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action);
+ });
+
+ server.onNotFound(notFound);
+
+ server.begin();
+}
+
+void loop() {
+}
diff --git a/components/AsyncWebServer/examples/simple_server/simple_server.ino b/components/AsyncWebServer/examples/simple_server/simple_server.ino
new file mode 100755
index 0000000..bdbcf60
--- /dev/null
+++ b/components/AsyncWebServer/examples/simple_server/simple_server.ino
@@ -0,0 +1,74 @@
+//
+// 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
+#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/components/AsyncWebServer/keywords.txt b/components/AsyncWebServer/keywords.txt
new file mode 100755
index 0000000..c391e6c
--- /dev/null
+++ b/components/AsyncWebServer/keywords.txt
@@ -0,0 +1,3 @@
+JsonArray KEYWORD1
+add KEYWORD2
+createArray KEYWORD3
diff --git a/components/AsyncWebServer/library.json b/components/AsyncWebServer/library.json
new file mode 100755
index 0000000..750ca28
--- /dev/null
+++ b/components/AsyncWebServer/library.json
@@ -0,0 +1,37 @@
+{
+ "name":"ESP Async WebServer",
+ "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32",
+ "keywords":"http,async,websocket,webserver",
+ "authors":
+ {
+ "name": "Hristo Gochkov",
+ "maintainer": true
+ },
+ "repository":
+ {
+ "type": "git",
+ "url": "https://github.com/me-no-dev/ESPAsyncWebServer.git"
+ },
+ "version": "1.2.3",
+ "license": "LGPL-3.0",
+ "frameworks": "arduino",
+ "platforms": ["espressif8266", "espressif32"],
+ "dependencies": [
+ {
+ "owner": "me-no-dev",
+ "name": "ESPAsyncTCP",
+ "version": "^1.2.2",
+ "platforms": "espressif8266"
+ },
+ {
+ "owner": "me-no-dev",
+ "name": "AsyncTCP",
+ "version": "^1.1.1",
+ "platforms": "espressif32"
+ },
+ {
+ "name": "Hash",
+ "platforms": "espressif8266"
+ }
+ ]
+}
diff --git a/components/AsyncWebServer/library.properties b/components/AsyncWebServer/library.properties
new file mode 100755
index 0000000..401b041
--- /dev/null
+++ b/components/AsyncWebServer/library.properties
@@ -0,0 +1,9 @@
+name=ESP Async WebServer
+version=1.2.3
+author=Me-No-Dev
+maintainer=Me-No-Dev
+sentence=Async Web Server for ESP8266 and ESP31B
+paragraph=Async Web Server for ESP8266 and ESP31B
+category=Other
+url=https://github.com/me-no-dev/ESPAsyncWebServer
+architectures=*
diff --git a/components/AsyncWebServer/src/AsyncEventSource.cpp b/components/AsyncWebServer/src/AsyncEventSource.cpp
new file mode 100755
index 0000000..f2914df
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncEventSource.cpp
@@ -0,0 +1,368 @@
+/*
+ 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"
+#include "AsyncEventSource.h"
+
+static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
+ String ev = "";
+
+ if(reconnect){
+ ev += "retry: ";
+ ev += String(reconnect);
+ ev += "\r\n";
+ }
+
+ if(id){
+ ev += "id: ";
+ ev += String(id);
+ ev += "\r\n";
+ }
+
+ if(event != NULL){
+ ev += "event: ";
+ ev += String(event);
+ ev += "\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 += "data: ";
+ ev += ldata;
+ ev += "\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 += "data: ";
+ ev += ldata;
+ ev += "\r\n";
+ free(ldata);
+ }
+ lineStart = nextLine;
+ if(lineStart == ((char *)message + messageLen))
+ ev += "\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;
+}
+
+size_t AsyncEventSourceMessage::send(AsyncClient *client) {
+ const size_t len = _len - _sent;
+ if(client->space() < len){
+ return 0;
+ }
+ size_t sent = client->add((const char *)_data, len);
+ if(client->canSend())
+ client->send();
+ _sent += sent;
+ return sent;
+}
+
+// Client
+
+AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
+: _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; }))
+{
+ _client = request->client();
+ _server = server;
+ _lastId = 0;
+ if(request->hasHeader("Last-Event-ID"))
+ _lastId = atoi(request->getHeader("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(){
+ _messageQueue.free();
+ close();
+}
+
+void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
+ if(dataMessage == NULL)
+ return;
+ if(!connected()){
+ delete dataMessage;
+ return;
+ }
+ if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
+ ets_printf("ERROR: Too many messages queued\n");
+ delete dataMessage;
+ } else {
+ _messageQueue.add(dataMessage);
+ }
+ if(_client->canSend())
+ _runQueue();
+}
+
+void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
+ while(len && !_messageQueue.isEmpty()){
+ len = _messageQueue.front()->ack(len, time);
+ if(_messageQueue.front()->finished())
+ _messageQueue.remove(_messageQueue.front());
+ }
+
+ _runQueue();
+}
+
+void AsyncEventSourceClient::_onPoll(){
+ if(!_messageQueue.isEmpty()){
+ _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){
+ _queueMessage(new AsyncEventSourceMessage(message, len));
+}
+
+void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
+ String ev = generateEventMessage(message, event, id, reconnect);
+ _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
+}
+
+void AsyncEventSourceClient::_runQueue(){
+ while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
+ _messageQueue.remove(_messageQueue.front());
+ }
+
+ for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
+ {
+ if(!(*i)->sent())
+ (*i)->send(_client);
+ }
+}
+
+
+// Handler
+
+AsyncEventSource::AsyncEventSource(const String& url)
+ : _url(url)
+ , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; }))
+ , _connectcb(NULL)
+{}
+
+AsyncEventSource::~AsyncEventSource(){
+ close();
+}
+
+void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
+ _connectcb = cb;
+}
+
+void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
+ /*char * temp = (char *)malloc(2054);
+ if(temp != NULL){
+ memset(temp+1,' ',2048);
+ temp[0] = ':';
+ temp[2049] = '\r';
+ temp[2050] = '\n';
+ temp[2051] = '\r';
+ temp[2052] = '\n';
+ temp[2053] = 0;
+ client->write((const char *)temp, 2053);
+ free(temp);
+ }*/
+
+ _clients.add(client);
+ if(_connectcb)
+ _connectcb(client);
+}
+
+void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
+ _clients.remove(client);
+}
+
+void AsyncEventSource::close(){
+ for(const auto &c: _clients){
+ if(c->connected())
+ c->close();
+ }
+}
+
+// pmb fix
+size_t AsyncEventSource::avgPacketsWaiting() const {
+ if(_clients.isEmpty())
+ return 0;
+
+ size_t aql=0;
+ uint32_t nConnectedClients=0;
+
+ for(const auto &c: _clients){
+ if(c->connected()) {
+ aql+=c->packetsWaiting();
+ ++nConnectedClients;
+ }
+ }
+// return aql / 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);
+ for(const auto &c: _clients){
+ if(c->connected()) {
+ c->write(ev.c_str(), ev.length());
+ }
+ }
+}
+
+size_t AsyncEventSource::count() const {
+ return _clients.count_if([](AsyncEventSourceClient *c){
+ return c->connected();
+ });
+}
+
+bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
+ if(request->method() != HTTP_GET || !request->url().equals(_url)) {
+ return false;
+ }
+ request->addInterestingHeader("Last-Event-ID");
+ return true;
+}
+
+void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
+ if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+ request->send(new AsyncEventSourceResponse(this));
+}
+
+// Response
+
+AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
+ _server = server;
+ _code = 200;
+ _contentType = "text/event-stream";
+ _sendContentLength = false;
+ addHeader("Cache-Control", "no-cache");
+ addHeader("Connection","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/components/AsyncWebServer/src/AsyncEventSource.h b/components/AsyncWebServer/src/AsyncEventSource.h
new file mode 100755
index 0000000..b097fa6
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncEventSource.h
@@ -0,0 +1,133 @@
+/*
+ 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
+#ifdef ESP32
+#include
+#define SSE_MAX_QUEUED_MESSAGES 32
+#else
+#include
+#define SSE_MAX_QUEUED_MESSAGES 8
+#endif
+#include
+
+#include "AsyncWebSynchronization.h"
+
+#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
+
+#ifdef ESP32
+#define DEFAULT_MAX_SSE_CLIENTS 8
+#else
+#define DEFAULT_MAX_SSE_CLIENTS 4
+#endif
+
+class AsyncEventSource;
+class AsyncEventSourceResponse;
+class AsyncEventSourceClient;
+typedef std::function ArEventHandlerFunction;
+
+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;
+ LinkedList _messageQueue;
+ void _queueMessage(AsyncEventSourceMessage *dataMessage);
+ 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 { return _messageQueue.length(); }
+
+ //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;
+ LinkedList _clients;
+ ArEventHandlerFunction _connectcb;
+ public:
+ AsyncEventSource(const String& url);
+ ~AsyncEventSource();
+
+ const char * url() const { return _url.c_str(); }
+ void close();
+ void onConnect(ArEventHandlerFunction cb);
+ void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
+ size_t count() const; //number clinets connected
+ 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/components/AsyncWebServer/src/AsyncJson.h b/components/AsyncWebServer/src/AsyncJson.h
new file mode 100755
index 0000000..2fa6a2d
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncJson.h
@@ -0,0 +1,254 @@
+// 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 == 5
+ #define ARDUINOJSON_5_COMPATIBILITY
+#else
+ #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:
+
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ DynamicJsonBuffer _jsonBuffer;
+#else
+ DynamicJsonDocument _jsonBuffer;
+#endif
+
+ JsonVariant _root;
+ bool _isValid;
+
+ public:
+
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ AsyncJsonResponse(bool isArray=false): _isValid{false} {
+ _code = 200;
+ _contentType = JSON_MIMETYPE;
+ if(isArray)
+ _root = _jsonBuffer.createArray();
+ else
+ _root = _jsonBuffer.createObject();
+ }
+#else
+ 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();
+ }
+#endif
+
+ ~AsyncJsonResponse() {}
+ JsonVariant & getRoot() { return _root; }
+ bool _sourceValid() const { return _isValid; }
+ size_t setLength() {
+
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ _contentLength = _root.measureLength();
+#else
+ _contentLength = measureJson(_root);
+#endif
+
+ if (_contentLength) { _isValid = true; }
+ return _contentLength;
+ }
+
+ size_t getSize() { return _jsonBuffer.size(); }
+
+ size_t _fillBuffer(uint8_t *data, size_t len){
+ ChunkPrint dest(data, _sentLength, len);
+
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ _root.printTo( dest ) ;
+#else
+ serializeJson(_root, dest);
+#endif
+ return len;
+ }
+};
+
+class PrettyAsyncJsonResponse: public AsyncJsonResponse {
+public:
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
+#else
+ PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
+#endif
+ size_t setLength () {
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ _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);
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ _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;
+#ifndef ARDUINOJSON_5_COMPATIBILITY
+ const size_t maxJsonBufferSize;
+#endif
+ size_t _maxContentLength;
+public:
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
+ : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
+#else
+ AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE)
+ : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _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;
+
+ if(!(_method & request->method()))
+ return false;
+
+ if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
+ return false;
+
+ if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) )
+ return false;
+
+ request->addInterestingHeader("ANY");
+ return true;
+ }
+
+ virtual void handleRequest(AsyncWebServerRequest *request) override final {
+ if(_onRequest) {
+ if (request->_tempObject != NULL) {
+
+#ifdef ARDUINOJSON_5_COMPATIBILITY
+ DynamicJsonBuffer jsonBuffer;
+ JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
+ if (json.success()) {
+#else
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ 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(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, 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/components/AsyncWebServer/src/AsyncWebSocket.cpp b/components/AsyncWebServer/src/AsyncWebSocket.cpp
new file mode 100755
index 0000000..2ac838e
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncWebSocket.cpp
@@ -0,0 +1,1362 @@
+/*
+ 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 "Arduino.h"
+#include "AsyncWebSocket.h"
+
+#include
+
+#ifndef ESP8266
+#include "mbedtls/sha1.h"
+#else
+#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())
+ return 0;
+ size_t space = client->space();
+ if(space < 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)
+ 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);
+ 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);
+ return 0;
+ }
+ free(buf);
+
+ if(len){
+ if(len && mask){
+ size_t i;
+ for(i=0;iadd((const char *)data, len) != len){
+ //os_printf("error adding %lu data bytes\n", len);
+ return 0;
+ }
+ }
+ if(!client->send()){
+ //os_printf("error sending frame: %lu\n", headLen+len);
+ return 0;
+ }
+ return len;
+}
+
+
+/*
+ * AsyncWebSocketMessageBuffer
+ */
+
+
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer()
+ :_data(nullptr)
+ ,_len(0)
+ ,_lock(false)
+ ,_count(0)
+{
+
+}
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size)
+ :_data(nullptr)
+ ,_len(size)
+ ,_lock(false)
+ ,_count(0)
+{
+
+ if (!data) {
+ return;
+ }
+
+ _data = new uint8_t[_len + 1];
+
+ if (_data) {
+ memcpy(_data, data, _len);
+ _data[_len] = 0;
+ }
+}
+
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
+ :_data(nullptr)
+ ,_len(size)
+ ,_lock(false)
+ ,_count(0)
+{
+ _data = new uint8_t[_len + 1];
+
+ if (_data) {
+ _data[_len] = 0;
+ }
+
+}
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy)
+ :_data(nullptr)
+ ,_len(0)
+ ,_lock(false)
+ ,_count(0)
+{
+ _len = copy._len;
+ _lock = copy._lock;
+ _count = 0;
+
+ if (_len) {
+ _data = new uint8_t[_len + 1];
+ _data[_len] = 0;
+ }
+
+ if (_data) {
+ memcpy(_data, copy._data, _len);
+ _data[_len] = 0;
+ }
+
+}
+
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy)
+ :_data(nullptr)
+ ,_len(0)
+ ,_lock(false)
+ ,_count(0)
+{
+ _len = copy._len;
+ _lock = copy._lock;
+ _count = 0;
+
+ if (copy._data) {
+ _data = copy._data;
+ copy._data = nullptr;
+ }
+
+}
+
+AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer()
+{
+ if (_data) {
+ delete[] _data;
+ }
+}
+
+bool AsyncWebSocketMessageBuffer::reserve(size_t size)
+{
+ _len = size;
+
+ if (_data) {
+ delete[] _data;
+ _data = nullptr;
+ }
+
+ _data = new uint8_t[_len + 1];
+
+ if (_data) {
+ _data[_len] = 0;
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+
+
+/*
+ * Control Frame
+ */
+
+class AsyncWebSocketControl {
+ private:
+ uint8_t _opcode;
+ uint8_t *_data;
+ size_t _len;
+ bool _mask;
+ bool _finished;
+ public:
+ AsyncWebSocketControl(uint8_t opcode, 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);
+ }
+};
+
+/*
+ * Basic Buffered Message
+ */
+
+
+AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode, bool mask)
+ :_len(len)
+ ,_sent(0)
+ ,_ack(0)
+ ,_acked(0)
+{
+ _opcode = opcode & 0x07;
+ _mask = mask;
+ _data = (uint8_t*)malloc(_len+1);
+ if(_data == NULL){
+ _len = 0;
+ _status = WS_MSG_ERROR;
+ } else {
+ _status = WS_MSG_SENDING;
+ memcpy(_data, data, _len);
+ _data[_len] = 0;
+ }
+}
+AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask)
+ :_len(0)
+ ,_sent(0)
+ ,_ack(0)
+ ,_acked(0)
+ ,_data(NULL)
+{
+ _opcode = opcode & 0x07;
+ _mask = mask;
+
+}
+
+
+AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() {
+ if(_data != NULL)
+ free(_data);
+}
+
+ void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) {
+ (void)time;
+ _acked += len;
+ if(_sent == _len && _acked == _ack){
+ _status = WS_MSG_SENT;
+ }
+}
+ size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) {
+ if(_status != WS_MSG_SENDING)
+ return 0;
+ if(_acked < _ack){
+ return 0;
+ }
+ if(_sent == _len){
+ if(_acked == _ack)
+ _status = WS_MSG_SENT;
+ return 0;
+ }
+ if(_sent > _len){
+ _status = WS_MSG_ERROR;
+ return 0;
+ }
+
+ size_t toSend = _len - _sent;
+ size_t window = webSocketSendFrameWindow(client);
+
+ if(window < toSend) {
+ toSend = window;
+ }
+
+ _sent += toSend;
+ _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4);
+
+ bool final = (_sent == _len);
+ uint8_t* dPtr = (uint8_t*)(_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){
+ _sent -= (toSend - sent);
+ _ack -= (toSend - sent);
+ }
+ return sent;
+}
+
+// bool AsyncWebSocketBasicMessage::reserve(size_t size) {
+// if (size) {
+// _data = (uint8_t*)malloc(size +1);
+// if (_data) {
+// memset(_data, 0, size);
+// _len = size;
+// _status = WS_MSG_SENDING;
+// return true;
+// }
+// }
+// return false;
+// }
+
+
+/*
+ * AsyncWebSocketMultiMessage Message
+ */
+
+
+AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode, bool mask)
+ :_len(0)
+ ,_sent(0)
+ ,_ack(0)
+ ,_acked(0)
+ ,_WSbuffer(nullptr)
+{
+
+ _opcode = opcode & 0x07;
+ _mask = mask;
+
+ if (buffer) {
+ _WSbuffer = buffer;
+ (*_WSbuffer)++;
+ _data = buffer->get();
+ _len = buffer->length();
+ _status = WS_MSG_SENDING;
+ //ets_printf("M: %u\n", _len);
+ } else {
+ _status = WS_MSG_ERROR;
+ }
+
+}
+
+
+AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() {
+ if (_WSbuffer) {
+ (*_WSbuffer)--; // decreases the counter.
+ }
+}
+
+ void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) {
+ (void)time;
+ _acked += len;
+ if(_sent >= _len && _acked >= _ack){
+ _status = WS_MSG_SENT;
+ }
+ //ets_printf("A: %u\n", len);
+}
+ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) {
+ if(_status != WS_MSG_SENDING)
+ return 0;
+ if(_acked < _ack){
+ return 0;
+ }
+ if(_sent == _len){
+ _status = WS_MSG_SENT;
+ return 0;
+ }
+ if(_sent > _len){
+ _status = WS_MSG_ERROR;
+ //ets_printf("E: %u > %u\n", _sent, _len);
+ return 0;
+ }
+
+ size_t toSend = _len - _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 == _len);
+ uint8_t* dPtr = (uint8_t*)(_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)
+ : _controlQueue(LinkedList([](AsyncWebSocketControl *c){ delete c; }))
+ , _messageQueue(LinkedList([](AsyncWebSocketMessage *m){ delete m; }))
+ , _tempObject(NULL)
+{
+ _client = request->client();
+ _server = server;
+ _clientId = _server->_getNextId();
+ _status = WS_CONNECTED;
+ _pstate = 0;
+ _partialHeader = nullptr;
+ _partialHeaderLen = 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->_addClient(this);
+ _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0);
+ delete request;
+}
+
+AsyncWebSocketClient::~AsyncWebSocketClient(){
+ _messageQueue.free();
+ _controlQueue.free();
+ _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
+}
+
+void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){
+ _lastMessageTime = millis();
+ if(!_controlQueue.isEmpty()){
+ auto head = _controlQueue.front();
+ if(head->finished()){
+ len -= head->len();
+ if(_status == WS_DISCONNECTING && head->opcode() == WS_DISCONNECT){
+ _controlQueue.remove(head);
+ _status = WS_DISCONNECTED;
+ _client->close(true);
+ return;
+ }
+ _controlQueue.remove(head);
+ }
+ }
+ if(len && !_messageQueue.isEmpty()){
+ _messageQueue.front()->ack(len, time);
+ }
+ _server->_cleanBuffers();
+ _runQueue();
+}
+
+void AsyncWebSocketClient::_onPoll(){
+ if(_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())){
+ _runQueue();
+ } else if(_keepAlivePeriod > 0 && _controlQueue.isEmpty() && _messageQueue.isEmpty() && (millis() - _lastMessageTime) >= _keepAlivePeriod){
+ ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
+ }
+}
+
+void AsyncWebSocketClient::_runQueue(){
+ while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
+ _messageQueue.remove(_messageQueue.front());
+ }
+
+ if(!_controlQueue.isEmpty() && (_messageQueue.isEmpty() || _messageQueue.front()->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front()->len() - 1)){
+ _controlQueue.front()->send(_client);
+ } else if(!_messageQueue.isEmpty() && _messageQueue.front()->betweenFrames() && webSocketSendFrameWindow(_client)){
+ _messageQueue.front()->send(_client);
+ }
+}
+
+bool AsyncWebSocketClient::queueIsFull(){
+ if((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) return true;
+ return false;
+}
+
+void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){
+ if(dataMessage == NULL)
+ return;
+ if(_status != WS_CONNECTED){
+ delete dataMessage;
+ return;
+ }
+ if(_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES){
+ ets_printf("ERROR: Too many messages queued\n");
+ delete dataMessage;
+ } else {
+ _messageQueue.add(dataMessage);
+ }
+ if(_client->canSend())
+ _runQueue();
+}
+
+void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){
+ if(controlMessage == NULL)
+ return;
+ _controlQueue.add(controlMessage);
+ if(_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(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen));
+ free(buf);
+ return;
+ }
+ }
+ _queueControl(new AsyncWebSocketControl(WS_DISCONNECT));
+}
+
+void AsyncWebSocketClient::ping(uint8_t *data, size_t len){
+ if(_status == WS_CONNECTED)
+ _queueControl(new AsyncWebSocketControl(WS_PING, data, len));
+}
+
+void AsyncWebSocketClient::_onError(int8_t){}
+
+void AsyncWebSocketClient::_onTimeout(uint32_t time){
+ (void)time;
+ _client->close(true);
+}
+
+void AsyncWebSocketClient::_onDisconnect(){
+ _client = NULL;
+ _server->_handleDisconnect(this);
+}
+
+void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
+ _lastMessageTime = millis();
+ uint8_t *data = (uint8_t*)pbuf;
+ while(plen > 0){
+ if(!_pstate) {
+ ssize_t dataPayloadOffset = 0;
+ const uint8_t *headerBuf = data;
+ bool headerInHeap = false;
+ bool failPartialHeader = false;
+ size_t initialPlen = plen;
+ size_t partialHeaderLen = 0;
+
+ if (_partialHeader != nullptr) {
+ // Recover previously received partial header
+ headerInHeap = true;
+ memcpy(_partialHeader + _partialHeaderLen, data, std::min(plen, (size_t) WS_MAX_HEADER_LEN - _partialHeaderLen));
+ headerBuf = _partialHeader;
+ initialPlen += _partialHeaderLen;
+ plen += _partialHeaderLen;
+ dataPayloadOffset -= _partialHeaderLen;
+ partialHeaderLen = _partialHeaderLen;
+
+ _partialHeader = nullptr;
+ _partialHeaderLen = 0;
+ }
+
+ // The following series of gotos could be a try-catch but we may be building with -fno-exceptions
+ if (plen < 2) {
+ failPartialHeader = true;
+ goto _exceptionHandleFailPartialHeader;
+ }
+ _pinfo.index = 0;
+ _pinfo.final = (headerBuf[0] & 0x80) != 0;
+ _pinfo.opcode = headerBuf[0] & 0x0F;
+ _pinfo.masked = (headerBuf[1] & 0x80) != 0;
+ _pinfo.len = headerBuf[1] & 0x7F;
+ dataPayloadOffset += 2;
+ plen -= 2;
+
+ if (_pinfo.len == 126) {
+ if (plen < 2) {
+ failPartialHeader = true;
+ goto _exceptionHandleFailPartialHeader;
+ }
+ _pinfo.len = headerBuf[3] | (uint16_t)(headerBuf[2]) << 8;
+ dataPayloadOffset += 2;
+ plen -= 2;
+ } else if(_pinfo.len == 127){
+ if (plen < 8) {
+ failPartialHeader = true;
+ goto _exceptionHandleFailPartialHeader;
+ }
+ _pinfo.len = headerBuf[9] | (uint16_t)(headerBuf[8]) << 8 | (uint32_t)(headerBuf[7]) << 16 | (uint32_t)(headerBuf[6]) << 24 | (uint64_t)(headerBuf[5]) << 32 | (uint64_t)(headerBuf[4]) << 40 | (uint64_t)(headerBuf[3]) << 48 | (uint64_t)(headerBuf[2]) << 56;
+ dataPayloadOffset += 8;
+ plen -= 8;
+ }
+
+ if(_pinfo.masked){
+ if (plen < 4) {
+ failPartialHeader = true;
+ goto _exceptionHandleFailPartialHeader;
+ }
+ memcpy(_pinfo.mask, headerBuf + dataPayloadOffset + partialHeaderLen, 4);
+ dataPayloadOffset += 4;
+ plen -= 4;
+ }
+
+ _exceptionHandleFailPartialHeader:
+ if (failPartialHeader) {
+ // We did not receive the entirety of the header - store it temporarily and we will parse it once we receive
+ // more data
+ if (initialPlen <= WS_MAX_HEADER_LEN) {
+ // If initialPlen > WS_MAX_HEADER_LEN there must be something wrong with this code. It should never happen but
+ // but it's better safe than sorry
+
+ _partialHeader = static_cast(malloc(WS_MAX_HEADER_LEN * sizeof(uint8_t)));
+ memcpy(_partialHeader, headerBuf, initialPlen * sizeof(uint8_t));
+ _partialHeaderLen = initialPlen;
+
+ } else if (initialPlen > WS_MAX_HEADER_LEN) {
+ DEBUGF("[AsyncWebSocketClient::_onData] initialPlen (= %d) > WS_MAX_HEADER_LEN (= %d)\n", initialPlen,
+ WS_MAX_HEADER_LEN);
+ }
+ }
+
+ if (headerInHeap) {
+ free((void *) headerBuf);
+ }
+
+ if (failPartialHeader) {
+ return;
+ }
+
+ data += dataPayloadOffset;
+ }
+
+ 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_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(new AsyncWebSocketControl(WS_DISCONNECT, data, datalen));
+ }
+ } else if(_pinfo.opcode == WS_PING){
+ _queueControl(new AsyncWebSocketControl(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);
+ }
+ } 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
+
+void AsyncWebSocketClient::text(const char * message, size_t len){
+ _queueMessage(new AsyncWebSocketBasicMessage(message, len));
+}
+void AsyncWebSocketClient::text(const char * message){
+ text(message, strlen(message));
+}
+void AsyncWebSocketClient::text(uint8_t * message, size_t len){
+ text((const char *)message, len);
+}
+void AsyncWebSocketClient::text(char * message){
+ text(message, strlen(message));
+}
+void AsyncWebSocketClient::text(const String &message){
+ text(message.c_str(), message.length());
+}
+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){
+ for(size_t b=0; b(data);
+ char * message = (char*) malloc(len);
+ if(message){
+ for(size_t b=0; bremoteIP();
+}
+
+uint16_t AsyncWebSocketClient::remotePort() {
+ if(!_client) {
+ return 0;
+ }
+ return _client->remotePort();
+}
+
+
+
+/*
+ * Async Web Socket - Each separate socket location
+ */
+
+AsyncWebSocket::AsyncWebSocket(const String& url)
+ :_url(url)
+ ,_clients(LinkedList([](AsyncWebSocketClient *c){ delete c; }))
+ ,_cNextId(1)
+ ,_enabled(true)
+ ,_buffers(LinkedList([](AsyncWebSocketMessageBuffer *b){ delete b; }))
+{
+ _eventHandler = NULL;
+}
+
+AsyncWebSocket::~AsyncWebSocket(){}
+
+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);
+ }
+}
+
+void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){
+ _clients.add(client);
+}
+
+void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){
+
+ _clients.remove_first([=](AsyncWebSocketClient * c){
+ return c->id() == client->id();
+ });
+}
+
+bool AsyncWebSocket::availableForWriteAll(){
+ for(const auto& c: _clients){
+ if(c->queueIsFull()) return false;
+ }
+ return true;
+}
+
+bool AsyncWebSocket::availableForWrite(uint32_t id){
+ for(const auto& c: _clients){
+ if(c->queueIsFull() && (c->id() == id )) return false;
+ }
+ return true;
+}
+
+size_t AsyncWebSocket::count() const {
+ return _clients.count_if([](AsyncWebSocketClient * c){
+ return c->status() == WS_CONNECTED;
+ });
+}
+
+AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){
+ for(const auto &c: _clients){
+ if(c->id() == id && c->status() == WS_CONNECTED){
+ return c;
+ }
+ }
+ return nullptr;
+}
+
+
+void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){
+ AsyncWebSocketClient * c = client(id);
+ if(c)
+ c->close(code, message);
+}
+
+void AsyncWebSocket::closeAll(uint16_t code, const char * message){
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c->close(code, message);
+ }
+}
+
+void AsyncWebSocket::cleanupClients(uint16_t maxClients)
+{
+ if (count() > maxClients){
+ _clients.front()->close();
+ }
+}
+
+void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){
+ AsyncWebSocketClient * c = client(id);
+ if(c)
+ c->ping(data, len);
+}
+
+void AsyncWebSocket::pingAll(uint8_t *data, size_t len){
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c->ping(data, len);
+ }
+}
+
+void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){
+ AsyncWebSocketClient * c = client(id);
+ if(c)
+ c->text(message, len);
+}
+
+void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer){
+ if (!buffer) return;
+ buffer->lock();
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED){
+ c->text(buffer);
+ }
+ }
+ buffer->unlock();
+ _cleanBuffers();
+}
+
+
+void AsyncWebSocket::textAll(const char * message, size_t len){
+ AsyncWebSocketMessageBuffer * WSBuffer = makeBuffer((uint8_t *)message, len);
+ textAll(WSBuffer);
+}
+
+void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){
+ AsyncWebSocketClient * c = client(id);
+ if(c)
+ c->binary(message, len);
+}
+
+void AsyncWebSocket::binaryAll(const char * message, size_t len){
+ AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len);
+ binaryAll(buffer);
+}
+
+void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer)
+{
+ if (!buffer) return;
+ buffer->lock();
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c->binary(buffer);
+ }
+ buffer->unlock();
+ _cleanBuffers();
+}
+
+void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){
+ AsyncWebSocketClient * c = client(id);
+ if(c)
+ c->message(message);
+}
+
+void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage *message){
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c->message(message);
+ }
+ _cleanBuffers();
+}
+
+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;
+
+ AsyncWebSocketMessageBuffer * buffer = makeBuffer(len);
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, format);
+ vsnprintf( (char *)buffer->get(), 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;
+}
+#endif
+
+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;
+
+ AsyncWebSocketMessageBuffer * buffer = makeBuffer(len + 1);
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, formatP);
+ vsnprintf_P((char *)buffer->get(), len + 1, formatP, arg);
+ va_end(arg);
+
+ textAll(buffer);
+ return len;
+}
+
+void AsyncWebSocket::text(uint32_t id, const char * message){
+ text(id, message, strlen(message));
+}
+void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){
+ text(id, (const char *)message, len);
+}
+void AsyncWebSocket::text(uint32_t id, char * message){
+ text(id, message, strlen(message));
+}
+void AsyncWebSocket::text(uint32_t id, const String &message){
+ text(id, message.c_str(), message.length());
+}
+void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *message){
+ AsyncWebSocketClient * c = client(id);
+ if(c != NULL)
+ c->text(message);
+}
+void AsyncWebSocket::textAll(const char * message){
+ textAll(message, strlen(message));
+}
+void AsyncWebSocket::textAll(uint8_t * message, size_t len){
+ textAll((const char *)message, len);
+}
+void AsyncWebSocket::textAll(char * message){
+ textAll(message, strlen(message));
+}
+void AsyncWebSocket::textAll(const String &message){
+ textAll(message.c_str(), message.length());
+}
+void AsyncWebSocket::textAll(const __FlashStringHelper *message){
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c->text(message);
+ }
+}
+void AsyncWebSocket::binary(uint32_t id, const char * message){
+ binary(id, message, strlen(message));
+}
+void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){
+ binary(id, (const char *)message, len);
+}
+void AsyncWebSocket::binary(uint32_t id, char * message){
+ binary(id, message, strlen(message));
+}
+void AsyncWebSocket::binary(uint32_t id, const String &message){
+ binary(id, message.c_str(), message.length());
+}
+void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *message, size_t len){
+ AsyncWebSocketClient * c = client(id);
+ if(c != NULL)
+ c-> binary(message, len);
+}
+void AsyncWebSocket::binaryAll(const char * message){
+ binaryAll(message, strlen(message));
+}
+void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){
+ binaryAll((const char *)message, len);
+}
+void AsyncWebSocket::binaryAll(char * message){
+ binaryAll(message, strlen(message));
+}
+void AsyncWebSocket::binaryAll(const String &message){
+ binaryAll(message.c_str(), message.length());
+}
+void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){
+ for(const auto& c: _clients){
+ if(c->status() == WS_CONNECTED)
+ c-> binary(message, len);
+ }
+ }
+
+const char * WS_STR_CONNECTION = "Connection";
+const char * WS_STR_UPGRADE = "Upgrade";
+const char * WS_STR_ORIGIN = "Origin";
+const char * WS_STR_VERSION = "Sec-WebSocket-Version";
+const char * WS_STR_KEY = "Sec-WebSocket-Key";
+const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol";
+const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept";
+const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+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_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 != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())){
+ return request->requestAuthentication();
+ }
+ AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
+ if(version->value().toInt() != 13){
+ AsyncWebServerResponse *response = request->beginResponse(400);
+ response->addHeader(WS_STR_VERSION,"13");
+ request->send(response);
+ return;
+ }
+ AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
+ AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
+ if(request->hasHeader(WS_STR_PROTOCOL)){
+ 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) {
+ AsyncWebLockGuard l(_lock);
+ _buffers.add(buffer);
+ }
+ return buffer;
+}
+
+AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size)
+{
+ AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size);
+
+ if (buffer) {
+ AsyncWebLockGuard l(_lock);
+ _buffers.add(buffer);
+ }
+
+ return buffer;
+}
+
+void AsyncWebSocket::_cleanBuffers()
+{
+ AsyncWebLockGuard l(_lock);
+
+ for(AsyncWebSocketMessageBuffer * c: _buffers){
+ if(c && c->canDelete()){
+ _buffers.remove(c);
+ }
+ }
+}
+
+AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const {
+ return _clients;
+}
+
+/*
+ * 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 = (uint8_t*)malloc(20);
+ if(hash == NULL){
+ _state = RESPONSE_FAILED;
+ return;
+ }
+ char * buffer = (char *) malloc(33);
+ if(buffer == NULL){
+ free(hash);
+ _state = RESPONSE_FAILED;
+ return;
+ }
+#ifdef ESP8266
+ sha1(key + WS_STR_UUID, hash);
+#else
+ (String&)key += WS_STR_UUID;
+ mbedtls_sha1_context ctx;
+ mbedtls_sha1_init(&ctx);
+ mbedtls_sha1_starts_ret(&ctx);
+ mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length());
+ mbedtls_sha1_finish_ret(&ctx, hash);
+ mbedtls_sha1_free(&ctx);
+#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, "websocket");
+ addHeader(WS_STR_ACCEPT,buffer);
+ free(buffer);
+ free(hash);
+}
+
+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){
+ new AsyncWebSocketClient(request, _server);
+ }
+ return 0;
+}
diff --git a/components/AsyncWebServer/src/AsyncWebSocket.h b/components/AsyncWebServer/src/AsyncWebSocket.h
new file mode 100755
index 0000000..d48289f
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncWebSocket.h
@@ -0,0 +1,354 @@
+/*
+ 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
+#define WS_MAX_QUEUED_MESSAGES 32
+#else
+#include
+#define WS_MAX_QUEUED_MESSAGES 8
+#endif
+#include
+
+#include "AsyncWebSynchronization.h"
+
+#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
+
+#ifdef ESP32
+#define DEFAULT_MAX_WS_CLIENTS 8
+#else
+#define DEFAULT_MAX_WS_CLIENTS 4
+#endif
+
+#define WS_MAX_HEADER_LEN 16
+
+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 {
+ private:
+ uint8_t * _data;
+ size_t _len;
+ bool _lock;
+ uint32_t _count;
+
+ public:
+ AsyncWebSocketMessageBuffer();
+ AsyncWebSocketMessageBuffer(size_t size);
+ AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
+ AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
+ AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
+ ~AsyncWebSocketMessageBuffer();
+ void operator ++(int i) { (void)i; _count++; }
+ void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; }
+ bool reserve(size_t size);
+ void lock() { _lock = true; }
+ void unlock() { _lock = false; }
+ uint8_t * get() { return _data; }
+ size_t length() { return _len; }
+ uint32_t count() { return _count; }
+ bool canDelete() { return (!_count && !_lock); }
+
+ friend AsyncWebSocket;
+
+};
+
+class AsyncWebSocketMessage {
+ protected:
+ uint8_t _opcode;
+ bool _mask;
+ AwsMessageStatus _status;
+ public:
+ AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
+ virtual ~AsyncWebSocketMessage(){}
+ virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
+ virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
+ virtual bool finished(){ return _status != WS_MSG_SENDING; }
+ virtual bool betweenFrames() const { return false; }
+};
+
+class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
+ private:
+ size_t _len;
+ size_t _sent;
+ size_t _ack;
+ size_t _acked;
+ uint8_t * _data;
+public:
+ AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
+ AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
+ virtual ~AsyncWebSocketBasicMessage() override;
+ virtual bool betweenFrames() const override { return _acked == _ack; }
+ virtual void ack(size_t len, uint32_t time) override ;
+ virtual size_t send(AsyncClient *client) override ;
+};
+
+class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
+ private:
+ uint8_t * _data;
+ size_t _len;
+ size_t _sent;
+ size_t _ack;
+ size_t _acked;
+ AsyncWebSocketMessageBuffer * _WSbuffer;
+public:
+ AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
+ virtual ~AsyncWebSocketMultiMessage() override;
+ virtual bool betweenFrames() const override { return _acked == _ack; }
+ virtual void ack(size_t len, uint32_t time) override ;
+ virtual size_t send(AsyncClient *client) override ;
+};
+
+class AsyncWebSocketClient {
+ private:
+ AsyncClient *_client;
+ AsyncWebSocket *_server;
+ uint32_t _clientId;
+ AwsClientStatus _status;
+
+ LinkedList _controlQueue;
+ LinkedList _messageQueue;
+
+ uint8_t _pstate;
+ AwsFrameInfo _pinfo;
+ uint8_t *_partialHeader;
+ uint8_t _partialHeaderLen;
+
+ uint32_t _lastMessageTime;
+ uint32_t _keepAlivePeriod;
+
+ void _queueMessage(AsyncWebSocketMessage *dataMessage);
+ void _queueControl(AsyncWebSocketControl *controlMessage);
+ void _runQueue();
+
+ public:
+ void *_tempObject;
+
+ AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
+ ~AsyncWebSocketClient();
+
+ //client id increments for the given server
+ uint32_t id(){ return _clientId; }
+ AwsClientStatus status(){ return _status; }
+ AsyncClient* client(){ return _client; }
+ AsyncWebSocket *server(){ return _server; }
+ AwsFrameInfo const &pinfo() const { return _pinfo; }
+
+ IPAddress remoteIP();
+ uint16_t remotePort();
+
+ //control frames
+ void close(uint16_t code=0, const char * message=NULL);
+ void ping(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(AsyncWebSocketMessage *message){ _queueMessage(message); }
+ bool queueIsFull();
+
+ 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(const char * message, size_t len);
+ void text(const char * message);
+ void text(uint8_t * message, size_t len);
+ void text(char * message);
+ void text(const String &message);
+ void text(const __FlashStringHelper *data);
+ void text(AsyncWebSocketMessageBuffer *buffer);
+
+ void binary(const char * message, size_t len);
+ void binary(const char * message);
+ void binary(uint8_t * message, size_t len);
+ void binary(char * message);
+ void binary(const String &message);
+ void binary(const __FlashStringHelper *data, size_t len);
+ void binary(AsyncWebSocketMessageBuffer *buffer);
+
+ bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; }
+
+ //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);
+};
+
+typedef std::function AwsEventHandler;
+
+//WebServer Handler implementation that plays the role of a socket server
+class AsyncWebSocket: public AsyncWebHandler {
+ public:
+ typedef LinkedList AsyncWebSocketClientLinkedList;
+ private:
+ String _url;
+ AsyncWebSocketClientLinkedList _clients;
+ uint32_t _cNextId;
+ AwsEventHandler _eventHandler;
+ bool _enabled;
+ AsyncWebLock _lock;
+
+ public:
+ AsyncWebSocket(const String& url);
+ ~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) != NULL; }
+
+ 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, uint8_t *data=NULL, size_t len=0);
+ void pingAll(uint8_t *data=NULL, size_t len=0); // done
+
+ void text(uint32_t id, const char * message, size_t len);
+ void text(uint32_t id, const char * message);
+ void text(uint32_t id, uint8_t * message, size_t len);
+ void text(uint32_t id, char * message);
+ void text(uint32_t id, const String &message);
+ void text(uint32_t id, const __FlashStringHelper *message);
+
+ void textAll(const char * message, size_t len);
+ void textAll(const char * message);
+ void textAll(uint8_t * message, size_t len);
+ void textAll(char * message);
+ void textAll(const String &message);
+ void textAll(const __FlashStringHelper *message); // need to convert
+ void textAll(AsyncWebSocketMessageBuffer * buffer);
+
+ void binary(uint32_t id, const char * message, size_t len);
+ void binary(uint32_t id, const char * message);
+ void binary(uint32_t id, uint8_t * message, size_t len);
+ void binary(uint32_t id, char * message);
+ void binary(uint32_t id, const String &message);
+ void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
+
+ void binaryAll(const char * message, size_t len);
+ void binaryAll(const char * message);
+ void binaryAll(uint8_t * message, size_t len);
+ void binaryAll(char * message);
+ void binaryAll(const String &message);
+ void binaryAll(const __FlashStringHelper *message, size_t len);
+ void binaryAll(AsyncWebSocketMessageBuffer * buffer);
+
+ void message(uint32_t id, AsyncWebSocketMessage *message);
+ void messageAll(AsyncWebSocketMultiMessage *message);
+
+ 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)));
+#endif
+ size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
+
+ //event listener
+ void onEvent(AwsEventHandler handler){
+ _eventHandler = handler;
+ }
+
+ //system callbacks (do not call)
+ uint32_t _getNextId(){ return _cNextId++; }
+ void _addClient(AsyncWebSocketClient * client);
+ void _handleDisconnect(AsyncWebSocketClient * client);
+ 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(uint8_t * data, size_t size);
+ LinkedList _buffers;
+ void _cleanBuffers();
+
+ AsyncWebSocketClientLinkedList getClients() const;
+};
+
+//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/components/AsyncWebServer/src/AsyncWebSynchronization.h b/components/AsyncWebServer/src/AsyncWebSynchronization.h
new file mode 100755
index 0000000..f36c52d
--- /dev/null
+++ b/components/AsyncWebServer/src/AsyncWebSynchronization.h
@@ -0,0 +1,87 @@
+#ifndef ASYNCWEBSYNCHRONIZATION_H_
+#define ASYNCWEBSYNCHRONIZATION_H_
+
+// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
+
+#include
+
+#ifdef ESP32
+
+// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
+class AsyncWebLock
+{
+private:
+ SemaphoreHandle_t _lock;
+ mutable void *_lockedBy;
+
+public:
+ AsyncWebLock() {
+ _lock = xSemaphoreCreateBinary();
+ _lockedBy = NULL;
+ xSemaphoreGive(_lock);
+ }
+
+ ~AsyncWebLock() {
+ vSemaphoreDelete(_lock);
+ }
+
+ bool lock() const {
+ extern void *pxCurrentTCB;
+ if (_lockedBy != pxCurrentTCB) {
+ xSemaphoreTake(_lock, portMAX_DELAY);
+ _lockedBy = pxCurrentTCB;
+ return true;
+ }
+ return false;
+ }
+
+ void unlock() const {
+ _lockedBy = NULL;
+ xSemaphoreGive(_lock);
+ }
+};
+
+#else
+
+// This is the 8266 version of the Sync Lock which is currently unimplemented
+class AsyncWebLock
+{
+
+public:
+ AsyncWebLock() {
+ }
+
+ ~AsyncWebLock() {
+ }
+
+ bool lock() const {
+ return false;
+ }
+
+ void unlock() const {
+ }
+};
+#endif
+
+class AsyncWebLockGuard
+{
+private:
+ const AsyncWebLock *_lock;
+
+public:
+ AsyncWebLockGuard(const AsyncWebLock &l) {
+ if (l.lock()) {
+ _lock = &l;
+ } else {
+ _lock = NULL;
+ }
+ }
+
+ ~AsyncWebLockGuard() {
+ if (_lock) {
+ _lock->unlock();
+ }
+ }
+};
+
+#endif // ASYNCWEBSYNCHRONIZATION_H_
\ No newline at end of file
diff --git a/components/AsyncWebServer/src/ESPAsyncWebServer.h b/components/AsyncWebServer/src/ESPAsyncWebServer.h
new file mode 100755
index 0000000..7cd21aa
--- /dev/null
+++ b/components/AsyncWebServer/src/ESPAsyncWebServer.h
@@ -0,0 +1,471 @@
+/*
+ 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
+#include "FS.h"
+
+#include "StringArray.h"
+
+#ifdef ESP32
+#include
+#include
+#elif defined(ESP8266)
+#include
+#include
+#else
+#error Platform not supported
+#endif
+
+#ifdef ASYNCWEBSERVER_REGEX
+#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
+#else
+#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
+#endif
+
+#define DEBUGF(...) //Serial.printf(__VA_ARGS__)
+
+class AsyncWebServer;
+class AsyncWebServerRequest;
+class AsyncWebServerResponse;
+class AsyncWebHeader;
+class AsyncWebParameter;
+class AsyncWebRewrite;
+class AsyncWebHandler;
+class AsyncStaticWebHandler;
+class AsyncCallbackWebHandler;
+class AsyncResponseStream;
+
+#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
+
+//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(const String& name, const String& value): _name(name), _value(value){}
+ AsyncWebHeader(const String& data): _name(), _value(){
+ if(!data) return;
+ int index = data.indexOf(':');
+ if (index < 0) return;
+ _name = data.substring(0, index);
+ _value = data.substring(index + 2);
+ }
+ ~AsyncWebHeader(){}
+ const String& name() const { return _name; }
+ const String& value() const { return _value; }
+ String toString() const { return String(_name+": "+_value+"\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;
+ StringArray _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;
+
+ LinkedList _headers;
+ LinkedList _params;
+ LinkedList _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 _addParam(AsyncWebParameter*);
+ 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; }
+ const char * methodToString() const;
+ const char * requestedConnTypeToString() const;
+ 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; }
+ void addInterestingHeader(const String& name);
+
+ void redirect(const String& url);
+
+ void send(AsyncWebServerResponse *response);
+ void send(int code, const String& contentType=String(), const String& content=String());
+ 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);
+ void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
+ void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
+
+ AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String());
+ 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);
+ AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
+ AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
+
+ size_t headers() const; // get header count
+ bool hasHeader(const String& name) const; // check if header exists
+ bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
+
+ AsyncWebHeader* getHeader(const String& name) const;
+ AsyncWebHeader* getHeader(const __FlashStringHelper * data) 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;
+
+ AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const;
+ AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
+ AsyncWebParameter* getParam(size_t num) const;
+
+ size_t args() const { return params(); } // get arguments count
+ const String& arg(const String& name) const; // get request argument value by name
+ const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
+ 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 __FlashStringHelper * data) const; // check if F(argument) exists
+
+ const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
+
+ const String& header(const char* name) const;// get request header value by name
+ const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
+ 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)
+ * */
+
+typedef std::function ArRequestFilterFunction;
+
+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;
+ public:
+ AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
+ 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;
+ String _username;
+ String _password;
+ public:
+ AsyncWebHandler():_username(""), _password(""){}
+ AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
+ AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(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;
+ LinkedList _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:
+ 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;
+ LinkedList _rewrites;
+ LinkedList _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);
+ bool removeRewrite(AsyncWebRewrite* rewrite);
+ AsyncWebRewrite& rewrite(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 = LinkedList;
+ headers_t _headers;
+
+ DefaultHeaders()
+ :_headers(headers_t([](AsyncWebHeader *h){ delete h; }))
+ {}
+public:
+ using ConstIterator = headers_t::ConstIterator;
+
+ void addHeader(const String& name, const String& value){
+ _headers.add(new AsyncWebHeader(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 "WebResponseImpl.h"
+#include "WebHandlerImpl.h"
+#include "AsyncWebSocket.h"
+#include "AsyncEventSource.h"
+
+#endif /* _AsyncWebServer_H_ */
diff --git a/components/AsyncWebServer/src/SPIFFSEditor.cpp b/components/AsyncWebServer/src/SPIFFSEditor.cpp
new file mode 100755
index 0000000..a84fa87
--- /dev/null
+++ b/components/AsyncWebServer/src/SPIFFSEditor.cpp
@@ -0,0 +1,544 @@
+#include "SPIFFSEditor.h"
+#include
+
+//File: edit.htm.gz, Size: 4151
+#define edit_htm_gz_len 4151
+const uint8_t edit_htm_gz[] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68,
+ 0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED,
+ 0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6,
+ 0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB,
+ 0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A,
+ 0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61,
+ 0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7,
+ 0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02,
+ 0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C,
+ 0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A,
+ 0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89,
+ 0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76,
+ 0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D,
+ 0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9,
+ 0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B,
+ 0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91,
+ 0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78,
+ 0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78,
+ 0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98,
+ 0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E,
+ 0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41,
+ 0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21,
+ 0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F,
+ 0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74,
+ 0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C,
+ 0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0,
+ 0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C,
+ 0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30,
+ 0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9,
+ 0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61,
+ 0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B,
+ 0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9,
+ 0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B,
+ 0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD,
+ 0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3,
+ 0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77,
+ 0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83,
+ 0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF,
+ 0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3,
+ 0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55,
+ 0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3,
+ 0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF,
+ 0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF,
+ 0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60,
+ 0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1,
+ 0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE,
+ 0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F,
+ 0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0,
+ 0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9,
+ 0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5,
+ 0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15,
+ 0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74,
+ 0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D,
+ 0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD,
+ 0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A,
+ 0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6,
+ 0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2,
+ 0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF,
+ 0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53,
+ 0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2,
+ 0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A,
+ 0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83,
+ 0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26,
+ 0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0,
+ 0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0,
+ 0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84,
+ 0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99,
+ 0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5,
+ 0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9,
+ 0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87,
+ 0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F,
+ 0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6,
+ 0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B,
+ 0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D,
+ 0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25,
+ 0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3,
+ 0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F,
+ 0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35,
+ 0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A,
+ 0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6,
+ 0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7,
+ 0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A,
+ 0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9,
+ 0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97,
+ 0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36,
+ 0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C,
+ 0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A,
+ 0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C,
+ 0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F,
+ 0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11,
+ 0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16,
+ 0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA,
+ 0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB,
+ 0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A,
+ 0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6,
+ 0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28,
+ 0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1,
+ 0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E,
+ 0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E,
+ 0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92,
+ 0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05,
+ 0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8,
+ 0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0,
+ 0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85,
+ 0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40,
+ 0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56,
+ 0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47,
+ 0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA,
+ 0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7,
+ 0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD,
+ 0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61,
+ 0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58,
+ 0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D,
+ 0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8,
+ 0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C,
+ 0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA,
+ 0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49,
+ 0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51,
+ 0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00,
+ 0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A,
+ 0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A,
+ 0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35,
+ 0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F,
+ 0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E,
+ 0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C,
+ 0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64,
+ 0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C,
+ 0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1,
+ 0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B,
+ 0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC,
+ 0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42,
+ 0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B,
+ 0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71,
+ 0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F,
+ 0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28,
+ 0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9,
+ 0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD,
+ 0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6,
+ 0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F,
+ 0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5,
+ 0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8,
+ 0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF,
+ 0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62,
+ 0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C,
+ 0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7,
+ 0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89,
+ 0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29,
+ 0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95,
+ 0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7,
+ 0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB,
+ 0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09,
+ 0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F,
+ 0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60,
+ 0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35,
+ 0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6,
+ 0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B,
+ 0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66,
+ 0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25,
+ 0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E,
+ 0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97,
+ 0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC,
+ 0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE,
+ 0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7,
+ 0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13,
+ 0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0,
+ 0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A,
+ 0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93,
+ 0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E,
+ 0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9,
+ 0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78,
+ 0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5,
+ 0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12,
+ 0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E,
+ 0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35,
+ 0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98,
+ 0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58,
+ 0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3,
+ 0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64,
+ 0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39,
+ 0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D,
+ 0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62,
+ 0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48,
+ 0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D,
+ 0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8,
+ 0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9,
+ 0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30,
+ 0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6,
+ 0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1,
+ 0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56,
+ 0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84,
+ 0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0,
+ 0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC,
+ 0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E,
+ 0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39,
+ 0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B,
+ 0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA,
+ 0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1,
+ 0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1,
+ 0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88,
+ 0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4,
+ 0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC,
+ 0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98,
+ 0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97,
+ 0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8,
+ 0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30,
+ 0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA,
+ 0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B,
+ 0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC,
+ 0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45,
+ 0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD,
+ 0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76,
+ 0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD,
+ 0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76,
+ 0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4,
+ 0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF,
+ 0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4,
+ 0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42,
+ 0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43,
+ 0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B,
+ 0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97,
+ 0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73,
+ 0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B,
+ 0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50,
+ 0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51,
+ 0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25,
+ 0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26,
+ 0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80,
+ 0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9,
+ 0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0,
+ 0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74,
+ 0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA,
+ 0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D,
+ 0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F,
+ 0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2,
+ 0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1,
+ 0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99,
+ 0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D,
+ 0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B,
+ 0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD,
+ 0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F,
+ 0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB,
+ 0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47,
+ 0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59,
+ 0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D,
+ 0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD,
+ 0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94,
+ 0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35,
+ 0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81,
+ 0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D,
+ 0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20,
+ 0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB,
+ 0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B,
+ 0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6,
+ 0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17,
+ 0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8,
+ 0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26,
+ 0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57,
+ 0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36,
+ 0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53,
+ 0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00
+};
+
+#define SPIFFS_MAXLENGTH_FILEPATH 32
+const char *excludeListFile = "/.exclude.files";
+
+typedef struct ExcludeListS {
+ char *item;
+ ExcludeListS *next;
+} ExcludeList;
+
+static ExcludeList *excludes = NULL;
+
+static bool matchWild(const char *pattern, const char *testee) {
+ const char *nxPat = NULL, *nxTst = NULL;
+
+ while (*testee) {
+ if (( *pattern == '?' ) || (*pattern == *testee)){
+ pattern++;testee++;
+ continue;
+ }
+ if (*pattern=='*'){
+ nxPat=pattern++; nxTst=testee;
+ continue;
+ }
+ if (nxPat){
+ pattern = nxPat+1; testee=++nxTst;
+ continue;
+ }
+ return false;
+ }
+ while (*pattern=='*'){pattern++;}
+ return (*pattern == 0);
+}
+
+static bool addExclude(const char *item){
+ size_t len = strlen(item);
+ if(!len){
+ return false;
+ }
+ ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList));
+ if(!e){
+ return false;
+ }
+ e->item = (char *)malloc(len+1);
+ if(!e->item){
+ free(e);
+ return false;
+ }
+ memcpy(e->item, item, len+1);
+ e->next = excludes;
+ excludes = e;
+ return true;
+}
+
+static void loadExcludeList(fs::FS &_fs, const char *filename){
+ static char linebuf[SPIFFS_MAXLENGTH_FILEPATH];
+ fs::File excludeFile=_fs.open(filename, "r");
+ if(!excludeFile){
+ //addExclude("/*.js.gz");
+ return;
+ }
+#ifdef ESP32
+ if(excludeFile.isDirectory()){
+ excludeFile.close();
+ return;
+ }
+#endif
+ if (excludeFile.size() > 0){
+ uint8_t idx;
+ bool isOverflowed = false;
+ while (excludeFile.available()){
+ linebuf[0] = '\0';
+ idx = 0;
+ int lastChar;
+ do {
+ lastChar = excludeFile.read();
+ if(lastChar != '\r'){
+ linebuf[idx++] = (char) lastChar;
+ }
+ } while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH));
+
+ if(isOverflowed){
+ isOverflowed = (lastChar != '\n');
+ continue;
+ }
+ isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH);
+ linebuf[idx-1] = '\0';
+ if(!addExclude(linebuf)){
+ excludeFile.close();
+ return;
+ }
+ }
+ }
+ excludeFile.close();
+}
+
+static bool isExcluded(fs::FS &_fs, const char *filename) {
+ if(excludes == NULL){
+ loadExcludeList(_fs, excludeListFile);
+ }
+ ExcludeList *e = excludes;
+ while(e){
+ if (matchWild(e->item, filename)){
+ return true;
+ }
+ e = e->next;
+ }
+ return false;
+}
+
+// WEB HANDLER IMPLEMENTATION
+
+#ifdef ESP32
+SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password)
+#else
+SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs)
+#endif
+:_fs(fs)
+,_username(username)
+,_password(password)
+,_authenticated(false)
+,_startTime(0)
+{}
+
+bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){
+ if(request->url().equalsIgnoreCase("/edit")){
+ if(request->method() == HTTP_GET){
+ if(request->hasParam("list"))
+ return true;
+ if(request->hasParam("edit")){
+ request->_tempFile = _fs.open(request->arg("edit"), "r");
+ if(!request->_tempFile){
+ return false;
+ }
+#ifdef ESP32
+ if(request->_tempFile.isDirectory()){
+ request->_tempFile.close();
+ return false;
+ }
+#endif
+ }
+ if(request->hasParam("download")){
+ request->_tempFile = _fs.open(request->arg("download"), "r");
+ if(!request->_tempFile){
+ return false;
+ }
+#ifdef ESP32
+ if(request->_tempFile.isDirectory()){
+ request->_tempFile.close();
+ return false;
+ }
+#endif
+ }
+ request->addInterestingHeader("If-Modified-Since");
+ return true;
+ }
+ else if(request->method() == HTTP_POST)
+ return true;
+ else if(request->method() == HTTP_DELETE)
+ return true;
+ else if(request->method() == HTTP_PUT)
+ return true;
+
+ }
+ return false;
+}
+
+
+void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){
+ if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+
+ if(request->method() == HTTP_GET){
+ if(request->hasParam("list")){
+ String path = request->getParam("list")->value();
+#ifdef ESP32
+ File dir = _fs.open(path);
+#else
+ Dir dir = _fs.openDir(path);
+#endif
+ path = String();
+ String output = "[";
+#ifdef ESP32
+ File entry = dir.openNextFile();
+ while(entry){
+#else
+ while(dir.next()){
+ fs::File entry = dir.openFile("r");
+#endif
+ if (isExcluded(_fs, entry.name())) {
+#ifdef ESP32
+ entry = dir.openNextFile();
+#endif
+ continue;
+ }
+ if (output != "[") output += ',';
+ output += "{\"type\":\"";
+ output += "file";
+ output += "\",\"name\":\"";
+ output += String(entry.name());
+ output += "\",\"size\":";
+ output += String(entry.size());
+ output += "}";
+#ifdef ESP32
+ entry = dir.openNextFile();
+#else
+ entry.close();
+#endif
+ }
+#ifdef ESP32
+ dir.close();
+#endif
+ output += "]";
+ request->send(200, "application/json", output);
+ output = String();
+ }
+ else if(request->hasParam("edit") || request->hasParam("download")){
+ request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download"));
+ }
+ else {
+ const char * buildTime = __DATE__ " " __TIME__ " GMT";
+ if (request->header("If-Modified-Since").equals(buildTime)) {
+ request->send(304);
+ } else {
+ AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
+ response->addHeader("Content-Encoding", "gzip");
+ response->addHeader("Last-Modified", buildTime);
+ request->send(response);
+ }
+ }
+ } else if(request->method() == HTTP_DELETE){
+ if(request->hasParam("path", true)){
+ _fs.remove(request->getParam("path", true)->value());
+ request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
+ } else
+ request->send(404);
+ } else if(request->method() == HTTP_POST){
+ if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value()))
+ request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
+ else
+ request->send(500);
+ } else if(request->method() == HTTP_PUT){
+ if(request->hasParam("path", true)){
+ String filename = request->getParam("path", true)->value();
+ if(_fs.exists(filename)){
+ request->send(200);
+ } else {
+ fs::File f = _fs.open(filename, "w");
+ if(f){
+ f.write((uint8_t)0x00);
+ f.close();
+ request->send(200, "", "CREATE: "+filename);
+ } else {
+ request->send(500);
+ }
+ }
+ } else
+ request->send(400);
+ }
+}
+
+void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
+ if(!index){
+ if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){
+ _authenticated = true;
+ request->_tempFile = _fs.open(filename, "w");
+ _startTime = millis();
+ }
+ }
+ if(_authenticated && request->_tempFile){
+ if(len){
+ request->_tempFile.write(data,len);
+ }
+ if(final){
+ request->_tempFile.close();
+ }
+ }
+}
diff --git a/components/AsyncWebServer/src/SPIFFSEditor.h b/components/AsyncWebServer/src/SPIFFSEditor.h
new file mode 100755
index 0000000..3586429
--- /dev/null
+++ b/components/AsyncWebServer/src/SPIFFSEditor.h
@@ -0,0 +1,24 @@
+#ifndef SPIFFSEditor_H_
+#define SPIFFSEditor_H_
+#include
+
+class SPIFFSEditor: public AsyncWebHandler {
+ private:
+ fs::FS _fs;
+ String _username;
+ String _password;
+ bool _authenticated;
+ uint32_t _startTime;
+ public:
+#ifdef ESP32
+ SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String());
+#else
+ SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS);
+#endif
+ virtual bool canHandle(AsyncWebServerRequest *request) override final;
+ virtual void handleRequest(AsyncWebServerRequest *request) override final;
+ virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final;
+ virtual bool isRequestHandlerTrivial() override final {return false;}
+};
+
+#endif
diff --git a/components/AsyncWebServer/src/StringArray.h b/components/AsyncWebServer/src/StringArray.h
new file mode 100755
index 0000000..266bd9d
--- /dev/null
+++ b/components/AsyncWebServer/src/StringArray.h
@@ -0,0 +1,202 @@
+/*
+ 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 STRINGARRAY_H_
+#define STRINGARRAY_H_
+
+#include "stddef.h"
+#include "WString.h"
+
+template
+class LinkedListNode {
+ T _value;
+ public:
+ LinkedListNode* next;
+ LinkedListNode(const T val): _value(val), next(nullptr) {}
+ ~LinkedListNode(){}
+ const T& value() const { return _value; };
+ T& value(){ return _value; }
+};
+
+template class Item = LinkedListNode>
+class LinkedList {
+ public:
+ typedef Item ItemType;
+ typedef std::function OnRemove;
+ typedef std::function Predicate;
+ private:
+ ItemType* _root;
+ OnRemove _onRemove;
+
+ class Iterator {
+ ItemType* _node;
+ ItemType* _nextNode = nullptr;
+ public:
+ Iterator(ItemType* current = nullptr) : _node(current) {
+ if (_node != nullptr) {
+ _nextNode = current->next;
+ }
+ }
+ Iterator(const Iterator& i) : _node(i._node) {}
+ Iterator& operator ++() {
+ _node = _nextNode;
+ _nextNode = _node != nullptr ? _node->next : nullptr;
+ return *this;
+ }
+ bool operator != (const Iterator& i) const { return _node != i._node; }
+ const T& operator * () const { return _node->value(); }
+ const T* operator -> () const { return &_node->value(); }
+ };
+
+ public:
+ typedef const Iterator ConstIterator;
+ ConstIterator begin() const { return ConstIterator(_root); }
+ ConstIterator end() const { return ConstIterator(nullptr); }
+
+ LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
+ ~LinkedList(){}
+ void add(const T& t){
+ auto it = new ItemType(t);
+ if(!_root){
+ _root = it;
+ } else {
+ auto i = _root;
+ while(i->next) i = i->next;
+ i->next = it;
+ }
+ }
+ T& front() const {
+ return _root->value();
+ }
+
+ bool isEmpty() const {
+ return _root == nullptr;
+ }
+ size_t length() const {
+ size_t i = 0;
+ auto it = _root;
+ while(it){
+ i++;
+ it = it->next;
+ }
+ return i;
+ }
+ size_t count_if(Predicate predicate) const {
+ size_t i = 0;
+ auto it = _root;
+ while(it){
+ if (!predicate){
+ i++;
+ }
+ else if (predicate(it->value())) {
+ i++;
+ }
+ it = it->next;
+ }
+ return i;
+ }
+ const T* nth(size_t N) const {
+ size_t i = 0;
+ auto it = _root;
+ while(it){
+ if(i++ == N)
+ return &(it->value());
+ it = it->next;
+ }
+ return nullptr;
+ }
+ bool remove(const T& t){
+ auto it = _root;
+ auto pit = _root;
+ while(it){
+ if(it->value() == t){
+ if(it == _root){
+ _root = _root->next;
+ } else {
+ pit->next = it->next;
+ }
+
+ if (_onRemove) {
+ _onRemove(it->value());
+ }
+
+ delete it;
+ return true;
+ }
+ pit = it;
+ it = it->next;
+ }
+ return false;
+ }
+ bool remove_first(Predicate predicate){
+ auto it = _root;
+ auto pit = _root;
+ while(it){
+ if(predicate(it->value())){
+ if(it == _root){
+ _root = _root->next;
+ } else {
+ pit->next = it->next;
+ }
+ if (_onRemove) {
+ _onRemove(it->value());
+ }
+ delete it;
+ return true;
+ }
+ pit = it;
+ it = it->next;
+ }
+ return false;
+ }
+
+ void free(){
+ while(_root != nullptr){
+ auto it = _root;
+ _root = _root->next;
+ if (_onRemove) {
+ _onRemove(it->value());
+ }
+ delete it;
+ }
+ _root = nullptr;
+ }
+};
+
+
+class StringArray : public LinkedList {
+public:
+
+ StringArray() : LinkedList(nullptr) {}
+
+ bool containsIgnoreCase(const String& str){
+ for (const auto& s : *this) {
+ if (str.equalsIgnoreCase(s)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+
+
+
+#endif /* STRINGARRAY_H_ */
diff --git a/components/AsyncWebServer/src/WebAuthentication.cpp b/components/AsyncWebServer/src/WebAuthentication.cpp
new file mode 100755
index 0000000..45246a1
--- /dev/null
+++ b/components/AsyncWebServer/src/WebAuthentication.cpp
@@ -0,0 +1,235 @@
+/*
+ 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
+#ifdef ESP32
+#include "mbedtls/md5.h"
+#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)
+ 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(toencode, "%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
+#ifdef ESP32
+ mbedtls_md5_context _ctx;
+#else
+ md5_context_t _ctx;
+#endif
+ uint8_t i;
+ uint8_t * _buf = (uint8_t*)malloc(16);
+ if(_buf == NULL)
+ return false;
+ memset(_buf, 0x00, 16);
+#ifdef ESP32
+ mbedtls_md5_init(&_ctx);
+ mbedtls_md5_starts_ret(&_ctx);
+ mbedtls_md5_update_ret(&_ctx, data, len);
+ mbedtls_md5_finish_ret(&_ctx, _buf);
+#else
+ MD5Init(&_ctx);
+ MD5Update(&_ctx, data, len);
+ MD5Final(_buf, &_ctx);
+#endif
+ for(i = 0; i < 16; i++) {
+ sprintf(output + (i * 2), "%02x", _buf[i]);
+ }
+ free(_buf);
+ 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 "";
+ 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 "";
+ 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 "";
+ }
+ char * out = (char*)malloc(33);
+ String res = String(username);
+ res.concat(":");
+ res.concat(realm);
+ res.concat(":");
+ String in = res;
+ in.concat(password);
+ if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
+ return "";
+ res.concat(out);
+ free(out);
+ return res;
+}
+
+String requestDigestAuthentication(const char * realm){
+ String header = "realm=\"";
+ if(realm == NULL)
+ header.concat("asyncesp");
+ else
+ header.concat(realm);
+ header.concat( "\", qop=\"auth\", nonce=\"");
+ header.concat(genRandomMD5());
+ header.concat("\", opaque=\"");
+ header.concat(genRandomMD5());
+ header.concat("\"");
+ return header;
+}
+
+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){
+ if(username == NULL || password == NULL || header == NULL || method == NULL){
+ //os_printf("AUTH FAIL: missing requred fields\n");
+ return false;
+ }
+
+ String myHeader = String(header);
+ int nextBreak = myHeader.indexOf(",");
+ if(nextBreak < 0){
+ //os_printf("AUTH FAIL: no variables\n");
+ return false;
+ }
+
+ String myUsername = String();
+ String myRealm = String();
+ String myNonce = String();
+ String myUri = String();
+ String myResponse = String();
+ String myQop = String();
+ String myNc = String();
+ String myCnonce = String();
+
+ myHeader += ", ";
+ 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("\"")){
+ avLine = avLine.substring(1, avLine.length() - 1);
+ }
+
+ if(varName.equals("username")){
+ if(!avLine.equals(username)){
+ //os_printf("AUTH FAIL: username\n");
+ return false;
+ }
+ myUsername = avLine;
+ } else if(varName.equals("realm")){
+ if(realm != NULL && !avLine.equals(realm)){
+ //os_printf("AUTH FAIL: realm\n");
+ return false;
+ }
+ myRealm = avLine;
+ } else if(varName.equals("nonce")){
+ if(nonce != NULL && !avLine.equals(nonce)){
+ //os_printf("AUTH FAIL: nonce\n");
+ return false;
+ }
+ myNonce = avLine;
+ } else if(varName.equals("opaque")){
+ if(opaque != NULL && !avLine.equals(opaque)){
+ //os_printf("AUTH FAIL: opaque\n");
+ return false;
+ }
+ } else if(varName.equals("uri")){
+ if(uri != NULL && !avLine.equals(uri)){
+ //os_printf("AUTH FAIL: uri\n");
+ return false;
+ }
+ myUri = avLine;
+ } else if(varName.equals("response")){
+ myResponse = avLine;
+ } else if(varName.equals("qop")){
+ myQop = avLine;
+ } else if(varName.equals("nc")){
+ myNc = avLine;
+ } else if(varName.equals("cnonce")){
+ myCnonce = avLine;
+ }
+ } while(nextBreak > 0);
+
+ String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(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/components/AsyncWebServer/src/WebAuthentication.h b/components/AsyncWebServer/src/WebAuthentication.h
new file mode 100755
index 0000000..ff68265
--- /dev/null
+++ b/components/AsyncWebServer/src/WebAuthentication.h
@@ -0,0 +1,34 @@
+/*
+ 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);
+
+//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/components/AsyncWebServer/src/WebHandlerImpl.h b/components/AsyncWebServer/src/WebHandlerImpl.h
new file mode 100755
index 0000000..9b7ba1b
--- /dev/null
+++ b/components/AsyncWebServer/src/WebHandlerImpl.h
@@ -0,0 +1,151 @@
+/*
+ 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/components/AsyncWebServer/src/WebHandlers.cpp b/components/AsyncWebServer/src/WebHandlers.cpp
new file mode 100755
index 0000000..1f435e6
--- /dev/null
+++ b/components/AsyncWebServer/src/WebHandlers.cpp
@@ -0,0 +1,220 @@
+/*
+ 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("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr)
+{
+ // Ensure leading '/'
+ if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
+ if (_path.length() == 0 || _path[0] != '/') _path = "/" + _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 = String(last_modified);
+ return *this;
+}
+
+AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
+ char result[30];
+ strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", 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("If-Modified-Since");
+
+ if(_cache_control.length())
+ request->addInterestingHeader("If-None-Match");
+
+ DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n");
+ 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 += "/";
+ 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 + ".gz";
+
+ if (_gzipFirst) {
+ request->_tempFile = _fs.open(gzip, "r");
+ gzipFound = FILE_IS_REAL(request->_tempFile);
+ if (!gzipFound){
+ request->_tempFile = _fs.open(path, "r");
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ }
+ } else {
+ request->_tempFile = _fs.open(path, "r");
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ if (!fileFound){
+ request->_tempFile = _fs.open(gzip, "r");
+ 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(_tempPath, pathLen+1, "%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 != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+
+ if (request->_tempFile == true) {
+ String etag = String(request->_tempFile.size());
+ if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) {
+ request->_tempFile.close();
+ request->send(304); // Not modified
+ } else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
+ request->_tempFile.close();
+ AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
+ response->addHeader("Cache-Control", _cache_control);
+ response->addHeader("ETag", etag);
+ request->send(response);
+ } else {
+ AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
+ if (_last_modified.length())
+ response->addHeader("Last-Modified", _last_modified);
+ if (_cache_control.length()){
+ response->addHeader("Cache-Control", _cache_control);
+ response->addHeader("ETag", etag);
+ }
+ request->send(response);
+ }
+ } else {
+ request->send(404);
+ }
+}
diff --git a/components/AsyncWebServer/src/WebRequest.cpp b/components/AsyncWebServer/src/WebRequest.cpp
new file mode 100755
index 0000000..bbce5ca
--- /dev/null
+++ b/components/AsyncWebServer/src/WebRequest.cpp
@@ -0,0 +1,1008 @@
+/*
+ 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 "WebAuthentication.h"
+
+#ifndef ESP8266
+#define os_strlen strlen
+#endif
+
+static const String SharedEmptyString = String();
+
+#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)
+ , _headers(LinkedList([](AsyncWebHeader *h){ delete h; }))
+ , _params(LinkedList([](AsyncWebParameter *p){ delete p; }))
+ , _pathParams(LinkedList([](String *p){ delete p; }))
+ , _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.free();
+
+ _params.free();
+ _pathParams.free();
+
+ _interestingHeaders.free();
+
+ if(_response != NULL){
+ delete _response;
+ }
+
+ if(_tempObject != NULL){
+ free(_tempObject);
+ }
+
+ if(_tempFile){
+ _tempFile.close();
+ }
+}
+
+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; ihandleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
+ _parsedLength += len;
+ } else if(needParse) {
+ size_t i;
+ for(i=0; ihandleRequest(this);
+ else send(501);
+ }
+ }
+ break;
+ }
+}
+
+void AsyncWebServerRequest::_removeNotInterestingHeaders(){
+ if (_interestingHeaders.containsIgnoreCase("ANY")) return; // nothing to do
+ for(const auto& header: _headers){
+ if(!_interestingHeaders.containsIgnoreCase(header->name().c_str())){
+ _headers.remove(header);
+ }
+ }
+}
+
+void AsyncWebServerRequest::_onPoll(){
+ //os_printf("p\n");
+ if(_response != NULL && _client != NULL && _client->canSend() && !_response->_finished()){
+ _response->_ack(this, 0, 0);
+ }
+}
+
+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 {
+ AsyncWebServerResponse* r = _response;
+ _response = NULL;
+ delete r;
+ }
+ }
+}
+
+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::_addParam(AsyncWebParameter *p){
+ _params.add(p);
+}
+
+void AsyncWebServerRequest::_addPathParam(const char *p){
+ _pathParams.add(new String(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();
+ _addParam(new AsyncWebParameter(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 == "GET"){
+ _method = HTTP_GET;
+ } else if(m == "POST"){
+ _method = HTTP_POST;
+ } else if(m == "DELETE"){
+ _method = HTTP_DELETE;
+ } else if(m == "PUT"){
+ _method = HTTP_PUT;
+ } else if(m == "PATCH"){
+ _method = HTTP_PATCH;
+ } else if(m == "HEAD"){
+ _method = HTTP_HEAD;
+ } else if(m == "OPTIONS"){
+ _method = HTTP_OPTIONS;
+ }
+
+ String g = String();
+ index = u.indexOf('?');
+ if(index > 0){
+ g = u.substring(index +1);
+ u = u.substring(0, index);
+ }
+ _url = urlDecode(u);
+ _addGetParams(g);
+
+ if(!_temp.startsWith("HTTP/1.0"))
+ _version = 1;
+
+ _temp = String();
+ return true;
+}
+
+bool strContains(String src, 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("Content-Type")){
+ _contentType = value.substring(0, value.indexOf(';'));
+ if (value.startsWith("multipart/")){
+ _boundary = value.substring(value.indexOf('=')+1);
+ _boundary.replace("\"","");
+ _isMultipart = true;
+ }
+ } else if(name.equalsIgnoreCase("Content-Length")){
+ _contentLength = atoi(value.c_str());
+ } else if(name.equalsIgnoreCase("Expect") && value == "100-continue"){
+ _expectingContinue = true;
+ } else if(name.equalsIgnoreCase("Authorization")){
+ if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase("Basic")){
+ _authorization = value.substring(6);
+ } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase("Digest")){
+ _isDigest = true;
+ _authorization = value.substring(7);
+ }
+ } else {
+ if(name.equalsIgnoreCase("Upgrade") && value.equalsIgnoreCase("websocket")){
+ // WebSocket request can be uniquely identified by header: [Upgrade: websocket]
+ _reqconntype = RCT_WS;
+ } else {
+ if(name.equalsIgnoreCase("Accept") && strContains(value, "text/event-stream", false)){
+ // WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
+ _reqconntype = RCT_EVENT;
+ }
+ }
+ }
+ _headers.add(new AsyncWebHeader(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 = "body";
+ String value = _temp;
+ if(!_temp.startsWith("{") && !_temp.startsWith("[") && _temp.indexOf('=') > 0){
+ name = _temp.substring(0, _temp.indexOf('='));
+ value = _temp.substring(_temp.indexOf('=') + 1);
+ }
+ _addParam(new AsyncWebParameter(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("Content-Type")){
+ _itemType = _temp.substring(14);
+ _itemIsFile = true;
+ } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("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 == "name"){
+ _itemName = nameVal;
+ } else if(name == "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 == "name"){
+ _itemName = nameVal;
+ } else if(name == "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){
+ _addParam(new AsyncWebParameter(_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;
+ _addParam(new AsyncWebParameter(_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){
+ const char * response = "HTTP/1.1 100 Continue\r\n\r\n";
+ _client->write(response, os_strlen(response));
+ }
+ //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.length();
+}
+
+bool AsyncWebServerRequest::hasHeader(const String& name) const {
+ for(const auto& h: _headers){
+ if(h->name().equalsIgnoreCase(name)){
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
+ PGM_P p = reinterpret_cast(data);
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p+n) == 0) break;
+ n += 1;
+ }
+ char * name = (char*) malloc(n+1);
+ name[n] = 0;
+ if (name) {
+ for(size_t b=0; bname().equalsIgnoreCase(name)){
+ return h;
+ }
+ }
+ return nullptr;
+}
+
+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);
+ AsyncWebHeader* result = getHeader( String(name));
+ free(name);
+ return result;
+ } else {
+ return nullptr;
+ }
+}
+
+AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
+ auto header = _headers.nth(num);
+ return header ? *header : nullptr;
+}
+
+size_t AsyncWebServerRequest::params() const {
+ return _params.length();
+}
+
+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 {
+ PGM_P p = reinterpret_cast(data);
+ size_t n = strlen_P(p);
+
+ char * name = (char*) malloc(n+1);
+ name[n] = 0;
+ if (name) {
+ strcpy_P(name,p);
+ bool result = hasParam( name, post, file);
+ free(name);
+ return result;
+ } else {
+ return false;
+ }
+}
+
+AsyncWebParameter* AsyncWebServerRequest::getParam(const String& 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;
+}
+
+AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) 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);
+ AsyncWebParameter* result = getParam(name, post, file);
+ free(name);
+ return result;
+ } else {
+ return nullptr;
+ }
+}
+
+AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
+ auto param = _params.nth(num);
+ return param ? *param : nullptr;
+}
+
+void AsyncWebServerRequest::addInterestingHeader(const String& name){
+ if(!_interestingHeaders.containsIgnoreCase(name))
+ _interestingHeaders.add(name);
+}
+
+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);
+ }
+}
+
+AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){
+ return new AsyncBasicResponse(code, contentType, content);
+}
+
+AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
+ if(fs.exists(path) || (!download && fs.exists(path+".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);
+}
+
+AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(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_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
+ return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
+}
+
+void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
+ send(beginResponse(code, contentType, content));
+}
+
+void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
+ if(fs.exists(path) || (!download && fs.exists(path+".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::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
+ send(beginResponse_P(code, contentType, content, len, callback));
+}
+
+void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
+ send(beginResponse_P(code, contentType, content, callback));
+}
+
+void AsyncWebServerRequest::redirect(const String& url){
+ AsyncWebServerResponse * response = beginResponse(302);
+ response->addHeader("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("WWW-Authenticate", "Basic realm=\"Login Required\"");
+ } else if(!isDigest){
+ String header = "Basic realm=\"";
+ header.concat(realm);
+ header.concat("\"");
+ r->addHeader("WWW-Authenticate", header);
+ } else {
+ String header = "Digest ";
+ header.concat(requestDigestAuthentication(realm));
+ r->addHeader("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;
+}
+
+bool AsyncWebServerRequest::hasArg(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);
+ bool result = hasArg( name );
+ free(name);
+ return result;
+ } else {
+ return false;
+ }
+}
+
+
+const String& AsyncWebServerRequest::arg(const String& name) const {
+ for(const auto& arg: _params){
+ if(arg->name() == name){
+ return arg->value();
+ }
+ }
+ return SharedEmptyString;
+}
+
+const String& AsyncWebServerRequest::arg(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 String & result = arg( String(name) );
+ free(name);
+ return result;
+ } else {
+ return SharedEmptyString;
+ }
+
+}
+
+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 {
+ auto param = _pathParams.nth(i);
+ return param ? **param : SharedEmptyString;
+}
+
+const String& AsyncWebServerRequest::header(const char* name) const {
+ AsyncWebHeader* h = getHeader(String(name));
+ return h ? h->value() : SharedEmptyString;
+}
+
+const String& AsyncWebServerRequest::header(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 String & result = header( (const char *)name );
+ free(name);
+ return result;
+ } else {
+ return SharedEmptyString;
+ }
+};
+
+
+const String& AsyncWebServerRequest::header(size_t i) const {
+ AsyncWebHeader* h = getHeader(i);
+ return h ? h->value() : SharedEmptyString;
+}
+
+const String& AsyncWebServerRequest::headerName(size_t i) const {
+ AsyncWebHeader* h = getHeader(i);
+ return h ? h->name() : SharedEmptyString;
+}
+
+String AsyncWebServerRequest::urlDecode(const String& text) const {
+ char temp[] = "0x00";
+ unsigned int len = text.length();
+ unsigned int i = 0;
+ String decoded = String();
+ 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;
+}
+
+
+const char * AsyncWebServerRequest::methodToString() const {
+ if(_method == HTTP_ANY) return "ANY";
+ else if(_method & HTTP_GET) return "GET";
+ else if(_method & HTTP_POST) return "POST";
+ else if(_method & HTTP_DELETE) return "DELETE";
+ else if(_method & HTTP_PUT) return "PUT";
+ else if(_method & HTTP_PATCH) return "PATCH";
+ else if(_method & HTTP_HEAD) return "HEAD";
+ else 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";
+ }
+}
+
+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/components/AsyncWebServer/src/WebResponseImpl.h b/components/AsyncWebServer/src/WebResponseImpl.h
new file mode 100755
index 0000000..9a64e3a
--- /dev/null
+++ b/components/AsyncWebServer/src/WebResponseImpl.h
@@ -0,0 +1,136 @@
+/*
+ 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
+// 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:
+ cbuf *_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/components/AsyncWebServer/src/WebResponses.cpp b/components/AsyncWebServer/src/WebResponses.cpp
new file mode 100755
index 0000000..a22e991
--- /dev/null
+++ b/components/AsyncWebServer/src/WebResponses.cpp
@@ -0,0 +1,699 @@
+/*
+ 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) {
+ switch (code) {
+ case 100: return "Continue";
+ case 101: return "Switching Protocols";
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 300: return "Multiple Choices";
+ case 301: return "Moved Permanently";
+ case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 307: return "Temporary Redirect";
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Time-out";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Request Entity Too Large";
+ case 414: return "Request-URI Too Large";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Requested range not satisfiable";
+ case 417: return "Expectation Failed";
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Time-out";
+ case 505: return "HTTP Version not supported";
+ default: return "";
+ }
+}
+
+AsyncWebServerResponse::AsyncWebServerResponse()
+ : _code(0)
+ , _headers(LinkedList([](AsyncWebHeader *h){ delete h; }))
+ , _contentType()
+ , _contentLength(0)
+ , _sendContentLength(true)
+ , _chunked(false)
+ , _headLength(0)
+ , _sentLength(0)
+ , _ackedLength(0)
+ , _writtenLength(0)
+ , _state(RESPONSE_SETUP)
+{
+ for(auto header: DefaultHeaders::Instance()) {
+ _headers.add(new AsyncWebHeader(header->name(), header->value()));
+ }
+}
+
+AsyncWebServerResponse::~AsyncWebServerResponse(){
+ _headers.free();
+}
+
+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.add(new AsyncWebHeader(name, value));
+}
+
+String AsyncWebServerResponse::_assembleHead(uint8_t version){
+ if(version){
+ addHeader("Accept-Ranges","none");
+ if(_chunked)
+ addHeader("Transfer-Encoding","chunked");
+ }
+ String out = String();
+ int bufSize = 300;
+ char buf[bufSize];
+
+ snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code));
+ out.concat(buf);
+
+ if(_sendContentLength) {
+ snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength);
+ out.concat(buf);
+ }
+ if(_contentType.length()) {
+ snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str());
+ out.concat(buf);
+ }
+
+ for(const auto& header: _headers){
+ snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str());
+ out.concat(buf);
+ }
+ _headers.free();
+
+ out.concat("\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 = "text/plain";
+ }
+ addHeader("Connection","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("Connection","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((char*)buf+headLen, "%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(sizeof(buf) - 1, (unsigned int)(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 (path.endsWith(".html")) _contentType = "text/html";
+ else if (path.endsWith(".htm")) _contentType = "text/html";
+ else if (path.endsWith(".css")) _contentType = "text/css";
+ else if (path.endsWith(".json")) _contentType = "application/json";
+ else if (path.endsWith(".js")) _contentType = "application/javascript";
+ else if (path.endsWith(".png")) _contentType = "image/png";
+ else if (path.endsWith(".gif")) _contentType = "image/gif";
+ else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
+ else if (path.endsWith(".ico")) _contentType = "image/x-icon";
+ else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
+ else if (path.endsWith(".eot")) _contentType = "font/eot";
+ else if (path.endsWith(".woff")) _contentType = "font/woff";
+ else if (path.endsWith(".woff2")) _contentType = "font/woff2";
+ else if (path.endsWith(".ttf")) _contentType = "font/ttf";
+ else if (path.endsWith(".xml")) _contentType = "text/xml";
+ else if (path.endsWith(".pdf")) _contentType = "application/pdf";
+ else if (path.endsWith(".zip")) _contentType = "application/zip";
+ else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
+ else _contentType = "text/plain";
+}
+
+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+".gz")){
+ _path = _path+".gz";
+ addHeader("Content-Encoding", "gzip");
+ _callback = nullptr; // Unable to process zipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = fs.open(_path, "r");
+ _contentLength = _content.size();
+
+ if(contentType == "")
+ _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(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
+ } else {
+ // set filename and force rendering
+ snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
+ }
+ addHeader("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(".gz") && !path.endsWith(".gz")){
+ addHeader("Content-Encoding", "gzip");
+ _callback = nullptr; // Unable to process gzipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = content;
+ _contentLength = _content.size();
+
+ if(contentType == "")
+ _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(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
+ } else {
+ snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
+ }
+ addHeader("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;iread();
+ 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 = new cbuf(bufferSize);
+}
+
+AsyncResponseStream::~AsyncResponseStream(){
+ delete _content;
+}
+
+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/components/AsyncWebServer/src/WebServer.cpp b/components/AsyncWebServer/src/WebServer.cpp
new file mode 100755
index 0000000..95c2dd6
--- /dev/null
+++ b/components/AsyncWebServer/src/WebServer.cpp
@@ -0,0 +1,193 @@
+/*
+ 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();
+}
+
+
+AsyncWebServer::AsyncWebServer(uint16_t port)
+ : _server(port)
+ , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; }))
+ , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; }))
+{
+ _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(AsyncWebRewrite* rewrite){
+ _rewrites.add(rewrite);
+ return *rewrite;
+}
+
+bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
+ return _rewrites.remove(rewrite);
+}
+
+AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
+ return addRewrite(new AsyncWebRewrite(from, to));
+}
+
+AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
+ _handlers.add(handler);
+ return *handler;
+}
+
+bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
+ return _handlers.remove(handler);
+}
+
+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(const auto& h: _handlers){
+ if (h->filter(request) && h->canHandle(request)){
+ request->setHandler(h);
+ return;
+ }
+ }
+
+ request->addInterestingHeader("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.free();
+ _handlers.free();
+
+ if (_catchAllHandler != NULL){
+ _catchAllHandler->onRequest(NULL);
+ _catchAllHandler->onUpload(NULL);
+ _catchAllHandler->onBody(NULL);
+ }
+}
+
diff --git a/components/AsyncWebServer/src/edit.htm b/components/AsyncWebServer/src/edit.htm
new file mode 100755
index 0000000..43d4984
--- /dev/null
+++ b/components/AsyncWebServer/src/edit.htm
@@ -0,0 +1,627 @@
+
+
+
+
+ESP Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html/logo.png b/html/logo.png
new file mode 100755
index 0000000..ae87fb0
Binary files /dev/null and b/html/logo.png differ
diff --git a/html/provision.html b/html/provision.html
new file mode 100755
index 0000000..6e459f2
--- /dev/null
+++ b/html/provision.html
@@ -0,0 +1,401 @@
+
+
+
+
+
+
+
+Untitled Document
+
+
+
+
+
+
+
+
+
+Beat-a-meat WiFi configuration and stuff
+
+
+
+
+
Beat-a-meat has ben succesfully configured. It is now ready for normal use.
+
+
+
Beat-a-meat was not able to connect to selected WiFi. Please check the spelling and password.
+
+
+
Please wait, scanning local WiFi...
+
+
+
+
Please wait, verifying connection...
+
+
+
+
+
+
+
+ WiFi name
+ Security
+ Signal
+
+
+
+
+
Refresh
+
+
+
+
+
Your WiFi:
+
+
+
Password:
+
+
+
Connect
+
+
+
+
+
diff --git a/main/App.cpp b/main/App.cpp
index 0224959..6cfb99f 100644
--- a/main/App.cpp
+++ b/main/App.cpp
@@ -5,6 +5,8 @@
#include
#include "Settings.h"
+#include "ProvisionSoftAP.h"
+
#define LED_PIN 26
App::App()
@@ -19,7 +21,9 @@ void App::init()
m_wifi = new Wifi();
m_led->setPulse(0, 127, 0);
- m_wifi->connect();
+ ProvisionSoftAP p(80);
+ p.init("wellhub", "12345678");
+ //m_wifi->connect();
m_led->setColor(0, 0, 0);
}
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 7a1de5a..6b10802 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,6 +1,7 @@
idf_component_register(SRCS main.cpp App.cpp Settings.cpp Led.cpp TaskFactory.cpp Wifi.cpp utilities.cpp
+ ProvisionSoftAP.cpp
INCLUDE_DIRS "."
-# EMBED_TXTFILES ../private.txt ../public.txt
+ EMBED_TXTFILES ../html/logo.png ../html/provision.html
)
#message(FATAL_ERROR "error ${ROLE}")
diff --git a/main/ProvisionSoftAP.cpp b/main/ProvisionSoftAP.cpp
new file mode 100755
index 0000000..80ef96b
--- /dev/null
+++ b/main/ProvisionSoftAP.cpp
@@ -0,0 +1,305 @@
+/*
+ * ProvisionSoftAP.cpp
+ *
+ * Created on: Apr 17, 2019
+ * Author: miro
+ */
+#include
+#include "WiFi.h"
+#include "Arduino.h"
+// #include "ArduinoJson.h"
+#include
+#include
+// #include "Storage.h"
+#include "Settings.h"
+#include
+#include "freertos/event_groups.h"
+
+#include
+
+static const char *TAG = "soft_ap";
+
+using namespace std::placeholders;
+
+
+extern const uint8_t provision_html_start[] asm("_binary_provision_html_start");
+extern const uint8_t provision_html_end[] asm("_binary_provision_html_end");
+extern const uint8_t logo_png_start[] asm("_binary_logo_png_start");
+extern const uint8_t logo_png_end[] asm("_binary_logo_png_end");
+
+
+ProvisionSoftAP::ProvisionSoftAP(int port)
+{
+ m_webServer = new AsyncWebServer(port);
+ m_webSocket = new AsyncWebSocket("/provision");
+}
+
+void ProvisionSoftAP::wifiEvent(arduino_event_id_t event, arduino_event_info_t info)
+{
+ switch (event)
+ {
+ case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
+ ESP_LOGI(TAG, "station " MACSTR " joined, AID=%d", MAC2STR(info.wifi_ap_staconnected.mac), info.wifi_ap_staconnected.aid);
+ break;
+
+ case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
+ ESP_LOGI(TAG, "station " MACSTR " left", MAC2STR(info.wifi_ap_stadisconnected.mac));
+ m_webSocket->closeAll();
+ break;
+
+ default:
+
+ break;
+ }
+}
+
+void ProvisionSoftAP::websocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client,
+ AwsEventType type, void * arg, uint8_t *data, size_t len)
+{
+ ESP_LOGI(TAG, "type:%d", type);
+
+ if (type == WS_EVT_CONNECT)
+ {
+ ESP_LOGI(TAG, "Websocket client id:%d connection received", client->id());
+ m_clients.push_back(client->id());
+ }
+ else if (type == WS_EVT_DISCONNECT)
+ {
+ ESP_LOGI(TAG, "Client id:%d disconnected", client->id());
+ m_clients.remove(client->id());
+ }
+ else if (type == WS_EVT_DATA)
+ {
+ ESP_LOGI(TAG, "Data client %d received: ", client->id());
+ parseWebsocketCommand((char*) data, len);
+ }
+}
+
+void ProvisionSoftAP::handlePortal(AsyncWebServerRequest* request)
+{
+ ESP_LOGI("", "handle root %s", request->url().c_str());
+ AsyncWebServerResponse * response = request->beginResponse_P(200, "text/html", (PGM_P)provision_html_start);
+ request->send(response);
+}
+
+void ProvisionSoftAP::handleLogo(AsyncWebServerRequest* request)
+{
+ AsyncWebServerResponse * response = request->beginResponse_P(200, "image/png", logo_png_start, logo_png_end-logo_png_start);
+ response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response->addHeader("Pragma", "no-cache");
+ response->addHeader("Expires", "-1");
+ request->send(response);
+}
+
+/**
+ * Initializes WiFi as AP, configures web, dhcp and dns servers and sets up captive portal.
+ * When provisioning is done, credentials are saved and device is reset.
+ *
+ * ssid[in] ssid of the AP
+ * password[in] password for the AP
+ */
+void ProvisionSoftAP::init(const char * ssid, const char * password)
+{
+ // start AP
+
+ /* Soft AP network parameters */
+ IPAddress apIP(4, 4, 4, 4);
+ IPAddress netMsk(255, 255, 255, 0);
+ IPAddress firstIp(4, 4, 4, 100);
+ wifiEventId = WiFi.onEvent(std::bind(&ProvisionSoftAP::wifiEvent, this, _1, _2));
+
+ WiFi.mode(WIFI_MODE_AP);
+ WiFi.softAPConfig(apIP, apIP, netMsk);
+ WiFi.softAP(ssid, password);
+
+ delay(200);
+
+ // set dhcp address range
+ tcpip_adapter_ip_info_t info = { };
+ IP4_ADDR(&info.ip, 4, 4, 4, 4);
+ IP4_ADDR(&info.gw, 4, 4, 4, 4);
+ IP4_ADDR(&info.netmask, 255, 255, 255, 0);
+ tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP);
+
+ if (tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info) == ESP_OK)
+ {
+ dhcps_lease_t lease = { };
+ lease.enable = true;
+
+ IP4_ADDR(&lease.start_ip, 4, 4, 4, 10);
+ IP4_ADDR(&lease.end_ip, 4, 4, 4, 100);
+
+ tcpip_adapter_dhcps_option((tcpip_adapter_dhcp_option_mode_t) TCPIP_ADAPTER_OP_SET,
+ (tcpip_adapter_dhcp_option_id_t) TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, (void*) &lease, sizeof(dhcps_lease_t));
+
+ tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP);
+ }
+
+ // setup the dns
+ m_dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
+ m_dnsServer.setTTL(30);
+ m_dnsServer.start(53, "*", apIP);
+
+ // setup the http server
+ m_webSocket->onEvent(std::bind(&ProvisionSoftAP::websocketEvent, this, _1, _2, _3, _4, _5, _6));
+ m_webServer->addHandler(m_webSocket);
+ m_webServer->on("/", std::bind(&ProvisionSoftAP::handlePortal, this, _1));
+ m_webServer->on("/provision.html", std::bind(&ProvisionSoftAP::handlePortal, this, _1));
+ m_webServer->on("/generate_204", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); //Android captive portal. Maybe not needed. Might be handled by notFound handler.
+ m_webServer->on("/fwlink", std::bind(&ProvisionSoftAP::handlePortal, this, _1)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
+ m_webServer->on("/logo.png", std::bind(&ProvisionSoftAP::handleLogo, this, _1));
+ m_webServer->onNotFound(std::bind(&ProvisionSoftAP::handlePortal, this, _1));
+
+
+ m_webServer->begin();
+
+
+
+ while (true)
+ {
+ vTaskDelay(1);
+ m_dnsServer.processNextRequest();
+
+ // if (m_startWifiScan)
+ // {
+ // m_startWifiScan = false;
+ // ESP_LOGI(TAG, "Starting scan");
+ // int num_networks = WiFi.scanNetworks();
+
+ // ESP_LOGI(TAG, "Found %d networks", num_networks);
+
+ // // m_jsonBuffer.clear();
+ // // JsonObject& root = m_jsonBuffer.createObject();
+ // // root[TYPE] = WIFI_SCAN;
+ // // JsonArray& data = root.createNestedArray(DATA);
+ // // for (int n = 0; n < num_networks; n++)
+ // // {
+ // // JsonArray& data_inner = data.createNestedArray();
+ // // data_inner.add(WiFi.SSID(n));
+ // // data_inner.add(WiFi.encryptionType(n) == WIFI_AUTH_OPEN ? 0 : 1);
+ // // data_inner.add(WiFi.RSSI(n));
+ // // }
+ // // size_t len = root.printTo(m_buff, sizeof(m_buff));
+ // // m_webSocket->textAll(m_buff, len);
+ // // waitBufferEmpty();
+ // }
+ // if(m_tryConnect)
+ // {
+ // m_tryConnect = false;
+ // tryConnect(m_ssid, m_password);
+ // }
+ // if(m_tailgate)
+ // {
+ // m_dnsServer.stop();
+ // m_webSocket->closeAll(0, "bye");
+ // waitBufferEmpty();
+ // WiFi.removeEvent(wifiEventId);
+ // return;
+ // }
+ }
+
+ for (int i = 200; i >= 0; i--)
+ {
+ ESP_LOGI(TAG, "sleeping %d seconds...", i);
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+}
+
+void ProvisionSoftAP::parseWebsocketCommand(char* data, size_t len)
+{
+ data[len] = 0;
+ ESP_LOGI(TAG, "'%s'", data);
+ // m_jsonBuffer.clear();
+ // if(len<1024)
+ // data[len] = '\0';
+
+ // JsonObject& root = m_jsonBuffer.parseObject(data);
+
+ // const char * command = root[COMMAND];
+ // ESP_LOGI(TAG, "command: %s", command);
+
+ // if (command && strcmp(command, WIFI_SCAN) == 0)
+ // {
+ // m_startWifiScan = true;
+ // ESP_LOGI(TAG, "Start scan command received");
+ // }
+ // else if (command && strcmp(command, WIFI_CONFIG) == 0)
+ // {
+ // const char * ssid = root[SSID];
+ // const char * pwd = root[PWD];
+ // strcpy(m_ssid, ssid);
+ // strcpy(m_password, pwd);
+ // ESP_LOGI(TAG, "ssid/credentials received: '%s'/'%s'", m_ssid, m_password);
+ // m_tryConnect = true;
+ // }
+ // else if (command && strcmp(command, TAILGATE) == 0)
+ // {
+ // m_tailgate = true;
+ // ESP_LOGI(TAG, "tailgate command received");
+ // }
+}
+
+void ProvisionSoftAP::waitBufferEmpty()
+{
+// ESP_LOGI(TAG, "waitBufferEmpty start");
+// bool empty = false;
+// while (!empty)
+// {
+// empty = true;
+// for (const auto& c : m_clients)
+// {
+// if (m_webSocket->client(c) && !m_webSocket->client(c)->isQueueEmpty())
+// {
+// empty = false;
+// break;
+// }
+// }
+// vTaskDelay(10);
+// }
+// ESP_LOGI(TAG, "waitBufferEmpty end");
+}
+
+void ProvisionSoftAP::tryConnect(const char * ssid, const char * pwd)
+{
+ ESP_LOGI(TAG, "trying to connect to %s", ssid);
+
+ WiFi.begin(ssid, pwd);
+ int connRes = WiFi.waitForConnectResult();
+ if(connRes == WL_CONNECTED)
+ {
+ // all is gud!
+ ESP_LOGI(TAG, "we're connected, sending confirmation");
+
+ // m_settings.storeSsidPwd(ssid, pwd);
+
+ // m_jsonBuffer.clear();
+ // JsonObject& root = m_jsonBuffer.createObject();
+
+ // root[TYPE] = ERROR;
+ // root[CODE] = 0;
+
+ // size_t len = root.printTo(m_buff, sizeof(m_buff));
+ // m_webSocket->textAll(m_buff, len);
+ // waitBufferEmpty();
+ // vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ ESP_LOGI(TAG, "restarting...");
+ esp_restart();
+ }
+ // ESP_LOGE(TAG, "nuh-uh peppernip, error %d", connRes);
+ // m_jsonBuffer.clear();
+ // JsonObject& root = m_jsonBuffer.createObject();
+ // root[TYPE] = ERROR;
+ // root[CODE] = 1;
+ // size_t len = root.printTo(m_buff, sizeof(m_buff));
+ // m_webSocket->textAll(m_buff, len);
+ // waitBufferEmpty();
+
+ m_webSocket->closeAll();
+}
+
+ProvisionSoftAP::~ProvisionSoftAP()
+{
+ delete m_webServer;
+}
+
diff --git a/main/ProvisionSoftAP.h b/main/ProvisionSoftAP.h
new file mode 100755
index 0000000..a2083bf
--- /dev/null
+++ b/main/ProvisionSoftAP.h
@@ -0,0 +1,63 @@
+/*
+ * ProvisionSoftAP.h
+ *
+ * Created on: Apr 17, 2019
+ * Author: miro
+ */
+
+#ifndef MAIN_PROVISIONSOFTAP_H_
+#define MAIN_PROVISIONSOFTAP_H_
+
+#include
+#include
+#include
+// #include "ArduinoJson.h"
+#include "Settings.h"
+
+class ProvisionSoftAP
+{
+private:
+ DNSServer m_dnsServer;
+ AsyncWebServer * m_webServer;
+ AsyncWebSocket * m_webSocket;
+ wifi_event_id_t wifiEventId = 0;
+ std::list m_clients;
+ bool m_startWifiScan = false;
+ bool m_tryConnect = false;
+ bool m_tailgate = false;
+ // StaticJsonBuffer<1200> m_jsonBuffer;
+ char m_buff[1200];
+
+private:
+ // json stuff
+ const char* TYPE = "type";
+ const char* WIFI_SCAN = "wifi_scan";
+ const char* DATA = "data";
+ const char* COMMAND = "command";
+ const char* WIFI_CONFIG = "wifi_config";
+ const char* SSID = "ssid";
+ const char* PWD = "pwd";
+ const char* ERROR = "error";
+ const char* CODE = "code";
+ const char* TAILGATE = "tailgate";
+
+private:
+ void parseWebsocketCommand(char *data, size_t len);
+ void tryConnect(const char * ssid, const char * pwd);
+
+ // callbacks
+ void wifiEvent(arduino_event_id_t event, arduino_event_info_t info);
+ void websocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client,
+ AwsEventType type, void * arg, uint8_t *data, size_t len);
+ void handlePortal(AsyncWebServerRequest* request);
+ void handleLogo(AsyncWebServerRequest *request);
+ void waitBufferEmpty();
+
+public:
+ void init(const char * ssid, const char * password);
+
+ ProvisionSoftAP(int port = 80);
+ virtual ~ProvisionSoftAP();
+};
+
+#endif /* MAIN_PROVISIONSOFTAP_H_ */
diff --git a/main/Settings.cpp b/main/Settings.cpp
index 8be8539..0ca1ae5 100644
--- a/main/Settings.cpp
+++ b/main/Settings.cpp
@@ -86,9 +86,9 @@ void Settings::setDefaults()
m_data.led.brightness = 25; // 25 %
strcpy(m_data.wifi.entry[0].ssid, "gumball");
- strcpy(m_data.wifi.entry[0].pwd, "mikelemembee");
+ strcpy(m_data.wifi.entry[0].pwd, "mikelemembe");
strcpy(m_data.wifi.entry[1].ssid, "miro");
- strcpy(m_data.wifi.entry[1].pwd, "mikelemembee");
+ strcpy(m_data.wifi.entry[1].pwd, "mikelemembe");
m_data.wifi.num = 2;
m_data.wifi.selected = 0xff;
diff --git a/main/Wifi.cpp b/main/Wifi.cpp
index 2980883..0e6f5f7 100644
--- a/main/Wifi.cpp
+++ b/main/Wifi.cpp
@@ -82,6 +82,8 @@ uint32_t Wifi::connectTo(int index)
return WH_ERR_WIFI_NOT_CONNECTED;
}
+/// @brief Connects to wifi. Returns immediately if already connected.
+/// @return WH_OK | WH_ERR_WIFI_NOT_PROVISIONED
uint32_t Wifi::connect()
{
if(SETTINGS.wifi.num == 0)
@@ -93,10 +95,9 @@ uint32_t Wifi::connect()
if(WiFi.isConnected())
return WH_OK;
+ // if only one network is provisioned
if(SETTINGS.wifi.num == 1)
{
- // only one wifi set up
- //ssid_ix = 0;
SETTINGS.wifi.selected = 0;
for(int n = 0; n < 3; n++)
{
@@ -110,22 +111,27 @@ uint32_t Wifi::connect()
return WH_ERR_WIFI_NOT_PROVISIONED;
}
-
+ // multiple networks are provisioned. Use either specific network or scan for highest rssi
+
memset(m_ignored, 0, SETTINGS_NUM_WIFI_ENTRIES);
int ssid_ix = SETTINGS.wifi.selected;
- if(SETTINGS.wifi.always_scan_before_connect || SETTINGS.wifi.selected >= SETTINGS_NUM_WIFI_ENTRIES)
+ if(SETTINGS.wifi.always_scan_before_connect || SETTINGS.wifi.selected >= SETTINGS.wifi.num)
ssid_ix = scan();
if(ssid_ix < 0)
return WH_ERR_WIFI_NOT_PROVISIONED;
if(connectTo(ssid_ix) == WH_OK)
+ {
+ SETTINGS.wifi.selected = ssid_ix;
return WH_OK;
+ }
+
+ // didn't work. go through them all giving priority to one with highest rssi
m_ignored[ssid_ix] = IGNORE_SSID_MINS;
- // try to connect to highest rssi
do
{
ssid_ix = scan();
@@ -141,7 +147,7 @@ uint32_t Wifi::connect()
} while(true);
- // tried all provisioned ssid's, try round robin few times
+ // that failed too, try them all again round-robin
for(int m = 0; m < 3; m++)
{
@@ -187,7 +193,7 @@ int Wifi::scan()
}
if(selected_index >= 0)
- ESP_LOGI(TAG, "Chosen network: '%s' based on RSSI", SETTINGS.wifi.entry[selected_index].ssid);
+ ESP_LOGI(TAG, "Chosen network '%s'", SETTINGS.wifi.entry[selected_index].ssid);
else
ESP_LOGI(TAG, "No suitable networks found at this time");
diff --git a/main/Wifi.h b/main/Wifi.h
index ba3e9a6..d2cf5a0 100644
--- a/main/Wifi.h
+++ b/main/Wifi.h
@@ -18,6 +18,7 @@ protected:
// void wifiTask();
uint32_t connectTo(int index);
+ int scan();
public:
Wifi();
@@ -27,7 +28,6 @@ protected:
public:
uint32_t connect();
- int scan();
};
#endif
\ No newline at end of file
diff --git a/sdkconfig b/sdkconfig
index 9f5828c..1df83e1 100644
--- a/sdkconfig
+++ b/sdkconfig
@@ -190,6 +190,16 @@ CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
+#
+# AsyncTCP Configuration
+#
+# CONFIG_ASYNC_TCP_RUN_CORE0 is not set
+CONFIG_ASYNC_TCP_RUN_CORE1=y
+# CONFIG_ASYNC_TCP_RUN_NO_AFFINITY is not set
+CONFIG_ASYNC_TCP_RUNNING_CORE=1
+CONFIG_ASYNC_TCP_USE_WDT=y
+# end of AsyncTCP Configuration
+
#
# Compiler options
#