summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--FTPClient.cpp316
-rw-r--r--FTPClient.h119
-rw-r--r--FTPCommon.cpp146
-rw-r--r--FTPCommon.h131
4 files changed, 712 insertions, 0 deletions
diff --git a/FTPClient.cpp b/FTPClient.cpp
new file mode 100644
index 0000000..c6cccc1
--- /dev/null
+++ b/FTPClient.cpp
@@ -0,0 +1,316 @@
+#include "FTPClient.h"
+
+// helper macro
+#define CLIENT_SEND(fmt, ...) \
+ do \
+ { \
+ FTP_DEBUG_MSG(">>> " fmt, ##__VA_ARGS__); \
+ control.printf_P(PSTR(fmt "\n"), ##__VA_ARGS__); \
+ } while (0)
+
+FTPClient::FTPClient(FS &_FSImplementation) : FTPCommon(_FSImplementation)
+{
+}
+
+void FTPClient::begin(const ServerInfo &theServer)
+{
+ _server = &theServer;
+}
+
+const FTPClient::Status &FTPClient::transfer(const String &localFileName, const String &remoteFileName, TransferType direction)
+{
+ _serverStatus.result = PROGRESS;
+ if (ftpState >= cIdle)
+ {
+ _remoteFileName = remoteFileName;
+ _direction = direction;
+
+ if (direction & FTP_GET)
+ file = THEFS.open(localFileName, "w");
+ else if (direction & FTP_PUT)
+ file = THEFS.open(localFileName, "r");
+
+ if (!file)
+ {
+ _serverStatus.result = ERROR;
+ _serverStatus.code = 65530;
+ _serverStatus.desc = F("Local file error");
+ }
+ else
+ {
+ ftpState = cConnect;
+ if (direction & 0x80)
+ {
+ while (ftpState <= cQuit)
+ {
+ handleFTP();
+ delay(25);
+ }
+ }
+ }
+ }
+ else
+ {
+ // return error code with status "in PROGRESS"
+ _serverStatus.code = 65529;
+ }
+ return _serverStatus;
+}
+
+const FTPClient::Status &FTPClient::check()
+{
+ return _serverStatus;
+}
+
+void FTPClient::handleFTP()
+{
+ if (_server == nullptr)
+ {
+ _serverStatus.result = TransferResult::ERROR;
+ _serverStatus.code = 65535;
+ _serverStatus.desc = F("begin() not called");
+ }
+ else if (ftpState > cIdle)
+ {
+ _serverStatus.result = TransferResult::ERROR;
+ }
+ else if (cConnect == ftpState)
+ {
+ _serverStatus.code = 65534;
+ _serverStatus.desc = F("No connection to FTP server");
+ if (controlConnect())
+ {
+ FTP_DEBUG_MSG("Connection to %s:%u established", control.remoteIP().toString().c_str(), control.remotePort());
+ _serverStatus.result = TransferResult::PROGRESS;
+ ftpState = cGreet;
+ }
+ else
+ {
+ ftpState = cError;
+ }
+ }
+ else if (cGreet == ftpState)
+ {
+ if (waitFor(220 /* 220 (vsFTPd version) */, F("No server greeting")))
+ {
+ CLIENT_SEND("USER %s", _server->login.c_str());
+ ftpState = cUser;
+ }
+ }
+ else if (cUser == ftpState)
+ {
+ if (waitFor(331 /* 331 Password */))
+ {
+ CLIENT_SEND("PASS %s", _server->password.c_str());
+ ftpState = cPassword;
+ }
+ }
+ else if (cPassword == ftpState)
+ {
+ if (waitFor(230 /* 230 Login successful*/))
+ {
+ CLIENT_SEND("PASV");
+ ftpState = cPassive;
+ }
+ }
+ else if (cPassive == ftpState)
+ {
+ if (waitFor(227 /* 227 Entering Passive Mode (ip,ip,ip,ip,port,port) */))
+ {
+ bool parseOK = false;
+ // find ()
+ uint8_t bracketOpen = _serverStatus.desc.indexOf(F("("));
+ uint8_t bracketClose = _serverStatus.desc.indexOf(F(")"));
+ if (bracketOpen && (bracketClose > bracketOpen))
+ {
+ FTP_DEBUG_MSG("Parsing PASV response %s", _serverStatus.desc.c_str());
+ _serverStatus.desc[bracketClose] = '\0';
+ if (parseDataIpPort(_serverStatus.desc.c_str() + bracketOpen + 1))
+ {
+ // catch ip=0.0.0.0 and replace with the control.remoteIP()
+ if (dataIP.toString() == F("0.0.0.0"))
+ {
+ dataIP = control.remoteIP();
+ }
+ parseOK = true;
+ ftpState = cData;
+ }
+ }
+ if (!parseOK)
+ {
+ _serverStatus.code = 65533;
+ _serverStatus.desc = F("FTP server response not understood.");
+ }
+ }
+ }
+ else if (cData == ftpState)
+ {
+ // open data connection
+ if (dataConnect() < 0)
+ {
+ _serverStatus.code = 65532;
+ _serverStatus.desc = F("No data connection to FTP server");
+ ftpState = cError;
+ }
+ else
+ {
+ FTP_DEBUG_MSG("Data connection to %s:%u established", data.remoteIP().toString().c_str(), data.remotePort());
+ millisBeginTrans = millis();
+ bytesTransfered = 0;
+ ftpState = cTransfer;
+ if (_direction & FTP_PUT_NONBLOCKING)
+ {
+ CLIENT_SEND("STOR %s", _remoteFileName.c_str());
+ allocateBuffer(file.size());
+ }
+ else if (_direction & FTP_GET_NONBLOCKING)
+ {
+ CLIENT_SEND("RETR %s", _remoteFileName.c_str());
+ allocateBuffer(2048);
+ }
+ }
+ }
+ else if (cTransfer == ftpState)
+ {
+ bool res = true;
+ if (_direction & FTP_PUT_NONBLOCKING)
+ {
+ res = doFiletoNetwork();
+ }
+ else
+ {
+ res = doNetworkToFile();
+ }
+ if (!res || !data.connected())
+ {
+ ftpState = cFinish;
+ }
+ }
+ else if (cFinish == ftpState)
+ {
+ closeTransfer();
+ ftpState = cQuit;
+ }
+ else if (cQuit == ftpState)
+ {
+ CLIENT_SEND("QUIT");
+ _serverStatus.result = OK;
+ ftpState = cIdle;
+ }
+ else if (cIdle == ftpState)
+ {
+ stop();
+ }
+}
+
+int8_t FTPClient::controlConnect()
+{
+ if (_server->validateCA)
+ {
+ FTP_DEBUG_MSG("Ignoring CA verification - FTP only");
+ }
+ control.connect(_server->servername, _server->port);
+ FTP_DEBUG_MSG("Connection to %s:%d ... %S", _server->servername.c_str(), _server->port, control.connected() ? PSTR("OK") : PSTR("failed"));
+ if (control.connected())
+ return 1;
+ return -1;
+}
+
+bool FTPClient::waitFor(const uint16_t respCode, const __FlashStringHelper *errorString, uint16_t timeOut)
+{
+ // initalize waiting
+ if (0 == waitUntil)
+ {
+ waitUntil = millis();
+ waitUntil += timeOut;
+ _serverStatus.desc.clear();
+ }
+ else
+ {
+ // timeout
+ if ((int32_t)(millis() - waitUntil) >= 0)
+ {
+ FTP_DEBUG_MSG("Waiting for code %u - timeout!", respCode);
+ _serverStatus.code = 65535;
+ if (errorString)
+ {
+ _serverStatus.desc = errorString;
+ }
+ else
+ {
+ _serverStatus.desc = F("timeout");
+ }
+ ftpState = cTimeout;
+ waitUntil = 0;
+ return false;
+ }
+
+ // check for bytes from the client
+ while (control.available())
+ {
+ char c = control.read();
+ //FTP_DEBUG_MSG("readChar() line='%s' <= %c", _serverStatus.desc.c_str(), c);
+ if (c == '\n' || c == '\r')
+ {
+ // filter out empty lines
+ _serverStatus.desc.trim();
+ if (0 == _serverStatus.desc.length())
+ continue;
+
+ // line complete, evaluate code
+ _serverStatus.code = strtol(_serverStatus.desc.c_str(), NULL, 0);
+ if (respCode != _serverStatus.code)
+ {
+ ftpState = cError;
+ FTP_DEBUG_MSG("Waiting for code %u but SMTP server replies: %s", respCode, _serverStatus.desc.c_str());
+ }
+ else
+ {
+ FTP_DEBUG_MSG("Waiting for code %u success, SMTP server replies: %s", respCode, _serverStatus.desc.c_str());
+ }
+
+ waitUntil = 0;
+ return (respCode == _serverStatus.code);
+ }
+ else
+ {
+ // just add the char
+ _serverStatus.desc += c;
+ }
+ }
+ }
+ return false;
+}
+
+/*
+bool SMTPSSender::connect()
+{
+ client = new WiFiClientSecure();
+ if (NULL == client)
+ return false;
+
+ DEBUG_MSG("%SCA validation!", _server->validateCA ? PSTR("") : PSTR("NO "));
+
+ if (_server->validateCA == false)
+ {
+ // disable CA checks
+ reinterpret_cast<WiFiClientSecure *>(client)->setInsecure();
+ }
+
+ // Determine if MFLN is supported by a server
+ // if it returns true, use the ::setBufferSizes(rx, tx) to shrink
+ // the needed BearSSL memory while staying within protocol limits.
+ bool mfln = reinterpret_cast<WiFiClientSecure *>(client)->probeMaxFragmentLength(_server->servername, _server->port, 512);
+
+ DEBUG_MSG("MFLN %Ssupported", mfln ? PSTR("") : PSTR("un"));
+
+ if (mfln)
+ {
+ reinterpret_cast<WiFiClientSecure *>(client)->setBufferSizes(512, 512);
+ }
+
+ reinterpret_cast<WiFiClientSecure *>(client)->connect(_server->servername, _server->port);
+ return reinterpret_cast<WiFiClientSecure *>(client)->connected();
+}
+
+*/ \ No newline at end of file
diff --git a/FTPClient.h b/FTPClient.h
new file mode 100644
index 0000000..d252add
--- /dev/null
+++ b/FTPClient.h
@@ -0,0 +1,119 @@
+/** \mainpage FTPClient library
+ *
+ * MIT license
+ * written by Daniel Plasa:
+ * 1. split into a plain FTP and a FTPS class to save code space in case only FTP is needed.
+ * 2. Supply two ways of getting/putting files:
+ * a) blocking, returns only after transfer complete (or error)
+ * b) non-blocking, returns immedeate. call check() for status of process
+ *
+ * When using non-blocking mode, be sure to call update() frequently, e.g. in loop().
+ */
+
+#ifndef FTP_CLIENT_H
+#define FTP_CLIENT_H
+
+/*******************************************************************************
+ ** **
+ ** DEFINITIONS FOR FTP SERVER/CLIENT **
+ ** **
+ *******************************************************************************/
+#include <FS.h>
+#include "FTPCommon.h"
+
+class FTPClient : public FTPCommon
+{
+public:
+ struct ServerInfo
+ {
+ ServerInfo(const String &_l, const String &_pw, const String &_sn, uint16_t _p = 21, bool v = false) : login(_l), password(_pw), servername(_sn), port(_p), validateCA(v) {}
+ ServerInfo() = default;
+ String login;
+ String password;
+ String servername;
+ uint16_t port;
+ bool authTLS = false;
+ bool validateCA = false;
+ };
+
+ typedef enum
+ {
+ OK,
+ PROGRESS,
+ ERROR,
+ } TransferResult;
+
+ typedef struct
+ {
+ TransferResult result;
+ uint16_t code;
+ String desc;
+ } Status;
+
+ typedef enum
+ {
+ FTP_PUT = 1 | 0x80,
+ FTP_GET = 2 | 0x80,
+ FTP_PUT_NONBLOCKING = FTP_PUT & 0x7f,
+ FTP_GET_NONBLOCKING = FTP_GET & 0x7f,
+ } TransferType;
+
+ // contruct an instance of the FTP Client using a
+ // given FS object, e.g. SPIFFS or LittleFS
+ FTPClient(FS &_FSImplementation);
+
+ // initialize FTP Client with the ftp server's credentials
+ void begin(const ServerInfo &server);
+
+ // transfer a file (nonblocking via handleFTP() )
+ const Status &transfer(const String &localFileName, const String &remoteFileName, TransferType direction = FTP_GET);
+
+ // check status
+ const Status &check();
+
+ // call freqently (e.g. in loop()), when using non-blocking mode
+ void handleFTP();
+
+protected:
+ typedef enum
+ {
+ cConnect = 0,
+ cGreet,
+ cUser,
+ cPassword,
+ cPassive,
+ cData,
+ cTransfer,
+ cFinish,
+ cQuit,
+ cIdle,
+ cTimeout,
+ cError
+ } internalState;
+ internalState ftpState = cIdle;
+ Status _serverStatus;
+ uint32_t waitUntil = 0;
+ const ServerInfo *_server = nullptr;
+
+ String _remoteFileName;
+ TransferType _direction;
+
+ int8_t controlConnect(); // connects to ServerInfo, returns -1: no connection possible, +1: connection established
+
+ bool waitFor(const uint16_t respCode, const __FlashStringHelper *errorString = nullptr, uint16_t timeOut = 10000);
+};
+
+// basically just the same as FTPClient but has a different connect() method to account for SSL/TLS
+// connection stuff
+/*
+class FTPSClient : public FTPClient
+{
+public:
+ FTPSClient() = default;
+
+private:
+ virtual bool connect();
+};
+*/
+
+#endif // FTP_CLIENT_H
diff --git a/FTPCommon.cpp b/FTPCommon.cpp
new file mode 100644
index 0000000..66211a8
--- /dev/null
+++ b/FTPCommon.cpp
@@ -0,0 +1,146 @@
+#include "FTPCommon.h"
+
+FTPCommon::FTPCommon(FS &_FSImplementation) : THEFS(_FSImplementation)
+{
+}
+
+FTPCommon::~FTPCommon()
+{
+ stop();
+}
+
+void FTPCommon::stop()
+{
+ control.stop();
+ data.stop();
+ file.close();
+ freeBuffer();
+}
+
+void FTPCommon::setTimeout(uint16_t timeout)
+{
+ sTimeOut = timeout;
+}
+
+uint16_t FTPCommon::allocateBuffer(uint16_t desiredBytes)
+{
+ // allocate a big buffer for file transfers
+ uint16_t maxBlock = ESP.getMaxFreeBlockSize() / 2;
+
+ if (desiredBytes > maxBlock)
+ desiredBytes = maxBlock;
+
+ while (fileBuffer == NULL && desiredBytes > 0)
+ {
+ fileBuffer = (uint8_t *)malloc(desiredBytes);
+ if (NULL == fileBuffer)
+ {
+ FTP_DEBUG_MSG("Cannot allocate buffer for file transfer, re-trying");
+ // try with less bytes
+ desiredBytes--;
+ }
+ else
+ {
+ fileBufferSize = desiredBytes;
+ }
+ }
+ return fileBufferSize;
+}
+
+void FTPCommon::freeBuffer()
+{
+ free(fileBuffer);
+ fileBuffer = NULL;
+}
+
+int8_t FTPCommon::dataConnect()
+{
+ // open our own data connection
+ data.stop();
+ FTP_DEBUG_MSG("Open data connection to %s:%u", dataIP.toString().c_str(), dataPort);
+ data.connect(dataIP, dataPort);
+ return data.connected() ? 1 : -1;
+}
+
+bool FTPCommon::parseDataIpPort(const char *p)
+{
+ // parse IP and data port of "ip,ip,ip,ip,port,port"
+ uint8_t parsecount = 0;
+ uint8_t tmp[6];
+ while (parsecount < sizeof(tmp))
+ {
+ tmp[parsecount++] = atoi(p);
+ p = strchr(p, ',');
+ if (NULL == p || *(++p) == '\0')
+ break;
+ }
+ if (parsecount >= sizeof(tmp))
+ {
+ // 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];
+ return true;
+ }
+ return false;
+}
+
+bool FTPCommon::doFiletoNetwork()
+{
+ // data connection lost or no more bytes to transfer?
+ if (!data.connected() || (bytesTransfered >= file.size()))
+ {
+ return false;
+ }
+
+ // how many bytes to transfer left?
+ uint32_t nb = (file.size() - bytesTransfered);
+ if (nb > fileBufferSize)
+ nb = fileBufferSize;
+
+ // transfer the file
+ FTP_DEBUG_MSG("Transfer %d bytes fs->net", nb);
+ nb = file.readBytes((char *)fileBuffer, nb);
+ if (nb > 0)
+ {
+ data.write(fileBuffer, nb);
+ bytesTransfered += nb;
+ }
+
+ return (nb > 0);
+}
+
+bool FTPCommon::doNetworkToFile()
+{
+ // Avoid blocking by never reading more bytes than are available
+ int16_t navail = data.available();
+
+ if (navail > 0)
+ {
+ if (navail > fileBufferSize)
+ navail = fileBufferSize;
+ FTP_DEBUG_MSG("Transfer %d bytes net->FS", navail);
+ navail = data.read(fileBuffer, navail);
+ file.write(fileBuffer, navail);
+ bytesTransfered += navail;
+ }
+
+ if (!data.connected() && (navail <= 0))
+ {
+ // connection closed or no more bytes to read
+ return false;
+ }
+ else
+ {
+ // inidcate, we need to be called again
+ return true;
+ }
+}
+
+void FTPCommon::closeTransfer()
+{
+ freeBuffer();
+ file.close();
+ data.stop();
+}
diff --git a/FTPCommon.h b/FTPCommon.h
new file mode 100644
index 0000000..55844f7
--- /dev/null
+++ b/FTPCommon.h
@@ -0,0 +1,131 @@
+#ifndef FTP_COMMON_H
+#define FTP_COMMON_H
+
+#include <stdint.h>
+#include <FS.h>
+#include <WiFiClient.h>
+#include <WString.h>
+
+#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
+
+// 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_CMD(CMD) (FTP_CMD_LE_##CMD) // make command
+#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)
+
+class FTPCommon
+{
+public:
+ // contruct an instance of the FTP Server or Client using a
+ // given FS object, e.g. SPIFFS or LittleFS
+ FTPCommon(FS &_FSImplementation);
+ virtual ~FTPCommon();
+
+ // stops the FTP Server or Client
+ virtual void stop();
+
+ // set a 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
+ virtual void handleFTP() = 0;
+
+protected:
+ WiFiClient control;
+ WiFiClient data;
+
+ File file;
+ FS &THEFS;
+
+ 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;
+ virtual int8_t dataConnect(); // connects to dataIP:dataPort, returns -1: no data connection possible, +1: data connection established
+ bool parseDataIpPort(const char *p);
+
+ uint16_t sTimeOut = // disconnect after 5 min of inactivity
+ FTP_TIME_OUT * 60;
+
+ bool doFiletoNetwork();
+ bool doNetworkToFile();
+ virtual void closeTransfer();
+
+ uint16_t allocateBuffer(uint16_t desiredBytes); // allocate buffer for transfer
+ void freeBuffer();
+ uint8_t *fileBuffer = NULL; // pointer to buffer for file transfer (by allocateBuffer)
+ uint16_t fileBufferSize; // size of buffer
+
+ uint32_t millisBeginTrans; // store time of beginning of a transaction
+ uint32_t bytesTransfered; // bytes transfered
+};
+
+#endif // FTP_COMMON_H