diff --git a/libraries/Ethernet/examples/Dhcp/Dhcp.ino b/libraries/Ethernet/examples/Dhcp/Dhcp.ino new file mode 100644 index 00000000000..7a34cea9145 --- /dev/null +++ b/libraries/Ethernet/examples/Dhcp/Dhcp.ino @@ -0,0 +1,43 @@ +#include +#include "WiFi.h" + +// declare the ethernet adapter +ESP32Ethernet ethernet; + +void WiFiEvent(WiFiEvent_t event){ + switch(event) { + case SYSTEM_EVENT_ETH_START: + //set eth hostname here + ethernet.setHostname("esp32-eth"); + Serial.println("ETH started"); + Serial.print("ETH MAC: "); + Serial.println(ethernet.getMacAddress()); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + Serial.println("ETH connected"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + Serial.print("ETH IPv4: "); + Serial.println(ethernet.localIP()); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + Serial.println("ETH disconnected"); + break; + default: + break; + } +} + +void setup(){ + // start the serial console + Serial.begin(115200); + // attach the callback event handler + WiFi.onEvent(WiFiEvent); + // start the ethernet + ethernet.begin(); +} + +void loop(){ + // nothing yet to do here +} + diff --git a/libraries/Ethernet/examples/Static/Static.ino b/libraries/Ethernet/examples/Static/Static.ino new file mode 100644 index 00000000000..c2cf7518988 --- /dev/null +++ b/libraries/Ethernet/examples/Static/Static.ino @@ -0,0 +1,48 @@ +#include +#include "WiFi.h" + +// declare the ethernet adapter +ESP32Ethernet ethernet; + +// define the network settings +IPAddress ip(192, 168, 0, 123); +IPAddress nm(255, 255, 255, 0); +IPAddress gw(192, 168, 0, 1); + +void WiFiEvent(WiFiEvent_t event){ + switch(event) { + case SYSTEM_EVENT_ETH_START: + //set eth hostname here + ethernet.setHostname("esp32-eth"); + Serial.println("ETH started"); + Serial.print("ETH MAC: "); + Serial.println(ethernet.getMacAddress()); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + Serial.println("ETH connected"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + Serial.print("ETH IPv4: "); + Serial.println(ethernet.localIP()); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + Serial.println("ETH disconnected"); + break; + default: + break; + } +} + +void setup(){ + // start the serial console + Serial.begin(115200); + // attach the callback event handler + WiFi.onEvent(WiFiEvent); + // start the ethernet + ethernet.begin(ip,nm,gw); +} + +void loop(){ + // nothing yet to do here +} + diff --git a/libraries/Ethernet/library.properties b/libraries/Ethernet/library.properties new file mode 100644 index 00000000000..53fdbdd36aa --- /dev/null +++ b/libraries/Ethernet/library.properties @@ -0,0 +1,9 @@ +name=Ethernet(ESP32-EVB) +version=1.0.5 +author=Bob Fisch +maintainer=Bob Fisch +sentence=Enables network connection (local and Internet) the onboard Ethernet controller. (ESP32-EVB from www.olimex.com) +paragraph=With this library you can use the port Ethernet to connect to Internet. The library permits you to connect to a local network also with DHCP and to resolve DNS. +category=Communication +url= +architectures=esp32 diff --git a/libraries/Ethernet/src/Ethernet.cpp b/libraries/Ethernet/src/Ethernet.cpp new file mode 100644 index 00000000000..03a2890cf6f --- /dev/null +++ b/libraries/Ethernet/src/Ethernet.cpp @@ -0,0 +1,142 @@ +#include "Ethernet.h" + +extern "C" { + #include "WiFiGeneric.h" + //void tcpipInit(); +}; + + +static eth_config_t eth_config = ETH_PHY_CONF; + +static void ethernet_config_gpio(void){ + // RMII data pins are fixed: + // CRS_DRV = GPIO27 + // TXD0 = GPIO19 + // TXD1 = GPIO22 + // TX_EN = GPIO21 + // RXD0 = GPIO25 + // RXD1 = GPIO26 + // CLK == GPIO0 + phy_rmii_configure_data_interface_pins(); + // MDC is GPIO 23, MDIO is GPIO 18 + phy_rmii_smi_configure_pins(PIN_SMI_MDC, PIN_SMI_MDIO); +} + +void tcpipInit(); + +int EthernetClass::begin() +{ + eth_config.phy_addr = ETH_PHY_ADDR; + eth_config.gpio_config = ethernet_config_gpio; + eth_config.tcpip_input = tcpip_adapter_eth_input; + //eth_config.phy_power_enable = ethernet_power_enable; + tcpipInit(); + esp_err_t err = esp_eth_init(ð_config); + if(!err){ + err = esp_eth_enable(); + return 1; + } + return 0; +} + +int EthernetClass::config(IPAddress local_ip, IPAddress subnet, IPAddress gateway) +{ + // stop the DHCP service + if(tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH) == ESP_OK) { + // continue + } + else { + return 0; + } + + // create a new TCP/IP info + tcpip_adapter_ip_info_t info; + info.ip.addr = static_cast((local_ip)); + info.gw.addr = static_cast((gateway)); + info.netmask.addr = static_cast((subnet)); + // apply the IP settings + if(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info) == ESP_OK) { + // continue + } else { + return 0; + } + + return 1; +} + +int EthernetClass::begin(IPAddress local_ip, IPAddress subnet, IPAddress gateway) +{ + // connect the callback function + esp_event_loop_init(&WiFiGenericClass::_eventCallback, NULL); + + // init the TCP/IP adapter + tcpip_adapter_init(); + + // manually configure the adapter + if(!config(local_ip,subnet,gateway)) + { + return 0; + } + + eth_config.phy_addr = ETH_PHY_ADDR; + eth_config.gpio_config = ethernet_config_gpio; + eth_config.tcpip_input = tcpip_adapter_eth_input; + //eth_config.phy_power_enable = ethernet_power_enable; + esp_err_t err = esp_eth_init(ð_config); + if(!err){ + err = esp_eth_enable(); + return 1; + } + + return 0; +} + +IPAddress EthernetClass::localIP() +{ + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.ip.addr); +} + +IPAddress EthernetClass::subnetMask() +{ + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.netmask.addr); + +} + +IPAddress EthernetClass::gatewayIP() +{ + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.gw.addr); +} + +bool EthernetClass::setHostname(const char * hostname) +{ + return tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, hostname) == 0; +} + +bool EthernetClass::isFullDuplex() +{ + return eth_config.phy_get_duplex_mode(); +} + +uint8_t EthernetClass::getLinkSpeed() +{ + return eth_config.phy_get_speed_mode()?100:10; +} + +String EthernetClass::getMacAddress() +{ + uint8_t mac[6]; + char macStr[18] = { 0 }; + esp_eth_get_mac(mac); + sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macStr); +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ETHERNET) +EthernetClass Ethernet; +#endif diff --git a/libraries/Ethernet/src/Ethernet.h b/libraries/Ethernet/src/Ethernet.h new file mode 100644 index 00000000000..7a00bc8e7c5 --- /dev/null +++ b/libraries/Ethernet/src/Ethernet.h @@ -0,0 +1,43 @@ +#ifndef ethernet_h +#define ethernet_h + +//#include +#include "IPAddress.h" +#include "WiFi.h" +#include "esp_eth.h" +#include "eth_phy/phy.h" +#include "eth_phy/phy_lan8720.h" + +#define ETH_PHY_CONF phy_lan8720_default_ethernet_config +#define ETH_PHY_ADDR PHY0 +//#define PIN_PHY_POWER 17 +#define PIN_SMI_MDC 23 +#define PIN_SMI_MDIO 18 + +class EthernetClass { +private: + +public: + // Initialise the ethernet controller and gain every configuration through DHCP. + // Returns 0 if the DHCP configuration failed, and 1 if it succeeded + int begin(); + int begin(IPAddress local_ip, IPAddress subnet, IPAddress gateway); + int config(IPAddress local_ip, IPAddress subnet, IPAddress gateway); + + IPAddress localIP(); + IPAddress subnetMask(); + IPAddress gatewayIP(); + + bool setHostname(const char * hostname); + bool isFullDuplex(); + uint8_t getLinkSpeed(); + String getMacAddress(); + + friend class EthernetClient; + friend class EthernetServer; +}; + +class ESP32Ethernet : public EthernetClass { +}; + +#endif diff --git a/libraries/WebServer/examples/SimpleWebServer/SimpleWebServer.ino b/libraries/WebServer/examples/SimpleWebServer/SimpleWebServer.ino new file mode 100644 index 00000000000..7e9847794dc --- /dev/null +++ b/libraries/WebServer/examples/SimpleWebServer/SimpleWebServer.ino @@ -0,0 +1,41 @@ +#include +#include "WiFi.h" +#include + +// declare the ethernet adapter +ESP32Ethernet ethernet; +// define a webserver on port 80 +ESP32WebServer server(80); + +void setup(){ + Serial.begin(115200); + + // start the ethernet adapter (DHCP) + ethernet.begin(); + + // attach handles + server.on("/",handleRoot); + server.on("/test",handleTest); + + // start the server + server.begin(); +} + +void handleRoot() +{ + String html = "You just loaded the root of your ESP WebServer

