#include "meshwifi/meshhttp.h" #include "NodeDB.h" #include "configuration.h" #include "main.h" #include "meshhttpStatic.h" #include "meshwifi/meshwifi.h" #include "sleep.h" #include #include #include #include #include #include // Persistant Data Storage #include Preferences prefs; /* Including the esp32_https_server library will trigger a compile time error. I've tracked it down to a reoccurrance of this bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824 The work around is described here: https://forums.xilinx.com/t5/Embedded-Development-Tools/Error-with-Standard-Libaries-in-Zynq/td-p/450032 Long story short is we need "#undef str" before including the esp32_https_server. - Jm Casler (jm@casler.org) Oct 2020 */ #undef str // Includes for the https server // https://github.com/fhessel/esp32_https_server #include #include #include #include #include // The HTTPS Server comes in a separate namespace. For easier use, include it here. using namespace httpsserver; SSLCert *cert; HTTPSServer *secureServer; HTTPServer *insecureServer; // Our API to handle messages to and from the radio. HttpAPI webAPI; // Declare some handler functions for the various URLs on the server void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res); void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res); void handleStyleCSS(HTTPRequest *req, HTTPResponse *res); void handleHotspot(HTTPRequest *req, HTTPResponse *res); void handleFavicon(HTTPRequest *req, HTTPResponse *res); void handleRoot(HTTPRequest *req, HTTPResponse *res); void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res); void handleStaticPost(HTTPRequest *req, HTTPResponse *res); void handleStatic(HTTPRequest *req, HTTPResponse *res); void handleRestart(HTTPRequest *req, HTTPResponse *res); void handle404(HTTPRequest *req, HTTPResponse *res); void handleFormUpload(HTTPRequest *req, HTTPResponse *res); void handleScanNetworks(HTTPRequest *req, HTTPResponse *res); void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res); void handleBlinkLED(HTTPRequest *req, HTTPResponse *res); void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next); void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next); void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function next); bool isWebServerReady = 0; bool isCertReady = 0; uint32_t timeSpeedUp = 0; // We need to specify some content-type mapping, so the resources get delivered with the // right content type and are displayed correctly in the browser char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}, {".js", "text/javascript"}, {".png", "image/png"}, {".jpg", "image/jpg"}, {".gz", "application/gzip"}, {".gif", "image/gif"}, {".json", "application/json"}, {".css", "text/css"}, {"", ""}}; void handleWebResponse() { if (isWifiAvailable() == 0) { return; } if (isWebServerReady) { // We're going to handle the DNS responder here so it // will be ignored by the NRF boards. handleDNSResponse(); secureServer->loop(); insecureServer->loop(); } /* Slow down the CPU if we have not received a request within the last few seconds. */ if (millis() - timeSpeedUp >= (25 * 1000)) { setCpuFrequencyMhz(80); timeSpeedUp = millis(); } } void taskCreateCert(void *parameter) { prefs.begin("MeshtasticHTTPS", false); // Delete the saved certs if (0) { DEBUG_MSG("Deleting any saved SSL keys ...\n"); // prefs.clear(); prefs.remove("PK"); prefs.remove("cert"); } size_t pkLen = prefs.getBytesLength("PK"); size_t certLen = prefs.getBytesLength("cert"); DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n"); if (pkLen && certLen) { DEBUG_MSG("Existing SSL Certificate found!\n"); } else { DEBUG_MSG("Creating the certificate. This may take a while. Please wait...\n"); yield(); cert = new SSLCert(); yield(); int createCertResult = createSelfSignedCert(*cert, KEYSIZE_2048, "CN=meshtastic.local,O=Meshtastic,C=US", "20190101000000", "20300101000000"); yield(); if (createCertResult != 0) { DEBUG_MSG("Creating the certificate failed\n"); // Serial.printf("Creating the certificate failed. Error Code = 0x%02X, check SSLCert.hpp for details", // createCertResult); // while (true) // delay(500); } else { DEBUG_MSG("Creating the certificate was successful\n"); DEBUG_MSG("Created Private Key: %d Bytes\n", cert->getPKLength()); // for (int i = 0; i < cert->getPKLength(); i++) // Serial.print(cert->getPKData()[i], HEX); // Serial.println(); DEBUG_MSG("Created Certificate: %d Bytes\n", cert->getCertLength()); // for (int i = 0; i < cert->getCertLength(); i++) // Serial.print(cert->getCertData()[i], HEX); // Serial.println(); prefs.putBytes("PK", (uint8_t *)cert->getPKData(), cert->getPKLength()); prefs.putBytes("cert", (uint8_t *)cert->getCertData(), cert->getCertLength()); } } isCertReady = 1; vTaskDelete(NULL); } void createSSLCert() { if (isWifiAvailable() == 0) { return; } // Create a new process just to handle creating the cert. // This is a workaround for Bug: https://github.com/fhessel/esp32_https_server/issues/48 // jm@casler.org (Oct 2020) xTaskCreate(taskCreateCert, /* Task function. */ "createCert", /* String with name of task. */ 16384, /* Stack size in bytes. */ NULL, /* Parameter passed as input of the task */ 16, /* Priority of the task. */ NULL); /* Task handle. */ DEBUG_MSG("Waiting for SSL Cert to be generated.\n"); while (!isCertReady) { DEBUG_MSG("."); delay(1000); yield(); } DEBUG_MSG("SSL Cert Ready!\n"); } void initWebServer() { DEBUG_MSG("Initializing Web Server ...\n"); prefs.begin("MeshtasticHTTPS", false); size_t pkLen = prefs.getBytesLength("PK"); size_t certLen = prefs.getBytesLength("cert"); DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n"); if (pkLen && certLen) { uint8_t *pkBuffer = new uint8_t[pkLen]; prefs.getBytes("PK", pkBuffer, pkLen); uint8_t *certBuffer = new uint8_t[certLen]; prefs.getBytes("cert", certBuffer, certLen); cert = new SSLCert(certBuffer, certLen, pkBuffer, pkLen); DEBUG_MSG("Retrieved Private Key: %d Bytes\n", cert->getPKLength()); // DEBUG_MSG("Retrieved Private Key: " + String(cert->getPKLength()) + " Bytes"); // for (int i = 0; i < cert->getPKLength(); i++) // Serial.print(cert->getPKData()[i], HEX); // Serial.println(); DEBUG_MSG("Retrieved Certificate: %d Bytes\n", cert->getCertLength()); // for (int i = 0; i < cert->getCertLength(); i++) // Serial.print(cert->getCertData()[i], HEX); // Serial.println(); } else { DEBUG_MSG("Web Server started without SSL keys! How did this happen?\n"); } // We can now use the new certificate to setup our server as usual. secureServer = new HTTPSServer(cert); insecureServer = new HTTPServer(); // For every resource available on the server, we need to create a ResourceNode // The ResourceNode links URL and HTTP method to a handler function ResourceNode *nodeAPIv1ToRadioOptions = new ResourceNode("/api/v1/toradio", "OPTIONS", &handleAPIv1ToRadio); ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio); ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio); ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot); ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon); ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot); ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse); ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost); ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic); ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart); ResourceNode *node404 = new ResourceNode("", "GET", &handle404); ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static/", "GET", &handleSpiffsBrowseStatic); // Secure nodes secureServer->registerNode(nodeAPIv1ToRadioOptions); secureServer->registerNode(nodeAPIv1ToRadio); secureServer->registerNode(nodeAPIv1FromRadio); secureServer->registerNode(nodeHotspot); secureServer->registerNode(nodeFavicon); secureServer->registerNode(nodeRoot); secureServer->registerNode(nodeStaticBrowse); secureServer->registerNode(nodeStaticPOST); secureServer->registerNode(nodeStatic); secureServer->registerNode(nodeRestart); secureServer->registerNode(nodeFormUpload); secureServer->registerNode(nodeJsonScanNetworks); secureServer->registerNode(nodeJsonBlinkLED); secureServer->registerNode(nodeJsonSpiffsBrowseStatic); secureServer->setDefaultNode(node404); secureServer->addMiddleware(&middlewareSpeedUp240); // Insecure nodes insecureServer->registerNode(nodeAPIv1ToRadioOptions); insecureServer->registerNode(nodeAPIv1ToRadio); insecureServer->registerNode(nodeAPIv1FromRadio); insecureServer->registerNode(nodeHotspot); insecureServer->registerNode(nodeFavicon); insecureServer->registerNode(nodeRoot); insecureServer->registerNode(nodeStaticBrowse); insecureServer->registerNode(nodeStaticPOST); insecureServer->registerNode(nodeStatic); insecureServer->registerNode(nodeRestart); insecureServer->registerNode(nodeFormUpload); insecureServer->registerNode(nodeJsonScanNetworks); insecureServer->registerNode(nodeJsonBlinkLED); insecureServer->registerNode(nodeJsonSpiffsBrowseStatic); insecureServer->setDefaultNode(node404); insecureServer->addMiddleware(&middlewareSpeedUp160); DEBUG_MSG("Starting Web Servers...\n"); secureServer->start(); insecureServer->start(); if (secureServer->isRunning() && insecureServer->isRunning()) { DEBUG_MSG("HTTP and HTTPS Web Servers Ready! :-) \n"); isWebServerReady = 1; } else { DEBUG_MSG("HTTP and HTTPS Web Servers Failed! ;-( \n"); } } void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function next) { // We want to print the response status, so we need to call next() first. next(); setCpuFrequencyMhz(240); timeSpeedUp = millis(); } void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function next) { // We want to print the response status, so we need to call next() first. next(); // If the frequency is 240mhz, we have recently gotten a HTTPS request. // In that case, leave the frequency where it is and just update the // countdown timer (timeSpeedUp). if (getCpuFrequencyMhz() != 240) { setCpuFrequencyMhz(160); } timeSpeedUp = millis(); } void handleStaticPost(HTTPRequest *req, HTTPResponse *res) { // Assume POST request. Contains submitted data. res->println("File Edited

