summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Plasa <dplasa@gmail.com>2020-05-24 18:58:42 +0200
committerDaniel Plasa <dplasa@gmail.com>2020-05-24 18:58:42 +0200
commit453e61d5dc538e19e545075818e94a9508501c44 (patch)
tree4e1d4aae43bf8bdde9bbf5bfba27922eb9d0fbb7
parentMerge pull request #20 from sandtec65/master (diff)
downloadFTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar.gz
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar.bz2
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar.lz
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar.xz
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.tar.zst
FTPCLientServer-453e61d5dc538e19e545075818e94a9508501c44.zip
-rw-r--r--ESP8266FtpServer.cpp1021
-rw-r--r--ESP8266FtpServer.h106
-rw-r--r--README.md19
-rw-r--r--espFtpCommands.h63
-rw-r--r--espFtpServer.cpp1315
-rw-r--r--espFtpServer.h147
-rw-r--r--examples/FTPServerSample/FTPServerSample.ino50
-rw-r--r--examples/LittleFSSample/LittleFSSample.ino125
-rw-r--r--examples/SPIFFSSample/SPIFFSSample.ino115
-rw-r--r--library.json14
-rw-r--r--library.properties12
11 files changed, 1789 insertions, 1198 deletions
diff --git a/ESP8266FtpServer.cpp b/ESP8266FtpServer.cpp
deleted file mode 100644
index 98401c6..0000000
--- a/ESP8266FtpServer.cpp
+++ /dev/null
@@ -1,1021 +0,0 @@
-/*
- * FTP Serveur for ESP8266
- * based on FTP Serveur 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
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#include "ESP8266FtpServer.h"
-#ifdef ESP8266
-#include <ESP8266WiFi.h>
-#elif defined ESP32
-#include <WiFi.h>
-#include "SPIFFS.h"
-#endif
-#include <WiFiClient.h>
-#include <FS.h>
-
-
-
-
-WiFiServer ftpServer( FTP_CTRL_PORT );
-WiFiServer dataServer( FTP_DATA_PORT_PASV );
-
-void FtpServer::begin(String uname, String pword)
-{
- // Tells the ftp server to begin listening for incoming connection
- _FTP_USER=uname;
- _FTP_PASS = pword;
-
- ftpServer.begin();
- delay(10);
- dataServer.begin();
- delay(10);
- millisTimeOut = (uint32_t)FTP_TIME_OUT * 60 * 1000;
- millisDelay = 0;
- cmdStatus = 0;
- iniVariables();
-}
-
-void FtpServer::iniVariables()
-{
- // Default for data port
- dataPort = FTP_DATA_PORT_PASV;
-
- // Default Data connection is Active
- dataPassiveConn = true;
-
- // Set the root directory
- strcpy( cwdName, "/" );
-
- rnfrCmd = false;
- transferStatus = 0;
-
-}
-
-void FtpServer::handleFTP()
-{
- if((int32_t) ( millisDelay - millis() ) > 0 )
- return;
-
- if (ftpServer.hasClient()) {
- 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("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("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("530 Timeout");
- millisDelay = millis() + 200; // delay of 200 ms
- cmdStatus = 0;
- }
-}
-
-void FtpServer::clientConnected()
-{
- #ifdef FTP_DEBUG
- Serial.println("Client connected!");
- #endif
- client.println( "220--- Welcome to FTP for ESP8266/ESP32 ---");
- client.println( "220--- By David Paiva ---");
- client.println( "220 -- Version "+ String(FTP_SERVER_VERSION) +" --");
- iCL = 0;
-}
-
-void FtpServer::disconnectClient()
-{
- #ifdef FTP_DEBUG
- Serial.println(" Disconnecting client");
- #endif
- abortTransfer();
- client.println("221 Goodbye");
- client.stop();
-}
-
-boolean FtpServer::userIdentity()
-{
- if( strcmp( command, "USER" ))
- client.println( "500 Syntax error");
- if( strcmp( parameters, _FTP_USER.c_str() ))
- client.println( "530 user not found");
- else
- {
- client.println( "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( "500 Syntax error");
- else if( strcmp( parameters, _FTP_PASS.c_str() ))
- client.println( "530 ");
- else
- {
- #ifdef FTP_DEBUG
- Serial.println( "OK. Waiting for commands.");
- #endif
- client.println( "230 OK.");
- return true;
- }
- millisDelay = millis() + 100; // delay of 100 ms
- return false;
-}
-
-boolean FtpServer::processCommand()
-{
- ///////////////////////////////////////
- // //
- // ACCESS CONTROL COMMANDS //
- // //
- ///////////////////////////////////////
-
- //
- // CDUP - Change to Parent Directory
- //
- if( ! strcmp( command, "CDUP" ))
- {
- client.println("250 Ok. Current directory is " + String(cwdName));
- }
- //
- // CWD - Change Working Directory
- //
- else if( ! strcmp( command, "CWD" ))
- {
- char path[ FTP_CWD_SIZE ];
- if( strcmp( parameters, "." ) == 0 ) // 'CWD .' is the same as PWD command
- client.println( "257 \"" + String(cwdName) + "\" is your current directory");
- else
- {
- client.println( "250 Ok. Current directory is " + String(cwdName) );
- }
-
- }
- //
- // PWD - Print Directory
- //
- else if( ! strcmp( command, "PWD" ))
- client.println( "257 \"" + String(cwdName) + "\" 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( "200 S Ok");
- // else if( ! strcmp( parameters, "B" ))
- // client.println( "200 B Ok\r\n";
- else
- client.println( "504 Only S(tream) is suported");
- }
- //
- // PASV - Passive Connection management
- //
- else if( ! strcmp( command, "PASV" ))
- {
- if (data.connected()) data.stop();
- //dataServer.begin();
- //dataIp = Ethernet.localIP();
- dataIp = client.localIP();
- dataPort = FTP_DATA_PORT_PASV;
- //data.connect( dataIp, dataPort );
- //data = dataServer.available();
- #ifdef FTP_DEBUG
- Serial.println("Connection management set to passive");
- Serial.println( "Data port set to " + String(dataPort));
- #endif
- client.println( "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();
- // 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( "501 Can't interpret parameters");
- else
- {
-
- client.println("200 PORT command successful");
- dataPassiveConn = false;
- }
- }
- //
- // STRU - File Structure
- //
- else if( ! strcmp( command, "STRU" ))
- {
- if( ! strcmp( parameters, "F" ))
- client.println( "200 F Ok");
- // else if( ! strcmp( parameters, "R" ))
- // client.println( "200 B Ok\r\n";
- else
- client.println( "504 Only F(ile) is suported");
- }
- //
- // TYPE - Data Type
- //
- else if( ! strcmp( command, "TYPE" ))
- {
- if( ! strcmp( parameters, "A" ))
- client.println( "200 TYPE is now ASII");
- else if( ! strcmp( parameters, "I" ))
- client.println( "200 TYPE is now 8-bit binary");
- else
- client.println( "504 Unknow TYPE");
- }
-
- ///////////////////////////////////////
- // //
- // FTP SERVICE COMMANDS //
- // //
- ///////////////////////////////////////
-
- //
- // ABOR - Abort
- //
- else if( ! strcmp( command, "ABOR" ))
- {
- abortTransfer();
- client.println( "226 Data connection closed");
- }
- //
- // DELE - Delete a File
- //
- else if( ! strcmp( command, "DELE" ))
- {
- char path[ FTP_CWD_SIZE ];
- if( strlen( parameters ) == 0 )
- client.println( "501 No file name");
- else if( makePath( path ))
- {
- if( ! SPIFFS.exists( path ))
- client.println( "550 File " + String(parameters) + " not found");
- else
- {
- if( SPIFFS.remove( path ))
- client.println( "250 Deleted " + String(parameters) );
- else
- client.println( "450 Can't delete " + String(parameters));
- }
- }
- }
- //
- // LIST - List
- //
- else if( ! strcmp( command, "LIST" ))
- {
- if( ! dataConnect())
- client.println( "425 No data connection");
- else
- {
- client.println( "150 Accepted data connection");
- uint16_t nm = 0;
-#ifdef ESP8266
- Dir dir=SPIFFS.openDir(cwdName);
- // if( !SPIFFS.exists(cwdName))
- // client.println( "550 Can't open directory " + String(cwdName) );
- // else
- {
- while( dir.next())
- {
- String fn, fs;
- fn = dir.fileName();
- fn.remove(0, 1);
- fs = String(dir.fileSize());
- data.println( "+r,s" + fs);
- data.println( ",\t" + fn );
- nm ++;
- }
- client.println( "226 " + String(nm) + " matches total");
- }
-#elif defined ESP32
- File root = SPIFFS.open(cwdName);
- if(!root){
- client.println( "550 Can't open directory " + String(cwdName) );
- // return;
- } else {
- // if(!root.isDirectory()){
- // Serial.println("Not a directory");
- // return;
- // }
-
- File file = root.openNextFile();
- while(file){
- if(file.isDirectory()){
- data.println( "+r,s <DIR> " + String(file.name()));
- // Serial.print(" DIR : ");
- // Serial.println(file.name());
- // if(levels){
- // listDir(fs, file.name(), levels -1);
- // }
- } else {
- String fn, fs;
- fn = file.name();
- // fn.remove(0, 1);
- fs = String(file.size());
- data.println( "+r,s" + fs);
- data.println( ",\t" + fn );
- nm ++;
- }
- file = root.openNextFile();
- }
- client.println( "226 " + String(nm) + " matches total");
- }
-#endif
- data.stop();
- }
- }
- //
- // MLSD - Listing for Machine Processing (see RFC 3659)
- //
- else if( ! strcmp( command, "MLSD" ))
- {
- if( ! dataConnect())
- client.println( "425 No data connection MLSD");
- else
- {
- client.println( "150 Accepted data connection");
- uint16_t nm = 0;
-#ifdef ESP8266
- Dir dir= SPIFFS.openDir(cwdName);
- char dtStr[ 15 ];
- // if(!SPIFFS.exists(cwdName))
- // client.println( "550 Can't open directory " +String(parameters)+ );
- // else
- {
- while( dir.next())
- {
- String fn,fs;
- fn = dir.fileName();
- fn.remove(0, 1);
- fs = String(dir.fileSize());
- data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn);
- nm ++;
- }
- client.println( "226-options: -a -l");
- client.println( "226 " + String(nm) + " matches total");
- }
-#elif defined ESP32
- File root = SPIFFS.open(cwdName);
- // if(!root){
- // client.println( "550 Can't open directory " + String(cwdName) );
- // // return;
- // } else {
- // if(!root.isDirectory()){
- // Serial.println("Not a directory");
- // return;
- // }
-
- File file = root.openNextFile();
- while(file){
- // if(file.isDirectory()){
- // data.println( "+r,s <DIR> " + String(file.name()));
- // // Serial.print(" DIR : ");
- // // Serial.println(file.name());
- // // if(levels){
- // // listDir(fs, file.name(), levels -1);
- // // }
- // } else {
- String fn, fs;
- fn = file.name();
- fn.remove(0, 1);
- fs = String(file.size());
- data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn);
- nm ++;
- // }
- file = root.openNextFile();
- }
- client.println( "226-options: -a -l");
- client.println( "226 " + String(nm) + " matches total");
- // }
-#endif
- data.stop();
- }
- }
- //
- // NLST - Name List
- //
- else if( ! strcmp( command, "NLST" ))
- {
- if( ! dataConnect())
- client.println( "425 No data connection");
- else
- {
- client.println( "150 Accepted data connection");
- uint16_t nm = 0;
-#ifdef ESP8266
- Dir dir=SPIFFS.openDir(cwdName);
- // if( !SPIFFS.exists( cwdName ))
- // client.println( "550 Can't open directory " + String(parameters));
- // else
- {
- while( dir.next())
- {
- data.println( dir.fileName());
- nm ++;
- }
- client.println( "226 " + String(nm) + " matches total");
- }
-#elif defined ESP32
- File root = SPIFFS.open(cwdName);
- if(!root){
- client.println( "550 Can't open directory " + String(cwdName) );
- } else {
-
- File file = root.openNextFile();
- while(file){
- data.println( file.name());
- nm ++;
- file = root.openNextFile();
- }
- client.println( "226 " + String(nm) + " matches total");
- }
-#endif
- data.stop();
- }
- }
- //
- // NOOP
- //
- else if( ! strcmp( command, "NOOP" ))
- {
- // dataPort = 0;
- client.println( "200 Zzz...");
- }
- //
- // RETR - Retrieve
- //
- else if( ! strcmp( command, "RETR" ))
- {
- char path[ FTP_CWD_SIZE ];
- if( strlen( parameters ) == 0 )
- client.println( "501 No file name");
- else if( makePath( path ))
- {
- file = SPIFFS.open(path, "r");
- if( !file)
- client.println( "550 File " +String(parameters)+ " not found");
- else if( !file )
- client.println( "450 Can't open " +String(parameters));
- else if( ! dataConnect())
- client.println( "425 No data connection");
- else
- {
- #ifdef FTP_DEBUG
- Serial.println("Sending " + String(parameters));
- #endif
- client.println( "150-Connected to port "+ String(dataPort));
- client.println( "150 " + String(file.size()) + " bytes to download");
- millisBeginTrans = millis();
- bytesTransfered = 0;
- transferStatus = 1;
- }
- }
- }
- //
- // STOR - Store
- //
- else if( ! strcmp( command, "STOR" ))
- {
- char path[ FTP_CWD_SIZE ];
- if( strlen( parameters ) == 0 )
- client.println( "501 No file name");
- else if( makePath( path ))
- {
- file = SPIFFS.open(path, "w");
- if( !file)
- client.println( "451 Can't open/create " +String(parameters) );
- else if( ! dataConnect())
- {
- client.println( "425 No data connection");
- file.close();
- }
- else
- {
- #ifdef FTP_DEBUG
- Serial.println( "Receiving " +String(parameters));
- #endif
- client.println( "150 Connected to port " + String(dataPort));
- millisBeginTrans = millis();
- bytesTransfered = 0;
- transferStatus = 2;
- }
- }
- }
- //
- // MKD - Make Directory
- //
- else if( ! strcmp( command, "MKD" ))
- {
- client.println( "550 Can't create \"" + String(parameters)); //not support on espyet
- }
- //
- // RMD - Remove a Directory
- //
- else if( ! strcmp( command, "RMD" ))
- {
- client.println( "501 Can't delete \"" +String(parameters));
-
- }
- //
- // RNFR - Rename From
- //
- else if( ! strcmp( command, "RNFR" ))
- {
- buf[ 0 ] = 0;
- if( strlen( parameters ) == 0 )
- client.println( "501 No file name");
- else if( makePath( buf ))
- {
- if( ! SPIFFS.exists( buf ))
- client.println( "550 File " +String(parameters)+ " not found");
- else
- {
- #ifdef FTP_DEBUG
- Serial.println("Renaming " + String(buf));
- #endif
- client.println( "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( "503 Need RNFR before RNTO");
- else if( strlen( parameters ) == 0 )
- client.println( "501 No file name");
- else if( makePath( path ))
- {
- if( SPIFFS.exists( path ))
- client.println( "553 " +String(parameters)+ " already exists");
- else
- {
- #ifdef FTP_DEBUG
- Serial.println("Renaming " + String(buf) + " to " + String(path));
- #endif
- if( SPIFFS.rename( buf, path ))
- client.println( "250 File successfully renamed or moved");
- else
- client.println( "451 Rename/move failure");
-
- }
- }
- rnfrCmd = false;
- }
-
- ///////////////////////////////////////
- // //
- // EXTENSIONS COMMANDS (RFC 3659) //
- // //
- ///////////////////////////////////////
-
- //
- // FEAT - New Features
- //
- else if( ! strcmp( command, "FEAT" ))
- {
- client.println( "211-Extensions suported:");
- client.println( " MLSD");
- client.println( "211 End.");
- }
- //
- // MDTM - File Modification Time (see RFC 3659)
- //
- else if (!strcmp(command, "MDTM"))
- {
- client.println("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( "501 No file name");
- else if( makePath( path ))
- {
- file = SPIFFS.open(path, "r");
- if(!file)
- client.println( "450 Can't open " +String(parameters) );
- else
- {
- client.println( "213 " + String(file.size()));
- file.close();
- }
- }
- }
- //
- // SITE - System command
- //
- else if( ! strcmp( command, "SITE" ))
- {
- client.println( "500 Unknow SITE command " +String(parameters) );
- }
- //
- // Unrecognized commands ...
- //
- else
- client.println( "500 Unknow 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)
- {
- //delay(100);
- yield();
- }
- if (dataServer.hasClient()) {
- data.stop();
- data = dataServer.available();
- #ifdef FTP_DEBUG
- Serial.println("ftpdataserver client....");
- #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);
- bytesTransfered += 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 buf.
- if (navail > FTP_BUF_SIZE) navail = FTP_BUF_SIZE;
- int16_t nb = data.read((uint8_t*) buf, navail );
- // int16_t nb = data.readBytes((uint8_t*) buf, FTP_BUF_SIZE );
- if( nb > 0 )
- {
- // Serial.println( millis() << " " << nb << endl;
- file.write((uint8_t*) buf, nb );
- bytesTransfered += 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 && bytesTransfered > 0 )
- {
- client.println( "226-File successfully transferred");
- client.println( "226 " + String(deltaT) + " ms, "+ String(bytesTransfered / deltaT) + " kbytes/s");
- }
- else
- client.println( "226 File successfully transferred");
-
- file.close();
- data.stop();
-}
-
-void FtpServer::abortTransfer()
-{
- if( transferStatus > 0 )
- {
- file.close();
- data.stop();
- client.println( "426 Transfer aborted" );
- #ifdef FTP_DEBUG
- Serial.println( "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();
- // char c;
- // client.readBytes((uint8_t*) c, 1);
- #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( "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( "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;
-}
-
diff --git a/ESP8266FtpServer.h b/ESP8266FtpServer.h
deleted file mode 100644
index 00a8057..0000000
--- a/ESP8266FtpServer.h
+++ /dev/null
@@ -1,106 +0,0 @@
-
-/*
-* FTP SERVER FOR ESP8266
- * based on FTP Serveur 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)
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-/*******************************************************************************
- ** **
- ** DEFINITIONS FOR FTP SERVER **
- ** **
- *******************************************************************************/
-
-// Uncomment to print debugging info to console attached to ESP8266
-//#define FTP_DEBUG
-
-#ifndef FTP_SERVERESP_H
-#define FTP_SERVERESP_H
-
-//#include "Streaming.h"
-#include <FS.h>
-#include <WiFiClient.h>
-
-#define FTP_SERVER_VERSION "FTP-2017-10-18"
-
-#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
-#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 1024 //512 // size of file buffer for read/write
-#define FTP_BUF_SIZE 2*1460 //512 // size of file buffer for read/write
-
-class FtpServer
-{
-public:
- void begin(String uname, String pword);
- void handleFTP();
-
-private:
- 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();
-
- IPAddress dataIp; // IP address of client for data
- WiFiClient client;
- WiFiClient data;
-
- 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
- bytesTransfered; //
- String _FTP_USER;
- String _FTP_PASS;
-
-
-
-};
-
-#endif // FTP_SERVERESP_H
-
-
diff --git a/README.md b/README.md
index dd9bc01..bee7d6e 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,25 @@
# esp8266FTPServer
Simple FTP Server for using esp8266/esp32 SPIFFs
+I've modified a FTP server from arduino/wifi shield to work with esp8266/esp32
-Now should support esp32!!!
+It should support esp32 and is also using LittleFS as SPIFFS has become deprecated.
+Use
+```cpp
+#define esp8266FTPServer_SPIFFS
+#include <esp8266FTPServer.h>
+```
+to switch back to SPIFFS, if you need to.
-I've modified a FTP server from arduino/wifi shield to work with esp8266....
+This allows you to FTP into your esp8266/esp32 and access/modify the LittleFS/SPIFFS folder/data...it only allows one ftp connection at a time....very simple for now...
-This allows you to FTP into your esp8266 and access/modify the spiffs folder/data...it only allows one ftp connection at a time....very simple for now...
+I've tested it with Filezilla, and the basics work (upload/download/rename/delete). There's no create/modify directory support in SPIFFS but in LittleFS there is!
-I've tested it with Filezilla, and the basics work (upload/download/rename/delete). There's no create/modify directory support(no directory support in SPIFFS yet).
-
-You need to setup Filezilla(or other client) to only allow 1 connection..
+You need to setup Filezilla (or other client) to only allow **1** connection..
To force FileZilla to use the primary connection for data transfers:
Go to File/Site Manager then select your site.
In Transfer Settings, check "Limit number of simultaneous connections" and set the maximum to 1
-only supports Passive ftp mode....
+It also only supports Passive ftp mode.
It does NOT support any encryption, so you'll have to disable any form of encryption...
diff --git a/espFtpCommands.h b/espFtpCommands.h
new file mode 100644
index 0000000..4cf8c21
--- /dev/null
+++ b/espFtpCommands.h
@@ -0,0 +1,63 @@
+#ifndef ESP_FTP_COMMANDS_H
+#define ESP_FTP_COMMANDS_H
+
+#define FTP_CMD(CMD) (FTP_CMD_LE_##CMD)
+
+#define FTP_CMD_LE_USER 0x52455355 // "USER" as uint32_t (little endian)
+#define FTP_CMD_BE_USER 0x55534552 // "USER" as uint32_t (big endian)
+#define FTP_CMD_LE_PASS 0x53534150 // "PASS" as uint32_t (little endian)
+#define FTP_CMD_BE_PASS 0x50415353 // "PASS" as uint32_t (big endian)
+#define FTP_CMD_LE_QUIT 0x54495551 // "QUIT" as uint32_t (little endian)
+#define FTP_CMD_BE_QUIT 0x51554954 // "QUIT" as uint32_t (big endian)
+#define FTP_CMD_LE_CDUP 0x50554443 // "CDUP" as uint32_t (little endian)
+#define FTP_CMD_BE_CDUP 0x43445550 // "CDUP" as uint32_t (big endian)
+#define FTP_CMD_LE_CWD 0x00445743 // "CWD" as uint32_t (little endian)
+#define FTP_CMD_BE_CWD 0x43574400 // "CWD" as uint32_t (big endian)
+#define FTP_CMD_LE_PWD 0x00445750 // "PWD" as uint32_t (little endian)
+#define FTP_CMD_BE_PWD 0x50574400 // "PWD" as uint32_t (big endian)
+#define FTP_CMD_LE_MODE 0x45444f4d // "MODE" as uint32_t (little endian)
+#define FTP_CMD_BE_MODE 0x4d4f4445 // "MODE" as uint32_t (big endian)
+#define FTP_CMD_LE_PASV 0x56534150 // "PASV" as uint32_t (little endian)
+#define FTP_CMD_BE_PASV 0x50415356 // "PASV" as uint32_t (big endian)
+#define FTP_CMD_LE_PORT 0x54524f50 // "PORT" as uint32_t (little endian)
+#define FTP_CMD_BE_PORT 0x504f5254 // "PORT" as uint32_t (big endian)
+#define FTP_CMD_LE_STRU 0x55525453 // "STRU" as uint32_t (little endian)
+#define FTP_CMD_BE_STRU 0x53545255 // "STRU" as uint32_t (big endian)
+#define FTP_CMD_LE_TYPE 0x45505954 // "TYPE" as uint32_t (little endian)
+#define FTP_CMD_BE_TYPE 0x54595045 // "TYPE" as uint32_t (big endian)
+#define FTP_CMD_LE_ABOR 0x524f4241 // "ABOR" as uint32_t (little endian)
+#define FTP_CMD_BE_ABOR 0x41424f52 // "ABOR" as uint32_t (big endian)
+#define FTP_CMD_LE_DELE 0x454c4544 // "DELE" as uint32_t (little endian)
+#define FTP_CMD_BE_DELE 0x44454c45 // "DELE" as uint32_t (big endian)
+#define FTP_CMD_LE_LIST 0x5453494c // "LIST" as uint32_t (little endian)
+#define FTP_CMD_BE_LIST 0x4c495354 // "LIST" as uint32_t (big endian)
+#define FTP_CMD_LE_MLSD 0x44534c4d // "MLSD" as uint32_t (little endian)
+#define FTP_CMD_BE_MLSD 0x4d4c5344 // "MLSD" as uint32_t (big endian)
+#define FTP_CMD_LE_NLST 0x54534c4e // "NLST" as uint32_t (little endian)
+#define FTP_CMD_BE_NLST 0x4e4c5354 // "NLST" as uint32_t (big endian)
+#define FTP_CMD_LE_NOOP 0x504f4f4e // "NOOP" as uint32_t (little endian)
+#define FTP_CMD_BE_NOOP 0x4e4f4f50 // "NOOP" as uint32_t (big endian)
+#define FTP_CMD_LE_RETR 0x52544552 // "RETR" as uint32_t (little endian)
+#define FTP_CMD_BE_RETR 0x52455452 // "RETR" as uint32_t (big endian)
+#define FTP_CMD_LE_STOR 0x524f5453 // "STOR" as uint32_t (little endian)
+#define FTP_CMD_BE_STOR 0x53544f52 // "STOR" as uint32_t (big endian)
+#define FTP_CMD_LE_MKD 0x00444b4d // "MKD" as uint32_t (little endian)
+#define FTP_CMD_BE_MKD 0x4d4b4400 // "MKD" as uint32_t (big endian)
+#define FTP_CMD_LE_RMD 0x00444d52 // "RMD" as uint32_t (little endian)
+#define FTP_CMD_BE_RMD 0x524d4400 // "RMD" as uint32_t (big endian)
+#define FTP_CMD_LE_RNFR 0x52464e52 // "RNFR" as uint32_t (little endian)
+#define FTP_CMD_BE_RNFR 0x524e4652 // "RNFR" as uint32_t (big endian)
+#define FTP_CMD_LE_RNTO 0x4f544e52 // "RNTO" as uint32_t (little endian)
+#define FTP_CMD_BE_RNTO 0x524e544f // "RNTO" as uint32_t (big endian)
+#define FTP_CMD_LE_FEAT 0x54414546 // "FEAT" as uint32_t (little endian)
+#define FTP_CMD_BE_FEAT 0x46454154 // "FEAT" as uint32_t (big endian)
+#define FTP_CMD_LE_MDTM 0x4d54444d // "MDTM" as uint32_t (little endian)
+#define FTP_CMD_BE_MDTM 0x4d44544d // "MDTM" as uint32_t (big endian)
+#define FTP_CMD_LE_SIZE 0x455a4953 // "SIZE" as uint32_t (little endian)
+#define FTP_CMD_BE_SIZE 0x53495a45 // "SIZE" as uint32_t (big endian)
+#define FTP_CMD_LE_SITE 0x45544953 // "SITE" as uint32_t (little endian)
+#define FTP_CMD_BE_SITE 0x53495445 // "SITE" as uint32_t (big endian)
+#define FTP_CMD_LE_SYST 0x54535953 // "SYST" as uint32_t (little endian)
+#define FTP_CMD_BE_SYST 0x53595354 // "SYST" as uint32_t (big endian)
+
+#endif // ESP_FTP_COMMANDS_H
diff --git a/espFtpServer.cpp b/espFtpServer.cpp
new file mode 100644
index 0000000..1acc655
--- /dev/null
+++ b/espFtpServer.cpp
@@ -0,0 +1,1315 @@
+/*
+ * FTP Server for ESP8266/ESP32
+ * based on FTP Serveur 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
+ * modified to work with esp8266 LitteFS by Daniel Plasa dplasa@gmail.com
+ * Also done some code reworks and all string contants are now in flash memory
+ * by using F(), PSTR() ... on the string literals.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+// code 330252-304168 = 26084 with SPIFFS
+// code 324512-298440 = 26072 with LittleFS
+#include "espFtpServer.h"
+
+#ifdef ESP8266
+#include <ESP8266WiFi.h>
+#elif defined ESP32
+#include <WiFi.h>
+#endif
+
+#include "espFtpCommands.h"
+
+WiFiServer controlServer(FTP_CTRL_PORT);
+WiFiServer dataServer(FTP_DATA_PORT_PASV);
+
+// helper macros
+#define FTP_STR(s) FTP_STR2(s)
+#define FTP_STR2(s) #s
+#define FTP_SEND_MSG(code, fmt, ...) \
+ do \
+ { \
+ FTP_DEBUG_MSG(">>> " FTP_STR(code) " " fmt, ##__VA_ARGS__); \
+ control.printf_P(PSTR(FTP_STR(code) " " fmt "\r\n"), ##__VA_ARGS__); \
+ } while (0)
+
+#define FTP_SEND_DASHMSG(code, fmt, ...) \
+ do \
+ { \
+ FTP_DEBUG_MSG(">>> " FTP_STR(code) "-" fmt, ##__VA_ARGS__); \
+ control.printf_P(PSTR(FTP_STR(code) "-" fmt "\r\n"), ##__VA_ARGS__); \
+ } while (0)
+
+// some constants
+static const char aSpace[] PROGMEM = " ";
+static const char aSlash[] PROGMEM = "/";
+
+// constructor
+FtpServer::FtpServer(FS &_FSImplementation) : THEFS(_FSImplementation) {}
+
+void FtpServer::begin(const String &uname, const String &pword)
+{
+ _FTP_USER = uname;
+ _FTP_PASS = pword;
+
+ iniVariables();
+
+ // Tells the ftp server to begin listening for incoming connections
+ controlServer.begin();
+ dataServer.begin();
+}
+
+void FtpServer::stop()
+{
+ abortTransfer();
+ disconnectClient(false);
+ controlServer.stop();
+ dataServer.stop();
+}
+
+void FtpServer::iniVariables()
+{
+ // Default Data connection is Active
+ dataPassiveConn = true;
+
+ // Set the root directory
+ cwd = FPSTR(aSlash);
+
+ // init internal status vars
+ cmdState = cInit;
+ transferState = tIdle;
+ rnFrom.clear();
+
+ // reset control connection input buffer, clear previous command
+ cmdLine.clear();
+ cmdString.clear();
+ parameters.clear();
+ command = 0;
+
+ // free any used fileBuffer
+ freeBuffer();
+}
+
+void FtpServer::handleFTP()
+{
+ // if ((int32_t)(millisDelay - millis()) > 0)
+ // return;
+
+ //
+ // control connection state sequence is
+ // cInit
+ // |
+ // V
+ // cWait
+ // |
+ // V
+ // cCheck -----------+
+ // | | (no username but password set)
+ // V |
+ // cUserId ----------+---+
+ // | | |
+ // +<--------------+ |
+ // V | (no password set)
+ // cPassword |
+ // | |
+ // +<------------------+
+ // V
+ // cLoginOk
+ // |
+ // V
+ // cProcess
+ //
+ if (cmdState == cInit)
+ {
+ if (control.connected())
+ {
+ abortTransfer();
+ disconnectClient(false);
+ }
+ iniVariables();
+ cmdState = cWait;
+ }
+
+ else if (cmdState == cWait) // FTP control server waiting for connection
+ {
+ if (controlServer.hasClient())
+ {
+ control = controlServer.available();
+
+ // wait 10s for login command
+ updateTimeout(10);
+ cmdState = cCheck;
+ }
+ }
+
+ else if (cmdState == cCheck) // FTP control server check/setup control connection
+ {
+ if (control.connected()) // A client connected, say "220 Hello client!"
+ {
+ FTP_DEBUG_MSG("control server got connection from %s:%d",
+ control.remoteIP().toString().c_str(), control.remotePort());
+
+ FTP_SEND_MSG(220, "(espFTP " FTP_SERVER_VERSION ")");
+
+ if (_FTP_USER.length())
+ {
+ // FTP_SEND_MSG(332, "Need account for login.");
+ cmdState = cUserId;
+ }
+ else if (_FTP_PASS.length())
+ {
+ // FTP_SEND_MSG(331, "Please specify the password.");
+ cmdState = cPassword;
+ }
+ else
+ {
+ cmdState = cLoginOk;
+ }
+ }
+ }
+
+ else if (cmdState == cLoginOk) // tell client "Login ok!"
+ {
+ FTP_SEND_MSG(230, "Login successful.");
+ updateTimeout(sTimeOut);
+ cmdState = cProcess;
+ }
+
+ //
+ // all other command states need to process commands froms control connection
+ //
+ else if (readChar() > 0)
+ {
+ // enforce USER than PASS commands before anything else
+ if (((cmdState == cUserId) && (FTP_CMD(USER) != command)) ||
+ ((cmdState == cPassword) && (FTP_CMD(PASS) != command)))
+ {
+ FTP_SEND_MSG(530, "Please login with USER and PASS.");
+ FTP_DEBUG_MSG("ignoring before login: cwd=%s cmd[%x]=%s, params='%s'", cwd.c_str(), command, cmdString.c_str(), parameters.c_str());
+ command = 0;
+ return;
+ }
+
+ // process the command
+ int8_t rc = processCommand();
+ // returns
+ // -1 : command processing indicates, we have to close control (e.g. QUIT)
+ // 0 : not yet finished, just call processCommend() again
+ // 1 : finished
+ if (rc < 0)
+ {
+ cmdState = cInit;
+ }
+ if (rc > 0)
+ {
+ // clear current command, so readChar() can fetch the next command
+ command = 0;
+
+ // command was successful, update command state
+ if (cmdState == cUserId)
+ {
+ if (_FTP_PASS.length())
+ {
+ // wait 10s for PASS command
+ updateTimeout(10);
+ FTP_SEND_MSG(331, "Please specify the password.");
+ cmdState = cPassword;
+ }
+ else
+ {
+ cmdState = cLoginOk;
+ }
+ }
+ else if (cmdState == cPassword)
+ {
+ cmdState = cLoginOk;
+ }
+ else
+ {
+ updateTimeout(sTimeOut);
+ }
+ }
+ }
+
+ //
+ // general connection handling
+ // (if we have an established control connection)
+ //
+ if (cmdState >= cCheck)
+ {
+ // detect lost/closed by remote connections
+ if (!control.connected() || !control)
+ {
+ cmdState = cInit;
+ FTP_DEBUG_MSG("client lost or disconnected");
+ }
+
+ // check for timeout
+ if (!((int32_t)(millisEndConnection - millis()) > 0))
+ {
+ FTP_SEND_MSG(530, "Timeout.");
+ FTP_DEBUG_MSG("client connection timed out");
+ cmdState = cInit;
+ }
+
+ // handle data file transfer
+ if (transferState == tRetrieve) // Retrieve data
+ {
+ if (!doRetrieve())
+ {
+ closeTransfer();
+ transferState = tIdle;
+ }
+ }
+ else if (transferState == tStore) // Store data
+ {
+ if (!doStore())
+ {
+ closeTransfer();
+ transferState = tIdle;
+ }
+ }
+ }
+}
+
+void FtpServer::disconnectClient(bool gracious)
+{
+ FTP_DEBUG_MSG("Disconnecting client");
+ abortTransfer();
+ if (gracious)
+ {
+ FTP_SEND_MSG(221, "Goodbye.");
+ }
+ else
+ {
+ FTP_SEND_MSG(231, "Service terminated.");
+ }
+ control.stop();
+}
+
+int8_t FtpServer::processCommand()
+{
+ // assume successful operation by default
+ int8_t rc = 1;
+
+ // make the full path of parameters (even if this makes no sense for all commands)
+ String path = getFileName(parameters, true);
+ FTP_DEBUG_MSG("processing: cmd=%s[%x], params='%s' (cwd='%s')", cmdString.c_str(), command, parameters.c_str());
+
+ ///////////////////////////////////////
+ // //
+ // ACCESS CONTROL COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // USER - Provide username
+ //
+ if (FTP_CMD(USER) == command)
+ {
+ if (_FTP_USER.length() && (_FTP_USER != parameters))
+ {
+ FTP_SEND_MSG(430, "User not found.");
+ command = 0;
+ rc = 0;
+ }
+ else
+ {
+ FTP_DEBUG_MSG("USER ok");
+ }
+ }
+
+ //
+ // PASS - Provide password
+ //
+ else if (FTP_CMD(PASS) == command)
+ {
+ if (_FTP_PASS.length() && (_FTP_PASS != parameters))
+ {
+ FTP_SEND_MSG(430, "Password invalid.");
+ command = 0;
+ rc = 0;
+ }
+ else
+ {
+ FTP_DEBUG_MSG("PASS ok");
+ }
+ }
+
+ //
+ // QUIT
+ //
+ else if (FTP_CMD(QUIT) == command)
+ {
+ disconnectClient();
+ rc = -1;
+ }
+
+ //
+ // NOOP
+ //
+ else if (FTP_CMD(NOOP) == command)
+ {
+ FTP_SEND_MSG(200, "Zzz...");
+ }
+
+ //
+ // CDUP - Change to Parent Directory
+ //
+ else if (FTP_CMD(CDUP) == command)
+ {
+ // up one level
+ cwd = getPathName("", false);
+ FTP_SEND_MSG(250, "Directory successfully changed.");
+ }
+
+ //
+ // CWD - Change Working Directory
+ //
+ else if (FTP_CMD(CWD) == command)
+ {
+ if (parameters == F(".")) // 'CWD .' is the same as PWD command
+ {
+ command = FTP_CMD(PWD); // make CWD a PWD command ;-)
+ rc = 0; // indicate we need another processCommand() call
+ }
+ else if (parameters == F("..")) // 'CWD ..' is the same as CDUP command
+ {
+ command = FTP_CMD(CDUP); // make CWD a CDUP command ;-)
+ rc = 0; // indicate we need another processCommand() call
+ }
+ else
+ {
+#if (defined esp8266FTPServer_SPIFFS)
+ // SPIFFS has no directories, it's always ok
+ cwd = path;
+ FTP_SEND_MSG(250, "Directory successfully changed.");
+#else
+ // check if directory exists
+ file = THEFS.open(path, "r");
+ if (file.isDirectory())
+ {
+ cwd = path;
+ FTP_SEND_MSG(250, "Directory successfully changed.");
+ }
+ else
+ {
+ FTP_SEND_MSG(550, "Failed to change directory.");
+ }
+ file.close();
+#endif
+ }
+ }
+
+ //
+ // PWD - Print Directory
+ //
+ else if (FTP_CMD(PWD) == command)
+ {
+ FTP_SEND_MSG(257, "\"%s\" is the current directory.", cwd.c_str());
+ }
+
+ ///////////////////////////////////////
+ // //
+ // TRANSFER PARAMETER COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // MODE - Transfer Mode
+ //
+ else if (FTP_CMD(MODE) == command)
+ {
+ if (parameters == F("S"))
+ FTP_SEND_MSG(504, "Only S(tream) mode is suported");
+ else
+ FTP_SEND_MSG(200, "Mode set to S.");
+ }
+
+ //
+ // PASV - Passive data connection management
+ //
+ else if (FTP_CMD(PASV) == command)
+ {
+ // stop a possible previous data connection
+ data.stop();
+ // tell client to open data connection to our ip:dataPort
+ dataPort = FTP_DATA_PORT_PASV;
+ dataPassiveConn = true;
+ String ip = control.localIP().toString();
+ ip.replace(".", ",");
+ FTP_SEND_MSG(227, "Entering Passive Mode (%s,%d,%d).", ip.c_str(), dataPort >> 8, dataPort & 255);
+ }
+
+ //
+ // PORT - Data Port, Active data connection management
+ //
+ else if (FTP_CMD(PORT) == command)
+ {
+ if (data)
+ data.stop();
+
+ // parse IP and data port of "PORT ip,ip,ip,ip,port,port"
+ uint8_t parsecount = 0;
+ uint8_t tmp[6];
+ const char *p = parameters.c_str();
+ while (parsecount < sizeof(tmp))
+ {
+ tmp[parsecount++] = atoi(p);
+ p = strchr(p, ',');
+ if (NULL == p || *(++p) == '\0')
+ break;
+ }
+ if (parsecount < sizeof(tmp))
+ {
+ FTP_SEND_MSG(501, "Can't interpret parameters");
+ }
+ else
+ {
+ // copy first 4 bytes = IP
+ for (uint8_t i = 0; i < 4; ++i)
+ dataIP[i] = tmp[i];
+ // data port is 5,6
+ dataPort = tmp[4] * 256 + tmp[5];
+ FTP_SEND_MSG(200, "PORT command successful");
+ dataPassiveConn = false;
+ FTP_DEBUG_MSG("Data connection management Active, using %s:%u", dataIP.toString().c_str(), dataPort);
+ }
+ }
+
+ //
+ // STRU - File Structure
+ //
+ else if (FTP_CMD(STRU) == command)
+ {
+ if (parameters == F("F"))
+ FTP_SEND_MSG(504, "Only F(ile) is suported");
+ else
+ FTP_SEND_MSG(200, "Structure set to F.");
+ }
+
+ //
+ // TYPE - Data Type
+ //
+ else if (FTP_CMD(TYPE) == command)
+ {
+ if (parameters == F("A"))
+ FTP_SEND_MSG(200, "TYPE is now ASII.");
+ else if (parameters == F("I"))
+ FTP_SEND_MSG(200, "TYPE is now 8-bit Binary.");
+ else
+ FTP_SEND_MSG(504, "Unrecognised TYPE.");
+ }
+
+ ///////////////////////////////////////
+ // //
+ // FTP SERVICE COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // ABOR - Abort
+ //
+ else if (FTP_CMD(ABOR) == command)
+ {
+ abortTransfer();
+ FTP_SEND_MSG(226, "Data connection closed");
+ }
+
+ //
+ // DELE - Delete a File
+ //
+ else if (FTP_CMD(DELE) == command)
+ {
+ if (parameters.length() == 0)
+ FTP_SEND_MSG(501, "No file name");
+ else
+ {
+ if (!THEFS.exists(path))
+ {
+ FTP_SEND_MSG(550, "Delete operation failed, file '%s' not found.", path.c_str());
+ }
+ else if (THEFS.remove(path))
+ {
+ FTP_SEND_MSG(250, "Delete operation successful.");
+ }
+ else
+ {
+ FTP_SEND_MSG(450, "Delete operation failed.");
+ }
+ }
+ }
+
+ //
+ // LIST - List directory contents
+ // MLSD - Listing for Machine Processing (see RFC 3659)
+ // NLST - Name List
+ //
+ else if ((FTP_CMD(LIST) == command) || (FTP_CMD(MLSD) == command) || (FTP_CMD(NLST) == command))
+ {
+ rc = dataConnect(); // returns -1: no data connection, 0: need more time, 1: data ok
+ if (rc < 0)
+ {
+ FTP_SEND_MSG(425, "No data connection");
+ rc = 1; // mark command as processed
+ }
+ else if (rc > 0)
+ {
+ FTP_SEND_MSG(150, "Accepted data connection");
+ uint16_t dirCount = 0;
+ Dir dir = THEFS.openDir(path);
+ while (dir.next())
+ {
+ ++dirCount;
+ bool isDir = false;
+ String fn = dir.fileName();
+ if (cwd == FPSTR(aSlash) && fn[0] == '/')
+ fn.remove(0, 1);
+ isDir = dir.isDirectory();
+ if (FTP_CMD(LIST) == command)
+ {
+ if (isDir)
+ {
+ data.printf_P(PSTR("+d\r\n,\t%s\r\n"), fn.c_str());
+ }
+ else
+ {
+ data.printf_P(PSTR("+r,s%lu\r\n,\t%s\r\n"), (uint32_t)dir.fileSize(), fn.c_str());
+ }
+ }
+ else if (FTP_CMD(MLSD) == command)
+ {
+ // "modify=20170122163911;type=dir;UNIX.group=0;UNIX.mode=0775;UNIX.owner=0; dirname"
+ // "modify=20170121000817;size=12;type=file;UNIX.group=0;UNIX.mode=0644;UNIX.owner=0; filename"
+ file = dir.openFile("r");
+ data.printf_P(PSTR("modify=%s;UNIX.group=0;UNIX.owner=0;UNIX.mode="), makeDateTimeStr(file.getLastWrite()).c_str());
+ file.close();
+ if (isDir)
+ {
+ data.printf_P(PSTR("0755;type=dir; "));
+ }
+ else
+ {
+ data.printf_P(PSTR("0644;size=%lu;type=file; "), (uint32_t)dir.fileSize());
+ }
+ data.printf_P(PSTR("%s\r\n"), fn.c_str());
+ }
+ else if (FTP_CMD(NLST) == command)
+ {
+ data.println(fn);
+ }
+ else
+ {
+ FTP_DEBUG_MSG("Implemetation of %s [%x] command - internal BUG", cmdString.c_str(), command);
+ }
+ }
+
+ if (FTP_CMD(MLSD) == command)
+ {
+ control.println(F("226-options: -a -l\r\n"));
+ }
+ FTP_SEND_MSG(226, "%d matches total", dirCount);
+ }
+#if defined ESP32
+ File root = THEFS.open(cwd);
+ if (!root)
+ {
+ FTP_SEND_MSG(550, "Can't open directory " + cwd);
+ // return;
+ }
+ else
+ {
+ // if(!root.isDirectory()){
+ // FTP_DEBUG_MSG("Not a directory: '%s'", cwd.c_str());
+ // return;
+ // }
+
+ File file = root.openNextFile();
+ while (file)
+ {
+ if (file.isDirectory())
+ {
+ data.println("+r,s <DIR> " + String(file.name()));
+ // Serial.print(" DIR : ");
+ // Serial.println(file.name());
+ // if(levels){
+ // listDir(fs, file.name(), levels -1);
+ // }
+ }
+ else
+ {
+ String fn, fs;
+ fn = file.name();
+ // fn.remove(0, 1);
+ fs = String(file.size());
+ data.println("+r,s" + fs);
+ data.println(",\t" + fn);
+ nm++;
+ }
+ file = root.openNextFile();
+ }
+ FTP_SEND_MSG(226, "%s matches total", nm);
+ }
+#endif
+ data.stop();
+ }
+
+#if defined ESP32
+ //
+ // FIXME MLSD ESP32
+ //
+ else if (!strcmp(command, "MLSD"))
+ {
+ File root = THEFS.open(cwd);
+ // if(!root){
+ // control.println( "550, "Can't open directory " + cwd );
+ // // return;
+ // } else {
+ // if(!root.isDirectory()){
+ // Serial.println("Not a directory");
+ // return;
+ // }
+
+ File file = root.openNextFile();
+ while (file)
+ {
+ // if(file.isDirectory()){
+ // data.println( "+r,s <DIR> " + String(file.name()));
+ // // Serial.print(" DIR : ");
+ // // Serial.println(file.name());
+ // // if(levels){
+ // // listDir(fs, file.name(), levels -1);
+ // // }
+ // } else {
+ String fn, fs;
+ fn = file.name();
+ fn.remove(0, 1);
+ fs = String(file.size());
+ data.println("Type=file;Size=" + fs + ";" + "modify=20000101160656;" + " " + fn);
+ nm++;
+ // }
+ file = root.openNextFile();
+ }
+ FTP_SEND_MSG(226, "-options: -a -l");
+ FTP_SEND_MSG(226, "%d matches total", nm);
+ // }
+ data.stop();
+ }
+ //
+ // NLST
+ //
+ else if (!strcmp(command, "NLST"))
+ {
+ File root = THEFS.open(cwd);
+ if (!root)
+ {
+ FTP_SEND_MSG(550, "Can't open directory %s\n"), cwd.c_str());
+ }
+ else
+ {
+ File file = root.openNextFile();
+ while (file)
+ {
+ data.println(file.name());
+ nm++;
+ file = root.openNextFile();
+ }
+ FTP_SEND_MSG(226, "%d matches total", nm);
+ }
+ data.stop();
+ }
+#endif
+
+ //
+ // RETR - Retrieve
+ //
+ else if (FTP_CMD(RETR) == command)
+ {
+ if (parameters.length() == 0)
+ {
+ FTP_SEND_MSG(501, "No file name");
+ }
+ else
+ {
+ // open the file if not opened before (when re-running processCommand() since data connetion needs time)
+ if (!file)
+ file = THEFS.open(path, "r");
+ if (!file)
+ {
+ FTP_SEND_MSG(550, "File '%s' not found.", parameters.c_str());
+ }
+ else if (!file.isFile())
+ {
+ FTP_SEND_MSG(450, "Cannot open file \"%s\".", parameters.c_str());
+ }
+ else
+ {
+ rc = dataConnect(); // returns -1: no data connection, 0: need more time, 1: data ok
+ if (rc < 0)
+ {
+ FTP_SEND_MSG(425, "No data connection");
+ rc = 1; // mark command as processed
+ }
+ else if (rc > 0)
+ {
+ transferState = tRetrieve;
+ millisBeginTrans = millis();
+ bytesTransfered = 0;
+ if (allocateBuffer(file.size()) <= 0)
+ {
+ closeTransfer();
+ FTP_SEND_MSG(451, "Internal error. Not enough memory.");
+ }
+ else
+ {
+ FTP_DEBUG_MSG("Sending file '%s'", path.c_str());
+ FTP_SEND_MSG(150, "%lu bytes to download", (uint32_t)file.size());
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // STOR - Store
+ //
+ else if (FTP_CMD(STOR) == command)
+ {
+ if (parameters.length() == 0)
+ {
+ FTP_SEND_MSG(501, "No file name.");
+ }
+ else
+ {
+ FTP_DEBUG_MSG("STOR '%s'", path.c_str());
+ if (!file)
+ file = THEFS.open(path, "w");
+ if (!file)
+ {
+ FTP_SEND_MSG(451, "Cannot open/create \"%s\"", path.c_str());
+ }
+ else
+ {
+ rc = dataConnect(); // returns -1: no data connection, 0: need more time, 1: data ok
+ if (rc < 0)
+ {
+ FTP_SEND_MSG(425, "No data connection");
+ file.close();
+ rc = 1; // mark command as processed
+ }
+ else if (rc > 0)
+ {
+ transferState = tStore;
+ millisBeginTrans = millis();
+ bytesTransfered = 0;
+ if (allocateBuffer(2048) <= 0)
+ {
+ closeTransfer();
+ FTP_SEND_MSG(451, "Internal error. Not enough memory.");
+ }
+ else
+ {
+ FTP_DEBUG_MSG("Receiving file '%s' => %s", parameters.c_str(), path.c_str());
+ FTP_SEND_MSG(150, "Connected to port %d", dataPort);
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // MKD - Make Directory
+ //
+ else if (FTP_CMD(MKD) == command)
+ {
+#if (defined esp8266FTPServer_SPIFFS)
+ FTP_SEND_MSG(550, "Create directory operation failed."); //not support on SPIFFS
+#else
+ FTP_DEBUG_MSG("mkdir(%s)", path.c_str());
+ if (THEFS.mkdir(path))
+ {
+ FTP_SEND_MSG(257, "\"%s\" created.", path.c_str());
+ }
+ else
+ {
+ FTP_SEND_MSG(550, "Create directory operation failed.");
+ }
+#endif
+ }
+
+ //
+ // RMD - Remove a Directory
+ //
+ else if (FTP_CMD(RMD) == command)
+ {
+#if (defined esp8266FTPServer_SPIFFS)
+ FTP_SEND_MSG(550, "Remove directory operation failed."); //not support on SPIFFS
+#else
+ // check directory for files
+ Dir dir = THEFS.openDir(path);
+ if (dir.next())
+ {
+ //only delete if dir is empty!
+ FTP_SEND_MSG(550, "Remove directory operation failed, directory is not empty.");
+ }
+ else
+ {
+ THEFS.rmdir(path);
+ FTP_SEND_MSG(250, "Remove directory operation successful.");
+ }
+#endif
+ }
+ //
+ // RNFR - Rename From
+ //
+ else if (FTP_CMD(RNFR) == command)
+ {
+ if (parameters.length() == 0)
+ FTP_SEND_MSG(501, "No file name");
+ else
+ {
+ if (!THEFS.exists(path))
+ FTP_SEND_MSG(550, "File \"%s\" not found.", path.c_str());
+ else
+ {
+ FTP_SEND_MSG(350, "RNFR accepted - file \"%s\" exists, ready for destination", path.c_str());
+ rnFrom = path;
+ }
+ }
+ }
+ //
+ // RNTO - Rename To
+ //
+ else if (FTP_CMD(RNTO) == command)
+ {
+ if (rnFrom.length() == 0)
+ FTP_SEND_MSG(503, "Need RNFR before RNTO");
+ else if (parameters.length() == 0)
+ FTP_SEND_MSG(501, "No file name");
+ else if (THEFS.exists(path))
+ FTP_SEND_MSG(553, "\"%s\" already exists.", parameters.c_str());
+ else
+ {
+ FTP_DEBUG_MSG("Renaming '%s' to '%s'", rnFrom.c_str(), path.c_str());
+ if (THEFS.rename(rnFrom, path))
+ FTP_SEND_MSG(250, "File successfully renamed or moved");
+ else
+ FTP_SEND_MSG(451, "Rename/move failure.");
+ }
+ rnFrom.clear();
+ }
+
+ ///////////////////////////////////////
+ // //
+ // EXTENSIONS COMMANDS (RFC 3659) //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // FEAT - New Features
+ //
+ else if (FTP_CMD(FEAT) == command)
+ {
+ FTP_SEND_DASHMSG(211, "Features:\r\n MLSD\r\n MDTM\r\n SIZE\r\n211 End.");
+ }
+
+ //
+ // MDTM - File Modification Time (see RFC 3659)
+ //
+ else if (FTP_CMD(MDTM) == command)
+ {
+ file = THEFS.open(path, "r");
+ if ((!file) || (0 == parameters.length()))
+ {
+ FTP_SEND_MSG(550, "Unable to retrieve time");
+ }
+ else
+ {
+ FTP_SEND_MSG(213, "%s", makeDateTimeStr(file.getLastWrite()).c_str());
+ }
+ file.close();
+ }
+
+ //
+ // SIZE - Size of the file
+ //
+ else if (FTP_CMD(SIZE) == command)
+ {
+ file = THEFS.open(path, "r");
+ if ((!file) || (0 == parameters.length()))
+ {
+ FTP_SEND_MSG(450, "Cannot open file.");
+ }
+ else
+ {
+ FTP_SEND_MSG(213, "%lu", (uint32_t)file.size());
+ }
+ file.close();
+ }
+
+ //
+ // SITE - System command
+ //
+ else if (FTP_CMD(SITE) == command)
+ {
+ FTP_SEND_MSG(502, "SITE command not implemented");
+ }
+
+ //
+ // SYST - System information
+ //
+ else if (FTP_CMD(SYST) == command)
+ {
+ FTP_SEND_MSG(215, "UNIX Type: L8");
+ }
+
+ //
+ // Unrecognized commands ...
+ //
+ else
+ {
+ FTP_DEBUG_MSG("Unknown command: %s [%#x], param: '%s')", cmdString.c_str(), command, parameters.c_str());
+ FTP_SEND_MSG(500, "unknown command \"%s\"", cmdString.c_str());
+ }
+
+ return rc;
+}
+
+int8_t FtpServer::dataConnect()
+{
+ int8_t rc = 1; // assume success
+
+ if (!dataPassiveConn)
+ {
+ // active mode
+ // open our own data connection
+ data.stop();
+ FTP_DEBUG_MSG("Open active data connection to %s:%u", dataIP.toString().c_str(), dataPort);
+ data.connect(dataIP, dataPort);
+ if (!data.connected())
+ rc = -1;
+ }
+ else
+ {
+ // passive mode
+ // wait for data connection from the client
+ if (!data.connected())
+ {
+ if (dataServer.hasClient())
+ {
+ data.stop();
+ data = dataServer.available();
+ FTP_DEBUG_MSG("Got incoming (passive) data connection from %s:%u", data.remoteIP().toString().c_str(), data.remotePort());
+ }
+ else
+ {
+ // give me more time waiting for a data connection
+ rc = 0;
+ }
+ }
+ }
+ return rc;
+}
+
+int32_t FtpServer::allocateBuffer(int32_t desiredBytes)
+{
+ // allocate a large buffer for file transfers
+ // --> max performance!
+ int32_t maxBlock = ESP.getMaxFreeBlockSize() - 32; /* FIXME: there seems to be a bug! at least 16 bytes less */
+ int32_t maxHeap = ESP.getFreeHeap();
+ if (maxBlock - desiredBytes < 16)
+ desiredBytes = ((maxBlock - 16) & ~16);
+ // but leave (10% of heap) as reserve
+ if (maxHeap - desiredBytes < (maxHeap / 10))
+ desiredBytes -= (maxHeap / 10);
+
+ // make 16byte bound
+ desiredBytes &= ~16;
+
+ FTP_DEBUG_MSG("maxB = %li, maxH = %li, desired = %li", maxBlock, maxHeap, desiredBytes);
+
+ if (desiredBytes > 0)
+ {
+ fileBuffer = (uint8_t *)malloc(desiredBytes);
+ if (NULL == fileBuffer)
+ {
+ FTP_DEBUG_MSG("Cannot allocate buffer for retrieve file");
+ fileBufferSize = 0;
+ }
+ else
+ {
+ fileBufferSize = desiredBytes;
+ }
+ }
+ return fileBufferSize;
+}
+
+void FtpServer::freeBuffer()
+{
+ free(fileBuffer);
+ fileBuffer = NULL;
+}
+
+bool FtpServer::doRetrieve()
+{
+ // how many bytes to transfer
+ int32_t nb = (file.size() - bytesTransfered);
+ if (nb > fileBufferSize)
+ nb = fileBufferSize;
+
+ // data connection lost or no more bytes to transfer?
+ if (!data.connected() || (nb <= 0))
+ {
+ return false;
+ }
+
+ // transfer the file
+ nb = file.readBytes((char *)fileBuffer, nb);
+ if (nb > 0)
+ {
+ data.write(fileBuffer, nb);
+ bytesTransfered += nb;
+ }
+
+ return (nb > 0);
+}
+
+bool FtpServer::doStore()
+{
+ // Avoid blocking by never reading more bytes than are available
+ int32_t navail = data.available();
+
+ if (navail > 0)
+ {
+ if (navail > fileBufferSize)
+ navail = fileBufferSize;
+
+ int16_t nb = data.read(fileBuffer, navail);
+ FTP_DEBUG_MSG("Transfer %d bytes at one batch (buf %d bytes)", nb, navail);
+ if (nb > 0)
+ {
+ file.write(fileBuffer, nb);
+ bytesTransfered += nb;
+ }
+ }
+ if (!data.connected() && (navail <= 0))
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void FtpServer::closeTransfer()
+{
+ uint32_t deltaT = (int32_t)(millis() - millisBeginTrans);
+ if (deltaT > 0 && bytesTransfered > 0)
+ {
+ FTP_SEND_MSG(226, "File successfully transferred, %lu ms, %f kB/s.", deltaT, float(bytesTransfered) / deltaT);
+ }
+ else
+ FTP_SEND_MSG(226, "File successfully transferred");
+
+ freeBuffer();
+ file.close();
+ data.stop();
+}
+
+void FtpServer::abortTransfer()
+{
+ if (transferState > tIdle)
+ {
+ file.close();
+ data.stop();
+ FTP_SEND_MSG(426, "Transfer aborted");
+ }
+ freeBuffer();
+ transferState = tIdle;
+}
+
+// Read a char from client connected to ftp server
+//
+// returns:
+// -1 if cmdLine too long
+// 0 cmdLine still incomplete (no \r or \n received yet)
+// 1 cmdLine processed, command and parameters available
+
+int8_t FtpServer::readChar()
+{
+ // only read/parse, if the previous command has been fully processed!
+ if (command)
+ return 1;
+
+ while (control.available())
+ {
+ char c = control.read();
+ // FTP_DEBUG_MSG("readChar() cmdLine='%s' <= %c", cmdLine.c_str(), c);
+
+ // substitute '\' with '/'
+ if (c == '\\')
+ c = '/';
+
+ // nl detected? then process line
+ if (c == '\n' || c == '\r')
+ {
+ cmdLine.trim();
+
+ // but only if we got at least chars in the line!
+ if (0 == cmdLine.length())
+ break;
+
+ // search for space between command and parameters
+ int pos = cmdLine.indexOf(FPSTR(aSpace));
+ if (pos > 0)
+ {
+ parameters = cmdLine.substring(pos + 1);
+ parameters.trim();
+ cmdLine.remove(pos);
+ }
+ else
+ {
+ parameters.remove(0);
+ }
+ cmdString = cmdLine;
+
+ // convert command to upper case
+ cmdString.toUpperCase();
+
+ // convert the (up to 4 command chars to numerical value)
+ command = *(const uint32_t *)cmdString.c_str();
+
+ // clear cmdline
+ cmdLine.clear();
+ FTP_DEBUG_MSG("readChar() success, command=%x, cmdString='%s', params='%s'", command, cmdString.c_str(), parameters.c_str());
+ return 1;
+ }
+ else
+ {
+ // just add char
+ cmdLine += c;
+ if (cmdLine.length() > FTP_CMD_SIZE)
+ {
+ cmdLine.clear();
+ FTP_SEND_MSG(500, "Line too long");
+ }
+ }
+ }
+ return 0;
+}
+
+// Get the complete path from cwd + parameters or complete filename from cwd + parameters
+//
+// 3 possible cases: parameters can be absolute path, relative path or only the name
+//
+// returns:
+// path WITHOUT file-/dirname (fullname=false)
+// full path WITH file-/dirname (fullname=true)
+String FtpServer::getPathName(const String &param, bool fullname)
+{
+ String tmp;
+
+ // is param an absoulte path?
+ if (param[0] == '/')
+ {
+ tmp = param;
+ }
+ else
+ {
+ // start with cwd
+ tmp = cwd;
+
+ // if param != "" then add param
+ if (param.length())
+ {
+ if (!tmp.endsWith(FPSTR(aSlash)))
+ tmp += '/';
+ tmp += param;
+ }
+ // => tmp becomes cdw [ + '/' + param ]
+ }
+
+ // strip filename
+ if (!fullname)
+ {
+ // search rightmost '/'
+ int lastslash = tmp.lastIndexOf(FPSTR(aSlash));
+ if (lastslash >= 0)
+ {
+ tmp.remove(lastslash);
+ }
+ }
+ // sanetize:
+ // "" -> "/"
+ // "/some/path/" => "/some/path"
+ while (tmp.length() > 1 && tmp.endsWith(FPSTR(aSlash)))
+ tmp.remove(cwd.length() - 1);
+ if (tmp.length() == 0)
+ tmp += '/';
+ return tmp;
+}
+
+// Get the [complete] file name from cwd + parameters
+//
+// 3 possible cases: parameters can be absolute path, relative path or only the filename
+//
+// returns:
+// filename or filename with complete path
+String FtpServer::getFileName(const String &param, bool fullFilePath)
+{
+ // build the filename with full path
+ String tmp = getPathName(param, true);
+
+ if (!fullFilePath)
+ {
+ // strip path
+ // search rightmost '/'
+ int lastslash = tmp.lastIndexOf(FPSTR(aSlash));
+ if (lastslash > 0)
+ {
+ tmp.remove(0, lastslash);
+ }
+ }
+
+ return tmp;
+}
+
+// Formats YYYYMMDDHHMMSS from a time_t timestamp
+//
+// uses the buf of the FtpServer to store the date string
+//
+// parameters:
+// timestamp
+//
+// return:
+// pointer to buf[0]
+
+String FtpServer::makeDateTimeStr(time_t ft)
+{
+ struct tm *_tm = gmtime(&ft);
+ String tmp;
+ tmp.reserve(17);
+ strftime((char *)tmp.c_str(), 17, "%Y%m%d%H%M%S", _tm);
+ return tmp;
+}
+
+void FtpServer::updateTimeout(uint16_t s)
+{
+ millisEndConnection = s;
+ millisEndConnection *= 60000UL;
+ millisEndConnection += millis();
+}
diff --git a/espFtpServer.h b/espFtpServer.h
new file mode 100644
index 0000000..2f3e329
--- /dev/null
+++ b/espFtpServer.h
@@ -0,0 +1,147 @@
+/*
+ * FTP SERVER FOR ESP8266/ESP32
+ * based on FTP Serveur 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)
+ * modified to work with esp8266 LitteFS by Daniel Plasa dplasa@gmail.com
+ * Also done some code reworks and all string contants are now in flash memory
+ * by using F(), PSTR() ... on the string literals.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ESP_FTP_SERVER_H
+#define ESP_FTP_SERVER_H
+
+#include <WiFiClient.h>
+#include <FS.h>
+
+/*******************************************************************************
+ ** **
+ ** DEFINITIONS FOR FTP SERVER **
+ ** **
+ *******************************************************************************/
+
+//
+// DEBUG via Serial Console
+// Please select in your Arduino IDE menu Tools->Debug Port to enable debugging.
+// (This will provide DEBUG_ESP_PORT at compile time.)
+//
+
+// Use ESP8266 Core Debug functionality
+#ifdef DEBUG_ESP_PORT
+#define FTP_DEBUG_MSG(fmt, ...) \
+ do \
+ { \
+ DEBUG_ESP_PORT.printf_P(PSTR("[FTP] " fmt "\n"), ##__VA_ARGS__); \
+ yield(); \
+ } while (0)
+#else
+#define FTP_DEBUG_MSG(...)
+#endif
+
+#define FTP_SERVER_VERSION "0.9-20200525"
+
+#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
+#define FTP_CMD_SIZE 127 // allow max. 127 chars in a received command
+
+class FtpServer
+{
+public:
+ // contruct an instance of the FTP server using a
+ // given FS object, e.g. SPIFFS or LittleFS
+ FtpServer(FS &_FSImplementation);
+
+ // starts the FTP server with username and password,
+ // either one can be empty to enable anonymous ftp
+ void begin(const String &uname, const String &pword);
+
+ // stops the FTP server
+ void stop();
+
+ // set the FTP server's timeout in seconds
+ void setTimeout(uint16_t timeout = FTP_TIME_OUT * 60);
+
+ // needs to be called frequently (e.g. in loop() )
+ // to process ftp requests
+ void handleFTP();
+
+private:
+ FS &THEFS;
+
+ enum internalState
+ {
+ cInit = 0,
+ cWait,
+ cCheck,
+ cUserId,
+ cPassword,
+ cLoginOk,
+ cProcess,
+
+ tIdle,
+ tRetrieve,
+ tStore
+ };
+ void iniVariables();
+ void disconnectClient(bool gracious = true);
+ int8_t processCommand();
+ int8_t dataConnect();
+
+ bool doRetrieve();
+ bool doStore();
+ void closeTransfer();
+ void abortTransfer();
+ int32_t allocateBuffer(int32_t desiredBytes);
+ void freeBuffer();
+
+ String getPathName(const String& param, bool includeLast = false);
+ String getFileName(const String &param, bool fullFilePath = false);
+ String makeDateTimeStr(time_t fileTime);
+ int8_t readChar();
+ void updateTimeout(uint16_t timeout);
+
+ WiFiClient control;
+ WiFiClient data;
+
+ File file;
+
+ bool dataPassiveConn = true; // PASV (passive) mode is our default
+ IPAddress dataIP; // IP address for PORT (active) mode
+ uint16_t dataPort = // holds our PASV port number or the port number provided by PORT
+ FTP_DATA_PORT_PASV;
+
+ uint32_t command; // numeric command code of command sent by the client
+ String cmdLine; // command line as read from client
+ String cmdString; // command as textual representation
+ String parameters; // parameters sent by client
+ String cwd; // the current directory
+ String rnFrom; // previous command was RNFR, this is the source file name
+
+ internalState cmdState, // state of ftp control connection
+ transferState; // state of ftp data connection
+ uint16_t sTimeOut = // disconnect after 5 min of inactivity
+ FTP_TIME_OUT * 60;
+ uint32_t millisEndConnection, //
+ millisBeginTrans, // store time of beginning of a transaction
+ bytesTransfered; //
+ uint8_t *fileBuffer = NULL; // pointer to buffer for file transfer (by allocateBuffer)
+ int32_t fileBufferSize; // size of buffer
+ String _FTP_USER; // usename
+ String _FTP_PASS; // password
+};
+
+#endif // ESP_FTP_SERVER_H
diff --git a/examples/FTPServerSample/FTPServerSample.ino b/examples/FTPServerSample/FTPServerSample.ino
deleted file mode 100644
index b8fa5f6..0000000
--- a/examples/FTPServerSample/FTPServerSample.ino
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifdef ESP8266
-#include <ESP8266WiFi.h>
-#elif defined ESP32
-#include <WiFi.h>
-#include "SPIFFS.h"
-#endif
-
-#include <ESP8266FtpServer.h>
-
-const char* ssid = "YOUR_SSID";
-const char* password = "YOUR_PASS";
-
-
-FtpServer ftpSrv; //set #define FTP_DEBUG in ESP8266FtpServer.h to see ftp verbose on serial
-
-
-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());
-
-
- /////FTP Setup, ensure SPIFFS is started before ftp; /////////
-
- /////FTP Setup, ensure SPIFFS is started before ftp; /////////
-#ifdef ESP32 //esp32 we send true to format spiffs if cannot mount
- if (SPIFFS.begin(true)) {
-#elif defined ESP8266
- if (SPIFFS.begin()) {
-#endif
- Serial.println("SPIFFS opened!");
- ftpSrv.begin("esp8266","esp8266"); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV)
- }
-}
-void loop(void){
- ftpSrv.handleFTP(); //make sure in loop you call handleFTP()!!
- // server.handleClient(); //example if running a webserver you still need to call .handleClient();
-
-}
diff --git a/examples/LittleFSSample/LittleFSSample.ino b/examples/LittleFSSample/LittleFSSample.ino
new file mode 100644
index 0000000..b6bfdd6
--- /dev/null
+++ b/examples/LittleFSSample/LittleFSSample.ino
@@ -0,0 +1,125 @@
+/*
+ This is an example sketch to show the use of the espFTP server.
+
+ Please replace
+ YOUR_SSID and YOUR_PASS
+ with your WiFi's values and compile.
+
+ If you want to see debugging output of the FTP server, please
+ select select an Serial Port in the Arduino IDE menu Tools->Debug Port
+
+ Send L via Serial Monitor, to display the contents of the FS
+ Send F via Serial Monitor, to fromat the FS
+
+ This example is provided as Public Domain
+ Daniel Plasa <dplasa@gmail.com>
+
+*/
+#ifdef ESP8266
+#include <LittleFS.h>
+#include <ESP8266WiFi.h>
+#elif defined ESP32
+#include <WiFi.h>
+#include <SPIFFS.h>
+#endif
+
+#include <espFtpServer.h>
+
+const char *ssid PROGMEM = "";
+const char *password PROGMEM = "";
+
+// tell the FtpServer to use LittleFS
+FtpServer ftpSrv(LittleFS);
+
+void setup(void)
+{
+ Serial.begin(74880);
+ WiFi.begin(ssid, password);
+
+ bool fsok = LittleFS.begin();
+ Serial.printf_P(PSTR("FS init: %S\n"), fsok ? PSTR("ok") : PSTR("fail!"));
+
+ // Wait for connection
+ while (WiFi.status() != WL_CONNECTED)
+ {
+ delay(500);
+ Serial.printf_P(PSTR("."));
+ }
+ Serial.printf_P(PSTR("\nConnected to %S, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str());
+
+ // setup the ftp server with username and password
+ // ports are defined in esFTP.h, default is
+ // 21 for the control connection
+ // 50009 for the data connection (passive mode by default)
+ ftpSrv.begin(F("ftp"), F("ftp")); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV)
+}
+
+enum consoleaction
+{
+ show,
+ wait,
+ format,
+ list
+};
+consoleaction action = show;
+
+void loop(void)
+{
+ // this is all you need
+ // make sure to call handleFTP() frequently
+ ftpSrv.handleFTP();
+
+ //
+ // Code below just to debug in Serial Monitor
+ //
+ if (action == show)
+ {
+ Serial.printf_P(PSTR("Enter 'F' to format, 'L' to list the contents of the FS\n"));
+ action = wait;
+ }
+ else if (action == wait)
+ {
+ if (Serial.available())
+ {
+ char c = Serial.read();
+ if (c == 'F')
+ action = format;
+ else if (c == 'L')
+ action = list;
+ else if (!(c == '\n' || c == '\r'))
+ action = show;
+ }
+ }
+ else if (action == format)
+ {
+ uint32_t startTime = millis();
+ LittleFS.format();
+ Serial.printf_P(PSTR("FS format done, took %lu ms!\n"), millis() - startTime);
+ action = show;
+ }
+ else if (action == list)
+ {
+ Serial.printf_P(PSTR("Listing contents...\n"));
+ uint16_t fileCount = listDir("", "/");
+ Serial.printf_P(PSTR("%d files/dirs total\n"), fileCount);
+ action = show;
+ }
+}
+
+uint16_t listDir(String indent, String path)
+{
+ uint16_t dirCount = 0;
+ Dir dir = LittleFS.openDir(path);
+ while (dir.next())
+ {
+ ++dirCount;
+ if (dir.isDirectory())
+ {
+ Serial.printf_P(PSTR("%s%s [Dir]\n"), indent.c_str(), dir.fileName().c_str());
+ dirCount += listDir(indent + " ", path + dir.fileName() + "/");
+ }
+ else
+ Serial.printf_P(PSTR("%s%-16s (%ld Bytes)\n"), indent.c_str(), dir.fileName().c_str(), (uint32_t)dir.fileSize());
+ }
+ return dirCount;
+}
diff --git a/examples/SPIFFSSample/SPIFFSSample.ino b/examples/SPIFFSSample/SPIFFSSample.ino
new file mode 100644
index 0000000..6438afe
--- /dev/null
+++ b/examples/SPIFFSSample/SPIFFSSample.ino
@@ -0,0 +1,115 @@
+/*
+ This is an example sketch to show the use of the espFTP server.
+
+ Please replace
+ YOUR_SSID and YOUR_PASS
+ with your WiFi's values and compile.
+
+ If you want to see debugging output of the FTP server, please
+ select select an Serial Port in the Arduino IDE menu Tools->Debug Port
+
+ Send L via Serial Monitor, to display the contents of the FS
+ Send F via Serial Monitor, to fromat the FS
+
+ This example is provided as Public Domain
+ Daniel Plasa <dplasa@gmail.com>
+
+*/
+#ifdef ESP8266
+#include <FS.h>
+#include <ESP8266WiFi.h>
+#elif defined ESP32
+#include <WiFi.h>
+#include <SPIFFS.h>
+#endif
+
+#include <espFtpServer.h>
+
+const char *ssid PROGMEM = "";
+const char *password PROGMEM = "";
+
+// Since SPIFFS is becoming deprecated but might still be in
+// use in your Projects, tell the FtpServer to use SPIFFS
+FtpServer ftpSrv(SPIFFS);
+
+void setup(void)
+{
+ Serial.begin(74880);
+ WiFi.begin(ssid, password);
+
+ bool fsok = SPIFFS.begin();
+ Serial.printf_P(PSTR("FS init: %S\n"), fsok ? PSTR("ok") : PSTR("fail!"));
+
+ // Wait for connection
+ while (WiFi.status() != WL_CONNECTED)
+ {
+ delay(500);
+ Serial.printf_P(PSTR("."));
+ }
+ Serial.printf_P(PSTR("\nConnected to %S, IP address is %s\n"), ssid, WiFi.localIP().toString().c_str());
+
+ // setup the ftp server with username and password
+ // ports are defined in esFTP.h, default is
+ // 21 for the control connection
+ // 50009 for the data connection (passive mode by default)
+ ftpSrv.begin(F("ftp"), F("ftp"));
+}
+
+enum consoleaction
+{
+ show,
+ wait,
+ format,
+ list
+};
+
+consoleaction action = show;
+
+void loop(void)
+{
+ // this is all you need
+ // make sure to call handleFTP() frequently
+ ftpSrv.handleFTP();
+
+ //
+ // Code below just to debug in Serial Monitor
+ //
+ if (action == show)
+ {
+ Serial.printf_P(PSTR("Enter 'F' to format, 'L' to list the contents of the FS\n"));
+ action = wait;
+ }
+ else if (action == wait)
+ {
+ if (Serial.available())
+ {
+ char c = Serial.read();
+ if (c == 'F')
+ action = format;
+ else if (c == 'L')
+ action = list;
+ else if (!(c == '\n' || c == '\r'))
+ action = show;
+ }
+ }
+ else if (action == format)
+ {
+ uint32_t startTime = millis();
+ SPIFFS.format();
+ Serial.printf_P(PSTR("FS format done, took %lu ms!\n"), millis() - startTime);
+ action = show;
+ }
+ else if (action == list)
+ {
+ Serial.printf_P(PSTR("Listing contents...\n"));
+ uint16_t dirCount = 0;
+ Dir dir = SPIFFS.openDir(F("/"));
+ while (dir.next())
+ {
+ ++dirCount;
+ Serial.printf_P(PSTR("%6ld %s\n"), (uint32_t)dir.fileSize(), dir.fileName().c_str());
+ }
+ Serial.printf_P(PSTR("%d files total\n"), dirCount);
+ action = show;
+ }
+}
diff --git a/library.json b/library.json
index ff9891f..5f4944f 100644
--- a/library.json
+++ b/library.json
@@ -1,19 +1,17 @@
{
- "name": "esp8266FTPServer",
- "description": "Simple FTP Server for using esp8266 SPIFFs",
- "keywords": "esp8266, ftp, spiffs",
+ "name": "espFTPServer",
+ "description": "Simple FTP Server for using SPIFFS or LittleFS",
+ "keywords": "esp8266, ftp, spiffs, littlefs",
"authors":
{
- "name": "David Paiva",
- "email": "david@paivahome.com",
- "url": "http://nailbuster.com/"
+ "name": "Daniel Plasa",
+ "email": "dplasa@gmail.com"
},
"repository":
{
"type": "git",
- "url": "https://github.com/nailbuster/esp8266FTPServer"
+ "url": "https://github.com/dplasa/espFTPServer"
},
- "url": "http://nailbuster.com/",
"frameworks": "Arduino",
"platforms": "*"
}
diff --git a/library.properties b/library.properties
index 0e89838..419d1fd 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,9 @@
-name=ESP8266FtpServer
-version=1.0.1
-author=
-maintainer=david@paivahome.com
-sentence=Very Simple FTP server for SPIFFS on esp8266
-paragraph=Very Simple FTP server for SPIFFS on esp8266
+name=espFtpServer
+version=0.9.1
+author=Daniel Plasa
+maintainer=dplasa@gmail.com
+sentence=Simple FTP server for SPIFFS and LittleFS on esp8266/esp32
+paragraph=Simple FTP server for SPIFFS and LittleFS on esp8266/esp32
category=Communication
url=
architectures=esp8266,esp32,arduino-esp32