Goto /test"; + server.setContentLength(html.length()); + server.send(200,"text/html",html); +} + +void handleTest() +{ + String html = "You just entered /test ..."; + server.setContentLength(html.length()); + server.send(200,"text/html",html); +} + +void loop(){ + // hanle clients + server.handleClient(); +} diff --git a/libraries/WebServer/keywords.txt b/libraries/WebServer/keywords.txt new file mode 100644 index 00000000000..22e3d74e426 --- /dev/null +++ b/libraries/WebServer/keywords.txt @@ -0,0 +1,36 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP8266WebServer 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 diff --git a/libraries/WebServer/library.properties b/libraries/WebServer/library.properties new file mode 100644 index 00000000000..2771a2bdb34 --- /dev/null +++ b/libraries/WebServer/library.properties @@ -0,0 +1,9 @@ +name=ESP32WebServer +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/libraries/WebServer/src/ESP32WebServer.cpp b/libraries/WebServer/src/ESP32WebServer.cpp new file mode 100644 index 00000000000..827171e4b53 --- /dev/null +++ b/libraries/WebServer/src/ESP32WebServer.cpp @@ -0,0 +1,525 @@ +/* + ESP32WebServer.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 "WiFiServer.h" +#include "WiFiClient.h" +#include "ESP32WebServer.h" +#include "FS.h" +#include "detail/RequestHandlersImpl.h" + +//#define DEBUG_ESP_HTTP_SERVER +#ifdef DEBUG_ESP_PORT +#define DEBUG_OUTPUT DEBUG_ESP_PORT +#else +#define DEBUG_OUTPUT Serial +#endif + +const char * AUTHORIZATION_HEADER = "Authorization"; + +ESP32WebServer::ESP32WebServer(IPAddress addr, int port) +: _server(addr, port) +, _currentMethod(HTTP_ANY) +, _currentVersion(0) +, _currentStatus(HC_NONE) +, _statusChange(0) +, _currentHandler(0) +, _firstHandler(0) +, _lastHandler(0) +, _currentArgCount(0) +, _currentArgs(0) +, _headerKeysCount(0) +, _currentHeaders(0) +, _contentLength(0) +, _chunked(false) +{ +} + +ESP32WebServer::ESP32WebServer(int port) +: _server(port) +, _currentMethod(HTTP_ANY) +, _currentVersion(0) +, _currentStatus(HC_NONE) +, _statusChange(0) +, _currentHandler(0) +, _firstHandler(0) +, _lastHandler(0) +, _currentArgCount(0) +, _currentArgs(0) +, _headerKeysCount(0) +, _currentHeaders(0) +, _contentLength(0) +, _chunked(false) +{ +} + +ESP32WebServer::~ESP32WebServer() { + if (_currentHeaders) + delete[]_currentHeaders; + _headerKeysCount = 0; + RequestHandler* handler = _firstHandler; + while (handler) { + RequestHandler* next = handler->next(); + delete handler; + handler = next; + } + close(); +} + +void ESP32WebServer::begin() { + _currentStatus = HC_NONE; + _server.begin(); + if(!_headerKeysCount) + collectHeaders(0, 0); +} + +bool ESP32WebServer::authenticate(const char * username, const char * password){ + if(hasHeader(AUTHORIZATION_HEADER)){ + String authReq = header(AUTHORIZATION_HEADER); + if(authReq.startsWith("Basic")){ + authReq = authReq.substring(6); + authReq.trim(); + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen + 1]; + if(toencode == NULL){ + authReq = String(); + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + authReq = String(); + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){ + authReq = String(); + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } + authReq = String(); + } + return false; +} + +void ESP32WebServer::requestAuthentication(){ + sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); + send(401); +} + +void ESP32WebServer::on(const String &uri, ESP32WebServer::THandlerFunction handler) { + on(uri, HTTP_ANY, handler); +} + +void ESP32WebServer::on(const String &uri, HTTPMethod method, ESP32WebServer::THandlerFunction fn) { + on(uri, method, fn, _fileUploadHandler); +} + +void ESP32WebServer::on(const String &uri, HTTPMethod method, ESP32WebServer::THandlerFunction fn, ESP32WebServer::THandlerFunction ufn) { + _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); +} + +void ESP32WebServer::addHandler(RequestHandler* handler) { + _addRequestHandler(handler); +} + +void ESP32WebServer::_addRequestHandler(RequestHandler* handler) { + if (!_lastHandler) { + _firstHandler = handler; + _lastHandler = handler; + } + else { + _lastHandler->next(handler); + _lastHandler = handler; + } +} + +void ESP32WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { + _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); +} + +void ESP32WebServer::handleClient() { + if (_currentStatus == HC_NONE) { + WiFiClient client = _server.available(); + if (!client) { + return; + } + +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("New client"); +#endif + + _currentClient = client; + _currentStatus = HC_WAIT_READ; + _statusChange = millis(); + } + + if (!_currentClient.connected()) { + _currentClient = WiFiClient(); + _currentStatus = HC_NONE; + return; + } + + // Wait for data from client to become available + if (_currentStatus == HC_WAIT_READ) { + if (!_currentClient.available()) { + if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) { + _currentClient = WiFiClient(); + _currentStatus = HC_NONE; + } + yield(); + return; + } + + if (!_parseRequest(_currentClient)) { + _currentClient = WiFiClient(); + _currentStatus = HC_NONE; + return; + } + _currentClient.setTimeout(HTTP_MAX_SEND_WAIT); + _contentLength = CONTENT_LENGTH_NOT_SET; + _handleRequest(); + + if (!_currentClient.connected()) { + _currentClient = WiFiClient(); + _currentStatus = HC_NONE; + return; + } else { + _currentStatus = HC_WAIT_CLOSE; + _statusChange = millis(); + return; + } + } + + if (_currentStatus == HC_WAIT_CLOSE) { + if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) { + _currentClient = WiFiClient(); + _currentStatus = HC_NONE; + } else { + yield(); + return; + } + } +} + +void ESP32WebServer::close() { + _server.close(); +} + +void ESP32WebServer::stop() { + close(); +} + +void ESP32WebServer::sendHeader(const String& name, const String& value, bool first) { + String headerLine = name; + headerLine += ": "; + headerLine += value; + headerLine += "\r\n"; + + if (first) { + _responseHeaders = headerLine + _responseHeaders; + } + else { + _responseHeaders += headerLine; + } +} + +void ESP32WebServer::setContentLength(size_t contentLength) { + _contentLength = contentLength; +} + +void ESP32WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { + response = "HTTP/1."+String(_currentVersion)+" "; + response += String(code); + response += " "; + response += _responseCodeToString(code); + response += "\r\n"; + + if (!content_type) + content_type = "text/html"; + + sendHeader("Content-Type", content_type, true); + if (_contentLength == CONTENT_LENGTH_NOT_SET) { + sendHeader("Content-Length", String(contentLength)); + } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { + sendHeader("Content-Length", String(_contentLength)); + } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client + //let's do chunked + _chunked = true; + sendHeader("Accept-Ranges","none"); + sendHeader("Transfer-Encoding","chunked"); + } + sendHeader("Connection", "close"); + + response += _responseHeaders; + response += "\r\n"; + _responseHeaders = String(); +} + +void ESP32WebServer::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()); + _currentClient.write(header.c_str(), header.length()); + if(content.length()) + sendContent(content); +} + +void ESP32WebServer::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); + _currentClient.write(header.c_str(), header.length()); + sendContent_P(content); +} + +void ESP32WebServer::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 ESP32WebServer::send(int code, char* content_type, const String& content) { + send(code, (const char*)content_type, content); +} + +void ESP32WebServer::send(int code, const String& content_type, const String& content) { + send(code, (const char*)content_type.c_str(), content); +} + +void ESP32WebServer::sendContent(const String& content) { + const char * footer = "\r\n"; + size_t len = content.length(); + if(_chunked) { + char * chunkSize = (char *)malloc(11); + if(chunkSize){ + sprintf(chunkSize, "%x%s", len, footer); + _currentClient.write(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + _currentClient.write(content.c_str(), len); + if(_chunked){ + _currentClient.write(footer, 2); + } +} + +void ESP32WebServer::sendContent_P(PGM_P content) { + sendContent_P(content, strlen_P(content)); +} + +void ESP32WebServer::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); + _currentClient.write(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + _currentClient.write_P(content, size); + if(_chunked){ + _currentClient.write(footer, 2); + } +} + + +String ESP32WebServer::arg(String name) { + for (int i = 0; i < _currentArgCount; ++i) { + if ( _currentArgs[i].key == name ) + return _currentArgs[i].value; + } + return String(); +} + +String ESP32WebServer::arg(int i) { + if (i < _currentArgCount) + return _currentArgs[i].value; + return String(); +} + +String ESP32WebServer::argName(int i) { + if (i < _currentArgCount) + return _currentArgs[i].key; + return String(); +} + +int ESP32WebServer::args() { + return _currentArgCount; +} + +bool ESP32WebServer::hasArg(String name) { + for (int i = 0; i < _currentArgCount; ++i) { + if (_currentArgs[i].key == name) + return true; + } + return false; +} + + +String ESP32WebServer::header(String name) { + for (int i = 0; i < _headerKeysCount; ++i) { + if (_currentHeaders[i].key.equalsIgnoreCase(name)) + return _currentHeaders[i].value; + } + return String(); +} + +void ESP32WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { + _headerKeysCount = headerKeysCount + 1; + if (_currentHeaders) + delete[]_currentHeaders; + _currentHeaders = new RequestArgument[_headerKeysCount]; + _currentHeaders[0].key = AUTHORIZATION_HEADER; + for (int i = 1; i < _headerKeysCount; i++){ + _currentHeaders[i].key = headerKeys[i-1]; + } +} + +String ESP32WebServer::header(int i) { + if (i < _headerKeysCount) + return _currentHeaders[i].value; + return String(); +} + +String ESP32WebServer::headerName(int i) { + if (i < _headerKeysCount) + return _currentHeaders[i].key; + return String(); +} + +int ESP32WebServer::headers() { + return _headerKeysCount; +} + +bool ESP32WebServer::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 ESP32WebServer::hostHeader() { + return _hostHeader; +} + +void ESP32WebServer::onFileUpload(THandlerFunction fn) { + _fileUploadHandler = fn; +} + +void ESP32WebServer::onNotFound(THandlerFunction fn) { + _notFoundHandler = fn; +} + +void ESP32WebServer::_handleRequest() { + bool handled = false; + if (!_currentHandler){ +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("request handler not found"); +#endif + } + else { + handled = _currentHandler->handle(*this, _currentMethod, _currentUri); +#ifdef DEBUG_ESP_HTTP_SERVER + if (!handled) { + DEBUG_OUTPUT.println("request handler failed to handle request"); + } +#endif + } + + if (!handled) { + if(_notFoundHandler) { + _notFoundHandler(); + } + else { + send(404, "text/plain", String("Not found: ") + _currentUri); + } + } + + _currentUri = String(); +} + +String ESP32WebServer::_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 ""; + } +} diff --git a/libraries/WebServer/src/ESP32WebServer.h b/libraries/WebServer/src/ESP32WebServer.h new file mode 100644 index 00000000000..75db5087ccd --- /dev/null +++ b/libraries/WebServer/src/ESP32WebServer.h @@ -0,0 +1,184 @@ +/* + ESP32WebServer.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 ESP32WEBSERVER_H +#define ESP32WEBSERVER_H + +#include +#include + +enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS }; +enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, + UPLOAD_FILE_ABORTED }; +enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; + +#define HTTP_DOWNLOAD_UNIT_SIZE 1460 +#define HTTP_UPLOAD_BUFLEN 2048 +#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request +#define HTTP_MAX_POST_WAIT 1000 //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 ESP32WebServer; + +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" + +namespace fs { +class FS; +} + +class ESP32WebServer +{ +public: + ESP32WebServer(IPAddress addr, int port = 80); + ESP32WebServer(int port = 80); + ~ESP32WebServer(); + + void begin(); + void handleClient(); + + void close(); + void stop(); + + bool authenticate(const char * username, const char * password); + void requestAuthentication(); + + typedef std::function THandlerFunction; + void on(const String &uri, THandlerFunction handler); + void on(const String &uri, HTTPMethod method, THandlerFunction fn); + void on(const String &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; } + WiFiClient client() { return _currentClient; } + HTTPUpload& upload() { return _currentUpload; } + + 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 setContentLength(size_t contentLength); + void sendHeader(const String& name, const String& value, bool first = false); + void sendContent(const String& content); + 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){ + setContentLength(file.size()); + if (String(file.name()).endsWith(".gz") && + contentType != "application/x-gzip" && + contentType != "application/octet-stream"){ + sendHeader("Content-Encoding", "gzip"); + } + send(200, contentType, ""); + return _currentClient.write(file); +} + +protected: + void _addRequestHandler(RequestHandler* handler); + void _handleRequest(); + bool _parseRequest(WiFiClient& client); + void _parseArguments(String data); + static String _responseCodeToString(int code); + bool _parseForm(WiFiClient& client, String boundary, uint32_t len); + bool _parseFormUploadAborted(); + void _uploadWriteByte(uint8_t b); + uint8_t _uploadReadByte(WiFiClient& client); + void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); + bool _collectHeader(const char* headerName, const char* headerValue); + + struct RequestArgument { + String key; + String value; + }; + + WiFiServer _server; + + WiFiClient _currentClient; + HTTPMethod _currentMethod; + String _currentUri; + uint8_t _currentVersion; + HTTPClientStatus _currentStatus; + unsigned long _statusChange; + + RequestHandler* _currentHandler; + RequestHandler* _firstHandler; + RequestHandler* _lastHandler; + THandlerFunction _notFoundHandler; + THandlerFunction _fileUploadHandler; + + int _currentArgCount; + RequestArgument* _currentArgs; + HTTPUpload _currentUpload; + + int _headerKeysCount; + RequestArgument* _currentHeaders; + size_t _contentLength; + String _responseHeaders; + + String _hostHeader; + bool _chunked; + +}; + + +#endif //ESP32WEBSERVER_H diff --git a/libraries/WebServer/src/Parsing.cpp b/libraries/WebServer/src/Parsing.cpp new file mode 100644 index 00000000000..45094944f08 --- /dev/null +++ b/libraries/WebServer/src/Parsing.cpp @@ -0,0 +1,607 @@ +/* + 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 "WiFiServer.h" +#include "WiFiClient.h" +#include "ESP32WebServer.h" + +//#define DEBUG_ESP_HTTP_SERVER +#ifdef DEBUG_ESP_PORT +#define DEBUG_OUTPUT DEBUG_ESP_PORT +#else +#define DEBUG_OUTPUT Serial +#endif + +static char* readBytesWithTimeout(WiFiClient& 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 ESP32WebServer::_parseRequest(WiFiClient& 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) { +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Invalid request: "); + DEBUG_OUTPUT.println(req); +#endif + 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 = urlDecode(url.substring(hasSearch + 1)); + url = url.substring(0, hasSearch); + } + _currentUri = url; + _chunked = false; + + HTTPMethod method = HTTP_GET; + if (methodStr == "POST") { + method = HTTP_POST; + } else if (methodStr == "DELETE") { + method = HTTP_DELETE; + } else if (methodStr == "OPTIONS") { + method = HTTP_OPTIONS; + } else if (methodStr == "PUT") { + method = HTTP_PUT; + } else if (methodStr == "PATCH") { + method = HTTP_PATCH; + } + _currentMethod = method; + +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("method: "); + DEBUG_OUTPUT.print(methodStr); + DEBUG_OUTPUT.print(" url: "); + DEBUG_OUTPUT.print(url); + DEBUG_OUTPUT.print(" search: "); + DEBUG_OUTPUT.println(searchStr); +#endif + + //attach handler + RequestHandler* handler; + for (handler = _firstHandler; handler; handler = handler->next()) { + 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()); + + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("headerName: "); + DEBUG_OUTPUT.println(headerName); + DEBUG_OUTPUT.print("headerValue: "); + DEBUG_OUTPUT.println(headerValue); + #endif + + if (headerName.equalsIgnoreCase("Content-Type")){ + if (headerValue.startsWith("text/plain")){ + isForm = false; + } else if (headerValue.startsWith("application/x-www-form-urlencoded")){ + isForm = false; + isEncoded = true; + } else if (headerValue.startsWith("multipart/")){ + boundaryStr = headerValue.substring(headerValue.indexOf('=')+1); + isForm = true; + } + } else if (headerName.equalsIgnoreCase("Content-Length")){ + contentLength = headerValue.toInt(); + } else if (headerName.equalsIgnoreCase("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 (searchStr != "") searchStr += '&'; + if(isEncoded){ + //url encoded form + String decoded = urlDecode(plainBuf); + size_t decodedLen = decoded.length(); + memcpy(plainBuf, decoded.c_str(), decodedLen); + plainBuf[decodedLen] = 0; + searchStr += plainBuf; + } + _parseArguments(searchStr); + if(!isEncoded){ + //plain post json or other data + RequestArgument& arg = _currentArgs[_currentArgCount++]; + arg.key = "plain"; + arg.value = String(plainBuf); + } + + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Plain: "); + DEBUG_OUTPUT.println(plainBuf); + #endif + free(plainBuf); + } + } + + 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()); + + #ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("headerName: "); + DEBUG_OUTPUT.println(headerName); + DEBUG_OUTPUT.print("headerValue: "); + DEBUG_OUTPUT.println(headerValue); + #endif + + if (headerName.equalsIgnoreCase("Host")){ + _hostHeader = headerValue; + } + } + _parseArguments(searchStr); + } + client.flush(); + +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Request: "); + DEBUG_OUTPUT.println(url); + DEBUG_OUTPUT.print(" Arguments: "); + DEBUG_OUTPUT.println(searchStr); +#endif + + return true; +} + +bool ESP32WebServer::_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 ESP32WebServer::_parseArguments(String data) { +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("args: "); + DEBUG_OUTPUT.println(data); +#endif + 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; + } +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("args count: "); + DEBUG_OUTPUT.println(_currentArgCount); +#endif + + _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); +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("pos "); + DEBUG_OUTPUT.print(pos); + DEBUG_OUTPUT.print("=@ "); + DEBUG_OUTPUT.print(equal_sign_index); + DEBUG_OUTPUT.print(" &@ "); + DEBUG_OUTPUT.println(next_arg_index); +#endif + if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("arg missing value: "); + DEBUG_OUTPUT.println(iarg); +#endif + if (next_arg_index == -1) + break; + pos = next_arg_index + 1; + continue; + } + RequestArgument& arg = _currentArgs[iarg]; + arg.key = data.substring(pos, equal_sign_index); + arg.value = data.substring(equal_sign_index + 1, next_arg_index); +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("arg "); + DEBUG_OUTPUT.print(iarg); + DEBUG_OUTPUT.print(" key: "); + DEBUG_OUTPUT.print(arg.key); + DEBUG_OUTPUT.print(" value: "); + DEBUG_OUTPUT.println(arg.value); +#endif + ++iarg; + if (next_arg_index == -1) + break; + pos = next_arg_index + 1; + } + _currentArgCount = iarg; +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("args count: "); + DEBUG_OUTPUT.println(_currentArgCount); +#endif + +} + +void ESP32WebServer::_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; +} + +uint8_t ESP32WebServer::_uploadReadByte(WiFiClient& client){ + int res = client.read(); + if(res == -1){ + while(!client.available() && client.connected()) + yield(); + res = client.read(); + } + return (uint8_t)res; +} + +bool ESP32WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ + (void) len; +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Parse Form: Boundary: "); + DEBUG_OUTPUT.print(boundary); + DEBUG_OUTPUT.print(" Length: "); + DEBUG_OUTPUT.println(len); +#endif + 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)){ + RequestArgument* postArgs = new RequestArgument[32]; + int 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("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; +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("PostArg FileName: "); + DEBUG_OUTPUT.println(argFilename); +#endif + //use GET to set the filename if uploading using blob + if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename"); + } +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("PostArg Name: "); + DEBUG_OUTPUT.println(argName); +#endif + argType = "text/plain"; + line = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){ + argType = line.substring(line.indexOf(':')+2); + //skip next line + client.readStringUntil('\r'); + client.readStringUntil('\n'); + } +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("PostArg Type: "); + DEBUG_OUTPUT.println(argType); +#endif + if (!argIsFile){ + while(1){ + line = client.readStringUntil('\r'); + client.readStringUntil('\n'); + if (line.startsWith("--"+boundary)) break; + if (argValue.length() > 0) argValue += "\n"; + argValue += line; + } +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("PostArg Value: "); + DEBUG_OUTPUT.println(argValue); + DEBUG_OUTPUT.println(); +#endif + + RequestArgument& arg = postArgs[postArgsLen++]; + arg.key = argName; + arg.value = argValue; + + if (line == ("--"+boundary+"--")){ +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Done Parsing POST"); +#endif + break; + } + } else { + _currentUpload.status = UPLOAD_FILE_START; + _currentUpload.name = argName; + _currentUpload.filename = argFilename; + _currentUpload.type = argType; + _currentUpload.totalSize = 0; + _currentUpload.currentSize = 0; +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Start File: "); + DEBUG_OUTPUT.print(_currentUpload.filename); + DEBUG_OUTPUT.print(" Type: "); + DEBUG_OUTPUT.println(_currentUpload.type); +#endif + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, _currentUpload); + _currentUpload.status = UPLOAD_FILE_WRITE; + uint8_t argByte = _uploadReadByte(client); +readfile: + while(argByte != 0x0D){ + if (!client.connected()) return _parseFormUploadAborted(); + _uploadWriteByte(argByte); + argByte = _uploadReadByte(client); + } + + argByte = _uploadReadByte(client); + if (!client.connected()) return _parseFormUploadAborted(); + if (argByte == 0x0A){ + argByte = _uploadReadByte(client); + if (!client.connected()) return _parseFormUploadAborted(); + if ((char)argByte != '-'){ + //continue reading the file + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + goto readfile; + } else { + argByte = _uploadReadByte(client); + if (!client.connected()) return _parseFormUploadAborted(); + if ((char)argByte != '-'){ + //continue reading the file + _uploadWriteByte(0x0D); + _uploadWriteByte(0x0A); + _uploadWriteByte((uint8_t)('-')); + goto readfile; + } + } + + uint8_t endBuf[boundary.length()]; + client.readBytes(endBuf, boundary.length()); + + 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); +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("End File: "); + DEBUG_OUTPUT.print(_currentUpload.filename); + DEBUG_OUTPUT.print(" Type: "); + DEBUG_OUTPUT.print(_currentUpload.type); + DEBUG_OUTPUT.print(" Size: "); + DEBUG_OUTPUT.println(_currentUpload.totalSize); +#endif + line = client.readStringUntil(0x0D); + client.readStringUntil(0x0A); + if (line == "--"){ +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.println("Done Parsing POST"); +#endif + 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 = ((32 - postArgsLen) < _currentArgCount)?(32 - 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; + return true; + } +#ifdef DEBUG_ESP_HTTP_SERVER + DEBUG_OUTPUT.print("Error: line: "); + DEBUG_OUTPUT.println(line); +#endif + return false; +} + +String ESP32WebServer::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 ESP32WebServer::_parseFormUploadAborted(){ + _currentUpload.status = UPLOAD_FILE_ABORTED; + if(_currentHandler && _currentHandler->canUpload(_currentUri)) + _currentHandler->upload(*this, _currentUri, _currentUpload); + return false; +} diff --git a/libraries/WebServer/src/detail/RequestHandler.h b/libraries/WebServer/src/detail/RequestHandler.h new file mode 100644 index 00000000000..2c216f39142 --- /dev/null +++ b/libraries/WebServer/src/detail/RequestHandler.h @@ -0,0 +1,19 @@ +#ifndef REQUESTHANDLER_H +#define REQUESTHANDLER_H + +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(ESP32WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } + virtual void upload(ESP32WebServer& 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; +}; + +#endif //REQUESTHANDLER_H diff --git a/libraries/WebServer/src/detail/RequestHandlersImpl.h b/libraries/WebServer/src/detail/RequestHandlersImpl.h new file mode 100644 index 00000000000..c065d7e95e5 --- /dev/null +++ b/libraries/WebServer/src/detail/RequestHandlersImpl.h @@ -0,0 +1,153 @@ +#ifndef REQUESTHANDLERSIMPL_H +#define REQUESTHANDLERSIMPL_H + +#include "RequestHandler.h" + +class FunctionRequestHandler : public RequestHandler { +public: + FunctionRequestHandler(ESP32WebServer::THandlerFunction fn, ESP32WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method) + : _fn(fn) + , _ufn(ufn) + , _uri(uri) + , _method(method) + { + } + + bool canHandle(HTTPMethod requestMethod, String requestUri) override { + if (_method != HTTP_ANY && _method != requestMethod) + return false; + + if (requestUri != _uri) + return false; + + return true; + } + + bool canUpload(String requestUri) override { + if (!_ufn || !canHandle(HTTP_POST, requestUri)) + return false; + + return true; + } + + bool handle(ESP32WebServer& server, HTTPMethod requestMethod, String requestUri) override { + (void) server; + if (!canHandle(requestMethod, requestUri)) + return false; + + _fn(); + return true; + } + + void upload(ESP32WebServer& server, String requestUri, HTTPUpload& upload) override { + (void) server; + (void) upload; + if (canUpload(requestUri)) + _ufn(); + } + +protected: + ESP32WebServer::THandlerFunction _fn; + ESP32WebServer::THandlerFunction _ufn; + String _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); + //DEBUGV("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(ESP32WebServer& server, HTTPMethod requestMethod, String requestUri) override { + if (!canHandle(requestMethod, requestUri)) + return false; + + //DEBUGV("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); + } + //DEBUGV("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(".gz") && !_fs.exists(path)) { + String pathWithGz = path + ".gz"; + if(_fs.exists(pathWithGz)) + path += ".gz"; + } + + File f = _fs.open(path, "r"); + if (!f) + 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) { + if (path.endsWith(".html")) return "text/html"; + else if (path.endsWith(".htm")) return "text/html"; + else if (path.endsWith(".css")) return "text/css"; + else if (path.endsWith(".txt")) return "text/plain"; + else if (path.endsWith(".js")) return "application/javascript"; + else if (path.endsWith(".png")) return "image/png"; + else if (path.endsWith(".gif")) return "image/gif"; + else if (path.endsWith(".jpg")) return "image/jpeg"; + else if (path.endsWith(".ico")) return "image/x-icon"; + else if (path.endsWith(".svg")) return "image/svg+xml"; + else if (path.endsWith(".ttf")) return "application/x-font-ttf"; + else if (path.endsWith(".otf")) return "application/x-font-opentype"; + else if (path.endsWith(".woff")) return "application/font-woff"; + else if (path.endsWith(".woff2")) return "application/font-woff2"; + else if (path.endsWith(".eot")) return "application/vnd.ms-fontobject"; + else if (path.endsWith(".sfnt")) return "application/font-sfnt"; + else if (path.endsWith(".xml")) return "text/xml"; + else if (path.endsWith(".pdf")) return "application/pdf"; + else if (path.endsWith(".zip")) return "application/zip"; + else if(path.endsWith(".gz")) return "application/x-gzip"; + else if (path.endsWith(".appcache")) return "text/cache-manifest"; + return "application/octet-stream"; + } + +protected: + FS _fs; + String _uri; + String _path; + String _cache_header; + bool _isFile; + size_t _baseUriLength; +}; + + +#endif //REQUESTHANDLERSIMPL_H diff --git a/tools/sdk/lib/libtcpip_adapter.a b/tools/sdk/lib/libtcpip_adapter.a index 0576944c106..d8b67a5b94f 100644 Binary files a/tools/sdk/lib/libtcpip_adapter.a and b/tools/sdk/lib/libtcpip_adapter.a differ