Новый браузер /edit. Основное: легче, папки, вид

This commit is contained in:
Mit4el
2023-04-27 01:29:23 +03:00
parent 80f8a917cd
commit 4342269e44
4 changed files with 619 additions and 159 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -4,12 +4,17 @@
#ifdef STANDARD_WEB_SERVER #ifdef STANDARD_WEB_SERVER
extern void standWebServerInit(); extern void standWebServerInit();
extern bool handleFileRead(String path); extern bool handleFileRead(String path);
extern String getContentType(String filename); //extern String getContentType(String filename);
//#ifdef REST_FILE_OPERATIONS //#ifdef REST_FILE_OPERATIONS
extern void handleFileUpload(); extern void handleFileUpload();
extern void handleFileDelete(); extern void handleFileDelete();
extern void handleFileCreate(); extern void handleFileCreate();
extern void handleFileList(); extern void handleFileList();
void printDirectory(File dir, String& out); //void printDirectory(File dir, String& out);
extern void handleStatus();
extern void handleGetEdit();
extern void replyOK ();
extern void handleNotFound ();
//#endif //#endif
#endif #endif

View File

@@ -1,9 +1,17 @@
#include "StandWebServer.h" #include "StandWebServer.h"
#ifdef STANDARD_WEB_SERVER #ifdef STANDARD_WEB_SERVER
File fsUploadFile; File uploadFile;
String unsupportedFiles = String();
void standWebServerInit() { static const char TEXT_PLAIN[] PROGMEM = "text/plain";
static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR";
static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound";
//static bool fsOK;
//const char* fsName = "LittleFS";
void standWebServerInit()
{
// Кэшировать файлы для быстрой работы // Кэшировать файлы для быстрой работы
HTTP.serveStatic("/bundle.js", FileFS, "/", "max-age=31536000"); // кеширование на 1 год HTTP.serveStatic("/bundle.js", FileFS, "/", "max-age=31536000"); // кеширование на 1 год
HTTP.serveStatic("/bundle.css", FileFS, "/", "max-age=31536000"); // кеширование на 1 год HTTP.serveStatic("/bundle.css", FileFS, "/", "max-age=31536000"); // кеширование на 1 год
@@ -60,193 +68,640 @@ void standWebServerInit() {
// Запускаем HTTP сервер // Запускаем HTTP сервер
HTTP.begin(); HTTP.begin();
//#ifdef REST_FILE_OPERATIONS
// SPIFFS.begin();
// {
// Dir dir = SPIFFS.openDir("/");
// while (dir.next()) {
// String fileName = dir.fileName();
// size_t fileSize = dir.fileSize();
// }
// }
// HTTP страницы для работы с FFS // HTTP страницы для работы с FFS
// list directory ////////////////////////////////
// WEB SERVER INIT
// Filesystem status
HTTP.on("/status", HTTP_GET, handleStatus);
// List directory
HTTP.on("/list", HTTP_GET, handleFileList); HTTP.on("/list", HTTP_GET, handleFileList);
//загрузка редактора editor // Load editor
HTTP.on("/edit", HTTP_GET, []() { HTTP.on("/edit", HTTP_GET, handleGetEdit);
if (!HTTP.args()) {
if (!handleFileRead("/edit.htm")) HTTP.send(404, "text/plain", "FileNotFound");
}
if (HTTP.hasArg("list")) { // Create file
handleFileList();
}
if (HTTP.hasArg("edit")) {
if (!handleFileRead(HTTP.arg("edit"))) HTTP.send(404, "text/plain", "FileNotFound");
}
if (HTTP.hasArg("download")) {
if (!handleFileRead(HTTP.arg("download"))) HTTP.send(404, "text/plain", "FileNotFound");
}
});
//Создание файла
HTTP.on("/edit", HTTP_PUT, handleFileCreate); HTTP.on("/edit", HTTP_PUT, handleFileCreate);
//Удаление файла // Delete file
HTTP.on("/edit", HTTP_DELETE, handleFileDelete); HTTP.on("/edit", HTTP_DELETE, handleFileDelete);
//Изменение файла // Upload file
HTTP.on( // - first callback is called after the request has ended with all parsed arguments
"/edit", HTTP_POST, []() { // - second callback handles file upload at that location
HTTP.send(200, "text/plain", ""); HTTP.on("/edit", HTTP_POST, replyOK, handleFileUpload);
},
handleFileUpload);
// called when the url is not defined here // Default handler for all URIs not defined above
HTTP.onNotFound([]() { // Use it to read files from filesystem
if (!handleFileRead(HTTP.uri())) HTTP.onNotFound(handleNotFound);
HTTP.send(404, "text/plain", "FileNotFound");
});
} }
bool handleFileRead(String path) { ////////////////////////////////
path = "/" + path; // Utils to return HTTP codes, and determine content-type
if (path.endsWith("/")) path += "index.html";
String contentType = getContentType(path); void replyOK()
String pathWithGz = path + ".gz"; {
if (FileFS.exists(pathWithGz) || FileFS.exists(path)) { HTTP.send(200, FPSTR(TEXT_PLAIN), "");
if (FileFS.exists(pathWithGz)) }
path += ".gz";
void replyOKWithMsg(String msg)
{
HTTP.send(200, FPSTR(TEXT_PLAIN), msg);
}
void replyNotFound(String msg)
{
HTTP.send(404, FPSTR(TEXT_PLAIN), msg);
}
void replyBadRequest(String msg)
{
// DBG_OUTPUT_PORT.println(msg);
HTTP.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n");
}
void replyServerError(String msg)
{
// DBG_OUTPUT_PORT.println(msg);
HTTP.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n");
}
/*
Return the FS type, status and size info
*/
void handleStatus()
{
// DBG_OUTPUT_PORT.println("handleStatus");
String json;
json.reserve(128);
json = "{\"type\":\"";
json += FS_NAME;
json += "\", \"isOk\":";
#ifdef ESP8266
FSInfo fs_info;
FileFS.info(fs_info);
json += F("\"true\", \"totalBytes\":\"");
json += fs_info.totalBytes;
json += F("\", \"usedBytes\":\"");
json += fs_info.usedBytes;
json += "\"";
#endif
#ifdef ESP32
json += F("\"true\", \"totalBytes\":\"");
json += String(FileFS.totalBytes());
json += F("\", \"usedBytes\":\"");
json += String(FileFS.usedBytes());
json += "\"";
#endif
json += F(",\"unsupportedFiles\":\"");
json += unsupportedFiles;
json += "\"}";
HTTP.send(200, "application/json", json);
}
#ifdef ESP32
String getContentType(String filename) {
if (HTTP.hasArg("download")) {
return "application/octet-stream";
} else if (filename.endsWith(".htm")) {
return "text/html";
} else if (filename.endsWith(".html")) {
return "text/html";
} else if (filename.endsWith(".css")) {
return "text/css";
} else if (filename.endsWith(".js")) {
return "application/javascript";
} else if (filename.endsWith(".png")) {
return "image/png";
} else if (filename.endsWith(".gif")) {
return "image/gif";
} else if (filename.endsWith(".jpg")) {
return "image/jpeg";
} else if (filename.endsWith(".ico")) {
return "image/x-icon";
} else if (filename.endsWith(".xml")) {
return "text/xml";
} else if (filename.endsWith(".pdf")) {
return "application/x-pdf";
} else if (filename.endsWith(".zip")) {
return "application/x-zip";
} else if (filename.endsWith(".gz")) {
return "application/x-gzip";
}
return "text/plain";
}
#endif
/*
Read the given file from the filesystem and stream it back to the client
*/
bool handleFileRead(String path)
{
// DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path);
if (path.endsWith("/"))
{
path += "index.html";
}
String contentType;
if (HTTP.hasArg("download"))
{
contentType = F("application/octet-stream");
}
else
{
#ifdef ESP32
contentType = getContentType(path);
#endif
#ifdef ESP8266
contentType = mime::getContentType(path);
#endif
}
if (!FileFS.exists(path))
{
// File not found, try gzip version
path = path + ".gz";
}
if (FileFS.exists(path))
{
File file = FileFS.open(path, "r"); File file = FileFS.open(path, "r");
if (contentType == "application/octet-stream") if (HTTP.streamFile(file, contentType) != file.size())
HTTP.sendHeader("Content-Disposition", "attachment;filename=" + (String)file.name()); {
HTTP.streamFile(file, contentType); // DBG_OUTPUT_PORT.println("Sent less data than expected!");
}
file.close(); file.close();
return true; return true;
} }
return false; return false;
} }
String getContentType(String filename) { /*
if (HTTP.hasArg("download")) As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed,
return "application/octet-stream"; return the path of the closest parent still existing
else if (filename.endsWith(".htm")) */
return "text/html"; String lastExistingParent(String path)
else if (filename.endsWith(".html")) {
return "text/html"; while (!path.isEmpty() && !FileFS.exists(path))
else if (filename.endsWith(".txt")) {
return "text/plain"; if (path.lastIndexOf('/') > 0)
else if (filename.endsWith(".json")) {
return "application/json"; path = path.substring(0, path.lastIndexOf('/'));
else if (filename.endsWith(".css")) }
return "text/css"; else
else if (filename.endsWith(".js")) {
return "application/javascript"; path = String(); // No slash => the top folder does not exist
else if (filename.endsWith(".png")) }
return "image/png"; }
else if (filename.endsWith(".gif")) // DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path);
return "image/gif"; return path;
else if (filename.endsWith(".jpg"))
return "image/jpeg";
else if (filename.endsWith(".ico"))
return "image/x-icon";
else if (filename.endsWith(".xml"))
return "text/xml";
else if (filename.endsWith(".pdf"))
return "application/x-pdf";
else if (filename.endsWith(".zip"))
return "application/x-zip";
else if (filename.endsWith(".gz"))
return "application/x-gzip";
return "text/plain";
} }
// Здесь функции для работы с файловой системой /*
void handleFileUpload() { Handle a file upload request
if (HTTP.uri() != "/edit") return; */
HTTPUpload& upload = HTTP.upload(); void handleFileUpload()
if (upload.status == UPLOAD_FILE_START) { {
if (HTTP.uri() != "/edit")
{
return;
}
HTTPUpload &upload = HTTP.upload();
if (upload.status == UPLOAD_FILE_START)
{
String filename = upload.filename; String filename = upload.filename;
if (!filename.startsWith("/")) filename = "/" + filename; // Make sure paths always start with "/"
fsUploadFile = FileFS.open(filename, "w"); if (!filename.startsWith("/"))
filename = String(); {
} else if (upload.status == UPLOAD_FILE_WRITE) { filename = "/" + filename;
// Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize); }
if (fsUploadFile) // DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename);
fsUploadFile.write(upload.buf, upload.currentSize); uploadFile = FileFS.open(filename, "w");
} else if (upload.status == UPLOAD_FILE_END) { if (!uploadFile)
if (fsUploadFile) {
fsUploadFile.close(); return replyServerError(F("CREATE FAILED"));
}
// DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename);
}
else if (upload.status == UPLOAD_FILE_WRITE)
{
if (uploadFile)
{
size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize);
if (bytesWritten != upload.currentSize)
{
return replyServerError(F("WRITE FAILED"));
}
}
// DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize);
}
else if (upload.status == UPLOAD_FILE_END)
{
if (uploadFile)
{
uploadFile.close();
}
// DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize);
} }
} }
void handleFileDelete() {
if (HTTP.args() == 0) return HTTP.send(500, "text/plain", "BAD ARGS"); #ifdef ESP8266
String path = HTTP.arg(0); void deleteRecursive(String path)
if (path == "/") {
return HTTP.send(500, "text/plain", "BAD PATH"); File file = FileFS.open(path, "r");
if (!FileFS.exists(path)) bool isDir = file.isDirectory();
return HTTP.send(404, "text/plain", "FileNotFound"); file.close();
// If it's a plain file, delete it
if (!isDir)
{
FileFS.remove(path); FileFS.remove(path);
HTTP.send(200, "text/plain", ""); return;
path = String(); }
Dir dir = FileFS.openDir(path);
while (dir.next())
{
deleteRecursive(path + '/' + dir.fileName());
}
// Then delete the folder itself
FileFS.rmdir(path);
}
#endif
#ifdef ESP32
struct treename{
uint8_t type;
char *name;
};
void deleteRecursive( String path ){
fs::File dir = FileFS.open( path );
if(!dir.isDirectory()){
Serial.printf("%s is a file\n", path);
dir.close();
Serial.printf( "result of removing file %s: %d\n", path, FileFS.remove( path ) );
return;
}
Serial.printf("%s is a directory\n", path);
fs::File entry, nextentry;
while ( entry = dir.openNextFile() ){
if ( entry.isDirectory() ){
deleteRecursive( entry.path() );
} else{
String tmpname = path+"/"+strdup( entry.name() ); // buffer file name
entry.close();
Serial.printf( "result of removing file %s: %d\n", tmpname, FileFS.remove( tmpname ) );
}
}
dir.close();
Serial.printf( "result of removing directory %s: %d\n", path, FileFS.rmdir( path ) );
}
#endif
/*
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 handleFileDelete()
{
String path = HTTP.arg(0);
if (path.isEmpty() || path == "/")
{
return replyBadRequest("BAD PATH");
}
// DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path);
if (!FileFS.exists(path))
{
return replyNotFound(FPSTR(FILE_NOT_FOUND));
}
deleteRecursive(path);
replyOKWithMsg(lastExistingParent(path));
} }
void handleFileCreate() { /*
if (HTTP.args() == 0) Handle the creation/rename of a new file
return HTTP.send(500, "text/plain", "BAD ARGS"); Operation | req.responseText
String path = HTTP.arg(0); ---------------+--------------------------------------------------------------
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 handleFileCreate()
{
String path = HTTP.arg("path");
if (path.isEmpty())
{
return replyBadRequest(F("PATH ARG MISSING"));
}
#ifdef USE_SPIFFS
if (checkForUnsupportedPath(path).length() > 0)
{
return replyServerError(F("INVALID FILENAME"));
}
#endif
if (path == "/") if (path == "/")
return HTTP.send(500, "text/plain", "BAD PATH"); {
return replyBadRequest("BAD PATH");
}
if (FileFS.exists(path)) if (FileFS.exists(path))
return HTTP.send(500, "text/plain", "FILE EXISTS"); {
return replyBadRequest(F("PATH FILE EXISTS"));
}
String src = HTTP.arg("src");
if (src.isEmpty())
{
// No source specified: creation
// DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path);
if (path.endsWith("/"))
{
// Create a folder
path.remove(path.length() - 1);
if (!FileFS.mkdir(path))
{
return replyServerError(F("MKDIR FAILED"));
}
}
else
{
// Create a file
File file = FileFS.open(path, "w"); File file = FileFS.open(path, "w");
if (file) if (file)
{
#ifdef ESP8266
file.write((const char *)0);
#endif
#ifdef ESP32
file.write(0);
#endif
file.close(); file.close();
}
else else
return HTTP.send(500, "text/plain", "CREATE FAILED"); {
HTTP.send(200, "text/plain", ""); return replyServerError(F("CREATE FAILED"));
path = String(); }
}
if (path.lastIndexOf('/') > -1)
{
path = path.substring(0, path.lastIndexOf('/'));
}
replyOKWithMsg(path);
}
else
{
// Source specified: rename
if (src == "/")
{
return replyBadRequest("BAD SRC");
}
if (!FileFS.exists(src))
{
return replyBadRequest(F("SRC FILE NOT FOUND"));
}
// DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src);
if (path.endsWith("/"))
{
path.remove(path.length() - 1);
}
if (src.endsWith("/"))
{
src.remove(src.length() - 1);
}
if (!FileFS.rename(src, path))
{
return replyServerError(F("RENAME FAILED"));
}
replyOKWithMsg(lastExistingParent(src));
}
} }
void handleFileList() { /*
File dir = FileFS.open("/", "r"); Return the list of files in the directory specified by the "dir" query string parameter.
String output = "["; Also demonstrates the use of chunked responses.
File entry; */
while (entry = dir.openNextFile()) { #ifdef ESP8266
if (output != "[") output += ','; void handleFileList()
bool isDir = entry.isDirectory(); {
if (!HTTP.hasArg("dir"))
{
return replyBadRequest(F("DIR ARG MISSING"));
}
String path = HTTP.arg("dir");
if (path != "/" && !FileFS.exists(path))
{
return replyBadRequest("BAD PATH");
}
// DBG_OUTPUT_PORT.println(String("handleFileList: ") + path);
Dir dir = FileFS.openDir(path);
path.clear();
// use HTTP/1.1 Chunked response to avoid building a huge temporary string
if (!HTTP.chunkedResponseModeStart(200, "text/json"))
{
HTTP.send(505, F("text/html"), F("HTTP1.1 required"));
return;
}
// use the same string for every line
String output;
output.reserve(64);
while (dir.next())
{
#ifdef USE_SPIFFS
String error = checkForUnsupportedPath(dir.fileName());
if (error.length() > 0)
{
// DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName());
continue;
}
#endif
if (output.length())
{
// send string from previous iteration
// as an HTTP chunk
HTTP.sendContent(output);
output = ',';
}
else
{
output = '[';
}
output += "{\"type\":\""; output += "{\"type\":\"";
output += (isDir) ? "dir" : "file"; if (dir.isDirectory())
output += "\",\"name\":\""; {
output += String(entry.name()); output += "dir";
}
else
{
output += F("file\",\"size\":\"");
output += dir.fileSize();
}
output += F("\",\"name\":\"");
// Always return names without leading "/"
if (dir.fileName()[0] == '/')
{
output += &(dir.fileName()[1]);
}
else
{
output += dir.fileName();
}
output += "\"}"; output += "\"}";
entry.close(); }
// send last string
output += "]";
HTTP.sendContent(output);
HTTP.chunkedResponseFinalize();
}
#endif
#ifdef ESP32
void handleFileList() {
if (!HTTP.hasArg("dir")) {
HTTP.send(500, "text/plain", "BAD ARGS");
return;
}
String path = HTTP.arg("dir");
// DBG_OUTPUT_PORT.println("handleFileList: " + path);
File root = FileFS.open(path);
path = String();
String output = "[";
if(root.isDirectory()){
File file = root.openNextFile();
while(file){
if (output != "[") {
output += ',';
}
output += "{\"type\":\"";
// output += (file.isDirectory()) ? "dir" : "file";
if (file.isDirectory())
{
output += "dir";
}
else
{
output += F("file\",\"size\":\"");
output += file.size();
}
output += "\",\"name\":\"";
output += String(file.name());
output += "\"}";
file = root.openNextFile();
}
} }
output += "]"; output += "]";
//Serial.println(output);
HTTP.send(200, "text/json", output); HTTP.send(200, "text/json", output);
}
#endif
/*
The "Not Found" handler catches all URI not explicitly declared in code
First try to find and return the requested file from the filesystem,
and if it fails, return a 404 page with debug information
*/
void handleNotFound()
{
#ifdef ESP8266
String uri = ESP8266WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks
#endif
#ifdef ESP32
String uri = WebServer::urlDecode(HTTP.uri()); // required to read paths with blanks
#endif
if (handleFileRead(uri))
{
return;
}
// Dump debug data
String message;
message.reserve(100);
message = F("Error: File not found\n\nURI: ");
message += uri;
message += F("\nMethod: ");
message += (HTTP.method() == HTTP_GET) ? "GET" : "POST";
message += F("\nArguments: ");
message += HTTP.args();
message += '\n';
for (uint8_t i = 0; i < HTTP.args(); i++)
{
message += F(" NAME:");
message += HTTP.argName(i);
message += F("\n VALUE:");
message += HTTP.arg(i);
message += '\n';
}
message += "path=";
message += HTTP.arg("path");
message += '\n';
// DBG_OUTPUT_PORT.print(message);
return replyNotFound(message);
} }
void printDirectory(File dir, String& out) { /*
while (true) { This specific handler returns the index.htm (or a gzipped version) from the /edit folder.
File entry = dir.openNextFile(); If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version
if (!entry) { embedded in the program code.
break; Otherwise, fails with a 404 page with debug information
} */
if (entry.isDirectory()) { void handleGetEdit()
out += entry.name(); {
out += "/"; if (handleFileRead(F("/edit.htm")))
printDirectory(entry, out); {
} else { return;
out += entry.name();
out += "\r\n";
}
} }
#ifdef INCLUDE_FALLBACK_INDEX_HTM
server.sendHeader(F("Content-Encoding"), "gzip");
server.send(200, "text/html", index_htm_gz, index_htm_gz_len);
#else
replyNotFound(FPSTR(FILE_NOT_FOUND));
#endif
} }
#endif #endif