├── .github └── workflows │ └── arduino-lint-action.yml ├── LICENSE ├── README.md ├── examples ├── CaptivePortal │ └── CaptivePortal.ino ├── ESP_AsyncFSBrowser │ ├── ESP_AsyncFSBrowser.ino │ └── data │ │ ├── .exclude.files │ │ ├── ace.js.gz │ │ ├── ext-searchbox.js.gz │ │ ├── favicon.ico │ │ ├── index.htm │ │ ├── mode-css.js.gz │ │ ├── mode-html.js.gz │ │ ├── mode-javascript.js.gz │ │ └── worker-html.js.gz ├── regex_patterns │ ├── .test.build_flags │ └── regex_patterns.ino └── simple_server │ └── simple_server.ino ├── keywords.txt ├── library.properties └── src ├── AsyncEventSource.cpp ├── AsyncEventSource.h ├── AsyncJson.h ├── AsyncWebSocket.cpp ├── AsyncWebSocket.h ├── AsyncWebSynchronization.h ├── ESPAsyncWebSrv.h ├── SPIFFSEditor.cpp ├── SPIFFSEditor.h ├── StringArray.h ├── WebAuthentication.cpp ├── WebAuthentication.h ├── WebHandlerImpl.h ├── WebHandlers.cpp ├── WebRequest.cpp ├── WebResponseImpl.h ├── WebResponses.cpp ├── WebServer.cpp └── edit.htm /.github/workflows/arduino-lint-action.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | lint: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v3 7 | - uses: arduino/arduino-lint-action@v1 8 | with: 9 | library-manager: update 10 | compliance: strict 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESPAsyncWebServer 2 | 3 | Async HTTP and WebSocket Server for ESP8266 Arduino 4 | 5 | For ESP8266 it requires [ESPAsyncTCP](https://github.com/dvarrel/ESPAsyncTCP) 6 | To use this library you might need to have the latest git versions of [ESP8266](https://github.com/esp8266/Arduino) Arduino Core 7 | 8 | For ESP32 it requires [AsyncTCP](https://github.com/dvarrel/AsyncTCP) to work 9 | To use this library you might need to have the latest git versions of [ESP32](https://github.com/espressif/arduino-esp32) Arduino Core 10 | 11 | -------------------------------------------------------------------------------- /examples/CaptivePortal/CaptivePortal.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #elif defined(ESP8266) 6 | #include 7 | #include 8 | #endif 9 | #include "ESPAsyncWebSrv.h" 10 | 11 | DNSServer dnsServer; 12 | AsyncWebServer server(80); 13 | 14 | class CaptiveRequestHandler : public AsyncWebHandler { 15 | public: 16 | CaptiveRequestHandler() {} 17 | virtual ~CaptiveRequestHandler() {} 18 | 19 | bool canHandle(AsyncWebServerRequest *request){ 20 | //request->addInterestingHeader("ANY"); 21 | return true; 22 | } 23 | 24 | void handleRequest(AsyncWebServerRequest *request) { 25 | AsyncResponseStream *response = request->beginResponseStream("text/html"); 26 | response->print("Captive Portal"); 27 | response->print("

This is out captive portal front page.

"); 28 | response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); 29 | response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); 30 | response->print(""); 31 | request->send(response); 32 | } 33 | }; 34 | 35 | 36 | void setup(){ 37 | //your other setup stuff... 38 | WiFi.softAP("esp-captive"); 39 | dnsServer.start(53, "*", WiFi.softAPIP()); 40 | server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP 41 | //more handlers... 42 | server.begin(); 43 | } 44 | 45 | void loop(){ 46 | dnsServer.processNextRequest(); 47 | } 48 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #elif defined(ESP8266) 9 | #include 10 | #include 11 | #include 12 | #endif 13 | #include 14 | #include 15 | 16 | // SKETCH BEGIN 17 | AsyncWebServer server(80); 18 | AsyncWebSocket ws("/ws"); 19 | AsyncEventSource events("/events"); 20 | 21 | void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ 22 | if(type == WS_EVT_CONNECT){ 23 | Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); 24 | client->printf("Hello Client %u :)", client->id()); 25 | client->ping(); 26 | } else if(type == WS_EVT_DISCONNECT){ 27 | Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id()); 28 | } else if(type == WS_EVT_ERROR){ 29 | Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); 30 | } else if(type == WS_EVT_PONG){ 31 | Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); 32 | } else if(type == WS_EVT_DATA){ 33 | AwsFrameInfo * info = (AwsFrameInfo*)arg; 34 | String msg = ""; 35 | if(info->final && info->index == 0 && info->len == len){ 36 | //the whole message is in a single frame and we got all of it's data 37 | Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); 38 | 39 | if(info->opcode == WS_TEXT){ 40 | for(size_t i=0; i < info->len; i++) { 41 | msg += (char) data[i]; 42 | } 43 | } else { 44 | char buff[3]; 45 | for(size_t i=0; i < info->len; i++) { 46 | sprintf(buff, "%02x ", (uint8_t) data[i]); 47 | msg += buff ; 48 | } 49 | } 50 | Serial.printf("%s\n",msg.c_str()); 51 | 52 | if(info->opcode == WS_TEXT) 53 | client->text("I got your text message"); 54 | else 55 | client->binary("I got your binary message"); 56 | } else { 57 | //message is comprised of multiple frames or the frame is split into multiple packets 58 | if(info->index == 0){ 59 | if(info->num == 0) 60 | Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); 61 | Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); 62 | } 63 | 64 | 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); 65 | 66 | if(info->opcode == WS_TEXT){ 67 | for(size_t i=0; i < len; i++) { 68 | msg += (char) data[i]; 69 | } 70 | } else { 71 | char buff[3]; 72 | for(size_t i=0; i < len; i++) { 73 | sprintf(buff, "%02x ", (uint8_t) data[i]); 74 | msg += buff ; 75 | } 76 | } 77 | Serial.printf("%s\n",msg.c_str()); 78 | 79 | if((info->index + len) == info->len){ 80 | Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); 81 | if(info->final){ 82 | Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); 83 | if(info->message_opcode == WS_TEXT) 84 | client->text("I got your text message"); 85 | else 86 | client->binary("I got your binary message"); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | const char* ssid = "*******"; 95 | const char* password = "*******"; 96 | const char * hostName = "esp-async"; 97 | const char* http_username = "admin"; 98 | const char* http_password = "admin"; 99 | 100 | void setup(){ 101 | Serial.begin(115200); 102 | Serial.setDebugOutput(true); 103 | WiFi.mode(WIFI_AP_STA); 104 | WiFi.softAP(hostName); 105 | WiFi.begin(ssid, password); 106 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 107 | Serial.printf("STA: Failed!\n"); 108 | WiFi.disconnect(false); 109 | delay(1000); 110 | WiFi.begin(ssid, password); 111 | } 112 | 113 | //Send OTA events to the browser 114 | ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); 115 | ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); }); 116 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 117 | char p[32]; 118 | sprintf(p, "Progress: %u%%\n", (progress/(total/100))); 119 | events.send(p, "ota"); 120 | }); 121 | ArduinoOTA.onError([](ota_error_t error) { 122 | if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota"); 123 | else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota"); 124 | else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota"); 125 | else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota"); 126 | else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); 127 | }); 128 | ArduinoOTA.setHostname(hostName); 129 | ArduinoOTA.begin(); 130 | 131 | MDNS.addService("http","tcp",80); 132 | 133 | SPIFFS.begin(); 134 | 135 | ws.onEvent(onWsEvent); 136 | server.addHandler(&ws); 137 | 138 | events.onConnect([](AsyncEventSourceClient *client){ 139 | client->send("hello!",NULL,millis(),1000); 140 | }); 141 | server.addHandler(&events); 142 | 143 | #ifdef ESP32 144 | server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password)); 145 | #elif defined(ESP8266) 146 | server.addHandler(new SPIFFSEditor(http_username,http_password)); 147 | #endif 148 | 149 | server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ 150 | request->send(200, "text/plain", String(ESP.getFreeHeap())); 151 | }); 152 | 153 | server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm"); 154 | 155 | server.onNotFound([](AsyncWebServerRequest *request){ 156 | Serial.printf("NOT_FOUND: "); 157 | if(request->method() == HTTP_GET) 158 | Serial.printf("GET"); 159 | else if(request->method() == HTTP_POST) 160 | Serial.printf("POST"); 161 | else if(request->method() == HTTP_DELETE) 162 | Serial.printf("DELETE"); 163 | else if(request->method() == HTTP_PUT) 164 | Serial.printf("PUT"); 165 | else if(request->method() == HTTP_PATCH) 166 | Serial.printf("PATCH"); 167 | else if(request->method() == HTTP_HEAD) 168 | Serial.printf("HEAD"); 169 | else if(request->method() == HTTP_OPTIONS) 170 | Serial.printf("OPTIONS"); 171 | else 172 | Serial.printf("UNKNOWN"); 173 | Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); 174 | 175 | if(request->contentLength()){ 176 | Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str()); 177 | Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength()); 178 | } 179 | 180 | int headers = request->headers(); 181 | int i; 182 | for(i=0;igetHeader(i); 184 | Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); 185 | } 186 | 187 | int params = request->params(); 188 | for(i=0;igetParam(i); 190 | if(p->isFile()){ 191 | Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); 192 | } else if(p->isPost()){ 193 | Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); 194 | } else { 195 | Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); 196 | } 197 | } 198 | 199 | request->send(404); 200 | }); 201 | server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ 202 | if(!index) 203 | Serial.printf("UploadStart: %s\n", filename.c_str()); 204 | Serial.printf("%s", (const char*)data); 205 | if(final) 206 | Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); 207 | }); 208 | server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ 209 | if(!index) 210 | Serial.printf("BodyStart: %u\n", total); 211 | Serial.printf("%s", (const char*)data); 212 | if(index + len == total) 213 | Serial.printf("BodyEnd: %u\n", total); 214 | }); 215 | server.begin(); 216 | } 217 | 218 | void loop(){ 219 | ArduinoOTA.handle(); 220 | ws.cleanupClients(); 221 | } 222 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/.exclude.files: -------------------------------------------------------------------------------- 1 | /*.js.gz 2 | /.exclude.files 3 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/ace.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/ace.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/ext-searchbox.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/favicon.ico -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/index.htm: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | WebSocketTester 23 | 52 | 124 | 125 | 126 |

127 |     
128 | $ 129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/mode-css.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/mode-css.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/mode-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/mode-html.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/mode-javascript.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/worker-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvarrel/ESPAsyncWebSrv/87424b3afe9a7df7828b5eb698efd68b6763705d/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz -------------------------------------------------------------------------------- /examples/regex_patterns/.test.build_flags: -------------------------------------------------------------------------------- 1 | -DASYNCWEBSERVER_REGEX=1 2 | -------------------------------------------------------------------------------- /examples/regex_patterns/regex_patterns.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation with regex routes: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | // Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support 9 | 10 | // For platformio: platformio.ini: 11 | // build_flags = 12 | // -DASYNCWEBSERVER_REGEX 13 | 14 | // For arduino IDE: create/update platform.local.txt 15 | // Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt 16 | // Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt 17 | // 18 | // compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1 19 | 20 | #include 21 | #ifdef ESP32 22 | #include 23 | #include 24 | #elif defined(ESP8266) 25 | #include 26 | #include 27 | #endif 28 | #include 29 | 30 | AsyncWebServer server(80); 31 | 32 | const char* ssid = "YOUR_SSID"; 33 | const char* password = "YOUR_PASSWORD"; 34 | 35 | const char* PARAM_MESSAGE = "message"; 36 | 37 | void notFound(AsyncWebServerRequest *request) { 38 | request->send(404, "text/plain", "Not found"); 39 | } 40 | 41 | void setup() { 42 | 43 | Serial.begin(115200); 44 | WiFi.mode(WIFI_STA); 45 | WiFi.begin(ssid, password); 46 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 47 | Serial.printf("WiFi Failed!\n"); 48 | return; 49 | } 50 | 51 | Serial.print("IP Address: "); 52 | Serial.println(WiFi.localIP()); 53 | 54 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 55 | request->send(200, "text/plain", "Hello, world"); 56 | }); 57 | 58 | // Send a GET request to /sensor/ 59 | server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 60 | String sensorNumber = request->pathArg(0); 61 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); 62 | }); 63 | 64 | // Send a GET request to /sensor//action/ 65 | server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 66 | String sensorNumber = request->pathArg(0); 67 | String action = request->pathArg(1); 68 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); 69 | }); 70 | 71 | server.onNotFound(notFound); 72 | 73 | server.begin(); 74 | } 75 | 76 | void loop() { 77 | } 78 | -------------------------------------------------------------------------------- /examples/simple_server/simple_server.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation showing how to: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | #include 9 | #ifdef ESP32 10 | #include 11 | #include 12 | #elif defined(ESP8266) 13 | #include 14 | #include 15 | #endif 16 | #include 17 | 18 | AsyncWebServer server(80); 19 | 20 | const char* ssid = "YOUR_SSID"; 21 | const char* password = "YOUR_PASSWORD"; 22 | 23 | const char* PARAM_MESSAGE = "message"; 24 | 25 | void notFound(AsyncWebServerRequest *request) { 26 | request->send(404, "text/plain", "Not found"); 27 | } 28 | 29 | void setup() { 30 | 31 | Serial.begin(115200); 32 | WiFi.mode(WIFI_STA); 33 | WiFi.begin(ssid, password); 34 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 35 | Serial.printf("WiFi Failed!\n"); 36 | return; 37 | } 38 | 39 | Serial.print("IP Address: "); 40 | Serial.println(WiFi.localIP()); 41 | 42 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 43 | request->send(200, "text/plain", "Hello, world"); 44 | }); 45 | 46 | // Send a GET request to /get?message= 47 | server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { 48 | String message; 49 | if (request->hasParam(PARAM_MESSAGE)) { 50 | message = request->getParam(PARAM_MESSAGE)->value(); 51 | } else { 52 | message = "No message sent"; 53 | } 54 | request->send(200, "text/plain", "Hello, GET: " + message); 55 | }); 56 | 57 | // Send a POST request to /post with a form field message set to 58 | server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ 59 | String message; 60 | if (request->hasParam(PARAM_MESSAGE, true)) { 61 | message = request->getParam(PARAM_MESSAGE, true)->value(); 62 | } else { 63 | message = "No message sent"; 64 | } 65 | request->send(200, "text/plain", "Hello, POST: " + message); 66 | }); 67 | 68 | server.onNotFound(notFound); 69 | 70 | server.begin(); 71 | } 72 | 73 | void loop() { 74 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | JsonArray KEYWORD1 2 | add KEYWORD2 3 | createArray KEYWORD3 4 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESPAsyncWebSrv 2 | version=1.2.9 3 | author=dvarrel 4 | maintainer=dvarrel 5 | sentence=Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32 . Forked from https://github.com/me-no-dev/ESPAsyncWebServer 6 | paragraph=Build a WebServer, with files saved in flash 7 | category=Communication 8 | url=https://github.com/dvarrel/ESPAsyncWebSrv 9 | architectures=esp8266, esp32 10 | depends=AsyncTCP, ESPAsyncTCP -------------------------------------------------------------------------------- /src/AsyncEventSource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #include "Arduino.h" 21 | #include "AsyncEventSource.h" 22 | 23 | static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 24 | String ev = ""; 25 | 26 | if(reconnect){ 27 | ev += "retry: "; 28 | ev += String(reconnect); 29 | ev += "\r\n"; 30 | } 31 | 32 | if(id){ 33 | ev += "id: "; 34 | ev += String(id); 35 | ev += "\r\n"; 36 | } 37 | 38 | if(event != NULL){ 39 | ev += "event: "; 40 | ev += String(event); 41 | ev += "\r\n"; 42 | } 43 | 44 | if(message != NULL){ 45 | size_t messageLen = strlen(message); 46 | char * lineStart = (char *)message; 47 | char * lineEnd; 48 | do { 49 | char * nextN = strchr(lineStart, '\n'); 50 | char * nextR = strchr(lineStart, '\r'); 51 | if(nextN == NULL && nextR == NULL){ 52 | size_t llen = ((char *)message + messageLen) - lineStart; 53 | char * ldata = (char *)malloc(llen+1); 54 | if(ldata != NULL){ 55 | memcpy(ldata, lineStart, llen); 56 | ldata[llen] = 0; 57 | ev += "data: "; 58 | ev += ldata; 59 | ev += "\r\n\r\n"; 60 | free(ldata); 61 | } 62 | lineStart = (char *)message + messageLen; 63 | } else { 64 | char * nextLine = NULL; 65 | if(nextN != NULL && nextR != NULL){ 66 | if(nextR < nextN){ 67 | lineEnd = nextR; 68 | if(nextN == (nextR + 1)) 69 | nextLine = nextN + 1; 70 | else 71 | nextLine = nextR + 1; 72 | } else { 73 | lineEnd = nextN; 74 | if(nextR == (nextN + 1)) 75 | nextLine = nextR + 1; 76 | else 77 | nextLine = nextN + 1; 78 | } 79 | } else if(nextN != NULL){ 80 | lineEnd = nextN; 81 | nextLine = nextN + 1; 82 | } else { 83 | lineEnd = nextR; 84 | nextLine = nextR + 1; 85 | } 86 | 87 | size_t llen = lineEnd - lineStart; 88 | char * ldata = (char *)malloc(llen+1); 89 | if(ldata != NULL){ 90 | memcpy(ldata, lineStart, llen); 91 | ldata[llen] = 0; 92 | ev += "data: "; 93 | ev += ldata; 94 | ev += "\r\n"; 95 | free(ldata); 96 | } 97 | lineStart = nextLine; 98 | if(lineStart == ((char *)message + messageLen)) 99 | ev += "\r\n"; 100 | } 101 | } while(lineStart < ((char *)message + messageLen)); 102 | } 103 | 104 | return ev; 105 | } 106 | 107 | // Message 108 | 109 | AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) 110 | : _data(nullptr), _len(len), _sent(0), _acked(0) 111 | { 112 | _data = (uint8_t*)malloc(_len+1); 113 | if(_data == nullptr){ 114 | _len = 0; 115 | } else { 116 | memcpy(_data, data, len); 117 | _data[_len] = 0; 118 | } 119 | } 120 | 121 | AsyncEventSourceMessage::~AsyncEventSourceMessage() { 122 | if(_data != NULL) 123 | free(_data); 124 | } 125 | 126 | size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { 127 | (void)time; 128 | // If the whole message is now acked... 129 | if(_acked + len > _len){ 130 | // Return the number of extra bytes acked (they will be carried on to the next message) 131 | const size_t extra = _acked + len - _len; 132 | _acked = _len; 133 | return extra; 134 | } 135 | // Return that no extra bytes left. 136 | _acked += len; 137 | return 0; 138 | } 139 | 140 | size_t AsyncEventSourceMessage::send(AsyncClient *client) { 141 | const size_t len = _len - _sent; 142 | if(client->space() < len){ 143 | return 0; 144 | } 145 | size_t sent = client->add((const char *)_data, len); 146 | if(client->canSend()) 147 | client->send(); 148 | _sent += sent; 149 | return sent; 150 | } 151 | 152 | // Client 153 | 154 | AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) 155 | : _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; })) 156 | { 157 | _client = request->client(); 158 | _server = server; 159 | _lastId = 0; 160 | if(request->hasHeader("Last-Event-ID")) 161 | _lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str()); 162 | 163 | _client->setRxTimeout(0); 164 | _client->onError(NULL, NULL); 165 | _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); 166 | _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); 167 | _client->onData(NULL, NULL); 168 | _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); 169 | _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); 170 | 171 | _server->_addClient(this); 172 | delete request; 173 | } 174 | 175 | AsyncEventSourceClient::~AsyncEventSourceClient(){ 176 | _messageQueue.free(); 177 | close(); 178 | } 179 | 180 | void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ 181 | if(dataMessage == NULL) 182 | return; 183 | if(!connected()){ 184 | delete dataMessage; 185 | return; 186 | } 187 | if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ 188 | //ets_printf("ERROR: Too many messages queued\n"); 189 | delete dataMessage; 190 | } else { 191 | _messageQueue.add(dataMessage); 192 | } 193 | if(_client->canSend()) 194 | _runQueue(); 195 | } 196 | 197 | void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ 198 | while(len && !_messageQueue.isEmpty()){ 199 | len = _messageQueue.front()->ack(len, time); 200 | if(_messageQueue.front()->finished()) 201 | _messageQueue.remove(_messageQueue.front()); 202 | } 203 | 204 | _runQueue(); 205 | } 206 | 207 | void AsyncEventSourceClient::_onPoll(){ 208 | if(!_messageQueue.isEmpty()){ 209 | _runQueue(); 210 | } 211 | } 212 | 213 | 214 | void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ 215 | _client->close(true); 216 | } 217 | 218 | void AsyncEventSourceClient::_onDisconnect(){ 219 | _client = NULL; 220 | _server->_handleDisconnect(this); 221 | } 222 | 223 | void AsyncEventSourceClient::close(){ 224 | if(_client != NULL) 225 | _client->close(); 226 | } 227 | 228 | void AsyncEventSourceClient::write(const char * message, size_t len){ 229 | _queueMessage(new AsyncEventSourceMessage(message, len)); 230 | } 231 | 232 | void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 233 | String ev = generateEventMessage(message, event, id, reconnect); 234 | _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); 235 | } 236 | 237 | void AsyncEventSourceClient::_runQueue(){ 238 | while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ 239 | _messageQueue.remove(_messageQueue.front()); 240 | } 241 | 242 | for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) 243 | { 244 | if(!(*i)->sent()) 245 | (*i)->send(_client); 246 | } 247 | } 248 | 249 | 250 | // Handler 251 | 252 | AsyncEventSource::AsyncEventSource(const String& url) 253 | : _url(url) 254 | , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; })) 255 | , _connectcb(NULL) 256 | {} 257 | 258 | AsyncEventSource::~AsyncEventSource(){ 259 | close(); 260 | } 261 | 262 | void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ 263 | _connectcb = cb; 264 | } 265 | 266 | void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ 267 | /*char * temp = (char *)malloc(2054); 268 | if(temp != NULL){ 269 | memset(temp+1,' ',2048); 270 | temp[0] = ':'; 271 | temp[2049] = '\r'; 272 | temp[2050] = '\n'; 273 | temp[2051] = '\r'; 274 | temp[2052] = '\n'; 275 | temp[2053] = 0; 276 | client->write((const char *)temp, 2053); 277 | free(temp); 278 | }*/ 279 | 280 | _clients.add(client); 281 | if(_connectcb) 282 | _connectcb(client); 283 | } 284 | 285 | void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ 286 | _clients.remove(client); 287 | } 288 | 289 | void AsyncEventSource::close(){ 290 | for(const auto &c: _clients){ 291 | if(c->connected()) 292 | c->close(); 293 | } 294 | } 295 | 296 | // pmb fix 297 | size_t AsyncEventSource::avgPacketsWaiting() const { 298 | if(_clients.isEmpty()) 299 | return 0; 300 | 301 | size_t aql=0; 302 | uint32_t nConnectedClients=0; 303 | 304 | for(const auto &c: _clients){ 305 | if(c->connected()) { 306 | aql+=c->packetsWaiting(); 307 | ++nConnectedClients; 308 | } 309 | } 310 | // return aql / nConnectedClients; 311 | return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up 312 | } 313 | 314 | void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ 315 | 316 | 317 | String ev = generateEventMessage(message, event, id, reconnect); 318 | for(const auto &c: _clients){ 319 | if(c->connected()) { 320 | c->write(ev.c_str(), ev.length()); 321 | } 322 | } 323 | } 324 | 325 | size_t AsyncEventSource::count() const { 326 | return _clients.count_if([](AsyncEventSourceClient *c){ 327 | return c->connected(); 328 | }); 329 | } 330 | 331 | bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ 332 | if(request->method() != HTTP_GET || !request->url().equals(_url)) { 333 | return false; 334 | } 335 | request->addInterestingHeader("Last-Event-ID"); 336 | return true; 337 | } 338 | 339 | void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ 340 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 341 | return request->requestAuthentication(); 342 | request->send(new AsyncEventSourceResponse(this)); 343 | } 344 | 345 | // Response 346 | 347 | AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ 348 | _server = server; 349 | _code = 200; 350 | _contentType = "text/event-stream"; 351 | _sendContentLength = false; 352 | addHeader("Cache-Control", "no-cache"); 353 | addHeader("Connection","keep-alive"); 354 | } 355 | 356 | void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ 357 | String out = _assembleHead(request->version()); 358 | request->client()->write(out.c_str(), _headLength); 359 | _state = RESPONSE_WAIT_ACK; 360 | } 361 | 362 | size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ 363 | if(len){ 364 | new AsyncEventSourceClient(request, _server); 365 | } 366 | return 0; 367 | } 368 | 369 | -------------------------------------------------------------------------------- /src/AsyncEventSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #ifndef ASYNCEVENTSOURCE_H_ 21 | #define ASYNCEVENTSOURCE_H_ 22 | 23 | #include 24 | #ifdef ESP32 25 | #include 26 | #define SSE_MAX_QUEUED_MESSAGES 32 27 | #else 28 | #include 29 | #define SSE_MAX_QUEUED_MESSAGES 8 30 | #endif 31 | #include 32 | 33 | #include "AsyncWebSynchronization.h" 34 | 35 | #ifdef ESP8266 36 | #include 37 | #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library 38 | #include <../src/Hash.h> 39 | #endif 40 | #endif 41 | 42 | #ifdef ESP32 43 | #define DEFAULT_MAX_SSE_CLIENTS 8 44 | #else 45 | #define DEFAULT_MAX_SSE_CLIENTS 4 46 | #endif 47 | 48 | class AsyncEventSource; 49 | class AsyncEventSourceResponse; 50 | class AsyncEventSourceClient; 51 | typedef std::function ArEventHandlerFunction; 52 | 53 | class AsyncEventSourceMessage { 54 | private: 55 | uint8_t * _data; 56 | size_t _len; 57 | size_t _sent; 58 | //size_t _ack; 59 | size_t _acked; 60 | public: 61 | AsyncEventSourceMessage(const char * data, size_t len); 62 | ~AsyncEventSourceMessage(); 63 | size_t ack(size_t len, uint32_t time __attribute__((unused))); 64 | size_t send(AsyncClient *client); 65 | bool finished(){ return _acked == _len; } 66 | bool sent() { return _sent == _len; } 67 | }; 68 | 69 | class AsyncEventSourceClient { 70 | private: 71 | AsyncClient *_client; 72 | AsyncEventSource *_server; 73 | uint32_t _lastId; 74 | LinkedList _messageQueue; 75 | void _queueMessage(AsyncEventSourceMessage *dataMessage); 76 | void _runQueue(); 77 | 78 | public: 79 | 80 | AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); 81 | ~AsyncEventSourceClient(); 82 | 83 | AsyncClient* client(){ return _client; } 84 | void close(); 85 | void write(const char * message, size_t len); 86 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 87 | bool connected() const { return (_client != NULL) && _client->connected(); } 88 | uint32_t lastId() const { return _lastId; } 89 | size_t packetsWaiting() const { return _messageQueue.length(); } 90 | 91 | //system callbacks (do not call) 92 | void _onAck(size_t len, uint32_t time); 93 | void _onPoll(); 94 | void _onTimeout(uint32_t time); 95 | void _onDisconnect(); 96 | }; 97 | 98 | class AsyncEventSource: public AsyncWebHandler { 99 | private: 100 | String _url; 101 | LinkedList _clients; 102 | ArEventHandlerFunction _connectcb; 103 | public: 104 | AsyncEventSource(const String& url); 105 | ~AsyncEventSource(); 106 | 107 | const char * url() const { return _url.c_str(); } 108 | void close(); 109 | void onConnect(ArEventHandlerFunction cb); 110 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 111 | size_t count() const; //number clinets connected 112 | size_t avgPacketsWaiting() const; 113 | 114 | //system callbacks (do not call) 115 | void _addClient(AsyncEventSourceClient * client); 116 | void _handleDisconnect(AsyncEventSourceClient * client); 117 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 118 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 119 | }; 120 | 121 | class AsyncEventSourceResponse: public AsyncWebServerResponse { 122 | private: 123 | String _content; 124 | AsyncEventSource *_server; 125 | public: 126 | AsyncEventSourceResponse(AsyncEventSource *server); 127 | void _respond(AsyncWebServerRequest *request); 128 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 129 | bool _sourceValid() const { return true; } 130 | }; 131 | 132 | 133 | #endif /* ASYNCEVENTSOURCE_H_ */ 134 | -------------------------------------------------------------------------------- /src/AsyncJson.h: -------------------------------------------------------------------------------- 1 | // AsyncJson.h 2 | /* 3 | Async Response to use with ArduinoJson and AsyncWebServer 4 | Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. 5 | 6 | Example of callback in use 7 | 8 | server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { 9 | 10 | AsyncJsonResponse * response = new AsyncJsonResponse(); 11 | JsonObject& root = response->getRoot(); 12 | root["key1"] = "key number one"; 13 | JsonObject& nested = root.createNestedObject("nested"); 14 | nested["key1"] = "key number one"; 15 | 16 | response->setLength(); 17 | request->send(response); 18 | }); 19 | 20 | -------------------- 21 | 22 | Async Request to use with ArduinoJson and AsyncWebServer 23 | Written by Arsène von Wyss (avonwyss) 24 | 25 | Example 26 | 27 | AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); 28 | handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { 29 | JsonObject& jsonObj = json.as(); 30 | // ... 31 | }); 32 | server.addHandler(handler); 33 | 34 | */ 35 | #ifndef ASYNC_JSON_H_ 36 | #define ASYNC_JSON_H_ 37 | #include 38 | #include 39 | #include 40 | 41 | #if ARDUINOJSON_VERSION_MAJOR == 5 42 | #define ARDUINOJSON_5_COMPATIBILITY 43 | #else 44 | #ifndef DYNAMIC_JSON_DOCUMENT_SIZE 45 | #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 46 | #endif 47 | #endif 48 | 49 | constexpr const char* JSON_MIMETYPE = "application/json"; 50 | 51 | /* 52 | * Json Response 53 | * */ 54 | 55 | class ChunkPrint : public Print { 56 | private: 57 | uint8_t* _destination; 58 | size_t _to_skip; 59 | size_t _to_write; 60 | size_t _pos; 61 | public: 62 | ChunkPrint(uint8_t* destination, size_t from, size_t len) 63 | : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} 64 | virtual ~ChunkPrint(){} 65 | size_t write(uint8_t c){ 66 | if (_to_skip > 0) { 67 | _to_skip--; 68 | return 1; 69 | } else if (_to_write > 0) { 70 | _to_write--; 71 | _destination[_pos++] = c; 72 | return 1; 73 | } 74 | return 0; 75 | } 76 | size_t write(const uint8_t *buffer, size_t size) 77 | { 78 | return this->Print::write(buffer, size); 79 | } 80 | }; 81 | 82 | class AsyncJsonResponse: public AsyncAbstractResponse { 83 | protected: 84 | 85 | #ifdef ARDUINOJSON_5_COMPATIBILITY 86 | DynamicJsonBuffer _jsonBuffer; 87 | #else 88 | DynamicJsonDocument _jsonBuffer; 89 | #endif 90 | 91 | JsonVariant _root; 92 | bool _isValid; 93 | 94 | public: 95 | 96 | #ifdef ARDUINOJSON_5_COMPATIBILITY 97 | AsyncJsonResponse(bool isArray=false): _isValid{false} { 98 | _code = 200; 99 | _contentType = JSON_MIMETYPE; 100 | if(isArray) 101 | _root = _jsonBuffer.createArray(); 102 | else 103 | _root = _jsonBuffer.createObject(); 104 | } 105 | #else 106 | AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { 107 | _code = 200; 108 | _contentType = JSON_MIMETYPE; 109 | if(isArray) 110 | _root = _jsonBuffer.createNestedArray(); 111 | else 112 | _root = _jsonBuffer.createNestedObject(); 113 | } 114 | #endif 115 | 116 | ~AsyncJsonResponse() {} 117 | JsonVariant & getRoot() { return _root; } 118 | bool _sourceValid() const { return _isValid; } 119 | size_t setLength() { 120 | 121 | #ifdef ARDUINOJSON_5_COMPATIBILITY 122 | _contentLength = _root.measureLength(); 123 | #else 124 | _contentLength = measureJson(_root); 125 | #endif 126 | 127 | if (_contentLength) { _isValid = true; } 128 | return _contentLength; 129 | } 130 | 131 | size_t getSize() { return _jsonBuffer.size(); } 132 | 133 | size_t _fillBuffer(uint8_t *data, size_t len){ 134 | ChunkPrint dest(data, _sentLength, len); 135 | 136 | #ifdef ARDUINOJSON_5_COMPATIBILITY 137 | _root.printTo( dest ) ; 138 | #else 139 | serializeJson(_root, dest); 140 | #endif 141 | return len; 142 | } 143 | }; 144 | 145 | class PrettyAsyncJsonResponse: public AsyncJsonResponse { 146 | public: 147 | #ifdef ARDUINOJSON_5_COMPATIBILITY 148 | PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {} 149 | #else 150 | PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} 151 | #endif 152 | size_t setLength () { 153 | #ifdef ARDUINOJSON_5_COMPATIBILITY 154 | _contentLength = _root.measurePrettyLength (); 155 | #else 156 | _contentLength = measureJsonPretty(_root); 157 | #endif 158 | if (_contentLength) {_isValid = true;} 159 | return _contentLength; 160 | } 161 | size_t _fillBuffer (uint8_t *data, size_t len) { 162 | ChunkPrint dest (data, _sentLength, len); 163 | #ifdef ARDUINOJSON_5_COMPATIBILITY 164 | _root.prettyPrintTo (dest); 165 | #else 166 | serializeJsonPretty(_root, dest); 167 | #endif 168 | return len; 169 | } 170 | }; 171 | 172 | typedef std::function ArJsonRequestHandlerFunction; 173 | 174 | class AsyncCallbackJsonWebHandler: public AsyncWebHandler { 175 | private: 176 | protected: 177 | const String _uri; 178 | WebRequestMethodComposite _method; 179 | ArJsonRequestHandlerFunction _onRequest; 180 | size_t _contentLength; 181 | #ifndef ARDUINOJSON_5_COMPATIBILITY 182 | const size_t maxJsonBufferSize; 183 | #endif 184 | size_t _maxContentLength; 185 | public: 186 | #ifdef ARDUINOJSON_5_COMPATIBILITY 187 | AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) 188 | : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} 189 | #else 190 | AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE) 191 | : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} 192 | #endif 193 | 194 | void setMethod(WebRequestMethodComposite method){ _method = method; } 195 | void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; } 196 | void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } 197 | 198 | virtual bool canHandle(AsyncWebServerRequest *request) override final{ 199 | if(!_onRequest) 200 | return false; 201 | 202 | if(!(_method & request->method())) 203 | return false; 204 | 205 | if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) 206 | return false; 207 | 208 | if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) ) 209 | return false; 210 | 211 | request->addInterestingHeader("ANY"); 212 | return true; 213 | } 214 | 215 | virtual void handleRequest(AsyncWebServerRequest *request) override final { 216 | if(_onRequest) { 217 | if (request->_tempObject != NULL) { 218 | 219 | #ifdef ARDUINOJSON_5_COMPATIBILITY 220 | DynamicJsonBuffer jsonBuffer; 221 | JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); 222 | if (json.success()) { 223 | #else 224 | DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); 225 | DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); 226 | if(!error) { 227 | JsonVariant json = jsonBuffer.as(); 228 | #endif 229 | 230 | _onRequest(request, json); 231 | return; 232 | } 233 | } 234 | request->send(_contentLength > _maxContentLength ? 413 : 400); 235 | } else { 236 | request->send(500); 237 | } 238 | } 239 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { 240 | } 241 | virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { 242 | if (_onRequest) { 243 | _contentLength = total; 244 | if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { 245 | request->_tempObject = malloc(total); 246 | } 247 | if (request->_tempObject != NULL) { 248 | memcpy((uint8_t*)(request->_tempObject) + index, data, len); 249 | } 250 | } 251 | } 252 | virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} 253 | }; 254 | #endif 255 | -------------------------------------------------------------------------------- /src/AsyncWebSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSOCKET_H_ 22 | #define ASYNCWEBSOCKET_H_ 23 | 24 | #include 25 | #ifdef ESP32 26 | #include 27 | #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) 28 | // Code for version 3.x 29 | #include "mbedtls/compat-2.x.h" 30 | #endif 31 | #define WS_MAX_QUEUED_MESSAGES 32 32 | #else 33 | #include 34 | #define WS_MAX_QUEUED_MESSAGES 8 35 | #endif 36 | #include 37 | 38 | #include "AsyncWebSynchronization.h" 39 | 40 | #ifdef ESP8266 41 | #include 42 | #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library 43 | #include <../src/Hash.h> 44 | #endif 45 | #endif 46 | 47 | #ifdef ESP32 48 | #define DEFAULT_MAX_WS_CLIENTS 8 49 | #else 50 | #define DEFAULT_MAX_WS_CLIENTS 4 51 | #endif 52 | 53 | class AsyncWebSocket; 54 | class AsyncWebSocketResponse; 55 | class AsyncWebSocketClient; 56 | class AsyncWebSocketControl; 57 | 58 | typedef struct { 59 | /** Message type as defined by enum AwsFrameType. 60 | * Note: Applications will only see WS_TEXT and WS_BINARY. 61 | * All other types are handled by the library. */ 62 | uint8_t message_opcode; 63 | /** Frame number of a fragmented message. */ 64 | uint32_t num; 65 | /** Is this the last frame in a fragmented message ?*/ 66 | uint8_t final; 67 | /** Is this frame masked? */ 68 | uint8_t masked; 69 | /** Message type as defined by enum AwsFrameType. 70 | * This value is the same as message_opcode for non-fragmented 71 | * messages, but may also be WS_CONTINUATION in a fragmented message. */ 72 | uint8_t opcode; 73 | /** Length of the current frame. 74 | * This equals the total length of the message if num == 0 && final == true */ 75 | uint64_t len; 76 | /** Mask key */ 77 | uint8_t mask[4]; 78 | /** Offset of the data inside the current frame. */ 79 | uint64_t index; 80 | } AwsFrameInfo; 81 | 82 | typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; 83 | typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; 84 | typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; 85 | typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; 86 | 87 | class AsyncWebSocketMessageBuffer { 88 | private: 89 | uint8_t * _data; 90 | size_t _len; 91 | bool _lock; 92 | uint32_t _count; 93 | 94 | public: 95 | AsyncWebSocketMessageBuffer(); 96 | AsyncWebSocketMessageBuffer(size_t size); 97 | AsyncWebSocketMessageBuffer(uint8_t * data, size_t size); 98 | AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &); 99 | AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); 100 | ~AsyncWebSocketMessageBuffer(); 101 | void operator ++(int i) { (void)i; _count++; } 102 | void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; } 103 | bool reserve(size_t size); 104 | void lock() { _lock = true; } 105 | void unlock() { _lock = false; } 106 | uint8_t * get() { return _data; } 107 | size_t length() { return _len; } 108 | uint32_t count() { return _count; } 109 | bool canDelete() { return (!_count && !_lock); } 110 | 111 | friend AsyncWebSocket; 112 | 113 | }; 114 | 115 | class AsyncWebSocketMessage { 116 | protected: 117 | uint8_t _opcode; 118 | bool _mask; 119 | AwsMessageStatus _status; 120 | public: 121 | AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){} 122 | virtual ~AsyncWebSocketMessage(){} 123 | virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){} 124 | virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; } 125 | virtual bool finished(){ return _status != WS_MSG_SENDING; } 126 | virtual bool betweenFrames() const { return false; } 127 | }; 128 | 129 | class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { 130 | private: 131 | size_t _len; 132 | size_t _sent; 133 | size_t _ack; 134 | size_t _acked; 135 | uint8_t * _data; 136 | public: 137 | AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false); 138 | AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false); 139 | virtual ~AsyncWebSocketBasicMessage() override; 140 | virtual bool betweenFrames() const override { return _acked == _ack; } 141 | virtual void ack(size_t len, uint32_t time) override ; 142 | virtual size_t send(AsyncClient *client) override ; 143 | }; 144 | 145 | class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { 146 | private: 147 | uint8_t * _data; 148 | size_t _len; 149 | size_t _sent; 150 | size_t _ack; 151 | size_t _acked; 152 | AsyncWebSocketMessageBuffer * _WSbuffer; 153 | public: 154 | AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false); 155 | virtual ~AsyncWebSocketMultiMessage() override; 156 | virtual bool betweenFrames() const override { return _acked == _ack; } 157 | virtual void ack(size_t len, uint32_t time) override ; 158 | virtual size_t send(AsyncClient *client) override ; 159 | }; 160 | 161 | class AsyncWebSocketClient { 162 | private: 163 | AsyncClient *_client; 164 | AsyncWebSocket *_server; 165 | uint32_t _clientId; 166 | AwsClientStatus _status; 167 | 168 | LinkedList _controlQueue; 169 | LinkedList _messageQueue; 170 | 171 | uint8_t _pstate; 172 | AwsFrameInfo _pinfo; 173 | 174 | uint32_t _lastMessageTime; 175 | uint32_t _keepAlivePeriod; 176 | 177 | void _queueMessage(AsyncWebSocketMessage *dataMessage); 178 | void _queueControl(AsyncWebSocketControl *controlMessage); 179 | void _runQueue(); 180 | 181 | public: 182 | void *_tempObject; 183 | 184 | AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); 185 | ~AsyncWebSocketClient(); 186 | 187 | //client id increments for the given server 188 | uint32_t id(){ return _clientId; } 189 | AwsClientStatus status(){ return _status; } 190 | AsyncClient* client(){ return _client; } 191 | AsyncWebSocket *server(){ return _server; } 192 | AwsFrameInfo const &pinfo() const { return _pinfo; } 193 | 194 | IPAddress remoteIP(); 195 | uint16_t remotePort(); 196 | 197 | //control frames 198 | void close(uint16_t code=0, const char * message=NULL); 199 | void ping(uint8_t *data=NULL, size_t len=0); 200 | 201 | //set auto-ping period in seconds. disabled if zero (default) 202 | void keepAlivePeriod(uint16_t seconds){ 203 | _keepAlivePeriod = seconds * 1000; 204 | } 205 | uint16_t keepAlivePeriod(){ 206 | return (uint16_t)(_keepAlivePeriod / 1000); 207 | } 208 | 209 | //data packets 210 | void message(AsyncWebSocketMessage *message){ _queueMessage(message); } 211 | bool queueIsFull(); 212 | 213 | size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); 214 | #ifndef ESP32 215 | size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); 216 | #endif 217 | void text(const char * message, size_t len); 218 | void text(const char * message); 219 | void text(uint8_t * message, size_t len); 220 | void text(char * message); 221 | void text(const String &message); 222 | void text(const __FlashStringHelper *data); 223 | void text(AsyncWebSocketMessageBuffer *buffer); 224 | 225 | void binary(const char * message, size_t len); 226 | void binary(const char * message); 227 | void binary(uint8_t * message, size_t len); 228 | void binary(char * message); 229 | void binary(const String &message); 230 | void binary(const __FlashStringHelper *data, size_t len); 231 | void binary(AsyncWebSocketMessageBuffer *buffer); 232 | 233 | bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } 234 | 235 | //system callbacks (do not call) 236 | void _onAck(size_t len, uint32_t time); 237 | void _onError(int8_t); 238 | void _onPoll(); 239 | void _onTimeout(uint32_t time); 240 | void _onDisconnect(); 241 | void _onData(void *pbuf, size_t plen); 242 | }; 243 | 244 | typedef std::function AwsEventHandler; 245 | 246 | //WebServer Handler implementation that plays the role of a socket server 247 | class AsyncWebSocket: public AsyncWebHandler { 248 | public: 249 | typedef LinkedList AsyncWebSocketClientLinkedList; 250 | private: 251 | String _url; 252 | AsyncWebSocketClientLinkedList _clients; 253 | uint32_t _cNextId; 254 | AwsEventHandler _eventHandler; 255 | bool _enabled; 256 | AsyncWebLock _lock; 257 | 258 | public: 259 | AsyncWebSocket(const String& url); 260 | ~AsyncWebSocket(); 261 | const char * url() const { return _url.c_str(); } 262 | void enable(bool e){ _enabled = e; } 263 | bool enabled() const { return _enabled; } 264 | bool availableForWriteAll(); 265 | bool availableForWrite(uint32_t id); 266 | 267 | size_t count() const; 268 | AsyncWebSocketClient * client(uint32_t id); 269 | bool hasClient(uint32_t id){ return client(id) != NULL; } 270 | 271 | void close(uint32_t id, uint16_t code=0, const char * message=NULL); 272 | void closeAll(uint16_t code=0, const char * message=NULL); 273 | void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); 274 | 275 | void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); 276 | void pingAll(uint8_t *data=NULL, size_t len=0); // done 277 | 278 | void text(uint32_t id, const char * message, size_t len); 279 | void text(uint32_t id, const char * message); 280 | void text(uint32_t id, uint8_t * message, size_t len); 281 | void text(uint32_t id, char * message); 282 | void text(uint32_t id, const String &message); 283 | void text(uint32_t id, const __FlashStringHelper *message); 284 | 285 | void textAll(const char * message, size_t len); 286 | void textAll(const char * message); 287 | void textAll(uint8_t * message, size_t len); 288 | void textAll(char * message); 289 | void textAll(const String &message); 290 | void textAll(const __FlashStringHelper *message); // need to convert 291 | void textAll(AsyncWebSocketMessageBuffer * buffer); 292 | 293 | void binary(uint32_t id, const char * message, size_t len); 294 | void binary(uint32_t id, const char * message); 295 | void binary(uint32_t id, uint8_t * message, size_t len); 296 | void binary(uint32_t id, char * message); 297 | void binary(uint32_t id, const String &message); 298 | void binary(uint32_t id, const __FlashStringHelper *message, size_t len); 299 | 300 | void binaryAll(const char * message, size_t len); 301 | void binaryAll(const char * message); 302 | void binaryAll(uint8_t * message, size_t len); 303 | void binaryAll(char * message); 304 | void binaryAll(const String &message); 305 | void binaryAll(const __FlashStringHelper *message, size_t len); 306 | void binaryAll(AsyncWebSocketMessageBuffer * buffer); 307 | 308 | void message(uint32_t id, AsyncWebSocketMessage *message); 309 | void messageAll(AsyncWebSocketMultiMessage *message); 310 | 311 | size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); 312 | size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); 313 | #ifndef ESP32 314 | size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); 315 | #endif 316 | size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); 317 | 318 | //event listener 319 | void onEvent(AwsEventHandler handler){ 320 | _eventHandler = handler; 321 | } 322 | 323 | //system callbacks (do not call) 324 | uint32_t _getNextId(){ return _cNextId++; } 325 | void _addClient(AsyncWebSocketClient * client); 326 | void _handleDisconnect(AsyncWebSocketClient * client); 327 | void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); 328 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 329 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 330 | 331 | 332 | // messagebuffer functions/objects. 333 | AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); 334 | AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); 335 | LinkedList _buffers; 336 | void _cleanBuffers(); 337 | 338 | AsyncWebSocketClientLinkedList getClients() const; 339 | }; 340 | 341 | //WebServer response to authenticate the socket and detach the tcp client from the web server request 342 | class AsyncWebSocketResponse: public AsyncWebServerResponse { 343 | private: 344 | String _content; 345 | AsyncWebSocket *_server; 346 | public: 347 | AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); 348 | void _respond(AsyncWebServerRequest *request); 349 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 350 | bool _sourceValid() const { return true; } 351 | }; 352 | 353 | 354 | #endif /* ASYNCWEBSOCKET_H_ */ 355 | -------------------------------------------------------------------------------- /src/AsyncWebSynchronization.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCWEBSYNCHRONIZATION_H_ 2 | #define ASYNCWEBSYNCHRONIZATION_H_ 3 | 4 | // Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default 5 | 6 | #include 7 | 8 | #ifdef ESP32 9 | 10 | // This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore 11 | class AsyncWebLock 12 | { 13 | private: 14 | SemaphoreHandle_t _lock; 15 | mutable void *_lockedBy; 16 | 17 | public: 18 | AsyncWebLock() { 19 | _lock = xSemaphoreCreateBinary(); 20 | _lockedBy = NULL; 21 | xSemaphoreGive(_lock); 22 | } 23 | 24 | ~AsyncWebLock() { 25 | vSemaphoreDelete(_lock); 26 | } 27 | 28 | bool lock() const { 29 | extern void *pxCurrentTCB; 30 | if (_lockedBy != pxCurrentTCB) { 31 | xSemaphoreTake(_lock, portMAX_DELAY); 32 | _lockedBy = pxCurrentTCB; 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | void unlock() const { 39 | _lockedBy = NULL; 40 | xSemaphoreGive(_lock); 41 | } 42 | }; 43 | 44 | #else 45 | 46 | // This is the 8266 version of the Sync Lock which is currently unimplemented 47 | class AsyncWebLock 48 | { 49 | 50 | public: 51 | AsyncWebLock() { 52 | } 53 | 54 | ~AsyncWebLock() { 55 | } 56 | 57 | bool lock() const { 58 | return false; 59 | } 60 | 61 | void unlock() const { 62 | } 63 | }; 64 | #endif 65 | 66 | class AsyncWebLockGuard 67 | { 68 | private: 69 | const AsyncWebLock *_lock; 70 | 71 | public: 72 | AsyncWebLockGuard(const AsyncWebLock &l) { 73 | if (l.lock()) { 74 | _lock = &l; 75 | } else { 76 | _lock = NULL; 77 | } 78 | } 79 | 80 | ~AsyncWebLockGuard() { 81 | if (_lock) { 82 | _lock->unlock(); 83 | } 84 | } 85 | }; 86 | 87 | #endif // ASYNCWEBSYNCHRONIZATION_H_ -------------------------------------------------------------------------------- /src/ESPAsyncWebSrv.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef _ESPAsyncWebSrv_H_ 22 | #define _ESPAsyncWebSrv_H_ 23 | 24 | #include "Arduino.h" 25 | 26 | #include 27 | #include "FS.h" 28 | 29 | #include "StringArray.h" 30 | 31 | #ifdef ESP32 32 | #include 33 | #include 34 | #elif defined(ESP8266) 35 | #include 36 | #include 37 | #else 38 | #error Platform not supported 39 | #endif 40 | 41 | #ifdef ASYNCWEBSERVER_REGEX 42 | #define ASYNCWEBSERVER_REGEX_ATTRIBUTE 43 | #else 44 | #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) 45 | #endif 46 | 47 | #define DEBUGF(...) //Serial.printf(__VA_ARGS__) 48 | 49 | class AsyncWebServer; 50 | class AsyncWebServerRequest; 51 | class AsyncWebServerResponse; 52 | class AsyncWebHeader; 53 | class AsyncWebParameter; 54 | class AsyncWebRewrite; 55 | class AsyncWebHandler; 56 | class AsyncStaticWebHandler; 57 | class AsyncCallbackWebHandler; 58 | class AsyncResponseStream; 59 | 60 | #ifndef WEBSERVER_H 61 | typedef enum { 62 | HTTP_GET = 0b00000001, 63 | HTTP_POST = 0b00000010, 64 | HTTP_DELETE = 0b00000100, 65 | HTTP_PUT = 0b00001000, 66 | HTTP_PATCH = 0b00010000, 67 | HTTP_HEAD = 0b00100000, 68 | HTTP_OPTIONS = 0b01000000, 69 | HTTP_ANY = 0b01111111, 70 | } WebRequestMethod; 71 | #endif 72 | 73 | //if this value is returned when asked for data, packet will not be sent and you will be asked for data again 74 | #define RESPONSE_TRY_AGAIN 0xFFFFFFFF 75 | 76 | typedef uint8_t WebRequestMethodComposite; 77 | typedef std::function ArDisconnectHandler; 78 | 79 | /* 80 | * PARAMETER :: Chainable object to hold GET/POST and FILE parameters 81 | * */ 82 | 83 | class AsyncWebParameter { 84 | private: 85 | String _name; 86 | String _value; 87 | size_t _size; 88 | bool _isForm; 89 | bool _isFile; 90 | 91 | public: 92 | 93 | 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){} 94 | const String& name() const { return _name; } 95 | const String& value() const { return _value; } 96 | size_t size() const { return _size; } 97 | bool isPost() const { return _isForm; } 98 | bool isFile() const { return _isFile; } 99 | }; 100 | 101 | /* 102 | * HEADER :: Chainable object to hold the headers 103 | * */ 104 | 105 | class AsyncWebHeader { 106 | private: 107 | String _name; 108 | String _value; 109 | 110 | public: 111 | AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} 112 | AsyncWebHeader(const String& data): _name(), _value(){ 113 | if(!data) return; 114 | int index = data.indexOf(':'); 115 | if (index < 0) return; 116 | _name = data.substring(0, index); 117 | _value = data.substring(index + 2); 118 | } 119 | ~AsyncWebHeader(){} 120 | const String& name() const { return _name; } 121 | const String& value() const { return _value; } 122 | String toString() const { return String(_name+": "+_value+"\r\n"); } 123 | }; 124 | 125 | /* 126 | * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect 127 | * */ 128 | 129 | typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; 130 | 131 | typedef std::function AwsResponseFiller; 132 | typedef std::function AwsTemplateProcessor; 133 | 134 | class AsyncWebServerRequest { 135 | using File = fs::File; 136 | using FS = fs::FS; 137 | friend class AsyncWebServer; 138 | friend class AsyncCallbackWebHandler; 139 | private: 140 | AsyncClient* _client; 141 | AsyncWebServer* _server; 142 | AsyncWebHandler* _handler; 143 | AsyncWebServerResponse* _response; 144 | StringArray _interestingHeaders; 145 | ArDisconnectHandler _onDisconnectfn; 146 | 147 | String _temp; 148 | uint8_t _parseState; 149 | 150 | uint8_t _version; 151 | WebRequestMethodComposite _method; 152 | String _url; 153 | String _host; 154 | String _contentType; 155 | String _boundary; 156 | String _authorization; 157 | RequestedConnectionType _reqconntype; 158 | void _removeNotInterestingHeaders(); 159 | bool _isDigest; 160 | bool _isMultipart; 161 | bool _isPlainPost; 162 | bool _expectingContinue; 163 | size_t _contentLength; 164 | size_t _parsedLength; 165 | 166 | LinkedList _headers; 167 | LinkedList _params; 168 | LinkedList _pathParams; 169 | 170 | uint8_t _multiParseState; 171 | uint8_t _boundaryPosition; 172 | size_t _itemStartIndex; 173 | size_t _itemSize; 174 | String _itemName; 175 | String _itemFilename; 176 | String _itemType; 177 | String _itemValue; 178 | uint8_t *_itemBuffer; 179 | size_t _itemBufferIndex; 180 | bool _itemIsFile; 181 | 182 | void _onPoll(); 183 | void _onAck(size_t len, uint32_t time); 184 | void _onError(int8_t error); 185 | void _onTimeout(uint32_t time); 186 | void _onDisconnect(); 187 | void _onData(void *buf, size_t len); 188 | 189 | void _addParam(AsyncWebParameter*); 190 | void _addPathParam(const char *param); 191 | 192 | bool _parseReqHead(); 193 | bool _parseReqHeader(); 194 | void _parseLine(); 195 | void _parsePlainPostChar(uint8_t data); 196 | void _parseMultipartPostByte(uint8_t data, bool last); 197 | void _addGetParams(const String& params); 198 | 199 | void _handleUploadStart(); 200 | void _handleUploadByte(uint8_t data, bool last); 201 | void _handleUploadEnd(); 202 | 203 | public: 204 | File _tempFile; 205 | void *_tempObject; 206 | 207 | AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); 208 | ~AsyncWebServerRequest(); 209 | 210 | AsyncClient* client(){ return _client; } 211 | uint8_t version() const { return _version; } 212 | WebRequestMethodComposite method() const { return _method; } 213 | const String& url() const { return _url; } 214 | const String& host() const { return _host; } 215 | const String& contentType() const { return _contentType; } 216 | size_t contentLength() const { return _contentLength; } 217 | bool multipart() const { return _isMultipart; } 218 | const char * methodToString() const; 219 | const char * requestedConnTypeToString() const; 220 | RequestedConnectionType requestedConnType() const { return _reqconntype; } 221 | bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); 222 | void onDisconnect (ArDisconnectHandler fn); 223 | 224 | //hash is the string representation of: 225 | // base64(user:pass) for basic or 226 | // user:realm:md5(user:realm:pass) for digest 227 | bool authenticate(const char * hash); 228 | bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); 229 | void requestAuthentication(const char * realm = NULL, bool isDigest = true); 230 | 231 | void setHandler(AsyncWebHandler *handler){ _handler = handler; } 232 | void addInterestingHeader(const String& name); 233 | 234 | void redirect(const String& url); 235 | 236 | void send(AsyncWebServerResponse *response); 237 | void send(int code, const String& contentType=String(), const String& content=String()); 238 | void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 239 | void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 240 | void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); 241 | void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 242 | void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 243 | void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); 244 | void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); 245 | 246 | AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); 247 | AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 248 | AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 249 | AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); 250 | AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 251 | AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 252 | AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); 253 | AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); 254 | AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); 255 | 256 | size_t headers() const; // get header count 257 | bool hasHeader(const String& name) const; // check if header exists 258 | bool hasHeader(const __FlashStringHelper * data) const; // check if header exists 259 | 260 | AsyncWebHeader* getHeader(const String& name) const; 261 | AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; 262 | AsyncWebHeader* getHeader(size_t num) const; 263 | 264 | size_t params() const; // get arguments count 265 | bool hasParam(const String& name, bool post=false, bool file=false) const; 266 | bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; 267 | 268 | AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; 269 | AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; 270 | AsyncWebParameter* getParam(size_t num) const; 271 | 272 | size_t args() const { return params(); } // get arguments count 273 | const String& arg(const String& name) const; // get request argument value by name 274 | const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) 275 | const String& arg(size_t i) const; // get request argument value by number 276 | const String& argName(size_t i) const; // get request argument name by number 277 | bool hasArg(const char* name) const; // check if argument exists 278 | bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists 279 | 280 | const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; 281 | 282 | const String& header(const char* name) const;// get request header value by name 283 | const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) 284 | const String& header(size_t i) const; // get request header value by number 285 | const String& headerName(size_t i) const; // get request header name by number 286 | String urlDecode(const String& text) const; 287 | }; 288 | 289 | /* 290 | * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) 291 | * */ 292 | 293 | typedef std::function ArRequestFilterFunction; 294 | 295 | bool ON_STA_FILTER(AsyncWebServerRequest *request); 296 | 297 | bool ON_AP_FILTER(AsyncWebServerRequest *request); 298 | 299 | /* 300 | * REWRITE :: One instance can be handle any Request (done by the Server) 301 | * */ 302 | 303 | class AsyncWebRewrite { 304 | protected: 305 | String _from; 306 | String _toUrl; 307 | String _params; 308 | ArRequestFilterFunction _filter; 309 | public: 310 | AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ 311 | int index = _toUrl.indexOf('?'); 312 | if (index > 0) { 313 | _params = _toUrl.substring(index +1); 314 | _toUrl = _toUrl.substring(0, index); 315 | } 316 | } 317 | virtual ~AsyncWebRewrite(){} 318 | AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } 319 | bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } 320 | const String& from(void) const { return _from; } 321 | const String& toUrl(void) const { return _toUrl; } 322 | const String& params(void) const { return _params; } 323 | virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } 324 | }; 325 | 326 | /* 327 | * HANDLER :: One instance can be attached to any Request (done by the Server) 328 | * */ 329 | 330 | class AsyncWebHandler { 331 | protected: 332 | ArRequestFilterFunction _filter; 333 | String _username; 334 | String _password; 335 | public: 336 | AsyncWebHandler():_username(""), _password(""){} 337 | AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } 338 | AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; 339 | bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } 340 | virtual ~AsyncWebHandler(){} 341 | virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ 342 | return false; 343 | } 344 | virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} 345 | 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))){} 346 | 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))){} 347 | virtual bool isRequestHandlerTrivial(){return true;} 348 | }; 349 | 350 | /* 351 | * RESPONSE :: One instance is created for each Request (attached by the Handler) 352 | * */ 353 | 354 | typedef enum { 355 | RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED 356 | } WebResponseState; 357 | 358 | class AsyncWebServerResponse { 359 | protected: 360 | int _code; 361 | LinkedList _headers; 362 | String _contentType; 363 | size_t _contentLength; 364 | bool _sendContentLength; 365 | bool _chunked; 366 | size_t _headLength; 367 | size_t _sentLength; 368 | size_t _ackedLength; 369 | size_t _writtenLength; 370 | WebResponseState _state; 371 | const char* _responseCodeToString(int code); 372 | 373 | public: 374 | AsyncWebServerResponse(); 375 | virtual ~AsyncWebServerResponse(); 376 | virtual void setCode(int code); 377 | virtual void setContentLength(size_t len); 378 | virtual void setContentType(const String& type); 379 | virtual void addHeader(const String& name, const String& value); 380 | virtual String _assembleHead(uint8_t version); 381 | virtual bool _started() const; 382 | virtual bool _finished() const; 383 | virtual bool _failed() const; 384 | virtual bool _sourceValid() const; 385 | virtual void _respond(AsyncWebServerRequest *request); 386 | virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 387 | }; 388 | 389 | /* 390 | * SERVER :: One instance 391 | * */ 392 | 393 | typedef std::function ArRequestHandlerFunction; 394 | typedef std::function ArUploadHandlerFunction; 395 | typedef std::function ArBodyHandlerFunction; 396 | 397 | class AsyncWebServer { 398 | protected: 399 | AsyncServer _server; 400 | LinkedList _rewrites; 401 | LinkedList _handlers; 402 | AsyncCallbackWebHandler* _catchAllHandler; 403 | 404 | public: 405 | AsyncWebServer(uint16_t port); 406 | ~AsyncWebServer(); 407 | 408 | void begin(); 409 | void end(); 410 | 411 | #if ASYNC_TCP_SSL_ENABLED 412 | void onSslFileRequest(AcSSlFileHandler cb, void* arg); 413 | void beginSecure(const char *cert, const char *private_key_file, const char *password); 414 | #endif 415 | 416 | AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); 417 | bool removeRewrite(AsyncWebRewrite* rewrite); 418 | AsyncWebRewrite& rewrite(const char* from, const char* to); 419 | 420 | AsyncWebHandler& addHandler(AsyncWebHandler* handler); 421 | bool removeHandler(AsyncWebHandler* handler); 422 | 423 | AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); 424 | AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); 425 | AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); 426 | AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); 427 | 428 | AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); 429 | 430 | void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned 431 | void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads 432 | void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) 433 | 434 | void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody 435 | 436 | void _handleDisconnect(AsyncWebServerRequest *request); 437 | void _attachHandler(AsyncWebServerRequest *request); 438 | void _rewriteRequest(AsyncWebServerRequest *request); 439 | }; 440 | 441 | class DefaultHeaders { 442 | using headers_t = LinkedList; 443 | headers_t _headers; 444 | 445 | DefaultHeaders() 446 | :_headers(headers_t([](AsyncWebHeader *h){ delete h; })) 447 | {} 448 | public: 449 | using ConstIterator = headers_t::ConstIterator; 450 | 451 | void addHeader(const String& name, const String& value){ 452 | _headers.add(new AsyncWebHeader(name, value)); 453 | } 454 | 455 | ConstIterator begin() const { return _headers.begin(); } 456 | ConstIterator end() const { return _headers.end(); } 457 | 458 | DefaultHeaders(DefaultHeaders const &) = delete; 459 | DefaultHeaders &operator=(DefaultHeaders const &) = delete; 460 | static DefaultHeaders &Instance() { 461 | static DefaultHeaders instance; 462 | return instance; 463 | } 464 | }; 465 | 466 | #include "WebResponseImpl.h" 467 | #include "WebHandlerImpl.h" 468 | #include "AsyncWebSocket.h" 469 | #include "AsyncEventSource.h" 470 | 471 | #endif /* _AsyncWebServer_H_ */ 472 | -------------------------------------------------------------------------------- /src/SPIFFSEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "SPIFFSEditor.h" 2 | #include 3 | 4 | //File: edit.htm.gz, Size: 4151 5 | #define edit_htm_gz_len 4151 6 | const uint8_t edit_htm_gz[] PROGMEM = { 7 | 0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68, 8 | 0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED, 9 | 0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6, 10 | 0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB, 11 | 0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A, 12 | 0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61, 13 | 0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7, 14 | 0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02, 15 | 0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C, 16 | 0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A, 17 | 0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89, 18 | 0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76, 19 | 0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D, 20 | 0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9, 21 | 0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B, 22 | 0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91, 23 | 0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78, 24 | 0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78, 25 | 0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98, 26 | 0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E, 27 | 0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41, 28 | 0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21, 29 | 0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F, 30 | 0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74, 31 | 0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C, 32 | 0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0, 33 | 0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C, 34 | 0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30, 35 | 0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9, 36 | 0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61, 37 | 0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B, 38 | 0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9, 39 | 0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B, 40 | 0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD, 41 | 0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3, 42 | 0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77, 43 | 0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83, 44 | 0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF, 45 | 0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3, 46 | 0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55, 47 | 0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3, 48 | 0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF, 49 | 0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF, 50 | 0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60, 51 | 0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1, 52 | 0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE, 53 | 0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F, 54 | 0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0, 55 | 0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9, 56 | 0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5, 57 | 0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15, 58 | 0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74, 59 | 0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D, 60 | 0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD, 61 | 0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A, 62 | 0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6, 63 | 0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2, 64 | 0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF, 65 | 0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53, 66 | 0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2, 67 | 0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A, 68 | 0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83, 69 | 0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26, 70 | 0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0, 71 | 0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0, 72 | 0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84, 73 | 0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99, 74 | 0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5, 75 | 0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9, 76 | 0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87, 77 | 0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F, 78 | 0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6, 79 | 0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B, 80 | 0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D, 81 | 0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25, 82 | 0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3, 83 | 0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F, 84 | 0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35, 85 | 0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A, 86 | 0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6, 87 | 0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7, 88 | 0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A, 89 | 0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9, 90 | 0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97, 91 | 0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36, 92 | 0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C, 93 | 0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A, 94 | 0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C, 95 | 0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F, 96 | 0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11, 97 | 0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16, 98 | 0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA, 99 | 0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB, 100 | 0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A, 101 | 0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6, 102 | 0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28, 103 | 0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1, 104 | 0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E, 105 | 0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E, 106 | 0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92, 107 | 0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05, 108 | 0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8, 109 | 0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0, 110 | 0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85, 111 | 0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40, 112 | 0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56, 113 | 0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47, 114 | 0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA, 115 | 0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7, 116 | 0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD, 117 | 0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61, 118 | 0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58, 119 | 0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D, 120 | 0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8, 121 | 0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C, 122 | 0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA, 123 | 0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49, 124 | 0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51, 125 | 0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00, 126 | 0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A, 127 | 0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A, 128 | 0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35, 129 | 0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F, 130 | 0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E, 131 | 0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C, 132 | 0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64, 133 | 0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C, 134 | 0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1, 135 | 0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B, 136 | 0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC, 137 | 0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42, 138 | 0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B, 139 | 0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71, 140 | 0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F, 141 | 0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28, 142 | 0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9, 143 | 0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD, 144 | 0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6, 145 | 0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F, 146 | 0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5, 147 | 0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8, 148 | 0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF, 149 | 0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62, 150 | 0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C, 151 | 0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7, 152 | 0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89, 153 | 0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29, 154 | 0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95, 155 | 0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7, 156 | 0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB, 157 | 0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09, 158 | 0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F, 159 | 0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60, 160 | 0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35, 161 | 0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6, 162 | 0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B, 163 | 0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66, 164 | 0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25, 165 | 0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E, 166 | 0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97, 167 | 0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC, 168 | 0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE, 169 | 0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7, 170 | 0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13, 171 | 0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0, 172 | 0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A, 173 | 0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93, 174 | 0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E, 175 | 0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9, 176 | 0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78, 177 | 0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5, 178 | 0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12, 179 | 0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E, 180 | 0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35, 181 | 0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98, 182 | 0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58, 183 | 0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3, 184 | 0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64, 185 | 0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39, 186 | 0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D, 187 | 0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62, 188 | 0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48, 189 | 0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D, 190 | 0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8, 191 | 0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9, 192 | 0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30, 193 | 0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6, 194 | 0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1, 195 | 0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56, 196 | 0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84, 197 | 0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0, 198 | 0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC, 199 | 0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E, 200 | 0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39, 201 | 0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B, 202 | 0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA, 203 | 0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1, 204 | 0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1, 205 | 0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88, 206 | 0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4, 207 | 0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC, 208 | 0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98, 209 | 0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97, 210 | 0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8, 211 | 0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30, 212 | 0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA, 213 | 0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B, 214 | 0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC, 215 | 0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45, 216 | 0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD, 217 | 0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76, 218 | 0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD, 219 | 0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76, 220 | 0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4, 221 | 0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF, 222 | 0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4, 223 | 0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42, 224 | 0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43, 225 | 0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B, 226 | 0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97, 227 | 0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73, 228 | 0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B, 229 | 0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50, 230 | 0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51, 231 | 0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25, 232 | 0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26, 233 | 0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80, 234 | 0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9, 235 | 0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0, 236 | 0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74, 237 | 0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA, 238 | 0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D, 239 | 0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F, 240 | 0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2, 241 | 0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1, 242 | 0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99, 243 | 0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D, 244 | 0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B, 245 | 0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD, 246 | 0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F, 247 | 0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB, 248 | 0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47, 249 | 0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59, 250 | 0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D, 251 | 0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD, 252 | 0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94, 253 | 0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35, 254 | 0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81, 255 | 0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D, 256 | 0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20, 257 | 0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB, 258 | 0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B, 259 | 0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6, 260 | 0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17, 261 | 0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8, 262 | 0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26, 263 | 0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57, 264 | 0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36, 265 | 0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53, 266 | 0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00 267 | }; 268 | 269 | #define SPIFFS_MAXLENGTH_FILEPATH 32 270 | const char *excludeListFile = "/.exclude.files"; 271 | 272 | typedef struct ExcludeListS { 273 | char *item; 274 | ExcludeListS *next; 275 | } ExcludeList; 276 | 277 | static ExcludeList *excludes = NULL; 278 | 279 | static bool matchWild(const char *pattern, const char *testee) { 280 | const char *nxPat = NULL, *nxTst = NULL; 281 | 282 | while (*testee) { 283 | if (( *pattern == '?' ) || (*pattern == *testee)){ 284 | pattern++;testee++; 285 | continue; 286 | } 287 | if (*pattern=='*'){ 288 | nxPat=pattern++; nxTst=testee; 289 | continue; 290 | } 291 | if (nxPat){ 292 | pattern = nxPat+1; testee=++nxTst; 293 | continue; 294 | } 295 | return false; 296 | } 297 | while (*pattern=='*'){pattern++;} 298 | return (*pattern == 0); 299 | } 300 | 301 | static bool addExclude(const char *item){ 302 | size_t len = strlen(item); 303 | if(!len){ 304 | return false; 305 | } 306 | ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList)); 307 | if(!e){ 308 | return false; 309 | } 310 | e->item = (char *)malloc(len+1); 311 | if(!e->item){ 312 | free(e); 313 | return false; 314 | } 315 | memcpy(e->item, item, len+1); 316 | e->next = excludes; 317 | excludes = e; 318 | return true; 319 | } 320 | 321 | static void loadExcludeList(fs::FS &_fs, const char *filename){ 322 | static char linebuf[SPIFFS_MAXLENGTH_FILEPATH]; 323 | fs::File excludeFile=_fs.open(filename, "r"); 324 | if(!excludeFile){ 325 | //addExclude("/*.js.gz"); 326 | return; 327 | } 328 | #ifdef ESP32 329 | if(excludeFile.isDirectory()){ 330 | excludeFile.close(); 331 | return; 332 | } 333 | #endif 334 | if (excludeFile.size() > 0){ 335 | uint8_t idx; 336 | bool isOverflowed = false; 337 | while (excludeFile.available()){ 338 | linebuf[0] = '\0'; 339 | idx = 0; 340 | int lastChar; 341 | do { 342 | lastChar = excludeFile.read(); 343 | if(lastChar != '\r'){ 344 | linebuf[idx++] = (char) lastChar; 345 | } 346 | } while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH)); 347 | 348 | if(isOverflowed){ 349 | isOverflowed = (lastChar != '\n'); 350 | continue; 351 | } 352 | isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH); 353 | linebuf[idx-1] = '\0'; 354 | if(!addExclude(linebuf)){ 355 | excludeFile.close(); 356 | return; 357 | } 358 | } 359 | } 360 | excludeFile.close(); 361 | } 362 | 363 | static bool isExcluded(fs::FS &_fs, const char *filename) { 364 | if(excludes == NULL){ 365 | loadExcludeList(_fs, excludeListFile); 366 | } 367 | ExcludeList *e = excludes; 368 | while(e){ 369 | if (matchWild(e->item, filename)){ 370 | return true; 371 | } 372 | e = e->next; 373 | } 374 | return false; 375 | } 376 | 377 | // WEB HANDLER IMPLEMENTATION 378 | 379 | #ifdef ESP32 380 | SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password) 381 | #else 382 | SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs) 383 | #endif 384 | :_fs(fs) 385 | ,_username(username) 386 | ,_password(password) 387 | ,_authenticated(false) 388 | ,_startTime(0) 389 | {} 390 | 391 | bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){ 392 | if(request->url().equalsIgnoreCase("/edit")){ 393 | if(request->method() == HTTP_GET){ 394 | if(request->hasParam("list")) 395 | return true; 396 | if(request->hasParam("edit")){ 397 | request->_tempFile = _fs.open(request->arg("edit"), "r"); 398 | if(!request->_tempFile){ 399 | return false; 400 | } 401 | #ifdef ESP32 402 | if(request->_tempFile.isDirectory()){ 403 | request->_tempFile.close(); 404 | return false; 405 | } 406 | #endif 407 | } 408 | if(request->hasParam("download")){ 409 | request->_tempFile = _fs.open(request->arg("download"), "r"); 410 | if(!request->_tempFile){ 411 | return false; 412 | } 413 | #ifdef ESP32 414 | if(request->_tempFile.isDirectory()){ 415 | request->_tempFile.close(); 416 | return false; 417 | } 418 | #endif 419 | } 420 | request->addInterestingHeader("If-Modified-Since"); 421 | return true; 422 | } 423 | else if(request->method() == HTTP_POST) 424 | return true; 425 | else if(request->method() == HTTP_DELETE) 426 | return true; 427 | else if(request->method() == HTTP_PUT) 428 | return true; 429 | 430 | } 431 | return false; 432 | } 433 | 434 | 435 | void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){ 436 | if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) 437 | return request->requestAuthentication(); 438 | 439 | if(request->method() == HTTP_GET){ 440 | if(request->hasParam("list")){ 441 | String path = request->getParam("list")->value(); 442 | #ifdef ESP32 443 | File dir = _fs.open(path); 444 | #else 445 | Dir dir = _fs.openDir(path); 446 | #endif 447 | path = String(); 448 | String output = "["; 449 | #ifdef ESP32 450 | File entry = dir.openNextFile(); 451 | while(entry){ 452 | #else 453 | while(dir.next()){ 454 | fs::File entry = dir.openFile("r"); 455 | #endif 456 | if (isExcluded(_fs, entry.name())) { 457 | #ifdef ESP32 458 | entry = dir.openNextFile(); 459 | #endif 460 | continue; 461 | } 462 | if (output != "[") output += ','; 463 | output += "{\"type\":\""; 464 | output += "file"; 465 | output += "\",\"name\":\""; 466 | output += String(entry.name()); 467 | output += "\",\"size\":"; 468 | output += String(entry.size()); 469 | output += "}"; 470 | #ifdef ESP32 471 | entry = dir.openNextFile(); 472 | #else 473 | entry.close(); 474 | #endif 475 | } 476 | #ifdef ESP32 477 | dir.close(); 478 | #endif 479 | output += "]"; 480 | request->send(200, "application/json", output); 481 | output = String(); 482 | } 483 | else if(request->hasParam("edit") || request->hasParam("download")){ 484 | request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download")); 485 | } 486 | else { 487 | const char * buildTime = __DATE__ " " __TIME__ " GMT"; 488 | if (request->header("If-Modified-Since").equals(buildTime)) { 489 | request->send(304); 490 | } else { 491 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len); 492 | response->addHeader("Content-Encoding", "gzip"); 493 | response->addHeader("Last-Modified", buildTime); 494 | request->send(response); 495 | } 496 | } 497 | } else if(request->method() == HTTP_DELETE){ 498 | if(request->hasParam("path", true)){ 499 | _fs.remove(request->getParam("path", true)->value()); 500 | request->send(200, "", "DELETE: "+request->getParam("path", true)->value()); 501 | } else 502 | request->send(404); 503 | } else if(request->method() == HTTP_POST){ 504 | if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value())) 505 | request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value()); 506 | else 507 | request->send(500); 508 | } else if(request->method() == HTTP_PUT){ 509 | if(request->hasParam("path", true)){ 510 | String filename = request->getParam("path", true)->value(); 511 | if(_fs.exists(filename)){ 512 | request->send(200); 513 | } else { 514 | fs::File f = _fs.open(filename, "w"); 515 | if(f){ 516 | f.write((uint8_t)0x00); 517 | f.close(); 518 | request->send(200, "", "CREATE: "+filename); 519 | } else { 520 | request->send(500); 521 | } 522 | } 523 | } else 524 | request->send(400); 525 | } 526 | } 527 | 528 | void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ 529 | if(!index){ 530 | if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){ 531 | _authenticated = true; 532 | request->_tempFile = _fs.open(filename, "w"); 533 | _startTime = millis(); 534 | } 535 | } 536 | if(_authenticated && request->_tempFile){ 537 | if(len){ 538 | request->_tempFile.write(data,len); 539 | } 540 | if(final){ 541 | request->_tempFile.close(); 542 | } 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/SPIFFSEditor.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIFFSEditor_H_ 2 | #define SPIFFSEditor_H_ 3 | #include 4 | 5 | class SPIFFSEditor: public AsyncWebHandler { 6 | private: 7 | fs::FS _fs; 8 | String _username; 9 | String _password; 10 | bool _authenticated; 11 | uint32_t _startTime; 12 | public: 13 | #ifdef ESP32 14 | SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); 15 | #else 16 | SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); 17 | #endif 18 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 19 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 20 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; 21 | virtual bool isRequestHandlerTrivial() override final {return false;} 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/StringArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef STRINGARRAY_H_ 22 | #define STRINGARRAY_H_ 23 | 24 | #include "stddef.h" 25 | #include "WString.h" 26 | 27 | template 28 | class LinkedListNode { 29 | T _value; 30 | public: 31 | LinkedListNode* next; 32 | LinkedListNode(const T val): _value(val), next(nullptr) {} 33 | ~LinkedListNode(){} 34 | const T& value() const { return _value; }; 35 | T& value(){ return _value; } 36 | }; 37 | 38 | template class Item = LinkedListNode> 39 | class LinkedList { 40 | public: 41 | typedef Item ItemType; 42 | typedef std::function OnRemove; 43 | typedef std::function Predicate; 44 | private: 45 | ItemType* _root; 46 | OnRemove _onRemove; 47 | 48 | class Iterator { 49 | ItemType* _node; 50 | public: 51 | Iterator(ItemType* current = nullptr) : _node(current) {} 52 | Iterator(const Iterator& i) : _node(i._node) {} 53 | Iterator& operator ++() { _node = _node->next; return *this; } 54 | bool operator != (const Iterator& i) const { return _node != i._node; } 55 | const T& operator * () const { return _node->value(); } 56 | const T* operator -> () const { return &_node->value(); } 57 | }; 58 | 59 | public: 60 | typedef const Iterator ConstIterator; 61 | ConstIterator begin() const { return ConstIterator(_root); } 62 | ConstIterator end() const { return ConstIterator(nullptr); } 63 | 64 | LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} 65 | ~LinkedList(){} 66 | void add(const T& t){ 67 | auto it = new ItemType(t); 68 | if(!_root){ 69 | _root = it; 70 | } else { 71 | auto i = _root; 72 | while(i->next) i = i->next; 73 | i->next = it; 74 | } 75 | } 76 | T& front() const { 77 | return _root->value(); 78 | } 79 | 80 | bool isEmpty() const { 81 | return _root == nullptr; 82 | } 83 | size_t length() const { 84 | size_t i = 0; 85 | auto it = _root; 86 | while(it){ 87 | i++; 88 | it = it->next; 89 | } 90 | return i; 91 | } 92 | size_t count_if(Predicate predicate) const { 93 | size_t i = 0; 94 | auto it = _root; 95 | while(it){ 96 | if (!predicate){ 97 | i++; 98 | } 99 | else if (predicate(it->value())) { 100 | i++; 101 | } 102 | it = it->next; 103 | } 104 | return i; 105 | } 106 | const T* nth(size_t N) const { 107 | size_t i = 0; 108 | auto it = _root; 109 | while(it){ 110 | if(i++ == N) 111 | return &(it->value()); 112 | it = it->next; 113 | } 114 | return nullptr; 115 | } 116 | bool remove(const T& t){ 117 | auto it = _root; 118 | auto pit = _root; 119 | while(it){ 120 | if(it->value() == t){ 121 | if(it == _root){ 122 | _root = _root->next; 123 | } else { 124 | pit->next = it->next; 125 | } 126 | 127 | if (_onRemove) { 128 | _onRemove(it->value()); 129 | } 130 | 131 | delete it; 132 | return true; 133 | } 134 | pit = it; 135 | it = it->next; 136 | } 137 | return false; 138 | } 139 | bool remove_first(Predicate predicate){ 140 | auto it = _root; 141 | auto pit = _root; 142 | while(it){ 143 | if(predicate(it->value())){ 144 | if(it == _root){ 145 | _root = _root->next; 146 | } else { 147 | pit->next = it->next; 148 | } 149 | if (_onRemove) { 150 | _onRemove(it->value()); 151 | } 152 | delete it; 153 | return true; 154 | } 155 | pit = it; 156 | it = it->next; 157 | } 158 | return false; 159 | } 160 | 161 | void free(){ 162 | while(_root != nullptr){ 163 | auto it = _root; 164 | _root = _root->next; 165 | if (_onRemove) { 166 | _onRemove(it->value()); 167 | } 168 | delete it; 169 | } 170 | _root = nullptr; 171 | } 172 | }; 173 | 174 | 175 | class StringArray : public LinkedList { 176 | public: 177 | 178 | StringArray() : LinkedList(nullptr) {} 179 | 180 | bool containsIgnoreCase(const String& str){ 181 | for (const auto& s : *this) { 182 | if (str.equalsIgnoreCase(s)) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | }; 189 | 190 | 191 | 192 | 193 | #endif /* STRINGARRAY_H_ */ 194 | -------------------------------------------------------------------------------- /src/WebAuthentication.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "WebAuthentication.h" 22 | #include 23 | #ifdef ESP32 24 | #include "mbedtls/md5.h" 25 | #else 26 | #include "md5.h" 27 | #endif 28 | 29 | 30 | // Basic Auth hash = base64("username:password") 31 | 32 | bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ 33 | if(username == NULL || password == NULL || hash == NULL) 34 | return false; 35 | 36 | size_t toencodeLen = strlen(username)+strlen(password)+1; 37 | size_t encodedLen = base64_encode_expected_len(toencodeLen); 38 | if(strlen(hash) != encodedLen) 39 | return false; 40 | 41 | char *toencode = new char[toencodeLen+1]; 42 | if(toencode == NULL){ 43 | return false; 44 | } 45 | char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; 46 | if(encoded == NULL){ 47 | delete[] toencode; 48 | return false; 49 | } 50 | sprintf(toencode, "%s:%s", username, password); 51 | if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ 52 | delete[] toencode; 53 | delete[] encoded; 54 | return true; 55 | } 56 | delete[] toencode; 57 | delete[] encoded; 58 | return false; 59 | } 60 | 61 | static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more 62 | #ifdef ESP32 63 | mbedtls_md5_context _ctx; 64 | #else 65 | md5_context_t _ctx; 66 | #endif 67 | uint8_t i; 68 | uint8_t * _buf = (uint8_t*)malloc(16); 69 | if(_buf == NULL) 70 | return false; 71 | memset(_buf, 0x00, 16); 72 | #ifdef ESP32 73 | mbedtls_md5_init(&_ctx); 74 | mbedtls_md5_starts_ret(&_ctx); 75 | mbedtls_md5_update_ret(&_ctx, data, len); 76 | mbedtls_md5_finish_ret(&_ctx, _buf); 77 | #else 78 | MD5Init(&_ctx); 79 | MD5Update(&_ctx, data, len); 80 | MD5Final(_buf, &_ctx); 81 | #endif 82 | for(i = 0; i < 16; i++) { 83 | sprintf(output + (i * 2), "%02x", _buf[i]); 84 | } 85 | free(_buf); 86 | return true; 87 | } 88 | 89 | static String genRandomMD5(){ 90 | #ifdef ESP8266 91 | uint32_t r = RANDOM_REG32; 92 | #else 93 | uint32_t r = rand(); 94 | #endif 95 | char * out = (char*)malloc(33); 96 | if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) 97 | return ""; 98 | String res = String(out); 99 | free(out); 100 | return res; 101 | } 102 | 103 | static String stringMD5(const String& in){ 104 | char * out = (char*)malloc(33); 105 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 106 | return ""; 107 | String res = String(out); 108 | free(out); 109 | return res; 110 | } 111 | 112 | String generateDigestHash(const char * username, const char * password, const char * realm){ 113 | if(username == NULL || password == NULL || realm == NULL){ 114 | return ""; 115 | } 116 | char * out = (char*)malloc(33); 117 | String res = String(username); 118 | res.concat(":"); 119 | res.concat(realm); 120 | res.concat(":"); 121 | String in = res; 122 | in.concat(password); 123 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 124 | return ""; 125 | res.concat(out); 126 | free(out); 127 | return res; 128 | } 129 | 130 | String requestDigestAuthentication(const char * realm){ 131 | String header = "realm=\""; 132 | if(realm == NULL) 133 | header.concat("asyncesp"); 134 | else 135 | header.concat(realm); 136 | header.concat( "\", qop=\"auth\", nonce=\""); 137 | header.concat(genRandomMD5()); 138 | header.concat("\", opaque=\""); 139 | header.concat(genRandomMD5()); 140 | header.concat("\""); 141 | return header; 142 | } 143 | 144 | 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){ 145 | if(username == NULL || password == NULL || header == NULL || method == NULL){ 146 | //os_printf("AUTH FAIL: missing requred fields\n"); 147 | return false; 148 | } 149 | 150 | String myHeader = String(header); 151 | int nextBreak = myHeader.indexOf(","); 152 | if(nextBreak < 0){ 153 | //os_printf("AUTH FAIL: no variables\n"); 154 | return false; 155 | } 156 | 157 | String myUsername = String(); 158 | String myRealm = String(); 159 | String myNonce = String(); 160 | String myUri = String(); 161 | String myResponse = String(); 162 | String myQop = String(); 163 | String myNc = String(); 164 | String myCnonce = String(); 165 | 166 | myHeader += ", "; 167 | do { 168 | String avLine = myHeader.substring(0, nextBreak); 169 | avLine.trim(); 170 | myHeader = myHeader.substring(nextBreak+1); 171 | nextBreak = myHeader.indexOf(","); 172 | 173 | int eqSign = avLine.indexOf("="); 174 | if(eqSign < 0){ 175 | //os_printf("AUTH FAIL: no = sign\n"); 176 | return false; 177 | } 178 | String varName = avLine.substring(0, eqSign); 179 | avLine = avLine.substring(eqSign + 1); 180 | if(avLine.startsWith("\"")){ 181 | avLine = avLine.substring(1, avLine.length() - 1); 182 | } 183 | 184 | if(varName.equals("username")){ 185 | if(!avLine.equals(username)){ 186 | //os_printf("AUTH FAIL: username\n"); 187 | return false; 188 | } 189 | myUsername = avLine; 190 | } else if(varName.equals("realm")){ 191 | if(realm != NULL && !avLine.equals(realm)){ 192 | //os_printf("AUTH FAIL: realm\n"); 193 | return false; 194 | } 195 | myRealm = avLine; 196 | } else if(varName.equals("nonce")){ 197 | if(nonce != NULL && !avLine.equals(nonce)){ 198 | //os_printf("AUTH FAIL: nonce\n"); 199 | return false; 200 | } 201 | myNonce = avLine; 202 | } else if(varName.equals("opaque")){ 203 | if(opaque != NULL && !avLine.equals(opaque)){ 204 | //os_printf("AUTH FAIL: opaque\n"); 205 | return false; 206 | } 207 | } else if(varName.equals("uri")){ 208 | if(uri != NULL && !avLine.equals(uri)){ 209 | //os_printf("AUTH FAIL: uri\n"); 210 | return false; 211 | } 212 | myUri = avLine; 213 | } else if(varName.equals("response")){ 214 | myResponse = avLine; 215 | } else if(varName.equals("qop")){ 216 | myQop = avLine; 217 | } else if(varName.equals("nc")){ 218 | myNc = avLine; 219 | } else if(varName.equals("cnonce")){ 220 | myCnonce = avLine; 221 | } 222 | } while(nextBreak > 0); 223 | 224 | String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password)); 225 | String ha2 = String(method) + ":" + myUri; 226 | String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2); 227 | 228 | if(myResponse.equals(stringMD5(response))){ 229 | //os_printf("AUTH SUCCESS\n"); 230 | return true; 231 | } 232 | 233 | //os_printf("AUTH FAIL: password\n"); 234 | return false; 235 | } 236 | -------------------------------------------------------------------------------- /src/WebAuthentication.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifndef WEB_AUTHENTICATION_H_ 23 | #define WEB_AUTHENTICATION_H_ 24 | 25 | #include "Arduino.h" 26 | 27 | #ifdef ESP32 28 | // Code for version 3.x 29 | #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) 30 | #include "mbedtls/compat-2.x.h" 31 | #endif 32 | #endif 33 | 34 | bool checkBasicAuthentication(const char * header, const char * username, const char * password); 35 | String requestDigestAuthentication(const char * realm); 36 | 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); 37 | 38 | //for storing hashed versions on the device that can be authenticated against 39 | String generateDigestHash(const char * username, const char * password, const char * realm); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/WebHandlerImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERHANDLERIMPL_H_ 22 | #define ASYNCWEBSERVERHANDLERIMPL_H_ 23 | 24 | #include 25 | #ifdef ASYNCWEBSERVER_REGEX 26 | #include 27 | #endif 28 | 29 | #include "stddef.h" 30 | #include 31 | 32 | class AsyncStaticWebHandler: public AsyncWebHandler { 33 | using File = fs::File; 34 | using FS = fs::FS; 35 | private: 36 | bool _getFile(AsyncWebServerRequest *request); 37 | bool _fileExists(AsyncWebServerRequest *request, const String& path); 38 | uint8_t _countBits(const uint8_t value) const; 39 | protected: 40 | FS _fs; 41 | String _uri; 42 | String _path; 43 | String _default_file; 44 | String _cache_control; 45 | String _last_modified; 46 | AwsTemplateProcessor _callback; 47 | bool _isDir; 48 | bool _gzipFirst; 49 | uint8_t _gzipStats; 50 | public: 51 | AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); 52 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 53 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 54 | AsyncStaticWebHandler& setIsDir(bool isDir); 55 | AsyncStaticWebHandler& setDefaultFile(const char* filename); 56 | AsyncStaticWebHandler& setCacheControl(const char* cache_control); 57 | AsyncStaticWebHandler& setLastModified(const char* last_modified); 58 | AsyncStaticWebHandler& setLastModified(struct tm* last_modified); 59 | #ifdef ESP8266 60 | AsyncStaticWebHandler& setLastModified(time_t last_modified); 61 | AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated 62 | #endif 63 | AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} 64 | }; 65 | 66 | class AsyncCallbackWebHandler: public AsyncWebHandler { 67 | private: 68 | protected: 69 | String _uri; 70 | WebRequestMethodComposite _method; 71 | ArRequestHandlerFunction _onRequest; 72 | ArUploadHandlerFunction _onUpload; 73 | ArBodyHandlerFunction _onBody; 74 | bool _isRegex; 75 | public: 76 | AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} 77 | void setUri(const String& uri){ 78 | _uri = uri; 79 | _isRegex = uri.startsWith("^") && uri.endsWith("$"); 80 | } 81 | void setMethod(WebRequestMethodComposite method){ _method = method; } 82 | void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } 83 | void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } 84 | void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } 85 | 86 | virtual bool canHandle(AsyncWebServerRequest *request) override final{ 87 | 88 | if(!_onRequest) 89 | return false; 90 | 91 | if(!(_method & request->method())) 92 | return false; 93 | 94 | #ifdef ASYNCWEBSERVER_REGEX 95 | if (_isRegex) { 96 | std::regex pattern(_uri.c_str()); 97 | std::smatch matches; 98 | std::string s(request->url().c_str()); 99 | if(std::regex_search(s, matches, pattern)) { 100 | for (size_t i = 1; i < matches.size(); ++i) { // start from 1 101 | request->_addPathParam(matches[i].str().c_str()); 102 | } 103 | } else { 104 | return false; 105 | } 106 | } else 107 | #endif 108 | if (_uri.length() && _uri.startsWith("/*.")) { 109 | String uriTemplate = String (_uri); 110 | uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); 111 | if (!request->url().endsWith(uriTemplate)) 112 | return false; 113 | } 114 | else 115 | if (_uri.length() && _uri.endsWith("*")) { 116 | String uriTemplate = String(_uri); 117 | uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); 118 | if (!request->url().startsWith(uriTemplate)) 119 | return false; 120 | } 121 | else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) 122 | return false; 123 | 124 | request->addInterestingHeader("ANY"); 125 | return true; 126 | } 127 | 128 | virtual void handleRequest(AsyncWebServerRequest *request) override final { 129 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 130 | return request->requestAuthentication(); 131 | if(_onRequest) 132 | _onRequest(request); 133 | else 134 | request->send(500); 135 | } 136 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { 137 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 138 | return request->requestAuthentication(); 139 | if(_onUpload) 140 | _onUpload(request, filename, index, data, len, final); 141 | } 142 | virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { 143 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 144 | return request->requestAuthentication(); 145 | if(_onBody) 146 | _onBody(request, data, len, index, total); 147 | } 148 | virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} 149 | }; 150 | 151 | #endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ 152 | -------------------------------------------------------------------------------- /src/WebHandlers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebSrv.h" 22 | #include "WebHandlerImpl.h" 23 | 24 | AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) 25 | : _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr) 26 | { 27 | // Ensure leading '/' 28 | if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; 29 | if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; 30 | 31 | // If path ends with '/' we assume a hint that this is a directory to improve performance. 32 | // However - if it does not end with '/' we, can't assume a file, path can still be a directory. 33 | _isDir = _path[_path.length()-1] == '/'; 34 | 35 | // Remove the trailing '/' so we can handle default file 36 | // Notice that root will be "" not "/" 37 | if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); 38 | if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); 39 | 40 | // Reset stats 41 | _gzipFirst = false; 42 | _gzipStats = 0xF8; 43 | } 44 | 45 | AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ 46 | _isDir = isDir; 47 | return *this; 48 | } 49 | 50 | AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ 51 | _default_file = String(filename); 52 | return *this; 53 | } 54 | 55 | AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ 56 | _cache_control = String(cache_control); 57 | return *this; 58 | } 59 | 60 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ 61 | _last_modified = String(last_modified); 62 | return *this; 63 | } 64 | 65 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ 66 | char result[30]; 67 | strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); 68 | return setLastModified((const char *)result); 69 | } 70 | 71 | #ifdef ESP8266 72 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ 73 | return setLastModified((struct tm *)gmtime(&last_modified)); 74 | } 75 | 76 | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ 77 | time_t last_modified; 78 | if(time(&last_modified) == 0) //time is not yet set 79 | return *this; 80 | return setLastModified(last_modified); 81 | } 82 | #endif 83 | bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ 84 | if(request->method() != HTTP_GET 85 | || !request->url().startsWith(_uri) 86 | || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) 87 | ){ 88 | return false; 89 | } 90 | if (_getFile(request)) { 91 | // We interested in "If-Modified-Since" header to check if file was modified 92 | if (_last_modified.length()) 93 | request->addInterestingHeader("If-Modified-Since"); 94 | 95 | if(_cache_control.length()) 96 | request->addInterestingHeader("If-None-Match"); 97 | 98 | DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); 99 | return true; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) 106 | { 107 | // Remove the found uri 108 | String path = request->url().substring(_uri.length()); 109 | 110 | // 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 '/' 111 | bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); 112 | 113 | path = _path + path; 114 | 115 | // Do we have a file or .gz file 116 | if (!canSkipFileCheck && _fileExists(request, path)) 117 | return true; 118 | 119 | // Can't handle if not default file 120 | if (_default_file.length() == 0) 121 | return false; 122 | 123 | // Try to add default file, ensure there is a trailing '/' ot the path. 124 | if (path.length() == 0 || path[path.length()-1] != '/') 125 | path += "/"; 126 | path += _default_file; 127 | 128 | return _fileExists(request, path); 129 | } 130 | 131 | #ifdef ESP32 132 | #define FILE_IS_REAL(f) (f == true && !f.isDirectory()) 133 | #else 134 | #define FILE_IS_REAL(f) (f == true) 135 | #endif 136 | 137 | bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) 138 | { 139 | bool fileFound = false; 140 | bool gzipFound = false; 141 | 142 | String gzip = path + ".gz"; 143 | 144 | if (_gzipFirst) { 145 | request->_tempFile = _fs.open(gzip, "r"); 146 | gzipFound = FILE_IS_REAL(request->_tempFile); 147 | if (!gzipFound){ 148 | request->_tempFile = _fs.open(path, "r"); 149 | fileFound = FILE_IS_REAL(request->_tempFile); 150 | } 151 | } else { 152 | request->_tempFile = _fs.open(path, "r"); 153 | fileFound = FILE_IS_REAL(request->_tempFile); 154 | if (!fileFound){ 155 | request->_tempFile = _fs.open(gzip, "r"); 156 | gzipFound = FILE_IS_REAL(request->_tempFile); 157 | } 158 | } 159 | 160 | bool found = fileFound || gzipFound; 161 | 162 | if (found) { 163 | // Extract the file name from the path and keep it in _tempObject 164 | size_t pathLen = path.length(); 165 | char * _tempPath = (char*)malloc(pathLen+1); 166 | snprintf(_tempPath, pathLen+1, "%s", path.c_str()); 167 | request->_tempObject = (void*)_tempPath; 168 | 169 | // Calculate gzip statistic 170 | _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); 171 | if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip 172 | else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip 173 | else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first 174 | } 175 | 176 | return found; 177 | } 178 | 179 | uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const 180 | { 181 | uint8_t w = value; 182 | uint8_t n; 183 | for (n=0; w!=0; n++) w&=w-1; 184 | return n; 185 | } 186 | 187 | void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) 188 | { 189 | // Get the filename from request->_tempObject and free it 190 | String filename = String((char*)request->_tempObject); 191 | free(request->_tempObject); 192 | request->_tempObject = NULL; 193 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 194 | return request->requestAuthentication(); 195 | 196 | if (request->_tempFile == true) { 197 | String etag = String(request->_tempFile.size()); 198 | if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { 199 | request->_tempFile.close(); 200 | request->send(304); // Not modified 201 | } else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { 202 | request->_tempFile.close(); 203 | AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified 204 | response->addHeader("Cache-Control", _cache_control); 205 | response->addHeader("ETag", etag); 206 | request->send(response); 207 | } else { 208 | AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); 209 | if (_last_modified.length()) 210 | response->addHeader("Last-Modified", _last_modified); 211 | if (_cache_control.length()){ 212 | response->addHeader("Cache-Control", _cache_control); 213 | response->addHeader("ETag", etag); 214 | } 215 | request->send(response); 216 | } 217 | } else { 218 | request->send(404); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/WebResponseImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ 22 | #define ASYNCWEBSERVERRESPONSEIMPL_H_ 23 | 24 | #ifdef Arduino_h 25 | // arduino is not compatible with std::vector 26 | #undef min 27 | #undef max 28 | #endif 29 | #include 30 | // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. 31 | 32 | class AsyncBasicResponse: public AsyncWebServerResponse { 33 | private: 34 | String _content; 35 | public: 36 | AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); 37 | void _respond(AsyncWebServerRequest *request); 38 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 39 | bool _sourceValid() const { return true; } 40 | }; 41 | 42 | class AsyncAbstractResponse: public AsyncWebServerResponse { 43 | private: 44 | String _head; 45 | // Data is inserted into cache at begin(). 46 | // This is inefficient with vector, but if we use some other container, 47 | // we won't be able to access it as contiguous array of bytes when reading from it, 48 | // so by gaining performance in one place, we'll lose it in another. 49 | std::vector _cache; 50 | size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); 51 | size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); 52 | protected: 53 | AwsTemplateProcessor _callback; 54 | public: 55 | AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); 56 | void _respond(AsyncWebServerRequest *request); 57 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 58 | bool _sourceValid() const { return false; } 59 | virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } 60 | }; 61 | 62 | #ifndef TEMPLATE_PLACEHOLDER 63 | #define TEMPLATE_PLACEHOLDER '%' 64 | #endif 65 | 66 | #define TEMPLATE_PARAM_NAME_LENGTH 32 67 | class AsyncFileResponse: public AsyncAbstractResponse { 68 | using File = fs::File; 69 | using FS = fs::FS; 70 | private: 71 | File _content; 72 | String _path; 73 | void _setContentType(const String& path); 74 | public: 75 | AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 76 | AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 77 | ~AsyncFileResponse(); 78 | bool _sourceValid() const { return !!(_content); } 79 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 80 | }; 81 | 82 | class AsyncStreamResponse: public AsyncAbstractResponse { 83 | private: 84 | Stream *_content; 85 | public: 86 | AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); 87 | bool _sourceValid() const { return !!(_content); } 88 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 89 | }; 90 | 91 | class AsyncCallbackResponse: public AsyncAbstractResponse { 92 | private: 93 | AwsResponseFiller _content; 94 | size_t _filledLength; 95 | public: 96 | AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 97 | bool _sourceValid() const { return !!(_content); } 98 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 99 | }; 100 | 101 | class AsyncChunkedResponse: public AsyncAbstractResponse { 102 | private: 103 | AwsResponseFiller _content; 104 | size_t _filledLength; 105 | public: 106 | AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 107 | bool _sourceValid() const { return !!(_content); } 108 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 109 | }; 110 | 111 | class AsyncProgmemResponse: public AsyncAbstractResponse { 112 | private: 113 | const uint8_t * _content; 114 | size_t _readLength; 115 | public: 116 | AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); 117 | bool _sourceValid() const { return true; } 118 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 119 | }; 120 | 121 | class cbuf; 122 | 123 | class AsyncResponseStream: public AsyncAbstractResponse, public Print { 124 | private: 125 | cbuf *_content; 126 | public: 127 | AsyncResponseStream(const String& contentType, size_t bufferSize); 128 | ~AsyncResponseStream(); 129 | bool _sourceValid() const { return (_state < RESPONSE_END); } 130 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 131 | size_t write(const uint8_t *data, size_t len); 132 | size_t write(uint8_t data); 133 | using Print::write; 134 | }; 135 | 136 | #endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ 137 | -------------------------------------------------------------------------------- /src/WebResponses.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebSrv.h" 22 | #include "WebResponseImpl.h" 23 | #include "cbuf.h" 24 | 25 | // Since ESP8266 does not link memchr by default, here's its implementation. 26 | void* memchr(void* ptr, int ch, size_t count) 27 | { 28 | unsigned char* p = static_cast(ptr); 29 | while(count--) 30 | if(*p++ == static_cast(ch)) 31 | return --p; 32 | return nullptr; 33 | } 34 | 35 | 36 | /* 37 | * Abstract Response 38 | * */ 39 | const char* AsyncWebServerResponse::_responseCodeToString(int code) { 40 | switch (code) { 41 | case 100: return "Continue"; 42 | case 101: return "Switching Protocols"; 43 | case 200: return "OK"; 44 | case 201: return "Created"; 45 | case 202: return "Accepted"; 46 | case 203: return "Non-Authoritative Information"; 47 | case 204: return "No Content"; 48 | case 205: return "Reset Content"; 49 | case 206: return "Partial Content"; 50 | case 300: return "Multiple Choices"; 51 | case 301: return "Moved Permanently"; 52 | case 302: return "Found"; 53 | case 303: return "See Other"; 54 | case 304: return "Not Modified"; 55 | case 305: return "Use Proxy"; 56 | case 307: return "Temporary Redirect"; 57 | case 400: return "Bad Request"; 58 | case 401: return "Unauthorized"; 59 | case 402: return "Payment Required"; 60 | case 403: return "Forbidden"; 61 | case 404: return "Not Found"; 62 | case 405: return "Method Not Allowed"; 63 | case 406: return "Not Acceptable"; 64 | case 407: return "Proxy Authentication Required"; 65 | case 408: return "Request Time-out"; 66 | case 409: return "Conflict"; 67 | case 410: return "Gone"; 68 | case 411: return "Length Required"; 69 | case 412: return "Precondition Failed"; 70 | case 413: return "Request Entity Too Large"; 71 | case 414: return "Request-URI Too Large"; 72 | case 415: return "Unsupported Media Type"; 73 | case 416: return "Requested range not satisfiable"; 74 | case 417: return "Expectation Failed"; 75 | case 500: return "Internal Server Error"; 76 | case 501: return "Not Implemented"; 77 | case 502: return "Bad Gateway"; 78 | case 503: return "Service Unavailable"; 79 | case 504: return "Gateway Time-out"; 80 | case 505: return "HTTP Version not supported"; 81 | default: return ""; 82 | } 83 | } 84 | 85 | AsyncWebServerResponse::AsyncWebServerResponse() 86 | : _code(0) 87 | , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) 88 | , _contentType() 89 | , _contentLength(0) 90 | , _sendContentLength(true) 91 | , _chunked(false) 92 | , _headLength(0) 93 | , _sentLength(0) 94 | , _ackedLength(0) 95 | , _writtenLength(0) 96 | , _state(RESPONSE_SETUP) 97 | { 98 | for(auto header: DefaultHeaders::Instance()) { 99 | _headers.add(new AsyncWebHeader(header->name(), header->value())); 100 | } 101 | } 102 | 103 | AsyncWebServerResponse::~AsyncWebServerResponse(){ 104 | _headers.free(); 105 | } 106 | 107 | void AsyncWebServerResponse::setCode(int code){ 108 | if(_state == RESPONSE_SETUP) 109 | _code = code; 110 | } 111 | 112 | void AsyncWebServerResponse::setContentLength(size_t len){ 113 | if(_state == RESPONSE_SETUP) 114 | _contentLength = len; 115 | } 116 | 117 | void AsyncWebServerResponse::setContentType(const String& type){ 118 | if(_state == RESPONSE_SETUP) 119 | _contentType = type; 120 | } 121 | 122 | void AsyncWebServerResponse::addHeader(const String& name, const String& value){ 123 | _headers.add(new AsyncWebHeader(name, value)); 124 | } 125 | 126 | String AsyncWebServerResponse::_assembleHead(uint8_t version){ 127 | if(version){ 128 | addHeader("Accept-Ranges","none"); 129 | if(_chunked) 130 | addHeader("Transfer-Encoding","chunked"); 131 | } 132 | String out = String(); 133 | int bufSize = 300; 134 | char buf[bufSize]; 135 | 136 | snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code)); 137 | out.concat(buf); 138 | 139 | if(_sendContentLength) { 140 | snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength); 141 | out.concat(buf); 142 | } 143 | if(_contentType.length()) { 144 | snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str()); 145 | out.concat(buf); 146 | } 147 | 148 | for(const auto& header: _headers){ 149 | snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str()); 150 | out.concat(buf); 151 | } 152 | _headers.free(); 153 | 154 | out.concat("\r\n"); 155 | _headLength = out.length(); 156 | return out; 157 | } 158 | 159 | bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } 160 | bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } 161 | bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } 162 | bool AsyncWebServerResponse::_sourceValid() const { return false; } 163 | void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } 164 | size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } 165 | 166 | /* 167 | * String/Code Response 168 | * */ 169 | AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ 170 | _code = code; 171 | _content = content; 172 | _contentType = contentType; 173 | if(_content.length()){ 174 | _contentLength = _content.length(); 175 | if(!_contentType.length()) 176 | _contentType = "text/plain"; 177 | } 178 | addHeader("Connection","close"); 179 | } 180 | 181 | void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ 182 | _state = RESPONSE_HEADERS; 183 | String out = _assembleHead(request->version()); 184 | size_t outLen = out.length(); 185 | size_t space = request->client()->space(); 186 | if(!_contentLength && space >= outLen){ 187 | _writtenLength += request->client()->write(out.c_str(), outLen); 188 | _state = RESPONSE_WAIT_ACK; 189 | } else if(_contentLength && space >= outLen + _contentLength){ 190 | out += _content; 191 | outLen += _contentLength; 192 | _writtenLength += request->client()->write(out.c_str(), outLen); 193 | _state = RESPONSE_WAIT_ACK; 194 | } else if(space && space < outLen){ 195 | String partial = out.substring(0, space); 196 | _content = out.substring(space) + _content; 197 | _contentLength += outLen - space; 198 | _writtenLength += request->client()->write(partial.c_str(), partial.length()); 199 | _state = RESPONSE_CONTENT; 200 | } else if(space > outLen && space < (outLen + _contentLength)){ 201 | size_t shift = space - outLen; 202 | outLen += shift; 203 | _sentLength += shift; 204 | out += _content.substring(0, shift); 205 | _content = _content.substring(shift); 206 | _writtenLength += request->client()->write(out.c_str(), outLen); 207 | _state = RESPONSE_CONTENT; 208 | } else { 209 | _content = out + _content; 210 | _contentLength += outLen; 211 | _state = RESPONSE_CONTENT; 212 | } 213 | } 214 | 215 | size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ 216 | (void)time; 217 | _ackedLength += len; 218 | if(_state == RESPONSE_CONTENT){ 219 | size_t available = _contentLength - _sentLength; 220 | size_t space = request->client()->space(); 221 | //we can fit in this packet 222 | if(space > available){ 223 | _writtenLength += request->client()->write(_content.c_str(), available); 224 | _content = String(); 225 | _state = RESPONSE_WAIT_ACK; 226 | return available; 227 | } 228 | //send some data, the rest on ack 229 | String out = _content.substring(0, space); 230 | _content = _content.substring(space); 231 | _sentLength += space; 232 | _writtenLength += request->client()->write(out.c_str(), space); 233 | return space; 234 | } else if(_state == RESPONSE_WAIT_ACK){ 235 | if(_ackedLength >= _writtenLength){ 236 | _state = RESPONSE_END; 237 | } 238 | } 239 | return 0; 240 | } 241 | 242 | 243 | /* 244 | * Abstract Response 245 | * */ 246 | 247 | AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) 248 | { 249 | // In case of template processing, we're unable to determine real response size 250 | if(callback) { 251 | _contentLength = 0; 252 | _sendContentLength = false; 253 | _chunked = true; 254 | } 255 | } 256 | 257 | void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ 258 | addHeader("Connection","close"); 259 | _head = _assembleHead(request->version()); 260 | _state = RESPONSE_HEADERS; 261 | _ack(request, 0, 0); 262 | } 263 | 264 | size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ 265 | (void)time; 266 | if(!_sourceValid()){ 267 | _state = RESPONSE_FAILED; 268 | request->client()->close(); 269 | return 0; 270 | } 271 | _ackedLength += len; 272 | size_t space = request->client()->space(); 273 | 274 | size_t headLen = _head.length(); 275 | if(_state == RESPONSE_HEADERS){ 276 | if(space >= headLen){ 277 | _state = RESPONSE_CONTENT; 278 | space -= headLen; 279 | } else { 280 | String out = _head.substring(0, space); 281 | _head = _head.substring(space); 282 | _writtenLength += request->client()->write(out.c_str(), out.length()); 283 | return out.length(); 284 | } 285 | } 286 | 287 | if(_state == RESPONSE_CONTENT){ 288 | size_t outLen; 289 | if(_chunked){ 290 | if(space <= 8){ 291 | return 0; 292 | } 293 | outLen = space; 294 | } else if(!_sendContentLength){ 295 | outLen = space; 296 | } else { 297 | outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); 298 | } 299 | 300 | uint8_t *buf = (uint8_t *)malloc(outLen+headLen); 301 | if (!buf) { 302 | // os_printf("_ack malloc %d failed\n", outLen+headLen); 303 | return 0; 304 | } 305 | 306 | if(headLen){ 307 | memcpy(buf, _head.c_str(), _head.length()); 308 | } 309 | 310 | size_t readLen = 0; 311 | 312 | if(_chunked){ 313 | // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. 314 | // See RFC2616 sections 2, 3.6.1. 315 | readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); 316 | if(readLen == RESPONSE_TRY_AGAIN){ 317 | free(buf); 318 | return 0; 319 | } 320 | outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen; 321 | while(outLen < headLen + 4) buf[outLen++] = ' '; 322 | buf[outLen++] = '\r'; 323 | buf[outLen++] = '\n'; 324 | outLen += readLen; 325 | buf[outLen++] = '\r'; 326 | buf[outLen++] = '\n'; 327 | } else { 328 | readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); 329 | if(readLen == RESPONSE_TRY_AGAIN){ 330 | free(buf); 331 | return 0; 332 | } 333 | outLen = readLen + headLen; 334 | } 335 | 336 | if(headLen){ 337 | _head = String(); 338 | } 339 | 340 | if(outLen){ 341 | _writtenLength += request->client()->write((const char*)buf, outLen); 342 | } 343 | 344 | if(_chunked){ 345 | _sentLength += readLen; 346 | } else { 347 | _sentLength += outLen - headLen; 348 | } 349 | 350 | free(buf); 351 | 352 | if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ 353 | _state = RESPONSE_WAIT_ACK; 354 | } 355 | return outLen; 356 | 357 | } else if(_state == RESPONSE_WAIT_ACK){ 358 | if(!_sendContentLength || _ackedLength >= _writtenLength){ 359 | _state = RESPONSE_END; 360 | if(!_chunked && !_sendContentLength) 361 | request->client()->close(true); 362 | } 363 | } 364 | return 0; 365 | } 366 | 367 | size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) 368 | { 369 | // If we have something in cache, copy it to buffer 370 | const size_t readFromCache = std::min(len, _cache.size()); 371 | if(readFromCache) { 372 | memcpy(data, _cache.data(), readFromCache); 373 | _cache.erase(_cache.begin(), _cache.begin() + readFromCache); 374 | } 375 | // If we need to read more... 376 | const size_t needFromFile = len - readFromCache; 377 | const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); 378 | return readFromCache + readFromContent; 379 | } 380 | 381 | size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) 382 | { 383 | if(!_callback) 384 | return _fillBuffer(data, len); 385 | 386 | const size_t originalLen = len; 387 | len = _readDataFromCacheOrContent(data, len); 388 | // Now we've read 'len' bytes, either from cache or from file 389 | // Search for template placeholders 390 | uint8_t* pTemplateStart = data; 391 | while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] 392 | uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; 393 | // temporary buffer to hold parameter name 394 | uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; 395 | String paramName; 396 | // If closing placeholder is found: 397 | if(pTemplateEnd) { 398 | // prepare argument to callback 399 | const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1)); 400 | if(paramNameLength) { 401 | memcpy(buf, pTemplateStart + 1, paramNameLength); 402 | buf[paramNameLength] = 0; 403 | paramName = String(reinterpret_cast(buf)); 404 | } else { // double percent sign encountered, this is single percent sign escaped. 405 | // remove the 2nd percent sign 406 | memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); 407 | len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; 408 | ++pTemplateStart; 409 | } 410 | } 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 411 | memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); 412 | const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); 413 | if(readFromCacheOrContent) { 414 | pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); 415 | if(pTemplateEnd) { 416 | // prepare argument to callback 417 | *pTemplateEnd = 0; 418 | paramName = String(reinterpret_cast(buf)); 419 | // Copy remaining read-ahead data into cache 420 | _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); 421 | pTemplateEnd = &data[len - 1]; 422 | } 423 | else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position 424 | { 425 | // but first, store read file data in cache 426 | _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); 427 | ++pTemplateStart; 428 | } 429 | } 430 | else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position 431 | ++pTemplateStart; 432 | } 433 | else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position 434 | ++pTemplateStart; 435 | if(paramName.length()) { 436 | // call callback and replace with result. 437 | // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. 438 | // Data after pTemplateEnd may need to be moved. 439 | // The first byte of data after placeholder is located at pTemplateEnd + 1. 440 | // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). 441 | const String paramValue(_callback(paramName)); 442 | const char* pvstr = paramValue.c_str(); 443 | const unsigned int pvlen = paramValue.length(); 444 | const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); 445 | // make room for param value 446 | // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store 447 | if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { 448 | _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); 449 | //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end 450 | memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); 451 | len = originalLen; // fix issue with truncated data, not sure if it has any side effects 452 | } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) 453 | //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. 454 | // Move the entire data after the placeholder 455 | memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); 456 | // 3. replace placeholder with actual value 457 | memcpy(pTemplateStart, pvstr, numBytesCopied); 458 | // 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) 459 | if(numBytesCopied < pvlen) { 460 | _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); 461 | } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... 462 | // there is some free room, fill it from cache 463 | const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; 464 | const size_t totalFreeRoom = originalLen - len + roomFreed; 465 | len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; 466 | } else { // result is copied fully; it is longer than placeholder text 467 | const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; 468 | len = std::min(len + roomTaken, originalLen); 469 | } 470 | } 471 | } // while(pTemplateStart) 472 | return len; 473 | } 474 | 475 | 476 | /* 477 | * File Response 478 | * */ 479 | 480 | AsyncFileResponse::~AsyncFileResponse(){ 481 | if(_content) 482 | _content.close(); 483 | } 484 | 485 | void AsyncFileResponse::_setContentType(const String& path){ 486 | if (path.endsWith(".html")) _contentType = "text/html"; 487 | else if (path.endsWith(".htm")) _contentType = "text/html"; 488 | else if (path.endsWith(".css")) _contentType = "text/css"; 489 | else if (path.endsWith(".json")) _contentType = "application/json"; 490 | else if (path.endsWith(".js")) _contentType = "application/javascript"; 491 | else if (path.endsWith(".png")) _contentType = "image/png"; 492 | else if (path.endsWith(".gif")) _contentType = "image/gif"; 493 | else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; 494 | else if (path.endsWith(".ico")) _contentType = "image/x-icon"; 495 | else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; 496 | else if (path.endsWith(".eot")) _contentType = "font/eot"; 497 | else if (path.endsWith(".woff")) _contentType = "font/woff"; 498 | else if (path.endsWith(".woff2")) _contentType = "font/woff2"; 499 | else if (path.endsWith(".ttf")) _contentType = "font/ttf"; 500 | else if (path.endsWith(".xml")) _contentType = "text/xml"; 501 | else if (path.endsWith(".pdf")) _contentType = "application/pdf"; 502 | else if (path.endsWith(".zip")) _contentType = "application/zip"; 503 | else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; 504 | else _contentType = "text/plain"; 505 | } 506 | 507 | AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ 508 | _code = 200; 509 | _path = path; 510 | 511 | if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ 512 | _path = _path+".gz"; 513 | addHeader("Content-Encoding", "gzip"); 514 | _callback = nullptr; // Unable to process zipped templates 515 | _sendContentLength = true; 516 | _chunked = false; 517 | } 518 | 519 | _content = fs.open(_path, "r"); 520 | _contentLength = _content.size(); 521 | 522 | if(contentType == "") 523 | _setContentType(path); 524 | else 525 | _contentType = contentType; 526 | 527 | int filenameStart = path.lastIndexOf('/') + 1; 528 | char buf[26+path.length()-filenameStart]; 529 | char* filename = (char*)path.c_str() + filenameStart; 530 | 531 | if(download) { 532 | // set filename and force download 533 | snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); 534 | } else { 535 | // set filename and force rendering 536 | snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); 537 | } 538 | addHeader("Content-Disposition", buf); 539 | } 540 | 541 | AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ 542 | _code = 200; 543 | _path = path; 544 | 545 | if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ 546 | addHeader("Content-Encoding", "gzip"); 547 | _callback = nullptr; // Unable to process gzipped templates 548 | _sendContentLength = true; 549 | _chunked = false; 550 | } 551 | 552 | _content = content; 553 | _contentLength = _content.size(); 554 | 555 | if(contentType == "") 556 | _setContentType(path); 557 | else 558 | _contentType = contentType; 559 | 560 | int filenameStart = path.lastIndexOf('/') + 1; 561 | char buf[26+path.length()-filenameStart]; 562 | char* filename = (char*)path.c_str() + filenameStart; 563 | 564 | if(download) { 565 | snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); 566 | } else { 567 | snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); 568 | } 569 | addHeader("Content-Disposition", buf); 570 | } 571 | 572 | size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ 573 | return _content.read(data, len); 574 | } 575 | 576 | /* 577 | * Stream Response 578 | * */ 579 | 580 | AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { 581 | _code = 200; 582 | _content = &stream; 583 | _contentLength = len; 584 | _contentType = contentType; 585 | } 586 | 587 | size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ 588 | size_t available = _content->available(); 589 | size_t outLen = (available > len)?len:available; 590 | size_t i; 591 | for(i=0;iread(); 593 | return outLen; 594 | } 595 | 596 | /* 597 | * Callback Response 598 | * */ 599 | 600 | AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { 601 | _code = 200; 602 | _content = callback; 603 | _contentLength = len; 604 | if(!len) 605 | _sendContentLength = false; 606 | _contentType = contentType; 607 | _filledLength = 0; 608 | } 609 | 610 | size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ 611 | size_t ret = _content(data, len, _filledLength); 612 | if(ret != RESPONSE_TRY_AGAIN){ 613 | _filledLength += ret; 614 | } 615 | return ret; 616 | } 617 | 618 | /* 619 | * Chunked Response 620 | * */ 621 | 622 | AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { 623 | _code = 200; 624 | _content = callback; 625 | _contentLength = 0; 626 | _contentType = contentType; 627 | _sendContentLength = false; 628 | _chunked = true; 629 | _filledLength = 0; 630 | } 631 | 632 | size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ 633 | size_t ret = _content(data, len, _filledLength); 634 | if(ret != RESPONSE_TRY_AGAIN){ 635 | _filledLength += ret; 636 | } 637 | return ret; 638 | } 639 | 640 | /* 641 | * Progmem Response 642 | * */ 643 | 644 | AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { 645 | _code = code; 646 | _content = content; 647 | _contentType = contentType; 648 | _contentLength = len; 649 | _readLength = 0; 650 | } 651 | 652 | size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ 653 | size_t left = _contentLength - _readLength; 654 | if (left > len) { 655 | memcpy_P(data, _content + _readLength, len); 656 | _readLength += len; 657 | return len; 658 | } 659 | memcpy_P(data, _content + _readLength, left); 660 | _readLength += left; 661 | return left; 662 | } 663 | 664 | 665 | /* 666 | * Response Stream (You can print/write/printf to it, up to the contentLen bytes) 667 | * */ 668 | 669 | AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ 670 | _code = 200; 671 | _contentLength = 0; 672 | _contentType = contentType; 673 | _content = new cbuf(bufferSize); 674 | } 675 | 676 | AsyncResponseStream::~AsyncResponseStream(){ 677 | delete _content; 678 | } 679 | 680 | size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ 681 | return _content->read((char*)buf, maxLen); 682 | } 683 | 684 | size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ 685 | if(_started()) 686 | return 0; 687 | 688 | if(len > _content->room()){ 689 | size_t needed = len - _content->room(); 690 | _content->resizeAdd(needed); 691 | } 692 | size_t written = _content->write((const char*)data, len); 693 | _contentLength += written; 694 | return written; 695 | } 696 | 697 | size_t AsyncResponseStream::write(uint8_t data){ 698 | return write(&data, 1); 699 | } 700 | -------------------------------------------------------------------------------- /src/WebServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebSrv.h" 22 | #include "WebHandlerImpl.h" 23 | 24 | bool ON_STA_FILTER(AsyncWebServerRequest *request) { 25 | return WiFi.localIP() == request->client()->localIP(); 26 | } 27 | 28 | bool ON_AP_FILTER(AsyncWebServerRequest *request) { 29 | return WiFi.localIP() != request->client()->localIP(); 30 | } 31 | 32 | 33 | AsyncWebServer::AsyncWebServer(uint16_t port) 34 | : _server(port) 35 | , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) 36 | , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) 37 | { 38 | _catchAllHandler = new AsyncCallbackWebHandler(); 39 | if(_catchAllHandler == NULL) 40 | return; 41 | _server.onClient([](void *s, AsyncClient* c){ 42 | if(c == NULL) 43 | return; 44 | c->setRxTimeout(3); 45 | AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); 46 | if(r == NULL){ 47 | c->close(true); 48 | c->free(); 49 | delete c; 50 | } 51 | }, this); 52 | } 53 | 54 | AsyncWebServer::~AsyncWebServer(){ 55 | reset(); 56 | end(); 57 | if(_catchAllHandler) delete _catchAllHandler; 58 | } 59 | 60 | AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ 61 | _rewrites.add(rewrite); 62 | return *rewrite; 63 | } 64 | 65 | bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ 66 | return _rewrites.remove(rewrite); 67 | } 68 | 69 | AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ 70 | return addRewrite(new AsyncWebRewrite(from, to)); 71 | } 72 | 73 | AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ 74 | _handlers.add(handler); 75 | return *handler; 76 | } 77 | 78 | bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ 79 | return _handlers.remove(handler); 80 | } 81 | 82 | void AsyncWebServer::begin(){ 83 | _server.setNoDelay(true); 84 | _server.begin(); 85 | } 86 | 87 | void AsyncWebServer::end(){ 88 | _server.end(); 89 | } 90 | 91 | #if ASYNC_TCP_SSL_ENABLED 92 | void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ 93 | _server.onSslFileRequest(cb, arg); 94 | } 95 | 96 | void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ 97 | _server.beginSecure(cert, key, password); 98 | } 99 | #endif 100 | 101 | void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ 102 | delete request; 103 | } 104 | 105 | void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ 106 | for(const auto& r: _rewrites){ 107 | if (r->match(request)){ 108 | request->_url = r->toUrl(); 109 | request->_addGetParams(r->params()); 110 | } 111 | } 112 | } 113 | 114 | void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ 115 | for(const auto& h: _handlers){ 116 | if (h->filter(request) && h->canHandle(request)){ 117 | request->setHandler(h); 118 | return; 119 | } 120 | } 121 | 122 | request->addInterestingHeader("ANY"); 123 | request->setHandler(_catchAllHandler); 124 | } 125 | 126 | 127 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ 128 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 129 | handler->setUri(uri); 130 | handler->setMethod(method); 131 | handler->onRequest(onRequest); 132 | handler->onUpload(onUpload); 133 | handler->onBody(onBody); 134 | addHandler(handler); 135 | return *handler; 136 | } 137 | 138 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ 139 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 140 | handler->setUri(uri); 141 | handler->setMethod(method); 142 | handler->onRequest(onRequest); 143 | handler->onUpload(onUpload); 144 | addHandler(handler); 145 | return *handler; 146 | } 147 | 148 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ 149 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 150 | handler->setUri(uri); 151 | handler->setMethod(method); 152 | handler->onRequest(onRequest); 153 | addHandler(handler); 154 | return *handler; 155 | } 156 | 157 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ 158 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 159 | handler->setUri(uri); 160 | handler->onRequest(onRequest); 161 | addHandler(handler); 162 | return *handler; 163 | } 164 | 165 | AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ 166 | AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); 167 | addHandler(handler); 168 | return *handler; 169 | } 170 | 171 | void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ 172 | _catchAllHandler->onRequest(fn); 173 | } 174 | 175 | void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ 176 | _catchAllHandler->onUpload(fn); 177 | } 178 | 179 | void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ 180 | _catchAllHandler->onBody(fn); 181 | } 182 | 183 | void AsyncWebServer::reset(){ 184 | _rewrites.free(); 185 | _handlers.free(); 186 | 187 | if (_catchAllHandler != NULL){ 188 | _catchAllHandler->onRequest(NULL); 189 | _catchAllHandler->onUpload(NULL); 190 | _catchAllHandler->onBody(NULL); 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /src/edit.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP Editor 6 | 135 | 609 | 610 | 618 | 619 | 620 |
621 |
622 |
623 |
624 | 625 | 626 | 627 | 628 | --------------------------------------------------------------------------------