esp-fs-webserver
Loading...
Searching...
No Matches
esp-fs-webserver.cpp
Go to the documentation of this file.
1#include "esp-fs-webserver.h"
2
3FSWebServer::FSWebServer(fs::FS &fs, WebServerClass &server)
4{
5 m_filesystem = &fs;
6 webserver = &server;
7 m_basePath[0] = '\0';
8}
9
10WebServerClass *FSWebServer::getRequest()
11{
12 return webserver;
13}
14
16{
17 webserver->handleClient();
18 if (m_apmode)
19 m_dnsServer.processNextRequest();
20}
21
22void FSWebServer::addHandler(const Uri &uri, HTTPMethod method, WebServerClass::THandlerFunction fn)
23{
24 webserver->on(uri, method, fn);
25}
26
27void FSWebServer::addHandler(const Uri &uri, WebServerClass::THandlerFunction handler)
28{
29 webserver->on(uri, HTTP_ANY, handler);
30}
31
32// List all files saved in the selected filesystem
33bool FSWebServer::checkDir(char *dirname, uint8_t levels)
34{
35 if (dirname[0] != '/')
36 dirname[0] = '/';
37 File root = m_filesystem->open(dirname, "r");
38 if (!root)
39 {
40 DebugPrintln("- failed to open directory\n");
41 return false;
42 }
43 if (!root.isDirectory())
44 {
45 DebugPrintln(" - not a directory\n");
46 return false;
47 }
48 File file = root.openNextFile();
49 while (file)
50 {
51 if (file.isDirectory())
52 {
53 char dir[16];
54 strcpy(dir, "/");
55 strcat(dir, file.name());
56 DebugPrintf("DIR : %s\n", dir);
57 checkDir(dir, levels - 1);
58 }
59 else
60 {
61 DebugPrintf(" FILE: %s\tSIZE: %d\n", file.name(), file.size());
62 }
63 file = root.openNextFile();
64 }
65 return true;
66}
67
68bool FSWebServer::begin(const char *path)
69{
70 DebugPrintln("\nList the files of webserver: ");
71 if (path != nullptr)
72 strcpy(m_basePath, path);
73
74 m_fsOK = checkDir(m_basePath, 2);
75
76#ifdef INCLUDE_EDIT_HTM
77 webserver->on("/status", HTTP_GET, std::bind(&FSWebServer::handleStatus, this));
78 webserver->on("/list", HTTP_GET, std::bind(&FSWebServer::handleFileList, this));
79 webserver->on("/edit", HTTP_GET, std::bind(&FSWebServer::handleGetEdit, this));
80 webserver->on("/edit", HTTP_PUT, std::bind(&FSWebServer::handleFileCreate, this));
81 webserver->on("/edit", HTTP_DELETE, std::bind(&FSWebServer::handleFileDelete, this));
82#endif
83 webserver->onNotFound(std::bind(&FSWebServer::handleRequest, this));
84 webserver->on("/favicon.ico", HTTP_GET, std::bind(&FSWebServer::replyOK, this));
85 webserver->on("/", HTTP_GET, std::bind(&FSWebServer::handleIndex, this));
86#ifdef INCLUDE_SETUP_HTM
87 webserver->on("/setup", HTTP_GET, std::bind(&FSWebServer::handleSetup, this));
88#endif
89 webserver->on("/scan", HTTP_GET, std::bind(&FSWebServer::handleScanNetworks, this));
90 webserver->on("/connect", HTTP_POST, std::bind(&FSWebServer::doWifiConnection, this));
91 webserver->on("/restart", HTTP_GET, std::bind(&FSWebServer::doRestart, this));
92 webserver->on("/ipaddress", HTTP_GET, std::bind(&FSWebServer::getIpAddress, this));
93
94 // Captive Portal redirect
95 webserver->on("/redirect", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
96 // Windows
97 webserver->on("/connecttest.txt", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
98 // Apple
99 webserver->on("/hotspot-detect.html", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
100 // Android
101 webserver->on("/generate_204", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
102 webserver->on("/gen_204", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
103
104 // Upload file
105 // - first callback is called after the request has ended with all parsed arguments
106 // - second callback handles file upload at that location
107 webserver->on("/edit", HTTP_POST, std::bind(&FSWebServer::replyOK, this), std::bind(&FSWebServer::handleFileUpload, this));
108
109 // OTA update via webbrowser
110 m_httpUpdater.setup(webserver);
111
112#ifdef ESP32
113 webserver->enableCrossOrigin(true);
114#endif
115 webserver->setContentLength(50);
116 webserver->begin();
117
118 return true;
119}
120
121void FSWebServer::setCaptiveWebage(const char *url)
122{
123 m_apWebpage = (char *)realloc(m_apWebpage, sizeof(url));
124 strcpy(m_apWebpage, url);
125}
126
127IPAddress FSWebServer::setAPmode(const char *ssid, const char *psk)
128{
129 m_apmode = true;
130 WiFi.mode(WIFI_AP_STA);
131 WiFi.persistent(false);
132 WiFi.softAP(ssid, psk);
133 /* Setup the DNS server redirecting all the domains to the apIP */
134 m_dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
135 m_dnsServer.start(53, "*", WiFi.softAPIP());
136 return WiFi.softAPIP();
137}
138
139IPAddress FSWebServer::startWiFi(uint32_t timeout, const char *apSSID, const char *apPsw)
140{
141 IPAddress ip;
142 m_timeout = timeout;
143 WiFi.mode(WIFI_STA);
144
145 const char *_ssid;
146 const char *_pass;
147#if defined(ESP8266)
148 struct station_config conf;
149 wifi_station_get_config_default(&conf);
150 _ssid = reinterpret_cast<const char *>(conf.ssid);
151 _pass = reinterpret_cast<const char *>(conf.password);
152
153#elif defined(ESP32)
154 wifi_config_t conf;
155 esp_wifi_get_config(WIFI_IF_STA, &conf);
156
157 _ssid = reinterpret_cast<const char *>(conf.sta.ssid);
158 _pass = reinterpret_cast<const char *>(conf.sta.password);
159#endif
160
161 if (strlen(_ssid) && strlen(_pass))
162 {
163 WiFi.begin(_ssid, _pass);
164 Serial.print(F("Connecting to "));
165 Serial.println(_ssid);
166 uint32_t startTime = millis();
167 while (WiFi.status() != WL_CONNECTED)
168 {
169 delay(500);
170 Serial.print(".");
171 if (WiFi.status() == WL_CONNECTED)
172 {
173 ip = WiFi.localIP();
174 return ip;
175 }
176 // If no connection after a while go in Access Point mode
177 if (millis() - startTime > m_timeout)
178 break;
179 }
180 }
181
182 if (apSSID != nullptr && apPsw != nullptr)
183 setAPmode(apSSID, apPsw);
184 else
185 setAPmode("ESP_AP", "123456789");
186
187 WiFi.begin();
188 ip = WiFi.softAPIP();
189 Serial.print(F("\nAP mode.\nServer IP address: "));
190 Serial.println(ip);
191 Serial.println();
192 return ip;
193}
194
196
200bool FSWebServer::captivePortal()
201{
202 IPAddress ip = webserver->client().localIP();
203 char serverLoc[sizeof("https:://255.255.255.255/") + sizeof(m_apWebpage) + 1];
204 snprintf(serverLoc, sizeof(serverLoc), "http://%d.%d.%d.%d%s", ip[0], ip[1], ip[2], ip[3], m_apWebpage);
205
206 // redirect if hostheader not server ip, prevent redirect loops
207 if (strcmp(serverLoc, webserver->hostHeader().c_str()))
208 {
209 webserver->sendHeader(F("Location"), serverLoc, true);
210 webserver->send(302, F("text/html"), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
211 webserver->client().stop(); // Stop is needed because we sent no content length
212 return true;
213 }
214 return false;
215}
216
217void FSWebServer::handleRequest()
218{
219 if (!m_fsOK)
220 {
221 replyToCLient(ERROR, PSTR(FS_INIT_ERROR));
222 return;
223 }
224#if defined(ESP32)
225 String _url = WebServer::urlDecode(webserver->uri());
226#elif defined(ESP8266)
227 String _url = ESP8266WebServer::urlDecode(webserver->uri());
228#endif
229 // First try to find and return the requested file from the filesystem,
230 // and if it fails, return a 404 page with debug information
231 // Serial.print("urlDecode: ");
232 // Serial.println(_url);
233 if (handleFileRead(_url))
234 return;
235 else
236 replyToCLient(NOT_FOUND, PSTR(FILE_NOT_FOUND));
237}
238
239void FSWebServer::getIpAddress()
240{
241 webserver->send(200, "text/json", WiFi.localIP().toString());
242}
243
244void FSWebServer::doRestart()
245{
246 webserver->send(200, "text/json", "Going to restart ESP");
247 delay(500);
248 ESP.restart();
249}
250
251void FSWebServer::doWifiConnection()
252{
253 String ssid, pass;
254 bool persistent = true;
255 WiFi.mode(WIFI_AP_STA);
256
257 if (webserver->hasArg("ssid"))
258 {
259 ssid = webserver->arg("ssid");
260 }
261
262 if (webserver->hasArg("password"))
263 {
264 pass = webserver->arg("password");
265 }
266
267 if (webserver->hasArg("persistent"))
268 {
269 String pers = webserver->arg("persistent");
270 if (pers.equals("false"))
271 {
272 persistent = false;
273 }
274 }
275
276 if (WiFi.status() == WL_CONNECTED)
277 {
278
279 IPAddress ip = WiFi.localIP();
280 String resp = "ESP is currently connected to a WiFi network.<br><br>"
281 "Actual connection will be closed and a new attempt will be done with <b>";
282 resp += ssid;
283 resp += "</b> WiFi network.";
284 webserver->send(200, "text/plain", resp);
285
286 delay(500);
287 Serial.println("Disconnect from current WiFi network");
288 WiFi.disconnect();
289 }
290
291 if (ssid.length() && pass.length())
292 {
293 // Try to connect to new ssid
294 Serial.print("\nConnecting to ");
295 Serial.println(ssid);
296 WiFi.begin(ssid.c_str(), pass.c_str());
297
298 uint32_t beginTime = millis();
299 while (WiFi.status() != WL_CONNECTED)
300 {
301 delay(500);
302 Serial.print("*.*");
303 if (millis() - beginTime > m_timeout)
304 break;
305 }
306 // reply to client
307 if (WiFi.status() == WL_CONNECTED)
308 {
309 // WiFi.softAPdisconnect();
310 IPAddress ip = WiFi.localIP();
311 Serial.print("\nConnected to Wifi! IP address: ");
312 Serial.println(ip);
313 String serverLoc = F("http://");
314 for (int i = 0; i < 4; i++)
315 serverLoc += i ? "." + String(ip[i]) : String(ip[i]);
316
317 String resp = "Restart ESP and then reload this page from <a href='";
318 resp += serverLoc;
319 resp += "/setup'>the new LAN address</a>";
320
321 webserver->send(200, "text/plain", resp);
322 m_apmode = false;
323
324 // Store current WiFi configuration in flash
325 if (persistent)
326 {
327#if defined(ESP8266)
328 struct station_config stationConf;
329 wifi_station_get_config_default(&stationConf);
330 // Clear previuos configuration
331 memset(&stationConf, 0, sizeof(stationConf));
332 os_memcpy(&stationConf.ssid, ssid.c_str(), ssid.length());
333 os_memcpy(&stationConf.password, pass.c_str(), pass.length());
334 wifi_set_opmode(STATION_MODE);
335 wifi_station_set_config(&stationConf);
336#elif defined(ESP32)
337 wifi_config_t stationConf;
338 esp_wifi_get_config(WIFI_IF_STA, &stationConf);
339 // Clear previuos configuration
340 memset(&stationConf, 0, sizeof(stationConf));
341 memcpy(&stationConf.sta.ssid, ssid.c_str(), ssid.length());
342 memcpy(&stationConf.sta.password, pass.c_str(), pass.length());
343 esp_wifi_set_config(WIFI_IF_STA, &stationConf);
344#endif
345 }
346 else {
347 #if defined(ESP8266)
348 struct station_config stationConf;
349 wifi_station_get_config_default(&stationConf);
350 // Clear previuos configuration
351 memset(&stationConf, 0, sizeof(stationConf));
352 wifi_station_set_config(&stationConf);
353#elif defined(ESP32)
354 wifi_config_t stationConf;
355 esp_wifi_get_config(WIFI_IF_STA, &stationConf);
356 // Clear previuos configuration
357 memset(&stationConf, 0, sizeof(stationConf));
358 esp_wifi_set_config(WIFI_IF_STA, &stationConf);
359#endif
360 }
361 }
362 else
363 webserver->send(500, "text/plain", "Connection error, maybe the password is wrong?");
364 }
365 webserver->send(500, "text/plain", "Wrong credentials provided");
366}
367
368void FSWebServer::setCrossOrigin()
369{
370 webserver->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
371 webserver->sendHeader(F("Access-Control-Max-Age"), F("600"));
372 webserver->sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
373 webserver->sendHeader(F("Access-Control-Allow-Headers"), F("*"));
374};
375
376void FSWebServer::handleScanNetworks()
377{
378 String jsonList = "[";
379 DebugPrint("Scanning WiFi networks...");
380 int n = WiFi.scanNetworks();
381 DebugPrintln(" complete.");
382 if (n == 0)
383 {
384 DebugPrintln("no networks found");
385 webserver->send(200, "text/json", "[]");
386 return;
387 }
388 else
389 {
390 DebugPrint(n);
391 DebugPrintln(" networks found:");
392
393 for (int i = 0; i < n; ++i)
394 {
395 String ssid = WiFi.SSID(i);
396 int rssi = WiFi.RSSI(i);
397#if defined(ESP8266)
398 String security = WiFi.encryptionType(i) == AUTH_OPEN ? "none" : "enabled";
399#elif defined(ESP32)
400 String security = WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? "none" : "enabled";
401#endif
402 jsonList += "{\"ssid\":\"";
403 jsonList += ssid;
404 jsonList += "\",\"strength\":\"";
405 jsonList += rssi;
406 jsonList += "\",\"security\":";
407 jsonList += security == "none" ? "false" : "true";
408 jsonList += ssid.equals(WiFi.SSID()) ? ",\"selected\": true" : "";
409 jsonList += i < n - 1 ? "}," : "}";
410 }
411 jsonList += "]";
412 }
413 webserver->send(200, "text/json", jsonList);
414 DebugPrintln(jsonList);
415}
416
417
418#ifdef INCLUDE_SETUP_HTM
419
420void FSWebServer::addDropdownList(const char *label, const char** array, size_t size) {
421 File file = m_filesystem->open("/config.json", "r");
422 int sz = file.size() * 1.33;
423 int docSize = max(sz, 2048);
424 DynamicJsonDocument doc((size_t)docSize);
425 if (file)
426 {
427 // If file is present, load actual configuration
428 DeserializationError error = deserializeJson(doc, file);
429 if (error)
430 {
431 DebugPrintln(F("Failed to deserialize file, may be corrupted"));
432 DebugPrintln(error.c_str());
433 file.close();
434 return;
435 }
436 file.close();
437 }
438 else
439 {
440 DebugPrintln(F("File not found, will be created new configuration file"));
441 }
442
443 numOptions++ ;
444
445 // If key is present in json, we don't need to create it.
446 if (doc.containsKey(label))
447 return;
448
449 JsonObject obj = doc.createNestedObject(label);
450 obj["selected"] = array[0]; // first element selected as default
451 JsonArray arr = obj.createNestedArray("values");
452 for (int i=0; i<size; i++) {
453 arr.add(array[i]);
454 }
455
456 file = m_filesystem->open("/config.json", "w");
457 if (serializeJsonPretty(doc, file) == 0)
458 {
459 DebugPrintln(F("Failed to write to file"));
460 }
461 file.close();
462}
463
464
465void FSWebServer::removeWhiteSpaces(String& str) {
466 const char noChars[] = {'\n', '\r', '\t'};
467 int pos = -1;
468 // Remove non printable characters
469 for (int i=0; i< sizeof(noChars); i++) {
470 pos = str.indexOf(noChars[i]);
471 while (pos > -1) {
472 str.replace(String(noChars[i]), "");
473 pos = str.indexOf(noChars[i]);
474 }
475 }
476 // Remove doubles spaces
477 pos = str.indexOf(" ");
478 while (pos > -1) {
479 str.replace(" ", " ");
480 pos = str.indexOf(" ");
481 }
482}
483
484void FSWebServer::handleSetup()
485{
486 webserver->sendHeader(PSTR("Content-Encoding"), "gzip");
487 webserver->send_P(200, "text/html", SETUP_HTML, SETUP_HTML_SIZE);
488}
489#endif
490
491void FSWebServer::handleIndex()
492{
493 if (m_filesystem->exists("/index.htm"))
494 {
495 handleFileRead("/index.htm");
496 }
497 else if (m_filesystem->exists("/index.html"))
498 {
499 handleFileRead("/index.html");
500 }
501#ifdef INCLUDE_SETUP_HTM
502 else
503 {
504 handleSetup();
505 }
506#endif
507}
508
509/*
510 Read the given file from the filesystem and stream it back to the client
511*/
512bool FSWebServer::handleFileRead(const String &uri)
513{
514 String path = m_basePath;
515 path = uri;
516
517 DebugPrintln("handleFileRead: " + path);
518 if (path.endsWith("/"))
519 {
520 path += "index.htm";
521 }
522 String pathWithGz = path + ".gz";
523
524 if (m_filesystem->exists(pathWithGz) || m_filesystem->exists(path))
525 {
526 if (m_filesystem->exists(pathWithGz))
527 {
528 path += ".gz";
529 }
530 const char *contentType = getContentType(path.c_str());
531 File file = m_filesystem->open(path, "r");
532 if (webserver->streamFile(file, contentType) != file.size())
533 {
534 DebugPrintln(PSTR("Sent less data than expected!"));
535 // webserver->stop();
536 }
537 file.close();
538 return true;
539 }
540 return false;
541}
542
543/*
544 Handle a file upload request
545*/
546void FSWebServer::handleFileUpload()
547{
548 if (webserver->uri() != "/edit")
549 {
550 return;
551 }
552 HTTPUpload &upload = webserver->upload();
553 if (upload.status == UPLOAD_FILE_START)
554 {
555 String filename = upload.filename;
556 String result;
557 // Make sure paths always start with "/"
558 if (!filename.startsWith("/"))
559 {
560 filename = "/" + filename;
561 }
562 checkForUnsupportedPath(filename, result);
563 if (result.length() > 0)
564 {
565 replyToCLient(ERROR, PSTR("INVALID FILENAME"));
566 return;
567 }
568
569 DebugPrintf_P(PSTR("handleFileUpload Name: %s\n"), filename.c_str());
570 m_uploadFile = m_filesystem->open(filename, "w");
571 if (!m_uploadFile)
572 {
573 replyToCLient(ERROR, PSTR("CREATE FAILED"));
574 return;
575 }
576 DebugPrintf_P(PSTR("Upload: START, filename: %s\n"), filename.c_str());
577 }
578 else if (upload.status == UPLOAD_FILE_WRITE)
579 {
580 if (m_uploadFile)
581 {
582 size_t bytesWritten = m_uploadFile.write(upload.buf, upload.currentSize);
583 if (bytesWritten != upload.currentSize)
584 {
585 replyToCLient(ERROR, PSTR("WRITE FAILED"));
586 return;
587 }
588 }
589 DebugPrintf_P(PSTR("Upload: WRITE, Bytes: %d\n"), upload.currentSize);
590 }
591 else if (upload.status == UPLOAD_FILE_END)
592 {
593 if (m_uploadFile)
594 {
595 m_uploadFile.close();
596 }
597 DebugPrintf_P(PSTR("Upload: END, Size: %d\n"), upload.totalSize);
598 }
599}
600
601void FSWebServer::replyOK()
602{
603 replyToCLient(OK, "");
604}
605
606void FSWebServer::replyToCLient(int msg_type = 0, const char *msg = "")
607{
608 webserver->sendHeader("Access-Control-Allow-Origin", "*");
609 switch (msg_type)
610 {
611 case OK:
612 webserver->send(200, FPSTR(TEXT_PLAIN), "");
613 break;
614 case CUSTOM:
615 webserver->send(200, FPSTR(TEXT_PLAIN), msg);
616 break;
617 case NOT_FOUND:
618 webserver->send(404, FPSTR(TEXT_PLAIN), msg);
619 break;
620 case BAD_REQUEST:
621 webserver->send(400, FPSTR(TEXT_PLAIN), msg);
622 break;
623 case ERROR:
624 webserver->send(500, FPSTR(TEXT_PLAIN), msg);
625 break;
626 }
627}
628
629/*
630 Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS).
631 Returns an empty String if supported, or detail of error(s) if unsupported
632*/
633void FSWebServer::checkForUnsupportedPath(String &filename, String &error)
634{
635 if (!filename.startsWith("/"))
636 {
637 error += PSTR(" !! NO_LEADING_SLASH !! ");
638 }
639 if (filename.indexOf("//") != -1)
640 {
641 error += PSTR(" !! DOUBLE_SLASH !! ");
642 }
643 if (filename.endsWith("/"))
644 {
645 error += PSTR(" ! TRAILING_SLASH ! ");
646 }
647 DebugPrintln(filename);
648 DebugPrintln(error);
649}
650
651const char *FSWebServer::getContentType(const char *filename)
652{
653 if (webserver->hasArg("download"))
654 return PSTR("application/octet-stream");
655 else if (strstr(filename, ".htm"))
656 return PSTR("text/html");
657 else if (strstr(filename, ".html"))
658 return PSTR("text/html");
659 else if (strstr(filename, ".css"))
660 return PSTR("text/css");
661 else if (strstr(filename, ".sass"))
662 return PSTR("text/css");
663 else if (strstr(filename, ".js"))
664 return PSTR("application/javascript");
665 else if (strstr(filename, ".png"))
666 return PSTR("image/png");
667 else if (strstr(filename, ".svg"))
668 return PSTR("image/svg+xml");
669 else if (strstr(filename, ".gif"))
670 return PSTR("image/gif");
671 else if (strstr(filename, ".jpg"))
672 return PSTR("image/jpeg");
673 else if (strstr(filename, ".ico"))
674 return PSTR("image/x-icon");
675 else if (strstr(filename, ".xml"))
676 return PSTR("text/xml");
677 else if (strstr(filename, ".pdf"))
678 return PSTR("application/x-pdf");
679 else if (strstr(filename, ".zip"))
680 return PSTR("application/x-zip");
681 else if (strstr(filename, ".gz"))
682 return PSTR("application/x-gzip");
683 return PSTR("text/plain");
684}
685
686// edit page, in usefull in some situation, but if you need to provide only a web interface, you can disable
687#ifdef INCLUDE_EDIT_HTM
688
689/*
690 Return the list of files in the directory specified by the "dir" query string parameter.
691 Also demonstrates the use of chuncked responses.
692*/
693void FSWebServer::handleFileList()
694{
695 if (!webserver->hasArg("dir"))
696 {
697 return replyToCLient(BAD_REQUEST, "DIR ARG MISSING");
698 }
699
700 String path = webserver->arg("dir");
701 DebugPrintln("handleFileList: " + path);
702 if (path != "/" && !m_filesystem->exists(path))
703 {
704 return replyToCLient(BAD_REQUEST, "BAD PATH");
705 }
706
707 File root = m_filesystem->open(path, "r");
708 path = String();
709 String output;
710 output.reserve(256);
711 output = "[";
712 if (root.isDirectory())
713 {
714 File file = root.openNextFile();
715 while (file)
716 {
717 String filename = file.name();
718 if (filename.lastIndexOf("/") > -1)
719 {
720 filename.remove(0, filename.lastIndexOf("/") + 1);
721 }
722 if (output != "[")
723 {
724 output += ',';
725 }
726 output += "{\"type\":\"";
727 output += (file.isDirectory()) ? "dir" : "file";
728 output += "\",\"size\":\"";
729 output += file.size();
730 output += "\",\"name\":\"";
731 output += filename;
732 output += "\"}";
733 file = root.openNextFile();
734 }
735 }
736 output += "]";
737 webserver->send(200, "text/json", output);
738}
739
740/*
741 Handle the creation/rename of a new file
742 Operation | req.responseText
743 ---------------+--------------------------------------------------------------
744 Create file | parent of created file
745 Create folder | parent of created folder
746 Rename file | parent of source file
747 Move file | parent of source file, or remaining ancestor
748 Rename folder | parent of source folder
749 Move folder | parent of source folder, or remaining ancestor
750*/
751void FSWebServer::handleFileCreate()
752{
753 String path = webserver->arg("path");
754 if (path.isEmpty())
755 {
756 replyToCLient(BAD_REQUEST, PSTR("PATH ARG MISSING"));
757 return;
758 }
759 if (path == "/")
760 {
761 replyToCLient(BAD_REQUEST, PSTR("BAD PATH"));
762 return;
763 }
764
765 String src = webserver->arg("src");
766 if (src.isEmpty())
767 {
768 // No source specified: creation
769 DebugPrintf_P(PSTR("handleFileCreate: %s\n"), path.c_str());
770 if (path.endsWith("/"))
771 {
772 // Create a folder
773 path.remove(path.length() - 1);
774 if (!m_filesystem->mkdir(path))
775 {
776 replyToCLient(ERROR, PSTR("MKDIR FAILED"));
777 return;
778 }
779 }
780 else
781 {
782 // Create a file
783 File file = m_filesystem->open(path, "w");
784 if (file)
785 {
786 file.write(0);
787 file.close();
788 }
789 else
790 {
791 replyToCLient(ERROR, PSTR("CREATE FAILED"));
792 return;
793 }
794 }
795 replyToCLient(CUSTOM, path.c_str());
796 }
797 else
798 {
799 // Source specified: rename
800 if (src == "/")
801 {
802 replyToCLient(BAD_REQUEST, PSTR("BAD SRC"));
803 return;
804 }
805 if (!m_filesystem->exists(src))
806 {
807 replyToCLient(BAD_REQUEST, PSTR("BSRC FILE NOT FOUND"));
808 return;
809 }
810
811 DebugPrintf_P(PSTR("handleFileCreate: %s from %s\n"), path.c_str(), src.c_str());
812 if (path.endsWith("/"))
813 {
814 path.remove(path.length() - 1);
815 }
816 if (src.endsWith("/"))
817 {
818 src.remove(src.length() - 1);
819 }
820 if (!m_filesystem->rename(src, path))
821 {
822 replyToCLient(ERROR, PSTR("RENAME FAILED"));
823 return;
824 }
825 replyOK();
826 }
827}
828
829/*
830 Handle a file deletion request
831 Operation | req.responseText
832 ---------------+--------------------------------------------------------------
833 Delete file | parent of deleted file, or remaining ancestor
834 Delete folder | parent of deleted folder, or remaining ancestor
835*/
836void FSWebServer::handleFileDelete()
837{
838
839 String path = webserver->arg(0);
840 if (path.isEmpty() || path == "/")
841 {
842 replyToCLient(BAD_REQUEST, PSTR("BAD PATH"));
843 return;
844 }
845
846 DebugPrintf_P(PSTR("handleFileDelete: %s\n"), path.c_str());
847 if (!m_filesystem->exists(path))
848 {
849 replyToCLient(NOT_FOUND, PSTR(FILE_NOT_FOUND));
850 return;
851 }
852 // deleteRecursive(path);
853 File root = m_filesystem->open(path, "r");
854 // If it's a plain file, delete it
855 if (!root.isDirectory())
856 {
857 root.close();
858 m_filesystem->remove(path);
859 replyOK();
860 }
861 else
862 {
863 m_filesystem->rmdir(path);
864 replyOK();
865 }
866}
867
868/*
869 This specific handler returns the edit.htm (or a gzipped version) from the /edit folder.
870 If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version
871 embedded in the program code.
872 Otherwise, fails with a 404 page with debug information
873*/
874void FSWebServer::handleGetEdit()
875{
876#ifdef INCLUDE_EDIT_HTM
877 webserver->sendHeader(PSTR("Content-Encoding"), "gzip");
878 webserver->send_P(200, "text/html", edit_htm_gz, sizeof(edit_htm_gz));
879#else
880 replyToCLient(NOT_FOUND, PSTR("FILE_NOT_FOUND"));
881#endif
882}
883
884/*
885 Return the FS type, status and size info
886*/
887void FSWebServer::handleStatus()
888{
889 DebugPrintln(PSTR("handleStatus"));
890
891 size_t totalBytes = 1024;
892 size_t usedBytes = 0;
893
894#ifdef ESP8266
895 FSInfo fs_info;
896 m_filesystem->info(fs_info);
897 totalBytes = fs_info.totalBytes;
898 usedBytes = fs_info.usedBytes;
899#elif defined(ESP32)
900 // totalBytes = m_filesystem->totalBytes();
901 // usedBytes = m_filesystem->usedBytes();
902#endif
903
904 String json;
905 json.reserve(128);
906 json = "{\"type\":\"Filesystem\", \"isOk\":";
907 if (m_fsOK)
908 {
909 json += PSTR("\"true\", \"totalBytes\":\"");
910 json += totalBytes;
911 json += PSTR("\", \"usedBytes\":\"");
912 json += usedBytes;
913 json += "\"";
914 }
915 else
916 json += "\"false\"";
917 json += PSTR(",\"unsupportedFiles\":\"\"}");
918 webserver->send(200, "application/json", json);
919}
920
921#endif // INCLUDE_EDIT_HTM
IPAddress startWiFi(uint32_t timeout, const char *apSSID, const char *apPsw)
void addDropdownList(const char *label, const char **array, size_t size)
WebServerClass * webserver
IPAddress setAPmode(const char *ssid, const char *psk)
void addHandler(const Uri &uri, HTTPMethod method, WebServerClass::THandlerFunction fn)
FSWebServer(fs::FS &fs, WebServerClass &server)
WebServerClass * getRequest()
void setCaptiveWebage(const char *url)
bool begin(const char *path=nullptr)
@ ERROR
@ CUSTOM
@ NOT_FOUND
@ BAD_REQUEST
#define FS_INIT_ERROR
#define DebugPrintln(x)
#define FILE_NOT_FOUND
#define DebugPrintf_P(x,...)
#define TEXT_PLAIN
#define DebugPrint(x)
#define DebugPrintf(x,...)
#define SETUP_HTML_SIZE
Definition: setup_htm.h:1