diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 867149351..d9d6fdd35 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -21,6 +21,10 @@ jobs: sketch-names: TestFile.ino arduino-board-fqbn: esp32:esp32:esp32s2 platform-url: https://github.com/espressif/arduino-esp32/releases/download/2.0.10/package_esp32_dev_index.json + + - name: Show Arduino dir structure + run: | + find /home/runner/.arduino15/packages/esp32/hardware/ - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 @@ -162,7 +166,6 @@ jobs: - name: Modify platform.txt run: | - echo "Chicken" for i in $(find /home/runner/.arduino15/packages/esp32/hardware/esp32/ -name "platform.txt"); do sed -i 's/compiler.c.elf.libs.esp32c3=/compiler.c.elf.libs.esp32c3=-zmuldefs /' "$i" sed -i 's/compiler.c.elf.libs.esp32s3=/compiler.c.elf.libs.esp32s3=-zmuldefs /' "$i" diff --git a/esp32_marauder/Assets.h b/esp32_marauder/Assets.h index 583638791..c52dd11d7 100644 --- a/esp32_marauder/Assets.h +++ b/esp32_marauder/Assets.h @@ -206,7 +206,7 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0xE7, 0xE7, 0x0B, 0xD0, 0xFD, 0xBF, 0xFF, 0xFF} }; -#ifndef MARAUDER_MINI +/*#ifndef MARAUDER_MINI static const uint8_t MarauderTitle[] PROGMEM = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xFF, 0xE1, 0x00, 0x22, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, @@ -1057,7 +1057,7 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x3F, 0xFF, 0xD9}; - /*static const uint8_t MarauderTitle[] PROGMEM = { + *//*static const uint8_t MarauderTitle[] PROGMEM = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xff, 0xe1, 0x00, 0x66, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, @@ -1455,8 +1455,9 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0xc4, 0x8b, 0x1c, 0x7b, 0xe4, 0x66, 0x6d, 0xaa, 0xbc, 0x05, 0xce, 0x00, 0xe0, 0x50, 0x07, 0x96, 0xd1, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x01, 0xff, 0xd9 };*/ -#endif +//#endif +/* #ifdef MARAUDER_MINI const uint8_t MarauderTitle[] PROGMEM = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, @@ -1832,5 +1833,6 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0x28, 0x00, 0xA2, 0x80, 0x0A, 0x28, 0x03, 0xFF, 0xD9}; #endif +*/ #endif diff --git a/esp32_marauder/Display.cpp b/esp32_marauder/Display.cpp index f150e5c29..d23776ac3 100644 --- a/esp32_marauder/Display.cpp +++ b/esp32_marauder/Display.cpp @@ -422,6 +422,7 @@ void Display::scrollAddress(uint16_t vsp) { // JPEG_functions +/* void Display::drawJpeg(const char *filename, int xpos, int ypos) { // Open the named file (the Jpeg decoder library will close it after rendering image) @@ -452,6 +453,7 @@ void Display::drawJpeg(const char *filename, int xpos, int ypos) { // Serial.println(F("Jpeg file format not supported!")); //} } +*/ /*void Display::setupDraw() { this->tft.drawLine(0, 0, 10, 0, TFT_MAGENTA); diff --git a/esp32_marauder/Display.h b/esp32_marauder/Display.h index 93976515c..4526c9cb0 100644 --- a/esp32_marauder/Display.h +++ b/esp32_marauder/Display.h @@ -110,7 +110,7 @@ class Display void buildBanner(String msg, int xpos); void clearScreen(); void displayBuffer(bool do_clear = false); - void drawJpeg(const char *filename, int xpos, int ypos); + //void drawJpeg(const char *filename, int xpos, int ypos); void getTouchWhileFunction(bool pressed); void initScrollValues(bool tte = false); void jpegInfo(); diff --git a/esp32_marauder/MenuFunctions.cpp b/esp32_marauder/MenuFunctions.cpp index ae49925ce..ba4b8b91b 100644 --- a/esp32_marauder/MenuFunctions.cpp +++ b/esp32_marauder/MenuFunctions.cpp @@ -1222,6 +1222,7 @@ void MenuFunctions::displaySetting(String key, Menu* menu, int index) { void MenuFunctions::RunSetup() { extern LinkedList* access_points; + extern LinkedList* stations; this->disable_touch = false; @@ -1257,6 +1258,9 @@ void MenuFunctions::RunSetup() wifiAttackMenu.list = new LinkedList(); wifiGeneralMenu.list = new LinkedList(); wifiAPMenu.list = new LinkedList(); + #ifndef HAS_ILI9341 + wifiStationMenu.list = new LinkedList(); + #endif // WiFi HTML menu stuff htmlMenu.list = new LinkedList(); @@ -1312,6 +1316,9 @@ void MenuFunctions::RunSetup() clearSSIDsMenu.name = text_table1[28]; clearAPsMenu.name = text_table1[29]; wifiAPMenu.name = "Access Points"; + #ifndef HAS_ILI9341 + wifiStationMenu.name = "Select Stations"; + #endif #ifdef HAS_GPS gpsInfoMenu.name = "GPS Data"; #endif @@ -1533,12 +1540,14 @@ void MenuFunctions::RunSetup() wifi_scan_obj.StartScan(LV_ADD_SSID, TFT_RED); addAPGFX(); }); + // Select Stations on OG this->addNodes(&wifiGeneralMenu, text_table1[61], TFT_LIGHTGREY, NULL, KEYBOARD_ICO, [this](){ display_obj.clearScreen(); wifi_scan_obj.currentScanMode = LV_ADD_SSID; wifi_scan_obj.StartScan(LV_ADD_SSID, TFT_RED); addStationGFX(); }); + // Select Evil Portal Files on OG this->addNodes(&wifiGeneralMenu, "Select EP HTML File", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ display_obj.clearScreen(); wifi_scan_obj.currentScanMode = LV_ADD_SSID; @@ -1621,11 +1630,11 @@ void MenuFunctions::RunSetup() current_menu->list->set(i + 1, new_node); // Change selection status of button key - if (new_ap.selected) { - this->buttonSelected(i + 1); - } else { - this->buttonNotSelected(i + 1); - } + //if (new_ap.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} access_points->set(i, new_ap); }, access_points->get(i).selected); } @@ -1636,6 +1645,115 @@ void MenuFunctions::RunSetup() this->addNodes(&wifiAPMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { this->changeMenu(wifiAPMenu.parentMenu); }); + + // Select Stations on Mini v1 + /* + this->addNodes(&wifiGeneralMenu, "Select Stations", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ + wifiStationMenu.list->clear(); + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); + int menu_limit; + + // Find out how many buttons we will need + if (stations->size() <= BUTTON_ARRAY_LEN) + menu_limit = stations->size(); + else + menu_limit = BUTTON_ARRAY_LEN; + + // Load buttons with stations + for (int i = 0; i < stations->size(); i++) { + + // Check if there is even space left + if (current_menu->list->size() >= menu_limit) + break; + + int cur_ap_sta = i; + + this->addNodes(&wifiStationMenu, wifi_scan_obj.macToString(stations->get(cur_ap_sta)), TFT_CYAN, NULL, KEYBOARD_ICO, [this, i, cur_ap_sta](){ + Station new_sta = stations->get(cur_ap_sta); + new_sta.selected = !stations->get(cur_ap_sta).selected; + + // Change selection status of menu node + MenuNode new_node = current_menu->list->get(i + 1); + new_node.selected = !current_menu->list->get(i + 1).selected; + current_menu->list->set(i + 1, new_node); + + // Change selection status of button key + //if (new_sta.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} + + stations->set(cur_ap_sta, new_sta); + }, stations->get(cur_ap_sta).selected); + } + this->changeMenu(&wifiStationMenu); + }); + */ + + // Select Stations on Mini v2 + this->addNodes(&wifiGeneralMenu, "Select Stations", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ + wifiAPMenu.list->clear(); + this->addNodes(&wifiAPMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiAPMenu.parentMenu); + }); + + int menu_limit; + + if (access_points->size() <= BUTTON_ARRAY_LEN) + menu_limit = access_points->size(); + else + menu_limit = BUTTON_ARRAY_LEN; + + for (int i = 0; i < menu_limit - 1; i++) { + wifiStationMenu.list->clear(); + this->addNodes(&wifiAPMenu, access_points->get(i).essid, TFT_CYAN, NULL, KEYBOARD_ICO, [this, i](){ + + wifiStationMenu.list->clear(); + + // Add back button to the APs + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); + + // Add the AP's stations to the specific AP menu + for (int x = 0; x < access_points->get(i).stations->size(); x++) { + int cur_ap_sta = access_points->get(i).stations->get(x); + + this->addNodes(&wifiStationMenu, wifi_scan_obj.macToString(stations->get(cur_ap_sta)), TFT_CYAN, NULL, KEYBOARD_ICO, [this, i, cur_ap_sta, x](){ + Station new_sta = stations->get(cur_ap_sta); + new_sta.selected = !stations->get(cur_ap_sta).selected; + + // Change selection status of menu node + MenuNode new_node = current_menu->list->get(x + 1); + new_node.selected = !current_menu->list->get(x + 1).selected; + current_menu->list->set(x + 1, new_node); + + // Change selection status of button key + //if (new_sta.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} + + stations->set(cur_ap_sta, new_sta); + }, stations->get(cur_ap_sta).selected); + } + + // Final change menu to the menu of Stations + this->changeMenu(&wifiStationMenu); + + }, false); + } + this->changeMenu(&wifiAPMenu); + }); + + wifiStationMenu.parentMenu = &wifiAPMenu; + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); #endif // Build generate ssids menu diff --git a/esp32_marauder/MenuFunctions.h b/esp32_marauder/MenuFunctions.h index 0f225d929..a41a7395b 100644 --- a/esp32_marauder/MenuFunctions.h +++ b/esp32_marauder/MenuFunctions.h @@ -148,6 +148,9 @@ class MenuFunctions Menu wifiAttackMenu; Menu wifiGeneralMenu; Menu wifiAPMenu; + #ifndef HAS_ILI9341 + Menu wifiStationMenu; + #endif // WiFi General Menu Menu htmlMenu; diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index b19320ebb..148ccf73e 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -305,6 +305,14 @@ WiFiScan::WiFiScan() { } +String WiFiScan::macToString(const Station& station) { + char macStr[18]; // 6 pairs of hex digits + 5 colons + null terminator + snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", + station.mac[0], station.mac[1], station.mac[2], + station.mac[3], station.mac[4], station.mac[5]); + return String(macStr); +} + void WiFiScan::RunSetup() { if (ieee80211_raw_frame_sanity_check(31337, 0, 0) == 1) this->wsl_bypass_enabled = true; diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index b6145530e..5b5ec6074 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -400,6 +400,7 @@ class WiFiScan bool save_serial = false; void startPcap(String file_name); void startLog(String file_name); + String macToString(const Station& station); static void getMAC(char *addr, uint8_t* data, uint16_t offset); static void pwnSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); diff --git a/esp32_marauder/configs.h b/esp32_marauder/configs.h index f097218c8..5fe5a4a6f 100644 --- a/esp32_marauder/configs.h +++ b/esp32_marauder/configs.h @@ -21,7 +21,7 @@ //#define MARAUDER_REV_FEATHER //// END BOARD TARGETS - #define MARAUDER_VERSION "v0.13.10" + #define MARAUDER_VERSION "v0.13.11" //// HARDWARE NAMES #ifdef MARAUDER_M5STICKC diff --git a/esp32_marauder/esp32_marauder.ino b/esp32_marauder/esp32_marauder.ino index a974e19e9..a310ac156 100644 --- a/esp32_marauder/esp32_marauder.ino +++ b/esp32_marauder/esp32_marauder.ino @@ -192,6 +192,7 @@ void setup() backlightOff(); // Draw the title screen + /* #ifdef HAS_SCREEN #ifndef MARAUDER_MINI display_obj.drawJpeg("/marauder3L.jpg", 0 , 0); // 240 x 320 image @@ -199,6 +200,7 @@ void setup() display_obj.drawJpeg("/marauder3L.jpg", 0, 0); #endif #endif + */ #ifdef HAS_SCREEN #ifndef MARAUDER_MINI @@ -210,6 +212,7 @@ void setup() #endif #endif + backlightOn(); // Need this #ifdef HAS_SCREEN diff --git a/libraries/ESPAsyncWebServer/LICENSE b/libraries/ESPAsyncWebServer/LICENSE new file mode 100644 index 000000000..153d416dc --- /dev/null +++ b/libraries/ESPAsyncWebServer/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/README.md b/libraries/ESPAsyncWebServer/README.md new file mode 100644 index 000000000..b10865c7d --- /dev/null +++ b/libraries/ESPAsyncWebServer/README.md @@ -0,0 +1,87 @@ +# ESP Async WebServer + +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) + +Asynchronous HTTP and WebSocket Server Library for ESP32. +Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. + +This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. + +## Changes in this fork + +- Removed SPIFFSEditor +- Deployed in PlatformIO registry and Arduino IDE library manager +- CI +- Some code cleanup +- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client +- `write()` function public in `AsyncEventSource.h` +- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. +- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client +- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. +- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time +- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) +- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler +- Added `setAuthentication(const String& username, const String& password)` +- Added `StreamConcat` example to shoiw how to stream multiple files in one response +- Remove filename after inline in Content-Disposition header according to RFC2183 +- Depends on `mathieucarbou/Async TCP @ ^3.1.4` +- Arduino 3 / ESP-IDF 5.1 compatibility +- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` +- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full + +## Documentation + +Usage and API stays the same as the original library. +Please look at the original libraries for more examples and documentation. + +[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) + +## `AsyncWebSocketMessageBuffer` and `makeBuffer()` + +The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. + +This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. +So you have the choice of which API to use. +I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient. + +Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); +} +``` + +Here is an example for serializing a Json document in a more optimized way, and compatible with both forks: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + +#if defined(ASYNCWEBSERVER_FORK_mathieucarbou) + + // this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project + auto buffer = std::make_shared>(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->data(), len); + _ws->textAll(std::move(buffer)); + +#else + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); + +#endif +} +``` diff --git a/libraries/ESPAsyncWebServer/docs/_config.yml b/libraries/ESPAsyncWebServer/docs/_config.yml new file mode 100644 index 000000000..532070ba6 --- /dev/null +++ b/libraries/ESPAsyncWebServer/docs/_config.yml @@ -0,0 +1,8 @@ +# bundle exec jekyll serve --host=0.0.0.0 + +title: ESP Async WebServer +description: "Asynchronous HTTP and WebSocket Server Library for ESP32" +remote_theme: pages-themes/cayman@v0.2.0 +plugins: + - jekyll-remote-theme + \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/docs/index.md b/libraries/ESPAsyncWebServer/docs/index.md new file mode 100644 index 000000000..b10865c7d --- /dev/null +++ b/libraries/ESPAsyncWebServer/docs/index.md @@ -0,0 +1,87 @@ +# ESP Async WebServer + +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) + +Asynchronous HTTP and WebSocket Server Library for ESP32. +Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. + +This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. + +## Changes in this fork + +- Removed SPIFFSEditor +- Deployed in PlatformIO registry and Arduino IDE library manager +- CI +- Some code cleanup +- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client +- `write()` function public in `AsyncEventSource.h` +- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. +- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client +- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. +- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time +- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) +- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler +- Added `setAuthentication(const String& username, const String& password)` +- Added `StreamConcat` example to shoiw how to stream multiple files in one response +- Remove filename after inline in Content-Disposition header according to RFC2183 +- Depends on `mathieucarbou/Async TCP @ ^3.1.4` +- Arduino 3 / ESP-IDF 5.1 compatibility +- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` +- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full + +## Documentation + +Usage and API stays the same as the original library. +Please look at the original libraries for more examples and documentation. + +[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) + +## `AsyncWebSocketMessageBuffer` and `makeBuffer()` + +The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. + +This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. +So you have the choice of which API to use. +I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient. + +Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); +} +``` + +Here is an example for serializing a Json document in a more optimized way, and compatible with both forks: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + +#if defined(ASYNCWEBSERVER_FORK_mathieucarbou) + + // this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project + auto buffer = std::make_shared>(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->data(), len); + _ws->textAll(std::move(buffer)); + +#else + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); + +#endif +} +``` diff --git a/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino b/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino new file mode 100644 index 000000000..c06dccd83 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino @@ -0,0 +1,54 @@ +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +DNSServer dnsServer; +AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { +public: + CaptiveRequestHandler() {} + virtual ~CaptiveRequestHandler() {} + + bool canHandle(__unused AsyncWebServerRequest *request){ + //request->addInterestingHeader("ANY"); + return true; + } + + void handleRequest(AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

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

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

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); + response->print(""); + request->send(response); + } +}; + + +void setup(){ + Serial.begin(115200); + Serial.println(); + Serial.println("Configuring access point..."); + + if (!WiFi.softAP("esp-captive")) { + Serial.println("Soft AP creation failed."); + while (1); + } + + dnsServer.start(53, "*", WiFi.softAPIP()); + server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP + //more handlers... + server.begin(); +} + +void loop(){ + dnsServer.processNextRequest(); +} diff --git a/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino b/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino new file mode 100644 index 000000000..90b6f19a0 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino @@ -0,0 +1,108 @@ +// Reproduced issue https://github.com/mathieucarbou/ESPAsyncWebServer/issues/26 + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +DNSServer dnsServer; +AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { + public: + CaptiveRequestHandler() {} + virtual ~CaptiveRequestHandler() {} + + bool canHandle(__unused AsyncWebServerRequest* request) { + // request->addInterestingHeader("ANY"); + return true; + } + + void handleRequest(AsyncWebServerRequest* request) { + AsyncResponseStream* response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

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

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

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); + response->print(""); + request->send(response); + } +}; + +bool hit1 = false; +bool hit2 = false; + +void setup() { + Serial.begin(115200); + + server + .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + Serial.println("Captive portal request..."); + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); + request->send(200, "text/plain", "This is the captive portal"); + hit1 = true; + }) + .setFilter(ON_AP_FILTER); + + server + .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + Serial.println("Website request..."); + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); + request->send(200, "text/plain", "This is the website"); + hit2 = true; + }) + .setFilter(ON_STA_FILTER); + + // assert(WiFi.softAP("esp-captive-portal")); + // dnsServer.start(53, "*", WiFi.softAPIP()); + // server.begin(); + // Serial.println("Captive portal started!"); + + // while (!hit1) { + // dnsServer.processNextRequest(); + // yield(); + // } + // delay(1000); // Wait for the client to process the response + + // Serial.println("Captive portal opened, stopping it and connecting to WiFi..."); + // dnsServer.stop(); + // WiFi.softAPdisconnect(); + + WiFi.persistent(false); + WiFi.begin("IoT"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + } + Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString()); + server.begin(); + + // while (!hit2) { + // delay(10); + // } + // delay(1000); // Wait for the client to process the response + // ESP.restart(); +} + +void loop() { +} diff --git a/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino b/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino new file mode 100644 index 000000000..bdbcf60dc --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino @@ -0,0 +1,74 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include + +AsyncWebServer server(80); + +const char* ssid = "YOUR_SSID"; +const char* password = "YOUR_PASSWORD"; + +const char* PARAM_MESSAGE = "message"; + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + +void setup() { + + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Hello, world"); + }); + + // Send a GET request to /get?message= + server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { + String message; + if (request->hasParam(PARAM_MESSAGE)) { + message = request->getParam(PARAM_MESSAGE)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, GET: " + message); + }); + + // Send a POST request to /post with a form field message set to + server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ + String message; + if (request->hasParam(PARAM_MESSAGE, true)) { + message = request->getParam(PARAM_MESSAGE, true)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, POST: " + message); + }); + + server.onNotFound(notFound); + + server.begin(); +} + +void loop() { +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h new file mode 100644 index 000000000..1a53f2cd9 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class StreamConcat : public Stream { + public: + StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {} + + size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; } + size_t write(__unused uint8_t c) override { return 0; } + void flush() override {} + + int available() override { return _s1->available() + _s2->available(); } + + int read() override { + int c = _s1->read(); + return c != -1 ? c : _s2->read(); + } + + size_t readBytes(char* buffer, size_t length) override { + size_t count = _s1->readBytes(buffer, length); + return count > 0 ? count : _s2->readBytes(buffer, length); + } + + int peek() override { + int c = _s1->peek(); + return c != -1 ? c : _s2->peek(); + } + + private: + Stream* _s1; + Stream* _s2; +}; diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino new file mode 100644 index 000000000..d5621f574 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino @@ -0,0 +1,73 @@ +#include +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "StreamConcat.h" +#include "StreamString.h" +#include +#include + +DNSServer dnsServer; +AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + + LittleFS.begin(); + + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); + dnsServer.start(53, "*", WiFi.softAPIP()); + + File file1 = LittleFS.open("/header.html", "w"); + file1.print("ESP Captive Portal"); + file1.close(); + + File file2 = LittleFS.open("/body.html", "w"); + file2.print("