File Edited

"); // The form is submitted with the x-www-form-urlencoded content type, so we need the // HTTPURLEncodedBodyParser to read the fields. // Note that the content of the file's content comes from a
"); res->println(""); res->println(""); res->println(""); return; } res->println("

Upload new file

"); res->println("

*** This interface is experimental ***

"); res->println("

This form allows you to upload files. Keep your filenames very short and files small. Big filenames and big " "files are a known problem.

"); res->println("
"); res->println("file:
"); res->println(""); res->println("
"); res->println("

All Files

"); File root = SPIFFS.open("/"); if (root.isDirectory()) { res->println(""); res->println(""); res->println(""); res->println(""); res->println(""); res->println(""); res->println(""); File file = root.openNextFile(); while (file) { String filePath = String(file.name()); if (filePath.indexOf("/static") == 0) { res->println(""); res->println(""); res->println(""); res->println(""); res->println(""); res->println(""); } file = root.openNextFile(); } res->println("
File"); res->println("Size"); res->println("Actions"); res->println("
"); if (String(file.name()).substring(1).endsWith(".gz")) { String modifiedFile = String(file.name()).substring(1); modifiedFile.remove((modifiedFile.length() - 3), 3); res->print("" + String(file.name()).substring(1) + ""); } else { res->print("" + String(file.name()).substring(1) + ""); } res->println(""); res->print(String(file.size())); res->println(""); res->print("Delete "); res->println(""); if (!String(file.name()).substring(1).endsWith(".gz")) { res->print("Edit"); } res->println("
"); res->print("
"); // res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes
"); res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes
"); res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes
"); } } void handleStatic(HTTPRequest *req, HTTPResponse *res) { // Get access to the parameters ResourceParameters *params = req->getParams(); std::string parameter1; // Print the first parameter value if (params->getPathParameter(0, parameter1)) { std::string filename = "/static/" + parameter1; std::string filenameGzip = "/static/" + parameter1 + ".gz"; if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { // Send "404 Not Found" as response, as the file doesn't seem to exist res->setStatusCode(404); res->setStatusText("Not found"); res->println("404 Not Found"); res->printf("

