From 92214ac63301807214d56b1893ff4eb17f0a5601 Mon Sep 17 00:00:00 2001 From: gemu Date: Mon, 8 Jan 2024 16:44:49 +0100 Subject: [PATCH] ADD FTP server to filesystem (#20402) * add ftp lib * add ftp server --- lib/lib_div/ESPFtpServer/ESPFtpServer.cpp | 1132 +++++++++++++++++ lib/lib_div/ESPFtpServer/ESPFtpServer.h | 125 ++ lib/lib_div/ESPFtpServer/LICENSE | 165 +++ lib/lib_div/ESPFtpServer/README.md | 14 + .../ESPFtpServer/examples/ESPFtpServer.ino | 104 ++ lib/lib_div/ESPFtpServer/library.json | 21 + lib/lib_div/ESPFtpServer/library.properties | 12 + tasmota/include/tasmota_types.h | 3 +- .../xdrv_50_filesystem.ino | 68 +- 9 files changed, 1641 insertions(+), 3 deletions(-) create mode 100755 lib/lib_div/ESPFtpServer/ESPFtpServer.cpp create mode 100755 lib/lib_div/ESPFtpServer/ESPFtpServer.h create mode 100644 lib/lib_div/ESPFtpServer/LICENSE create mode 100644 lib/lib_div/ESPFtpServer/README.md create mode 100644 lib/lib_div/ESPFtpServer/examples/ESPFtpServer.ino create mode 100644 lib/lib_div/ESPFtpServer/library.json create mode 100644 lib/lib_div/ESPFtpServer/library.properties diff --git a/lib/lib_div/ESPFtpServer/ESPFtpServer.cpp b/lib/lib_div/ESPFtpServer/ESPFtpServer.cpp new file mode 100755 index 000000000..c70d17dcf --- /dev/null +++ b/lib/lib_div/ESPFtpServer/ESPFtpServer.cpp @@ -0,0 +1,1132 @@ +/* + * FTP SERVER FOR ESP32/ESP8266 + * based on FTP Server for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200) + * based on Jean-Michel Gallego's work + * modified to work with esp8266 SPIFFS by David Paiva david@nailbuster.com + * 2017: modified by @robo8080 (ported to ESP32 and SD) + * 2019: modified by @fa1ke5 (use SD card in SD_MMC mode (No SD lib, SD_MMC lib), and added fully fuctional passive mode ftp server) + * 2020: modified by @jmwislez (support generic FS, and re-introduced ESP8266) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "ESPFtpServer.h" +#include + +#ifdef ESP32 +#define F(A) A +#endif + +void FtpServer::begin (String uname, String pword, FS *ufp) { + + if (is_up) return; + + cfsp = ufp; + + // Tells the ftp server to begin listening for incoming connection + _FTP_USER = uname; + _FTP_PASS = pword; + + ftpServer = new WiFiServer(FTP_CTRL_PORT); + if (!ftpServer) return; + dataServer = new WiFiServer(FTP_DATA_PORT_PASV); + if (!dataServer) return; + + ftpServer->begin (); + delay (10); + dataServer->begin (); + delay (10); + millisTimeOut = (uint32_t)FTP_TIME_OUT * 60 * 1000; + millisDelay = 0; + cmdStatus = 0; + iniVariables (); + is_up = true; +} + +FtpServer::~FtpServer(void) { + ftpServer->close(); + ftpServer->stop(); + delete ftpServer; + dataServer->close(); + dataServer->stop(); + delete dataServer; +} + + +void FtpServer::iniVariables () { + // Default for data port + dataPort = FTP_DATA_PORT_PASV; + + // Default Data connection is Active + dataPassiveConn = false; + + // Set the root directory + strcpy (cwdName, "/"); + + rnfrCmd = false; + transferStatus = 0; +} + +void FtpServer::handleFTP () { + + if (!is_up) return; + + if ((int32_t) (millisDelay - millis ()) > 0) { + return; + } + + if (ftpServer->hasClient ()) { + #ifdef FTP_DEBUG + Serial.println (F("-> disconnecting client")); + #endif + client.stop (); + client = ftpServer->available (); + } + + if (cmdStatus == 0) { + if (client.connected ()) { + disconnectClient (); + } + cmdStatus = 1; + } + else if (cmdStatus == 1) { // Ftp server waiting for connection + abortTransfer (); + iniVariables (); + #ifdef FTP_DEBUG + Serial.println (F("-> ftp server waiting for connection on port ") + String (FTP_CTRL_PORT)); + #endif + cmdStatus = 2; + } + else if (cmdStatus == 2) { // Ftp server idle + if (client.connected ()) { // A client connected + clientConnected (); + millisEndConnection = millis () + 10 * 1000; // wait client id during 10 s. + cmdStatus = 3; + } + } + else if (readChar () > 0) { // got response + if (cmdStatus == 3) { // Ftp server waiting for user identity + if (userIdentity ()) { + cmdStatus = 4; + } + else { + cmdStatus = 0; + } + } + else if (cmdStatus == 4) { // Ftp server waiting for user registration + if (userPassword ()) { + cmdStatus = 5; + millisEndConnection = millis () + millisTimeOut; + } + else { + cmdStatus = 0; + } + } + else if (cmdStatus == 5) { // Ftp server waiting for user command + if (!processCommand ()) { + cmdStatus = 0; + } + else { + millisEndConnection = millis () + millisTimeOut; + } + } + } + else if (!client.connected () || !client) { + cmdStatus = 1; + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected")); + #endif + } + + if (transferStatus == 1) { // Retrieve data + if (!doRetrieve ()) { + transferStatus = 0; + } + } + else if (transferStatus == 2) { // Store data + if (!doStore ()) { + transferStatus = 0; + } + } + else if (cmdStatus > 2 && ! ((int32_t) (millisEndConnection - millis ()) > 0 )) { + client.println (F("530 Timeout")); + millisDelay = millis () + 200; // delay of 200 ms + cmdStatus = 0; + } +} + +void FtpServer::clientConnected () { + #ifdef FTP_DEBUG + Serial.println (F("-> client connected")); + #endif + client.println (F("220-Welcome to FTP for ESP8266/ESP32")); + client.println (F("220-By David Paiva")); + client.println (F("220-Version ") + String (FTP_SERVER_VERSION)); + client.println (F("220 Put your ftp client in passive mode, and do not attempt more than one connection")); + iCL = 0; +} + +void FtpServer::disconnectClient () { + #ifdef FTP_DEBUG + Serial.println (F("-> disconnecting client")); + #endif + abortTransfer (); + client.println (F("221 Goodbye")); + client.stop (); +} + +boolean FtpServer::userIdentity () { + if (strcmp (command, "USER")) { + client.println (F("500 Syntax error")); + } + if (strcmp (parameters, _FTP_USER.c_str ())) { + client.println (F("530 user not found")); + } + else { + client.println (F("331 OK. Password required")); + strcpy (cwdName, "/"); + return true; + } + millisDelay = millis () + 100; // delay of 100 ms + return false; +} + +boolean FtpServer::userPassword () { + if (strcmp (command, "PASS")) { + client.println (F("500 Syntax error")); + } + else if (strcmp (parameters, _FTP_PASS.c_str ())) { + client.println (F("530 ")); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> user authenticated")); + #endif + client.println (F("230 OK.")); + return true; + } + millisDelay = millis () + 100; // delay of 100 ms + return false; +} + +boolean FtpServer::processCommand () { + struct tm * ptm; + time_t ftime; + char buffer[80]; + + /////////////////////////////////////// + // // + // ACCESS CONTROL COMMANDS // + // // + /////////////////////////////////////// + + // + // CDUP - Change to Parent Directory + // + if (!strcmp (command, "CDUP") || (!strcmp (command, "CWD") && !strcmp (parameters, ".."))) { + bool ok = false; + if (strlen (cwdName) > 1) { // do nothing if cwdName is root + // if cwdName ends with '/', remove it (must not append) + if (cwdName[strlen (cwdName) - 1] == '/') { + cwdName[ strlen (cwdName ) - 1 ] = 0; + } + // search last '/' + char * pSep = strrchr (cwdName, '/'); + ok = pSep > cwdName; + // if found, ends the string on its position + if (ok) { + * pSep = 0; + ok = cfsp->exists (cwdName); + } + } + // if an error appends, move to root + if (!ok) { + strcpy (cwdName, "/"); + } + client.println (F("250 Ok. Current directory is ") + String (cwdName)); + } + + // + // CWD - Change Working Directory + // + else if (!strcmp (command, "CWD")) { + char path[FTP_CWD_SIZE]; + if (haveParameter () && makeExistsPath (path)) { + strcpy (cwdName, path); + client.println (F("250 Ok. Current directory is ") + String (cwdName)); + } + } + + // + // PWD - Print Directory + // + else if (!strcmp (command, "PWD")) { + client.println (F("257 \"") + String (cwdName) + F("\" is your current directory")); + } + + // + // QUIT + // + else if (!strcmp (command, "QUIT")) { + disconnectClient (); + return false; + } + + /////////////////////////////////////// + // // + // TRANSFER PARAMETER COMMANDS // + // // + /////////////////////////////////////// + + // + // MODE - Transfer Mode + // + else if (!strcmp (command, "MODE")) { + if (!strcmp (parameters, "S")) { + client.println (F("200 S Ok")); + } + else { + client.println (F("504 Only S (tream) is supported")); + } + } + + // + // PASV - Passive Connection management + // + else if (!strcmp (command, "PASV")) { + if (data.connected ()) { + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + } + dataIp = WiFi.localIP (); + dataPort = FTP_DATA_PORT_PASV; + #ifdef FTP_DEBUG + Serial.println (F("-> connection management set to passive")); + Serial.println (F("-> data port set to ") + String (dataPort)); + #endif + client.println (F("227 Entering Passive Mode (") + String (dataIp[0]) + "," + String (dataIp[1]) + "," + String (dataIp[2]) + "," + String (dataIp[3]) + "," + String (dataPort >> 8) + "," + String (dataPort & 255) + ")."); + dataPassiveConn = true; + } + + // + // PORT - Data Port + // + else if (!strcmp (command, "PORT")) { + if (data) { + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + } + // get IP of data client + dataIp[0] = atoi (parameters); + char * p = strchr (parameters, ','); + for (uint8_t i = 1; i < 4; i ++) { + dataIp[i] = atoi (++ p); + p = strchr (p, ','); + } + // get port of data client + dataPort = 256 * atoi (++ p); + p = strchr (p, ','); + dataPort += atoi (++ p); + if (p == NULL) { + client.println (F("501 Can't interpret parameters")); + } + else { + client.println (F("200 PORT command successful")); + dataPassiveConn = false; + } + } + + // + // STRU - File Structure + // + else if (!strcmp (command, "STRU")) { + if (!strcmp (parameters, "F")) { + client.println (F("200 F Ok")); + } + else { + client.println (F("504 Only F (ile) is supported")); + } + } + + // + // TYPE - Data Type + // + else if (!strcmp (command, "TYPE")) { + if (!strcmp (parameters, "A")) { + client.println (F("200 TYPE is now ASCII")); + } + else if (!strcmp (parameters, "I" )) { + client.println (F("200 TYPE is now 8-bit binary")); + } + else { + client.println (F("504 Unknown TYPE")); + } + } + + /////////////////////////////////////// + // // + // FTP SERVICE COMMANDS // + // // + /////////////////////////////////////// + + // + // ABOR - Abort + // + else if (!strcmp (command, "ABOR")) { + abortTransfer (); + client.println (F("226 Data connection closed")); + } + + // + // DELE - Delete a File + // + else if (!strcmp (command, "DELE")) { + char path[FTP_CWD_SIZE]; + if (strlen (parameters) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (path)) { + if (!cfsp->exists (path)) { + client.println (F("550 File ") + String (parameters) + F(" not found")); + } + else { + if (cfsp->remove (path)) { + client.println (F("250 Deleted ") + String (parameters)); + // silently recreate the directory if it vanished with the last file it contained + String directory = String (path).substring (0, String(path).lastIndexOf ("/")); + if (!cfsp->exists (directory.c_str())) { + cfsp->mkdir (directory.c_str()); + } + } + else { + client.println (F("450 Can't delete ") + String (parameters)); + } + } + } + } + + // + // LIST - List + // + else if (!strcmp (command, "LIST")) { + if (dataConnect ()) { + client.println (F("150 Accepted data connection")); + uint16_t nm = 0; + + #ifdef ESP8266 + Dir dir = cfsp->openDir (cwdName); + while (dir.next ()) { + String fname, fsize; + fname = dir.fileName (); + time_t ftime = dir.fileTime (); + ptm = gmtime (&ftime); + int pos = fname.lastIndexOf ("/"); //looking for the beginning of the file by the last "/" + fname.remove (0, pos + 1); //Delete everything up to and including the filename + fsize = String (dir.fileSize ()); + if (dir.isDirectory ()){ + sprintf (buffer, "%04d-%02d-%02d %02d:%02d %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fname.c_str()); + } + else { + sprintf (buffer, "%04d-%02d-%02d %02d:%02d %s %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fillSpaces (14, String (fsize)).c_str(), fname.c_str()); + } + data.println (buffer); + nm ++; + } + client.println( "226 " + String (nm) + " matches total"); + #endif + #ifdef ESP32 + File dir = cfsp->open (cwdName); + if ((!dir) || (!dir.isDirectory ())) { + client.println (F("550 Can't open directory ") + String (cwdName)); + } + else { + File file = dir.openNextFile (); + while (file) { + String fname, fsize; + fname = file.name (); + ftime = file.getLastWrite (); + ptm = gmtime (&ftime); + int pos = fname.lastIndexOf ("/"); //looking for the beginning of the file by the last "/" + fname.remove (0, pos + 1); //Delete everything up to and including the filename + #ifdef FTP_DEBUG + Serial.println ("-> " + fname); + #endif + fsize = String (file.size ()); + if (file.isDirectory ()){ + sprintf (buffer, "%04u-%02u-%02u %02u:%02u %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fname.c_str()); + } + else { + sprintf (buffer, "%04u-%02u-%02u %02d:%02u %s %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fillSpaces (14, String (fsize)).c_str(), fname.c_str()); + } + data.println (buffer); + nm ++; + file = dir.openNextFile (); + } + client.println ("226 " + String (nm) + F(" matches total")); + } + #endif + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + } + else { + client.println (F("425 No data connection")); + data.stop (); + } + } + + // + // MLSD - Listing for Machine Processing (see RFC 3659) + // + else if (!strcmp (command, "MLSD")) { + if (!dataConnect ()) { + client.println (F("425 No data connection MLSD")); + } + else { + client.println (F("150 Accepted data connection")); + uint16_t nm = 0; + #ifdef ESP8266 + Dir dir = cfsp->openDir (cwdName); + char dtStr[15]; + while (dir.next ()) { + String fn, fs; + fn = dir.fileName (); + int pos = fn.lastIndexOf ("/"); //looking for the beginning of the file by the last "/" + fn.remove (0, pos + 1); //Delete everything up to and including the filename + fs = String (dir.fileSize ()); + ftime = dir.fileTime (); + ptm = gmtime (&ftime); + if (dir.isDirectory ()) { + sprintf (buffer, "Type=dir;Modify=%04u%02u%02u%02u%02u%02u; %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str()); + } + else { + sprintf (buffer, "Type=file;Size=%s;Modify=%04u%02u%02u%02u%02u%02u; %s", fs.c_str(), ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str()); + } + data.println (buffer); + nm ++; + } + client.println (F("226-options: -a -l")); + client.println("226 " + String(nm) + F(" matches total")); + #endif + #ifdef ESP32 + File dir = cfsp->open (cwdName); + char dtStr[15]; + if (!cfsp->exists (cwdName)) { + client.println (F("550 Can't open directory ") + String (parameters)); + } + else { + File file = dir.openNextFile (); + while (file) { + String fn, fs; + fn = file.name (); + int pos = fn.lastIndexOf ("/"); // looking for the beginning of the file by the last "/" + fn.remove (0, pos + 1); // delete everything up to and including the filename + fs = String (file.size ()); + ftime = file.getLastWrite (); + ptm = gmtime (&ftime); + if (file.isDirectory ()) { + sprintf (buffer, "Type=dir;Modify=%04u%02u%02u%02u%02u%02u; %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str()); + } + else { + sprintf (buffer, "Type=file;Size=%s;Modify=%04u%02u%02u%02u%02u%02u; %s", fs.c_str(), ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str()); + } + data.println (buffer); + nm ++; + file = dir.openNextFile (); + } + client.println (F("226-options: -a -l")); + client.println ("226 " + String (nm) + F(" matches total")); + } + #endif + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + } + } + + // + // NLST - Name List + // + else if (!strcmp (command, "NLST" )) { + if (!dataConnect ()) { + client.println (F("425 No data connection")); + } + else { + client.println (F("150 Accepted data connection")); + uint16_t nm = 0; + #ifdef ESP8266 + Dir dir = cfsp->openDir (cwdName); + if (!cfsp->exists (cwdName)) { + client.println (F("550 Can't open directory ") + String (parameters)); + } + else { + while (dir.next ()) { + data.println (dir.fileName ()); + nm ++; + } + client.println ("226 " + String(nm) + F(" matches total")); + } + #endif + #ifdef ESP32 + File dir = cfsp->open (cwdName); + if (!cfsp->exists (cwdName)) { + client.println (F("550 Can't open directory ") + String (parameters)); + } + else { + File file = dir.openNextFile (); + while (file) { + data.println (file.name ()); + nm ++; + file = dir.openNextFile (); + } + client.println ("226 " + String (nm) + F(" matches total")); + } + #endif + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + } + } + + // + // NOOP + // + else if (!strcmp (command, "NOOP")) { + client.println (F("200 Zzz...")); + } + + // + // RETR - Retrieve + // + else if (!strcmp (command, "RETR")) { + char path[FTP_CWD_SIZE]; + if (strlen (parameters) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (path)) { + file = cfsp->open (path, "r"); + if (!file) { + client.println (F("550 File ") + String (parameters) + F(" not found")); + } + else if (!file) { + client.println (F("450 Can't open ") + String (parameters)); + } + else if (!dataConnect ()) { + client.println (F("425 No data connection")); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> sending ") + String (parameters)); + #endif + client.println (F("150-Connected to port ") + String (dataPort)); + client.println (F("150 ") + String (file.size ()) + F(" bytes to download")); + millisBeginTrans = millis (); + bytesTransferred = 0; + transferStatus = 1; + } + } + } + + // + // STOR - Store + // + else if (!strcmp (command, "STOR")) { + char path[FTP_CWD_SIZE]; + if (strlen (parameters) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (path)) { + file = cfsp->open (path, "w"); + if (!file) { + client.println (F("451 Can't open/create ") + String (parameters)); + } + else if (!dataConnect ()) { + client.println (F("425 No data connection")); + file.close (); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> receiving ") + String (parameters)); + #endif + client.println (F("150 Connected to port ") + String (dataPort)); + millisBeginTrans = millis (); + bytesTransferred = 0; + transferStatus = 2; + } + } + } + + // + // MKD - Make Directory + // + + else if (!strcmp (command, "MKD")) { + char path[FTP_CWD_SIZE]; + if (haveParameter () && makePath (path)) { + if (cfsp->exists (path)) { + client.println (F("521 Can't create \"") + String (parameters) + F("\", Directory exists")); + } + else { + if (cfsp->mkdir (path)) { + client.println (F("257 \"") + String (parameters) + F("\" created")); + } + else { + client.println (F("550 Can't create \"") + String (parameters) + "\""); + } + } + } + } + + // + // RMD - Remove a Directory + // + else if (!strcmp (command, "RMD")) { + char path[FTP_CWD_SIZE]; + if (haveParameter () && makePath (path)) { + if (cfsp->rmdir (path)) { + #ifdef FTP_DEBUG + Serial.println (F("-> deleting ") + String (parameters)); + #endif + client.println ("250 \"" + String (parameters) + F("\" deleted")); + } + else { + if (cfsp->exists (path)) { // hack + client.println (F("550 Can't remove \"") + String (parameters) + F("\". Directory not empty?")); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> deleting ") + String (parameters)); + #endif + client.println ("250 \"" + String (parameters) + F("\" deleted")); + } + } + } + } + + // + // RNFR - Rename From + // + else if (!strcmp (command, "RNFR")) { + buf[0] = 0; + if (strlen (parameters) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (buf)) { + if (!cfsp->exists (buf)) { + client.println (F("550 File ") + String (parameters) + F(" not found")); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> renaming ") + String (buf)); + #endif + client.println (F("350 RNFR accepted - file exists, ready for destination")); + rnfrCmd = true; + } + } + } + + // + // RNTO - Rename To + // + else if (!strcmp (command, "RNTO")) { + char path[FTP_CWD_SIZE]; + char dir[FTP_FIL_SIZE]; + if (strlen (buf ) == 0 || ! rnfrCmd) { + client.println (F("503 Need RNFR before RNTO")); + } + else if (strlen (parameters ) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (path)) { + if (cfsp->exists (path)) { + client.println (F("553 ") + String (parameters) + F(" already exists")); + } + else { + #ifdef FTP_DEBUG + Serial.println (F("-> renaming ") + String (buf) + " to " + String (path)); + #endif + if (cfsp->rename (buf, path)) { + client.println (F("250 File successfully renamed or moved")); + } + else { + client.println (F("451 Rename/move failure")); + } + } + } + rnfrCmd = false; + } + + /////////////////////////////////////// + // // + // EXTENSIONS COMMANDS (RFC 3659) // + // // + /////////////////////////////////////// + + // + // FEAT - New Features + // + else if (!strcmp (command, "FEAT")) { + client.println (F("211-Extensions supported:")); + client.println (F(" MLSD")); + client.println (F("211 End.")); + } + + // + // MDTM - File Modification Time (see RFC 3659) + // + else if (!strcmp (command, "MDTM")) { + client.println (F("550 Unable to retrieve time")); + } + + // + // SIZE - Size of the file + // + else if (!strcmp (command, "SIZE")) { + char path[FTP_CWD_SIZE]; + if (strlen (parameters) == 0) { + client.println (F("501 No file name")); + } + else if (makePath (path)) { + file = cfsp->open (path, "r"); + if (!file) { + client.println (F("450 Can't open ") + String (parameters)); + } + else { + client.println (F("213 ") + String (file.size ())); + file.close (); + } + } + } + + // + // SITE - System command + // + else if (!strcmp (command, "SITE")) { + client.println (F("500 Unknown SITE command ") + String (parameters)); + } + + // + // Unrecognized commands ... + // + else { + client.println (F("500 Unknown command")); + } + return true; +} + +boolean FtpServer::dataConnect () { + unsigned long startTime = millis (); + //wait 5 seconds for a data connection + if (!data.connected ()) { + while (!dataServer->hasClient () && millis () - startTime < 10000) { + yield (); + } + if (dataServer->hasClient ()) { + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + data = dataServer->available (); + #ifdef FTP_DEBUG + Serial.println (F("-> client connected to dataserver")); + #endif + } + } + return data.connected (); +} + +boolean FtpServer::doRetrieve () { + if (data.connected ()) { + int16_t nb = file.readBytes (buf, FTP_BUF_SIZE); + if (nb > 0) { + data.write ((uint8_t*)buf, nb); + bytesTransferred += nb; + return true; + } + } + closeTransfer (); + return false; +} + +boolean FtpServer::doStore () { + // Avoid blocking by never reading more bytes than are available + int navail = data.available(); + if (navail > 0) { + // And be sure not to overflow the buffer + if (navail > FTP_BUF_SIZE) { + navail = FTP_BUF_SIZE; + } + int16_t nb = data.read((uint8_t *)buf, navail); + if (nb > 0) { + file.write((uint8_t *)buf, nb); + bytesTransferred += nb; + } + } + if (!data.connected() && (navail <= 0)) { + closeTransfer(); + return false; + } + else { + return true; + } +} + +void FtpServer::closeTransfer () { + uint32_t deltaT = (int32_t) (millis () - millisBeginTrans); + if (deltaT > 0 && bytesTransferred > 0) { + client.println (F("226-File successfully transferred")); + client.println ("226 " + String (deltaT) + " ms, " + String (bytesTransferred / deltaT) + " kbytes/s"); + } + else { + client.println (F("226 File successfully transferred")); + } + file.close (); + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> file successfully transferred")); + Serial.println (F("-> client disconnected from dataserver")); + #endif +} + +void FtpServer::abortTransfer () { + if (transferStatus > 0) { + file.close (); + data.stop (); + #ifdef FTP_DEBUG + Serial.println (F("-> client disconnected from dataserver")); + #endif + client.println (F("426 Transfer aborted")); + #ifdef FTP_DEBUG + Serial.println (F("-> transfer aborted")); + #endif + } + transferStatus = 0; +} + +// Read a char from client connected to ftp server +// +// update cmdLine and command buffers, iCL and parameters pointers +// +// return: +// -2 if buffer cmdLine is full +// -1 if line not completed +// 0 if empty line received +// length of cmdLine (positive) if no empty line received + +int8_t FtpServer::readChar () { + int8_t rc = -1; + + if (client.available ()) { + char c = client.read (); + #ifdef FTP_DEBUG + Serial.print (c); + #endif + if (c == '\\') { + c = '/'; + } + if (c != '\r' ) { + if (c != '\n' ) { + if (iCL < FTP_CMD_SIZE) { + cmdLine[iCL ++] = c; + } + else { + rc = -2; // Line too long + } + } + else { + cmdLine[iCL] = 0; + command[0] = 0; + parameters = NULL; + // empty line? + if (iCL == 0) { + rc = 0; + } + else { + rc = iCL; + // search for space between command and parameters + parameters = strchr (cmdLine, ' '); + if (parameters != NULL) { + if (parameters - cmdLine > 4) { + rc = -2; // Syntax error + } + else { + strncpy (command, cmdLine, parameters - cmdLine); + command[parameters - cmdLine] = 0; + + while (* (++ parameters) == ' ') { + ; + } + } + } + else if (strlen (cmdLine) > 4) { + rc = -2; // Syntax error. + } + else { + strcpy (command, cmdLine); + } + iCL = 0; + } + } + } + if (rc > 0) { + for (uint8_t i = 0; i < strlen (command); i ++) { + command[i] = toupper (command[i]); + } + } + if (rc == -2) { + iCL = 0; + client.println (F("500 Syntax error")); + } + } + return rc; +} + +// Make complete path/name from cwdName and parameters +// +// 3 possible cases: parameters can be absolute path, relative path or only the name +// +// parameters: +// fullName : where to store the path/name +// +// return: +// true, if done + +boolean FtpServer::makePath (char * fullName) { + return makePath (fullName, parameters); +} + +boolean FtpServer::makePath (char * fullName, char * param) { + if (param == NULL) { + param = parameters; + } + // Root or empty? + if (strcmp (param, "/") == 0 || strlen (param) == 0) { + strcpy (fullName, "/"); + return true; + } + // If relative path, concatenate with current dir + if (param[0] != '/' ) { + strcpy (fullName, cwdName); + if (fullName[strlen (fullName) - 1] != '/') { + strncat (fullName, "/", FTP_CWD_SIZE); + } + strncat (fullName, param, FTP_CWD_SIZE); + } + else { + strcpy (fullName, param); + } + // If ends with '/', remove it + uint16_t strl = strlen (fullName) - 1; + if (fullName[strl] == '/' && strl > 1) { + fullName[strl] = 0; + } + if (strlen (fullName) < FTP_CWD_SIZE) { + return true; + } + client.println (F("500 Command line too long")); + return false; +} + +// Calculate year, month, day, hour, minute and second +// from first parameter sent by MDTM command (YYYYMMDDHHMMSS) +// +// parameters: +// pyear, pmonth, pday, phour, pminute and psecond: pointer of +// variables where to store data +// +// return: +// 0 if parameter is not YYYYMMDDHHMMSS +// length of parameter + space + +uint8_t FtpServer::getDateTime (uint16_t * pyear, uint8_t * pmonth, uint8_t * pday, + uint8_t * phour, uint8_t * pminute, uint8_t * psecond) { + char dt[15]; + + // Date/time are expressed as a 14 digits long string + // terminated by a space and followed by name of file + if (strlen (parameters ) < 15 || parameters[14] != ' ') { + return 0; + } + for (uint8_t i = 0; i < 14; i ++) { + if (!isdigit (parameters[i])) { + return 0; + } + } + strncpy (dt, parameters, 14); + dt[14] = 0; + * psecond = atoi (dt + 12); + dt[12] = 0; + * pminute = atoi (dt + 10); + dt[10] = 0; + * phour = atoi (dt + 8); + dt[8] = 0; + * pday = atoi (dt + 6); + dt[6] = 0; + * pmonth = atoi (dt + 4); + dt[4] = 0; + * pyear = atoi (dt); + return 15; +} + +// Create string YYYYMMDDHHMMSS from date and time +// +// parameters: +// date, time +// tstr: where to store the string. Must be at least 15 characters long +// +// return: +// pointer to tstr + +char * FtpServer::makeDateTimeStr (char * tstr, uint16_t date, uint16_t time) { + sprintf (tstr, "%04u%02u%02u%02u%02u%02u", + ((date & 0xFE00) >> 9) + 1980, (date & 0x01E0) >> 5, date & 0x001F, + (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) << 1); + return tstr; +} + +bool FtpServer::haveParameter () { + if (parameters != NULL && strlen (parameters) > 0) { + return true; + } + client.println (F("501 No file name")); + return false; +} + +bool FtpServer::makeExistsPath (char * path, char * param) { + if (!makePath (path, param)) { + return false; + } + if (cfsp->exists (path)) { + return true; + } + client.println (F("550 ") + String (path) + F(" not found.")); + return false; +} + +String FtpServer::fillSpaces (uint8_t length, String input) { + String output; + output = ""; + while (output.length() < length - input.length()) { + output += " "; + } + output += input; + return (output); +} diff --git a/lib/lib_div/ESPFtpServer/ESPFtpServer.h b/lib/lib_div/ESPFtpServer/ESPFtpServer.h new file mode 100755 index 000000000..84f380658 --- /dev/null +++ b/lib/lib_div/ESPFtpServer/ESPFtpServer.h @@ -0,0 +1,125 @@ +/* +* FTP SERVER FOR ESP8266/ESP32 + * based on FTP Server for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200) + * based on Jean-Michel Gallego's work + * modified to work with esp8266 SPIFFS by David Paiva (david@nailbuster.com) + * 2017: modified by @robo8080 (ported to ESP32 and SD) + * 2019: modified by @fa1ke5 (use SD card in SD_MMC mode (No SD lib, SD_MMC lib), and added fully fuctional passive mode ftp server) + * 2020: modified by @jmwislez (support generic FS, and re-introduced ESP8266) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +/******************************************************************************* + ** ** + ** DEFINITIONS FOR FTP SERVER ** + ** ** + *******************************************************************************/ + +// Uncomment to print debugging info to console +//#define FTP_DEBUG + +#ifndef FTP_SERVERESP_H +#define FTP_SERVERESP_H + +#include +#include +#ifdef ESP32 + #include +#else + #include +#endif + +#define FTP_SERVER_VERSION "jmwislez/ESP32FtpServer 0.1.0" + +#define FTP_CTRL_PORT 21 // Command port on wich server is listening +#define FTP_DATA_PORT_PASV 50009 // Data port in passive mode + +#define FTP_TIME_OUT 5 // Disconnect client after 5 minutes of inactivity + +#ifdef ESP32 +#define FTP_CMD_SIZE 255 + 8 // max size of a command +#define FTP_CWD_SIZE 255 + 8 // max size of a directory name +#define FTP_FIL_SIZE 255 // max size of a file name +#define FTP_BUF_SIZE 4096 //512 // 700 KByte/s download in AP mode, direct connection. +#endif + +#ifdef ESP8266 +#define FTP_CMD_SIZE 128 // max size of a command +#define FTP_CWD_SIZE 128 // max size of a directory name +#define FTP_FIL_SIZE 64 // max size of a file name +#define FTP_BUF_SIZE 256 // 700 KByte/s download in AP mode, direct connection. +#endif + +class FtpServer { + public: + void begin(String uname, String pword, FS *ufp); + void handleFTP (void); + ~FtpServer(void); + bool is_up = false; + + private: + bool haveParameter (); + bool makeExistsPath (char * path, char * param = NULL); + void iniVariables (); + void clientConnected (); + void disconnectClient (); + boolean userIdentity (); + boolean userPassword (); + boolean processCommand (); + boolean dataConnect (); + boolean doRetrieve (); + boolean doStore (); + void closeTransfer (); + void abortTransfer (); + boolean makePath (char * fullname); + boolean makePath (char * fullName, char * param); + uint8_t getDateTime (uint16_t * pyear, uint8_t * pmonth, uint8_t * pday, + uint8_t * phour, uint8_t * pminute, uint8_t * second); + char * makeDateTimeStr (char * tstr, uint16_t date, uint16_t time); + int8_t readChar (); + String fillSpaces (uint8_t len, String input_str); + + IPAddress dataIp; // IP address of client for data + WiFiClient client; + WiFiClient data; + + WiFiServer *ftpServer; + WiFiServer *dataServer; + + FS *cfsp; + + File file; + + boolean dataPassiveConn; + uint16_t dataPort; + char buf[FTP_BUF_SIZE]; // data buffer for transfers + char cmdLine[FTP_CMD_SIZE]; // where to store incoming char from client + char cwdName[FTP_CWD_SIZE]; // name of current directory + char command[5]; // command sent by client + boolean rnfrCmd; // previous command was RNFR + char * parameters; // point to begin of parameters sent by client + uint16_t iCL; // pointer to cmdLine next incoming char + int8_t cmdStatus, // status of ftp command connexion + transferStatus; // status of ftp data transfer + uint32_t millisTimeOut, // disconnect after 5 min of inactivity + millisDelay, + millisEndConnection, // + millisBeginTrans, // store time of beginning of a transaction + bytesTransferred; // + String _FTP_USER; + String _FTP_PASS; +}; + +#endif // FTP_SERVERESP_H diff --git a/lib/lib_div/ESPFtpServer/LICENSE b/lib/lib_div/ESPFtpServer/LICENSE new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/lib/lib_div/ESPFtpServer/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/lib_div/ESPFtpServer/README.md b/lib/lib_div/ESPFtpServer/README.md new file mode 100644 index 000000000..8976e20d2 --- /dev/null +++ b/lib/lib_div/ESPFtpServer/README.md @@ -0,0 +1,14 @@ +# ESPFtpServer + +The history of this code is as follows: + +* https://github.com/gallegojm/Arduino-Ftp-Server/tree/master/FtpServer: FTP server for Arduino Mega2560 and Due with ethernet module W5100, W5200 or W5500 +* https://github.com/nailbuster/esp8266FTPServer: ported to ESP8266 and SPIFFS file system +* https://github.com/robo8080/ESP32_FTPServer_SD: ported to ESP32 and SD card file system +* https://github.com/fa1ke5/ESP32_FTPServer_SD_MMC: use SD card in SD_MMC mode (No SD lib, SD_MMC lib). Added fully fuctional passive mode ftp server, browse dir, change dir, rename dir/file, delete dir/file, upload and download files, dirs. + +The current repository is forked from fa1ke5, and has the following changes: +* use of any file system file system (like SPIFFS/LittleFS/SD_MMC) +* codebase to work for both ESP8266 and ESP32 +* clean-up of code layout and English +* addition of library description files diff --git a/lib/lib_div/ESPFtpServer/examples/ESPFtpServer.ino b/lib/lib_div/ESPFtpServer/examples/ESPFtpServer.ino new file mode 100644 index 000000000..7fcd1a08c --- /dev/null +++ b/lib/lib_div/ESPFtpServer/examples/ESPFtpServer.ino @@ -0,0 +1,104 @@ +// Uncomment the file system to use for FTP server + +#define FS_LITTLEFS +//#define FS_SPIFFS +//#define FS_SD_MMC + +#ifdef ESP32 +#include +#endif +#ifdef ESP8266 +#include +#endif +#include +#include +#include "ESPFtpServer.h" + +#if defined(FS_LITTLEFS) +#ifdef ESP32 +#include "LITTLEFS.h" +#define FS_ID LITTLEFS +#endif +#ifdef ESP8266 +#include "LittleFS.h" +#define FS_ID LittleFS +#endif +#define FS_NAME "LittleFS" +#elif defined(FS_SPIFFS) +#ifdef ESP32 +#include "SPIFFS.h" +#endif +#define FS_ID SPIFFS +#define FS_NAME "SPIFFS" +#elif defined(FS_SD_MMC) +#include "SD_MMC.h" +#define FS_ID SD_MMC +#define FS_NAME "SD_MMC" +#else +#define FS_ID SD +#define FS_NAME "UNDEF" +#endif + +const char* ssid = "*********************"; +const char* password = "*********************"; + +const char* ntpServer = "pool.ntp.org"; +const long gmtOffset_sec = 3600; +const int daylightOffset_sec = 3600; +struct tm timeinfo; + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + +#ifdef ESP8266 +bool getLocalTime (struct tm * info) { + time_t now; + + time(&now); + localtime_r (&now, info); + + if (info->tm_year > (2016 - 1900)) { + return true; + } + else { + return false; + } +} +#endif + +void setup (void) { + Serial.begin (115200); + + WiFi.begin (ssid, password); + Serial.println (""); + + // Wait for connection + while (WiFi.status () != WL_CONNECTED) { + delay (500); + Serial.print ("."); + } + Serial.println (""); + Serial.print ("Connected to "); + Serial.println (ssid); + Serial.print ("IP address: "); + Serial.println (WiFi.localIP ()); + + configTime (gmtOffset_sec, daylightOffset_sec, ntpServer); + while (!getLocalTime (&timeinfo)) { + delay (500); + Serial.print ("."); + } + Serial.printf ("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (timeinfo.tm_year) + 1900, (timeinfo.tm_mon) + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + + //FS_ID.format (); + if (FS_ID.begin ()) { + Serial.println ("File system opened (" + String (FS_NAME) + ")"); + ftpSrv.begin ("esp32", "esp32"); //username, password for ftp. set ports in ESPFtpServer.h (default 21, 50009 for PASV) + } + else { + Serial.println ("File system could not be opened; ftp server will not work"); + } +} + +void loop (void){ + ftpSrv.handleFTP (FS_ID); //make sure in loop you call handleFTP()! +} diff --git a/lib/lib_div/ESPFtpServer/library.json b/lib/lib_div/ESPFtpServer/library.json new file mode 100644 index 000000000..9c2bc1525 --- /dev/null +++ b/lib/lib_div/ESPFtpServer/library.json @@ -0,0 +1,21 @@ +{ + "name": "ESPFtpServer", + "keywords": "ftp, littlefs, spiffs, esp32, esp8266", + "description": "ESPFtpServer implements a simple FTP server (passive mode, only one connection at a time) on ESP8266 and ESP32, for any file system (SPIFFS/LittleFS/SD_MMC).", + "homepage": "https://github.com/jmwislez/ESPFtpServer/", + "repository": { + "type": "git", + "url": "https://github.com/jmwislez/ESPFtpServer.git" + }, + "version": "0.1.0", + "authors": { + "name": "Jean-Marc Wislez", + "url": "https://github.com/jmwislez/" + }, + "exclude": [ + ".github", + "extras" + ], + "frameworks": "arduino", + "platforms": ["espressif8266", "espressif32"] +} diff --git a/lib/lib_div/ESPFtpServer/library.properties b/lib/lib_div/ESPFtpServer/library.properties new file mode 100644 index 000000000..e6de7105f --- /dev/null +++ b/lib/lib_div/ESPFtpServer/library.properties @@ -0,0 +1,12 @@ +name=ESPFtpServer +version=0.1.0 +author=Jean-Marc Wislez +maintainer=Jean-Marc Wislez +sentence=A simple FTP server for LittleFS/SPIFFS on ESP8266/ESP32 +paragraph=ESPFtpServer implements a simple FTP server (passive mode, only one connection at a time) on ESP8266 and ESP32, for any file system (SPIFFS/LittleFS/SD_MMC). +category=Network +url=https://github.com/jmwislez/ESPFtpServer/ +architectures=esp8266,esp32 +repository=https://github.com/jmwislez/ESPFtpServer.git +license=GPL +architectures=* diff --git a/tasmota/include/tasmota_types.h b/tasmota/include/tasmota_types.h index 7ed4dbab3..b2df33857 100755 --- a/tasmota/include/tasmota_types.h +++ b/tasmota/include/tasmota_types.h @@ -264,8 +264,7 @@ typedef union { uint32_t spare21 : 1; // bit 21 uint32_t spare22 : 1; // bit 22 uint32_t spare23 : 1; // bit 23 - uint32_t spare24 : 1; // bit 24 - uint32_t spare25 : 1; // bit 25 + uint32_t FTP_Mode : 2; // bit 24, 25 uint32_t tariff_forced : 2; // bit 26..27 (v12.4.0.2) - Energy forced tariff : 0=tariff change on time, 1|2=tariff forced uint32_t sunrise_dawn_angle : 2; // bits 28/29 (v12.1.1.4) - uint32_t temperature_set_res : 2; // bits 30/31 (v9.3.1.4) - (Tuya) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino index 1a515bd47..f577d5904 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino @@ -48,6 +48,13 @@ ufs fs info ufstype get filesytem type 0=none 1=SD 2=Flashfile ufssize total size in kB ufsfree free size in kB +ufsdelete +ufsrename +ufsrun +ufsServe +ftp start stop ftp server: 0 = OFF, 1 = SDC, 2 = FlashFile + + \*********************************************************************************************/ #define XDRV_50 50 @@ -547,6 +554,9 @@ const char kUFSCommands[] PROGMEM = "Ufs|" // Prefix "|Type|Size|Free|Delete|Rename|Run" #ifdef UFILESYS_STATIC_SERVING "|Serve" +#endif +#ifdef USE_FTP + "|FTP" #endif ; @@ -554,7 +564,10 @@ void (* const kUFSCommand[])(void) PROGMEM = { &UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete, &UFSRename, &UFSRun #ifdef UFILESYS_STATIC_SERVING ,&UFSServe -#endif +#endif +#ifdef USE_FTP + ,&Switch_FTP +#endif }; void UFSInfo(void) { @@ -1348,17 +1361,70 @@ void UfsEditorUpload(void) { #endif // USE_WEBSERVER + +#ifdef USE_FTP +#include +FtpServer *ftpSrv; + +void FTP_Server(uint32_t mode) { + if (mode > 0) { + if (ftpSrv) { + delete ftpSrv; + } + ftpSrv = new FtpServer; + if (mode == 1) { + ftpSrv->begin(USER_FTP,PW_FTP, ufsp); + } else { + ftpSrv->begin(USER_FTP,PW_FTP, ffsp); + } + AddLog(LOG_LEVEL_INFO, PSTR("UFS: FTP Server started in mode: '%d'"), mode); + } else { + if (ftpSrv) { + delete ftpSrv; + ftpSrv = nullptr; + } + } +} + +void Switch_FTP(void) { + if (XdrvMailbox.data_len > 0) { + if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) { + FTP_Server(XdrvMailbox.payload); + Settings->mbflag2.FTP_Mode = XdrvMailbox.payload; + } + } + ResponseCmndNumber(Settings->mbflag2.FTP_Mode); +} +#endif // USE_FTP + /*********************************************************************************************\ * Interface \*********************************************************************************************/ + bool Xdrv50(uint32_t function) { bool result = false; switch (function) { case FUNC_LOOP: UfsExecuteCommandFileLoop(); + +#ifdef USE_FTP + if (ftpSrv) { + ftpSrv->handleFTP(); + } +#endif + break; + + case FUNC_NETWORK_UP: +#ifdef USE_FTP + if (Settings->mbflag2.FTP_Mode && !ftpSrv) { + FTP_Server(Settings->mbflag2.FTP_Mode); + } +#endif + break; + /* // Moved to support_tasmota.ino for earlier init to be used by scripter #ifdef USE_SDCARD