336 lines
8.8 KiB
C++
336 lines
8.8 KiB
C++
|
#include "SPIFFSEditor.h"
|
||
|
#include <FS.h>
|
||
|
|
||
|
#include "edit.htm.gz.h"
|
||
|
|
||
|
#ifdef ESP32
|
||
|
#define fullName(x) name(x)
|
||
|
#endif
|
||
|
|
||
|
#define SPIFFS_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[SPIFFS_MAXLENGTH_FILEPATH];
|
||
|
fs::File excludeFile=_fs.open(filename, "r");
|
||
|
if(!excludeFile){
|
||
|
//addExclude("/*.js.gz");
|
||
|
return;
|
||
|
}
|
||
|
#ifdef ESP32
|
||
|
if(excludeFile.isDirectory()){
|
||
|
excludeFile.close();
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
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 < SPIFFS_MAXLENGTH_FILEPATH));
|
||
|
|
||
|
if(isOverflowed){
|
||
|
isOverflowed = (lastChar != '\n');
|
||
|
continue;
|
||
|
}
|
||
|
isOverflowed = (idx >= SPIFFS_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);
|
||
|
}
|
||
|
ExcludeList *e = excludes;
|
||
|
while(e){
|
||
|
if (matchWild(e->item, filename)){
|
||
|
return true;
|
||
|
}
|
||
|
e = e->next;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// WEB HANDLER IMPLEMENTATION
|
||
|
|
||
|
#ifdef ESP32
|
||
|
SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password)
|
||
|
#else
|
||
|
SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs)
|
||
|
#endif
|
||
|
:_fs(fs)
|
||
|
,_username(username)
|
||
|
,_password(password)
|
||
|
,_authenticated(false)
|
||
|
,_startTime(0)
|
||
|
{}
|
||
|
|
||
|
bool SPIFFSEditor::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;
|
||
|
}
|
||
|
#ifdef ESP32
|
||
|
if(request->_tempFile.isDirectory()){
|
||
|
request->_tempFile.close();
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
if(request->hasParam("download")){
|
||
|
request->_tempFile = _fs.open(request->arg("download"), "r");
|
||
|
if(!request->_tempFile){
|
||
|
return false;
|
||
|
}
|
||
|
#ifdef ESP32
|
||
|
if(request->_tempFile.isDirectory()){
|
||
|
request->_tempFile.close();
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
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 SPIFFSEditor::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_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
|
||
|
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 SPIFFSEditor::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();
|
||
|
}
|
||
|
}
|
||
|
}
|