From 119d2db30b5d9cfab3946d3435473242859ef9fb Mon Sep 17 00:00:00 2001 From: karan6190 Date: Thu, 28 Jun 2018 10:33:15 +0530 Subject: [PATCH 01/22] added OTAWebUpdater --- .../examples/OTAWebUpdater/OTAWebUpdater.ino | 160 +++++ libraries/ArduinoOTA/src/ESP32WebServer.cpp | 537 ++++++++++++++++ libraries/ArduinoOTA/src/ESP32WebServer.h | 192 ++++++ libraries/ArduinoOTA/src/Parsing.cpp | 607 ++++++++++++++++++ 4 files changed, 1496 insertions(+) create mode 100644 libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino create mode 100644 libraries/ArduinoOTA/src/ESP32WebServer.cpp create mode 100644 libraries/ArduinoOTA/src/ESP32WebServer.h create mode 100644 libraries/ArduinoOTA/src/Parsing.cpp diff --git a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino new file mode 100644 index 00000000000..5358bfc33ce --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include + +const char* host = "ESP32"; +const char* ssid = "xxx"; +const char* password = "xxxx"; + +ESP32WebServer server(80); + +const char* loginIndex = "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
" + "" + "" + "" + "
" + "
" + "" + + "" + "" + "" + "
ESP32 Login Page
" + "
" + + "
Username:
Password:
" + "
" + ""; + + + + + +const char* serverIndex = "" + "
" + "" + "" + "
" + "
progress: 0%
" + ""; +void setup(void) { + Serial.begin(115200); + + // Connect to WiFi network + 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()); + + /*use mdns for host name resolution*/ + if (!MDNS.begin(host)) { //http://esp32.local + Serial.println("Error setting up MDNS responder!"); + while (1) { + delay(1000); + } + } + Serial.println("mDNS responder started"); + /*return index page which is stored in serverIndex */ + server.on("/", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", loginIndex); + }); + server.on("/serverIndex", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", serverIndex); + }); + /*handling uploading firmware file */ + 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.printf("Update: %s\n", upload.filename.c_str()); + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + /* flashing firmware to ESP*/ + 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); + } + } + }); + server.begin(); +} + +void loop(void) { + server.handleClient(); + delay(1); +} diff --git a/libraries/ArduinoOTA/src/ESP32WebServer.cpp b/libraries/ArduinoOTA/src/ESP32WebServer.cpp new file mode 100644 index 00000000000..81bd5cac968 --- /dev/null +++ b/libraries/ArduinoOTA/src/ESP32WebServer.cpp @@ -0,0 +1,537 @@ +/* + 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 + log_e("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.end(); +} + +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); + } + } + size_t chunkSize = 20; + int idx = 0; + while(size != 0){ + if(size < chunkSize){ + _currentClient.write(&content[idx], size); + size = 0; + } else { + _currentClient.write(&content[idx], chunkSize); + size -= chunkSize; + idx += chunkSize; + } + yield(); + } + 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/ArduinoOTA/src/ESP32WebServer.h b/libraries/ArduinoOTA/src/ESP32WebServer.h new file mode 100644 index 00000000000..2401a35eafc --- /dev/null +++ b/libraries/ArduinoOTA/src/ESP32WebServer.h @@ -0,0 +1,192 @@ +/* + 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, ""); + uint8_t buf[20]; + size_t fsize = 0; + while(file.available()){ + int got = file.read(buf, 20); + _currentClient.write(buf, got); + fsize += got; + yield(); + } + return fsize; +} + +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/ArduinoOTA/src/Parsing.cpp b/libraries/ArduinoOTA/src/Parsing.cpp new file mode 100644 index 00000000000..45094944f08 --- /dev/null +++ b/libraries/ArduinoOTA/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; +} From 08d90286b6fcdad3458f36b7f414474ac2896965 Mon Sep 17 00:00:00 2001 From: karan6190 Date: Thu, 28 Jun 2018 10:53:35 +0530 Subject: [PATCH 02/22] added OTAWebUpdater --- libraries/ArduinoOTA/src/ESP32WebServer.cpp | 537 ----------------- libraries/ArduinoOTA/src/ESP32WebServer.h | 192 ------- libraries/ArduinoOTA/src/Parsing.cpp | 607 -------------------- 3 files changed, 1336 deletions(-) delete mode 100644 libraries/ArduinoOTA/src/ESP32WebServer.cpp delete mode 100644 libraries/ArduinoOTA/src/ESP32WebServer.h delete mode 100644 libraries/ArduinoOTA/src/Parsing.cpp diff --git a/libraries/ArduinoOTA/src/ESP32WebServer.cpp b/libraries/ArduinoOTA/src/ESP32WebServer.cpp deleted file mode 100644 index 81bd5cac968..00000000000 --- a/libraries/ArduinoOTA/src/ESP32WebServer.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* - 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 - log_e("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.end(); -} - -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); - } - } - size_t chunkSize = 20; - int idx = 0; - while(size != 0){ - if(size < chunkSize){ - _currentClient.write(&content[idx], size); - size = 0; - } else { - _currentClient.write(&content[idx], chunkSize); - size -= chunkSize; - idx += chunkSize; - } - yield(); - } - 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/ArduinoOTA/src/ESP32WebServer.h b/libraries/ArduinoOTA/src/ESP32WebServer.h deleted file mode 100644 index 2401a35eafc..00000000000 --- a/libraries/ArduinoOTA/src/ESP32WebServer.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - 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, ""); - uint8_t buf[20]; - size_t fsize = 0; - while(file.available()){ - int got = file.read(buf, 20); - _currentClient.write(buf, got); - fsize += got; - yield(); - } - return fsize; -} - -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/ArduinoOTA/src/Parsing.cpp b/libraries/ArduinoOTA/src/Parsing.cpp deleted file mode 100644 index 45094944f08..00000000000 --- a/libraries/ArduinoOTA/src/Parsing.cpp +++ /dev/null @@ -1,607 +0,0 @@ -/* - 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; -} From 18b8ee928b8e67bb84ee233bf489eba6c54207e3 Mon Sep 17 00:00:00 2001 From: karan6190 Date: Thu, 28 Jun 2018 20:19:46 +0530 Subject: [PATCH 03/22] Updated OTAWebUpdater --- libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino index 5358bfc33ce..aa8aaf03f4b 100644 --- a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino +++ b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include @@ -8,7 +8,7 @@ const char* host = "ESP32"; const char* ssid = "xxx"; const char* password = "xxxx"; -ESP32WebServer server(80); +WebServer server(80); const char* loginIndex = "
" "" From ee206e38c190e36918950e3189775bdb3b53840f Mon Sep 17 00:00:00 2001 From: "PBI\\NEX3DJO" Date: Tue, 3 Jul 2018 11:22:01 +0530 Subject: [PATCH 04/22] OTAWebUpdate Doc --- docs/OTAWebUpdate/OTAWebUpdate.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/OTAWebUpdate/OTAWebUpdate.md diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md new file mode 100644 index 00000000000..e69de29bb2d From 7971a88d7ad83f6ea4177030594b0fa544ce820a Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 11:53:07 +0530 Subject: [PATCH 05/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index e69de29bb2d..a4c45d4469d 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -0,0 +1,20 @@ +# Over the Air through Web browser +OTAWebUpdate is done with a web browser that can be useful in the following typical scenarios: +- Once the application developed and loading directly from Arduino IDE is inconvenient or not possible +- after deployment if user is unable to expose Firmware for OTA from external update server +- provide updates after deployment to small quantity of modules when setting an update server is not practicable + +## Requirements +- The ESP and the computer must be connected to the same network + +## Implementation +The sample implementation has been done using: +- example sketch OTAWebUpdate.ino +- NodeMCU 1.0 (ESP-12E Module) +You can use another module also if it meets Flash chip size of the sketch +1-Before you begin, please make sure that you have the following software installed: + - Arduino IDE + - Host software depending on O/S you use: + 1- Avahi http://avahi.org/ for Linux + 2- Bonjour http://www.apple.com/support/bonjour/ for Windows + 3- Mac OSX and iOS - support is already built in / no any extra s/w is required From c26f924771433af8a0810c2c4e9f8f26e0a2c550 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 11:54:12 +0530 Subject: [PATCH 06/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index a4c45d4469d..cd32bce08dc 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -15,6 +15,6 @@ You can use another module also if it meets Flash chip size of the sketch 1-Before you begin, please make sure that you have the following software installed: - Arduino IDE - Host software depending on O/S you use: - 1- Avahi http://avahi.org/ for Linux - 2- Bonjour http://www.apple.com/support/bonjour/ for Windows - 3- Mac OSX and iOS - support is already built in / no any extra s/w is required + 1- Avahi http://avahi.org/ for Linux + 2- Bonjour http://www.apple.com/support/bonjour/ for Windows + 3- Mac OSX and iOS - support is already built in / no any extra s/w is required From 2ea630305d4823d8ab36e83b4f87946a507e5ed0 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 12:30:54 +0530 Subject: [PATCH 07/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index cd32bce08dc..95637ecdf0c 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -9,12 +9,17 @@ OTAWebUpdate is done with a web browser that can be useful in the following typi ## Implementation The sample implementation has been done using: -- example sketch OTAWebUpdate.ino +- example sketch OTAWebUpdater.ino - NodeMCU 1.0 (ESP-12E Module) You can use another module also if it meets Flash chip size of the sketch 1-Before you begin, please make sure that you have the following software installed: - Arduino IDE - Host software depending on O/S you use: - 1- Avahi http://avahi.org/ for Linux - 2- Bonjour http://www.apple.com/support/bonjour/ for Windows - 3- Mac OSX and iOS - support is already built in / no any extra s/w is required + - Avahi http://avahi.org/ for Linux + - Bonjour http://www.apple.com/support/bonjour/ for Windows + - Mac OSX and iOS - support is already built in / no any extra s/w is required + +Prepare the sketch and configuration for initial upload with a serial port +- Start Arduino IDE and load sketch OTAWebUpdater.ino available under File > Examples > OTAWebUpdater.ino +- Update ssid and pass in the sketch so the module can join your Wi-Fi network +- Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option From 6472d04c4c744a42fe61f4549136c0d644714806 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:44:23 +0530 Subject: [PATCH 08/22] Added Images for OTAWebUpdater docs --- docs/OTAWebUpdate/esp32login.PNG | Bin 0 -> 12468 bytes docs/OTAWebUpdate/esp32upload.PNG | Bin 0 -> 9044 bytes docs/OTAWebUpdate/esp32verbose.PNG | Bin 0 -> 13872 bytes docs/OTAWebUpdate/exportTobinary.PNG | Bin 0 -> 19432 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/OTAWebUpdate/esp32login.PNG create mode 100644 docs/OTAWebUpdate/esp32upload.PNG create mode 100644 docs/OTAWebUpdate/esp32verbose.PNG create mode 100644 docs/OTAWebUpdate/exportTobinary.PNG diff --git a/docs/OTAWebUpdate/esp32login.PNG b/docs/OTAWebUpdate/esp32login.PNG new file mode 100644 index 0000000000000000000000000000000000000000..48c90c7f452b3f87c22b6936cd2f02d2c05ab39d GIT binary patch literal 12468 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 Gd;bHcSjfNt literal 0 HcmV?d00001 diff --git a/docs/OTAWebUpdate/esp32upload.PNG b/docs/OTAWebUpdate/esp32upload.PNG new file mode 100644 index 0000000000000000000000000000000000000000..81f2eaba79d5124bb08adb2cf8eb73d302dafc9d GIT binary patch literal 9044 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 WO0?fp$NUL{rS()_twQB_*na^GXWYpE literal 0 HcmV?d00001 diff --git a/docs/OTAWebUpdate/esp32verbose.PNG b/docs/OTAWebUpdate/esp32verbose.PNG new file mode 100644 index 0000000000000000000000000000000000000000..bd9c3fc41fed1dfd93629bb83faaf3e70665deec GIT binary patch literal 13872 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 literal 0 HcmV?d00001 From fe223a894383da81c763de940a9b146c7c92a2ee Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:44:56 +0530 Subject: [PATCH 09/22] Udapted OTAWebupdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 95637ecdf0c..95255731d09 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -23,3 +23,7 @@ Prepare the sketch and configuration for initial upload with a serial port - Start Arduino IDE and load sketch OTAWebUpdater.ino available under File > Examples > OTAWebUpdater.ino - Update ssid and pass in the sketch so the module can join your Wi-Fi network - Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option +[! verbrose ] (docs/OTAWebUpdate/esp32verbose.png) + +- Upload sketch (Ctrl+U) + From 63490d69cb2ba6126ca936358ffc9add495fb161 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:46:31 +0530 Subject: [PATCH 10/22] Updated OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 95255731d09..a549f29f8d2 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -23,7 +23,8 @@ Prepare the sketch and configuration for initial upload with a serial port - Start Arduino IDE and load sketch OTAWebUpdater.ino available under File > Examples > OTAWebUpdater.ino - Update ssid and pass in the sketch so the module can join your Wi-Fi network - Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option -[! verbrose ] (docs/OTAWebUpdate/esp32verbose.png) + +[! verbrose] (docs/OTAWebUpdate/esp32verbose.png) - Upload sketch (Ctrl+U) From 21431767e171a562b1d1abe68179c6256c92312f Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:48:46 +0530 Subject: [PATCH 11/22] Updated OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index a549f29f8d2..e067b64375a 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -24,7 +24,7 @@ Prepare the sketch and configuration for initial upload with a serial port - Update ssid and pass in the sketch so the module can join your Wi-Fi network - Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option -[! verbrose] (docs/OTAWebUpdate/esp32verbose.png) +![verbrose](docs/OTAWebUpdate/esp32verbose.png) - Upload sketch (Ctrl+U) From 4102fb1913b75c85d9030cec54ce863aa5ed4ea2 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:49:46 +0530 Subject: [PATCH 12/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index e067b64375a..c8ab255f83c 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -24,7 +24,7 @@ Prepare the sketch and configuration for initial upload with a serial port - Update ssid and pass in the sketch so the module can join your Wi-Fi network - Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option -![verbrose](docs/OTAWebUpdate/esp32verbose.png) +![verbrose](docs/OTAWebUpdate/esp32verbose.PNG) - Upload sketch (Ctrl+U) From c08cf6eed69aa998193fcf010249f39fa5ae4612 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 14:50:44 +0530 Subject: [PATCH 13/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index c8ab255f83c..6fb75d6351e 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -24,7 +24,7 @@ Prepare the sketch and configuration for initial upload with a serial port - Update ssid and pass in the sketch so the module can join your Wi-Fi network - Open File > Preferences, look for “Show verbose output during:” and check out “compilation” option -![verbrose](docs/OTAWebUpdate/esp32verbose.PNG) +![verbrose](esp32verbose.PNG) - Upload sketch (Ctrl+U) From 8c1e419ca099f43b919d0bfb71714586dce7501d Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 15:23:58 +0530 Subject: [PATCH 14/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 6fb75d6351e..de4651b9613 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -27,4 +27,28 @@ Prepare the sketch and configuration for initial upload with a serial port ![verbrose](esp32verbose.PNG) - Upload sketch (Ctrl+U) +- Now open web browser and enter the url provided on Serial Monitor, i.e. http://ESP32.local. Once entered, browser should display a form + +![login](esp32login.PNG) + +> username= admin +> password= admin + +**Note**-*If entering “http://ESP32.local” does not work, try replacing “ESP32” with module’s IP address.This workaround is useful in case the host software installed does not work*. + +Now click on Login button and browser will display a upload form + +![upload](esp32upload.PNG) +For Uploading the New Firmware you need to provide the Binary File of your Code. + +Exporting Binary file of the Firmware (Code) +- Open up the Arduino IDE +- Open up the Code, for Exporting up Binary file +- Now go to Sketch > export compiled Binary +![export](exportTobinary.PNG) +- Binary file is exported to the same Directory where your code is present + +Once you are comfortable with this procedure go ahead and modify OTAWebUpdater.ino sketch to print some additional messages, compile it, Export new binary file and upload it using web browser to see entered changes on a Serial Monitor + + From bc7c5627ab24dcf272a8418aa01ce3af281dddb7 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 15:24:49 +0530 Subject: [PATCH 15/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index de4651b9613..1be7fb3cc18 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -32,6 +32,7 @@ Prepare the sketch and configuration for initial upload with a serial port ![login](esp32login.PNG) > username= admin + > password= admin **Note**-*If entering “http://ESP32.local” does not work, try replacing “ESP32” with module’s IP address.This workaround is useful in case the host software installed does not work*. @@ -39,6 +40,7 @@ Prepare the sketch and configuration for initial upload with a serial port Now click on Login button and browser will display a upload form ![upload](esp32upload.PNG) + For Uploading the New Firmware you need to provide the Binary File of your Code. Exporting Binary file of the Firmware (Code) @@ -46,6 +48,7 @@ Exporting Binary file of the Firmware (Code) - Open up the Code, for Exporting up Binary file - Now go to Sketch > export compiled Binary ![export](exportTobinary.PNG) + - Binary file is exported to the same Directory where your code is present Once you are comfortable with this procedure go ahead and modify OTAWebUpdater.ino sketch to print some additional messages, compile it, Export new binary file and upload it using web browser to see entered changes on a Serial Monitor From 7c63528d3e4e669aa85f08eeec7631f8e03538ff Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 15:26:20 +0530 Subject: [PATCH 16/22] Updated OTAWebUpdater.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 1be7fb3cc18..52129e7e5f3 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -27,7 +27,7 @@ Prepare the sketch and configuration for initial upload with a serial port ![verbrose](esp32verbose.PNG) - Upload sketch (Ctrl+U) -- Now open web browser and enter the url provided on Serial Monitor, i.e. http://ESP32.local. Once entered, browser should display a form +- Now open web browser and enter the url, i.e. http://ESP32.local. Once entered, browser should display a form ![login](esp32login.PNG) From ed8b970ef894c3abbfbdd352501c7009bd3e7311 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 15:32:59 +0530 Subject: [PATCH 17/22] Updated Readme.md with OTAWebUpdater Doc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4c0c046e5a8..4b3c14a4bff 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Most of the framework is implemented. Most noticable is the missing analogWrite. - [Using PlatformIO](docs/platformio.md) - [Building with make](docs/make.md) - [Using as ESP-IDF component](docs/esp-idf_component.md) +- [Using OTAWebUpdater](docs/OTAWebUpdate/OTAWebUpdate.md) #### Decoding exceptions From 4cd70fa23c045de927c08db84b5136e95348f16c Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 18:47:53 +0530 Subject: [PATCH 18/22] Updated Module Required --- docs/OTAWebUpdate/OTAWebUpdate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 52129e7e5f3..dc4181dc355 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -10,7 +10,7 @@ OTAWebUpdate is done with a web browser that can be useful in the following typi ## Implementation The sample implementation has been done using: - example sketch OTAWebUpdater.ino -- NodeMCU 1.0 (ESP-12E Module) +- ESP32 (Dev Module) You can use another module also if it meets Flash chip size of the sketch 1-Before you begin, please make sure that you have the following software installed: - Arduino IDE @@ -27,7 +27,7 @@ Prepare the sketch and configuration for initial upload with a serial port ![verbrose](esp32verbose.PNG) - Upload sketch (Ctrl+U) -- Now open web browser and enter the url, i.e. http://ESP32.local. Once entered, browser should display a form +- Now open web browser and enter the url, i.e. http://esp32.local. Once entered, browser should display a form ![login](esp32login.PNG) From 7913214e827a21524ab52d299ade9c5bda66154b Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 18:49:13 +0530 Subject: [PATCH 19/22] Updated OTAWebUpdater --- .../examples/OTAWebUpdater/OTAWebUpdater.ino | 169 +++++++++--------- 1 file changed, 88 insertions(+), 81 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino index aa8aaf03f4b..0b4710521cf 100644 --- a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino +++ b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino @@ -4,93 +4,100 @@ #include #include -const char* host = "ESP32"; +const char* host = "esp32"; const char* ssid = "xxx"; const char* password = "xxxx"; WebServer server(80); -const char* loginIndex = "" - "
" - "" - "" - "
" - "
" - "
" - "" - "" - "" - "
" - "
" - "
" - "" - "" - "
" - "
" - "
" +const char* loginIndex = + "" + "
ESP32 Login Page
" - "
" +/* + * Login page + */ - "
Username:
Password:
" + "" + "" + "
" + "
" + "
" + "" + "" + "" + "
" + "
" + "
" + "" + "" + "
" + "
" + "
" + "" + "" + "" + "
ESP32 Login Page
" + "
" + "
Username:
Password:
" + "" + ""; + +/* + * Server Index Page + */ + +const char* serverIndex = +"" +"
" +"" +"" +"
" +"
progress: 0%
" +""; - "" - "" - "" - "" - "" - ""; - - - - - -const char* serverIndex = "" - "
" - "" - "" - "
" - "
progress: 0%
" - ""; +/* + * setup function + */ void setup(void) { Serial.begin(115200); @@ -110,7 +117,7 @@ void setup(void) { Serial.println(WiFi.localIP()); /*use mdns for host name resolution*/ - if (!MDNS.begin(host)) { //http://esp32.local + if (!MDNS.begin(host)) { //http://esp32.local Serial.println("Error setting up MDNS responder!"); while (1) { delay(1000); From eb325820cdd9623b2b080128adb0859bb76ed8aa Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 18:54:01 +0530 Subject: [PATCH 20/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index dc4181dc355..098856caf85 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -11,6 +11,7 @@ OTAWebUpdate is done with a web browser that can be useful in the following typi The sample implementation has been done using: - example sketch OTAWebUpdater.ino - ESP32 (Dev Module) + You can use another module also if it meets Flash chip size of the sketch 1-Before you begin, please make sure that you have the following software installed: - Arduino IDE From 004beca9901f9ec5eb4ac870be779256ef3e740c Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 18:54:37 +0530 Subject: [PATCH 21/22] Update OTAWebUpdate.md --- docs/OTAWebUpdate/OTAWebUpdate.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/OTAWebUpdate/OTAWebUpdate.md b/docs/OTAWebUpdate/OTAWebUpdate.md index 098856caf85..383f6214433 100644 --- a/docs/OTAWebUpdate/OTAWebUpdate.md +++ b/docs/OTAWebUpdate/OTAWebUpdate.md @@ -13,6 +13,7 @@ The sample implementation has been done using: - ESP32 (Dev Module) You can use another module also if it meets Flash chip size of the sketch + 1-Before you begin, please make sure that you have the following software installed: - Arduino IDE - Host software depending on O/S you use: From 72bb2eae63652eabb1a2e6e9957dd26e3ee80e12 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 3 Jul 2018 23:12:50 +0530 Subject: [PATCH 22/22] Updated OTAWebUpdater --- .../examples/OTAWebUpdater/OTAWebUpdater.ino | 142 +++++++++--------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino index 57d8d094a79..ed93c59f975 100644 --- a/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino +++ b/libraries/ArduinoOTA/examples/OTAWebUpdater/OTAWebUpdater.ino @@ -16,43 +16,44 @@ WebServer server(80); const char* loginIndex = "
" - "" - "" - "" - "
" - "
" - "" - "" - "" - "" - "
" - "
" - "" - "" - "" - "
" - "
" - "" - "" - "" - "" - "
ESP32 Login Page
" - "
" - "
Username:
Password:
" - "
" - ""; + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
ESP32 Login Page
" + "
" + "
Username:
Password:
" +"" +""; /* * Server Index Page @@ -61,44 +62,43 @@ const char* loginIndex = const char* serverIndex = "" "
" -"" -"" -"
" -"
progress: 0%
" -""; + "" + "" + "" + "
progress: 0%
" + ""; /* * setup function */ - void setup(void) { Serial.begin(115200);