File not found: %s

\n", filename.c_str()); return; } // Try to open the file from SPIFFS File file; if (SPIFFS.exists(filename.c_str())) { file = SPIFFS.open(filename.c_str()); if (!file.available()) { DEBUG_MSG("File not available - %s\n", filename.c_str()); } } else if (SPIFFS.exists(filenameGzip.c_str())) { file = SPIFFS.open(filenameGzip.c_str()); res->setHeader("Content-Encoding", "gzip"); if (!file.available()) { DEBUG_MSG("File not available\n"); } } res->setHeader("Content-Length", httpsserver::intToString(file.size())); bool has_set_content_type = false; // Content-Type is guessed using the definition of the contentTypes-table defined above int cTypeIdx = 0; do { if (filename.rfind(contentTypes[cTypeIdx][0]) != std::string::npos) { res->setHeader("Content-Type", contentTypes[cTypeIdx][1]); has_set_content_type = true; break; } cTypeIdx += 1; } while (strlen(contentTypes[cTypeIdx][0]) > 0); if (!has_set_content_type) { // Set a default content type res->setHeader("Content-Type", "application/octet-stream"); } // Read the file from SPIFFS and write it to the HTTP response body size_t length = 0; do { char buffer[256]; length = file.read((uint8_t *)buffer, 256); std::string bufferString(buffer, length); res->write((uint8_t *)bufferString.c_str(), bufferString.size()); } while (length > 0); file.close(); return; } else { res->println("ERROR: This should not have happened..."); } } void handleFormUpload(HTTPRequest *req, HTTPResponse *res) { // First, we need to check the encoding of the form that we have received. // The browser will set the Content-Type request header, so we can use it for that purpose. // Then we select the body parser based on the encoding. // Actually we do this only for documentary purposes, we know the form is going // to be multipart/form-data. HTTPBodyParser *parser; std::string contentType = req->getHeader("Content-Type"); // The content type may have additional properties after a semicolon, for exampel: // Content-Type: text/html;charset=utf-8 // Content-Type: multipart/form-data;boundary=------s0m3w31rdch4r4c73rs // As we're interested only in the actual mime _type_, we strip everything after the // first semicolon, if one exists: size_t semicolonPos = contentType.find(";"); if (semicolonPos != std::string::npos) { contentType = contentType.substr(0, semicolonPos); } // Now, we can decide based on the content type: if (contentType == "multipart/form-data") { parser = new HTTPMultipartBodyParser(req); } else { Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str()); return; } res->println("File " "Upload

