921 lines
27 KiB
C++
921 lines
27 KiB
C++
#include "esp-fs-webserver.h"
|
|
|
|
FSWebServer::FSWebServer(fs::FS &fs, WebServerClass &server)
|
|
{
|
|
m_filesystem = &fs;
|
|
webserver = &server;
|
|
m_basePath[0] = '\0';
|
|
}
|
|
|
|
WebServerClass *FSWebServer::getRequest()
|
|
{
|
|
return webserver;
|
|
}
|
|
|
|
void FSWebServer::run()
|
|
{
|
|
webserver->handleClient();
|
|
if (m_apmode)
|
|
m_dnsServer.processNextRequest();
|
|
}
|
|
|
|
void FSWebServer::addHandler(const Uri &uri, HTTPMethod method, WebServerClass::THandlerFunction fn)
|
|
{
|
|
webserver->on(uri, method, fn);
|
|
}
|
|
|
|
void FSWebServer::addHandler(const Uri &uri, WebServerClass::THandlerFunction handler)
|
|
{
|
|
webserver->on(uri, HTTP_ANY, handler);
|
|
}
|
|
|
|
// List all files saved in the selected filesystem
|
|
bool FSWebServer::checkDir(char *dirname, uint8_t levels)
|
|
{
|
|
if (dirname[0] != '/')
|
|
dirname[0] = '/';
|
|
File root = m_filesystem->open(dirname, "r");
|
|
if (!root)
|
|
{
|
|
DebugPrintln("- failed to open directory\n");
|
|
return false;
|
|
}
|
|
if (!root.isDirectory())
|
|
{
|
|
DebugPrintln(" - not a directory\n");
|
|
return false;
|
|
}
|
|
File file = root.openNextFile();
|
|
while (file)
|
|
{
|
|
if (file.isDirectory())
|
|
{
|
|
char dir[16];
|
|
strcpy(dir, "/");
|
|
strcat(dir, file.name());
|
|
DebugPrintf("DIR : %s\n", dir);
|
|
checkDir(dir, levels - 1);
|
|
}
|
|
else
|
|
{
|
|
DebugPrintf(" FILE: %s\tSIZE: %d\n", file.name(), file.size());
|
|
}
|
|
file = root.openNextFile();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FSWebServer::begin(const char *path)
|
|
{
|
|
DebugPrintln("\nList the files of webserver: ");
|
|
if (path != nullptr)
|
|
strcpy(m_basePath, path);
|
|
|
|
m_fsOK = checkDir(m_basePath, 2);
|
|
|
|
#ifdef INCLUDE_EDIT_HTM
|
|
webserver->on("/status", HTTP_GET, std::bind(&FSWebServer::handleStatus, this));
|
|
webserver->on("/list", HTTP_GET, std::bind(&FSWebServer::handleFileList, this));
|
|
webserver->on("/edit", HTTP_GET, std::bind(&FSWebServer::handleGetEdit, this));
|
|
webserver->on("/edit", HTTP_PUT, std::bind(&FSWebServer::handleFileCreate, this));
|
|
webserver->on("/edit", HTTP_DELETE, std::bind(&FSWebServer::handleFileDelete, this));
|
|
#endif
|
|
webserver->onNotFound(std::bind(&FSWebServer::handleRequest, this));
|
|
webserver->on("/favicon.ico", HTTP_GET, std::bind(&FSWebServer::replyOK, this));
|
|
webserver->on("/", HTTP_GET, std::bind(&FSWebServer::handleIndex, this));
|
|
#ifdef INCLUDE_SETUP_HTM
|
|
webserver->on("/setup", HTTP_GET, std::bind(&FSWebServer::handleSetup, this));
|
|
#endif
|
|
webserver->on("/scan", HTTP_GET, std::bind(&FSWebServer::handleScanNetworks, this));
|
|
webserver->on("/connect", HTTP_POST, std::bind(&FSWebServer::doWifiConnection, this));
|
|
webserver->on("/restart", HTTP_GET, std::bind(&FSWebServer::doRestart, this));
|
|
webserver->on("/ipaddress", HTTP_GET, std::bind(&FSWebServer::getIpAddress, this));
|
|
|
|
// Captive Portal redirect
|
|
webserver->on("/redirect", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
|
|
// Windows
|
|
webserver->on("/connecttest.txt", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
|
|
// Apple
|
|
webserver->on("/hotspot-detect.html", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
|
|
// Android
|
|
webserver->on("/generate_204", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
|
|
webserver->on("/gen_204", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
|
|
|
|
// Upload file
|
|
// - first callback is called after the request has ended with all parsed arguments
|
|
// - second callback handles file upload at that location
|
|
webserver->on("/edit", HTTP_POST, std::bind(&FSWebServer::replyOK, this), std::bind(&FSWebServer::handleFileUpload, this));
|
|
|
|
// OTA update via webbrowser
|
|
m_httpUpdater.setup(webserver);
|
|
|
|
#ifdef ESP32
|
|
webserver->enableCrossOrigin(true);
|
|
#endif
|
|
webserver->setContentLength(50);
|
|
webserver->begin();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FSWebServer::setCaptiveWebage(const char *url)
|
|
{
|
|
m_apWebpage = (char *)realloc(m_apWebpage, sizeof(url));
|
|
strcpy(m_apWebpage, url);
|
|
}
|
|
|
|
IPAddress FSWebServer::setAPmode(const char *ssid, const char *psk)
|
|
{
|
|
m_apmode = true;
|
|
WiFi.mode(WIFI_AP_STA);
|
|
WiFi.persistent(false);
|
|
WiFi.softAP(ssid, psk);
|
|
/* Setup the DNS server redirecting all the domains to the apIP */
|
|
m_dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
|
m_dnsServer.start(53, "*", WiFi.softAPIP());
|
|
return WiFi.softAPIP();
|
|
}
|
|
|
|
IPAddress FSWebServer::startWiFi(uint32_t timeout, const char *apSSID, const char *apPsw)
|
|
{
|
|
IPAddress ip;
|
|
m_timeout = timeout;
|
|
WiFi.mode(WIFI_STA);
|
|
|
|
const char *_ssid;
|
|
const char *_pass;
|
|
#if defined(ESP8266)
|
|
struct station_config conf;
|
|
wifi_station_get_config_default(&conf);
|
|
_ssid = reinterpret_cast<const char *>(conf.ssid);
|
|
_pass = reinterpret_cast<const char *>(conf.password);
|
|
|
|
#elif defined(ESP32)
|
|
wifi_config_t conf;
|
|
esp_wifi_get_config(WIFI_IF_STA, &conf);
|
|
|
|
_ssid = reinterpret_cast<const char *>(conf.sta.ssid);
|
|
_pass = reinterpret_cast<const char *>(conf.sta.password);
|
|
#endif
|
|
|
|
if (strlen(_ssid) && strlen(_pass))
|
|
{
|
|
WiFi.begin(_ssid, _pass);
|
|
Serial.print(F("Connecting to "));
|
|
Serial.println(_ssid);
|
|
uint32_t startTime = millis();
|
|
while (WiFi.status() != WL_CONNECTED)
|
|
{
|
|
delay(500);
|
|
Serial.print(".");
|
|
if (WiFi.status() == WL_CONNECTED)
|
|
{
|
|
ip = WiFi.localIP();
|
|
return ip;
|
|
}
|
|
// If no connection after a while go in Access Point mode
|
|
if (millis() - startTime > m_timeout)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (apSSID != nullptr && apPsw != nullptr)
|
|
setAPmode(apSSID, apPsw);
|
|
else
|
|
setAPmode("ESP_AP", "123456789");
|
|
|
|
WiFi.begin();
|
|
ip = WiFi.softAPIP();
|
|
Serial.print(F("\nAP mode.\nServer IP address: "));
|
|
Serial.println(ip);
|
|
Serial.println();
|
|
return ip;
|
|
}
|
|
|
|
//////////////////////////////// WiFi /////////////////////////////////////////
|
|
|
|
/**
|
|
* Redirect to captive portal if we got a request for another domain.
|
|
*/
|
|
bool FSWebServer::captivePortal()
|
|
{
|
|
IPAddress ip = webserver->client().localIP();
|
|
char serverLoc[sizeof("https:://255.255.255.255/") + sizeof(m_apWebpage) + 1];
|
|
snprintf(serverLoc, sizeof(serverLoc), "http://%d.%d.%d.%d%s", ip[0], ip[1], ip[2], ip[3], m_apWebpage);
|
|
|
|
// redirect if hostheader not server ip, prevent redirect loops
|
|
if (strcmp(serverLoc, webserver->hostHeader().c_str()))
|
|
{
|
|
webserver->sendHeader(F("Location"), serverLoc, true);
|
|
webserver->send(302, F("text/html"), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
|
webserver->client().stop(); // Stop is needed because we sent no content length
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FSWebServer::handleRequest()
|
|
{
|
|
if (!m_fsOK)
|
|
{
|
|
replyToCLient(ERROR, PSTR(FS_INIT_ERROR));
|
|
return;
|
|
}
|
|
#if defined(ESP32)
|
|
String _url = WebServer::urlDecode(webserver->uri());
|
|
#elif defined(ESP8266)
|
|
String _url = ESP8266WebServer::urlDecode(webserver->uri());
|
|
#endif
|
|
// First try to find and return the requested file from the filesystem,
|
|
// and if it fails, return a 404 page with debug information
|
|
// Serial.print("urlDecode: ");
|
|
// Serial.println(_url);
|
|
if (handleFileRead(_url))
|
|
return;
|
|
else
|
|
replyToCLient(NOT_FOUND, PSTR(FILE_NOT_FOUND));
|
|
}
|
|
|
|
void FSWebServer::getIpAddress()
|
|
{
|
|
webserver->send(200, "text/json", WiFi.localIP().toString());
|
|
}
|
|
|
|
void FSWebServer::doRestart()
|
|
{
|
|
Serial.println("RESTART");
|
|
webserver->send(200, "text/json", "Going to restart ESP");
|
|
delay(500);
|
|
ESP.restart();
|
|
}
|
|
|
|
void FSWebServer::doWifiConnection()
|
|
{
|
|
String ssid, pass;
|
|
bool persistent = true;
|
|
WiFi.mode(WIFI_AP_STA);
|
|
|
|
if (webserver->hasArg("ssid"))
|
|
{
|
|
ssid = webserver->arg("ssid");
|
|
}
|
|
|
|
if (webserver->hasArg("password"))
|
|
{
|
|
pass = webserver->arg("password");
|
|
}
|
|
|
|
if (webserver->hasArg("persistent"))
|
|
{
|
|
String pers = webserver->arg("persistent");
|
|
if (pers.equals("false"))
|
|
{
|
|
persistent = false;
|
|
}
|
|
}
|
|
|
|
if (WiFi.status() == WL_CONNECTED)
|
|
{
|
|
|
|
IPAddress ip = WiFi.localIP();
|
|
String resp = "ESP is currently connected to a WiFi network.<br><br>"
|
|
"Actual connection will be closed and a new attempt will be done with <b>";
|
|
resp += ssid;
|
|
resp += "</b> WiFi network.";
|
|
webserver->send(200, "text/plain", resp);
|
|
|
|
delay(500);
|
|
Serial.println("Disconnect from current WiFi network");
|
|
WiFi.disconnect();
|
|
}
|
|
|
|
if (ssid.length() && pass.length())
|
|
{
|
|
// Try to connect to new ssid
|
|
Serial.print("\nConnecting to ");
|
|
Serial.println(ssid);
|
|
WiFi.begin(ssid.c_str(), pass.c_str());
|
|
|
|
uint32_t beginTime = millis();
|
|
while (WiFi.status() != WL_CONNECTED)
|
|
{
|
|
delay(100);
|
|
Serial.print("*.*");
|
|
if (millis() - beginTime > m_timeout)
|
|
break;
|
|
}
|
|
// reply to client
|
|
if (WiFi.status() == WL_CONNECTED)
|
|
{
|
|
// WiFi.softAPdisconnect();
|
|
IPAddress ip = WiFi.localIP();
|
|
Serial.print("\nConnected to Wifi! IP address: ");
|
|
Serial.println(ip);
|
|
webserver->send(200, "text/plain", ip.toString());
|
|
m_apmode = false;
|
|
delay(500);
|
|
ESP.restart();
|
|
// Store current WiFi configuration in flash
|
|
if (persistent)
|
|
{
|
|
#if defined(ESP8266)
|
|
struct station_config stationConf;
|
|
wifi_station_get_config_default(&stationConf);
|
|
// Clear previuos configuration
|
|
memset(&stationConf, 0, sizeof(stationConf));
|
|
os_memcpy(&stationConf.ssid, ssid.c_str(), ssid.length());
|
|
os_memcpy(&stationConf.password, pass.c_str(), pass.length());
|
|
wifi_set_opmode(STATION_MODE);
|
|
wifi_station_set_config(&stationConf);
|
|
#elif defined(ESP32)
|
|
wifi_config_t stationConf;
|
|
esp_wifi_get_config(WIFI_IF_STA, &stationConf);
|
|
// Clear previuos configuration
|
|
memset(&stationConf, 0, sizeof(stationConf));
|
|
memcpy(&stationConf.sta.ssid, ssid.c_str(), ssid.length());
|
|
memcpy(&stationConf.sta.password, pass.c_str(), pass.length());
|
|
esp_wifi_set_config(WIFI_IF_STA, &stationConf);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if defined(ESP8266)
|
|
struct station_config stationConf;
|
|
wifi_station_get_config_default(&stationConf);
|
|
// Clear previuos configuration
|
|
memset(&stationConf, 0, sizeof(stationConf));
|
|
wifi_station_set_config(&stationConf);
|
|
#elif defined(ESP32)
|
|
wifi_config_t stationConf;
|
|
esp_wifi_get_config(WIFI_IF_STA, &stationConf);
|
|
// Clear previuos configuration
|
|
memset(&stationConf, 0, sizeof(stationConf));
|
|
esp_wifi_set_config(WIFI_IF_STA, &stationConf);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
webserver->send(500, "text/plain", "Connection error, maybe the password is wrong?");
|
|
}
|
|
webserver->send(500, "text/plain", "Wrong credentials provided");
|
|
}
|
|
|
|
void FSWebServer::setCrossOrigin()
|
|
{
|
|
webserver->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
|
|
webserver->sendHeader(F("Access-Control-Max-Age"), F("600"));
|
|
webserver->sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
|
|
webserver->sendHeader(F("Access-Control-Allow-Headers"), F("*"));
|
|
};
|
|
|
|
void FSWebServer::handleScanNetworks()
|
|
{
|
|
String jsonList = "[";
|
|
DebugPrint("Scanning WiFi networks...");
|
|
int n = WiFi.scanNetworks();
|
|
DebugPrintln(" complete.");
|
|
if (n == 0)
|
|
{
|
|
DebugPrintln("no networks found");
|
|
webserver->send(200, "text/json", "[]");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DebugPrint(n);
|
|
DebugPrintln(" networks found:");
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
String ssid = WiFi.SSID(i);
|
|
int rssi = WiFi.RSSI(i);
|
|
#if defined(ESP8266)
|
|
String security = WiFi.encryptionType(i) == AUTH_OPEN ? "none" : "enabled";
|
|
#elif defined(ESP32)
|
|
String security = WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? "none" : "enabled";
|
|
#endif
|
|
jsonList += "{\"ssid\":\"";
|
|
jsonList += ssid;
|
|
jsonList += "\",\"strength\":\"";
|
|
jsonList += rssi;
|
|
jsonList += "\",\"security\":";
|
|
jsonList += security == "none" ? "false" : "true";
|
|
jsonList += ssid.equals(WiFi.SSID()) ? ",\"selected\": true" : "";
|
|
jsonList += i < n - 1 ? "}," : "}";
|
|
}
|
|
jsonList += "]";
|
|
}
|
|
webserver->send(200, "text/json", jsonList);
|
|
DebugPrintln(jsonList);
|
|
}
|
|
|
|
#ifdef INCLUDE_SETUP_HTM
|
|
|
|
void FSWebServer::addDropdownList(const char *label, const char **array, size_t size)
|
|
{
|
|
File file = m_filesystem->open("/config.json", "r");
|
|
int sz = file.size() * 1.33;
|
|
int docSize = max(sz, 2048);
|
|
DynamicJsonDocument doc((size_t)docSize);
|
|
if (file)
|
|
{
|
|
// If file is present, load actual configuration
|
|
DeserializationError error = deserializeJson(doc, file);
|
|
if (error)
|
|
{
|
|
DebugPrintln(F("Failed to deserialize file, may be corrupted"));
|
|
DebugPrintln(error.c_str());
|
|
file.close();
|
|
return;
|
|
}
|
|
file.close();
|
|
}
|
|
else
|
|
{
|
|
DebugPrintln(F("File not found, will be created new configuration file"));
|
|
}
|
|
|
|
numOptions++;
|
|
|
|
// If key is present in json, we don't need to create it.
|
|
if (doc.containsKey(label))
|
|
return;
|
|
|
|
JsonObject obj = doc.createNestedObject(label);
|
|
obj["selected"] = array[0]; // first element selected as default
|
|
JsonArray arr = obj.createNestedArray("values");
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
arr.add(array[i]);
|
|
}
|
|
|
|
file = m_filesystem->open("/config.json", "w");
|
|
if (serializeJsonPretty(doc, file) == 0)
|
|
{
|
|
DebugPrintln(F("Failed to write to file"));
|
|
}
|
|
file.close();
|
|
}
|
|
|
|
void FSWebServer::removeWhiteSpaces(String &str)
|
|
{
|
|
const char noChars[] = {'\n', '\r', '\t'};
|
|
int pos = -1;
|
|
// Remove non printable characters
|
|
for (int i = 0; i < sizeof(noChars); i++)
|
|
{
|
|
pos = str.indexOf(noChars[i]);
|
|
while (pos > -1)
|
|
{
|
|
str.replace(String(noChars[i]), "");
|
|
pos = str.indexOf(noChars[i]);
|
|
}
|
|
}
|
|
// Remove doubles spaces
|
|
pos = str.indexOf(" ");
|
|
while (pos > -1)
|
|
{
|
|
str.replace(" ", " ");
|
|
pos = str.indexOf(" ");
|
|
}
|
|
}
|
|
|
|
void FSWebServer::handleSetup()
|
|
{
|
|
webserver->sendHeader(PSTR("Content-Encoding"), "gzip");
|
|
webserver->send_P(200, "text/html", SETUP_HTML, SETUP_HTML_SIZE);
|
|
}
|
|
#endif
|
|
|
|
void FSWebServer::handleIndex()
|
|
{
|
|
if (m_filesystem->exists("/index.htm"))
|
|
{
|
|
handleFileRead("/index.htm");
|
|
}
|
|
else if (m_filesystem->exists("/index.html"))
|
|
{
|
|
handleFileRead("/index.html");
|
|
}
|
|
#ifdef INCLUDE_SETUP_HTM
|
|
else
|
|
{
|
|
handleSetup();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
Read the given file from the filesystem and stream it back to the client
|
|
*/
|
|
bool FSWebServer::handleFileRead(const String &uri)
|
|
{
|
|
String path = m_basePath;
|
|
path = uri;
|
|
|
|
DebugPrintln("handleFileRead: " + path);
|
|
if (path.endsWith("/"))
|
|
{
|
|
path += "index.htm";
|
|
}
|
|
String pathWithGz = path + ".gz";
|
|
|
|
if (m_filesystem->exists(pathWithGz) || m_filesystem->exists(path))
|
|
{
|
|
if (m_filesystem->exists(pathWithGz))
|
|
{
|
|
path += ".gz";
|
|
}
|
|
const char *contentType = getContentType(path.c_str());
|
|
File file = m_filesystem->open(path, "r");
|
|
if (webserver->streamFile(file, contentType) != file.size())
|
|
{
|
|
DebugPrintln(PSTR("Sent less data than expected!"));
|
|
// webserver->stop();
|
|
}
|
|
file.close();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Handle a file upload request
|
|
*/
|
|
void FSWebServer::handleFileUpload()
|
|
{
|
|
if (webserver->uri() != "/edit")
|
|
{
|
|
return;
|
|
}
|
|
HTTPUpload &upload = webserver->upload();
|
|
if (upload.status == UPLOAD_FILE_START)
|
|
{
|
|
String filename = upload.filename;
|
|
String result;
|
|
// Make sure paths always start with "/"
|
|
if (!filename.startsWith("/"))
|
|
{
|
|
filename = "/" + filename;
|
|
}
|
|
checkForUnsupportedPath(filename, result);
|
|
if (result.length() > 0)
|
|
{
|
|
replyToCLient(ERROR, PSTR("INVALID FILENAME"));
|
|
return;
|
|
}
|
|
|
|
DebugPrintf_P(PSTR("handleFileUpload Name: %s\n"), filename.c_str());
|
|
m_uploadFile = m_filesystem->open(filename, "w");
|
|
if (!m_uploadFile)
|
|
{
|
|
replyToCLient(ERROR, PSTR("CREATE FAILED"));
|
|
return;
|
|
}
|
|
DebugPrintf_P(PSTR("Upload: START, filename: %s\n"), filename.c_str());
|
|
}
|
|
else if (upload.status == UPLOAD_FILE_WRITE)
|
|
{
|
|
if (m_uploadFile)
|
|
{
|
|
size_t bytesWritten = m_uploadFile.write(upload.buf, upload.currentSize);
|
|
if (bytesWritten != upload.currentSize)
|
|
{
|
|
replyToCLient(ERROR, PSTR("WRITE FAILED"));
|
|
return;
|
|
}
|
|
}
|
|
DebugPrintf_P(PSTR("Upload: WRITE, Bytes: %d\n"), upload.currentSize);
|
|
}
|
|
else if (upload.status == UPLOAD_FILE_END)
|
|
{
|
|
if (m_uploadFile)
|
|
{
|
|
m_uploadFile.close();
|
|
}
|
|
DebugPrintf_P(PSTR("Upload: END, Size: %d\n"), upload.totalSize);
|
|
}
|
|
}
|
|
|
|
void FSWebServer::replyOK()
|
|
{
|
|
replyToCLient(OK, "");
|
|
}
|
|
|
|
void FSWebServer::replyToCLient(int msg_type = 0, const char *msg = "")
|
|
{
|
|
webserver->sendHeader("Access-Control-Allow-Origin", "*");
|
|
switch (msg_type)
|
|
{
|
|
case OK:
|
|
webserver->send(200, FPSTR(TEXT_PLAIN), "");
|
|
break;
|
|
case CUSTOM:
|
|
webserver->send(200, FPSTR(TEXT_PLAIN), msg);
|
|
break;
|
|
case NOT_FOUND:
|
|
webserver->send(404, FPSTR(TEXT_PLAIN), msg);
|
|
break;
|
|
case BAD_REQUEST:
|
|
webserver->send(400, FPSTR(TEXT_PLAIN), msg);
|
|
break;
|
|
case ERROR:
|
|
webserver->send(500, FPSTR(TEXT_PLAIN), msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS).
|
|
Returns an empty String if supported, or detail of error(s) if unsupported
|
|
*/
|
|
void FSWebServer::checkForUnsupportedPath(String &filename, String &error)
|
|
{
|
|
if (!filename.startsWith("/"))
|
|
{
|
|
error += PSTR(" !! NO_LEADING_SLASH !! ");
|
|
}
|
|
if (filename.indexOf("//") != -1)
|
|
{
|
|
error += PSTR(" !! DOUBLE_SLASH !! ");
|
|
}
|
|
if (filename.endsWith("/"))
|
|
{
|
|
error += PSTR(" ! TRAILING_SLASH ! ");
|
|
}
|
|
DebugPrintln(filename);
|
|
DebugPrintln(error);
|
|
}
|
|
|
|
const char *FSWebServer::getContentType(const char *filename)
|
|
{
|
|
if (webserver->hasArg("download"))
|
|
return PSTR("application/octet-stream");
|
|
else if (strstr(filename, ".htm"))
|
|
return PSTR("text/html");
|
|
else if (strstr(filename, ".html"))
|
|
return PSTR("text/html");
|
|
else if (strstr(filename, ".css"))
|
|
return PSTR("text/css");
|
|
else if (strstr(filename, ".sass"))
|
|
return PSTR("text/css");
|
|
else if (strstr(filename, ".js"))
|
|
return PSTR("application/javascript");
|
|
else if (strstr(filename, ".png"))
|
|
return PSTR("image/png");
|
|
else if (strstr(filename, ".svg"))
|
|
return PSTR("image/svg+xml");
|
|
else if (strstr(filename, ".gif"))
|
|
return PSTR("image/gif");
|
|
else if (strstr(filename, ".jpg"))
|
|
return PSTR("image/jpeg");
|
|
else if (strstr(filename, ".ico"))
|
|
return PSTR("image/x-icon");
|
|
else if (strstr(filename, ".xml"))
|
|
return PSTR("text/xml");
|
|
else if (strstr(filename, ".pdf"))
|
|
return PSTR("application/x-pdf");
|
|
else if (strstr(filename, ".zip"))
|
|
return PSTR("application/x-zip");
|
|
else if (strstr(filename, ".gz"))
|
|
return PSTR("application/x-gzip");
|
|
return PSTR("text/plain");
|
|
}
|
|
|
|
// edit page, in usefull in some situation, but if you need to provide only a web interface, you can disable
|
|
#ifdef INCLUDE_EDIT_HTM
|
|
|
|
/*
|
|
Return the list of files in the directory specified by the "dir" query string parameter.
|
|
Also demonstrates the use of chuncked responses.
|
|
*/
|
|
void FSWebServer::handleFileList()
|
|
{
|
|
if (!webserver->hasArg("dir"))
|
|
{
|
|
return replyToCLient(BAD_REQUEST, "DIR ARG MISSING");
|
|
}
|
|
|
|
String path = webserver->arg("dir");
|
|
DebugPrintln("handleFileList: " + path);
|
|
if (path != "/" && !m_filesystem->exists(path))
|
|
{
|
|
return replyToCLient(BAD_REQUEST, "BAD PATH");
|
|
}
|
|
|
|
File root = m_filesystem->open(path, "r");
|
|
path = String();
|
|
String output;
|
|
output.reserve(256);
|
|
output = "[";
|
|
if (root.isDirectory())
|
|
{
|
|
File file = root.openNextFile();
|
|
while (file)
|
|
{
|
|
String filename = file.name();
|
|
if (filename.lastIndexOf("/") > -1)
|
|
{
|
|
filename.remove(0, filename.lastIndexOf("/") + 1);
|
|
}
|
|
if (output != "[")
|
|
{
|
|
output += ',';
|
|
}
|
|
output += "{\"type\":\"";
|
|
output += (file.isDirectory()) ? "dir" : "file";
|
|
output += "\",\"size\":\"";
|
|
output += file.size();
|
|
output += "\",\"name\":\"";
|
|
output += filename;
|
|
output += "\"}";
|
|
file = root.openNextFile();
|
|
}
|
|
}
|
|
output += "]";
|
|
webserver->send(200, "text/json", output);
|
|
}
|
|
|
|
/*
|
|
Handle the creation/rename of a new file
|
|
Operation | req.responseText
|
|
---------------+--------------------------------------------------------------
|
|
Create file | parent of created file
|
|
Create folder | parent of created folder
|
|
Rename file | parent of source file
|
|
Move file | parent of source file, or remaining ancestor
|
|
Rename folder | parent of source folder
|
|
Move folder | parent of source folder, or remaining ancestor
|
|
*/
|
|
void FSWebServer::handleFileCreate()
|
|
{
|
|
String path = webserver->arg("path");
|
|
if (path.isEmpty())
|
|
{
|
|
replyToCLient(BAD_REQUEST, PSTR("PATH ARG MISSING"));
|
|
return;
|
|
}
|
|
if (path == "/")
|
|
{
|
|
replyToCLient(BAD_REQUEST, PSTR("BAD PATH"));
|
|
return;
|
|
}
|
|
|
|
String src = webserver->arg("src");
|
|
if (src.isEmpty())
|
|
{
|
|
// No source specified: creation
|
|
DebugPrintf_P(PSTR("handleFileCreate: %s\n"), path.c_str());
|
|
if (path.endsWith("/"))
|
|
{
|
|
// Create a folder
|
|
path.remove(path.length() - 1);
|
|
if (!m_filesystem->mkdir(path))
|
|
{
|
|
replyToCLient(ERROR, PSTR("MKDIR FAILED"));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create a file
|
|
File file = m_filesystem->open(path, "w");
|
|
if (file)
|
|
{
|
|
file.write(0);
|
|
file.close();
|
|
}
|
|
else
|
|
{
|
|
replyToCLient(ERROR, PSTR("CREATE FAILED"));
|
|
return;
|
|
}
|
|
}
|
|
replyToCLient(CUSTOM, path.c_str());
|
|
}
|
|
else
|
|
{
|
|
// Source specified: rename
|
|
if (src == "/")
|
|
{
|
|
replyToCLient(BAD_REQUEST, PSTR("BAD SRC"));
|
|
return;
|
|
}
|
|
if (!m_filesystem->exists(src))
|
|
{
|
|
replyToCLient(BAD_REQUEST, PSTR("BSRC FILE NOT FOUND"));
|
|
return;
|
|
}
|
|
|
|
DebugPrintf_P(PSTR("handleFileCreate: %s from %s\n"), path.c_str(), src.c_str());
|
|
if (path.endsWith("/"))
|
|
{
|
|
path.remove(path.length() - 1);
|
|
}
|
|
if (src.endsWith("/"))
|
|
{
|
|
src.remove(src.length() - 1);
|
|
}
|
|
if (!m_filesystem->rename(src, path))
|
|
{
|
|
replyToCLient(ERROR, PSTR("RENAME FAILED"));
|
|
return;
|
|
}
|
|
replyOK();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Handle a file deletion request
|
|
Operation | req.responseText
|
|
---------------+--------------------------------------------------------------
|
|
Delete file | parent of deleted file, or remaining ancestor
|
|
Delete folder | parent of deleted folder, or remaining ancestor
|
|
*/
|
|
void FSWebServer::handleFileDelete()
|
|
{
|
|
|
|
String path = webserver->arg(0);
|
|
if (path.isEmpty() || path == "/")
|
|
{
|
|
replyToCLient(BAD_REQUEST, PSTR("BAD PATH"));
|
|
return;
|
|
}
|
|
|
|
DebugPrintf_P(PSTR("handleFileDelete: %s\n"), path.c_str());
|
|
if (!m_filesystem->exists(path))
|
|
{
|
|
replyToCLient(NOT_FOUND, PSTR(FILE_NOT_FOUND));
|
|
return;
|
|
}
|
|
// deleteRecursive(path);
|
|
File root = m_filesystem->open(path, "r");
|
|
// If it's a plain file, delete it
|
|
if (!root.isDirectory())
|
|
{
|
|
root.close();
|
|
m_filesystem->remove(path);
|
|
replyOK();
|
|
}
|
|
else
|
|
{
|
|
m_filesystem->rmdir(path);
|
|
replyOK();
|
|
}
|
|
}
|
|
|
|
/*
|
|
This specific handler returns the edit.htm (or a gzipped version) from the /edit folder.
|
|
If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version
|
|
embedded in the program code.
|
|
Otherwise, fails with a 404 page with debug information
|
|
*/
|
|
void FSWebServer::handleGetEdit()
|
|
{
|
|
#ifdef INCLUDE_EDIT_HTM
|
|
webserver->sendHeader(PSTR("Content-Encoding"), "gzip");
|
|
webserver->send_P(200, "text/html", edit_htm_gz, sizeof(edit_htm_gz));
|
|
#else
|
|
replyToCLient(NOT_FOUND, PSTR("FILE_NOT_FOUND"));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
Return the FS type, status and size info
|
|
*/
|
|
void FSWebServer::handleStatus()
|
|
{
|
|
DebugPrintln(PSTR("handleStatus"));
|
|
|
|
size_t totalBytes = 1024;
|
|
size_t usedBytes = 0;
|
|
|
|
#ifdef ESP8266
|
|
FSInfo fs_info;
|
|
m_filesystem->info(fs_info);
|
|
totalBytes = fs_info.totalBytes;
|
|
usedBytes = fs_info.usedBytes;
|
|
#elif defined(ESP32)
|
|
totalBytes = LittleFS.totalBytes();
|
|
usedBytes = LittleFS.usedBytes();
|
|
#endif
|
|
|
|
String json;
|
|
json.reserve(128);
|
|
json = "{\"type\":\"Filesystem\", \"isOk\":";
|
|
if (m_fsOK)
|
|
{
|
|
json += PSTR("\"true\", \"totalBytes\":\"");
|
|
json += totalBytes;
|
|
json += PSTR("\", \"usedBytes\":\"");
|
|
json += usedBytes;
|
|
json += "\"";
|
|
}
|
|
else
|
|
json += "\"false\"";
|
|
json += PSTR(",\"unsupportedFiles\":\"\"}");
|
|
webserver->send(200, "application/json", json);
|
|
}
|
|
|
|
#endif // INCLUDE_EDIT_HTM
|