From 453e61d5dc538e19e545075818e94a9508501c44 Mon Sep 17 00:00:00 2001 From: Daniel Plasa Date: Sun, 24 May 2020 18:58:42 +0200 Subject: rewrite/reworks to also use LittleFS since SPIFFS has become deprecated Also modifications to support also active mode and performance tunings with the use of dynamic buffers when transfering file data. --- ESP8266FtpServer.cpp | 1021 -------------------- ESP8266FtpServer.h | 106 --- README.md | 19 +- espFtpCommands.h | 63 ++ espFtpServer.cpp | 1315 ++++++++++++++++++++++++++ espFtpServer.h | 147 +++ examples/FTPServerSample/FTPServerSample.ino | 50 - examples/LittleFSSample/LittleFSSample.ino | 125 +++ examples/SPIFFSSample/SPIFFSSample.ino | 115 +++ library.json | 14 +- library.properties | 12 +- 11 files changed, 1789 insertions(+), 1198 deletions(-) delete mode 100644 ESP8266FtpServer.cpp delete mode 100644 ESP8266FtpServer.h create mode 100644 espFtpCommands.h create mode 100644 espFtpServer.cpp create mode 100644 espFtpServer.h delete mode 100644 examples/FTPServerSample/FTPServerSample.ino create mode 100644 examples/LittleFSSample/LittleFSSample.ino create mode 100644 examples/SPIFFSSample/SPIFFSSample.ino 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 . - */ - -#include "ESP8266FtpServer.h" -#ifdef ESP8266 -#include -#elif defined ESP32 -#include -#include "SPIFFS.h" -#endif -#include -#include - - - - -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 " + 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 " + 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 . - */ - -/******************************************************************************* - ** ** - ** 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 -#include - -#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 +``` +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 . + */ + +// code 330252-304168 = 26084 with SPIFFS +// code 324512-298440 = 26072 with LittleFS +#include "espFtpServer.h" + +#ifdef ESP8266 +#include +#elif defined ESP32 +#include +#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 " + 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 " + 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 ¶m, 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 ¶m, 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 . + */ + +#ifndef ESP_FTP_SERVER_H +#define ESP_FTP_SERVER_H + +#include +#include + +/******************************************************************************* + ** ** + ** 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 ¶m, 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 -#elif defined ESP32 -#include -#include "SPIFFS.h" -#endif - -#include - -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 + +*/ +#ifdef ESP8266 +#include +#include +#elif defined ESP32 +#include +#include +#endif + +#include + +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 + +*/ +#ifdef ESP8266 +#include +#include +#elif defined ESP32 +#include +#include +#endif + +#include + +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 -- cgit v1.2.3