1041 lines
34 KiB
C++
1041 lines
34 KiB
C++
#ifndef FTPSERVER_H
|
|
#define FTPSERVER_H
|
|
|
|
#include <SD.h>
|
|
#include "TCPServer.h"
|
|
#include "utilities.h"
|
|
#include "Dictionary.h"
|
|
//#define DEBUG_FTPS
|
|
#define SEND_RECV_BUFFER_SIZE 5000
|
|
|
|
template <typename T>
|
|
class FTPServer : public TCPServer<T>
|
|
{
|
|
public:
|
|
enum FTPClientState {INIT, WAITING_FOR_COMMANDS};
|
|
enum FTPClientDataTransfer {NONE = 0, LIST_DF, NLST_DF, RETR_DF, STOR_DF, APPE_DF};
|
|
enum FileTransferStatus {OK, NOT_FOUND, NO_FILE_NAME, MALLOC_ERROR};
|
|
enum BinaryFlag {OFF = 0, ON};
|
|
enum FTPMsgCode {_150, _200, _215, _220, _221, _230, _226, _227, _250, _257, _331, _350, _451, _5_502, _504, _530, _550 };
|
|
|
|
FTPServer(uint16_t port = 21, SDClass *sdClass = NULL, const char *login = NULL, const char *password = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientCommandDataBufferSize = 255) : TCPServer<T>(port, maxClient, clientCommandDataBufferSize),
|
|
_dataServer(_dataPort),
|
|
_sdClass(sdClass)
|
|
{
|
|
if (login != NULL)
|
|
{
|
|
if (strlen(login))
|
|
{
|
|
_login = (char *)malloc((sizeof(char) * strlen(login)) + 1);
|
|
strcpy(_login, login);
|
|
}
|
|
}
|
|
|
|
if (password != NULL)
|
|
{
|
|
if (strlen(password))
|
|
{
|
|
_password = (char *)malloc((sizeof(char) * strlen(password)) + 1);
|
|
strcpy(_password, password);
|
|
}
|
|
}
|
|
_dataServer.begin(_dataPort);
|
|
}
|
|
|
|
void setCustomDataPort(unsigned int port)
|
|
{
|
|
_dataPort = port;
|
|
}
|
|
|
|
virtual ~FTPServer()
|
|
{
|
|
_dataServer.stop();
|
|
free(_login); free(_password); free(_FTPDir);
|
|
}
|
|
|
|
virtual void stop()
|
|
{
|
|
if(TCPServer<T>::_serverStarted)
|
|
{
|
|
_dataServer.stop();
|
|
TCPServer<T>::stop();
|
|
}
|
|
}
|
|
|
|
virtual void setFTPDir(const char *FTPDir)
|
|
{
|
|
if(FTPDir)
|
|
{
|
|
free(_FTPDir);
|
|
_FTPDir = (char *)malloc((strlen(FTPDir) * sizeof(char)) + 1);
|
|
strcpy(_FTPDir, FTPDir);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
virtual T* createNewClient(WiFiClient wc)
|
|
{
|
|
return new T(wc, TCPServer<T>::freeClientId(), TCPServer<T>::_clientDataBufferSize);
|
|
}
|
|
|
|
virtual void greetClient(T *client)
|
|
{
|
|
//The first time the client connects, we send the server's information
|
|
client->_client.println("220 Welcome to the ESP8266SwissArmyBoard embedded FTP server.");
|
|
client->_clientState = TCPClient::HANDLED;
|
|
}
|
|
|
|
IRAM_ATTR virtual void processClientData(T *client)
|
|
{
|
|
/*if (client->_waitingForDataConnection)
|
|
{
|
|
//#ifdef DEBUG_FTPS
|
|
//Serial.println("Listening for new data client");
|
|
//#endif
|
|
WiFiClient dataClient = _dataServer.available();
|
|
|
|
if(dataClient)
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Data client returns true");
|
|
#endif
|
|
if (dataClient.connected())
|
|
{
|
|
client->_waitingForDataConnection = false;
|
|
client->setDataClient(dataClient);
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Data client accepted successfully");
|
|
#endif
|
|
}
|
|
else
|
|
dataClient.stop();
|
|
}
|
|
}*/
|
|
|
|
switch(client->_dataTransferPending)
|
|
{
|
|
case LIST_DF:
|
|
if (client->_dataClient.connected())
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Listing started");
|
|
#endif
|
|
|
|
client->_client.println("150 File status okay.");
|
|
if(sendFSTree(client))
|
|
{
|
|
client->_client.println("226 Closing data connection.");
|
|
}
|
|
else
|
|
{
|
|
client->_client.println("451 Requested action aborted.");
|
|
}
|
|
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
break;
|
|
case RETR_DF:
|
|
if (client->_dataClient.connected())
|
|
{
|
|
if(client->_fileSentBytes == 0)
|
|
client->_client.println("150 File status okay.");
|
|
|
|
FileTransferStatus fts;
|
|
if(!sendFile(client,&fts))//Whole file was sent or error an occured
|
|
{
|
|
//we check the return code
|
|
if(fts == OK)
|
|
{
|
|
client->_client.println("226 Closing data connection.");
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
else if(fts == NOT_FOUND)
|
|
{
|
|
client->_client.println("451 File not found.");
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
else if(fts == MALLOC_ERROR)
|
|
{
|
|
client->_client.println("451 Insufficient RAM space.");
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
}
|
|
}
|
|
else if(client->_fileSentBytes != 0)
|
|
{
|
|
client->_client.println("426 Connection closed; transfer aborted.");
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
break;
|
|
case STOR_DF:
|
|
if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data available for reading. IMPORTANT
|
|
{
|
|
if(client->_dataClient.available())
|
|
{
|
|
client->_fileIsBeeingReceived = true;
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("receiving file %s\n", client->_currentFile);
|
|
#endif
|
|
FileTransferStatus fts = OK;
|
|
|
|
if(!writeToSdCard(client, &fts)) //An error occured
|
|
{
|
|
if(fts == NO_FILE_NAME)
|
|
{
|
|
client->_client.println("501 No file name given.");
|
|
}
|
|
else if(fts == NOT_FOUND)
|
|
{
|
|
client->_client.println("451 File not found.");
|
|
}
|
|
else if(fts == MALLOC_ERROR)
|
|
{
|
|
client->_client.println("451 Insufficient RAM space.");
|
|
}
|
|
else
|
|
{
|
|
client->_client.println("451 Requested action aborted: local error in processing.");
|
|
}
|
|
client->closeDataConnection();
|
|
client->_fileIsBeeingReceived = false;
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
}
|
|
}
|
|
//If no data connection exists and no error was raised during writting, then it could be just a file creation with no data connection opened
|
|
else if(client->_fileIsBeeingReceived || (client->_dataClientConnected || millis() - client->_actionTimeout > 5000))
|
|
{
|
|
if(client->_fileRecvBytes == 0) //File was just created empty
|
|
{
|
|
if(_sdClass->exists(client->_currentFile))
|
|
_sdClass->remove(client->_currentFile);
|
|
|
|
File file2create = _sdClass->open(client->_currentFile, FILE_WRITE);
|
|
|
|
file2create.close();
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("File just created");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Whole file received");
|
|
#endif
|
|
}
|
|
|
|
client->_client.println("226 Closing data connection.");
|
|
|
|
client->_fileIsBeeingReceived = false;
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
client->_fileRecvBytes = 0;
|
|
}
|
|
break;
|
|
case APPE_DF:
|
|
if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data for reading. IMPORTANT
|
|
{
|
|
if(client->_dataClient.available())
|
|
{
|
|
client->_fileIsBeeingReceived = true;
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("appending to file %s\n", client->_currentFile);
|
|
#endif
|
|
FileTransferStatus fts = OK;
|
|
|
|
if(!writeToSdCard(client, &fts, true)) //An error occured
|
|
{
|
|
if(fts == NO_FILE_NAME)
|
|
{
|
|
client->_client.println("501 No file name given.");
|
|
}
|
|
else if(fts == NOT_FOUND)
|
|
{
|
|
client->_client.println("451 File not found.");
|
|
}
|
|
else if(fts == MALLOC_ERROR)
|
|
{
|
|
client->_client.println("451 Insufficient RAM space.");
|
|
}
|
|
else
|
|
{
|
|
client->_client.println("451 Requested action aborted: local error in processing.");
|
|
}
|
|
client->closeDataConnection();
|
|
client->_fileIsBeeingReceived = false;
|
|
client->_dataTransferPending = NONE;
|
|
}
|
|
}
|
|
}
|
|
//If the connection is closed and data has been received, then we got the whole file
|
|
else if(client->_fileIsBeeingReceived)
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Whole file received");
|
|
#endif
|
|
|
|
client->_client.println("226 Closing data connection.");
|
|
|
|
client->_fileIsBeeingReceived = false;
|
|
client->closeDataConnection();
|
|
client->_dataTransferPending = NONE;
|
|
client->_fileRecvBytes = 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#ifdef DEBUG_FTPS
|
|
if (client->_newDataAvailable)
|
|
{
|
|
Serial.print("Client --> "); Serial.print(client->_id); Serial.print(" : "); Serial.print((char *)client->_data); Serial.println("#");
|
|
}
|
|
#endif
|
|
|
|
if(client->_dataSize > 0)
|
|
{
|
|
switch (client->_ftpClientState)
|
|
{
|
|
case WAITING_FOR_COMMANDS:
|
|
processCommands(client);
|
|
break;
|
|
case INIT:
|
|
_FTPDir ? client->setCurrentDirectory(_FTPDir) : client->setCurrentDirectory("");
|
|
client->_ftpClientState = WAITING_FOR_COMMANDS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void processCommands(T *client)
|
|
{
|
|
if (!client->parseCommandAndParameters()) //Failed to retrieve command and parameters
|
|
{
|
|
//We can close the connection or do other things
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Issued command : '%s'\n", client->_ftpCommand);
|
|
Serial.println("Get params :");
|
|
for (int i = 0; i < client->_cmdParameters->count(); i++)
|
|
{
|
|
Serial.print(client->_cmdParameters->getParameter(i)); Serial.print(" : "); Serial.printf("'%s'\n", client->_cmdParameters->getAt(i)->getString());
|
|
}
|
|
#endif
|
|
|
|
if (strcmp(client->_ftpCommand, "USER") == 0)
|
|
{
|
|
//We check if we set a login and a password :
|
|
if (_login != NULL && _password != NULL)
|
|
{
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
client->_client.println("331 User name okay, need password.");
|
|
client->setUsername(client->_cmdParameters->getAt(0)->getString());
|
|
}
|
|
else
|
|
sendInfoResponse(_530, client);//client->_client.println("530 Username required.");
|
|
}
|
|
else //The ftp access is open
|
|
{
|
|
client->_loggedIn = true;
|
|
client->_client.println("230 User logged in, proceed.");
|
|
}
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "PASS") == 0)
|
|
{
|
|
//We now check if the username and password correspond
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
if (strcmp(_login, client->_username) == 0 && strcmp(_password, client->_cmdParameters->getAt(0)->getString()) == 0)
|
|
{
|
|
client->_loggedIn = true;
|
|
client->_client.println("230 User logged in, proceed.");
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_530, client, "Wrong username or password !");//client->_client.println("530 Wrong username or password !.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_530, client,"");//client->_client.println("530 Password required.");
|
|
}
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "PWD") == 0) //We set the default directory
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
client->_client.printf("257 \"%s\"\r\n", client->_currentDirectory);
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "TYPE") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
switch (client->_cmdParameters->getAt(0)->getString()[0])
|
|
{
|
|
case 'I':
|
|
client->_binaryFlag = ON;
|
|
sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay.");
|
|
break;
|
|
case 'L':
|
|
client->_binaryFlag = ON;
|
|
sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay.");
|
|
break;
|
|
case 'A':
|
|
client->_binaryFlag = OFF;
|
|
sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay.");
|
|
break;
|
|
default:
|
|
client->_client.println("504 Command not implemented for TYPE.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
client->_client.println("504 Command not implemented for TYPE.");
|
|
}
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "PASV") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", client->_client.localIP()[0], client->_client.localIP()[1], client->_client.localIP()[2], client->_client.localIP()[3], _dataPort / 256, _dataPort % 256);
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Opening data server for new data client");
|
|
#endif
|
|
//We listen for 100 ms directly to accept the client
|
|
uint64_t timeOut(millis());
|
|
while(true)
|
|
{
|
|
WiFiClient dataClient = _dataServer.available();
|
|
//Serial.printf("Client state : %d\n", dataClient.status());
|
|
if (dataClient)
|
|
{
|
|
//Serial.printf("Data client is true , available : %d\n", dataClient.available());
|
|
if (dataClient.connected())//Connected returns true if the status() is ESTABLISHED or available() returns true
|
|
{
|
|
client->setDataClient(dataClient);
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Data client accepted from loop");
|
|
#endif
|
|
break;
|
|
}
|
|
else
|
|
dataClient.stop();
|
|
}
|
|
|
|
if(millis() - timeOut > 100)
|
|
{
|
|
client->_waitingForDataConnection = true;
|
|
break;
|
|
}
|
|
yield();
|
|
}
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "LIST") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
//We inform that a data transfer is pending
|
|
client->_dataTransferPending = LIST_DF;
|
|
}
|
|
else if (strcmp(client->_ftpCommand, "CWD") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
//Go back one level
|
|
if (strcmp(client->_cmdParameters->getAt(0)->getString(), "..") == 0)
|
|
{
|
|
char *dirCopy = (char *)malloc((sizeof(char) * strlen(client->_currentDirectory)) + 1);
|
|
strcpy(dirCopy, client->_currentDirectory);
|
|
|
|
char *p = strrchr(dirCopy, '/');
|
|
if (dirCopy == p)
|
|
{
|
|
*(p + 1) = '\0';
|
|
}
|
|
else
|
|
*(p) = '\0';
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Final dir : %s\n", dirCopy);
|
|
#endif
|
|
|
|
client->setCurrentDirectory(dirCopy);
|
|
free(dirCopy);
|
|
}
|
|
else if (strcmp(client->_cmdParameters->getAt(0)->getString(), "/") == 0)
|
|
{
|
|
client->setCurrentDirectory("/");
|
|
}
|
|
else //If the client already nows the path, he will send it with a /
|
|
{
|
|
if(client->_cmdParameters->getAt(0)->getString()[0] == '/')//Then this is a name prefix
|
|
{
|
|
char *fullDirPath = constructFileNameWithPath("",client->_cmdParameters);
|
|
client->setCurrentDirectory(fullDirPath);
|
|
free(fullDirPath);
|
|
}
|
|
else
|
|
{
|
|
//Directories with spaces are now working:
|
|
char *directoryFullpath = constructFileNameWithPath(client->_currentDirectory,client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Final dir : %s\n", directoryFullpath);
|
|
#endif
|
|
client->setCurrentDirectory(directoryFullpath);
|
|
free(directoryFullpath);
|
|
}
|
|
}
|
|
|
|
client->_client.println("250 Requested file action okay, completed.");
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("CWD new dir : %s\n", client->_currentDirectory);
|
|
#endif
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "RETR") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
//We save the file path to be sent
|
|
char *file2store(NULL);
|
|
if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
|
|
file2store = constructFileNameWithPath("", client->_cmdParameters);
|
|
else
|
|
file2store = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Final file path : %s\n",file2store);
|
|
#endif
|
|
client->setCurrentFile(file2store);
|
|
free(file2store);
|
|
client->_dataTransferPending = RETR_DF;
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "MKD") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
char *dirNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
if(dirNameWithPath != NULL)
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Final dirName : #%s#\n",dirNameWithPath);
|
|
#endif
|
|
|
|
if(_sdClass->mkdir(dirNameWithPath))
|
|
{
|
|
client->_client.printf("257 \"%s\"\r\n", dirNameWithPath);
|
|
}
|
|
else
|
|
client->_client.println("550 Failed to mkdir (no spaces allowed in dir name).");
|
|
|
|
free(dirNameWithPath);
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "RMD") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
//We have the dir name, we need to append the current directory...
|
|
char *dirNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("pathDirName to delete : #%s#\n",dirNameWithPath);
|
|
#endif
|
|
|
|
if(_sdClass->rmdir(dirNameWithPath))
|
|
{
|
|
client->_client.println("250 Requested file action okay.");
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
|
|
free(dirNameWithPath);
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "STOR") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n"));
|
|
//We save the file path to be sent
|
|
char *fileNameWithPath(NULL);
|
|
|
|
if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
|
|
fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
|
|
else
|
|
fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
client->setCurrentFile(fileNameWithPath);
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("File to store : #%s#\n", client->_currentFile);
|
|
#endif
|
|
free(fileNameWithPath);
|
|
client->_dataTransferPending = STOR_DF;
|
|
client->startTimeout();
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "APPE") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n"));
|
|
//We save the file path to be sent
|
|
char *fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
client->setCurrentFile(fileNameWithPath);
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("File to append : #%s#\n", client->_currentFile);
|
|
#endif
|
|
free(fileNameWithPath);
|
|
client->_dataTransferPending = APPE_DF;
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "SYST") == 0)
|
|
{
|
|
client->_client.println("215 UNIX Type: L8");
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "DELE") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
//We have the file name, we need to append the current directory...
|
|
char *file2deleteNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("file to delete : #%s#\n",file2deleteNameWithPath);
|
|
#endif
|
|
|
|
if(_sdClass->remove(file2deleteNameWithPath))
|
|
{
|
|
client->_client.println("250 Requested file action okay.");
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
|
|
free(file2deleteNameWithPath);
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "RNFR") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
//We have the file name, we need to append the current directory...
|
|
char *fileNameWithPath(NULL);
|
|
|
|
if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
|
|
fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
|
|
else
|
|
fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("file to rename : #%s#\n",fileNameWithPath);
|
|
#endif
|
|
|
|
client->setCurrentFile(fileNameWithPath);
|
|
free(fileNameWithPath);
|
|
|
|
client->_client.println("350 Requested file action pending further information.");
|
|
}
|
|
else
|
|
{
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "RNTO") == 0)
|
|
{
|
|
if(!client->_loggedIn)
|
|
{
|
|
sendInfoResponse(_530, client);
|
|
return;
|
|
}
|
|
|
|
if (client->_cmdParameters->count() > 0) //If the name starts with a /, we do not need to prepend the directory
|
|
{
|
|
//Here we rename the file
|
|
char *file2RenameNameWithPath(NULL);
|
|
if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
|
|
file2RenameNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
|
|
else
|
|
file2RenameNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("file to rename to : #%s#\n",file2RenameNameWithPath);
|
|
Serial.printf("Old name : %s --> %s\n",client->_currentFile,file2RenameNameWithPath);
|
|
#endif
|
|
|
|
if(_sdClass->rename(client->_currentFile,file2RenameNameWithPath))
|
|
{
|
|
client->_client.println("250 Requested file action okay.");
|
|
}else
|
|
sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
|
|
|
|
free(file2RenameNameWithPath);
|
|
}
|
|
}
|
|
else if(strcmp(client->_ftpCommand, "QUIT") == 0)
|
|
{
|
|
if(client->_dataTransferPending == NONE)//If no transfers are in progress
|
|
{
|
|
sendInfoResponse(_221, client);
|
|
|
|
client->_client.stop();
|
|
client->_clientState = TCPClient::ClientState::DISCARDED;
|
|
}
|
|
else
|
|
sendInfoResponse(_221, client);
|
|
}
|
|
/*else if(strcmp(client->_ftpCommand, "SIZE") == 0)
|
|
{
|
|
if (client->_cmdParameters->count() > 0)
|
|
{
|
|
client->_client.println("213 15224");
|
|
}
|
|
}*/
|
|
else
|
|
{
|
|
client->_client.println("502 Command not implemented.");
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Command not implemented");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//Here we write the received file to the sd card
|
|
IRAM_ATTR inline boolean writeToSdCard(T *client, FileTransferStatus *fts, boolean append = false)
|
|
{
|
|
if (client->_currentFile != NULL)
|
|
{
|
|
if(_sdClass->exists(client->_currentFile) && client->_fileRecvBytes == 0 && !append)
|
|
_sdClass->remove(client->_currentFile);
|
|
|
|
File fileBeeingReceived = _sdClass->open(client->_currentFile, FILE_WRITE);
|
|
|
|
if(fileBeeingReceived)
|
|
{
|
|
size_t mallocAcceptedSize(0);
|
|
uint8_t *recvBuffer = (uint8_t*)mallocWithFallback(SEND_RECV_BUFFER_SIZE * sizeof(uint8_t), &mallocAcceptedSize);
|
|
|
|
if(!recvBuffer)
|
|
{
|
|
*fts = MALLOC_ERROR;
|
|
fileBeeingReceived.close();
|
|
return false;
|
|
}
|
|
|
|
/*fileBeeingReceived.seek(client->_fileRecvBytes);*/
|
|
size_t size = client->_dataClient.read(recvBuffer, mallocAcceptedSize);
|
|
fileBeeingReceived.write(recvBuffer, size);
|
|
|
|
free(recvBuffer);
|
|
|
|
client->_fileRecvBytes += size;
|
|
fileBeeingReceived.close();
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Writting : %d bytes to file\n", size);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
*fts = NOT_FOUND;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*fts = NO_FILE_NAME;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Here we send the fs tree to the ftp client
|
|
IRAM_ATTR inline boolean sendFSTree(T *client)
|
|
{
|
|
if (client->_currentDirectory != NULL)
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Directory : %s\n",client->_currentDirectory);
|
|
#endif
|
|
File currentDirectory = _sdClass->open(client->_currentDirectory);
|
|
if (currentDirectory)
|
|
{
|
|
//currentDirectory.rewindDirectory();
|
|
while (true) //May be removed in the future to improve responsiveness
|
|
{
|
|
File fileOrDir = currentDirectory.openNextFile();
|
|
|
|
if (!fileOrDir) //No more files in the directory
|
|
break;
|
|
|
|
//We try to retrieve the last modification date and we remove one day and one hour
|
|
const time_t fileModifDate = fileOrDir.getLastWrite() - 60*60;
|
|
struct tm *timeP = localtime(&fileModifDate);
|
|
|
|
//We get the month's three letter abbreviation
|
|
uint32_t monthAbbreviation = monthNumTo3LetterAbbreviation(timeP->tm_mon + 1); //+1 because in the tm struct, month goes from 0 to 11 ...
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Filename : %s, %s %d %d %d:%d\n", fileOrDir.name(), (char *)&monthAbbreviation, timeP->tm_mon, timeP->tm_mday, timeP->tm_hour, timeP->tm_min);
|
|
#endif
|
|
|
|
char zero_prepended[3][3] = {"","",""};
|
|
|
|
client->_dataClient.printf("%crwxrwxrwx 1 owner esp8266 %d %s %s %s:%s %s\r\n",
|
|
fileOrDir.isDirectory() ? 'd' : '-',
|
|
fileOrDir.isDirectory() ? 0 : fileOrDir.size(),
|
|
(char *)&monthAbbreviation,
|
|
dateTimeFormater(zero_prepended[0],timeP->tm_mday,'0'),
|
|
dateTimeFormater(zero_prepended[1],timeP->tm_hour ,'0'),
|
|
dateTimeFormater(zero_prepended[2],timeP->tm_min,'0'),
|
|
fileOrDir.name());
|
|
|
|
fileOrDir.close();
|
|
}
|
|
|
|
currentDirectory.close();
|
|
}
|
|
else //Failed to open directory
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Failed to open directory");
|
|
#endif
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//The binary flag needs to be taken into consideration
|
|
IRAM_ATTR inline boolean sendFile(T *client, FileTransferStatus *fts)
|
|
{
|
|
if (client->_currentFile != nullptr)
|
|
{
|
|
File fileToSend = _sdClass->open(client->_currentFile);
|
|
|
|
if (fileToSend)
|
|
{
|
|
*fts = OK;
|
|
size_t readBytes(0), sentBytes(0);
|
|
fileToSend.seek(client->_fileSentBytes);
|
|
|
|
if(fileToSend.available())
|
|
{
|
|
size_t mallocAcceptedSize(0);
|
|
uint8_t *sendBuffer = (uint8_t*)mallocWithFallback(SEND_RECV_BUFFER_SIZE * sizeof(uint8_t), &mallocAcceptedSize);
|
|
|
|
if(!sendBuffer)
|
|
{
|
|
*fts = MALLOC_ERROR;
|
|
fileToSend.close();
|
|
return false;
|
|
}
|
|
|
|
readBytes = fileToSend.read(sendBuffer, mallocAcceptedSize);
|
|
sentBytes = client->_dataClient.write(sendBuffer, readBytes);
|
|
|
|
free(sendBuffer);
|
|
|
|
client->_fileSentBytes += sentBytes;
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("File : bytes read : %u, bytes sent : %u - free stack : %u\n", readBytes, sentBytes, ESP.getFreeContStack());
|
|
#endif
|
|
}
|
|
else //The whole file has been sent
|
|
{
|
|
fileToSend.close();
|
|
client->_fileSentBytes = 0;
|
|
return false;
|
|
}
|
|
|
|
fileToSend.close();
|
|
}
|
|
else //Failed to open file maybe not found
|
|
{
|
|
#ifdef DEBUG_FTPS
|
|
Serial.println("Failed to open file");
|
|
#endif
|
|
*fts = NOT_FOUND;
|
|
return false;
|
|
}
|
|
|
|
return true;//Still things to send
|
|
}
|
|
|
|
*fts = NOT_FOUND;
|
|
return false;
|
|
}
|
|
|
|
//This functions construct the full file path.
|
|
//ie : if the current directory is : "/somedir/subdir" and the received file name is : "my file .txt"
|
|
//then it will return : "/somedir/subdir/my file .txt"
|
|
//Note that the file name is contained in a parameter list : "my" in the first element, then "file" in the second and finally ".txt" in the third.
|
|
//Return NULL if malloc fails
|
|
// DO NOT FORGET TO FREE THE ALLOCATED STRING AFTER USING IT....
|
|
static char *constructFileNameWithPath(const char *dir, Dictionary<DictionaryHelper::StringEntity> *parameters)
|
|
{
|
|
uint16_t fileWithPathSize(strlen(dir) + 1) /*dir path plus filename*/, paramCount(parameters->count());
|
|
char *fileNameWithPath(NULL);
|
|
|
|
for(int i(0); i < paramCount; i++)
|
|
{
|
|
fileWithPathSize += strlen(parameters->getAt(i)->getString()) + 1;
|
|
}
|
|
|
|
fileNameWithPath = (char *)malloc( sizeof(char) * fileWithPathSize );
|
|
|
|
#ifdef DEBUG_FTPS
|
|
Serial.printf("Allocated string size : %d\n",fileWithPathSize);
|
|
#endif
|
|
|
|
if(fileNameWithPath == NULL)//Malloc fails
|
|
return NULL;
|
|
|
|
strcpy(fileNameWithPath, dir);
|
|
if(strcmp(fileNameWithPath, "/") != 0 && strlen(dir) != 0)
|
|
strcat(fileNameWithPath,"/");
|
|
|
|
for(int i(0); i < paramCount; i++)
|
|
{
|
|
strcat(fileNameWithPath, parameters->getAt(i)->getString());
|
|
|
|
if(i != paramCount-1)
|
|
strcat(fileNameWithPath," ");
|
|
}
|
|
|
|
return fileNameWithPath;
|
|
}
|
|
|
|
//Error code functions
|
|
void sendInfoResponse(FTPMsgCode code, T *client, const char *msg = "")
|
|
{
|
|
switch(code)
|
|
{
|
|
case _200:
|
|
client->_client.printf_P(PSTR("200 Command okay. %s\r\n"), msg);
|
|
break;
|
|
case _221:
|
|
client->_client.printf_P(PSTR("221 Service closing control connection. %s\r\n"));
|
|
break;
|
|
case _530:
|
|
client->_client.printf_P(PSTR("530 Password required. %s\r\n"), msg);
|
|
break;
|
|
case _550:
|
|
client->_client.printf_P(PSTR("550 Requested action not taken. %s\r\n"), msg);
|
|
break;
|
|
default:
|
|
client->_client.printf_P(PSTR("XXX Unhandled error code. %s\r\n"), msg);
|
|
}
|
|
}
|
|
|
|
char *_login = nullptr;
|
|
char *_password = nullptr;
|
|
char *_FTPDir = nullptr;
|
|
uint16_t _dataPort = 1024;
|
|
|
|
WiFiServer _dataServer; //In passive mode, the FTP server opens two different ports (one for the commands and the other for the data stream)
|
|
SDClass *_sdClass;
|
|
};
|
|
|
|
#endif //FTPSERVER_H
|