diff --git a/data/edit.htm b/data/edit.htm
new file mode 100644
index 00000000..1ebb989d
--- /dev/null
+++ b/data/edit.htm
@@ -0,0 +1,658 @@
+
+
+
+
+
+ FS Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/include/FSEditor.h b/include/FSEditor.h
new file mode 100644
index 00000000..394fb0e2
--- /dev/null
+++ b/include/FSEditor.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include
+#include
+
+#ifdef ESP8266
+#include
+#endif
+
+class FSEditor : public AsyncWebHandler {
+ private:
+ fs::FS _fs;
+ String _username;
+ String _password;
+ bool _authenticated;
+ uint32_t _startTime;
+
+ public:
+#ifdef ESP32
+ FSEditor(const fs::FS& fs, const String& username = String(), const String& password = String());
+#else
+ FSEditor(const String& username = String(), const String& password = String(), const fs::FS& fs = LittleFS);
+#endif
+ virtual bool canHandle(AsyncWebServerRequest* request) override final;
+ virtual void handleRequest(AsyncWebServerRequest* request) override final;
+ virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final;
+ virtual bool isRequestHandlerTrivial() override final { return false; }
+};
\ No newline at end of file
diff --git a/src/FSEditor.cpp b/src/FSEditor.cpp
new file mode 100644
index 00000000..bdaa5129
--- /dev/null
+++ b/src/FSEditor.cpp
@@ -0,0 +1,326 @@
+#include "FSEditor.h"
+
+#ifdef ESP32
+#include "LITTLEFS.h"
+#define LittleFS LITTLEFS
+#endif
+#ifdef ESP8266
+#include
+#endif
+
+#define FS_MAXLENGTH_FILEPATH 32
+
+const char *excludeListFile = "/.exclude.files";
+
+typedef struct ExcludeListS {
+ char *item;
+ ExcludeListS *next;
+} ExcludeList;
+
+static ExcludeList *excludes = NULL;
+
+static bool matchWild(const char *pattern, const char *testee) {
+ const char *nxPat = NULL, *nxTst = NULL;
+
+ while (*testee) {
+ if ((*pattern == '?') || (*pattern == *testee)) {
+ pattern++;
+ testee++;
+ continue;
+ }
+ if (*pattern == '*') {
+ nxPat = pattern++;
+ nxTst = testee;
+ continue;
+ }
+ if (nxPat) {
+ pattern = nxPat + 1;
+ testee = ++nxTst;
+ continue;
+ }
+ return false;
+ }
+ while (*pattern == '*') {
+ pattern++;
+ }
+ return (*pattern == 0);
+}
+
+static bool addExclude(const char *item) {
+ size_t len = strlen(item);
+ if (!len) {
+ return false;
+ }
+ ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList));
+ if (!e) {
+ return false;
+ }
+ e->item = (char *)malloc(len + 1);
+ if (!e->item) {
+ free(e);
+ return false;
+ }
+ memcpy(e->item, item, len + 1);
+ e->next = excludes;
+ excludes = e;
+ return true;
+}
+
+static void loadExcludeList(fs::FS &_fs, const char *filename) {
+ static char linebuf[FS_MAXLENGTH_FILEPATH];
+ fs::File excludeFile = _fs.open(filename, "r");
+ if (!excludeFile) {
+ return;
+ }
+ if (excludeFile.isDirectory()) {
+ excludeFile.close();
+ return;
+ }
+ if (excludeFile.size() > 0) {
+ uint8_t idx;
+ bool isOverflowed = false;
+ while (excludeFile.available()) {
+ linebuf[0] = '\0';
+ idx = 0;
+ int lastChar;
+ do {
+ lastChar = excludeFile.read();
+ if (lastChar != '\r') {
+ linebuf[idx++] = (char)lastChar;
+ }
+ } while ((lastChar >= 0) && (lastChar != '\n') && (idx < FS_MAXLENGTH_FILEPATH));
+
+ if (isOverflowed) {
+ isOverflowed = (lastChar != '\n');
+ continue;
+ }
+ isOverflowed = (idx >= FS_MAXLENGTH_FILEPATH);
+ linebuf[idx - 1] = '\0';
+ if (!addExclude(linebuf)) {
+ excludeFile.close();
+ return;
+ }
+ }
+ }
+ excludeFile.close();
+}
+
+static bool isExcluded(fs::FS &_fs, const char *filename) {
+ if (excludes == NULL) {
+ loadExcludeList(_fs, excludeListFile);
+ }
+ if (strcmp(excludeListFile, filename) == 0) return true;
+ ExcludeList *e = excludes;
+ while (e) {
+ if (matchWild(e->item, filename)) {
+ return true;
+ }
+ e = e->next;
+ }
+ return false;
+}
+
+// WEB HANDLER IMPLEMENTATION
+
+#ifdef ESP32
+FSEditor::FSEditor(const fs::FS &fs, const String &username, const String &password)
+#else
+FSEditor::FSEditor(const String &username, const String &password, const fs::FS &fs)
+#endif
+ : _fs(fs), _username(username), _password(password), _authenticated(false), _startTime(0) {
+}
+
+bool FSEditor::canHandle(AsyncWebServerRequest *request) {
+ if (request->url().equalsIgnoreCase("/edit")) {
+ if (request->method() == HTTP_GET) {
+ if (request->hasParam("list"))
+ return true;
+ if (request->hasParam("edit")) {
+ request->_tempFile = _fs.open(request->arg("edit"), "r");
+ if (!request->_tempFile) {
+ return false;
+ }
+ if (request->_tempFile.isDirectory()) {
+ request->_tempFile.close();
+ return false;
+ }
+ }
+ if (request->hasParam("download")) {
+ request->_tempFile = _fs.open(request->arg("download"), "r");
+ if (!request->_tempFile) {
+ return false;
+ }
+ if (request->_tempFile.isDirectory()) {
+ request->_tempFile.close();
+ return false;
+ }
+ }
+ request->addInterestingHeader("If-Modified-Since");
+ return true;
+ } else if (request->method() == HTTP_POST)
+ return true;
+ else if (request->method() == HTTP_DELETE)
+ return true;
+ else if (request->method() == HTTP_PUT)
+ return true;
+ }
+ return false;
+}
+
+void FSEditor::handleRequest(AsyncWebServerRequest *request) {
+ if (_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str()))
+ return request->requestAuthentication();
+
+ if (request->method() == HTTP_GET) {
+ if (request->hasParam("list")) {
+ String path = request->getParam("list")->value();
+#ifdef ESP32
+ File dir = _fs.open(path);
+#else
+ fs::Dir dir = _fs.openDir(path);
+#endif
+ path = String();
+ String output = "[";
+#ifdef ESP32
+ File entry = dir.openNextFile();
+ while (entry) {
+#else
+ while (dir.next()) {
+ fs::File entry = dir.openFile("r");
+#endif
+ String fname = entry.fullName();
+ if (fname.charAt(0) != '/') fname = "/" + fname;
+
+ if (isExcluded(_fs, fname.c_str())) {
+#ifdef ESP32
+ entry = dir.openNextFile();
+#endif
+ continue;
+ }
+ if (output != "[") output += ',';
+ output += "{\"type\":\"";
+ output += "file";
+ output += "\",\"name\":\"";
+ output += String(fname);
+ output += "\",\"size\":";
+ output += String(entry.size());
+ output += "}";
+#ifdef ESP32
+ entry = dir.openNextFile();
+#else
+ entry.close();
+#endif
+ }
+#ifdef ESP32
+ dir.close();
+#endif
+ output += "]";
+ request->send(200, "application/json", output);
+ output = String();
+ } else if (request->hasParam("edit") || request->hasParam("download")) {
+ request->send(request->_tempFile, request->_tempFile.fullName(), String(), request->hasParam("download"));
+ } else {
+ const char *buildTime = __DATE__ " " __TIME__ " GMT";
+ if (request->header("If-Modified-Since").equals(buildTime)) {
+ request->send(304);
+ } else {
+ AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/edit.htm", "text/html");
+ // response->addHeader("Content-Encoding", "gzip");
+ response->addHeader("Last-Modified", buildTime);
+ request->send(response);
+ }
+ }
+ } else if (request->method() == HTTP_DELETE) {
+ if (request->hasParam("path", true)) {
+ if (!(_fs.remove(request->getParam("path", true)->value()))) {
+#ifdef ESP32
+ _fs.rmdir(request->getParam("path", true)->value()); // try rmdir for littlefs
+#endif
+ }
+
+ request->send(200, "", "DELETE: " + request->getParam("path", true)->value());
+ } else
+ request->send(404);
+ } else if (request->method() == HTTP_POST) {
+ if (request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value()))
+ request->send(200, "", "UPLOADED: " + request->getParam("data", true, true)->value());
+
+ else if (request->hasParam("rawname", true) && request->hasParam("raw0", true)) {
+ String rawnam = request->getParam("rawname", true)->value();
+
+ if (_fs.exists(rawnam)) _fs.remove(rawnam); // delete it to allow a mode
+
+ int k = 0;
+ uint16_t i = 0;
+ fs::File f = _fs.open(rawnam, "a");
+
+ while (request->hasParam("raw" + String(k), true)) { //raw0 .. raw1
+ if (f) {
+ i += f.print(request->getParam("raw" + String(k), true)->value());
+ }
+ k++;
+ }
+ f.close();
+ request->send(200, "", "IPADWRITE: " + rawnam + ":" + String(i));
+
+ } else {
+ request->send(500);
+ }
+
+ } else if (request->method() == HTTP_PUT) {
+ if (request->hasParam("path", true)) {
+ String filename = request->getParam("path", true)->value();
+ if (_fs.exists(filename)) {
+ request->send(200);
+ } else {
+/*******************************************************/
+#ifdef ESP32
+ if (strchr(filename.c_str(), '/')) {
+ // For file creation, silently make subdirs as needed. If any fail,
+ // it will be caught by the real file open later on
+ char *pathStr = strdup(filename.c_str());
+ if (pathStr) {
+ // Make dirs up to the final fnamepart
+ char *ptr = strchr(pathStr, '/');
+ while (ptr) {
+ *ptr = 0;
+ _fs.mkdir(pathStr);
+ *ptr = '/';
+ ptr = strchr(ptr + 1, '/');
+ }
+ }
+ free(pathStr);
+ }
+#endif
+ /*******************************************************/
+ fs::File f = _fs.open(filename, "w");
+ if (f) {
+ f.write((uint8_t)0x00);
+ f.close();
+ request->send(200, "", "CREATE: " + filename);
+ } else {
+ request->send(500);
+ }
+ }
+ } else
+ request->send(400);
+ }
+}
+
+void FSEditor::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
+ if (!index) {
+ if (!_username.length() || request->authenticate(_username.c_str(), _password.c_str())) {
+ _authenticated = true;
+ request->_tempFile = _fs.open(filename, "w");
+ _startTime = millis();
+ }
+ }
+ if (_authenticated && request->_tempFile) {
+ if (len) {
+ request->_tempFile.write(data, len);
+ }
+ if (final) {
+ request->_tempFile.close();
+ }
+ }
+}
diff --git a/src/WebServer.cpp b/src/WebServer.cpp
index 78e898a4..cf4ef379 100644
--- a/src/WebServer.cpp
+++ b/src/WebServer.cpp
@@ -2,6 +2,7 @@
#include "Utils/FileUtils.h"
#include "Utils/WebUtils.h"
+#include "FSEditor.h"
namespace HttpServer {
@@ -15,9 +16,9 @@ void init() {
String login = jsonReadStr(configSetupJson, "weblogin");
String pass = jsonReadStr(configSetupJson, "webpass");
#ifdef ESP32
- server.addHandler(new SPIFFSEditor(LittleFS, login, pass));
+ server.addHandler(new FSEditor(LittleFS, login, pass));
#else
- server.addHandler(new SPIFFSEditor(login, pass));
+ server.addHandler(new FSEditor(login, pass));
#endif
server.serveStatic("/css/", LittleFS, "/css/").setCacheControl("max-age=600");