File Upload

"); // We iterate over the fields. Any field with a filename is uploaded. // Note that the BodyParser consumes the request body, meaning that you can iterate over the request's // fields only a single time. The reason for this is that it allows you to handle large requests // which would not fit into memory. bool didwrite = false; // parser->nextField() will move the parser to the next field in the request body (field meaning a // form field, if you take the HTML perspective). After the last field has been processed, nextField() // returns false and the while loop ends. while (parser->nextField()) { // For Multipart data, each field has three properties: // The name ("name" value of the tag) // The filename (If it was a , this is the filename on the machine of the // user uploading it) // The mime type (It is determined by the client. So do not trust this value and blindly start // parsing files only if the type matches) std::string name = parser->getFieldName(); std::string filename = parser->getFieldFilename(); std::string mimeType = parser->getFieldMimeType(); // We log all three values, so that you can observe the upload on the serial monitor: DEBUG_MSG("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(), mimeType.c_str()); // Double check that it is what we expect if (name != "file") { DEBUG_MSG("Skipping unexpected field"); res->println("

No file found.

"); return; } // Double check that it is what we expect if (filename == "") { DEBUG_MSG("Skipping unexpected field"); res->println("

No file found.

"); return; } // SPIFFS limits the total lenth of a path + file to 31 characters. if (filename.length() + 8 > 31) { DEBUG_MSG("Uploaded filename too long!"); res->println("

