ESP8266_swiss_army_board/src/app/FTPServer.h
2019-10-16 19:19:57 +02:00

705 lines
23 KiB
C++

#ifndef FTPSERVER_H
#define FTPSERVER_H
#include "TCPServer.h"
#include "SDCardManager.h"
#include "definition.h"
#define DEBUG_FTPS
#define READ_WRITE_BUFFER_SIZE 2048
template <typename T>
class FTPServer : public TCPServer<T>
{
public:
enum FTPClientState {INIT, WAITING_FOR_COMMANDS};
enum FTPClientDataTransfer {NONE, LIST_DF, NLST_DF, RETR_DF, STOR_DF};
enum FileTransferStatus {OK, NOT_FOUND, NO_FILE_NAME};
enum BinaryFlag {OFF = 0, ON};
FTPServer(unsigned int port = 21, SDCardManager *sdCardManager = NULL, const char *login = NULL, const char *password = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientCommandDataBufferSize = 255) : TCPServer<T>(port, maxClient, clientCommandDataBufferSize),
_login(NULL),
_password(NULL),
_dataPort(1024),
_dataServer(_dataPort),
_sdCardManager(sdCardManager)
{
if (login != NULL)
{
if (strlen(login) > 0)
{
_login = (char *)malloc((sizeof(char) * strlen(login)) + 1);
strcpy(_login, login);
}
}
if (password != NULL)
{
if (strlen(password) > 0)
{
_password = (char *)malloc((sizeof(char) * strlen(password)) + 1);
strcpy(_password, password);
}
}
_dataServer.begin(_dataPort);
}
void setCustomDataPort(unsigned int port)
{
_dataPort = port;
}
virtual ~FTPServer()
{
free(_login); free(_password);
}
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;
}
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)
{
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: //We list the files of the current directory
//We check if the dataConnection is established:
if (client->_dataClient.connected())
{
client->_client.println("150 File status okay;");
sendFSTree(client);
client->_client.println("226 Closing data connection.");
client->_dataClient.stop();
client->_dataTransferPending = NONE;
}
//A timeout should be added
/*else
{
client->_client.println("425 Can't open data connection.");
}*/
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))//File was sent or error occured
{
//we check the return code
switch(fts)
{
case OK:
client->_client.println("226 Closing data connection.");
client->_dataClient.stop();
client->_dataTransferPending = NONE;
break;
case NOT_FOUND:
client->_client.println("451 File not found.");
client->_dataClient.stop();
client->_dataTransferPending = NONE;
break;
}
}
}
//A timeout should be added
/*else
{
client->_client.println("425 Can't open data connection.");
}*/
break;
case STOR_DF :
if (client->_dataClient.connected() || client->_dataClient.available())
{
if(client->_dataClient.available())
{
#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
{
client->_client.println("451 Requested action aborted: local error in processing.");
}
client->_dataClient.stop();
client->_fileIsBeeingReceived = false;
client->_dataTransferPending = NONE;
}
else
client->_fileIsBeeingReceived = true;
}
}
else if(client->_fileIsBeeingReceived)
{
#ifdef DEBUG_FTPS
Serial.println("Whole file received");
#endif
client->_client.println("226 Closing data connection.");
client->_fileIsBeeingReceived = false;
client->_dataClient.stop();
client->_dataTransferPending = NONE;
client->_fileRecvBytes = 0;
}
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 INIT:
client->setCurrentDirectory(FTP_DIR);
client->_ftpClientState = WAITING_FOR_COMMANDS;
break;
case WAITING_FOR_COMMANDS:
processCommands(client);
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
client->_client.println("530 Username required.");
}
else //The ftp access is open
{
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
{
client->_client.println("530 Wrong username or password !.");
}
}
else
{
client->_client.println("530 Password required.");
}
}
else if (strcmp(client->_ftpCommand, "PWD") == 0) //We set the default directory
{
client->_client.printf("257 \"%s\"\r\n", client->_currentDirectory);
}
else if (strcmp(client->_ftpCommand, "TYPE") == 0)
{
if (client->_cmdParameters->count() > 0)
{
switch (client->_cmdParameters->getAt(0)->getString()[0])
{
case 'I':
client->_binaryFlag = ON;
client->_client.println("200 Command okay.");
break;
case 'L':
client->_binaryFlag = ON;
client->_client.println("200 Command okay.");
break;
case 'A':
client->_binaryFlag = OFF;
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)
{
client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3], _dataPort / 256, _dataPort % 256);
client->_waitingForDataConnection = true;
#ifdef DEBUG_FTPS
Serial.println("Opening data server for new data client");
#endif
}
else if (strcmp(client->_ftpCommand, "LIST") == 0)
{
//We inform that a data transfer is pending
client->_dataTransferPending = LIST_DF;
}
else if (strcmp(client->_ftpCommand, "CWD") == 0)
{
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 = lastIndexOf(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(client->_cmdParameters->getAt(0)->getString()[0] == '/')//Then this is a name prefix
{
client->setCurrentDirectory(client->_cmdParameters->getAt(0)->getString());
}
else
{
char *temp = (char *)malloc((sizeof(char) * strlen(client->_currentDirectory)) + (sizeof(char) * strlen(client->_cmdParameters->getAt(0)->getString())) + 2);
strcpy(temp,client->_currentDirectory);
if(strcmp(temp, "/") != 0)strcat(temp,"/");
strcat(temp,client->_cmdParameters->getAt(0)->getString());
#ifdef DEBUG_FTPS
Serial.printf("Final dir : %s\n",temp);
#endif
client->setCurrentDirectory(temp);
free(temp);
}
}
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->_cmdParameters->count() > 0)
{
//We save the file path to be sent
char *temp = (char *)malloc((sizeof(char) * strlen(client->_currentDirectory)) + (sizeof(char) * strlen(client->_cmdParameters->getAt(0)->getString())) + (strcmp(client->_currentDirectory,"/") == 0 ? 0 : 1) + 1);
strcpy(temp, client->_currentDirectory);
if(strcmp(client->_currentDirectory,"/") != 0 )strcat(temp, "/");
strcat(temp, client->_cmdParameters->getAt(0)->getString());
client->setCurrentFile(temp);
#ifdef DEBUG_FTPS
Serial.printf("File to donwload : %s\n", temp);
#endif
free(temp);
client->_dataTransferPending = RETR_DF;
}
}
else if(strcmp(client->_ftpCommand, "MKD") == 0)
{
if (client->_cmdParameters->count() > 0)
{
uint16_t dirNameSize(0), paramCount(client->_cmdParameters->count());
char *dirName(NULL);
for(int i(0); i < paramCount; i++)
{
dirNameSize += strlen(client->_cmdParameters->getAt(i)->getString());
if(i != paramCount-1)
dirNameSize++;
}
dirName = (char *)malloc((sizeof(char)*dirNameSize) + 1);
if(dirName != NULL)
{
dirName[0] = '\0';
for(int i(0); i < paramCount; i++)
{
strcat(dirName,client->_cmdParameters->getAt(i)->getString());
if(i != paramCount-1)
strcat(dirName," ");
}
#ifdef DEBUG_FTPS
Serial.printf("dirName size : %d, dirName #%s#\n",dirNameSize,dirName);
#endif
if(strlen(dirName) > 8)
dirName[8] = '\0';
//We have the dir name, we need to append the current directory...
uint16_t pathDirNameLength = strlen(dirName) + strlen(client->_currentDirectory) + /*If we need to add a /*/ (strcmp(client->_currentDirectory,"/") == 0 ? 0 : 1) + 1; //for \0
char *pathWithDirName = (char *)malloc(sizeof(char) * pathDirNameLength);
sprintf(pathWithDirName,"%s%s%s",client->_currentDirectory, strcmp(client->_currentDirectory,"/") == 0 ? "" : "/", dirName);
#ifdef DEBUG_FTPS
Serial.printf("Final dirName and path #%s# #%s# size %d\n",dirName, pathWithDirName, pathDirNameLength);
#endif
if(_sdCardManager->mkdir(strupr(pathWithDirName)))
{
client->_client.printf("257 \"%s\"\r\n", pathWithDirName);
}
else
client->_client.println("550 Failed to mkdir (no spaces allowed in dir name).");
free(pathWithDirName);
free(dirName);
}
else
{
client->_client.println("550 Requested action not taken.");
}
}
else
{
client->_client.println("550 Requested action not taken.");
}
}
else if(strcmp(client->_ftpCommand, "RMD") == 0)
{
if (client->_cmdParameters->count() > 0)
{
//We have the dir name, we need to append the current directory...
uint16_t pathDirNameLength = strlen(client->_cmdParameters->getAt(0)->getString()) + strlen(client->_currentDirectory) + /*If we need to add a /*/ (strcmp(client->_currentDirectory,"/") == 0 ? 0 : 1) + 1; //for \0
char *pathWithDirName = (char *)malloc(sizeof(char) * pathDirNameLength);
sprintf(pathWithDirName,"%s%s%s",client->_currentDirectory, strcmp(client->_currentDirectory,"/") == 0 ? "" : "/", client->_cmdParameters->getAt(0)->getString());
#ifdef DEBUG_FTPS
Serial.printf("pathDirName to delete : #%s#\n",pathWithDirName);
#endif
if(_sdCardManager->rmdir(pathWithDirName))
{
client->_client.println("250 Requested file action okay.");
}
else
{
client->_client.println("550 Requested action not taken.");
}
free(pathWithDirName);
}
else
{
client->_client.println("550 Requested action not taken.");
}
}
else if(strcmp(client->_ftpCommand, "STOR") == 0)
{
if (client->_cmdParameters->count() > 0)
{
//We save the file path to be sent
client->_client.println("150 File status okay; about to open data connection.");
char *temp = (char *)malloc((sizeof(char) * strlen(client->_currentDirectory)) + (sizeof(char) * strlen(_83FileNameFormat(client->_cmdParameters->getAt(0)->getString()))) + (strcmp(client->_currentDirectory,"/") == 0 ? 0 : 1) + 1);
strcpy(temp, client->_currentDirectory);
if(strcmp(client->_currentDirectory,"/") != 0 )strcat(temp, "/");
strcat(temp, client->_cmdParameters->getAt(0)->getString());
client->setCurrentFile(temp);
#ifdef DEBUG_FTPS
Serial.printf("File to store : %s\n", client->_currentFile);
#endif
free(temp);
client->_dataTransferPending = STOR_DF;
}
}
else if(strcmp(client->_ftpCommand, "SYST") == 0)
{
client->_client.println("215 UNIX Type: L8");
}
else if(strcmp(client->_ftpCommand, "DELE") == 0)
{
if (client->_cmdParameters->count() > 0)
{
//We have the dir name, we need to append the current directory...
uint16_t pathDirNameLength = strlen(client->_cmdParameters->getAt(0)->getString()) + strlen(client->_currentDirectory) + /*If we need to add a /*/ (strcmp(client->_currentDirectory,"/") == 0 ? 0 : 1) + 1; //for \0
char *pathWithDirName = (char *)malloc(sizeof(char) * pathDirNameLength);
sprintf(pathWithDirName,"%s%s%s",client->_currentDirectory, strcmp(client->_currentDirectory,"/") == 0 ? "" : "/", client->_cmdParameters->getAt(0)->getString());
#ifdef DEBUG_FTPS
Serial.printf("file to delete : #%s#\n",pathWithDirName);
#endif
if(_sdCardManager->remove(pathWithDirName))
{
client->_client.println("250 Requested file action okay.");
}
else
{
client->_client.println("550 Requested action not taken.");
}
free(pathWithDirName);
}
else
{
client->_client.println("550 Requested action not taken.");
}
}
else
client->_client.println("502 Command not implemented.");
}
//Here we write the received file to the sd card
boolean writeToSdCard(T *client, FileTransferStatus *fts)
{
if (client->_currentFile != NULL)
{
if(_sdCardManager->exists(client->_currentFile) && client->_fileRecvBytes == 0)
_sdCardManager->remove(client->_currentFile);
File fileBeeingReceived = _sdCardManager->open(client->_currentFile, FILE_WRITE);
if(fileBeeingReceived)
{
char recvBuffer[1024];
/*fileBeeingReceived.seek(client->_fileRecvBytes);*/
uint16_t size = client->_dataClient.read((uint8_t *)recvBuffer, 1024);
fileBeeingReceived.write(recvBuffer, size);
client->_fileRecvBytes += size;
fileBeeingReceived.close();
#ifdef DEBUG_FTPS
Serial.println("File beeing written");
#endif
}
else
{
return false;
}
}
else
{
*fts = NO_FILE_NAME;
return false;
}
return true;
}
//Here we send the fs tree to the ftp client
boolean sendFSTree(T *client)
{
if (client->_currentDirectory != NULL)
{
#ifdef DEBUG_FTPS
Serial.printf("Directory : %s\n",client->_currentDirectory);
#endif
File currentDirectory = _sdCardManager->open(client->_currentDirectory);
currentDirectory.rewindDirectory();
if (currentDirectory)
{
while (true) //Maybe be remove in the future to improve responsiveness
{
File fileOrDir = currentDirectory.openNextFile();
if (!fileOrDir) //No more files in the directory
break;
#ifdef DEBUG_FTPS
Serial.printf("Filename : %s\n", fileOrDir.name());
#endif
client->_dataClient.printf("%crwxrwxrwx 1 owner esp8266 %d Aug 26 16:31 %s\r\n", fileOrDir.isDirectory() ? 'd' : '-', fileOrDir.isDirectory() ? 0 : fileOrDir.size(), 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
boolean sendFile(T *client, FileTransferStatus *fts)
{
if (client->_currentFile != NULL)
{
char sendBuffer[READ_WRITE_BUFFER_SIZE];
File fileToSend = _sdCardManager->open(client->_currentFile);
if (fileToSend)
{
*fts = OK;
unsigned int readBytes(0);
fileToSend.seek(client->_fileSentBytes);
if(fileToSend.available())
{
readBytes = fileToSend.read(sendBuffer, READ_WRITE_BUFFER_SIZE);
client->_dataClient.write(sendBuffer, readBytes);
client->_fileSentBytes += readBytes;
#ifdef DEBUG_FTPS
Serial.printf("File : bytes sent : %u\n",readBytes);
#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;
}
char *_83FileNameFormat(char *filename)
{
char *buffer = (char *)malloc((sizeof(char) * strlen(filename)) + 1);
strcpy(buffer, filename);
char *delimiter = strchr(buffer, '.');
if(delimiter != NULL)
{
*delimiter = '\0';
if(strlen(buffer) > 8)
buffer[8] = '\0';
strcpy(filename, buffer);
if(strlen(delimiter + 1) > 3)
*(delimiter + 1 + 3) = '\0';
strcat(filename, ".");
strcat(filename, delimiter + 1);
}
else
{
if(strlen(buffer) > 8)
{
buffer[8] = '\0';
}
strcpy(filename, buffer);
}
free(buffer);
return filename;
}
char *_login;
char *_password;
unsigned int _dataPort;
WiFiServer _dataServer; //In passive mode, the FTP server opens two different ports (one for the commands and the other for the data stream)
SDCardManager *_sdCardManager;
};
#endif //FTPSERVER_H