diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a2b8cb..4147715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,12 +11,11 @@ include_directories(${PROJECT_NAME} lib/nuki_ble/src lib/ESP32_BLE_Arduino-1.0.1/src lib/WiFiManager - lib/pubsubclient/src/ + lib/pubsubclient/src + lib/EthernetWebServer/src ) file(GLOB SRCFILES -# "Lib/FreeRTOS/src/*.c" -# "Lib/FreeRTOS/src/*.cpp" Pins.h Network.cpp networkDevices/NetworkDevice.h @@ -45,6 +44,8 @@ file(GLOB_RECURSE SRCFILESREC lib/NimBLE-Arduino/src/*.c lib/NimBLE-Arduino/src/*.cpp lib/NimBLE-Arduino/src/*.h + lib/EthernetWebServer/src/*.cpp + lib/EthernetWebServer/src/*.h ) add_executable(${PROJECT_NAME} diff --git a/Network.cpp b/Network.cpp index 882d8ac..d21700b 100644 --- a/Network.cpp +++ b/Network.cpp @@ -1,5 +1,4 @@ #include "Network.h" -#include // https://github.com/tzapu/WiFiManager #include "Arduino.h" #include "MqttTopics.h" #include "PreferencesKeys.h" diff --git a/WebCfgServer.h b/WebCfgServer.h index e07fc4b..42481e9 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include "NukiWrapper.h" #include "Network.h" @@ -42,7 +42,7 @@ private: void waitAndProcess(const bool blocking, const uint32_t duration); - WebServer server; + EthernetWebServer server; NukiWrapper* _nuki; Network* _network; Preferences* _preferences; diff --git a/lib/EthernetWebServer/examples/AdvancedWebServer/AdvancedWebServer.ino b/lib/EthernetWebServer/examples/AdvancedWebServer/AdvancedWebServer.ino new file mode 100644 index 0000000..22ae426 --- /dev/null +++ b/lib/EthernetWebServer/examples/AdvancedWebServer/AdvancedWebServer.ino @@ -0,0 +1,147 @@ +/* + Copyright (c) 2015, Majenko Technologies + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + * * Neither the name of Majenko Technologies nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include + +const char *ssid = "YourSSIDHere"; +const char *password = "YourPSKHere"; + +WebServer server(80); + +const int led = 13; + +void handleRoot() { + digitalWrite(led, 1); + char temp[400]; + int sec = millis() / 1000; + int min = sec / 60; + int hr = min / 60; + + snprintf(temp, 400, + + "\ + \ + \ + ESP32 Demo\ + \ + \ + \ +

Hello from ESP32!

\ +

Uptime: %02d:%02d:%02d

\ + \ + \ +", + + hr, min % 60, sec % 60 + ); + server.send(200, "text/html", temp); + digitalWrite(led, 0); +} + +void handleNotFound() { + digitalWrite(led, 1); + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + + server.send(404, "text/plain", message); + digitalWrite(led, 0); +} + +void setup(void) { + pinMode(led, OUTPUT); + digitalWrite(led, 0); + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + if (MDNS.begin("esp32")) { + Serial.println("MDNS responder started"); + } + + server.on("/", handleRoot); + server.on("/test.svg", drawGraph); + server.on("/inline", []() { + server.send(200, "text/plain", "this works as well"); + }); + server.onNotFound(handleNotFound); + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} + +void drawGraph() { + String out = ""; + char temp[100]; + out += "\n"; + out += "\n"; + out += "\n"; + int y = rand() % 130; + for (int x = 10; x < 390; x += 10) { + int y2 = rand() % 130; + sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); + out += temp; + y = y2; + } + out += "\n\n"; + + server.send(200, "image/svg+xml", out); +} diff --git a/lib/EthernetWebServer/examples/FSBrowser/FSBrowser.ino b/lib/EthernetWebServer/examples/FSBrowser/FSBrowser.ino new file mode 100644 index 0000000..7c04a79 --- /dev/null +++ b/lib/EthernetWebServer/examples/FSBrowser/FSBrowser.ino @@ -0,0 +1,304 @@ +/* + FSWebServer - Example EthernetWebServer with FS backend for esp8266/esp32 + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the EthernetWebServer library 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 + + upload the contents of the data folder with MkSPIFFS Tool ("ESP32 Sketch Data Upload" in Tools menu in Arduino IDE) + or you can upload the contents of a folder if you CD in that folder and run the following command: + for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp32fs.local/edit; done + + access the sample web page at http://esp32fs.local + edit the page by going to http://esp32fs.local/edit +*/ +#include +#include +#include +#include + +#define FILESYSTEM SPIFFS +// You only need to format the filesystem once +#define FORMAT_FILESYSTEM false +#define DBG_OUTPUT_PORT Serial + +#if FILESYSTEM == FFat +#include +#endif +#if FILESYSTEM == SPIFFS +#include +#endif + +const char* ssid = "wifi-ssid"; +const char* password = "wifi-password"; +const char* host = "esp32fs"; +WebServer server(80); +//holds the current upload +File fsUploadFile; + +//format bytes +String formatBytes(size_t bytes) { + if (bytes < 1024) { + return String(bytes) + "B"; + } else if (bytes < (1024 * 1024)) { + return String(bytes / 1024.0) + "KB"; + } else if (bytes < (1024 * 1024 * 1024)) { + return String(bytes / 1024.0 / 1024.0) + "MB"; + } else { + return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; + } +} + +String getContentType(String filename) { + if (server.hasArg("download")) { + return "application/octet-stream"; + } else if (filename.endsWith(".htm")) { + return "text/html"; + } else if (filename.endsWith(".html")) { + return "text/html"; + } else if (filename.endsWith(".css")) { + return "text/css"; + } else if (filename.endsWith(".js")) { + return "application/javascript"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } else if (filename.endsWith(".jpg")) { + return "image/jpeg"; + } else if (filename.endsWith(".ico")) { + return "image/x-icon"; + } else if (filename.endsWith(".xml")) { + return "text/xml"; + } else if (filename.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (filename.endsWith(".zip")) { + return "application/x-zip"; + } else if (filename.endsWith(".gz")) { + return "application/x-gzip"; + } + return "text/plain"; +} + +bool exists(String path){ + bool yes = false; + File file = FILESYSTEM.open(path, "r"); + if(!file.isDirectory()){ + yes = true; + } + file.close(); + return yes; +} + +bool handleFileRead(String path) { + DBG_OUTPUT_PORT.println("handleFileRead: " + path); + if (path.endsWith("/")) { + path += "index.htm"; + } + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + if (exists(pathWithGz) || exists(path)) { + if (exists(pathWithGz)) { + path += ".gz"; + } + File file = FILESYSTEM.open(path, "r"); + server.streamFile(file, contentType); + file.close(); + return true; + } + return false; +} + +void handleFileUpload() { + if (server.uri() != "/edit") { + return; + } + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); + fsUploadFile = FILESYSTEM.open(filename, "w"); + filename = String(); + } else if (upload.status == UPLOAD_FILE_WRITE) { + //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); + if (fsUploadFile) { + fsUploadFile.write(upload.buf, upload.currentSize); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (fsUploadFile) { + fsUploadFile.close(); + } + DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); + } +} + +void handleFileDelete() { + if (server.args() == 0) { + return server.send(500, "text/plain", "BAD ARGS"); + } + String path = server.arg(0); + DBG_OUTPUT_PORT.println("handleFileDelete: " + path); + if (path == "/") { + return server.send(500, "text/plain", "BAD PATH"); + } + if (!exists(path)) { + return server.send(404, "text/plain", "FileNotFound"); + } + FILESYSTEM.remove(path); + server.send(200, "text/plain", ""); + path = String(); +} + +void handleFileCreate() { + if (server.args() == 0) { + return server.send(500, "text/plain", "BAD ARGS"); + } + String path = server.arg(0); + DBG_OUTPUT_PORT.println("handleFileCreate: " + path); + if (path == "/") { + return server.send(500, "text/plain", "BAD PATH"); + } + if (exists(path)) { + return server.send(500, "text/plain", "FILE EXISTS"); + } + File file = FILESYSTEM.open(path, "w"); + if (file) { + file.close(); + } else { + return server.send(500, "text/plain", "CREATE FAILED"); + } + server.send(200, "text/plain", ""); + path = String(); +} + +void handleFileList() { + if (!server.hasArg("dir")) { + server.send(500, "text/plain", "BAD ARGS"); + return; + } + + String path = server.arg("dir"); + DBG_OUTPUT_PORT.println("handleFileList: " + path); + + + File root = FILESYSTEM.open(path); + path = String(); + + String output = "["; + if(root.isDirectory()){ + File file = root.openNextFile(); + while(file){ + if (output != "[") { + output += ','; + } + output += "{\"type\":\""; + output += (file.isDirectory()) ? "dir" : "file"; + output += "\",\"name\":\""; + output += String(file.name()).substring(1); + output += "\"}"; + file = root.openNextFile(); + } + } + output += "]"; + server.send(200, "text/json", output); +} + +void setup(void) { + DBG_OUTPUT_PORT.begin(115200); + DBG_OUTPUT_PORT.print("\n"); + DBG_OUTPUT_PORT.setDebugOutput(true); + if (FORMAT_FILESYSTEM) FILESYSTEM.format(); + FILESYSTEM.begin(); + { + File root = FILESYSTEM.open("/"); + File file = root.openNextFile(); + while(file){ + String fileName = file.name(); + size_t fileSize = file.size(); + DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); + file = root.openNextFile(); + } + DBG_OUTPUT_PORT.printf("\n"); + } + + + //WIFI INIT + DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); + if (String(WiFi.SSID()) != String(ssid)) { + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + } + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + DBG_OUTPUT_PORT.print("."); + } + DBG_OUTPUT_PORT.println(""); + DBG_OUTPUT_PORT.print("Connected! IP address: "); + DBG_OUTPUT_PORT.println(WiFi.localIP()); + + MDNS.begin(host); + DBG_OUTPUT_PORT.print("Open http://"); + DBG_OUTPUT_PORT.print(host); + DBG_OUTPUT_PORT.println(".local/edit to see the file browser"); + + + //SERVER INIT + //list directory + server.on("/list", HTTP_GET, handleFileList); + //load editor + server.on("/edit", HTTP_GET, []() { + if (!handleFileRead("/edit.htm")) { + server.send(404, "text/plain", "FileNotFound"); + } + }); + //create file + server.on("/edit", HTTP_PUT, handleFileCreate); + //delete file + server.on("/edit", HTTP_DELETE, handleFileDelete); + //first callback is called after the request has ended with all parsed arguments + //second callback handles file uploads at that location + server.on("/edit", HTTP_POST, []() { + server.send(200, "text/plain", ""); + }, handleFileUpload); + + //called when the url is not defined here + //use it to load content from FILESYSTEM + server.onNotFound([]() { + if (!handleFileRead(server.uri())) { + server.send(404, "text/plain", "FileNotFound"); + } + }); + + //get heap status, analog input value and all GPIO statuses in one json call + server.on("/all", HTTP_GET, []() { + String json = "{"; + json += "\"heap\":" + String(ESP.getFreeHeap()); + json += ", \"analog\":" + String(analogRead(A0)); + json += ", \"gpio\":" + String((uint32_t)(0)); + json += "}"; + server.send(200, "text/json", json); + json = String(); + }); + server.begin(); + DBG_OUTPUT_PORT.println("HTTP server started"); + +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/FSBrowser/data/edit.htm.gz b/lib/EthernetWebServer/examples/FSBrowser/data/edit.htm.gz new file mode 100644 index 0000000..69ce414 Binary files /dev/null and b/lib/EthernetWebServer/examples/FSBrowser/data/edit.htm.gz differ diff --git a/lib/EthernetWebServer/examples/FSBrowser/data/favicon.ico b/lib/EthernetWebServer/examples/FSBrowser/data/favicon.ico new file mode 100644 index 0000000..71b25fe Binary files /dev/null and b/lib/EthernetWebServer/examples/FSBrowser/data/favicon.ico differ diff --git a/lib/EthernetWebServer/examples/FSBrowser/data/graphs.js.gz b/lib/EthernetWebServer/examples/FSBrowser/data/graphs.js.gz new file mode 100644 index 0000000..7243544 Binary files /dev/null and b/lib/EthernetWebServer/examples/FSBrowser/data/graphs.js.gz differ diff --git a/lib/EthernetWebServer/examples/FSBrowser/data/index.htm b/lib/EthernetWebServer/examples/FSBrowser/data/index.htm new file mode 100644 index 0000000..7941860 --- /dev/null +++ b/lib/EthernetWebServer/examples/FSBrowser/data/index.htm @@ -0,0 +1,97 @@ + + + + + + ESP Monitor + + + + +
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/lib/EthernetWebServer/examples/HelloServer/HelloServer.ino b/lib/EthernetWebServer/examples/HelloServer/HelloServer.ino new file mode 100644 index 0000000..676b63a --- /dev/null +++ b/lib/EthernetWebServer/examples/HelloServer/HelloServer.ino @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +const int led = 13; + +void handleRoot() { + digitalWrite(led, 1); + server.send(200, "text/plain", "hello from esp32!"); + digitalWrite(led, 0); +} + +void handleNotFound() { + digitalWrite(led, 1); + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); + digitalWrite(led, 0); +} + +void setup(void) { + pinMode(led, OUTPUT); + digitalWrite(led, 0); + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + if (MDNS.begin("esp32")) { + Serial.println("MDNS responder started"); + } + + server.on("/", handleRoot); + + server.on("/inline", []() { + server.send(200, "text/plain", "this works as well"); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/lib/EthernetWebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino new file mode 100644 index 0000000..43e86fe --- /dev/null +++ b/lib/EthernetWebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino @@ -0,0 +1,60 @@ +/* + HTTP Advanced Authentication example + Created Mar 16, 2017 by Ahmed El-Sharnoby. + This example code is in the public domain. +*/ + +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +const char* www_username = "admin"; +const char* www_password = "esp32"; +// allows you to set the realm of authentication Default:"Login Required" +const char* www_realm = "Custom Auth Realm"; +// the Content of the HTML response in case of Unautherized Access Default:empty +String authFailResponse = "Authentication Failed"; + +void setup() { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Connect Failed! Rebooting..."); + delay(1000); + ESP.restart(); + } + ArduinoOTA.begin(); + + server.on("/", []() { + if (!server.authenticate(www_username, www_password)) + //Basic Auth Method with Custom realm and Failure Response + //return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); + //Digest Auth Method with realm="Login Required" and empty Failure Response + //return server.requestAuthentication(DIGEST_AUTH); + //Digest Auth Method with Custom realm and empty Failure Response + //return server.requestAuthentication(DIGEST_AUTH, www_realm); + //Digest Auth Method with Custom realm and Failure Response + { + return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse); + } + server.send(200, "text/plain", "Login OK"); + }); + server.begin(); + + Serial.print("Open http://"); + Serial.print(WiFi.localIP()); + Serial.println("/ in your browser to see it working"); +} + +void loop() { + ArduinoOTA.handle(); + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/HttpBasicAuth/HttpBasicAuth.ino b/lib/EthernetWebServer/examples/HttpBasicAuth/HttpBasicAuth.ino new file mode 100644 index 0000000..4328a7d --- /dev/null +++ b/lib/EthernetWebServer/examples/HttpBasicAuth/HttpBasicAuth.ino @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +const char* www_username = "admin"; +const char* www_password = "esp32"; + +void setup() { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("WiFi Connect Failed! Rebooting..."); + delay(1000); + ESP.restart(); + } + ArduinoOTA.begin(); + + server.on("/", []() { + if (!server.authenticate(www_username, www_password)) { + return server.requestAuthentication(); + } + server.send(200, "text/plain", "Login OK"); + }); + server.begin(); + + Serial.print("Open http://"); + Serial.print(WiFi.localIP()); + Serial.println("/ in your browser to see it working"); +} + +void loop() { + ArduinoOTA.handle(); + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/PathArgServer/PathArgServer.ino b/lib/EthernetWebServer/examples/PathArgServer/PathArgServer.ino new file mode 100644 index 0000000..f51fe69 --- /dev/null +++ b/lib/EthernetWebServer/examples/PathArgServer/PathArgServer.ino @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include +#include + +const char *ssid = "........"; +const char *password = "........"; + +WebServer server(80); + +void setup(void) { + Serial.begin(9600); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + if (MDNS.begin("esp32")) { + Serial.println("MDNS responder started"); + } + + server.on(F("/"), []() { + server.send(200, "text/plain", "hello from esp32!"); + }); + + server.on(UriBraces("/users/{}"), []() { + String user = server.pathArg(0); + server.send(200, "text/plain", "User: '" + user + "'"); + }); + + server.on(UriRegex("^\\/users\\/([0-9]+)\\/devices\\/([0-9]+)$"), []() { + String user = server.pathArg(0); + String device = server.pathArg(1); + server.send(200, "text/plain", "User: '" + user + "' and Device: '" + device + "'"); + }); + + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/SDWebServer/SDWebServer.ino b/lib/EthernetWebServer/examples/SDWebServer/SDWebServer.ino new file mode 100644 index 0000000..e2af2df --- /dev/null +++ b/lib/EthernetWebServer/examples/SDWebServer/SDWebServer.ino @@ -0,0 +1,314 @@ +/* + SDWebServer - Example EthernetWebServer with SD Card backend for esp8266 + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the EthernetWebServer library 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 + + Have a FAT Formatted SD Card connected to the SPI port of the ESP8266 + The web root is the SD Card root folder + File extensions with more than 3 charecters are not supported by the SD Library + File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter + index.htm is the default index (works on subfolders as well) + + upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit + +*/ +#include +#include +#include +#include +#include +#include + +#define DBG_OUTPUT_PORT Serial + +const char* ssid = "**********"; +const char* password = "**********"; +const char* host = "esp32sd"; + +WebServer server(80); + +static bool hasSD = false; +File uploadFile; + + +void returnOK() { + server.send(200, "text/plain", ""); +} + +void returnFail(String msg) { + server.send(500, "text/plain", msg + "\r\n"); +} + +bool loadFromSdCard(String path) { + String dataType = "text/plain"; + if (path.endsWith("/")) { + path += "index.htm"; + } + + if (path.endsWith(".src")) { + path = path.substring(0, path.lastIndexOf(".")); + } else if (path.endsWith(".htm")) { + dataType = "text/html"; + } else if (path.endsWith(".css")) { + dataType = "text/css"; + } else if (path.endsWith(".js")) { + dataType = "application/javascript"; + } else if (path.endsWith(".png")) { + dataType = "image/png"; + } else if (path.endsWith(".gif")) { + dataType = "image/gif"; + } else if (path.endsWith(".jpg")) { + dataType = "image/jpeg"; + } else if (path.endsWith(".ico")) { + dataType = "image/x-icon"; + } else if (path.endsWith(".xml")) { + dataType = "text/xml"; + } else if (path.endsWith(".pdf")) { + dataType = "application/pdf"; + } else if (path.endsWith(".zip")) { + dataType = "application/zip"; + } + + File dataFile = SD.open(path.c_str()); + if (dataFile.isDirectory()) { + path += "/index.htm"; + dataType = "text/html"; + dataFile = SD.open(path.c_str()); + } + + if (!dataFile) { + return false; + } + + if (server.hasArg("download")) { + dataType = "application/octet-stream"; + } + + if (server.streamFile(dataFile, dataType) != dataFile.size()) { + DBG_OUTPUT_PORT.println("Sent less data than expected!"); + } + + dataFile.close(); + return true; +} + +void handleFileUpload() { + if (server.uri() != "/edit") { + return; + } + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + if (SD.exists((char *)upload.filename.c_str())) { + SD.remove((char *)upload.filename.c_str()); + } + uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE); + DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename); + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (uploadFile) { + uploadFile.write(upload.buf, upload.currentSize); + } + DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize); + } else if (upload.status == UPLOAD_FILE_END) { + if (uploadFile) { + uploadFile.close(); + } + DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); + } +} + +void deleteRecursive(String path) { + File file = SD.open((char *)path.c_str()); + if (!file.isDirectory()) { + file.close(); + SD.remove((char *)path.c_str()); + return; + } + + file.rewindDirectory(); + while (true) { + File entry = file.openNextFile(); + if (!entry) { + break; + } + String entryPath = path + "/" + entry.name(); + if (entry.isDirectory()) { + entry.close(); + deleteRecursive(entryPath); + } else { + entry.close(); + SD.remove((char *)entryPath.c_str()); + } + yield(); + } + + SD.rmdir((char *)path.c_str()); + file.close(); +} + +void handleDelete() { + if (server.args() == 0) { + return returnFail("BAD ARGS"); + } + String path = server.arg(0); + if (path == "/" || !SD.exists((char *)path.c_str())) { + returnFail("BAD PATH"); + return; + } + deleteRecursive(path); + returnOK(); +} + +void handleCreate() { + if (server.args() == 0) { + return returnFail("BAD ARGS"); + } + String path = server.arg(0); + if (path == "/" || SD.exists((char *)path.c_str())) { + returnFail("BAD PATH"); + return; + } + + if (path.indexOf('.') > 0) { + File file = SD.open((char *)path.c_str(), FILE_WRITE); + if (file) { + file.write(0); + file.close(); + } + } else { + SD.mkdir((char *)path.c_str()); + } + returnOK(); +} + +void printDirectory() { + if (!server.hasArg("dir")) { + return returnFail("BAD ARGS"); + } + String path = server.arg("dir"); + if (path != "/" && !SD.exists((char *)path.c_str())) { + return returnFail("BAD PATH"); + } + File dir = SD.open((char *)path.c_str()); + path = String(); + if (!dir.isDirectory()) { + dir.close(); + return returnFail("NOT DIR"); + } + dir.rewindDirectory(); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/json", ""); + WiFiClient client = server.client(); + + server.sendContent("["); + for (int cnt = 0; true; ++cnt) { + File entry = dir.openNextFile(); + if (!entry) { + break; + } + + String output; + if (cnt > 0) { + output = ','; + } + + output += "{\"type\":\""; + output += (entry.isDirectory()) ? "dir" : "file"; + output += "\",\"name\":\""; + output += entry.name(); + output += "\""; + output += "}"; + server.sendContent(output); + entry.close(); + } + server.sendContent("]"); + dir.close(); +} + +void handleNotFound() { + if (hasSD && loadFromSdCard(server.uri())) { + return; + } + String message = "SDCARD Not Detected\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); + DBG_OUTPUT_PORT.print(message); +} + +void setup(void) { + DBG_OUTPUT_PORT.begin(115200); + DBG_OUTPUT_PORT.setDebugOutput(true); + DBG_OUTPUT_PORT.print("\n"); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + DBG_OUTPUT_PORT.print("Connecting to "); + DBG_OUTPUT_PORT.println(ssid); + + // Wait for connection + uint8_t i = 0; + while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds + delay(500); + } + if (i == 21) { + DBG_OUTPUT_PORT.print("Could not connect to"); + DBG_OUTPUT_PORT.println(ssid); + while (1) { + delay(500); + } + } + DBG_OUTPUT_PORT.print("Connected! IP address: "); + DBG_OUTPUT_PORT.println(WiFi.localIP()); + + if (MDNS.begin(host)) { + MDNS.addService("http", "tcp", 80); + DBG_OUTPUT_PORT.println("MDNS responder started"); + DBG_OUTPUT_PORT.print("You can now connect to http://"); + DBG_OUTPUT_PORT.print(host); + DBG_OUTPUT_PORT.println(".local"); + } + + + server.on("/list", HTTP_GET, printDirectory); + server.on("/edit", HTTP_DELETE, handleDelete); + server.on("/edit", HTTP_PUT, handleCreate); + server.on("/edit", HTTP_POST, []() { + returnOK(); + }, handleFileUpload); + server.onNotFound(handleNotFound); + + server.begin(); + DBG_OUTPUT_PORT.println("HTTP server started"); + + if (SD.begin(SS)) { + DBG_OUTPUT_PORT.println("SD Card initialized."); + hasSD = true; + } +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/SDWebServer/SdRoot/edit/index.htm b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/edit/index.htm new file mode 100644 index 0000000..f535601 --- /dev/null +++ b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/edit/index.htm @@ -0,0 +1,674 @@ + + + + SD Editor + + + + + +
+
+
+ + + + diff --git a/lib/EthernetWebServer/examples/SDWebServer/SdRoot/index.htm b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/index.htm new file mode 100644 index 0000000..55fe5a6 --- /dev/null +++ b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/index.htm @@ -0,0 +1,22 @@ + + + + + ESP Index + + + + +

ESP8266 Pin Functions

+ + + diff --git a/lib/EthernetWebServer/examples/SDWebServer/SdRoot/pins.png b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/pins.png new file mode 100644 index 0000000..ac7fc0f Binary files /dev/null and b/lib/EthernetWebServer/examples/SDWebServer/SdRoot/pins.png differ diff --git a/lib/EthernetWebServer/examples/SimpleAuthentification/SimpleAuthentification.ino b/lib/EthernetWebServer/examples/SimpleAuthentification/SimpleAuthentification.ino new file mode 100644 index 0000000..9cc818b --- /dev/null +++ b/lib/EthernetWebServer/examples/SimpleAuthentification/SimpleAuthentification.ino @@ -0,0 +1,133 @@ +#include +#include +#include + +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); + +//Check if header is present and correct +bool is_authentified() { + Serial.println("Enter is_authentified"); + if (server.hasHeader("Cookie")) { + Serial.print("Found cookie: "); + String cookie = server.header("Cookie"); + Serial.println(cookie); + if (cookie.indexOf("ESPSESSIONID=1") != -1) { + Serial.println("Authentification Successful"); + return true; + } + } + Serial.println("Authentification Failed"); + return false; +} + +//login page, also called for disconnect +void handleLogin() { + String msg; + if (server.hasHeader("Cookie")) { + Serial.print("Found cookie: "); + String cookie = server.header("Cookie"); + Serial.println(cookie); + } + if (server.hasArg("DISCONNECT")) { + Serial.println("Disconnection"); + server.sendHeader("Location", "/login"); + server.sendHeader("Cache-Control", "no-cache"); + server.sendHeader("Set-Cookie", "ESPSESSIONID=0"); + server.send(301); + return; + } + if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) { + if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") { + server.sendHeader("Location", "/"); + server.sendHeader("Cache-Control", "no-cache"); + server.sendHeader("Set-Cookie", "ESPSESSIONID=1"); + server.send(301); + Serial.println("Log in Successful"); + return; + } + msg = "Wrong username/password! try again."; + Serial.println("Log in Failed"); + } + String content = "
To log in, please use : admin/admin
"; + content += "User:
"; + content += "Password:
"; + content += "
" + msg + "
"; + content += "You also can go here"; + server.send(200, "text/html", content); +} + +//root page can be accessed only if authentification is ok +void handleRoot() { + Serial.println("Enter handleRoot"); + String header; + if (!is_authentified()) { + server.sendHeader("Location", "/login"); + server.sendHeader("Cache-Control", "no-cache"); + server.send(301); + return; + } + String content = "

hello, you successfully connected to esp8266!


"; + if (server.hasHeader("User-Agent")) { + content += "the user agent used is : " + server.header("User-Agent") + "

"; + } + content += "You can access this page until you disconnect"; + server.send(200, "text/html", content); +} + +//no need authentification +void handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += (server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for (uint8_t i = 0; i < server.args(); i++) { + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; + } + server.send(404, "text/plain", message); +} + +void setup(void) { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + + server.on("/", handleRoot); + server.on("/login", handleLogin); + server.on("/inline", []() { + server.send(200, "text/plain", "this works without need of authentification"); + }); + + server.onNotFound(handleNotFound); + //here the list of headers to be recorded + const char * headerkeys[] = {"User-Agent", "Cookie"} ; + size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); + //ask server to track these headers + server.collectHeaders(headerkeys, headerkeyssize); + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/examples/WebUpdate/WebUpdate.ino b/lib/EthernetWebServer/examples/WebUpdate/WebUpdate.ino new file mode 100644 index 0000000..d682739 --- /dev/null +++ b/lib/EthernetWebServer/examples/WebUpdate/WebUpdate.ino @@ -0,0 +1,69 @@ +/* + To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update +*/ + +#include +#include +#include +#include +#include + +const char* host = "esp32-webupdate"; +const char* ssid = "........"; +const char* password = "........"; + +WebServer server(80); +const char* serverIndex = "
"; + +void setup(void) { + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); + WiFi.mode(WIFI_AP_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() == WL_CONNECTED) { + MDNS.begin(host); + server.on("/", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", serverIndex); + }); + server.on("/update", HTTP_POST, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + ESP.restart(); + }, []() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + Serial.setDebugOutput(true); + Serial.printf("Update: %s\n", upload.filename.c_str()); + if (!Update.begin()) { //start with max available size + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + } else { + Update.printError(Serial); + } + Serial.setDebugOutput(false); + } else { + Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); + } + }); + server.begin(); + MDNS.addService("http", "tcp", 80); + + Serial.printf("Ready! Open http://%s.local in your browser\n", host); + } else { + Serial.println("WiFi Failed"); + } +} + +void loop(void) { + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} diff --git a/lib/EthernetWebServer/keywords.txt b/lib/EthernetWebServer/keywords.txt new file mode 100644 index 0000000..df20ff7 --- /dev/null +++ b/lib/EthernetWebServer/keywords.txt @@ -0,0 +1,38 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +WebServer KEYWORD1 +WebServerSecure KEYWORD1 +HTTPMethod KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +handleClient KEYWORD2 +on KEYWORD2 +addHandler KEYWORD2 +uri KEYWORD2 +method KEYWORD2 +client KEYWORD2 +send KEYWORD2 +arg KEYWORD2 +argName KEYWORD2 +args KEYWORD2 +hasArg KEYWORD2 +onNotFound KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +HTTP_GET LITERAL1 +HTTP_POST LITERAL1 +HTTP_ANY LITERAL1 +CONTENT_LENGTH_UNKNOWN LITERAL1 diff --git a/lib/EthernetWebServer/library.properties b/lib/EthernetWebServer/library.properties new file mode 100644 index 0000000..a2ac5f5 --- /dev/null +++ b/lib/EthernetWebServer/library.properties @@ -0,0 +1,9 @@ +name=WebServer +version=1.0 +author=Ivan Grokhotkov +maintainer=Ivan Grokhtkov +sentence=Simple web server library +paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time. +category=Communication +url= +architectures=esp32 diff --git a/lib/EthernetWebServer/src/EthernetServerImpl.cpp b/lib/EthernetWebServer/src/EthernetServerImpl.cpp new file mode 100644 index 0000000..6b25a9d --- /dev/null +++ b/lib/EthernetWebServer/src/EthernetServerImpl.cpp @@ -0,0 +1,29 @@ +#include "EthernetServerImpl.h" + +EthernetServerImpl::EthernetServerImpl(IPAddress addr, int port) + : EthernetServer(port) +{} + +EthernetServerImpl::EthernetServerImpl(int port) + : EthernetServer(port) +{} + +void EthernetServerImpl::begin(uint16_t port) +{ + EthernetServer::begin(); +} + +void EthernetServerImpl::begin() +{ + EthernetServer::begin(); +} + +void EthernetServerImpl::close() +{ + +} + +int EthernetServerImpl::setNoDelay(bool nodelay) +{ + return 0; +} diff --git a/lib/EthernetWebServer/src/EthernetServerImpl.h b/lib/EthernetWebServer/src/EthernetServerImpl.h new file mode 100644 index 0000000..76ad3bf --- /dev/null +++ b/lib/EthernetWebServer/src/EthernetServerImpl.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class EthernetServerImpl : public EthernetServer +{ +public: + EthernetServerImpl(IPAddress addr, int port); + EthernetServerImpl(int port); + + void begin(); + void begin(uint16_t port); + void close(); + int setNoDelay(bool nodelay); +}; \ No newline at end of file diff --git a/lib/EthernetWebServer/src/EthernetWebServer.cpp b/lib/EthernetWebServer/src/EthernetWebServer.cpp new file mode 100644 index 0000000..47846d5 --- /dev/null +++ b/lib/EthernetWebServer/src/EthernetWebServer.cpp @@ -0,0 +1,712 @@ +/* + EthernetWebServer.cpp - Dead simple web-server. + Supports only one simultaneous client, knows how to handle GET and POST. + + Copyright (c) 2014 Ivan Grokhotkov. 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 + Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) +*/ + + +#include +#include +#include +#include "WiFiServer.h" +#include "EthernetClient.h" +#include "EthernetWebServer.h" +#include "FS.h" +#include "detail/RequestHandlersImpl.h" +#include "mbedtls/md5.h" + + +static const char AUTHORIZATION_HEADER[] = "Authorization"; +static const char qop_auth[] PROGMEM = "qop=auth"; +static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\""; +static const char WWW_Authenticate[] = "WWW-Authenticate"; +static const char Content_Length[] = "Content-Length"; + + +EthernetWebServer::EthernetWebServer(IPAddress addr, int port) +: _corsEnabled(false) +, _server(addr, port) +, _currentMethod(HTTP_ANY) +, _currentVersion(0) +, _currentStatus(HC_NONE) +, _statusChange(0) +, _nullDelay(true) +, _currentHandler(nullptr) +, _firstHandler(nullptr) +, _lastHandler(nullptr) +, _currentArgCount(0) +, _currentArgs(nullptr) +, _postArgsLen(0) +, _postArgs(nullptr) +, _headerKeysCount(0) +, _currentHeaders(nullptr) +, _contentLength(0) +, _chunked(false) +{ +} + +EthernetWebServer::EthernetWebServer(int port) +: _corsEnabled(false) +, _server(port) +, _currentMethod(HTTP_ANY) +, _currentVersion(0) +, _currentStatus(HC_NONE) +, _statusChange(0) +, _nullDelay(true) +, _currentHandler(nullptr) +, _firstHandler(nullptr) +, _lastHandler(nullptr) +, _currentArgCount(0) +, _currentArgs(nullptr) +, _postArgsLen(0) +, _postArgs(nullptr) +, _headerKeysCount(0) +, _currentHeaders(nullptr) +, _contentLength(0) +, _chunked(false) +{ +} + +EthernetWebServer::~EthernetWebServer() { + _server.close(); + if (_currentHeaders) + delete[]_currentHeaders; + RequestHandler* handler = _firstHandler; + while (handler) { + RequestHandler* next = handler->next(); + delete handler; + handler = next; + } +} + +void EthernetWebServer::begin() { + close(); + _server.begin(); + _server.setNoDelay(true); +} + +void EthernetWebServer::begin(uint16_t port) { + close(); + _server.begin(port); + _server.setNoDelay(true); +} + +String EthernetWebServer::_extractParam(String& authReq, const String& param, const char delimit){ + int _begin = authReq.indexOf(param); + if (_begin == -1) + return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); +} + +static String md5str(String &in){ + char out[33] = {0}; + mbedtls_md5_context _ctx; + uint8_t i; + uint8_t * _buf = (uint8_t*)malloc(16); + if(_buf == NULL) + return String(out); + memset(_buf, 0x00, 16); + mbedtls_md5_init(&_ctx); + mbedtls_md5_starts(&_ctx); + mbedtls_md5_update(&_ctx, (const uint8_t *)in.c_str(), in.length()); + mbedtls_md5_finish(&_ctx, _buf); + for(i = 0; i < 16; i++) { + sprintf(out + (i * 2), "%02x", _buf[i]); + } + out[32] = 0; + free(_buf); + return String(out); +} + +bool EthernetWebServer::authenticate(const char * username, const char * password){ + if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { + String authReq = header(FPSTR(AUTHORIZATION_HEADER)); + if(authReq.startsWith(F("Basic"))){ + authReq = authReq.substring(6); + authReq.trim(); + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen + 1]; + if(toencode == NULL){ + authReq = ""; + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + authReq = ""; + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { + authReq = ""; + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } else if(authReq.startsWith(F("Digest"))) { + authReq = authReq.substring(7); + log_v("%s", authReq.c_str()); + String _username = _extractParam(authReq,F("username=\""),'\"'); + if(!_username.length() || _username != String(username)) { + authReq = ""; + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + String _response = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { + authReq = ""; + return false; + } + if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { + authReq = ""; + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); + log_v("Hash of user:realm:pass=%s", _H1.c_str()); + String _H2 = ""; + if(_currentMethod == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_currentMethod == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_currentMethod == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_currentMethod == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + log_v("Hash of GET:uri=%s", _H2.c_str()); + String _responsecheck = ""; + if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + } + log_v("The Proper response=%s", _responsecheck.c_str()); + if(_response == _responsecheck){ + authReq = ""; + return true; + } + } + authReq = ""; + } + return false; +} + +String EthernetWebServer::_getRandomHexString() { + char buffer[33]; // buffer to hold 32 Hex Digit + /0 + int i; + for(i = 0; i < 4; i++) { + sprintf (buffer + (i*8), "%08x", esp_random()); + } + return String(buffer); +} + +void EthernetWebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) { + if(realm == NULL) { + _srealm = String(F("Login Required")); + } else { + _srealm = String(realm); + } + if(mode == BASIC_AUTH) { + sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); + } else { + _snonce=_getRandomHexString(); + _sopaque=_getRandomHexString(); + sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); + } + using namespace mime; + send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); +} + +void EthernetWebServer::on(const Uri &uri, EthernetWebServer::THandlerFunction handler) { + on(uri, HTTP_ANY, handler); +} + +void EthernetWebServer::on(const Uri &uri, HTTPMethod method, EthernetWebServer::THandlerFunction fn) { + on(uri, method, fn, _fileUploadHandler); +} + +void EthernetWebServer::on(const Uri &uri, HTTPMethod method, EthernetWebServer::THandlerFunction fn, EthernetWebServer::THandlerFunction ufn) { + _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); +} + +void EthernetWebServer::addHandler(RequestHandler* handler) { + _addRequestHandler(handler); +} + +void EthernetWebServer::_addRequestHandler(RequestHandler* handler) { + if (!_lastHandler) { + _firstHandler = handler; + _lastHandler = handler; + } + else { + _lastHandler->next(handler); + _lastHandler = handler; + } +} + +void EthernetWebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { + _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); +} + +void EthernetWebServer::handleClient() { + if (_currentStatus == HC_NONE) { + EthernetClient client = _server.available(); + if (!client) { + if (_nullDelay) { + delay(1); + } + return; + } + + log_v("New client"); + + _currentClient = client; + _currentStatus = HC_WAIT_READ; + _statusChange = millis(); + } + + bool keepCurrentClient = false; + bool callYield = false; + + if (_currentClient.connected()) { + switch (_currentStatus) { + case HC_NONE: + // No-op to avoid C++ compiler warning + break; + case HC_WAIT_READ: + // Wait for data from client to become available + if (_currentClient.available()) { + if (_parseRequest(_currentClient)) { + // because HTTP_MAX_SEND_WAIT is expressed in milliseconds, + // it must be divided by 1000 + _currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000); + _contentLength = CONTENT_LENGTH_NOT_SET; + _handleRequest(); + +// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652 +// if (_currentClient.connected()) { +// _currentStatus = HC_WAIT_CLOSE; +// _statusChange = millis(); +// keepCurrentClient = true; +// } + } + } else { // !_currentClient.available() + if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) { + keepCurrentClient = true; + } + callYield = true; + } + break; + case HC_WAIT_CLOSE: + // Wait for client to close the connection + if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) { + keepCurrentClient = true; + callYield = true; + } + } + } + + if (!keepCurrentClient) { + _currentClient = EthernetClient(); + _currentStatus = HC_NONE; + _currentUpload.reset(); + } + + if (callYield) { + yield(); + } +} + +void EthernetWebServer::close() { + _server.close(); + _currentStatus = HC_NONE; + if(!_headerKeysCount) + collectHeaders(0, 0); +} + +void EthernetWebServer::stop() { + close(); +} + +void EthernetWebServer::sendHeader(const String& name, const String& value, bool first) { + String headerLine = name; + headerLine += F(": "); + headerLine += value; + headerLine += "\r\n"; + + if (first) { + _responseHeaders = headerLine + _responseHeaders; + } + else { + _responseHeaders += headerLine; + } +} + +void EthernetWebServer::setContentLength(const size_t contentLength) { + _contentLength = contentLength; +} + +void EthernetWebServer::enableDelay(boolean value) { + _nullDelay = value; +} + +void EthernetWebServer::enableCORS(boolean value) { + _corsEnabled = value; +} + +void EthernetWebServer::enableCrossOrigin(boolean value) { + enableCORS(value); +} + +void EthernetWebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { + response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; + response += String(code); + response += ' '; + response += _responseCodeToString(code); + response += "\r\n"; + + using namespace mime; + if (!content_type) + content_type = mimeTable[html].mimeType; + + sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true); + if (_contentLength == CONTENT_LENGTH_NOT_SET) { + sendHeader(String(FPSTR(Content_Length)), String(contentLength)); + } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { + sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); + } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client + //let's do chunked + _chunked = true; + sendHeader(String(F("Accept-Ranges")),String(F("none"))); + sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); + } + if (_corsEnabled) { + sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*")); + sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*")); + sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*")); + } + sendHeader(String(F("Connection")), String(F("close"))); + + response += _responseHeaders; + response += "\r\n"; + _responseHeaders = ""; +} + +void EthernetWebServer::send(int code, const char* content_type, const String& content) { + String header; + // Can we asume the following? + //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET) + // _contentLength = CONTENT_LENGTH_UNKNOWN; + _prepareHeader(header, code, content_type, content.length()); + _currentClientWrite(header.c_str(), header.length()); + if(content.length()) + sendContent(content); +} + +void EthernetWebServer::send_P(int code, PGM_P content_type, PGM_P content) { + size_t contentLength = 0; + + if (content != NULL) { + contentLength = strlen_P(content); + } + + String header; + char type[64]; + memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); + _prepareHeader(header, code, (const char* )type, contentLength); + _currentClientWrite(header.c_str(), header.length()); + sendContent_P(content); +} + +void EthernetWebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) { + String header; + char type[64]; + memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); + _prepareHeader(header, code, (const char* )type, contentLength); + sendContent(header); + sendContent_P(content, contentLength); +} + +void EthernetWebServer::send(int code, char* content_type, const String& content) { + send(code, (const char*)content_type, content); +} + +void EthernetWebServer::send(int code, const String& content_type, const String& content) { + send(code, (const char*)content_type.c_str(), content); +} + +void EthernetWebServer::sendContent(const String& content) { + sendContent(content.c_str(), content.length()); +} + +void EthernetWebServer::sendContent(const char* content, size_t contentLength) { + const char * footer = "\r\n"; + if(_chunked) { + char * chunkSize = (char *)malloc(11); + if(chunkSize){ + sprintf(chunkSize, "%x%s", contentLength, footer); + _currentClientWrite(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + _currentClientWrite(content, contentLength); + if(_chunked){ + _currentClient.write(footer, 2); + if (contentLength == 0) { + _chunked = false; + } + } +} + +void EthernetWebServer::sendContent_P(PGM_P content) { + sendContent_P(content, strlen_P(content)); +} + +void EthernetWebServer::sendContent_P(PGM_P content, size_t size) { + const char * footer = "\r\n"; + if(_chunked) { + char * chunkSize = (char *)malloc(11); + if(chunkSize){ + sprintf(chunkSize, "%x%s", size, footer); + _currentClientWrite(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + _currentClientWrite_P(content, size); + if(_chunked){ + _currentClient.write(footer, 2); + if (size == 0) { + _chunked = false; + } + } +} + + +void EthernetWebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType) +{ + using namespace mime; + setContentLength(fileSize); + if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) && + contentType != String(FPSTR(mimeTable[gz].mimeType)) && + contentType != String(FPSTR(mimeTable[none].mimeType))) { + sendHeader(F("Content-Encoding"), F("gzip")); + } + send(200, contentType, ""); +} + +String EthernetWebServer::pathArg(unsigned int i) { + if (_currentHandler != nullptr) + return _currentHandler->pathArg(i); + return ""; +} + +String EthernetWebServer::arg(String name) { + for (int j = 0; j < _postArgsLen; ++j) { + if ( _postArgs[j].key == name ) + return _postArgs[j].value; + } + for (int i = 0; i < _currentArgCount; ++i) { + if ( _currentArgs[i].key == name ) + return _currentArgs[i].value; + } + return ""; +} + +String EthernetWebServer::arg(int i) { + if (i < _currentArgCount) + return _currentArgs[i].value; + return ""; +} + +String EthernetWebServer::argName(int i) { + if (i < _currentArgCount) + return _currentArgs[i].key; + return ""; +} + +int EthernetWebServer::args() { + return _currentArgCount; +} + +bool EthernetWebServer::hasArg(String name) { + for (int j = 0; j < _postArgsLen; ++j) { + if (_postArgs[j].key == name) + return true; + } + for (int i = 0; i < _currentArgCount; ++i) { + if (_currentArgs[i].key == name) + return true; + } + return false; +} + + +String EthernetWebServer::header(String name) { + for (int i = 0; i < _headerKeysCount; ++i) { + if (_currentHeaders[i].key.equalsIgnoreCase(name)) + return _currentHeaders[i].value; + } + return ""; +} + +void EthernetWebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { + _headerKeysCount = headerKeysCount + 1; + if (_currentHeaders) + delete[]_currentHeaders; + _currentHeaders = new RequestArgument[_headerKeysCount]; + _currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER); + for (int i = 1; i < _headerKeysCount; i++){ + _currentHeaders[i].key = headerKeys[i-1]; + } +} + +String EthernetWebServer::header(int i) { + if (i < _headerKeysCount) + return _currentHeaders[i].value; + return ""; +} + +String EthernetWebServer::headerName(int i) { + if (i < _headerKeysCount) + return _currentHeaders[i].key; + return ""; +} + +int EthernetWebServer::headers() { + return _headerKeysCount; +} + +bool EthernetWebServer::hasHeader(String name) { + for (int i = 0; i < _headerKeysCount; ++i) { + if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) + return true; + } + return false; +} + +String EthernetWebServer::hostHeader() { + return _hostHeader; +} + +void EthernetWebServer::onFileUpload(THandlerFunction fn) { + _fileUploadHandler = fn; +} + +void EthernetWebServer::onNotFound(THandlerFunction fn) { + _notFoundHandler = fn; +} + +void EthernetWebServer::_handleRequest() { + bool handled = false; + if (!_currentHandler){ + log_e("request handler not found"); + } + else { + handled = _currentHandler->handle(*this, _currentMethod, _currentUri); + if (!handled) { + log_e("request handler failed to handle request"); + } + } + if (!handled && _notFoundHandler) { + _notFoundHandler(); + handled = true; + } + if (!handled) { + using namespace mime; + send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); + handled = true; + } + if (handled) { + _finalizeResponse(); + } + _currentUri = ""; +} + + +void EthernetWebServer::_finalizeResponse() { + if (_chunked) { + sendContent(""); + } +} + +String EthernetWebServer::_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(""); + } +} + +size_t EthernetWebServer::write_P(const char *buf, size_t size) +{ + return _currentClient.write(buf, size); +} diff --git a/lib/EthernetWebServer/src/EthernetWebServer.h b/lib/EthernetWebServer/src/EthernetWebServer.h new file mode 100644 index 0000000..d850218 --- /dev/null +++ b/lib/EthernetWebServer/src/EthernetWebServer.h @@ -0,0 +1,214 @@ +/* + EthernetWebServer.h - Dead simple web-server. + Supports only one simultaneous client, knows how to handle GET and POST. + + Copyright (c) 2014 Ivan Grokhotkov. 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 + Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) +*/ + + +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include +#include +#include +#include +#include "HTTP_Method.h" +#include "Uri.h" + +enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, + UPLOAD_FILE_ABORTED }; +enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; + +#define HTTP_DOWNLOAD_UNIT_SIZE 1436 + +#ifndef HTTP_UPLOAD_BUFLEN +#define HTTP_UPLOAD_BUFLEN 1436 +#endif + +#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request +#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive +#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed +#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection + +#define CONTENT_LENGTH_UNKNOWN ((size_t) -1) +#define CONTENT_LENGTH_NOT_SET ((size_t) -2) + +class EthernetWebServer; + +typedef struct { + HTTPUploadStatus status; + String filename; + String name; + String type; + size_t totalSize; // file size + size_t currentSize; // size of data currently in buf + uint8_t buf[HTTP_UPLOAD_BUFLEN]; +} HTTPUpload; + +#include "detail/RequestHandler.h" +#include "EthernetServerImpl.h" + +namespace fs { +class FS; +} + +class EthernetWebServer +{ +public: + EthernetWebServer(IPAddress addr, int port = 80); + EthernetWebServer(int port = 80); + virtual ~EthernetWebServer(); + + virtual void begin(); + virtual void begin(uint16_t port); + virtual void handleClient(); + + virtual void close(); + void stop(); + + bool authenticate(const char * username, const char * password); + void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); + + typedef std::function THandlerFunction; + void on(const Uri &uri, THandlerFunction handler); + void on(const Uri &uri, HTTPMethod method, THandlerFunction fn); + void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); + void addHandler(RequestHandler* handler); + void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL ); + void onNotFound(THandlerFunction fn); //called when handler is not assigned + void onFileUpload(THandlerFunction fn); //handle file uploads + + String uri() { return _currentUri; } + HTTPMethod method() { return _currentMethod; } + virtual EthernetClient client() { return _currentClient; } + HTTPUpload& upload() { return *_currentUpload; } + + String pathArg(unsigned int i); // get request path argument by number + String arg(String name); // get request argument value by name + String arg(int i); // get request argument value by number + String argName(int i); // get request argument name by number + int args(); // get arguments count + bool hasArg(String name); // check if argument exists + void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect + String header(String name); // get request header value by name + String header(int i); // get request header value by number + String headerName(int i); // get request header name by number + int headers(); // get header count + bool hasHeader(String name); // check if header exists + + String hostHeader(); // get request host header if available or empty String if not + + // send response to the client + // code - HTTP response code, can be 200 or 404 + // content_type - HTTP content type, like "text/plain" or "image/png" + // content - actual content body + void send(int code, const char* content_type = NULL, const String& content = String("")); + void send(int code, char* content_type, const String& content); + void send(int code, const String& content_type, const String& content); + void send_P(int code, PGM_P content_type, PGM_P content); + void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength); + + void enableDelay(boolean value); + void enableCORS(boolean value = true); + void enableCrossOrigin(boolean value = true); + + void setContentLength(const size_t contentLength); + void sendHeader(const String& name, const String& value, bool first = false); + void sendContent(const String& content); + void sendContent(const char* content, size_t contentLength); + void sendContent_P(PGM_P content); + void sendContent_P(PGM_P content, size_t size); + + static String urlDecode(const String& text); + + template + size_t streamFile(T &file, const String& contentType) { + _streamFileCore(file.size(), file.name(), contentType); + return _currentClient.write(file); + } + +protected: + size_t write_P(PGM_P buf, size_t size); + virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient.write( b, l ); } + virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return write_P( b, l ); } + void _addRequestHandler(RequestHandler* handler); + void _handleRequest(); + void _finalizeResponse(); + bool _parseRequest(EthernetClient& client); + void _parseArguments(String data); + static String _responseCodeToString(int code); + bool _parseForm(EthernetClient& client, String boundary, uint32_t len); + bool _parseFormUploadAborted(); + void _uploadWriteByte(uint8_t b); + int _uploadReadByte(EthernetClient& client); + void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); + bool _collectHeader(const char* headerName, const char* headerValue); + + void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); + + String _getRandomHexString(); + // for extracting Auth parameters + String _extractParam(String& authReq,const String& param,const char delimit = '"'); + + struct RequestArgument { + String key; + String value; + }; + + boolean _corsEnabled; + EthernetServerImpl _server; + + EthernetClient _currentClient; + HTTPMethod _currentMethod; + String _currentUri; + uint8_t _currentVersion; + HTTPClientStatus _currentStatus; + unsigned long _statusChange; + boolean _nullDelay; + + RequestHandler* _currentHandler; + RequestHandler* _firstHandler; + RequestHandler* _lastHandler; + THandlerFunction _notFoundHandler; + THandlerFunction _fileUploadHandler; + + int _currentArgCount; + RequestArgument* _currentArgs; + int _postArgsLen; + RequestArgument* _postArgs; + + std::unique_ptr _currentUpload; + + int _headerKeysCount; + RequestArgument* _currentHeaders; + size_t _contentLength; + String _responseHeaders; + + String _hostHeader; + bool _chunked; + + String _snonce; // Store noance and opaque for future comparison + String _sopaque; + String _srealm; // Store the Auth realm between Calls + +}; + + +#endif //ESP8266WEBSERVER_H diff --git a/lib/EthernetWebServer/src/HTTP_Method.h b/lib/EthernetWebServer/src/HTTP_Method.h new file mode 100644 index 0000000..66d53bf --- /dev/null +++ b/lib/EthernetWebServer/src/HTTP_Method.h @@ -0,0 +1,9 @@ +#ifndef _HTTP_Method_H_ +#define _HTTP_Method_H_ + +#include "http_parser.h" + +typedef enum http_method HTTPMethod; +#define HTTP_ANY (HTTPMethod)(255) + +#endif /* _HTTP_Method_H_ */ diff --git a/lib/EthernetWebServer/src/Parsing.cpp b/lib/EthernetWebServer/src/Parsing.cpp new file mode 100644 index 0000000..0739d33 --- /dev/null +++ b/lib/EthernetWebServer/src/Parsing.cpp @@ -0,0 +1,589 @@ +/* + Parsing.cpp - HTTP request parsing. + + Copyright (c) 2015 Ivan Grokhotkov. 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 + Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) +*/ + +#include +#include +#include "WiFiServer.h" +#include "EthernetClient.h" +#include "EthernetWebServer.h" +#include "detail/mimetable.h" + +#ifndef WEBSERVER_MAX_POST_ARGS +#define WEBSERVER_MAX_POST_ARGS 32 +#endif + +#define __STR(a) #a +#define _STR(a) __STR(a) +const char * _eth_http_method_str[] = { +#define XX(num, name, string) _STR(name), + HTTP_METHOD_MAP(XX) +#undef XX +}; + +static const char Content_Type[] PROGMEM = "Content-Type"; +static const char filename[] PROGMEM = "filename"; + +static char* readBytesWithTimeout(EthernetClient& client, size_t maxLength, size_t& dataLength, int timeout_ms) +{ + char *buf = nullptr; + dataLength = 0; + while (dataLength < maxLength) { + int tries = timeout_ms; + size_t newLength; + while (!(newLength = client.available()) && tries--) delay(1); + if (!newLength) { + break; + } + if (!buf) { + buf = (char *) malloc(newLength + 1); + if (!buf) { + return nullptr; + } + } + else { + char* newBuf = (char *) realloc(buf, dataLength + newLength + 1); + if (!newBuf) { + free(buf); + return nullptr; + } + buf = newBuf; + } + client.readBytes(buf + dataLength, newLength); + dataLength += newLength; + buf[dataLength] = '\0'; + } + return buf; +} + +bool EthernetWebServer::_parseRequest(EthernetClient& client) { + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); + client.readStringUntil('\n'); + //reset header value + for (int i = 0; i < _headerKeysCount; ++i) { + _currentHeaders[i].value =String(); + } + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { + log_e("Invalid request: %s", req.c_str()); + return false; + } + + String methodStr = req.substring(0, addr_start); + String url = req.substring(addr_start + 1, addr_end); + String versionEnd = req.substring(addr_end + 8); + _currentVersion = atoi(versionEnd.c_str()); + String searchStr = ""; + int hasSearch = url.indexOf('?'); + if (hasSearch != -1){ + searchStr = url.substring(hasSearch + 1); + url = url.substring(0, hasSearch); + } + _currentUri = url; + _chunked = false; + + HTTPMethod method = HTTP_ANY; + size_t num_methods = sizeof(_eth_http_method_str) / sizeof(const char *); + for (size_t i=0; inext()) { + if (handler->canHandle(_currentMethod, _currentUri)) + break; + } + _currentHandler = handler; + + String formData; + // below is needed only when POST type request + if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ + String boundaryStr; + String headerName; + String headerValue; + bool isForm = false; + bool isEncoded = false; + uint32_t contentLength = 0; + //parse headers + while(1){ + req = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (req == "") break;//no moar headers + int headerDiv = req.indexOf(':'); + if (headerDiv == -1){ + break; + } + headerName = req.substring(0, headerDiv); + headerValue = req.substring(headerDiv + 1); + headerValue.trim(); + _collectHeader(headerName.c_str(),headerValue.c_str()); + + log_v("headerName: %s", headerName.c_str()); + log_v("headerValue: %s", headerValue.c_str()); + + if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ + using namespace mime; + if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ + isForm = false; + } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ + isForm = false; + isEncoded = true; + } else if (headerValue.startsWith(F("multipart/"))){ + boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); + boundaryStr.replace("\"",""); + isForm = true; + } + } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ + contentLength = headerValue.toInt(); + } else if (headerName.equalsIgnoreCase(F("Host"))){ + _hostHeader = headerValue; + } + } + + if (!isForm){ + size_t plainLength; + char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT); + if (plainLength < contentLength) { + free(plainBuf); + return false; + } + if (contentLength > 0) { + if(isEncoded){ + //url encoded form + if (searchStr != "") searchStr += '&'; + searchStr += plainBuf; + } + _parseArguments(searchStr); + if(!isEncoded){ + //plain post json or other data + RequestArgument& arg = _currentArgs[_currentArgCount++]; + arg.key = F("plain"); + arg.value = String(plainBuf); + } + + log_v("Plain: %s", plainBuf); + free(plainBuf); + } else { + // No content - but we can still have arguments in the URL. + _parseArguments(searchStr); + } + } + + if (isForm){ + _parseArguments(searchStr); + if (!_parseForm(client, boundaryStr, contentLength)) { + return false; + } + } + } else { + String headerName; + String headerValue; + //parse headers + while(1){ + req = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (req == "") break;//no moar headers + int headerDiv = req.indexOf(':'); + if (headerDiv == -1){ + break; + } + headerName = req.substring(0, headerDiv); + headerValue = req.substring(headerDiv + 2); + _collectHeader(headerName.c_str(),headerValue.c_str()); + + log_v("headerName: %s", headerName.c_str()); + log_v("headerValue: %s", headerValue.c_str()); + + if (headerName.equalsIgnoreCase("Host")){ + _hostHeader = headerValue; + } + } + _parseArguments(searchStr); + } + client.flush(); + + log_v("Request: %s", url.c_str()); + log_v(" Arguments: %s", searchStr.c_str()); + + return true; +} + +bool EthernetWebServer::_collectHeader(const char* headerName, const char* headerValue) { + for (int i = 0; i < _headerKeysCount; i++) { + if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { + _currentHeaders[i].value=headerValue; + return true; + } + } + return false; +} + +void EthernetWebServer::_parseArguments(String data) { + log_v("args: %s", data.c_str()); + if (_currentArgs) + delete[] _currentArgs; + _currentArgs = 0; + if (data.length() == 0) { + _currentArgCount = 0; + _currentArgs = new RequestArgument[1]; + return; + } + _currentArgCount = 1; + + for (int i = 0; i < (int)data.length(); ) { + i = data.indexOf('&', i); + if (i == -1) + break; + ++i; + ++_currentArgCount; + } + log_v("args count: %d", _currentArgCount); + + _currentArgs = new RequestArgument[_currentArgCount+1]; + int pos = 0; + int iarg; + for (iarg = 0; iarg < _currentArgCount;) { + int equal_sign_index = data.indexOf('=', pos); + int next_arg_index = data.indexOf('&', pos); + log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index); + if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { + log_e("arg missing value: %d", iarg); + if (next_arg_index == -1) + break; + pos = next_arg_index + 1; + continue; + } + RequestArgument& arg = _currentArgs[iarg]; + arg.key = urlDecode(data.substring(pos, equal_sign_index)); + arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index)); + log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str()); + ++iarg; + if (next_arg_index == -1) + break; + pos = next_arg_index + 1; + } + _currentArgCount = iarg; + log_v("args count: %d", _currentArgCount); + +} + +void EthernetWebServer::_uploadWriteByte(uint8_t b){ + if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){ + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, *_currentUpload); + _currentUpload->totalSize += _currentUpload->currentSize; + _currentUpload->currentSize = 0; + } + _currentUpload->buf[_currentUpload->currentSize++] = b; +} + +int EthernetWebServer::_uploadReadByte(EthernetClient& client){ + int res = client.read(); + if(res < 0) { + // keep trying until you either read a valid byte or timeout + unsigned long startMillis = millis(); + long timeoutIntervalMillis = client.getTimeout(); + boolean timedOut = false; + for(;;) { + if (!client.connected()) return -1; + // loosely modeled after blinkWithoutDelay pattern + while(!timedOut && !client.available() && client.connected()){ + delay(2); + timedOut = millis() - startMillis >= timeoutIntervalMillis; + } + + res = client.read(); + if(res >= 0) { + return res; // exit on a valid read + } + // NOTE: it is possible to get here and have all of the following + // assertions hold true + // + // -- client.available() > 0 + // -- client.connected == true + // -- res == -1 + // + // a simple retry strategy overcomes this which is to say the + // assertion is not permanent, but the reason that this works + // is elusive, and possibly indicative of a more subtle underlying + // issue + + timedOut = millis() - startMillis >= timeoutIntervalMillis; + if(timedOut) { + return res; // exit on a timeout + } + } + } + + return res; +} + +bool EthernetWebServer::_parseForm(EthernetClient& client, String boundary, uint32_t len){ + (void) len; + log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len); + String line; + int retry = 0; + do { + line = client.readStringUntil('\r'); + ++retry; + } while (line.length() == 0 && retry < 3); + + client.readStringUntil('\n'); + //start reading the form + if (line == ("--"+boundary)){ + if(_postArgs) delete[] _postArgs; + _postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS]; + _postArgsLen = 0; + while(1){ + String argName; + String argValue; + String argType; + String argFilename; + bool argIsFile = false; + + line = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ + int nameStart = line.indexOf('='); + if (nameStart != -1){ + argName = line.substring(nameStart+2); + nameStart = argName.indexOf('='); + if (nameStart == -1){ + argName = argName.substring(0, argName.length() - 1); + } else { + argFilename = argName.substring(nameStart+2, argName.length() - 1); + argName = argName.substring(0, argName.indexOf('"')); + argIsFile = true; + log_v("PostArg FileName: %s",argFilename.c_str()); + //use GET to set the filename if uploading using blob + if (argFilename == F("blob") && hasArg(FPSTR(filename))) + argFilename = arg(FPSTR(filename)); + } + log_v("PostArg Name: %s", argName.c_str()); + using namespace mime; + argType = FPSTR(mimeTable[txt].mimeType); + line = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ + argType = line.substring(line.indexOf(':')+2); + //skip next line + client.readStringUntil('\r'); + client.readStringUntil('\n'); + } + log_v("PostArg Type: %s", argType.c_str()); + if (!argIsFile){ + while(1){ + line = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (line.startsWith("--"+boundary)) break; + if (argValue.length() > 0) argValue += "\n"; + argValue += line; + } + log_v("PostArg Value: %s", argValue.c_str()); + + RequestArgument& arg = _postArgs[_postArgsLen++]; + arg.key = argName; + arg.value = argValue; + + if (line == ("--"+boundary+"--")){ + log_v("Done Parsing POST"); + break; + } else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) { + log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS); + return false; + } + } else { + _currentUpload.reset(new HTTPUpload()); + _currentUpload->status = UPLOAD_FILE_START; + _currentUpload->name = argName; + _currentUpload->filename = argFilename; + _currentUpload->type = argType; + _currentUpload->totalSize = 0; + _currentUpload->currentSize = 0; + log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str()); + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, *_currentUpload); + _currentUpload->status = UPLOAD_FILE_WRITE; + int argByte = _uploadReadByte(client); +readfile: + + while(argByte != 0x0D){ + if(argByte < 0) return _parseFormUploadAborted(); + _uploadWriteByte(argByte); + argByte = _uploadReadByte(client); + } + + argByte = _uploadReadByte(client); + if(argByte < 0) return _parseFormUploadAborted(); + if (argByte == 0x0A){ + argByte = _uploadReadByte(client); + if(argByte < 0) return _parseFormUploadAborted(); + if ((char)argByte != '-'){ + //continue reading the file + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + goto readfile; + } else { + argByte = _uploadReadByte(client); + if(argByte < 0) return _parseFormUploadAborted(); + if ((char)argByte != '-'){ + //continue reading the file + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + _uploadWriteByte((uint8_t)('-')); + goto readfile; + } + } + + uint8_t endBuf[boundary.length()]; + uint32_t i = 0; + while(i < boundary.length()){ + argByte = _uploadReadByte(client); + if(argByte < 0) return _parseFormUploadAborted(); + if ((char)argByte == 0x0D){ + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + _uploadWriteByte((uint8_t)('-')); + _uploadWriteByte((uint8_t)('-')); + uint32_t j = 0; + while(j < i){ + _uploadWriteByte(endBuf[j++]); + } + goto readfile; + } + endBuf[i++] = (uint8_t)argByte; + } + + if (strstr((const char*)endBuf, boundary.c_str()) != NULL){ + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, *_currentUpload); + _currentUpload->totalSize += _currentUpload->currentSize; + _currentUpload->status = UPLOAD_FILE_END; + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, *_currentUpload); + log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize); + line = client.readStringUntil(0x0D); + client.readStringUntil(0x0A); + if (line == "--"){ + log_v("Done Parsing POST"); + break; + } + continue; + } else { + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + _uploadWriteByte((uint8_t)('-')); + _uploadWriteByte((uint8_t)('-')); + uint32_t i = 0; + while(i < boundary.length()){ + _uploadWriteByte(endBuf[i++]); + } + argByte = _uploadReadByte(client); + goto readfile; + } + } else { + _uploadWriteByte(0x0D); + goto readfile; + } + break; + } + } + } + } + + int iarg; + int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)?(WEBSERVER_MAX_POST_ARGS - _postArgsLen):_currentArgCount; + for (iarg = 0; iarg < totalArgs; iarg++){ + RequestArgument& arg = _postArgs[_postArgsLen++]; + arg.key = _currentArgs[iarg].key; + arg.value = _currentArgs[iarg].value; + } + if (_currentArgs) delete[] _currentArgs; + _currentArgs = new RequestArgument[_postArgsLen]; + for (iarg = 0; iarg < _postArgsLen; iarg++){ + RequestArgument& arg = _currentArgs[iarg]; + arg.key = _postArgs[iarg].key; + arg.value = _postArgs[iarg].value; + } + _currentArgCount = iarg; + if (_postArgs) { + delete[] _postArgs; + _postArgs=nullptr; + _postArgsLen = 0; + } + return true; + } + log_e("Error: line: %s", line.c_str()); + return false; +} + +String EthernetWebServer::urlDecode(const String& text) +{ + String decoded = ""; + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + 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 += decodedChar; + } + return decoded; +} + +bool EthernetWebServer::_parseFormUploadAborted(){ + _currentUpload->status = UPLOAD_FILE_ABORTED; + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, *_currentUpload); + return false; +} diff --git a/lib/EthernetWebServer/src/Uri.h b/lib/EthernetWebServer/src/Uri.h new file mode 100644 index 0000000..d924a0b --- /dev/null +++ b/lib/EthernetWebServer/src/Uri.h @@ -0,0 +1,29 @@ +#ifndef URI_H +#define URI_H + +#include +#include + +class Uri { + + protected: + const String _uri; + + public: + Uri(const char *uri) : _uri(uri) {} + Uri(const String &uri) : _uri(uri) {} + Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {} + virtual ~Uri() {} + + virtual Uri* clone() const { + return new Uri(_uri); + }; + + virtual void initPathArgs(__attribute__((unused)) std::vector &pathArgs) {} + + virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector &pathArgs) { + return _uri == requestUri; + } +}; + +#endif diff --git a/lib/EthernetWebServer/src/detail/RequestHandler.h b/lib/EthernetWebServer/src/detail/RequestHandler.h new file mode 100644 index 0000000..ef4cd11 --- /dev/null +++ b/lib/EthernetWebServer/src/detail/RequestHandler.h @@ -0,0 +1,31 @@ +#ifndef REQUESTHANDLER_H +#define REQUESTHANDLER_H + +#include +#include + +class RequestHandler { +public: + virtual ~RequestHandler() { } + virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } + virtual bool canUpload(String uri) { (void) uri; return false; } + virtual bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } + virtual void upload(EthernetWebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; } + + RequestHandler* next() { return _next; } + void next(RequestHandler* r) { _next = r; } + +private: + RequestHandler* _next = nullptr; + +protected: + std::vector pathArgs; + +public: + const String& pathArg(unsigned int i) { + assert(i < pathArgs.size()); + return pathArgs[i]; + } +}; + +#endif //REQUESTHANDLER_H diff --git a/lib/EthernetWebServer/src/detail/RequestHandlersImpl.h b/lib/EthernetWebServer/src/detail/RequestHandlersImpl.h new file mode 100644 index 0000000..217b616 --- /dev/null +++ b/lib/EthernetWebServer/src/detail/RequestHandlersImpl.h @@ -0,0 +1,151 @@ +#ifndef REQUESTHANDLERSIMPL_H +#define REQUESTHANDLERSIMPL_H + +#include "RequestHandler.h" +#include "mimetable.h" +#include "WString.h" +#include "Uri.h" + +using namespace mime; + +class FunctionRequestHandler : public RequestHandler { +public: + FunctionRequestHandler(EthernetWebServer::THandlerFunction fn, EthernetWebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method) + : _fn(fn) + , _ufn(ufn) + , _uri(uri.clone()) + , _method(method) + { + _uri->initPathArgs(pathArgs); + } + + ~FunctionRequestHandler() { + delete _uri; + } + + bool canHandle(HTTPMethod requestMethod, String requestUri) override { + if (_method != HTTP_ANY && _method != requestMethod) + return false; + + return _uri->canHandle(requestUri, pathArgs); + } + + bool canUpload(String requestUri) override { + if (!_ufn || !canHandle(HTTP_POST, requestUri)) + return false; + + return true; + } + + bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) override { + (void) server; + if (!canHandle(requestMethod, requestUri)) + return false; + + _fn(); + return true; + } + + void upload(EthernetWebServer& server, String requestUri, HTTPUpload& upload) override { + (void) server; + (void) upload; + if (canUpload(requestUri)) + _ufn(); + } + +protected: + EthernetWebServer::THandlerFunction _fn; + EthernetWebServer::THandlerFunction _ufn; + Uri *_uri; + HTTPMethod _method; +}; + +class StaticRequestHandler : public RequestHandler { +public: + StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header) + : _fs(fs) + , _uri(uri) + , _path(path) + , _cache_header(cache_header) + { + _isFile = fs.exists(path); + log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header); + _baseUriLength = _uri.length(); + } + + bool canHandle(HTTPMethod requestMethod, String requestUri) override { + if (requestMethod != HTTP_GET) + return false; + + if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) + return false; + + return true; + } + + bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) override { + if (!canHandle(requestMethod, requestUri)) + return false; + + log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); + + String path(_path); + + if (!_isFile) { + // Base URI doesn't point to a file. + // If a directory is requested, look for index file. + if (requestUri.endsWith("/")) + requestUri += "index.htm"; + + // Append whatever follows this URI in request to get the file path. + path += requestUri.substring(_baseUriLength); + } + log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); + + String contentType = getContentType(path); + + // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for + // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... + if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) { + String pathWithGz = path + FPSTR(mimeTable[gz].endsWith); + if(_fs.exists(pathWithGz)) + path += FPSTR(mimeTable[gz].endsWith); + } + + File f = _fs.open(path, "r"); + if (!f || !f.available()) + return false; + + if (_cache_header.length() != 0) + server.sendHeader("Cache-Control", _cache_header); + + server.streamFile(f, contentType); + return true; + } + + static String getContentType(const String& path) { + char buff[sizeof(mimeTable[0].mimeType)]; + // Check all entries but last one for match, return if found + for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) { + strcpy_P(buff, mimeTable[i].endsWith); + if (path.endsWith(buff)) { + strcpy_P(buff, mimeTable[i].mimeType); + return String(buff); + } + } + // Fall-through and just return default type + strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType); + return String(buff); + } + +protected: + FS _fs; + String _uri; + String _path; + String _cache_header; + bool _isFile; + size_t _baseUriLength; +}; + + +#endif //REQUESTHANDLERSIMPL_H diff --git a/lib/EthernetWebServer/src/detail/mimetable.cpp b/lib/EthernetWebServer/src/detail/mimetable.cpp new file mode 100644 index 0000000..563556a --- /dev/null +++ b/lib/EthernetWebServer/src/detail/mimetable.cpp @@ -0,0 +1,35 @@ +#include "mimetable.h" +#include "pgmspace.h" + +namespace mime +{ + +// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules +const Entry mimeTable[maxType] = +{ + { ".html", "text/html" }, + { ".htm", "text/html" }, + { ".css", "text/css" }, + { ".txt", "text/plain" }, + { ".js", "application/javascript" }, + { ".json", "application/json" }, + { ".png", "image/png" }, + { ".gif", "image/gif" }, + { ".jpg", "image/jpeg" }, + { ".ico", "image/x-icon" }, + { ".svg", "image/svg+xml" }, + { ".ttf", "application/x-font-ttf" }, + { ".otf", "application/x-font-opentype" }, + { ".woff", "application/font-woff" }, + { ".woff2", "application/font-woff2" }, + { ".eot", "application/vnd.ms-fontobject" }, + { ".sfnt", "application/font-sfnt" }, + { ".xml", "text/xml" }, + { ".pdf", "application/pdf" }, + { ".zip", "application/zip" }, + { ".gz", "application/x-gzip" }, + { ".appcache", "text/cache-manifest" }, + { "", "application/octet-stream" } +}; + +} diff --git a/lib/EthernetWebServer/src/detail/mimetable.h b/lib/EthernetWebServer/src/detail/mimetable.h new file mode 100644 index 0000000..191356c --- /dev/null +++ b/lib/EthernetWebServer/src/detail/mimetable.h @@ -0,0 +1,47 @@ +#ifndef __MIMETABLE_H__ +#define __MIMETABLE_H__ + + +namespace mime +{ + +enum type +{ + html, + htm, + css, + txt, + js, + json, + png, + gif, + jpg, + ico, + svg, + ttf, + otf, + woff, + woff2, + eot, + sfnt, + xml, + pdf, + zip, + gz, + appcache, + none, + maxType +}; + +struct Entry +{ + const char endsWith[16]; + const char mimeType[32]; +}; + + +extern const Entry mimeTable[maxType]; +} + + +#endif diff --git a/lib/EthernetWebServer/src/uri/UriBraces.h b/lib/EthernetWebServer/src/uri/UriBraces.h new file mode 100644 index 0000000..4a6049b --- /dev/null +++ b/lib/EthernetWebServer/src/uri/UriBraces.h @@ -0,0 +1,66 @@ +#ifndef URI_BRACES_H +#define URI_BRACES_H + +#include "Uri.h" + +class UriBraces : public Uri { + + public: + explicit UriBraces(const char *uri) : Uri(uri) {}; + explicit UriBraces(const String &uri) : Uri(uri) {}; + + Uri* clone() const override final { + return new UriBraces(_uri); + }; + + void initPathArgs(std::vector &pathArgs) override final { + int numParams = 0, start = 0; + do { + start = _uri.indexOf("{}", start); + if (start > 0) { + numParams++; + start += 2; + } + } while (start > 0); + pathArgs.resize(numParams); + } + + bool canHandle(const String &requestUri, std::vector &pathArgs) override final { + if (Uri::canHandle(requestUri, pathArgs)) + return true; + + size_t uriLength = _uri.length(); + unsigned int pathArgIndex = 0; + unsigned int requestUriIndex = 0; + for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) { + char uriChar = _uri[i]; + char requestUriChar = requestUri[requestUriIndex]; + + if (uriChar == requestUriChar) + continue; + if (uriChar != '{') + return false; + + i += 2; // index of char after '}' + if (i >= uriLength) { + // there is no char after '}' + pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex); + return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/' + } + else + { + char charEnd = _uri[i]; + int uriIndex = requestUri.indexOf(charEnd, requestUriIndex); + if (uriIndex < 0) + return false; + pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex); + requestUriIndex = (unsigned int) uriIndex; + } + pathArgIndex++; + } + + return requestUriIndex >= requestUri.length(); + } +}; + +#endif diff --git a/lib/EthernetWebServer/src/uri/UriGlob.h b/lib/EthernetWebServer/src/uri/UriGlob.h new file mode 100644 index 0000000..1e222cb --- /dev/null +++ b/lib/EthernetWebServer/src/uri/UriGlob.h @@ -0,0 +1,22 @@ +#ifndef URI_GLOB_H +#define URI_GLOB_H + +#include "Uri.h" +#include + +class UriGlob : public Uri { + + public: + explicit UriGlob(const char *uri) : Uri(uri) {}; + explicit UriGlob(const String &uri) : Uri(uri) {}; + + Uri* clone() const override final { + return new UriGlob(_uri); + }; + + bool canHandle(const String &requestUri, __attribute__((unused)) std::vector &pathArgs) override final { + return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0; + } +}; + +#endif diff --git a/lib/EthernetWebServer/src/uri/UriRegex.h b/lib/EthernetWebServer/src/uri/UriRegex.h new file mode 100644 index 0000000..0570201 --- /dev/null +++ b/lib/EthernetWebServer/src/uri/UriRegex.h @@ -0,0 +1,44 @@ +#ifndef URI_REGEX_H +#define URI_REGEX_H + +#include "Uri.h" +#include + +class UriRegex : public Uri { + + public: + explicit UriRegex(const char *uri) : Uri(uri) {}; + explicit UriRegex(const String &uri) : Uri(uri) {}; + + Uri* clone() const override final { + return new UriRegex(_uri); + }; + + void initPathArgs(std::vector &pathArgs) override final { + std::regex rgx((_uri + "|").c_str()); + std::smatch matches; + std::string s{""}; + std::regex_search(s, matches, rgx); + pathArgs.resize(matches.size() - 1); + } + + bool canHandle(const String &requestUri, std::vector &pathArgs) override final { + if (Uri::canHandle(requestUri, pathArgs)) + return true; + + unsigned int pathArgIndex = 0; + std::regex rgx(_uri.c_str()); + std::smatch matches; + std::string s(requestUri.c_str()); + if (std::regex_search(s, matches, rgx)) { + for (size_t i = 1; i < matches.size(); ++i) { // skip first + pathArgs[pathArgIndex] = String(matches[i].str().c_str()); + pathArgIndex++; + } + return true; + } + return false; + } +}; + +#endif diff --git a/main.cpp b/main.cpp index 0379bd7..59d652f 100644 --- a/main.cpp +++ b/main.cpp @@ -85,8 +85,8 @@ void setup() Serial.begin(115200); - const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi; -// const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500; +// const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi; + const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500; preferences = new Preferences(); preferences->begin("nukihub", false);