Welcome to ESP Captive Portal

"); + file2.close(); + + File file3 = LittleFS.open("/footer.html", "w"); + file3.print(""); + file3.close(); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + File header = LittleFS.open("/header.html", "r"); + File body = LittleFS.open("/body.html", "r"); + StreamConcat stream1(&header, &body); + + StreamString content; + content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); + StreamConcat stream2 = StreamConcat(&stream1, &content); + + File footer = LittleFS.open("/footer.html", "r"); + StreamConcat stream3 = StreamConcat(&stream2, &footer); + + request->send(stream3, "text/html", stream3.available()); + header.close(); + body.close(); + footer.close(); + }); + + server.onNotFound([](AsyncWebServerRequest* request) { + request->send(404, "text/plain", "Not found"); + }); + + server.begin(); +} + +uint32_t last = 0; + +void loop() { + // dnsServer.processNextRequest(); + + if (millis() - last > 2000) { + Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); + last = millis(); + } +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h new file mode 100644 index 000000000..f392b4525 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +class StreamString : public Stream { + public: + size_t write(const uint8_t* p, size_t n) override { return _buffer.concat(reinterpret_cast(p), n) ? n : 0; } + size_t write(uint8_t c) override { return _buffer.concat(static_cast(c)) ? 1 : 0; } + void flush() override {} + + int available() override { return static_cast(_buffer.length()); } + + int read() override { + if (_buffer.length() == 0) + return -1; + char c = _buffer[0]; + _buffer.remove(0, 1); + return c; + } + + size_t readBytes(char* buffer, size_t length) override { + if (length > _buffer.length()) + length = _buffer.length(); + // Don't use _str.ToCharArray() because it inserts a terminator + memcpy(buffer, _buffer.c_str(), length); + _buffer.remove(0, static_cast(length)); + return length; + } + + int peek() override { return _buffer.length() > 0 ? _buffer[0] : -1; } + + const String& buffer() const { return _buffer; } + + private: + String _buffer; +}; diff --git a/libraries/ESPAsyncWebServer/library.json b/libraries/ESPAsyncWebServer/library.json new file mode 100644 index 000000000..539ff5d45 --- /dev/null +++ b/libraries/ESPAsyncWebServer/library.json @@ -0,0 +1,54 @@ +{ + "name": "ESP Async WebServer", + "version": "2.10.4", + "description": "Asynchronous HTTP and WebSocket Server Library for ESP32. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", + "keywords": "http,async,websocket,webserver", + "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", + "repository": { + "type": "git", + "url": "https://github.com/mathieucarbou/ESPAsyncWebServer.git" + }, + "authors": [ + { + "name": "Hristo Gochkov" + }, + { + "name": "Mathieu Carbou", + "maintainer": true + } + ], + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": [ + "espressif32", + "espressif8266" + ], + "dependencies": [ + { + "owner": "mathieucarbou", + "name": "Async TCP", + "version": "^3.1.4", + "platforms": "espressif32" + }, + { + "owner": "esphome", + "name": "ESPAsyncTCP-esphome", + "version": "^2.0.0", + "platforms": "espressif8266" + }, + { + "name": "Hash", + "platforms": "espressif8266" + } + ], + "export": { + "include": [ + "examples", + "src", + "library.json", + "library.properties", + "LICENSE", + "README.md" + ] + } +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/library.properties b/libraries/ESPAsyncWebServer/library.properties new file mode 100644 index 000000000..fc68a1c3f --- /dev/null +++ b/libraries/ESPAsyncWebServer/library.properties @@ -0,0 +1,10 @@ +name=ESP Async WebServer +version=2.10.4 +author=Me-No-Dev +maintainer=Mathieu Carbou +sentence=Asynchronous HTTP and WebSocket Server Library for ESP32 +paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc +category=Other +url=https://github.com/mathieucarbou/ESPAsyncWebServer +architectures=esp8266,esp32 +license=LGPL-3.0 diff --git a/libraries/ESPAsyncWebServer/platformio.ini b/libraries/ESPAsyncWebServer/platformio.ini new file mode 100644 index 000000000..eff66778f --- /dev/null +++ b/libraries/ESPAsyncWebServer/platformio.ini @@ -0,0 +1,44 @@ +[env] +framework = arduino +build_flags = + -Wall -Wextra + -D CONFIG_ARDUHAL_LOG_COLORS + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE +lib_deps = + bblanchon/ArduinoJson @ 7.0.4 + mathieucarbou/Async TCP @ ^3.1.4 + ; https://github.com/mathieucarbou/AsyncTCP + ; https://github.com/me-no-dev/AsyncTCP + esphome/ESPAsyncTCP-esphome @ 2.0.0 +upload_protocol = esptool +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder, log2file + +[platformio] +lib_dir = . +; src_dir = examples/CaptivePortal +; src_dir = examples/SimpleServer +src_dir = examples/StreamFiles +; src_dir = examples/Filters + +[env:arduino] +platform = espressif32 +board = esp32dev + +[env:arduino-2] +platform = espressif32@6.7.0 +board = esp32dev + +[env:arduino-3] +platform = espressif32 +platform_packages= + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.0 + platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.0/esp32-arduino-libs-3.0.0.zip +board = esp32dev + +[env:esp8266] +platform = espressif8266 +board = huzzah +lib_deps = + bblanchon/ArduinoJson @ 7.0.4 + esphome/ESPAsyncTCP-esphome @ 2.0.0 diff --git a/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp b/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp new file mode 100644 index 000000000..3c0e390b7 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp @@ -0,0 +1,412 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncEventSource.h" +#ifndef ESP8266 + #include +#endif + +static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev; + + if(reconnect){ + ev += F("retry: "); + ev += reconnect; + ev += F("\r\n"); + } + + if(id){ + ev += F("id: "); + ev += String(id); + ev += F("\r\n"); + } + + if(event != NULL){ + ev += F("event: "); + ev += String(event); + ev += F("\r\n"); + } + + if(message != NULL){ + size_t messageLen = strlen(message); + char * lineStart = (char *)message; + char * lineEnd; + do { + char * nextN = strchr(lineStart, '\n'); + char * nextR = strchr(lineStart, '\r'); + if(nextN == NULL && nextR == NULL){ + size_t llen = ((char *)message + messageLen) - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += F("data: "); + ev += ldata; + ev += F("\r\n\r\n"); + free(ldata); + } + lineStart = (char *)message + messageLen; + } else { + char * nextLine = NULL; + if(nextN != NULL && nextR != NULL){ + if(nextR < nextN){ + lineEnd = nextR; + if(nextN == (nextR + 1)) + nextLine = nextN + 1; + else + nextLine = nextR + 1; + } else { + lineEnd = nextN; + if(nextR == (nextN + 1)) + nextLine = nextR + 1; + else + nextLine = nextN + 1; + } + } else if(nextN != NULL){ + lineEnd = nextN; + nextLine = nextN + 1; + } else { + lineEnd = nextR; + nextLine = nextR + 1; + } + + size_t llen = lineEnd - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += F("data: "); + ev += ldata; + ev += F("\r\n"); + free(ldata); + } + lineStart = nextLine; + if(lineStart == ((char *)message + messageLen)) + ev += F("\r\n"); + } + } while(lineStart < ((char *)message + messageLen)); + } + + return ev; +} + +// Message + +AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) +: _data(nullptr), _len(len), _sent(0), _acked(0) +{ + _data = (uint8_t*)malloc(_len+1); + if(_data == nullptr){ + _len = 0; + } else { + memcpy(_data, data, len); + _data[_len] = 0; + } +} + +AsyncEventSourceMessage::~AsyncEventSourceMessage() { + if(_data != NULL) + free(_data); +} + +size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { + (void)time; + // If the whole message is now acked... + if(_acked + len > _len){ + // Return the number of extra bytes acked (they will be carried on to the next message) + const size_t extra = _acked + len - _len; + _acked = _len; + return extra; + } + // Return that no extra bytes left. + _acked += len; + return 0; +} + +// This could also return void as the return value is not used. +// Leaving as-is for compatibility... +size_t AsyncEventSourceMessage::send(AsyncClient *client) { + if (_sent >= _len) { + return 0; + } + const size_t len_to_send = _len - _sent; + auto position = reinterpret_cast(_data + _sent); + const size_t sent_now = client->write(position, len_to_send); + _sent += sent_now; + return sent_now; +} + +// Client + +AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) +: _messageQueue(AlternativeLinkedList([](AsyncEventSourceMessage *m){ delete m; })) +{ + _client = request->client(); + _server = server; + _lastId = 0; + if(request->hasHeader(F("Last-Event-ID"))) + _lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str()); + + _client->setRxTimeout(0); + _client->onError(NULL, NULL); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); + _client->onData(NULL, NULL); + _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); + _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); + + _server->_addClient(this); + delete request; +} + +AsyncEventSourceClient::~AsyncEventSourceClient(){ + _lockmq.lock(); + _messageQueue.free(); + _lockmq.unlock(); + close(); +} + +void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ + if(dataMessage == NULL) + return; + if(!connected()){ + delete dataMessage; + return; + } + //length() is not thread-safe, thus acquiring the lock before this call.. + _lockmq.lock(); + if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ +#ifdef ESP8266 + ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); +#else + log_e("Too many messages queued: deleting message"); +#endif + delete dataMessage; + } else { + _messageQueue.add(dataMessage); + // runqueue trigger when new messages added + if(_client->canSend()) { + _runQueue(); + } + } + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ + // Same here, acquiring the lock early + _lockmq.lock(); + while(len && !_messageQueue.isEmpty()){ + len = _messageQueue.front()->ack(len, time); + if(_messageQueue.front()->finished()) + _messageQueue.remove(_messageQueue.front()); + } + _runQueue(); + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onPoll(){ + _lockmq.lock(); + if(!_messageQueue.isEmpty()){ + _runQueue(); + } + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ + _client->close(true); +} + +void AsyncEventSourceClient::_onDisconnect(){ + _client = NULL; + _server->_handleDisconnect(this); +} + +void AsyncEventSourceClient::close(){ + if(_client != NULL) + _client->close(); +} + +void AsyncEventSourceClient::write(const char * message, size_t len){ + _queueMessage(new AsyncEventSourceMessage(message, len)); +} + +void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); +} + +size_t AsyncEventSourceClient::packetsWaiting() const { + size_t len; + _lockmq.lock(); + len = _messageQueue.length(); + _lockmq.unlock(); + return len; +} + +void AsyncEventSourceClient::_runQueue() { + // Calls to this private method now already protected by _lockmq acquisition + // so no extra call of _lockmq.lock() here.. + for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) { + // If it crashes here, iterator (i) has been invalidated as _messageQueue + // has been changed... (UL 2020-11-15: Not supposed to happen any more ;-) ) + if (!(*i)->sent()) { + (*i)->send(_client); + } + } +} + + +// Handler + +AsyncEventSource::AsyncEventSource(const String& url) + : _url(url) + , _clients(AlternativeLinkedList([](AsyncEventSourceClient *c){ delete c; })) + , _connectcb(NULL) +{} + +AsyncEventSource::~AsyncEventSource(){ + close(); +} + +void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ + _connectcb = cb; +} + +void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){ + _authorizeConnectHandler = cb; +} + +void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ + /*char * temp = (char *)malloc(2054); + if(temp != NULL){ + memset(temp+1,' ',2048); + temp[0] = ':'; + temp[2049] = '\r'; + temp[2050] = '\n'; + temp[2051] = '\r'; + temp[2052] = '\n'; + temp[2053] = 0; + client->write((const char *)temp, 2053); + free(temp); + }*/ + AsyncWebLockGuard l(_client_queue_lock); + _clients.add(client); + if(_connectcb) + _connectcb(client); +} + +void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ + AsyncWebLockGuard l(_client_queue_lock); + _clients.remove(client); +} + +void AsyncEventSource::close(){ + // While the whole loop is not done, the linked list is locked and so the + // iterator should remain valid even when AsyncEventSource::_handleDisconnect() + // is called very early + AsyncWebLockGuard l(_client_queue_lock); + for(const auto &c: _clients){ + if(c->connected()) + c->close(); + } +} + +// pmb fix +size_t AsyncEventSource::avgPacketsWaiting() const { + size_t aql = 0; + uint32_t nConnectedClients = 0; + AsyncWebLockGuard l(_client_queue_lock); + if (_clients.isEmpty()) { + return 0; + } + for(const auto &c: _clients){ + if(c->connected()) { + aql += c->packetsWaiting(); + ++nConnectedClients; + } + } + return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up +} + +void AsyncEventSource::send( + const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + AsyncWebLockGuard l(_client_queue_lock); + for(const auto &c: _clients){ + if(c->connected()) { + c->write(ev.c_str(), ev.length()); + } + } +} + +size_t AsyncEventSource::count() const { + size_t n_clients; + AsyncWebLockGuard l(_client_queue_lock); + n_clients = _clients.count_if([](AsyncEventSourceClient *c){ + return c->connected(); + }); + return n_clients; +} + +bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET || !request->url().equals(_url)) { + return false; + } + request->addInterestingHeader(F("Last-Event-ID")); + request->addInterestingHeader("Cookie"); + return true; +} + +void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ + if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { + return request->requestAuthentication(); + } + if(_authorizeConnectHandler != NULL){ + if(!_authorizeConnectHandler(request)){ + return request->send(401); + } + } + request->send(new AsyncEventSourceResponse(this)); +} + +// Response + +AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ + _server = server; + _code = 200; + _contentType = F("text/event-stream"); + _sendContentLength = false; + addHeader(F("Cache-Control"), F("no-cache")); + addHeader(F("Connection"), F("keep-alive")); +} + +void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ + if(len){ + new AsyncEventSourceClient(request, _server); + } + return 0; +} + diff --git a/libraries/ESPAsyncWebServer/src/AsyncEventSource.h b/libraries/ESPAsyncWebServer/src/AsyncEventSource.h new file mode 100644 index 000000000..ac8c36498 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncEventSource.h @@ -0,0 +1,147 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCEVENTSOURCE_H_ +#define ASYNCEVENTSOURCE_H_ + +#include +#ifdef ESP32 +#include +#ifndef SSE_MAX_QUEUED_MESSAGES +#define SSE_MAX_QUEUED_MESSAGES 32 +#endif +#else +#include +#ifndef SSE_MAX_QUEUED_MESSAGES +#define SSE_MAX_QUEUED_MESSAGES 8 +#endif +#endif + +#include + +#include "AsyncWebSynchronization.h" + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_SSE_CLIENTS 8 +#else +#define DEFAULT_MAX_SSE_CLIENTS 4 +#endif + +class AsyncEventSource; +class AsyncEventSourceResponse; +class AsyncEventSourceClient; +typedef std::function ArEventHandlerFunction; +typedef std::function ArAuthorizeConnectHandler; + +class AsyncEventSourceMessage { + private: + uint8_t * _data; + size_t _len; + size_t _sent; + //size_t _ack; + size_t _acked; + public: + AsyncEventSourceMessage(const char * data, size_t len); + ~AsyncEventSourceMessage(); + size_t ack(size_t len, uint32_t time __attribute__((unused))); + size_t send(AsyncClient *client); + bool finished(){ return _acked == _len; } + bool sent() { return _sent == _len; } +}; + +class AsyncEventSourceClient { + private: + AsyncClient *_client; + AsyncEventSource *_server; + uint32_t _lastId; + AlternativeLinkedList _messageQueue; + // ArFi 2020-08-27 for protecting/serializing _messageQueue + AsyncPlainLock _lockmq; + void _queueMessage(AsyncEventSourceMessage *dataMessage); + void _runQueue(); + + public: + + AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); + ~AsyncEventSourceClient(); + + AsyncClient* client(){ return _client; } + void close(); + void write(const char * message, size_t len); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + bool connected() const { return (_client != NULL) && _client->connected(); } + uint32_t lastId() const { return _lastId; } + size_t packetsWaiting() const; + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); +}; + +class AsyncEventSource: public AsyncWebHandler { + private: + String _url; + AlternativeLinkedList _clients; + // Same as for individual messages, protect mutations of _clients list + // since simultaneous access from different tasks is possible + AsyncWebLock _client_queue_lock; + ArEventHandlerFunction _connectcb; + ArAuthorizeConnectHandler _authorizeConnectHandler; + public: + AsyncEventSource(const String& url); + ~AsyncEventSource(); + + const char * url() const { return _url.c_str(); } + void close(); + void onConnect(ArEventHandlerFunction cb); + void authorizeConnect(ArAuthorizeConnectHandler cb); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + // number of clients connected + size_t count() const; + size_t avgPacketsWaiting() const; + + //system callbacks (do not call) + void _addClient(AsyncEventSourceClient * client); + void _handleDisconnect(AsyncEventSourceClient * client); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; +}; + +class AsyncEventSourceResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncEventSource *_server; + public: + AsyncEventSourceResponse(AsyncEventSource *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCEVENTSOURCE_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/AsyncJson.h b/libraries/ESPAsyncWebServer/src/AsyncJson.h new file mode 100644 index 000000000..677445401 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncJson.h @@ -0,0 +1,282 @@ +// AsyncJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + + Example of callback in use + + server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { + + AsyncJsonResponse * response = new AsyncJsonResponse(); + JsonObject& root = response->getRoot(); + root["key1"] = "key number one"; + JsonObject& nested = root.createNestedObject("nested"); + nested["key1"] = "key number one"; + + response->setLength(); + request->send(response); + }); + + -------------------- + + Async Request to use with ArduinoJson and AsyncWebServer + Written by Arsène von Wyss (avonwyss) + + Example + + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); + handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { + JsonObject& jsonObj = json.as(); + // ... + }); + server.addHandler(handler); + +*/ +#ifndef ASYNC_JSON_H_ +#define ASYNC_JSON_H_ +#include +#include +#include + +#if ARDUINOJSON_VERSION_MAJOR == 6 +#ifndef DYNAMIC_JSON_DOCUMENT_SIZE +#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 +#endif +#endif + +constexpr const char* JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class ChunkPrint : public Print { + private: + uint8_t* _destination; + size_t _to_skip; + size_t _to_write; + size_t _pos; + + public: + ChunkPrint(uint8_t* destination, size_t from, size_t len) + : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} + virtual ~ChunkPrint() {} + size_t write(uint8_t c) { + if (_to_skip > 0) { + _to_skip--; + return 1; + } else if (_to_write > 0) { + _to_write--; + _destination[_pos++] = c; + return 1; + } + return 0; + } + size_t write(const uint8_t* buffer, size_t size) { + return this->Print::write(buffer, size); + } +}; + +class AsyncJsonResponse : public AsyncAbstractResponse { + protected: +#if ARDUINOJSON_VERSION_MAJOR == 5 + DynamicJsonBuffer _jsonBuffer; +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument _jsonBuffer; +#else + JsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + bool _isValid; + + public: +#if ARDUINOJSON_VERSION_MAJOR == 5 + AsyncJsonResponse(bool isArray = false) : _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#elif ARDUINOJSON_VERSION_MAJOR == 6 + AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#else + AsyncJsonResponse(bool isArray = false) : _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.add(); + else + _root = _jsonBuffer.add(); + } +#endif + + ~AsyncJsonResponse() {} + JsonVariant& getRoot() { return _root; } + bool _sourceValid() const { return _isValid; } + size_t setLength() { + +#if ARDUINOJSON_VERSION_MAJOR == 5 + _contentLength = _root.measureLength(); +#else + _contentLength = measureJson(_root); +#endif + + if (_contentLength) { + _isValid = true; + } + return _contentLength; + } + + size_t getSize() const { return _jsonBuffer.size(); } + +#if ARDUINOJSON_VERSION_MAJOR >= 6 + bool overflowed() const { return _jsonBuffer.overflowed(); } +#endif + + size_t _fillBuffer(uint8_t* data, size_t len) { + ChunkPrint dest(data, _sentLength, len); + +#if ARDUINOJSON_VERSION_MAJOR == 5 + _root.printTo(dest); +#else + serializeJson(_root, dest); +#endif + return len; + } +}; + +class PrettyAsyncJsonResponse : public AsyncJsonResponse { + public: +#if ARDUINOJSON_VERSION_MAJOR == 6 + PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} +#else + PrettyAsyncJsonResponse(bool isArray = false) : AsyncJsonResponse{isArray} {} +#endif + size_t setLength() { +#if ARDUINOJSON_VERSION_MAJOR == 5 + _contentLength = _root.measurePrettyLength(); +#else + _contentLength = measureJsonPretty(_root); +#endif + if (_contentLength) { + _isValid = true; + } + return _contentLength; + } + size_t _fillBuffer(uint8_t* data, size_t len) { + ChunkPrint dest(data, _sentLength, len); +#if ARDUINOJSON_VERSION_MAJOR == 5 + _root.prettyPrintTo(dest); +#else + serializeJsonPretty(_root, dest); +#endif + return len; + } +}; + +typedef std::function ArJsonRequestHandlerFunction; + +class AsyncCallbackJsonWebHandler : public AsyncWebHandler { + private: + protected: + const String _uri; + WebRequestMethodComposite _method; + ArJsonRequestHandlerFunction _onRequest; + size_t _contentLength; +#if ARDUINOJSON_VERSION_MAJOR == 6 + const size_t maxJsonBufferSize; +#endif + size_t _maxContentLength; + + public: +#if ARDUINOJSON_VERSION_MAJOR == 6 + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} +#else + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) + : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} +#endif + + void setMethod(WebRequestMethodComposite method) { _method = method; } + void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } + void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } + + virtual bool canHandle(AsyncWebServerRequest* request) override final { + if (!_onRequest) + return false; + + WebRequestMethodComposite request_method = request->method(); + if (!(_method & request_method)) + return false; + + if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) + return false; + + if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(JSON_MIMETYPE)) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest* request) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if (_onRequest) { + if (request->method() == HTTP_GET) { + JsonVariant json; + _onRequest(request, json); + return; + } else if (request->_tempObject != NULL) { + +#if ARDUINOJSON_VERSION_MAJOR == 5 + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); + if (json.success()) { +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); + if (!error) { + JsonVariant json = jsonBuffer.as(); +#else + JsonDocument jsonBuffer; + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); + if (!error) { + JsonVariant json = jsonBuffer.as(); +#endif + + _onRequest(request, json); + return; + } + } + request->send(_contentLength > _maxContentLength ? 413 : 400); + } else { + request->send(500); + } + } + virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final { + } + virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final { + if (_onRequest) { + _contentLength = total; + if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { + request->_tempObject = malloc(total); + } + if (request->_tempObject != NULL) { + memcpy((uint8_t*)(request->_tempObject) + index, data, len); + } + } + } + virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; } +}; +#endif diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp new file mode 100644 index 000000000..f231d2364 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp @@ -0,0 +1,1353 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncWebSocket.h" + +#include + +#include + +#ifndef ESP8266 +#include "mbedtls/sha1.h" +#include +#else +#include +#endif + +#define MAX_PRINTF_LEN 64 + +size_t webSocketSendFrameWindow(AsyncClient *client){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 9) + return 0; + return space - 8; +} + +size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){ + if(!client->canSend()) { + // Serial.println("SF 1"); + return 0; + } + size_t space = client->space(); + if(space < 2) { + // Serial.println("SF 2"); + return 0; + } + uint8_t mbuf[4] = {0,0,0,0}; + uint8_t headLen = 2; + if(len && mask){ + headLen += 4; + mbuf[0] = rand() % 0xFF; + mbuf[1] = rand() % 0xFF; + mbuf[2] = rand() % 0xFF; + mbuf[3] = rand() % 0xFF; + } + if(len > 125) + headLen += 2; + if(space < headLen) { + // Serial.println("SF 2"); + return 0; + } + space -= headLen; + + if(len > space) len = space; + + uint8_t *buf = (uint8_t*)malloc(headLen); + if(buf == NULL){ + //os_printf("could not malloc %u bytes for frame header\n", headLen); + // Serial.println("SF 3"); + return 0; + } + + buf[0] = opcode & 0x0F; + if(final) + buf[0] |= 0x80; + if(len < 126) + buf[1] = len & 0x7F; + else { + buf[1] = 126; + buf[2] = (uint8_t)((len >> 8) & 0xFF); + buf[3] = (uint8_t)(len & 0xFF); + } + if(len && mask){ + buf[1] |= 0x80; + memcpy(buf + (headLen - 4), mbuf, 4); + } + if(client->add((const char *)buf, headLen) != headLen){ + //os_printf("error adding %lu header bytes\n", headLen); + free(buf); + // Serial.println("SF 4"); + return 0; + } + free(buf); + + if(len){ + if(len && mask){ + size_t i; + for(i=0;iadd((const char *)data, len) != len){ + //os_printf("error adding %lu data bytes\n", len); + // Serial.println("SF 5"); + return 0; + } + } + if(!client->send()){ + //os_printf("error sending frame: %lu\n", headLen+len); + // Serial.println("SF 6"); + return 0; + } + // Serial.println("SF"); + return len; +} + + + +/* + * AsyncWebSocketMessageBuffer + */ + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() + : _buffer(std::make_shared>(0)) +{ +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t* data, size_t size) + : _buffer(std::make_shared>(size)) +{ + if (_buffer->capacity() < size) { + _buffer.reset(); + _buffer = std::make_shared>(0); + } else { + std::memcpy(_buffer->data(), data, size); + } +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) + : _buffer(std::make_shared>(size)) +{ + if (_buffer->capacity() < size) { + _buffer.reset(); + _buffer = std::make_shared>(0); + } +} + +AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() +{ + _buffer.reset(); +} + +bool AsyncWebSocketMessageBuffer::reserve(size_t size) +{ + if (_buffer->capacity() >= size) + return true; + _buffer->reserve(size); + return _buffer->capacity() >= size; +} + +/* + * Control Frame + */ + +class AsyncWebSocketControl { +private: + uint8_t _opcode; + uint8_t *_data; + size_t _len; + bool _mask; + bool _finished; + +public: + AsyncWebSocketControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false) + :_opcode(opcode) + ,_len(len) + ,_mask(len && mask) + ,_finished(false) + { + if (data == NULL) + _len = 0; + if (_len) + { + if (_len > 125) + _len = 125; + + _data = (uint8_t*)malloc(_len); + + if(_data == NULL) + _len = 0; + else + memcpy(_data, data, len); + } + else + _data = NULL; + } + + virtual ~AsyncWebSocketControl() + { + if (_data != NULL) + free(_data); + } + + virtual bool finished() const { return _finished; } + uint8_t opcode(){ return _opcode; } + uint8_t len(){ return _len + 2; } + size_t send(AsyncClient *client){ + _finished = true; + return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); + } +}; + + +/* + * AsyncWebSocketMessage Message + */ + + +AsyncWebSocketMessage::AsyncWebSocketMessage(std::shared_ptr> buffer, uint8_t opcode, bool mask) : + _WSbuffer{buffer}, + _opcode(opcode & 0x07), + _mask{mask}, + _status{_WSbuffer?WS_MSG_SENDING:WS_MSG_ERROR} +{ +} + +void AsyncWebSocketMessage::ack(size_t len, uint32_t time) +{ + (void)time; + _acked += len; + if (_sent >= _WSbuffer->size() && _acked >= _ack) + { + _status = WS_MSG_SENT; + } + //ets_printf("A: %u\n", len); +} + +size_t AsyncWebSocketMessage::send(AsyncClient *client) +{ + if (_status != WS_MSG_SENDING) + return 0; + if (_acked < _ack){ + return 0; + } + if (_sent == _WSbuffer->size()) + { + if(_acked == _ack) + _status = WS_MSG_SENT; + return 0; + } + if (_sent > _WSbuffer->size()) + { + _status = WS_MSG_ERROR; + //ets_printf("E: %u > %u\n", _sent, _WSbuffer->length()); + return 0; + } + + size_t toSend = _WSbuffer->size() - _sent; + size_t window = webSocketSendFrameWindow(client); + + if (window < toSend) { + toSend = window; + } + + _sent += toSend; + _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); + + //ets_printf("W: %u %u\n", _sent - toSend, toSend); + + bool final = (_sent == _WSbuffer->size()); + uint8_t* dPtr = (uint8_t*)(_WSbuffer->data() + (_sent - toSend)); + uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; + + size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); + _status = WS_MSG_SENDING; + if (toSend && sent != toSend){ + //ets_printf("E: %u != %u\n", toSend, sent); + _sent -= (toSend - sent); + _ack -= (toSend - sent); + } + //ets_printf("S: %u %u\n", _sent, sent); + return sent; +} + + +/* + * Async WebSocket Client + */ + const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; + const size_t AWSC_PING_PAYLOAD_LEN = 22; + +AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) + : _tempObject(NULL) +{ + _client = request->client(); + _server = server; + _clientId = _server->_getNextId(); + _status = WS_CONNECTED; + _pstate = 0; + _lastMessageTime = millis(); + _keepAlivePeriod = 0; + _client->setRxTimeout(0); + _client->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); + _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); + _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); + _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); + _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0); + delete request; + memset(&_pinfo,0,sizeof(_pinfo)); +} + +AsyncWebSocketClient::~AsyncWebSocketClient() +{ + { + AsyncWebLockGuard l(_lock); + + _messageQueue.clear(); + _controlQueue.clear(); + } + _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); +} + +void AsyncWebSocketClient::_clearQueue() +{ + while (!_messageQueue.empty() && _messageQueue.front().finished()) + _messageQueue.pop_front(); +} + +void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){ + _lastMessageTime = millis(); + + AsyncWebLockGuard l(_lock); + + if (!_controlQueue.empty()) { + auto &head = _controlQueue.front(); + if (head.finished()){ + len -= head.len(); + if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT){ + _controlQueue.pop_front(); + _status = WS_DISCONNECTED; + l.unlock(); + if (_client) _client->close(true); + return; + } + _controlQueue.pop_front(); + } + } + + if(len && !_messageQueue.empty()){ + _messageQueue.front().ack(len, time); + } + + _clearQueue(); + + _runQueue(); +} + +void AsyncWebSocketClient::_onPoll() +{ + if (!_client) + return; + + AsyncWebLockGuard l(_lock); + if (_client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) + { + l.unlock(); + _runQueue(); + } + else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) + { + l.unlock(); + ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); + } +} + +void AsyncWebSocketClient::_runQueue() +{ + if (!_client) + return; + + AsyncWebLockGuard l(_lock); + + _clearQueue(); + + if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) + { + //l.unlock(); + _controlQueue.front().send(_client); + } + else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) + { + //l.unlock(); + _messageQueue.front().send(_client); + } +} + +bool AsyncWebSocketClient::queueIsFull() const +{ + size_t size; + { + AsyncWebLockGuard l(_lock); + size = _messageQueue.size(); + } + return (size >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED); +} + +size_t AsyncWebSocketClient::queueLen() const +{ + AsyncWebLockGuard l(_lock); + + return _messageQueue.size() + _controlQueue.size(); +} + +bool AsyncWebSocketClient::canSend() const +{ + size_t size; + { + AsyncWebLockGuard l(_lock); + size = _messageQueue.size(); + } + return size < WS_MAX_QUEUED_MESSAGES; +} + +void AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) +{ + if (!_client) + return; + + { + AsyncWebLockGuard l(_lock); + _controlQueue.emplace_back(opcode, data, len, mask); + } + + if (_client && _client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::_queueMessage(std::shared_ptr> buffer, uint8_t opcode, bool mask) +{ + if(_status != WS_CONNECTED) + return; + + if (!_client) + return; + + if (buffer->size() == 0) + return; + + { + AsyncWebLockGuard l(_lock); + if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) + { + l.unlock(); + if(closeWhenFull) + { +#ifdef ESP8266 + ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n"); +#else + log_e("Too many messages queued: closing connection"); +#endif + _status = WS_DISCONNECTED; + if (_client) _client->close(true); + } else { +#ifdef ESP8266 + ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n"); +#else + log_e("Too many messages queued: discarding new message"); +#endif + } + return; + } + else + { + _messageQueue.emplace_back(buffer, opcode, mask); + } + } + + if (_client && _client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::close(uint16_t code, const char * message) +{ + if(_status != WS_CONNECTED) + return; + + if(code) + { + uint8_t packetLen = 2; + if (message != NULL) + { + size_t mlen = strlen(message); + if(mlen > 123) mlen = 123; + packetLen += mlen; + } + char * buf = (char*)malloc(packetLen); + if (buf != NULL) + { + buf[0] = (uint8_t)(code >> 8); + buf[1] = (uint8_t)(code & 0xFF); + if(message != NULL){ + memcpy(buf+2, message, packetLen -2); + } + _queueControl(WS_DISCONNECT, (uint8_t*)buf, packetLen); + free(buf); + return; + } + } + _queueControl(WS_DISCONNECT); +} + +void AsyncWebSocketClient::ping(const uint8_t *data, size_t len) +{ + if (_status == WS_CONNECTED) + _queueControl(WS_PING, data, len); +} + +void AsyncWebSocketClient::_onError(int8_t) +{ + //Serial.println("onErr"); +} + +void AsyncWebSocketClient::_onTimeout(uint32_t time) +{ + // Serial.println("onTime"); + (void)time; + _client->close(true); +} + +void AsyncWebSocketClient::_onDisconnect() +{ + // Serial.println("onDis"); + _client = NULL; +} + +void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) +{ + // Serial.println("onData"); + _lastMessageTime = millis(); + uint8_t *data = (uint8_t*)pbuf; + while(plen > 0){ + if(!_pstate){ + const uint8_t *fdata = data; + _pinfo.index = 0; + _pinfo.final = (fdata[0] & 0x80) != 0; + _pinfo.opcode = fdata[0] & 0x0F; + _pinfo.masked = (fdata[1] & 0x80) != 0; + _pinfo.len = fdata[1] & 0x7F; + data += 2; + plen -= 2; + if(_pinfo.len == 126){ + _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; + data += 2; + plen -= 2; + } else if(_pinfo.len == 127){ + _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + data += 8; + plen -= 8; + } + + if(_pinfo.masked){ + memcpy(_pinfo.mask, data, 4); + data += 4; + plen -= 4; + } + } + + const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); + const auto datalast = data[datalen]; + + if(_pinfo.masked){ + for(size_t i=0;i 0) _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen); + + _pinfo.index += datalen; + } else if((datalen + _pinfo.index) == _pinfo.len){ + _pstate = 0; + if(_pinfo.opcode == WS_DISCONNECT){ + if(datalen){ + uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; + char * reasonString = (char*)(data+2); + if(reasonCode > 1001){ + _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); + } + } + if(_status == WS_DISCONNECTING){ + _status = WS_DISCONNECTED; + _client->close(true); + } else { + _status = WS_DISCONNECTING; + _client->ackLater(); + _queueControl(WS_DISCONNECT, data, datalen); + } + } else if(_pinfo.opcode == WS_PING){ + _queueControl(WS_PONG, data, datalen); + } else if(_pinfo.opcode == WS_PONG){ + if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) + _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen); + } else if(_pinfo.opcode < 8){//continuation or text/binary frame + _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); + if (_pinfo.final) _pinfo.num = 0; + else _pinfo.num += 1; + } + } else { + //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); + //what should we do? + break; + } + + // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; + if (datalen > 0) + data[datalen] = datalast; + + data += datalen; + plen -= datalen; + } +} + +size_t AsyncWebSocketClient::printf(const char *format, ...) +{ + va_list arg; + va_start(arg, format); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, format); + vsnprintf(buffer, len + 1, format, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) +{ + va_list arg; + va_start(arg, formatP); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, formatP); + vsnprintf_P(buffer, len + 1, formatP, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} +#endif + +namespace { +std::shared_ptr> makeSharedBuffer(const uint8_t *message, size_t len) +{ + auto buffer = std::make_shared>(len); + std::memcpy(buffer->data(), message, len); + return buffer; +} +} + +void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + text(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocketClient::text(std::shared_ptr> buffer) +{ + _queueMessage(buffer); +} + +void AsyncWebSocketClient::text(const uint8_t *message, size_t len) +{ + text(makeSharedBuffer(message, len)); +} + +void AsyncWebSocketClient::text(const char *message, size_t len) +{ + text((const uint8_t *)message, len); +} + +void AsyncWebSocketClient::text(const char *message) +{ + text(message, strlen(message)); +} + +void AsyncWebSocketClient::text(const String &message) +{ + text(message.c_str(), message.length()); +} + +void AsyncWebSocketClient::text(const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (1) + { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + + char * message = (char*) malloc(n+1); + if(message) + { + memcpy_P(message, p, n); + message[n] = 0; + text(message, n); + free(message); + } +} + +void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + binary(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocketClient::binary(std::shared_ptr> buffer) +{ + _queueMessage(buffer, WS_BINARY); +} + +void AsyncWebSocketClient::binary(const uint8_t *message, size_t len) +{ + binary(makeSharedBuffer(message, len)); +} + +void AsyncWebSocketClient::binary(const char *message, size_t len) +{ + binary((const uint8_t *)message, len); +} + +void AsyncWebSocketClient::binary(const char *message) +{ + binary(message, strlen(message)); +} + +void AsyncWebSocketClient::binary(const String &message) +{ + binary(message.c_str(), message.length()); +} + +void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char *message = (char*) malloc(len); + if (message) { + memcpy_P(message, p, len); + binary(message, len); + free(message); + } +} + +IPAddress AsyncWebSocketClient::remoteIP() const +{ + if (!_client) + return IPAddress((uint32_t)0U); + + return _client->remoteIP(); +} + +uint16_t AsyncWebSocketClient::remotePort() const +{ + if(!_client) + return 0; + + return _client->remotePort(); +} + + + +/* + * Async Web Socket - Each separate socket location + */ + +AsyncWebSocket::AsyncWebSocket(const String& url) + :_url(url) + ,_cNextId(1) + ,_enabled(true) +{ + _eventHandler = NULL; +} + +AsyncWebSocket::~AsyncWebSocket(){} + +void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(_eventHandler != NULL){ + _eventHandler(this, client, type, arg, data, len); + } +} + +AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) +{ + _clients.emplace_back(request, this); + return &_clients.back(); +} + +bool AsyncWebSocket::availableForWriteAll() +{ + return std::none_of(std::begin(_clients), std::end(_clients), + [](const AsyncWebSocketClient &c){ return c.queueIsFull(); }); +} + +bool AsyncWebSocket::availableForWrite(uint32_t id) +{ + const auto iter = std::find_if(std::begin(_clients), std::end(_clients), + [id](const AsyncWebSocketClient &c){ return c.id() == id; }); + if (iter == std::end(_clients)) + return true; + return !iter->queueIsFull(); +} + +size_t AsyncWebSocket::count() const +{ + return std::count_if(std::begin(_clients), std::end(_clients), + [](const AsyncWebSocketClient &c){ return c.status() == WS_CONNECTED; }); +} + +AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) +{ + const auto iter = std::find_if(std::begin(_clients), std::end(_clients), + [id](const AsyncWebSocketClient &c){ return c.id() == id && c.status() == WS_CONNECTED; }); + if (iter == std::end(_clients)) + return nullptr; + + return &(*iter); +} + + +void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) +{ + if (AsyncWebSocketClient *c = client(id)) + c->close(code, message); +} + +void AsyncWebSocket::closeAll(uint16_t code, const char * message) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.close(code, message); +} + +void AsyncWebSocket::cleanupClients(uint16_t maxClients) +{ + if (count() > maxClients) + _clients.front().close(); + + for (auto iter = std::begin(_clients); iter != std::end(_clients);) + { + if (iter->shouldBeDeleted()) + iter = _clients.erase(iter); + else + iter++; + } +} + +void AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) +{ + if (AsyncWebSocketClient * c = client(id)) + c->ping(data, len); +} + +void AsyncWebSocket::pingAll(const uint8_t *data, size_t len) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.ping(data, len); +} + +void AsyncWebSocket::text(uint32_t id, const uint8_t *message, size_t len) +{ + if (AsyncWebSocketClient * c = client(id)) + c->text(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::text(uint32_t id, const char *message, size_t len) +{ + text(id, (const uint8_t *)message, len); +} +void AsyncWebSocket::text(uint32_t id, const char * message) +{ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, const String &message) +{ + text(id, message.c_str(), message.length()); +} +void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (true) + { + if (pgm_read_byte(p+n) == 0) + break; + n += 1; + } + + char * message = (char*) malloc(n+1); + if (message) + { + memcpy_P(message, p, n); + message[n] = 0; + text(id, message, n); + free(message); + } +} +void AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer *buffer) +{ + if (buffer) { + text(id, std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::text(uint32_t id, std::shared_ptr> buffer) +{ + if (AsyncWebSocketClient *c = client(id)) + c->text(buffer); +} + +void AsyncWebSocket::textAll(const uint8_t *message, size_t len) +{ + textAll(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::textAll(const char * message, size_t len) +{ + textAll((const uint8_t *)message, len); +} +void AsyncWebSocket::textAll(const char *message) +{ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(const String &message) +{ + textAll(message.c_str(), message.length()); +} +void AsyncWebSocket::textAll(const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (1) + { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + + char *message = (char*)malloc(n+1); + if(message) + { + memcpy_P(message, p, n); + message[n] = 0; + textAll(message, n); + free(message); + } +} +void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + textAll(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocket::textAll(std::shared_ptr> buffer) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.text(buffer); +} + +void AsyncWebSocket::binary(uint32_t id, const uint8_t *message, size_t len) +{ + if (AsyncWebSocketClient *c = client(id)) + c->binary(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) +{ + binary(id, (const uint8_t *)message, len); +} +void AsyncWebSocket::binary(uint32_t id, const char * message) +{ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, const String &message) +{ + binary(id, message.c_str(), message.length()); +} +void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char *message = (char*) malloc(len); + if (message) + { + memcpy_P(message, p, len); + binary(id, message, len); + free(message); + } +} +void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer) +{ + if (buffer) { + binary(id, std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::binary(uint32_t id, std::shared_ptr> buffer) +{ + if (AsyncWebSocketClient *c = client(id)) + c->binary(buffer); +} + + +void AsyncWebSocket::binaryAll(const uint8_t *message, size_t len) +{ + binaryAll(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::binaryAll(const char *message, size_t len) +{ + binaryAll((const uint8_t *)message, len); +} +void AsyncWebSocket::binaryAll(const char *message) +{ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(const String &message) +{ + binaryAll(message.c_str(), message.length()); +} +void AsyncWebSocket::binaryAll(const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char * message = (char*) malloc(len); + if(message) + { + memcpy_P(message, p, len); + binaryAll(message, len); + free(message); + } +} +void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + binaryAll(std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::binaryAll(std::shared_ptr> buffer) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.binary(buffer); +} + +size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){ + AsyncWebSocketClient * c = client(id); + if (c) + { + va_list arg; + va_start(arg, format); + size_t len = c->printf(format, arg); + va_end(arg); + return len; + } + return 0; +} + +size_t AsyncWebSocket::printfAll(const char *format, ...) +{ + va_list arg; + char *temp = new char[MAX_PRINTF_LEN]; + if (!temp) + return 0; + + va_start(arg, format); + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + delete[] temp; + + std::shared_ptr> buffer = std::make_shared>(len); + + va_start(arg, format); + vsnprintf( (char *)buffer->data(), len + 1, format, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){ + AsyncWebSocketClient * c = client(id); + if(c != NULL){ + va_list arg; + va_start(arg, formatP); + size_t len = c->printf_P(formatP, arg); + va_end(arg); + return len; + } + return 0; +} +#endif + +size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) +{ + va_list arg; + char *temp = new char[MAX_PRINTF_LEN]; + if (!temp) + return 0; + + va_start(arg, formatP); + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + delete[] temp; + + std::shared_ptr> buffer = std::make_shared>(len + 1); + + va_start(arg, formatP); + vsnprintf_P((char *)buffer->data(), len + 1, formatP, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +const char __WS_STR_CONNECTION[] PROGMEM = { "Connection" }; +const char __WS_STR_UPGRADE[] PROGMEM = { "Upgrade" }; +const char __WS_STR_ORIGIN[] PROGMEM = { "Origin" }; +const char __WS_STR_COOKIE[] PROGMEM = { "Cookie" }; +const char __WS_STR_VERSION[] PROGMEM = { "Sec-WebSocket-Version" }; +const char __WS_STR_KEY[] PROGMEM = { "Sec-WebSocket-Key" }; +const char __WS_STR_PROTOCOL[] PROGMEM = { "Sec-WebSocket-Protocol" }; +const char __WS_STR_ACCEPT[] PROGMEM = { "Sec-WebSocket-Accept" }; +const char __WS_STR_UUID[] PROGMEM = { "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" }; + +#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION) +#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE) +#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN) +#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE) +#define WS_STR_VERSION FPSTR(__WS_STR_VERSION) +#define WS_STR_KEY FPSTR(__WS_STR_KEY) +#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL) +#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT) +#define WS_STR_UUID FPSTR(__WS_STR_UUID) + +bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){ + if(!_enabled) + return false; + + if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS)) + return false; + + request->addInterestingHeader(WS_STR_CONNECTION); + request->addInterestingHeader(WS_STR_UPGRADE); + request->addInterestingHeader(WS_STR_ORIGIN); + request->addInterestingHeader(WS_STR_COOKIE); + request->addInterestingHeader(WS_STR_VERSION); + request->addInterestingHeader(WS_STR_KEY); + request->addInterestingHeader(WS_STR_PROTOCOL); + return true; +} + +void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request) +{ + if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) + { + request->send(400); + return; + } + if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) + { + return request->requestAuthentication(); + } + if (_handshakeHandler != nullptr){ + if(!_handshakeHandler(request)){ + request->send(401); + return; + } + } + AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); + if (version->value().toInt() != 13) + { + AsyncWebServerResponse *response = request->beginResponse(400); + response->addHeader(WS_STR_VERSION, F("13")); + request->send(response); + return; + } + AsyncWebHeader* key = request->getHeader(WS_STR_KEY); + AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); + if (request->hasHeader(WS_STR_PROTOCOL)) + { + AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); + //ToDo: check protocol + response->addHeader(WS_STR_PROTOCOL, protocol->value()); + } + request->send(response); +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); + if (buffer->length() != size) + { + delete buffer; + return nullptr; + } else { + return buffer; + } +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); + if (buffer->length() != size) + { + delete buffer; + return nullptr; + } else { + return buffer; + } +} + +/* + * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server + * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 + */ + +AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server) +{ + _server = server; + _code = 101; + _sendContentLength = false; + + uint8_t * hash = (uint8_t*)malloc(20); + if(hash == NULL) + { + _state = RESPONSE_FAILED; + return; + } + char * buffer = (char *) malloc(33); + if(buffer == NULL) + { + free(hash); + _state = RESPONSE_FAILED; + return; + } +#ifdef ESP8266 + sha1(key + WS_STR_UUID, hash); +#else + (String&)key += WS_STR_UUID; + mbedtls_sha1_context ctx; + mbedtls_sha1_init(&ctx); +#if ESP_IDF_VERSION_MAJOR == 5 + mbedtls_sha1_starts(&ctx); + mbedtls_sha1_update(&ctx, (const unsigned char*)key.c_str(), key.length()); + mbedtls_sha1_finish(&ctx, hash); +#else + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length()); + mbedtls_sha1_finish_ret(&ctx, hash); +#endif + mbedtls_sha1_free(&ctx); +#endif + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) hash, 20, buffer, &_state); + len = base64_encode_blockend((buffer + len), &_state); + addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); + addHeader(WS_STR_UPGRADE, F("websocket")); + addHeader(WS_STR_ACCEPT,buffer); + free(buffer); + free(hash); +} + +void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request) +{ + if(_state == RESPONSE_FAILED) + { + request->client()->close(true); + return; + } + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) +{ + (void)time; + + if(len) + _server->_newClient(request); + + return 0; +} diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h new file mode 100644 index 000000000..ab182ea9f --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h @@ -0,0 +1,354 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSOCKET_H_ +#define ASYNCWEBSOCKET_H_ + +#include +#ifdef ESP32 +#include +#ifndef WS_MAX_QUEUED_MESSAGES +#define WS_MAX_QUEUED_MESSAGES 32 +#endif +#else +#include +#ifndef WS_MAX_QUEUED_MESSAGES +#define WS_MAX_QUEUED_MESSAGES 8 +#endif +#endif +#include + +#include "AsyncWebSynchronization.h" + +#include +#include +#include + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_WS_CLIENTS 8 +#else +#define DEFAULT_MAX_WS_CLIENTS 4 +#endif + +class AsyncWebSocket; +class AsyncWebSocketResponse; +class AsyncWebSocketClient; +class AsyncWebSocketControl; + +typedef struct { + /** Message type as defined by enum AwsFrameType. + * Note: Applications will only see WS_TEXT and WS_BINARY. + * All other types are handled by the library. */ + uint8_t message_opcode; + /** Frame number of a fragmented message. */ + uint32_t num; + /** Is this the last frame in a fragmented message ?*/ + uint8_t final; + /** Is this frame masked? */ + uint8_t masked; + /** Message type as defined by enum AwsFrameType. + * This value is the same as message_opcode for non-fragmented + * messages, but may also be WS_CONTINUATION in a fragmented message. */ + uint8_t opcode; + /** Length of the current frame. + * This equals the total length of the message if num == 0 && final == true */ + uint64_t len; + /** Mask key */ + uint8_t mask[4]; + /** Offset of the data inside the current frame. */ + uint64_t index; +} AwsFrameInfo; + +typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; +typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; +typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; +typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; + +class AsyncWebSocketMessageBuffer { + friend AsyncWebSocket; + friend AsyncWebSocketClient; + + private: + std::shared_ptr> _buffer; + + public: + AsyncWebSocketMessageBuffer(); + AsyncWebSocketMessageBuffer(size_t size); + AsyncWebSocketMessageBuffer(uint8_t* data, size_t size); + ~AsyncWebSocketMessageBuffer(); + bool reserve(size_t size); + uint8_t* get() { return _buffer->data(); } + size_t length() const { return _buffer->size(); } +}; + +class AsyncWebSocketMessage +{ +private: + std::shared_ptr> _WSbuffer; + uint8_t _opcode{WS_TEXT}; + bool _mask{false}; + AwsMessageStatus _status{WS_MSG_ERROR}; + size_t _sent{}; + size_t _ack{}; + size_t _acked{}; + +public: + AsyncWebSocketMessage(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false); + + bool finished() const { return _status != WS_MSG_SENDING; } + bool betweenFrames() const { return _acked == _ack; } + + void ack(size_t len, uint32_t time); + size_t send(AsyncClient *client); +}; + +class AsyncWebSocketClient { + private: + AsyncClient *_client; + AsyncWebSocket *_server; + uint32_t _clientId; + AwsClientStatus _status; + + AsyncWebLock _lock; + + std::deque _controlQueue; + std::deque _messageQueue; + bool closeWhenFull = true; + + uint8_t _pstate; + AwsFrameInfo _pinfo; + + uint32_t _lastMessageTime; + uint32_t _keepAlivePeriod; + + void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false); + void _queueMessage(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false); + void _runQueue(); + void _clearQueue(); + + public: + void *_tempObject; + + AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); + ~AsyncWebSocketClient(); + + //client id increments for the given server + uint32_t id() const { return _clientId; } + AwsClientStatus status() const { return _status; } + AsyncClient* client() { return _client; } + const AsyncClient* client() const { return _client; } + AsyncWebSocket *server(){ return _server; } + const AsyncWebSocket *server() const { return _server; } + AwsFrameInfo const &pinfo() const { return _pinfo; } + + // - If "true" (default), the connection will be closed if the message queue is full. + // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection. + // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again, + // and so on, causing a resource exhaustion. + // + // - If "false", the incoming message will be discarded if the queue is full. + // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev. + // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost). + // + // - In any case, when the queue is full, a message is logged. + // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message. + // + // Usage: + // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT) + // + // Use cases:, + // - if using websocket to send logging messages, maybe some loss is acceptable. + // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn. + void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; } + bool willCloseClientOnQueueFull() const { return closeWhenFull; } + + IPAddress remoteIP() const; + uint16_t remotePort() const; + + bool shouldBeDeleted() const { return !_client; } + + //control frames + void close(uint16_t code=0, const char * message=NULL); + void ping(const uint8_t *data=NULL, size_t len=0); + + //set auto-ping period in seconds. disabled if zero (default) + void keepAlivePeriod(uint16_t seconds){ + _keepAlivePeriod = seconds * 1000; + } + uint16_t keepAlivePeriod(){ + return (uint16_t)(_keepAlivePeriod / 1000); + } + + //data packets + void message(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false) { _queueMessage(buffer, opcode, mask); } + bool queueIsFull() const; + size_t queueLen() const; + + size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); +#endif + + void text(std::shared_ptr> buffer); + void text(const uint8_t *message, size_t len); + void text(const char *message, size_t len); + void text(const char *message); + void text(const String &message); + void text(const __FlashStringHelper *message); + void text(AsyncWebSocketMessageBuffer *buffer); + + void binary(std::shared_ptr> buffer); + void binary(const uint8_t *message, size_t len); + void binary(const char * message, size_t len); + void binary(const char * message); + void binary(const String &message); + void binary(const __FlashStringHelper *message, size_t len); + void binary(AsyncWebSocketMessageBuffer *buffer); + + bool canSend() const; + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onError(int8_t); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *pbuf, size_t plen); +}; + +typedef std::function AwsHandshakeHandler; +typedef std::function AwsEventHandler; + +//WebServer Handler implementation that plays the role of a socket server +class AsyncWebSocket: public AsyncWebHandler { + private: + String _url; + std::list _clients; + uint32_t _cNextId; + AwsEventHandler _eventHandler; + AwsHandshakeHandler _handshakeHandler; + bool _enabled; + AsyncWebLock _lock; + + public: + AsyncWebSocket(const String& url); + ~AsyncWebSocket(); + const char * url() const { return _url.c_str(); } + void enable(bool e){ _enabled = e; } + bool enabled() const { return _enabled; } + bool availableForWriteAll(); + bool availableForWrite(uint32_t id); + + size_t count() const; + AsyncWebSocketClient * client(uint32_t id); + bool hasClient(uint32_t id){ return client(id) != NULL; } + + void close(uint32_t id, uint16_t code=0, const char * message=NULL); + void closeAll(uint16_t code=0, const char * message=NULL); + void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); + + void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0); + void pingAll(const uint8_t *data=NULL, size_t len=0); // done + + void text(uint32_t id, const uint8_t * message, size_t len); + void text(uint32_t id, const char *message, size_t len); + void text(uint32_t id, const char *message); + void text(uint32_t id, const String &message); + void text(uint32_t id, const __FlashStringHelper *message); + void text(uint32_t id, AsyncWebSocketMessageBuffer *buffer); + void text(uint32_t id, std::shared_ptr> buffer); + + void textAll(const uint8_t *message, size_t len); + void textAll(const char * message, size_t len); + void textAll(const char * message); + void textAll(const String &message); + void textAll(const __FlashStringHelper *message); + void textAll(AsyncWebSocketMessageBuffer *buffer); + void textAll(std::shared_ptr> buffer); + + void binary(uint32_t id, const uint8_t *message, size_t len); + void binary(uint32_t id, const char *message, size_t len); + void binary(uint32_t id, const char *message); + void binary(uint32_t id, const String &message); + void binary(uint32_t id, const __FlashStringHelper *message, size_t len); + void binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer); + void binary(uint32_t id, std::shared_ptr> buffer); + + void binaryAll(const uint8_t *message, size_t len); + void binaryAll(const char *message, size_t len); + void binaryAll(const char *message); + void binaryAll(const String &message); + void binaryAll(const __FlashStringHelper *message, size_t len); + void binaryAll(AsyncWebSocketMessageBuffer *buffer); + void binaryAll(std::shared_ptr> buffer); + + size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); + size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); +#endif + size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); + + //event listener + void onEvent(AwsEventHandler handler){ + _eventHandler = handler; + } + + // Handshake Handler + void handleHandshake(AwsHandshakeHandler handler){ + _handshakeHandler = handler; + } + + //system callbacks (do not call) + uint32_t _getNextId(){ return _cNextId++; } + AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request); + void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + + + // messagebuffer functions/objects. + AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); + AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); + + const std::list &getClients() const { return _clients; } +}; + +//WebServer response to authenticate the socket and detach the tcp client from the web server request +class AsyncWebSocketResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncWebSocket *_server; + public: + AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h b/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h new file mode 100644 index 000000000..0ff8ab63b --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h @@ -0,0 +1,134 @@ +#ifndef ASYNCWEBSYNCHRONIZATION_H_ +#define ASYNCWEBSYNCHRONIZATION_H_ + +// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default + +#include + +#ifdef ESP32 + +// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore +// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not +// always available. According to example by Arjan Filius, changed name, +// added unimplemented version for ESP8266 +class AsyncPlainLock +{ +private: + SemaphoreHandle_t _lock; + +public: + AsyncPlainLock() { + _lock = xSemaphoreCreateBinary(); + // In this fails, the system is likely that much out of memory that + // we should abort anyways. If assertions are disabled, nothing is lost.. + assert(_lock); + xSemaphoreGive(_lock); + } + + ~AsyncPlainLock() { + vSemaphoreDelete(_lock); + } + + bool lock() const { + xSemaphoreTake(_lock, portMAX_DELAY); + return true; + } + + void unlock() const { + xSemaphoreGive(_lock); + } +}; + +// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore +class AsyncWebLock +{ +private: + SemaphoreHandle_t _lock; + mutable TaskHandle_t _lockedBy{}; + +public: + AsyncWebLock() + { + _lock = xSemaphoreCreateBinary(); + // In this fails, the system is likely that much out of memory that + // we should abort anyways. If assertions are disabled, nothing is lost.. + assert(_lock); + _lockedBy = NULL; + xSemaphoreGive(_lock); + } + + ~AsyncWebLock() { + vSemaphoreDelete(_lock); + } + + bool lock() const { + const auto currentTask = xTaskGetCurrentTaskHandle(); + if (_lockedBy != currentTask) { + xSemaphoreTake(_lock, portMAX_DELAY); + _lockedBy = currentTask; + return true; + } + return false; + } + + void unlock() const { + _lockedBy = NULL; + xSemaphoreGive(_lock); + } +}; + +#else + +// This is the 8266 version of the Sync Lock which is currently unimplemented +class AsyncWebLock +{ + +public: + AsyncWebLock() { + } + + ~AsyncWebLock() { + } + + bool lock() const { + return false; + } + + void unlock() const { + } +}; + +// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above. +using AsyncPlainLock = AsyncWebLock; + +#endif + +class AsyncWebLockGuard +{ +private: + const AsyncWebLock *_lock; + +public: + AsyncWebLockGuard(const AsyncWebLock &l) { + if (l.lock()) { + _lock = &l; + } else { + _lock = NULL; + } + } + + ~AsyncWebLockGuard() { + if (_lock) { + _lock->unlock(); + } + } + + void unlock() { + if (_lock) { + _lock->unlock(); + _lock = NULL; + } + } +}; + +#endif // ASYNCWEBSYNCHRONIZATION_H_ diff --git a/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h b/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h new file mode 100644 index 000000000..e0cc06830 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -0,0 +1,501 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _ESPAsyncWebServer_H_ +#define _ESPAsyncWebServer_H_ + +#include "Arduino.h" + +#include +#include +#include +#include "FS.h" + +#include "StringArray.h" + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#else +#error Platform not supported +#endif + +#define ASYNCWEBSERVER_VERSION "2.10.4" +#define ASYNCWEBSERVER_VERSION_MAJOR 2 +#define ASYNCWEBSERVER_VERSION_MINOR 10 +#define ASYNCWEBSERVER_VERSION_REVISION 4 +#define ASYNCWEBSERVER_FORK_mathieucarbou + +#ifdef ASYNCWEBSERVER_REGEX +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE +#else +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) +#endif + +class AsyncWebServer; +class AsyncWebServerRequest; +class AsyncWebServerResponse; +class AsyncWebHeader; +class AsyncWebParameter; +class AsyncWebRewrite; +class AsyncWebHandler; +class AsyncStaticWebHandler; +class AsyncCallbackWebHandler; +class AsyncResponseStream; + +#ifndef WEBSERVER_H +typedef enum { + HTTP_GET = 0b00000001, + HTTP_POST = 0b00000010, + HTTP_DELETE = 0b00000100, + HTTP_PUT = 0b00001000, + HTTP_PATCH = 0b00010000, + HTTP_HEAD = 0b00100000, + HTTP_OPTIONS = 0b01000000, + HTTP_ANY = 0b01111111, +} WebRequestMethod; +#endif + +#ifndef HAVE_FS_FILE_OPEN_MODE +namespace fs { + class FileOpenMode { + public: + static const char *read; + static const char *write; + static const char *append; + }; +}; +#else +#include "FileOpenMode.h" +#endif + +//if this value is returned when asked for data, packet will not be sent and you will be asked for data again +#define RESPONSE_TRY_AGAIN 0xFFFFFFFF + +typedef uint8_t WebRequestMethodComposite; +typedef std::function ArDisconnectHandler; + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + + AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } +}; + +/* + * HEADER :: Chainable object to hold the headers + * */ + +class AsyncWebHeader { + private: + String _name; + String _value; + + public: + AsyncWebHeader() = default; + AsyncWebHeader(const AsyncWebHeader &) = default; + + AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} + AsyncWebHeader(const String& data): _name(), _value(){ + if(!data) return; + int index = data.indexOf(':'); + if (index < 0) return; + _name = data.substring(0, index); + _value = data.substring(index + 2); + } + + AsyncWebHeader &operator=(const AsyncWebHeader &) = default; + + const String& name() const { return _name; } + const String& value() const { return _value; } + String toString() const { return _name + F(": ") + _value + F("\r\n"); } +}; + +/* + * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect + * */ + +typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; + +typedef std::function AwsResponseFiller; +typedef std::function AwsTemplateProcessor; + +class AsyncWebServerRequest { + using File = fs::File; + using FS = fs::FS; + friend class AsyncWebServer; + friend class AsyncCallbackWebHandler; + private: + AsyncClient* _client; + AsyncWebServer* _server; + AsyncWebHandler* _handler; + AsyncWebServerResponse* _response; + std::vector _interestingHeaders; + ArDisconnectHandler _onDisconnectfn; + + String _temp; + uint8_t _parseState; + + uint8_t _version; + WebRequestMethodComposite _method; + String _url; + String _host; + String _contentType; + String _boundary; + String _authorization; + RequestedConnectionType _reqconntype; + void _removeNotInterestingHeaders(); + bool _isDigest; + bool _isMultipart; + bool _isPlainPost; + bool _expectingContinue; + size_t _contentLength; + size_t _parsedLength; + + std::list _headers; + AlternativeLinkedList _params; + std::vector _pathParams; + + uint8_t _multiParseState; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + void _onPoll(); + void _onAck(size_t len, uint32_t time); + void _onError(int8_t error); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t len); + + void _addParam(AsyncWebParameter*); + void _addPathParam(const char *param); + + bool _parseReqHead(); + bool _parseReqHeader(); + void _parseLine(); + void _parsePlainPostChar(uint8_t data); + void _parseMultipartPostByte(uint8_t data, bool last); + void _addGetParams(const String& params); + + void _handleUploadStart(); + void _handleUploadByte(uint8_t data, bool last); + void _handleUploadEnd(); + + public: + File _tempFile; + void *_tempObject; + + AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); + ~AsyncWebServerRequest(); + + AsyncClient* client(){ return _client; } + uint8_t version() const { return _version; } + WebRequestMethodComposite method() const { return _method; } + const String& url() const { return _url; } + const String& host() const { return _host; } + const String& contentType() const { return _contentType; } + size_t contentLength() const { return _contentLength; } + bool multipart() const { return _isMultipart; } + const __FlashStringHelper *methodToString() const; + const __FlashStringHelper *requestedConnTypeToString() const; + RequestedConnectionType requestedConnType() const { return _reqconntype; } + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); + void onDisconnect (ArDisconnectHandler fn); + + //hash is the string representation of: + // base64(user:pass) for basic or + // user:realm:md5(user:realm:pass) for digest + bool authenticate(const char * hash); + bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); + void requestAuthentication(const char * realm = NULL, bool isDigest = true); + + void setHandler(AsyncWebHandler *handler){ _handler = handler; } + void addInterestingHeader(const String& name); + + void redirect(const String& url); + + void send(AsyncWebServerResponse *response); + void send(int code, const String& contentType=String(), const String& content=String()); + void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); + AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + size_t headers() const; // get header count + bool hasHeader(const String& name) const; // check if header exists + bool hasHeader(const __FlashStringHelper * data) const; // check if header exists + + AsyncWebHeader* getHeader(const String& name); + const AsyncWebHeader* getHeader(const String& name) const; + AsyncWebHeader* getHeader(const __FlashStringHelper * data); + const AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; + AsyncWebHeader* getHeader(size_t num); + const AsyncWebHeader* getHeader(size_t num) const; + + size_t params() const; // get arguments count + bool hasParam(const String& name, bool post=false, bool file=false) const; + bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; + + AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; + AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; + AsyncWebParameter* getParam(size_t num) const; + + size_t args() const { return params(); } // get arguments count + const String& arg(const String& name) const; // get request argument value by name + const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) + const String& arg(size_t i) const; // get request argument value by number + const String& argName(size_t i) const; // get request argument name by number + bool hasArg(const char* name) const; // check if argument exists + bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists + + const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; + + const String& header(const char* name) const;// get request header value by name + const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) + const String& header(size_t i) const; // get request header value by number + const String& headerName(size_t i) const; // get request header name by number + String urlDecode(const String& text) const; +}; + +/* + * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) + * */ + +typedef std::function ArRequestFilterFunction; + +bool ON_STA_FILTER(AsyncWebServerRequest *request); + +bool ON_AP_FILTER(AsyncWebServerRequest *request); + +/* + * REWRITE :: One instance can be handle any Request (done by the Server) + * */ + +class AsyncWebRewrite { + protected: + String _from; + String _toUrl; + String _params; + ArRequestFilterFunction _filter; + public: + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ + int index = _toUrl.indexOf('?'); + if (index > 0) { + _params = _toUrl.substring(index +1); + _toUrl = _toUrl.substring(0, index); + } + } + virtual ~AsyncWebRewrite(){} + AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } + const String& from(void) const { return _from; } + const String& toUrl(void) const { return _toUrl; } + const String& params(void) const { return _params; } + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } +}; + +/* + * HANDLER :: One instance can be attached to any Request (done by the Server) + * */ + +class AsyncWebHandler { + protected: + ArRequestFilterFunction _filter; + String _username; + String _password; + public: + AsyncWebHandler():_username(""), _password(""){} + AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; + AsyncWebHandler& setAuthentication(const String& username, const String& password){ _username = username;_password = password; return *this; }; + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } + virtual ~AsyncWebHandler(){} + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ + return false; + } + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} + virtual bool isRequestHandlerTrivial(){return true;} +}; + +/* + * RESPONSE :: One instance is created for each Request (attached by the Handler) + * */ + +typedef enum { + RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED +} WebResponseState; + +class AsyncWebServerResponse { + protected: + int _code; + std::list _headers; + String _contentType; + size_t _contentLength; + bool _sendContentLength; + bool _chunked; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + size_t _writtenLength; + WebResponseState _state; + const char* _responseCodeToString(int code); +public: + static const __FlashStringHelper *responseCodeToString(int code); + + public: + AsyncWebServerResponse(); + virtual ~AsyncWebServerResponse(); + virtual void setCode(int code); + virtual void setContentLength(size_t len); + virtual void setContentType(const String& type); + virtual void addHeader(const String& name, const String& value); + virtual String _assembleHead(uint8_t version); + virtual bool _started() const; + virtual bool _finished() const; + virtual bool _failed() const; + virtual bool _sourceValid() const; + virtual void _respond(AsyncWebServerRequest *request); + virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +/* + * SERVER :: One instance + * */ + +typedef std::function ArRequestHandlerFunction; +typedef std::function ArUploadHandlerFunction; +typedef std::function ArBodyHandlerFunction; + +class AsyncWebServer { + protected: + AsyncServer _server; + AlternativeLinkedList _rewrites; + AlternativeLinkedList _handlers; + AsyncCallbackWebHandler* _catchAllHandler; + + public: + AsyncWebServer(uint16_t port); + ~AsyncWebServer(); + + void begin(); + void end(); + +#if ASYNC_TCP_SSL_ENABLED + void onSslFileRequest(AcSSlFileHandler cb, void* arg); + void beginSecure(const char *cert, const char *private_key_file, const char *password); +#endif + + AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); + bool removeRewrite(AsyncWebRewrite* rewrite); + AsyncWebRewrite& rewrite(const char* from, const char* to); + + AsyncWebHandler& addHandler(AsyncWebHandler* handler); + bool removeHandler(AsyncWebHandler* handler); + + AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + + AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); + + void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned + void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads + void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) + + void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody + + void _handleDisconnect(AsyncWebServerRequest *request); + void _attachHandler(AsyncWebServerRequest *request); + void _rewriteRequest(AsyncWebServerRequest *request); +}; + +class DefaultHeaders { + using headers_t = std::list; + headers_t _headers; + +public: + DefaultHeaders() = default; + + using ConstIterator = headers_t::const_iterator; + + void addHeader(const String& name, const String& value){ + _headers.emplace_back(name, value); + } + + ConstIterator begin() const { return _headers.begin(); } + ConstIterator end() const { return _headers.end(); } + + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; + + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#include "WebResponseImpl.h" +#include "WebHandlerImpl.h" +#include "AsyncWebSocket.h" +#include "AsyncEventSource.h" + +#endif /* _AsyncWebServer_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h b/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h new file mode 100644 index 000000000..ca6a11262 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h @@ -0,0 +1,2 @@ +// to please Arduino Lint +#include "ESPAsyncWebServer.h" diff --git a/libraries/ESPAsyncWebServer/src/StringArray.h b/libraries/ESPAsyncWebServer/src/StringArray.h new file mode 100644 index 000000000..39b175a35 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/StringArray.h @@ -0,0 +1,174 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef STRINGARRAY_H_ +#define STRINGARRAY_H_ + +#include "stddef.h" +#include "WString.h" + +template +class AlternativeLinkedListNode { + T _value; + public: + AlternativeLinkedListNode* next; + AlternativeLinkedListNode(const T val): _value(val), next(nullptr) {} + ~AlternativeLinkedListNode(){} + const T& value() const { return _value; }; + T& value(){ return _value; } +}; + +template class Item = AlternativeLinkedListNode> +class AlternativeLinkedList { + public: + typedef Item ItemType; + typedef std::function OnRemove; + typedef std::function Predicate; + private: + ItemType* _root; + OnRemove _onRemove; + + class Iterator { + ItemType* _node; + public: + Iterator(ItemType* current = nullptr) : _node(current) {} + Iterator(const Iterator& i) : _node(i._node) {} + Iterator& operator ++() { _node = _node->next; return *this; } + bool operator != (const Iterator& i) const { return _node != i._node; } + const T& operator * () const { return _node->value(); } + const T* operator -> () const { return &_node->value(); } + }; + + public: + typedef const Iterator ConstIterator; + ConstIterator begin() const { return ConstIterator(_root); } + ConstIterator end() const { return ConstIterator(nullptr); } + + AlternativeLinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} + ~AlternativeLinkedList(){} + void add(const T& t){ + auto it = new ItemType(t); + if(!_root){ + _root = it; + } else { + auto i = _root; + while(i->next) i = i->next; + i->next = it; + } + } + T& front() const { + return _root->value(); + } + + bool isEmpty() const { + return _root == nullptr; + } + size_t length() const { + size_t i = 0; + auto it = _root; + while(it){ + i++; + it = it->next; + } + return i; + } + size_t count_if(Predicate predicate) const { + size_t i = 0; + auto it = _root; + while(it){ + if (!predicate){ + i++; + } + else if (predicate(it->value())) { + i++; + } + it = it->next; + } + return i; + } + const T* nth(size_t N) const { + size_t i = 0; + auto it = _root; + while(it){ + if(i++ == N) + return &(it->value()); + it = it->next; + } + return nullptr; + } + bool remove(const T& t){ + auto it = _root; + auto pit = _root; + while(it){ + if(it->value() == t){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + + if (_onRemove) { + _onRemove(it->value()); + } + + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + bool remove_first(Predicate predicate){ + auto it = _root; + auto pit = _root; + while(it){ + if(predicate(it->value())){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + + void free(){ + while(_root != nullptr){ + auto it = _root; + _root = _root->next; + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + } + _root = nullptr; + } +}; + +#endif /* STRINGARRAY_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp b/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp new file mode 100644 index 000000000..2f122d7cd --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp @@ -0,0 +1,247 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WebAuthentication.h" +#include +#ifdef ESP32 +#include "mbedtls/md5.h" +#else +#include "md5.h" +#endif + + +// Basic Auth hash = base64("username:password") + +bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ + if(username == NULL || password == NULL || hash == NULL) + return false; + + size_t toencodeLen = strlen(username)+strlen(password)+1; + size_t encodedLen = base64_encode_expected_len(toencodeLen); + if(strlen(hash) != encodedLen) +// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 +#ifdef ARDUINO_ARCH_ESP32 + if(strlen(hash) != encodedLen) +#else + if (strlen(hash) != encodedLen - 1) +#endif + return false; + + char *toencode = new char[toencodeLen+1]; + if(toencode == NULL){ + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + delete[] toencode; + return false; + } + sprintf_P(toencode, PSTR("%s:%s"), username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + return false; +} + +static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more +#ifdef ESP32 + mbedtls_md5_context _ctx; +#else + md5_context_t _ctx; +#endif + uint8_t i; + uint8_t * _buf = (uint8_t*)malloc(16); + if(_buf == NULL) + return false; + memset(_buf, 0x00, 16); +#ifdef ESP32 + mbedtls_md5_init(&_ctx); +#if ESP_IDF_VERSION_MAJOR == 5 + mbedtls_md5_starts(&_ctx); + mbedtls_md5_update(&_ctx, data, len); + mbedtls_md5_finish(&_ctx, _buf); +#else + mbedtls_md5_starts_ret(&_ctx); + mbedtls_md5_update_ret(&_ctx, data, len); + mbedtls_md5_finish_ret(&_ctx, _buf); +#endif +#else + MD5Init(&_ctx); + MD5Update(&_ctx, data, len); + MD5Final(_buf, &_ctx); +#endif + for(i = 0; i < 16; i++) { + sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); + } + free(_buf); + return true; +} + +static String genRandomMD5(){ +#ifdef ESP8266 + uint32_t r = RANDOM_REG32; +#else + uint32_t r = rand(); +#endif + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +static String stringMD5(const String& in){ + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +String generateDigestHash(const char * username, const char * password, const char * realm){ + if(username == NULL || password == NULL || realm == NULL){ + return emptyString; + } + char * out = (char*)malloc(33); + String res = String(username); + res += ':'; + res.concat(realm); + res += ':'; + String in = res; + in.concat(password); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + res.concat(out); + free(out); + return res; +} + +String requestDigestAuthentication(const char * realm){ + String header = F("realm=\""); + if(realm == NULL) + header.concat(F("asyncesp")); + else + header.concat(realm); + header.concat(F("\", qop=\"auth\", nonce=\"")); + header.concat(genRandomMD5()); + header.concat(F("\", opaque=\"")); + header.concat(genRandomMD5()); + header += '"'; + return header; +} + +bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ + if(username == NULL || password == NULL || header == NULL || method == NULL){ + //os_printf("AUTH FAIL: missing requred fields\n"); + return false; + } + + String myHeader = String(header); + int nextBreak = myHeader.indexOf(','); + if(nextBreak < 0){ + //os_printf("AUTH FAIL: no variables\n"); + return false; + } + + String myUsername = String(); + String myRealm = String(); + String myNonce = String(); + String myUri = String(); + String myResponse = String(); + String myQop = String(); + String myNc = String(); + String myCnonce = String(); + + myHeader += F(", "); + do { + String avLine = myHeader.substring(0, nextBreak); + avLine.trim(); + myHeader = myHeader.substring(nextBreak+1); + nextBreak = myHeader.indexOf(','); + + int eqSign = avLine.indexOf('='); + if(eqSign < 0){ + //os_printf("AUTH FAIL: no = sign\n"); + return false; + } + String varName = avLine.substring(0, eqSign); + avLine = avLine.substring(eqSign + 1); + if(avLine.startsWith(String('"'))){ + avLine = avLine.substring(1, avLine.length() - 1); + } + + if(varName.equals(F("username"))){ + if(!avLine.equals(username)){ + //os_printf("AUTH FAIL: username\n"); + return false; + } + myUsername = avLine; + } else if(varName.equals(F("realm"))){ + if(realm != NULL && !avLine.equals(realm)){ + //os_printf("AUTH FAIL: realm\n"); + return false; + } + myRealm = avLine; + } else if(varName.equals(F("nonce"))){ + if(nonce != NULL && !avLine.equals(nonce)){ + //os_printf("AUTH FAIL: nonce\n"); + return false; + } + myNonce = avLine; + } else if(varName.equals(F("opaque"))){ + if(opaque != NULL && !avLine.equals(opaque)){ + //os_printf("AUTH FAIL: opaque\n"); + return false; + } + } else if(varName.equals(F("uri"))){ + if(uri != NULL && !avLine.equals(uri)){ + //os_printf("AUTH FAIL: uri\n"); + return false; + } + myUri = avLine; + } else if(varName.equals(F("response"))){ + myResponse = avLine; + } else if(varName.equals(F("qop"))){ + myQop = avLine; + } else if(varName.equals(F("nc"))){ + myNc = avLine; + } else if(varName.equals(F("cnonce"))){ + myCnonce = avLine; + } + } while(nextBreak > 0); + + String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); + String ha2 = String(method) + ':' + myUri; + String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); + + if(myResponse.equals(stringMD5(response))){ + //os_printf("AUTH SUCCESS\n"); + return true; + } + + //os_printf("AUTH FAIL: password\n"); + return false; +} diff --git a/libraries/ESPAsyncWebServer/src/WebAuthentication.h b/libraries/ESPAsyncWebServer/src/WebAuthentication.h new file mode 100644 index 000000000..a6f1966e3 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebAuthentication.h @@ -0,0 +1,34 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WEB_AUTHENTICATION_H_ +#define WEB_AUTHENTICATION_H_ + +#include "Arduino.h" + +bool checkBasicAuthentication(const char * header, const char * username, const char * password); +String requestDigestAuthentication(const char * realm); +bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); + +//for storing hashed versions on the device that can be authenticated against +String generateDigestHash(const char * username, const char * password, const char * realm); + +#endif diff --git a/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h b/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h new file mode 100644 index 000000000..9b7ba1b04 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h @@ -0,0 +1,151 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ +#define ASYNCWEBSERVERHANDLERIMPL_H_ + +#include +#ifdef ASYNCWEBSERVER_REGEX +#include +#endif + +#include "stddef.h" +#include + +class AsyncStaticWebHandler: public AsyncWebHandler { + using File = fs::File; + using FS = fs::FS; + private: + bool _getFile(AsyncWebServerRequest *request); + bool _fileExists(AsyncWebServerRequest *request, const String& path); + uint8_t _countBits(const uint8_t value) const; + protected: + FS _fs; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + AwsTemplateProcessor _callback; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + public: + AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + AsyncStaticWebHandler& setIsDir(bool isDir); + AsyncStaticWebHandler& setDefaultFile(const char* filename); + AsyncStaticWebHandler& setCacheControl(const char* cache_control); + AsyncStaticWebHandler& setLastModified(const char* last_modified); + AsyncStaticWebHandler& setLastModified(struct tm* last_modified); + #ifdef ESP8266 + AsyncStaticWebHandler& setLastModified(time_t last_modified); + AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated + #endif + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +class AsyncCallbackWebHandler: public AsyncWebHandler { + private: + protected: + String _uri; + WebRequestMethodComposite _method; + ArRequestHandlerFunction _onRequest; + ArUploadHandlerFunction _onUpload; + ArBodyHandlerFunction _onBody; + bool _isRegex; + public: + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} + void setUri(const String& uri){ + _uri = uri; + _isRegex = uri.startsWith("^") && uri.endsWith("$"); + } + void setMethod(WebRequestMethodComposite method){ _method = method; } + void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } + void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } + void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } + + virtual bool canHandle(AsyncWebServerRequest *request) override final{ + + if(!_onRequest) + return false; + + if(!(_method & request->method())) + return false; + +#ifdef ASYNCWEBSERVER_REGEX + if (_isRegex) { + std::regex pattern(_uri.c_str()); + std::smatch matches; + std::string s(request->url().c_str()); + if(std::regex_search(s, matches, pattern)) { + for (size_t i = 1; i < matches.size(); ++i) { // start from 1 + request->_addPathParam(matches[i].str().c_str()); + } + } else { + return false; + } + } else +#endif + if (_uri.length() && _uri.startsWith("/*.")) { + String uriTemplate = String (_uri); + uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); + if (!request->url().endsWith(uriTemplate)) + return false; + } + else + if (_uri.length() && _uri.endsWith("*")) { + String uriTemplate = String(_uri); + uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); + if (!request->url().startsWith(uriTemplate)) + return false; + } + else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest *request) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onRequest) + _onRequest(request); + else + request->send(500); + } + virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onUpload) + _onUpload(request, filename, index, data, len, final); + } + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onBody) + _onBody(request, data, len, index, total); + } + virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} +}; + +#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebHandlers.cpp b/libraries/ESPAsyncWebServer/src/WebHandlers.cpp new file mode 100644 index 000000000..0b55b0ddf --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebHandlers.cpp @@ -0,0 +1,233 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length()-1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); + if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ + _last_modified = last_modified; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ + auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); + char format[strlen_P(formatP) + 1]; + strcpy_P(format, formatP); + + char result[30]; + strftime(result, sizeof(result), format, last_modified); + return setLastModified((const char *)result); +} + +#ifdef ESP8266 +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ + return setLastModified((struct tm *)gmtime(&last_modified)); +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ + time_t last_modified; + if(time(&last_modified) == 0) //time is not yet set + return *this; + return setLastModified(last_modified); +} +#endif +bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET + || !request->url().startsWith(_uri) + || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) + ){ + return false; + } + if (_getFile(request)) { + // We interested in "If-Modified-Since" header to check if file was modified + if (_last_modified.length()) + request->addInterestingHeader(F("If-Modified-Since")); + + if(_cache_control.length()) + request->addInterestingHeader(F("If-None-Match")); + + return true; + } + + return false; +} + +bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) +{ + // Remove the found uri + String path = request->url().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(request, path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') + path += String('/'); + path += _default_file; + + return _fileExists(request, path); +} + +#ifdef ESP32 +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) +#else +#define FILE_IS_REAL(f) (f == true) +#endif + +bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) +{ + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + F(".gz"); + + if (_gzipFirst) { + if (_fs.exists(gzip)) { + request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); + gzipFound = FILE_IS_REAL(request->_tempFile); + } + if (!gzipFound){ + if (_fs.exists(path)) { + request->_tempFile = _fs.open(path, fs::FileOpenMode::read); + fileFound = FILE_IS_REAL(request->_tempFile); + } + } + } else { + if (_fs.exists(path)) { + request->_tempFile = _fs.open(path, fs::FileOpenMode::read); + fileFound = FILE_IS_REAL(request->_tempFile); + } + if (!fileFound){ + if (_fs.exists(gzip)) { + request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); + gzipFound = FILE_IS_REAL(request->_tempFile); + } + } + } + + bool found = fileFound || gzipFound; + + if (found) { + // Extract the file name from the path and keep it in _tempObject + size_t pathLen = path.length(); + char * _tempPath = (char*)malloc(pathLen+1); + snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str()); + request->_tempObject = (void*)_tempPath; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; +} + +void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) +{ + // Get the filename from request->_tempObject and free it + String filename = String((char*)request->_tempObject); + free(request->_tempObject); + request->_tempObject = NULL; + if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + + if (request->_tempFile == true) { + time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS) + if (lw) setLastModified(gmtime(&lw)); + String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size + if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) { + request->_tempFile.close(); + request->send(304); // Not modified + } else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) { + request->_tempFile.close(); + AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified + response->addHeader(F("Cache-Control"), _cache_control); + response->addHeader(F("ETag"), etag); + request->send(response); + } else { + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); + if (_last_modified.length()) + response->addHeader(F("Last-Modified"), _last_modified); + if (_cache_control.length()){ + response->addHeader(F("Cache-Control"), _cache_control); + response->addHeader(F("ETag"), etag); + } + request->send(response); + } + } else { + request->send(404); + } +} diff --git a/libraries/ESPAsyncWebServer/src/WebRequest.cpp b/libraries/ESPAsyncWebServer/src/WebRequest.cpp new file mode 100644 index 000000000..9b71f1691 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebRequest.cpp @@ -0,0 +1,993 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "WebAuthentication.h" + +#ifndef ESP8266 +#define os_strlen strlen +#endif + +#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) + +enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; + +AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) + : _client(c) + , _server(s) + , _handler(NULL) + , _response(NULL) + , _temp() + , _parseState(0) + , _version(0) + , _method(HTTP_ANY) + , _url() + , _host() + , _contentType() + , _boundary() + , _authorization() + , _reqconntype(RCT_HTTP) + , _isDigest(false) + , _isMultipart(false) + , _isPlainPost(false) + , _expectingContinue(false) + , _contentLength(0) + , _parsedLength(0) + , _params(AlternativeLinkedList([](AsyncWebParameter *p){ delete p; })) + , _multiParseState(0) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + , _tempObject(NULL) +{ + c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); + c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); + c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); + c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); + c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); + c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); +} + +AsyncWebServerRequest::~AsyncWebServerRequest(){ + _headers.clear(); + + _params.free(); + _pathParams.clear(); + + _interestingHeaders.clear(); + + if(_response != NULL){ + delete _response; + } + + if(_tempObject != NULL){ + free(_tempObject); + } + + if(_tempFile){ + _tempFile.close(); + } + + if(_itemBuffer){ + free(_itemBuffer); + } + +} + +void AsyncWebServerRequest::_onData(void *buf, size_t len){ + size_t i = 0; + while (true) { + + if(_parseState < PARSE_REQ_BODY){ + // Find new line in buf + char *str = (char*)buf; + for (i = 0; i < len; i++) { + if (str[i] == '\n') { + break; + } + } + if (i == len) { // No new line, just add the buffer in _temp + char ch = str[len-1]; + str[len-1] = 0; + _temp.reserve(_temp.length()+len); + _temp.concat(str); + _temp.concat(ch); + } else { // Found new line - extract it and parse + str[i] = 0; // Terminate the string at the end of the line. + _temp.concat(str); + _temp.trim(); + _parseLine(); + if (++i < len) { + // Still have more buffer to process + buf = str+i; + len-= i; + continue; + } + } + } else if(_parseState == PARSE_REQ_BODY){ + // A handler should be already attached at this point in _parseLine function. + // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. + const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); + if(_isMultipart){ + if(needParse){ + size_t i; + for(i=0; i end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); + start = end + 1; + } +} + +bool AsyncWebServerRequest::_parseReqHead(){ + // Split the head into method, url and version + int index = _temp.indexOf(' '); + String m = _temp.substring(0, index); + index = _temp.indexOf(' ', index+1); + String u = _temp.substring(m.length()+1, index); + _temp = _temp.substring(index+1); + + if(m == F("GET")){ + _method = HTTP_GET; + } else if(m == F("POST")){ + _method = HTTP_POST; + } else if(m == F("DELETE")){ + _method = HTTP_DELETE; + } else if(m == F("PUT")){ + _method = HTTP_PUT; + } else if(m == F("PATCH")){ + _method = HTTP_PATCH; + } else if(m == F("HEAD")){ + _method = HTTP_HEAD; + } else if(m == F("OPTIONS")){ + _method = HTTP_OPTIONS; + } + + String g; + index = u.indexOf('?'); + if(index > 0){ + g = u.substring(index +1); + u = u.substring(0, index); + } + _url = urlDecode(u); + _addGetParams(g); + + if(!_temp.startsWith(F("HTTP/1.0"))) + _version = 1; + + _temp = String(); + return true; +} + +bool strContains(const String &src, const String &find, bool mindcase = true) { + int pos=0, i=0; + const int slen = src.length(); + const int flen = find.length(); + + if (slen < flen) return false; + while (pos <= (slen - flen)) { + for (i=0; i < flen; i++) { + if (mindcase) { + if (src[pos+i] != find[i]) i = flen + 1; // no match + } + else if (tolower(src[pos+i]) != tolower(find[i])) { + i = flen + 1; // no match + } + } + if (i == flen) return true; + pos++; + } + return false; +} + +bool AsyncWebServerRequest::_parseReqHeader(){ + int index = _temp.indexOf(':'); + if(index){ + String name = _temp.substring(0, index); + String value = _temp.substring(index + 2); + if(name.equalsIgnoreCase("Host")){ + _host = value; + } else if(name.equalsIgnoreCase(F("Content-Type"))){ + _contentType = value.substring(0, value.indexOf(';')); + if (value.startsWith(F("multipart/"))){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace(String('"'), String()); + _isMultipart = true; + } + } else if(name.equalsIgnoreCase(F("Content-Length"))){ + _contentLength = atoi(value.c_str()); + } else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){ + _expectingContinue = true; + } else if(name.equalsIgnoreCase(F("Authorization"))){ + if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){ + _authorization = value.substring(6); + } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){ + _isDigest = true; + _authorization = value.substring(7); + } + } else { + if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){ + // WebSocket request can be uniquely identified by header: [Upgrade: websocket] + _reqconntype = RCT_WS; + } else { + if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){ + // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] + _reqconntype = RCT_EVENT; + } + } + } + _headers.emplace_back(name, value); + } + _temp = String(); + return true; +} + +void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ + if(data && (char)data != '&') + _temp += (char)data; + if(!data || (char)data == '&' || _parsedLength == _contentLength){ + String name = F("body"); + String value = _temp; + if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){ + name = _temp.substring(0, _temp.indexOf('=')); + value = _temp.substring(_temp.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); + _temp = String(); + } +} + +void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ + _itemBuffer[_itemBufferIndex++] = data; + + if(last || _itemBufferIndex == 1460){ + //check if authenticated before calling the upload + if(_handler) + _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); + _itemBufferIndex = 0; + } +} + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ +#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) + + if(!_parsedLength){ + _multiParseState = EXPECT_BOUNDARY; + _temp = String(); + _itemName = String(); + _itemFilename = String(); + _itemType = String(); + } + + if(_multiParseState == WAIT_FOR_RETURN1){ + if(data != '\r'){ + itemWriteByte(data); + } else { + _multiParseState = EXPECT_FEED1; + } + } else if(_multiParseState == EXPECT_BOUNDARY){ + if(_parsedLength < 2 && data != '-'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 3 == _boundary.length()){ + if(data != '\n'){ + _multiParseState = PARSE_ERROR; + return; + } + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } + } else if(_multiParseState == PARSE_HEADERS){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n'){ + if(_temp.length()){ + if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(1460); + if(_itemBuffer == NULL){ + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + //check if authenticated before calling the upload + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ + //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); + _contentLength = _parsedLength + 4;//lets close the request gracefully + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _contentLength == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} + +void AsyncWebServerRequest::_parseLine(){ + if(_parseState == PARSE_REQ_START){ + if(!_temp.length()){ + _parseState = PARSE_REQ_FAIL; + _client->close(); + } else { + _parseReqHead(); + _parseState = PARSE_REQ_HEADERS; + } + return; + } + + if(_parseState == PARSE_REQ_HEADERS){ + if(!_temp.length()){ + //end of headers + _server->_rewriteRequest(this); + _server->_attachHandler(this); + _removeNotInterestingHeaders(); + if(_expectingContinue){ + String response = F("HTTP/1.1 100 Continue\r\n\r\n"); + _client->write(response.c_str(), response.length()); + } + //check handler for authentication + if(_contentLength){ + _parseState = PARSE_REQ_BODY; + } else { + _parseState = PARSE_REQ_END; + if(_handler) _handler->handleRequest(this); + else send(501); + } + } else _parseReqHeader(); + } +} + +size_t AsyncWebServerRequest::headers() const{ + return _headers.size(); +} + +bool AsyncWebServerRequest::hasHeader(const String& name) const { + for(const auto& h: _headers){ + if(h.name().equalsIgnoreCase(name)){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { + return hasHeader(String(data)); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) { + auto iter = std::find_if(std::begin(_headers), std::end(_headers), + [&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); }); + + if (iter == std::end(_headers)) + return nullptr; + + return &(*iter); +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { + auto iter = std::find_if(std::begin(_headers), std::end(_headers), + [&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); }); + + if (iter == std::end(_headers)) + return nullptr; + + return &(*iter); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + AsyncWebHeader* result = getHeader( String(name)); + free(name); + return result; + } else { + return nullptr; + } +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + const AsyncWebHeader* result = getHeader( String(name)); + free(name); + return result; + } else { + return nullptr; + } +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) { + if (num >= _headers.size()) + return nullptr; + return &(*std::next(std::begin(_headers), num)); +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { + if (num >= _headers.size()) + return nullptr; + return &(*std::next(std::begin(_headers), num)); +} + +size_t AsyncWebServerRequest::params() const { + return _params.length(); +} + +bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { + return hasParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return p; + } + } + return nullptr; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { + return getParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { + auto param = _params.nth(num); + return param ? *param : nullptr; +} + +void AsyncWebServerRequest::addInterestingHeader(const String& name){ + if(std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), + [&name](const String &str){ return str.equalsIgnoreCase(name); })) + _interestingHeaders.push_back(name); +} + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ + _response = response; + if(_response == NULL){ + _client->close(true); + _onDisconnect(); + return; + } + if(!_response->_sourceValid()){ + delete response; + _response = NULL; + send(500); + } + else { + _client->setRxTimeout(0); + _response->_respond(this); + } +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ + return new AsyncBasicResponse(code, contentType, content); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))) + return new AsyncFileResponse(fs, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true) + return new AsyncFileResponse(content, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + return new AsyncStreamResponse(stream, contentType, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + return new AsyncCallbackResponse(contentType, len, callback, templateCallback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + if(_version) + return new AsyncChunkedResponse(contentType, callback, templateCallback); + return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); +} + +AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ + return new AsyncResponseStream(contentType, bufferSize); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + return new AsyncProgmemResponse(code, contentType, content, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); +} + +void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ + send(beginResponse(code, contentType, content)); +} + +void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){ + send(beginResponse(fs, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true){ + send(beginResponse(content, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + send(beginResponse(stream, contentType, len, callback)); +} + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginResponse(contentType, len, callback, templateCallback)); +} + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginChunkedResponse(contentType, callback, templateCallback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, len, callback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, callback)); +} + +void AsyncWebServerRequest::redirect(const String& url){ + AsyncWebServerResponse * response = beginResponse(302); + response->addHeader(F("Location"), url); + send(response); +} + +bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ + if(_authorization.length()){ + if(_isDigest) + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); + else if(!passwordIsHash) + return checkBasicAuthentication(_authorization.c_str(), username, password); + else + return _authorization.equals(password); + } + return false; +} + +bool AsyncWebServerRequest::authenticate(const char * hash){ + if(!_authorization.length() || hash == NULL) + return false; + + if(_isDigest){ + String hStr = String(hash); + int separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String username = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String realm = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); + } + + return (_authorization.equals(hash)); +} + +void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ + AsyncWebServerResponse * r = beginResponse(401); + if(!isDigest && realm == NULL){ + r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); + } else if(!isDigest){ + String header = F("Basic realm=\""); + header.concat(realm); + header += '"'; + r->addHeader(F("WWW-Authenticate"), header); + } else { + String header = F("Digest "); + header.concat(requestDigestAuthentication(realm)); + r->addHeader(F("WWW-Authenticate"), header); + } + send(r); +} + +bool AsyncWebServerRequest::hasArg(const char* name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { + return hasArg(String(data).c_str()); +} + + +const String& AsyncWebServerRequest::arg(const String& name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return arg->value(); + } + } + return emptyString; +} + +const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { + return arg(String(data).c_str()); +} + +const String& AsyncWebServerRequest::arg(size_t i) const { + return getParam(i)->value(); +} + +const String& AsyncWebServerRequest::argName(size_t i) const { + return getParam(i)->name(); +} + +const String& AsyncWebServerRequest::pathArg(size_t i) const { + return i < _pathParams.size() ? _pathParams[i] : emptyString; +} + +const String& AsyncWebServerRequest::header(const char* name) const { + const AsyncWebHeader* h = getHeader(String(name)); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { + return header(String(data).c_str()); +}; + + +const String& AsyncWebServerRequest::header(size_t i) const { + const AsyncWebHeader* h = getHeader(i); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::headerName(size_t i) const { + const AsyncWebHeader* h = getHeader(i); + return h ? h->name() : emptyString; +} + +String AsyncWebServerRequest::urlDecode(const String& text) const { + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + String decoded = String(); + decoded.reserve(len); // Allocate the string internal buffer - never longer from source text + while (i < len){ + char decodedChar; + char encodedChar = text.charAt(i++); + if ((encodedChar == '%') && (i + 1 < len)){ + temp[2] = text.charAt(i++); + temp[3] = text.charAt(i++); + decodedChar = strtol(temp, NULL, 16); + } else if (encodedChar == '+') { + decodedChar = ' '; + } else { + decodedChar = encodedChar; // normal ascii char + } + decoded.concat(decodedChar); + } + return decoded; +} + + +const __FlashStringHelper *AsyncWebServerRequest::methodToString() const { + if(_method == HTTP_ANY) return F("ANY"); + else if(_method & HTTP_GET) return F("GET"); + else if(_method & HTTP_POST) return F("POST"); + else if(_method & HTTP_DELETE) return F("DELETE"); + else if(_method & HTTP_PUT) return F("PUT"); + else if(_method & HTTP_PATCH) return F("PATCH"); + else if(_method & HTTP_HEAD) return F("HEAD"); + else if(_method & HTTP_OPTIONS) return F("OPTIONS"); + return F("UNKNOWN"); +} + +const __FlashStringHelper *AsyncWebServerRequest::requestedConnTypeToString() const { + switch (_reqconntype) { + case RCT_NOT_USED: return F("RCT_NOT_USED"); + case RCT_DEFAULT: return F("RCT_DEFAULT"); + case RCT_HTTP: return F("RCT_HTTP"); + case RCT_WS: return F("RCT_WS"); + case RCT_EVENT: return F("RCT_EVENT"); + default: return F("ERROR"); + } +} + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { + bool res = false; + if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; + if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; + if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; + return res; +} diff --git a/libraries/ESPAsyncWebServer/src/WebResponseImpl.h b/libraries/ESPAsyncWebServer/src/WebResponseImpl.h new file mode 100644 index 000000000..4a472254b --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebResponseImpl.h @@ -0,0 +1,138 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ +#define ASYNCWEBSERVERRESPONSEIMPL_H_ + +#ifdef Arduino_h +// arduino is not compatible with std::vector +#undef min +#undef max +#endif +#include +#include + +// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. + +class AsyncBasicResponse: public AsyncWebServerResponse { + private: + String _content; + public: + AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + +class AsyncAbstractResponse: public AsyncWebServerResponse { + private: + String _head; + // Data is inserted into cache at begin(). + // This is inefficient with vector, but if we use some other container, + // we won't be able to access it as contiguous array of bytes when reading from it, + // so by gaining performance in one place, we'll lose it in another. + std::vector _cache; + size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); + size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); + protected: + AwsTemplateProcessor _callback; + public: + AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return false; } + virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } +}; + +#ifndef TEMPLATE_PLACEHOLDER +#define TEMPLATE_PLACEHOLDER '%' +#endif + +#define TEMPLATE_PARAM_NAME_LENGTH 32 +class AsyncFileResponse: public AsyncAbstractResponse { + using File = fs::File; + using FS = fs::FS; + private: + File _content; + String _path; + void _setContentType(const String& path); + public: + AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + ~AsyncFileResponse(); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncStreamResponse: public AsyncAbstractResponse { + private: + Stream *_content; + public: + AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncCallbackResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncChunkedResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncProgmemResponse: public AsyncAbstractResponse { + private: + const uint8_t * _content; + size_t _readLength; + public: + AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return true; } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class cbuf; + +class AsyncResponseStream: public AsyncAbstractResponse, public Print { + private: + std::unique_ptr _content; + public: + AsyncResponseStream(const String& contentType, size_t bufferSize); + ~AsyncResponseStream(); + bool _sourceValid() const { return (_state < RESPONSE_END); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); + using Print::write; +}; + +#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebResponses.cpp b/libraries/ESPAsyncWebServer/src/WebResponses.cpp new file mode 100644 index 000000000..a783de0f4 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebResponses.cpp @@ -0,0 +1,704 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "cbuf.h" + +// Since ESP8266 does not link memchr by default, here's its implementation. +void* memchr(void* ptr, int ch, size_t count) +{ + unsigned char* p = static_cast(ptr); + while(count--) + if(*p++ == static_cast(ch)) + return --p; + return nullptr; +} + + +/* + * Abstract Response + * */ +const char* AsyncWebServerResponse::_responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); +} + +const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { + switch (code) { + case 100: return F("Continue"); + case 101: return F("Switching Protocols"); + case 200: return F("OK"); + case 201: return F("Created"); + case 202: return F("Accepted"); + case 203: return F("Non-Authoritative Information"); + case 204: return F("No Content"); + case 205: return F("Reset Content"); + case 206: return F("Partial Content"); + case 300: return F("Multiple Choices"); + case 301: return F("Moved Permanently"); + case 302: return F("Found"); + case 303: return F("See Other"); + case 304: return F("Not Modified"); + case 305: return F("Use Proxy"); + case 307: return F("Temporary Redirect"); + case 400: return F("Bad Request"); + case 401: return F("Unauthorized"); + case 402: return F("Payment Required"); + case 403: return F("Forbidden"); + case 404: return F("Not Found"); + case 405: return F("Method Not Allowed"); + case 406: return F("Not Acceptable"); + case 407: return F("Proxy Authentication Required"); + case 408: return F("Request Time-out"); + case 409: return F("Conflict"); + case 410: return F("Gone"); + case 411: return F("Length Required"); + case 412: return F("Precondition Failed"); + case 413: return F("Request Entity Too Large"); + case 414: return F("Request-URI Too Large"); + case 415: return F("Unsupported Media Type"); + case 416: return F("Requested range not satisfiable"); + case 417: return F("Expectation Failed"); + case 500: return F("Internal Server Error"); + case 501: return F("Not Implemented"); + case 502: return F("Bad Gateway"); + case 503: return F("Service Unavailable"); + case 504: return F("Gateway Time-out"); + case 505: return F("HTTP Version not supported"); + default: return F(""); + } +} + +AsyncWebServerResponse::AsyncWebServerResponse() + : _code(0) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) +{ + for(const auto &header: DefaultHeaders::Instance()) { + _headers.emplace_back(header); + } +} + +AsyncWebServerResponse::~AsyncWebServerResponse() = default; + +void AsyncWebServerResponse::setCode(int code){ + if(_state == RESPONSE_SETUP) + _code = code; +} + +void AsyncWebServerResponse::setContentLength(size_t len){ + if(_state == RESPONSE_SETUP) + _contentLength = len; +} + +void AsyncWebServerResponse::setContentType(const String& type){ + if(_state == RESPONSE_SETUP) + _contentType = type; +} + +void AsyncWebServerResponse::addHeader(const String& name, const String& value){ + _headers.emplace_back(name, value); +} + +String AsyncWebServerResponse::_assembleHead(uint8_t version){ + if(version){ + addHeader(F("Accept-Ranges"), F("none")); + if(_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; + + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); + out.concat(buf); + + if(_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if(_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } + + for(const auto& header: _headers){ + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str()); + out.concat(buf); + } + _headers.clear(); + + out.concat(F("\r\n")); + _headLength = out.length(); + return out; +} + +bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } +bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } +bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } +bool AsyncWebServerResponse::_sourceValid() const { return false; } +void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } + +/* + * String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ + _code = code; + _content = content; + _contentType = contentType; + if(_content.length()){ + _contentLength = _content.length(); + if(!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); +} + +void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if(!_contentLength && space >= outLen){ + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(_contentLength && space >= outLen + _contentLength){ + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(space && space < outLen){ + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if(space > outLen && space < (outLen + _contentLength)){ + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } +} + +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + _ackedLength += len; + if(_state == RESPONSE_CONTENT){ + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if(space > available){ + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= _writtenLength){ + _state = RESPONSE_END; + } + } + return 0; +} + + +/* + * Abstract Response + * */ + +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) +{ + // In case of template processing, we're unable to determine real response size + if(callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; + } +} + +void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + if(!_sourceValid()){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if(_state == RESPONSE_HEADERS){ + if(space >= headLen){ + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } + + if(_state == RESPONSE_CONTENT){ + size_t outLen; + if(_chunked){ + if(space <= 8){ + return 0; + } + outLen = space; + } else if(!_sendContentLength){ + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + + uint8_t *buf = (uint8_t *)malloc(outLen+headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if(headLen){ + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if(_chunked){ + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen; + while(outLen < headLen + 4) buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if(headLen){ + _head = String(); + } + + if(outLen){ + _writtenLength += request->client()->write((const char*)buf, outLen); + } + + if(_chunked){ + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if(_state == RESPONSE_WAIT_ACK){ + if(!_sendContentLength || _ackedLength >= _writtenLength){ + _state = RESPONSE_END; + if(!_chunked && !_sendContentLength) + request->client()->close(true); + } + } + return 0; +} + +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) +{ + // If we have something in cache, copy it to buffer + const size_t readFromCache = std::min(len, _cache.size()); + if(readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + } + // If we need to read more... + const size_t needFromFile = len - readFromCache; + const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); + return readFromCache + readFromContent; +} + +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) +{ + if(!_callback) + return _fillBuffer(data, len); + + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t* pTemplateStart = data; + while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if(pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if(paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if(readFromCacheOrContent) { + pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if(pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } + else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if(paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char* pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if(numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } + } + } // while(pTemplateStart) + return len; +} + + +/* + * File Response + * */ + +AsyncFileResponse::~AsyncFileResponse(){ + if(_content) + _content.close(); +} + +void AsyncFileResponse::_setContentType(const String& path){ +#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION + extern const __FlashStringHelper *getContentType(const String &path); + _contentType = getContentType(path); +#else + if (path.endsWith(F(".html"))) _contentType = F("text/html"); + else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); + else if (path.endsWith(F(".css"))) _contentType = F("text/css"); + else if (path.endsWith(F(".json"))) _contentType = F("application/json"); + else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); + else if (path.endsWith(F(".png"))) _contentType = F("image/png"); + else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); + else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); + else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); + else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); + else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); + else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); + else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); + else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); + else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); + else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); + else _contentType = F("text/plain"); +#endif +} + +AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + // set filename and force download + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof (buf), PSTR("inline")); + } + addHeader(F("Content-Disposition"), buf); +} + +AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof (buf), PSTR("inline")); + } + addHeader(F("Content-Disposition"), buf); +} + +size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ + return _content.read(data, len); +} + +/* + * Stream Response + * */ + +AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; +} + +size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t available = _content->available(); + size_t outLen = (available > len)?len:available; + size_t i; + for(i=0;iread(); + return outLen; +} + +/* + * Callback Response + * */ + +AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if(!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; +} + +size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Chunked Response + * */ + +AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; +} + +size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Progmem Response + * */ + +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; +} + +size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; +} + + +/* + * Response Stream (You can print/write/printf to it, up to the contentLen bytes) + * */ + +AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) +{ + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = std::unique_ptr(new cbuf(bufferSize)); //std::make_unique(bufferSize); +} + +AsyncResponseStream::~AsyncResponseStream() = default; + +size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ + return _content->read((char*)buf, maxLen); +} + +size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ + if(_started()) + return 0; + + if(len > _content->room()){ + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char*)data, len); + _contentLength += written; + return written; +} + +size_t AsyncResponseStream::write(uint8_t data){ + return write(&data, 1); +} diff --git a/libraries/ESPAsyncWebServer/src/WebServer.cpp b/libraries/ESPAsyncWebServer/src/WebServer.cpp new file mode 100644 index 000000000..2a531261e --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebServer.cpp @@ -0,0 +1,198 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +bool ON_STA_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() != request->client()->localIP(); +} + +#ifndef HAVE_FS_FILE_OPEN_MODE +const char *fs::FileOpenMode::read = "r"; +const char *fs::FileOpenMode::write = "w"; +const char *fs::FileOpenMode::append = "a"; +#endif + +AsyncWebServer::AsyncWebServer(uint16_t port) + : _server(port) + , _rewrites(AlternativeLinkedList([](AsyncWebRewrite* r){ delete r; })) + , _handlers(AlternativeLinkedList([](AsyncWebHandler* h){ delete h; })) +{ + _catchAllHandler = new AsyncCallbackWebHandler(); + if(_catchAllHandler == NULL) + return; + _server.onClient([](void *s, AsyncClient* c){ + if(c == NULL) + return; + c->setRxTimeout(3); + AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); + if(r == NULL){ + c->close(true); + c->free(); + delete c; + } + }, this); +} + +AsyncWebServer::~AsyncWebServer(){ + reset(); + end(); + if(_catchAllHandler) delete _catchAllHandler; +} + +AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ + _rewrites.add(rewrite); + return *rewrite; +} + +bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ + return _rewrites.remove(rewrite); +} + +AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ + return addRewrite(new AsyncWebRewrite(from, to)); +} + +AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ + _handlers.add(handler); + return *handler; +} + +bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ + return _handlers.remove(handler); +} + +void AsyncWebServer::begin(){ + _server.setNoDelay(true); + _server.begin(); +} + +void AsyncWebServer::end(){ + _server.end(); +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ + _server.onSslFileRequest(cb, arg); +} + +void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ + _server.beginSecure(cert, key, password); +} +#endif + +void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ + delete request; +} + +void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ + for(const auto& r: _rewrites){ + if (r->match(request)){ + request->_url = r->toUrl(); + request->_addGetParams(r->params()); + } + } +} + +void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ + for(const auto& h: _handlers){ + if (h->filter(request) && h->canHandle(request)){ + request->setHandler(h); + return; + } + } + + request->addInterestingHeader(F("ANY")); + request->setHandler(_catchAllHandler); +} + + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + handler->onBody(onBody); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ + AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); + addHandler(handler); + return *handler; +} + +void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ + _catchAllHandler->onRequest(fn); +} + +void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ + _catchAllHandler->onUpload(fn); +} + +void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ + _catchAllHandler->onBody(fn); +} + +void AsyncWebServer::reset(){ + _rewrites.free(); + _handlers.free(); + + if (_catchAllHandler != NULL){ + _catchAllHandler->onRequest(NULL); + _catchAllHandler->onUpload(NULL); + _catchAllHandler->onBody(NULL); + } +} + diff --git a/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl new file mode 100644 index 000000000..4d1a4e4cf Binary files /dev/null and b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl differ diff --git a/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl new file mode 100644 index 000000000..8d8f436dc Binary files /dev/null and b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl differ diff --git a/mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl b/mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl new file mode 100644 index 000000000..d35a66b88 Binary files /dev/null and b/mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl differ