/*
* FTP SERVER FOR ESP32/ESP8266
* based on FTP Server 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
* 2017: modified by @robo8080 (ported to ESP32 and SD)
* 2019: modified by @fa1ke5 (use SD card in SD_MMC mode (No SD lib, SD_MMC lib), and added fully fuctional passive mode ftp server)
* 2020: modified by @jmwislez (support generic FS, and re-introduced ESP8266)
* 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 "ESPFtpServer.h"
#include
#ifdef ESP32
#define F(A) A
#endif
void FtpServer::begin (String uname, String pword, FS *ufp) {
if (is_up) return;
cfsp = ufp;
// Tells the ftp server to begin listening for incoming connection
_FTP_USER = uname;
_FTP_PASS = pword;
ftpServer = new WiFiServer(FTP_CTRL_PORT);
if (!ftpServer) return;
dataServer = new WiFiServer(FTP_DATA_PORT_PASV);
if (!dataServer) return;
ftpServer->begin ();
delay (10);
dataServer->begin ();
delay (10);
millisTimeOut = (uint32_t)FTP_TIME_OUT * 60 * 1000;
millisDelay = 0;
cmdStatus = 0;
iniVariables ();
is_up = true;
}
FtpServer::~FtpServer(void) {
ftpServer->close();
ftpServer->stop();
delete ftpServer;
dataServer->close();
dataServer->stop();
delete dataServer;
}
void FtpServer::iniVariables () {
// Default for data port
dataPort = FTP_DATA_PORT_PASV;
// Default Data connection is Active
dataPassiveConn = false;
// Set the root directory
strcpy (cwdName, "/");
rnfrCmd = false;
transferStatus = 0;
}
void FtpServer::handleFTP () {
if (!is_up) return;
if ((int32_t) (millisDelay - millis ()) > 0) {
return;
}
if (ftpServer->hasClient ()) {
#ifdef FTP_DEBUG
Serial.println (F("-> disconnecting client"));
#endif
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 (F("-> 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 (F("-> 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 (F("530 Timeout"));
millisDelay = millis () + 200; // delay of 200 ms
cmdStatus = 0;
}
}
void FtpServer::clientConnected () {
#ifdef FTP_DEBUG
Serial.println (F("-> client connected"));
#endif
client.println (F("220-Welcome to FTP for ESP8266/ESP32"));
client.println (F("220-By David Paiva"));
client.println (F("220-Version ") + String (FTP_SERVER_VERSION));
client.println (F("220 Put your ftp client in passive mode, and do not attempt more than one connection"));
iCL = 0;
}
void FtpServer::disconnectClient () {
#ifdef FTP_DEBUG
Serial.println (F("-> disconnecting client"));
#endif
abortTransfer ();
client.println (F("221 Goodbye"));
client.stop ();
}
boolean FtpServer::userIdentity () {
if (strcmp (command, "USER")) {
client.println (F("500 Syntax error"));
}
if (strcmp (parameters, _FTP_USER.c_str ())) {
client.println (F("530 user not found"));
}
else {
client.println (F("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 (F("500 Syntax error"));
}
else if (strcmp (parameters, _FTP_PASS.c_str ())) {
client.println (F("530 "));
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> user authenticated"));
#endif
client.println (F("230 OK."));
return true;
}
millisDelay = millis () + 100; // delay of 100 ms
return false;
}
boolean FtpServer::processCommand () {
struct tm * ptm;
time_t ftime;
char buffer[80];
///////////////////////////////////////
// //
// ACCESS CONTROL COMMANDS //
// //
///////////////////////////////////////
//
// CDUP - Change to Parent Directory
//
if (!strcmp (command, "CDUP") || (!strcmp (command, "CWD") && !strcmp (parameters, ".."))) {
bool ok = false;
if (strlen (cwdName) > 1) { // do nothing if cwdName is root
// if cwdName ends with '/', remove it (must not append)
if (cwdName[strlen (cwdName) - 1] == '/') {
cwdName[ strlen (cwdName ) - 1 ] = 0;
}
// search last '/'
char * pSep = strrchr (cwdName, '/');
ok = pSep > cwdName;
// if found, ends the string on its position
if (ok) {
* pSep = 0;
ok = cfsp->exists (cwdName);
}
}
// if an error appends, move to root
if (!ok) {
strcpy (cwdName, "/");
}
client.println (F("250 Ok. Current directory is ") + String (cwdName));
}
//
// CWD - Change Working Directory
//
else if (!strcmp (command, "CWD")) {
char path[FTP_CWD_SIZE];
if (haveParameter () && makeExistsPath (path)) {
strcpy (cwdName, path);
client.println (F("250 Ok. Current directory is ") + String (cwdName));
}
}
//
// PWD - Print Directory
//
else if (!strcmp (command, "PWD")) {
client.println (F("257 \"") + String (cwdName) + F("\" 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 (F("200 S Ok"));
}
else {
client.println (F("504 Only S (tream) is supported"));
}
}
//
// PASV - Passive Connection management
//
else if (!strcmp (command, "PASV")) {
if (data.connected ()) {
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
dataIp = WiFi.localIP ();
dataPort = FTP_DATA_PORT_PASV;
#ifdef FTP_DEBUG
Serial.println (F("-> connection management set to passive"));
Serial.println (F("-> data port set to ") + String (dataPort));
#endif
client.println (F("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 ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
// 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 (F("501 Can't interpret parameters"));
}
else {
client.println (F("200 PORT command successful"));
dataPassiveConn = false;
}
}
//
// STRU - File Structure
//
else if (!strcmp (command, "STRU")) {
if (!strcmp (parameters, "F")) {
client.println (F("200 F Ok"));
}
else {
client.println (F("504 Only F (ile) is supported"));
}
}
//
// TYPE - Data Type
//
else if (!strcmp (command, "TYPE")) {
if (!strcmp (parameters, "A")) {
client.println (F("200 TYPE is now ASCII"));
}
else if (!strcmp (parameters, "I" )) {
client.println (F("200 TYPE is now 8-bit binary"));
}
else {
client.println (F("504 Unknown TYPE"));
}
}
///////////////////////////////////////
// //
// FTP SERVICE COMMANDS //
// //
///////////////////////////////////////
//
// ABOR - Abort
//
else if (!strcmp (command, "ABOR")) {
abortTransfer ();
client.println (F("226 Data connection closed"));
}
//
// DELE - Delete a File
//
else if (!strcmp (command, "DELE")) {
char path[FTP_CWD_SIZE];
if (strlen (parameters) == 0) {
client.println (F("501 No file name"));
}
else if (makePath (path)) {
if (!cfsp->exists (path)) {
client.println (F("550 File ") + String (parameters) + F(" not found"));
}
else {
if (cfsp->remove (path)) {
client.println (F("250 Deleted ") + String (parameters));
// silently recreate the directory if it vanished with the last file it contained
String directory = String (path).substring (0, String(path).lastIndexOf ("/"));
if (!cfsp->exists (directory.c_str())) {
cfsp->mkdir (directory.c_str());
}
}
else {
client.println (F("450 Can't delete ") + String (parameters));
}
}
}
}
//
// LIST - List
//
else if (!strcmp (command, "LIST")) {
if (dataConnect ()) {
client.println (F("150 Accepted data connection"));
uint16_t nm = 0;
#ifdef ESP8266
Dir dir = cfsp->openDir (cwdName);
while (dir.next ()) {
String fname, fsize;
fname = dir.fileName ();
time_t ftime = dir.fileTime ();
ptm = gmtime (&ftime);
int pos = fname.lastIndexOf ("/"); //looking for the beginning of the file by the last "/"
fname.remove (0, pos + 1); //Delete everything up to and including the filename
fsize = String (dir.fileSize ());
if (dir.isDirectory ()){
sprintf (buffer, "%04d-%02d-%02d %02d:%02d %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fname.c_str());
}
else {
sprintf (buffer, "%04d-%02d-%02d %02d:%02d %s %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fillSpaces (14, String (fsize)).c_str(), fname.c_str());
}
data.println (buffer);
nm ++;
}
client.println( "226 " + String (nm) + " matches total");
#endif
#ifdef ESP32
File dir = cfsp->open (cwdName);
if ((!dir) || (!dir.isDirectory ())) {
client.println (F("550 Can't open directory ") + String (cwdName));
}
else {
File file = dir.openNextFile ();
while (file) {
String fname, fsize;
fname = file.name ();
ftime = file.getLastWrite ();
ptm = gmtime (&ftime);
int pos = fname.lastIndexOf ("/"); //looking for the beginning of the file by the last "/"
fname.remove (0, pos + 1); //Delete everything up to and including the filename
#ifdef FTP_DEBUG
Serial.println ("-> " + fname);
#endif
fsize = String (file.size ());
if (file.isDirectory ()){
sprintf (buffer, "%04u-%02u-%02u %02u:%02u %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fname.c_str());
}
else {
sprintf (buffer, "%04u-%02u-%02u %02d:%02u %s %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, fillSpaces (14, String (fsize)).c_str(), fname.c_str());
}
data.println (buffer);
nm ++;
file = dir.openNextFile ();
}
client.println ("226 " + String (nm) + F(" matches total"));
}
#endif
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
else {
client.println (F("425 No data connection"));
data.stop ();
}
}
//
// MLSD - Listing for Machine Processing (see RFC 3659)
//
else if (!strcmp (command, "MLSD")) {
if (!dataConnect ()) {
client.println (F("425 No data connection MLSD"));
}
else {
client.println (F("150 Accepted data connection"));
uint16_t nm = 0;
#ifdef ESP8266
Dir dir = cfsp->openDir (cwdName);
char dtStr[15];
while (dir.next ()) {
String fn, fs;
fn = dir.fileName ();
int pos = fn.lastIndexOf ("/"); //looking for the beginning of the file by the last "/"
fn.remove (0, pos + 1); //Delete everything up to and including the filename
fs = String (dir.fileSize ());
ftime = dir.fileTime ();
ptm = gmtime (&ftime);
if (dir.isDirectory ()) {
sprintf (buffer, "Type=dir;Modify=%04u%02u%02u%02u%02u%02u; %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str());
}
else {
sprintf (buffer, "Type=file;Size=%s;Modify=%04u%02u%02u%02u%02u%02u; %s", fs.c_str(), ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str());
}
data.println (buffer);
nm ++;
}
client.println (F("226-options: -a -l"));
client.println("226 " + String(nm) + F(" matches total"));
#endif
#ifdef ESP32
File dir = cfsp->open (cwdName);
char dtStr[15];
if (!cfsp->exists (cwdName)) {
client.println (F("550 Can't open directory ") + String (parameters));
}
else {
File file = dir.openNextFile ();
while (file) {
String fn, fs;
fn = file.name ();
int pos = fn.lastIndexOf ("/"); // looking for the beginning of the file by the last "/"
fn.remove (0, pos + 1); // delete everything up to and including the filename
fs = String (file.size ());
ftime = file.getLastWrite ();
ptm = gmtime (&ftime);
if (file.isDirectory ()) {
sprintf (buffer, "Type=dir;Modify=%04u%02u%02u%02u%02u%02u; %s", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str());
}
else {
sprintf (buffer, "Type=file;Size=%s;Modify=%04u%02u%02u%02u%02u%02u; %s", fs.c_str(), ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, fn.c_str());
}
data.println (buffer);
nm ++;
file = dir.openNextFile ();
}
client.println (F("226-options: -a -l"));
client.println ("226 " + String (nm) + F(" matches total"));
}
#endif
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
}
//
// NLST - Name List
//
else if (!strcmp (command, "NLST" )) {
if (!dataConnect ()) {
client.println (F("425 No data connection"));
}
else {
client.println (F("150 Accepted data connection"));
uint16_t nm = 0;
#ifdef ESP8266
Dir dir = cfsp->openDir (cwdName);
if (!cfsp->exists (cwdName)) {
client.println (F("550 Can't open directory ") + String (parameters));
}
else {
while (dir.next ()) {
data.println (dir.fileName ());
nm ++;
}
client.println ("226 " + String(nm) + F(" matches total"));
}
#endif
#ifdef ESP32
File dir = cfsp->open (cwdName);
if (!cfsp->exists (cwdName)) {
client.println (F("550 Can't open directory ") + String (parameters));
}
else {
File file = dir.openNextFile ();
while (file) {
data.println (file.name ());
nm ++;
file = dir.openNextFile ();
}
client.println ("226 " + String (nm) + F(" matches total"));
}
#endif
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
}
//
// NOOP
//
else if (!strcmp (command, "NOOP")) {
client.println (F("200 Zzz..."));
}
//
// RETR - Retrieve
//
else if (!strcmp (command, "RETR")) {
char path[FTP_CWD_SIZE];
if (strlen (parameters) == 0) {
client.println (F("501 No file name"));
}
else if (makePath (path)) {
file = cfsp->open (path, "r");
if (!file) {
client.println (F("550 File ") + String (parameters) + F(" not found"));
}
else if (!file) {
client.println (F("450 Can't open ") + String (parameters));
}
else if (!dataConnect ()) {
client.println (F("425 No data connection"));
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> sending ") + String (parameters));
#endif
client.println (F("150-Connected to port ") + String (dataPort));
client.println (F("150 ") + String (file.size ()) + F(" bytes to download"));
millisBeginTrans = millis ();
bytesTransferred = 0;
transferStatus = 1;
}
}
}
//
// STOR - Store
//
else if (!strcmp (command, "STOR")) {
char path[FTP_CWD_SIZE];
if (strlen (parameters) == 0) {
client.println (F("501 No file name"));
}
else if (makePath (path)) {
file = cfsp->open (path, "w");
if (!file) {
client.println (F("451 Can't open/create ") + String (parameters));
}
else if (!dataConnect ()) {
client.println (F("425 No data connection"));
file.close ();
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> receiving ") + String (parameters));
#endif
client.println (F("150 Connected to port ") + String (dataPort));
millisBeginTrans = millis ();
bytesTransferred = 0;
transferStatus = 2;
}
}
}
//
// MKD - Make Directory
//
else if (!strcmp (command, "MKD")) {
char path[FTP_CWD_SIZE];
if (haveParameter () && makePath (path)) {
if (cfsp->exists (path)) {
client.println (F("521 Can't create \"") + String (parameters) + F("\", Directory exists"));
}
else {
if (cfsp->mkdir (path)) {
client.println (F("257 \"") + String (parameters) + F("\" created"));
}
else {
client.println (F("550 Can't create \"") + String (parameters) + "\"");
}
}
}
}
//
// RMD - Remove a Directory
//
else if (!strcmp (command, "RMD")) {
char path[FTP_CWD_SIZE];
if (haveParameter () && makePath (path)) {
if (cfsp->rmdir (path)) {
#ifdef FTP_DEBUG
Serial.println (F("-> deleting ") + String (parameters));
#endif
client.println ("250 \"" + String (parameters) + F("\" deleted"));
}
else {
if (cfsp->exists (path)) { // hack
client.println (F("550 Can't remove \"") + String (parameters) + F("\". Directory not empty?"));
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> deleting ") + String (parameters));
#endif
client.println ("250 \"" + String (parameters) + F("\" deleted"));
}
}
}
}
//
// RNFR - Rename From
//
else if (!strcmp (command, "RNFR")) {
buf[0] = 0;
if (strlen (parameters) == 0) {
client.println (F("501 No file name"));
}
else if (makePath (buf)) {
if (!cfsp->exists (buf)) {
client.println (F("550 File ") + String (parameters) + F(" not found"));
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> renaming ") + String (buf));
#endif
client.println (F("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 (F("503 Need RNFR before RNTO"));
}
else if (strlen (parameters ) == 0) {
client.println (F("501 No file name"));
}
else if (makePath (path)) {
if (cfsp->exists (path)) {
client.println (F("553 ") + String (parameters) + F(" already exists"));
}
else {
#ifdef FTP_DEBUG
Serial.println (F("-> renaming ") + String (buf) + " to " + String (path));
#endif
if (cfsp->rename (buf, path)) {
client.println (F("250 File successfully renamed or moved"));
}
else {
client.println (F("451 Rename/move failure"));
}
}
}
rnfrCmd = false;
}
///////////////////////////////////////
// //
// EXTENSIONS COMMANDS (RFC 3659) //
// //
///////////////////////////////////////
//
// FEAT - New Features
//
else if (!strcmp (command, "FEAT")) {
client.println (F("211-Extensions supported:"));
client.println (F(" MLSD"));
client.println (F("211 End."));
}
//
// MDTM - File Modification Time (see RFC 3659)
//
else if (!strcmp (command, "MDTM")) {
client.println (F("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 (F("501 No file name"));
}
else if (makePath (path)) {
file = cfsp->open (path, "r");
if (!file) {
client.println (F("450 Can't open ") + String (parameters));
}
else {
client.println (F("213 ") + String (file.size ()));
file.close ();
}
}
}
//
// SITE - System command
//
else if (!strcmp (command, "SITE")) {
client.println (F("500 Unknown SITE command ") + String (parameters));
}
//
// Unrecognized commands ...
//
else {
client.println (F("500 Unknown 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) {
yield ();
}
if (dataServer->hasClient ()) {
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
data = dataServer->available ();
#ifdef FTP_DEBUG
Serial.println (F("-> client connected to dataserver"));
#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);
bytesTransferred += 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 the buffer
if (navail > FTP_BUF_SIZE) {
navail = FTP_BUF_SIZE;
}
int16_t nb = data.read((uint8_t *)buf, navail);
if (nb > 0) {
file.write((uint8_t *)buf, nb);
bytesTransferred += 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 && bytesTransferred > 0) {
client.println (F("226-File successfully transferred"));
client.println ("226 " + String (deltaT) + " ms, " + String (bytesTransferred / deltaT) + " kbytes/s");
}
else {
client.println (F("226 File successfully transferred"));
}
file.close ();
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> file successfully transferred"));
Serial.println (F("-> client disconnected from dataserver"));
#endif
}
void FtpServer::abortTransfer () {
if (transferStatus > 0) {
file.close ();
data.stop ();
#ifdef FTP_DEBUG
Serial.println (F("-> client disconnected from dataserver"));
#endif
client.println (F("426 Transfer aborted"));
#ifdef FTP_DEBUG
Serial.println (F("-> 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 ();
#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 (F("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 (F("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;
}
bool FtpServer::haveParameter () {
if (parameters != NULL && strlen (parameters) > 0) {
return true;
}
client.println (F("501 No file name"));
return false;
}
bool FtpServer::makeExistsPath (char * path, char * param) {
if (!makePath (path, param)) {
return false;
}
if (cfsp->exists (path)) {
return true;
}
client.println (F("550 ") + String (path) + F(" not found."));
return false;
}
String FtpServer::fillSpaces (uint8_t length, String input) {
String output;
output = "";
while (output.length() < length - input.length()) {
output += " ";
}
output += input;
return (output);
}