Uploaded filename too long! Limit of 23 characters.

"); delete parser; return; } // You should check file name validity and all that, but we skip that to make the core // concepts of the body parser functionality easier to understand. std::string pathname = "/static/" + filename; // Create a new file on spiffs to stream the data into File file = SPIFFS.open(pathname.c_str(), "w"); size_t fileLength = 0; didwrite = true; // With endOfField you can check whether the end of field has been reached or if there's // still data pending. With multipart bodies, you cannot know the field size in advance. while (!parser->endOfField()) { byte buf[512]; size_t readLength = parser->read(buf, 512); file.write(buf, readLength); fileLength += readLength; // Abort the transfer if there is less than 50k space left on the filesystem. if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 51200) { file.close(); res->println("

Write aborted! File is won't fit!

"); delete parser; return; } } file.close(); res->printf("

Saved %d bytes to %s

", (int)fileLength, pathname.c_str()); } if (!didwrite) { res->println("

Did not write any file

"); } res->println(""); delete parser; } void handle404(HTTPRequest *req, HTTPResponse *res) { // Discard request body, if we received any // We do this, as this is the default node and may also server POST/PUT requests req->discardRequestBody(); // Set the response status res->setStatusCode(404); res->setStatusText("Not Found"); // Set content type of the response res->setHeader("Content-Type", "text/html"); // Write a tiny HTTP page res->println(""); res->println(""); res->println("Not Found"); res->println("

404 Not Found

The requested resource was not found on this server.

"); res->println(""); } /* This supports the Apple Captive Network Assistant (CNA) Portal */ void handleHotspot(HTTPRequest *req, HTTPResponse *res) { DEBUG_MSG("Hotspot Request\n"); /* If we don't do a redirect, be sure to return a "Success" message otherwise iOS will have trouble detecting that the connection to the SoftAP worked. */ // Status code is 200 OK by default. // We want to deliver a simple HTML page, so we send a corresponding content type: res->setHeader("Content-Type", "text/html"); // The response implements the Print interface, so you can use it just like // you would write to Serial etc. res->println(""); res->println("\n"); } void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) { DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1FromRadio\n"); /* For documentation, see: https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md Example: http://10.10.30.198/api/v1/fromradio */ // Get access to the parameters ResourceParameters *params = req->getParams(); // std::string paramAll = "all"; std::string valueAll; // Status code is 200 OK by default. res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "PUT, GET"); res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); uint8_t txBuf[MAX_STREAM_BUF_SIZE]; uint32_t len = 1; if (params->getQueryParameter("all", valueAll)) { // If all is ture, return all the buffers we have available // to us at this point in time. if (valueAll == "true") { while (len) { len = webAPI.getFromRadio(txBuf); res->write(txBuf, len); } // Otherwise, just return one protobuf } else { len = webAPI.getFromRadio(txBuf); res->write(txBuf, len); } // the param "all" was not spcified. Return just one protobuf } else { len = webAPI.getFromRadio(txBuf); res->write(txBuf, len); } DEBUG_MSG("--------------- webAPI handleAPIv1FromRadio, len %d\n", len); } void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) { DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1ToRadio\n"); /* For documentation, see: https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md Example: http://10.10.30.198/api/v1/toradio */ // Status code is 200 OK by default. res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Headers", "Content-Type"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS"); res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto"); if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content res->print(""); return; } byte buffer[MAX_TO_FROM_RADIO_SIZE]; size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE); DEBUG_MSG("Received %d bytes from PUT request\n", s); webAPI.handleToRadio(buffer, s); res->write(buffer, s); DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n"); } /* To convert text to c strings: https://tomeko.net/online_tools/cpp_text_escape.php?lang=en */ void handleRoot(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "text/html"); randomSeed(millis()); res->setHeader("Set-Cookie", "mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST"); std::string cookie = req->getHeader("Cookie"); // String cookieString = cookie.c_str(); // uint8_t nameIndex = cookieString.indexOf("mt_session"); // DEBUG_MSG(cookie.c_str()); std::string filename = "/static/index.html"; std::string filenameGzip = "/static/index.html.gz"; if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) { // Send "404 Not Found" as response, as the file doesn't seem to exist res->setStatusCode(404); res->setStatusText("Not found"); res->println("404 Not Found"); res->printf("

File not found: %s

\n", filename.c_str()); res->printf("

\n"); res->printf("

You have gotten this error because the filesystem for the web server has not been loaded.

\n"); res->printf("

Please review the 'Common Problems' section of the web interface documentation.

\n"); return; } // Try to open the file from SPIFFS File file; if (SPIFFS.exists(filename.c_str())) { file = SPIFFS.open(filename.c_str()); if (!file.available()) { DEBUG_MSG("File not available - %s\n", filename.c_str()); } } else if (SPIFFS.exists(filenameGzip.c_str())) { file = SPIFFS.open(filenameGzip.c_str()); res->setHeader("Content-Encoding", "gzip"); if (!file.available()) { DEBUG_MSG("File not available\n"); } } // Read the file from SPIFFS and write it to the HTTP response body size_t length = 0; do { char buffer[256]; length = file.read((uint8_t *)buffer, 256); std::string bufferString(buffer, length); res->write((uint8_t *)bufferString.c_str(), bufferString.size()); } while (length > 0); } void handleRestart(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "text/html"); DEBUG_MSG("***** Restarted on HTTP(s) Request *****\n"); res->println("Restarting"); ESP.restart(); } void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); // This can be cleaned up at some point to make it non-blocking and to allow for more configuration. std::string contentType = req->getHeader("Content-Type"); std::string blink_target; HTTPBodyParser *parser; if (contentType.rfind("multipart/form-data",0) == 0) { // If a body was submitted to /blink, then figure out whether the user // watned to blink the LED or the screen parser = new HTTPMultipartBodyParser(req); while (parser->nextField()) { std::string name = parser->getFieldName(); if (name == "blink_target") { char buf[512]; size_t readLength = parser->read((byte *)buf, 512); // filename = std::string("/public/") + std::string(buf, readLength); blink_target = std::string(buf, readLength); } } } else { blink_target = "LED"; } if (blink_target == "LED" ) { uint8_t count = 10; while (count > 0) { setLed(true); delay(50); setLed(false); delay(50); count = count - 1; } } else { screen->blink(); } res->println("{"); res->println("\"status\": \"Blink completed: LED\""); res->println("}"); } void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); // res->setHeader("Content-Type", "text/html"); int n = WiFi.scanNetworks(); res->println("{"); res->println("\"data\": {"); if (n == 0) { // No networks found. res->println("\"networks\": []"); } else { res->println("\"networks\": ["); for (int i = 0; i < n; ++i) { char ssidArray[50]; String ssidString = String(WiFi.SSID(i)); // String ssidString = String(WiFi.SSID(i)).toCharArray(ssidArray, WiFi.SSID(i).length()); ssidString.replace("\"", "\\\""); ssidString.toCharArray(ssidArray, 50); if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) { // res->println("{\"ssid\": \"%s\",\"rssi\": -75}, ", String(WiFi.SSID(i).c_str() ); res->printf("{\"ssid\": \"%s\",\"rssi\": %d}", ssidArray, WiFi.RSSI(i)); // WiFi.RSSI(i) if (i != n - 1) { res->printf(","); } } // Yield some cpu cycles to IP stack. // This is important in case the list is large and it takes us time to return // to the main loop. yield(); } res->println("]"); } res->println("},"); res->println("\"status\": \"ok\""); res->println("}"); } void handleFavicon(HTTPRequest *req, HTTPResponse *res) { // Set Content-Type res->setHeader("Content-Type", "image/vnd.microsoft.icon"); // Write data from header file res->write(FAVICON_DATA, FAVICON_LENGTH); } void replaceAll(std::string &str, const std::string &from, const std::string &to) { if (from.empty()) return; size_t start_pos = 0; while ((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' } }