1009 lines
25 KiB
C++
1009 lines
25 KiB
C++
|
/*
|
||
|
* FTP Serveur for ESP8266
|
||
|
* based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
|
||
|
* based on Jean-Michel Gallego's work
|
||
|
* modified to work with esp8266 SPIFFS by David Paiva david@nailbuster.com
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "ESP8266FtpServer.h"
|
||
|
#ifdef ESP8266
|
||
|
#include <ESP8266WiFi.h>
|
||
|
#elif defined ESP32
|
||
|
#include <WiFi.h>
|
||
|
#include "SPIFFS.h"
|
||
|
#endif
|
||
|
#include <SD.h>
|
||
|
#include <WiFiClient.h>
|
||
|
#include <FS.h>
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
WiFiServer ftpServer( FTP_CTRL_PORT );
|
||
|
WiFiServer dataServer( FTP_DATA_PORT_PASV );
|
||
|
|
||
|
void FtpServer::begin(String uname, String pword)
|
||
|
{
|
||
|
// Tells the ftp server to begin listening for incoming connection
|
||
|
_FTP_USER=uname;
|
||
|
_FTP_PASS = pword;
|
||
|
|
||
|
ftpServer.begin();
|
||
|
delay(10);
|
||
|
dataServer.begin();
|
||
|
delay(10);
|
||
|
millisTimeOut = (uint32_t)FTP_TIME_OUT * 60 * 1000;
|
||
|
millisDelay = 0;
|
||
|
cmdStatus = 0;
|
||
|
iniVariables();
|
||
|
}
|
||
|
|
||
|
void FtpServer::iniVariables()
|
||
|
{
|
||
|
// Default for data port
|
||
|
dataPort = FTP_DATA_PORT_PASV;
|
||
|
|
||
|
// Default Data connection is Active
|
||
|
dataPassiveConn = true;
|
||
|
|
||
|
// Set the root directory
|
||
|
strcpy( cwdName, "/" );
|
||
|
|
||
|
rnfrCmd = false;
|
||
|
transferStatus = 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
void FtpServer::handleFTP()
|
||
|
{
|
||
|
if((int32_t) ( millisDelay - millis() ) > 0 )
|
||
|
return;
|
||
|
|
||
|
if (ftpServer.hasClient()) {
|
||
|
client.stop();
|
||
|
client = ftpServer.available();
|
||
|
}
|
||
|
|
||
|
if( cmdStatus == 0 )
|
||
|
{
|
||
|
if( client.connected())
|
||
|
disconnectClient();
|
||
|
cmdStatus = 1;
|
||
|
}
|
||
|
else if( cmdStatus == 1 ) // Ftp server waiting for connection
|
||
|
{
|
||
|
abortTransfer();
|
||
|
iniVariables();
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Ftp server waiting for connection on port "+ String(FTP_CTRL_PORT));
|
||
|
#endif
|
||
|
cmdStatus = 2;
|
||
|
}
|
||
|
else if( cmdStatus == 2 ) // Ftp server idle
|
||
|
{
|
||
|
|
||
|
if( client.connected() ) // A client connected
|
||
|
{
|
||
|
clientConnected();
|
||
|
millisEndConnection = millis() + 10 * 1000 ; // wait client id during 10 s.
|
||
|
cmdStatus = 3;
|
||
|
}
|
||
|
}
|
||
|
else if( readChar() > 0 ) // got response
|
||
|
{
|
||
|
if( cmdStatus == 3 ) // Ftp server waiting for user identity
|
||
|
if( userIdentity() )
|
||
|
cmdStatus = 4;
|
||
|
else
|
||
|
cmdStatus = 0;
|
||
|
else if( cmdStatus == 4 ) // Ftp server waiting for user registration
|
||
|
if( userPassword() )
|
||
|
{
|
||
|
cmdStatus = 5;
|
||
|
millisEndConnection = millis() + millisTimeOut;
|
||
|
}
|
||
|
else
|
||
|
cmdStatus = 0;
|
||
|
else if( cmdStatus == 5 ) // Ftp server waiting for user command
|
||
|
if( ! processCommand())
|
||
|
cmdStatus = 0;
|
||
|
else
|
||
|
millisEndConnection = millis() + millisTimeOut;
|
||
|
}
|
||
|
else if (!client.connected() || !client)
|
||
|
{
|
||
|
cmdStatus = 1;
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("client disconnected");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if( transferStatus == 1 ) // Retrieve data
|
||
|
{
|
||
|
if( ! doRetrieve())
|
||
|
transferStatus = 0;
|
||
|
}
|
||
|
else if( transferStatus == 2 ) // Store data
|
||
|
{
|
||
|
if( ! doStore())
|
||
|
transferStatus = 0;
|
||
|
}
|
||
|
else if( cmdStatus > 2 && ! ((int32_t) ( millisEndConnection - millis() ) > 0 ))
|
||
|
{
|
||
|
client.println("530 Timeout");
|
||
|
millisDelay = millis() + 200; // delay of 200 ms
|
||
|
cmdStatus = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FtpServer::clientConnected()
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Client connected!");
|
||
|
#endif
|
||
|
client.println( "220--- Welcome to FTP for ESP8266/ESP32 ---");
|
||
|
client.println( "220--- By David Paiva ---");
|
||
|
client.println( "220 -- Version "+ String(FTP_SERVER_VERSION) +" --");
|
||
|
iCL = 0;
|
||
|
}
|
||
|
|
||
|
void FtpServer::disconnectClient()
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println(" Disconnecting client");
|
||
|
#endif
|
||
|
abortTransfer();
|
||
|
client.println("221 Goodbye");
|
||
|
client.stop();
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::userIdentity()
|
||
|
{
|
||
|
if( strcmp( command, "USER" ))
|
||
|
client.println( "500 Syntax error");
|
||
|
if( strcmp( parameters, _FTP_USER.c_str() ))
|
||
|
client.println( "530 user not found");
|
||
|
else
|
||
|
{
|
||
|
client.println( "331 OK. Password required");
|
||
|
strcpy( cwdName, "/" );
|
||
|
return true;
|
||
|
}
|
||
|
millisDelay = millis() + 100; // delay of 100 ms
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::userPassword()
|
||
|
{
|
||
|
if( strcmp( command, "PASS" ))
|
||
|
client.println( "500 Syntax error");
|
||
|
else if( strcmp( parameters, _FTP_PASS.c_str() ))
|
||
|
client.println( "530 ");
|
||
|
else
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println( "OK. Waiting for commands.");
|
||
|
#endif
|
||
|
client.println( "230 OK.");
|
||
|
return true;
|
||
|
}
|
||
|
millisDelay = millis() + 100; // delay of 100 ms
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::processCommand()
|
||
|
{
|
||
|
///////////////////////////////////////
|
||
|
// //
|
||
|
// ACCESS CONTROL COMMANDS //
|
||
|
// //
|
||
|
///////////////////////////////////////
|
||
|
|
||
|
//
|
||
|
// CDUP - Change to Parent Directory
|
||
|
//
|
||
|
if( ! strcmp( command, "CDUP" ))
|
||
|
{
|
||
|
char* pos = strrchr(cwdName, '/');
|
||
|
if (pos > cwdName) cwdName[pos-cwdName]='\0';
|
||
|
else if (pos==cwdName) cwdName[1]='\0';
|
||
|
client.println("250 Ok. Current directory is " + String(cwdName));
|
||
|
}
|
||
|
//
|
||
|
// CWD - Change Working Directory
|
||
|
//
|
||
|
else if( ! strcmp( command, "CWD" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
|
||
|
if( strcmp( parameters, "." ) == 0 ) // 'CWD .' is the same as PWD command
|
||
|
client.println( "257 \"" + String(cwdName) + "\" is your current directory");
|
||
|
else if (strcmp(parameters, "..")==0) {
|
||
|
char* pos = strrchr(cwdName, '/');
|
||
|
if (pos > cwdName) cwdName[pos-cwdName]='\0';
|
||
|
else if (pos==cwdName) cwdName[1]='\0';
|
||
|
client.println( "250 Ok. Current directory is " + String(cwdName) );
|
||
|
} else
|
||
|
{
|
||
|
if (makePath(path)) {
|
||
|
if (SD.exists(path)) {
|
||
|
File f = SD.open(path);
|
||
|
if (f.isDirectory()) {
|
||
|
strcpy(cwdName, path);
|
||
|
client.println( "250 Ok. Current directory is " + String(cwdName) );
|
||
|
} else {
|
||
|
client.println("450 Path is not a directory.");
|
||
|
}
|
||
|
f.close();
|
||
|
} else {
|
||
|
client.println("450 path does not exist");
|
||
|
}
|
||
|
} else {
|
||
|
client.println( "450 Invalid path");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
//
|
||
|
// PWD - Print Directory
|
||
|
//
|
||
|
else if( ! strcmp( command, "PWD" ))
|
||
|
client.println( "257 \"" + String(cwdName) + "\" is your current directory");
|
||
|
//
|
||
|
// QUIT
|
||
|
//
|
||
|
else if( ! strcmp( command, "QUIT" ))
|
||
|
{
|
||
|
disconnectClient();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////
|
||
|
// //
|
||
|
// TRANSFER PARAMETER COMMANDS //
|
||
|
// //
|
||
|
///////////////////////////////////////
|
||
|
|
||
|
//
|
||
|
// MODE - Transfer Mode
|
||
|
//
|
||
|
else if( ! strcmp( command, "MODE" ))
|
||
|
{
|
||
|
if( ! strcmp( parameters, "S" ))
|
||
|
client.println( "200 S Ok");
|
||
|
// else if( ! strcmp( parameters, "B" ))
|
||
|
// client.println( "200 B Ok\r\n";
|
||
|
else
|
||
|
client.println( "504 Only S(tream) is suported");
|
||
|
}
|
||
|
//
|
||
|
// PASV - Passive Connection management
|
||
|
//
|
||
|
else if( ! strcmp( command, "PASV" ))
|
||
|
{
|
||
|
if (data.connected()) data.stop();
|
||
|
//dataServer.begin();
|
||
|
//dataIp = Ethernet.localIP();
|
||
|
dataIp = client.localIP();
|
||
|
dataPort = FTP_DATA_PORT_PASV;
|
||
|
//data.connect( dataIp, dataPort );
|
||
|
//data = dataServer.available();
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Connection management set to passive");
|
||
|
Serial.println( "Data port set to " + String(dataPort));
|
||
|
#endif
|
||
|
client.println( "227 Entering Passive Mode ("+ String(dataIp[0]) + "," + String(dataIp[1])+","+ String(dataIp[2])+","+ String(dataIp[3])+","+String( dataPort >> 8 ) +","+String ( dataPort & 255 )+").");
|
||
|
dataPassiveConn = true;
|
||
|
}
|
||
|
//
|
||
|
// PORT - Data Port
|
||
|
//
|
||
|
else if( ! strcmp( command, "PORT" ))
|
||
|
{
|
||
|
if (data) data.stop();
|
||
|
// get IP of data client
|
||
|
dataIp[ 0 ] = atoi( parameters );
|
||
|
char * p = strchr( parameters, ',' );
|
||
|
for( uint8_t i = 1; i < 4; i ++ )
|
||
|
{
|
||
|
dataIp[ i ] = atoi( ++ p );
|
||
|
p = strchr( p, ',' );
|
||
|
}
|
||
|
// get port of data client
|
||
|
dataPort = 256 * atoi( ++ p );
|
||
|
p = strchr( p, ',' );
|
||
|
dataPort += atoi( ++ p );
|
||
|
if( p == NULL )
|
||
|
client.println( "501 Can't interpret parameters");
|
||
|
else
|
||
|
{
|
||
|
|
||
|
client.println("200 PORT command successful");
|
||
|
dataPassiveConn = false;
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// STRU - File Structure
|
||
|
//
|
||
|
else if( ! strcmp( command, "STRU" ))
|
||
|
{
|
||
|
if( ! strcmp( parameters, "F" ))
|
||
|
client.println( "200 F Ok");
|
||
|
// else if( ! strcmp( parameters, "R" ))
|
||
|
// client.println( "200 B Ok\r\n";
|
||
|
else
|
||
|
client.println( "504 Only F(ile) is suported");
|
||
|
}
|
||
|
//
|
||
|
// TYPE - Data Type
|
||
|
//
|
||
|
else if( ! strcmp( command, "TYPE" ))
|
||
|
{
|
||
|
if( ! strcmp( parameters, "A" ))
|
||
|
client.println( "200 TYPE is now ASII");
|
||
|
else if( ! strcmp( parameters, "I" ))
|
||
|
client.println( "200 TYPE is now 8-bit binary");
|
||
|
else
|
||
|
client.println( "504 Unknow TYPE");
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////
|
||
|
// //
|
||
|
// FTP SERVICE COMMANDS //
|
||
|
// //
|
||
|
///////////////////////////////////////
|
||
|
|
||
|
//
|
||
|
// ABOR - Abort
|
||
|
//
|
||
|
else if( ! strcmp( command, "ABOR" ))
|
||
|
{
|
||
|
abortTransfer();
|
||
|
client.println( "226 Data connection closed");
|
||
|
}
|
||
|
//
|
||
|
// DELE - Delete a File
|
||
|
//
|
||
|
else if( ! strcmp( command, "DELE" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( path ))
|
||
|
{
|
||
|
if( ! SD.exists( path ))
|
||
|
client.println( "550 File " + String(parameters) + " not found");
|
||
|
else
|
||
|
{
|
||
|
if( SD.remove( path ))
|
||
|
client.println( "250 Deleted " + String(parameters) );
|
||
|
else
|
||
|
client.println( "450 Can't delete " + String(parameters));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// LIST - List
|
||
|
//
|
||
|
else if( ! strcmp( command, "LIST" ))
|
||
|
{
|
||
|
if( ! dataConnect())
|
||
|
client.println( "425 No data connection");
|
||
|
else
|
||
|
{
|
||
|
client.println( "150 Accepted data connection");
|
||
|
uint16_t nm = 0;
|
||
|
File root = SD.open(cwdName);
|
||
|
if(!root){
|
||
|
client.println( "550 Can't open directory " + String(cwdName) );
|
||
|
// return;
|
||
|
} else {
|
||
|
// if(!root.isDirectory()){
|
||
|
// Serial.println("Not a directory");
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
File file = root.openNextFile();
|
||
|
while(file){
|
||
|
if (parameters && strcmp(parameters, "-la")==0) {
|
||
|
data.printf("%s %13d Jan 01 00:00 %s\r\n", file.isDirectory() ? "drwxr-xr-x 1 owner group" : "-rw-r--r-- 1 owner group", file.size(), file.name());
|
||
|
} else {
|
||
|
if(file.isDirectory()){
|
||
|
data.println( "+r,s <DIR> " + String(file.name()));
|
||
|
// Serial.print(" DIR : ");
|
||
|
// Serial.println(file.name());
|
||
|
// if(levels){
|
||
|
// listDir(fs, file.name(), levels -1);
|
||
|
// }
|
||
|
} else {
|
||
|
String fn, fs;
|
||
|
fn = file.name();
|
||
|
// fn.remove(0, 1);
|
||
|
fs = String(file.size());
|
||
|
data.println( "+r,s" + fs);
|
||
|
data.println( ",\t" + fn );
|
||
|
nm ++;
|
||
|
}
|
||
|
}
|
||
|
file = root.openNextFile();
|
||
|
}
|
||
|
client.println( "226 " + String(nm) + " matches total");
|
||
|
}
|
||
|
data.stop();
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// MLSD - Listing for Machine Processing (see RFC 3659)
|
||
|
//
|
||
|
else if( ! strcmp( command, "MLSD" ))
|
||
|
{
|
||
|
if( ! dataConnect())
|
||
|
client.println( "425 No data connection MLSD");
|
||
|
else
|
||
|
{
|
||
|
client.println( "150 Accepted data connection");
|
||
|
uint16_t nm = 0;
|
||
|
File root = SD.open(cwdName);
|
||
|
// if(!root){
|
||
|
// client.println( "550 Can't open directory " + String(cwdName) );
|
||
|
// // return;
|
||
|
// } else {
|
||
|
// if(!root.isDirectory()){
|
||
|
// Serial.println("Not a directory");
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
File file = root.openNextFile();
|
||
|
while(file){
|
||
|
if(file.isDirectory()){
|
||
|
// if(levels){
|
||
|
// listDir(fs, file.name(), levels -1);
|
||
|
// }
|
||
|
data.println(String("Type=dir;modify=20000101160656; ") + file.name());
|
||
|
} else {
|
||
|
String fn, fs;
|
||
|
fn = file.name();
|
||
|
fn.remove(0, 1);
|
||
|
fs = String(file.size());
|
||
|
data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn);
|
||
|
nm ++;
|
||
|
}
|
||
|
file = root.openNextFile();
|
||
|
}
|
||
|
client.println( "226-options: -a -l");
|
||
|
client.println( "226 " + String(nm) + " matches total");
|
||
|
// }
|
||
|
data.stop();
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// NLST - Name List
|
||
|
//
|
||
|
else if( ! strcmp( command, "NLST" ))
|
||
|
{
|
||
|
if( ! dataConnect())
|
||
|
client.println( "425 No data connection");
|
||
|
else
|
||
|
{
|
||
|
client.println( "150 Accepted data connection");
|
||
|
uint16_t nm = 0;
|
||
|
File root = SD.open(cwdName);
|
||
|
if(!root){
|
||
|
client.println( "550 Can't open directory " + String(cwdName) );
|
||
|
} else {
|
||
|
|
||
|
File file = root.openNextFile();
|
||
|
while(file){
|
||
|
data.println( file.name());
|
||
|
nm ++;
|
||
|
file = root.openNextFile();
|
||
|
}
|
||
|
client.println( "226 " + String(nm) + " matches total");
|
||
|
}
|
||
|
data.stop();
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// NOOP
|
||
|
//
|
||
|
else if( ! strcmp( command, "NOOP" ))
|
||
|
{
|
||
|
// dataPort = 0;
|
||
|
client.println( "200 Zzz...");
|
||
|
}
|
||
|
//
|
||
|
// RETR - Retrieve
|
||
|
//
|
||
|
else if( ! strcmp( command, "RETR" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( path ))
|
||
|
{
|
||
|
file = SD.open(path, FILE_READ);
|
||
|
if( !file)
|
||
|
client.println( "550 File " +String(parameters)+ " not found");
|
||
|
else if( !file )
|
||
|
client.println( "450 Can't open " +String(parameters));
|
||
|
else if( ! dataConnect())
|
||
|
client.println( "425 No data connection");
|
||
|
else
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Sending " + String(parameters));
|
||
|
#endif
|
||
|
client.println( "150-Connected to port "+ String(dataPort));
|
||
|
client.println( "150 " + String(file.size()) + " bytes to download");
|
||
|
millisBeginTrans = millis();
|
||
|
bytesTransfered = 0;
|
||
|
transferStatus = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// STOR - Store
|
||
|
//
|
||
|
else if( ! strcmp( command, "STOR" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( path ))
|
||
|
{
|
||
|
file = SD.open(path, FILE_WRITE);
|
||
|
if( !file)
|
||
|
client.println( "451 Can't open/create " +String(parameters) );
|
||
|
else if( ! dataConnect())
|
||
|
{
|
||
|
client.println( "425 No data connection");
|
||
|
file.close();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println( "Receiving " +String(parameters));
|
||
|
#endif
|
||
|
client.println( "150 Connected to port " + String(dataPort));
|
||
|
millisBeginTrans = millis();
|
||
|
bytesTransfered = 0;
|
||
|
transferStatus = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// MKD - Make Directory
|
||
|
//
|
||
|
else if( ! strcmp( command, "MKD" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( path )) {
|
||
|
if (SD.exists(path)) {
|
||
|
client.println("550 Directory already exists");
|
||
|
} else {
|
||
|
SD.mkdir(path);
|
||
|
client.println("257 Directory created");
|
||
|
}
|
||
|
} else {
|
||
|
client.println( "550 Can't create \"" + String(parameters) + "\" - malformed path?"); //not support on espyet
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// RMD - Remove a Directory
|
||
|
//
|
||
|
else if( ! strcmp( command, "RMD" ))
|
||
|
{
|
||
|
client.println( "501 Can't delete \"" +String(parameters));
|
||
|
|
||
|
}
|
||
|
//
|
||
|
// RNFR - Rename From
|
||
|
//
|
||
|
else if( ! strcmp( command, "RNFR" ))
|
||
|
{
|
||
|
client.println("500 Rename not supported");
|
||
|
/*buf[ 0 ] = 0;
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( buf ))
|
||
|
{
|
||
|
if( ! SD.exists( buf ))
|
||
|
client.println( "550 File " +String(parameters)+ " not found");
|
||
|
else
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Renaming " + String(buf));
|
||
|
#endif
|
||
|
client.println( "350 RNFR accepted - file exists, ready for destination");
|
||
|
rnfrCmd = true;
|
||
|
}
|
||
|
}*/
|
||
|
}
|
||
|
//
|
||
|
// RNTO - Rename To
|
||
|
//
|
||
|
else if( ! strcmp( command, "RNTO" ))
|
||
|
{
|
||
|
client.println("500 Rename not supported");
|
||
|
/*char path[ FTP_CWD_SIZE ];
|
||
|
char dir[ FTP_FIL_SIZE ];
|
||
|
if( strlen( buf ) == 0 || ! rnfrCmd )
|
||
|
client.println( "503 Need RNFR before RNTO");
|
||
|
else if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else
|
||
|
|
||
|
else if( makePath( path ))
|
||
|
{
|
||
|
if( SD.exists( path ))
|
||
|
client.println( "553 " +String(parameters)+ " already exists");
|
||
|
else
|
||
|
{
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("Renaming " + String(buf) + " to " + String(path));
|
||
|
#endif
|
||
|
if( SD.rename( buf, path ))
|
||
|
client.println( "250 File successfully renamed or moved");
|
||
|
else
|
||
|
client.println( "451 Rename/move failure");
|
||
|
|
||
|
}
|
||
|
}*/
|
||
|
rnfrCmd = false;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////
|
||
|
// //
|
||
|
// EXTENSIONS COMMANDS (RFC 3659) //
|
||
|
// //
|
||
|
///////////////////////////////////////
|
||
|
|
||
|
//
|
||
|
// FEAT - New Features
|
||
|
//
|
||
|
else if( ! strcmp( command, "FEAT" ))
|
||
|
{
|
||
|
client.println( "211-Extensions suported:");
|
||
|
client.println( " MLSD");
|
||
|
client.println( "211 End.");
|
||
|
}
|
||
|
//
|
||
|
// MDTM - File Modification Time (see RFC 3659)
|
||
|
//
|
||
|
else if (!strcmp(command, "MDTM"))
|
||
|
{
|
||
|
client.println("550 Unable to retrieve time");
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// SIZE - Size of the file
|
||
|
//
|
||
|
else if( ! strcmp( command, "SIZE" ))
|
||
|
{
|
||
|
char path[ FTP_CWD_SIZE ];
|
||
|
if( strlen( parameters ) == 0 )
|
||
|
client.println( "501 No file name");
|
||
|
else if( makePath( path ))
|
||
|
{
|
||
|
file = SD.open(path, FILE_READ);
|
||
|
if(!file)
|
||
|
client.println( "450 Can't open " +String(parameters) );
|
||
|
else
|
||
|
{
|
||
|
client.println( "213 " + String(file.size()));
|
||
|
file.close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
// SITE - System command
|
||
|
//
|
||
|
else if( ! strcmp( command, "SITE" ))
|
||
|
{
|
||
|
client.println( "500 Unknow SITE command " +String(parameters) );
|
||
|
}
|
||
|
//
|
||
|
// Unrecognized commands ...
|
||
|
//
|
||
|
else
|
||
|
client.println( "500 Unknow command");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::dataConnect()
|
||
|
{
|
||
|
unsigned long startTime = millis();
|
||
|
//wait 5 seconds for a data connection
|
||
|
if (!data.connected())
|
||
|
{
|
||
|
while (!dataServer.hasClient() && millis() - startTime < 10000)
|
||
|
{
|
||
|
//delay(100);
|
||
|
yield();
|
||
|
}
|
||
|
if (dataServer.hasClient()) {
|
||
|
data.stop();
|
||
|
data = dataServer.available();
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println("ftpdataserver client....");
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return data.connected();
|
||
|
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::doRetrieve()
|
||
|
{
|
||
|
if (data.connected())
|
||
|
{
|
||
|
int16_t nb = file.readBytes(buf, FTP_BUF_SIZE);
|
||
|
if (nb > 0)
|
||
|
{
|
||
|
data.write((uint8_t*)buf, nb);
|
||
|
bytesTransfered += nb;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
closeTransfer();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::doStore()
|
||
|
{
|
||
|
// Avoid blocking by never reading more bytes than are available
|
||
|
int navail = data.available();
|
||
|
|
||
|
if (navail > 0)
|
||
|
{
|
||
|
// And be sure not to overflow buf.
|
||
|
if (navail > FTP_BUF_SIZE) navail = FTP_BUF_SIZE;
|
||
|
int16_t nb = data.read((uint8_t*) buf, navail );
|
||
|
// int16_t nb = data.readBytes((uint8_t*) buf, FTP_BUF_SIZE );
|
||
|
if( nb > 0 )
|
||
|
{
|
||
|
// Serial.println( millis() << " " << nb << endl;
|
||
|
file.write((uint8_t*) buf, nb );
|
||
|
bytesTransfered += nb;
|
||
|
}
|
||
|
}
|
||
|
if( !data.connected() && (navail <= 0) )
|
||
|
{
|
||
|
closeTransfer();
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FtpServer::closeTransfer()
|
||
|
{
|
||
|
uint32_t deltaT = (int32_t) ( millis() - millisBeginTrans );
|
||
|
if( deltaT > 0 && bytesTransfered > 0 )
|
||
|
{
|
||
|
client.println( "226-File successfully transferred");
|
||
|
client.println( "226 " + String(deltaT) + " ms, "+ String(bytesTransfered / deltaT) + " kbytes/s");
|
||
|
}
|
||
|
else
|
||
|
client.println( "226 File successfully transferred");
|
||
|
|
||
|
file.close();
|
||
|
data.stop();
|
||
|
}
|
||
|
|
||
|
void FtpServer::abortTransfer()
|
||
|
{
|
||
|
if( transferStatus > 0 )
|
||
|
{
|
||
|
file.close();
|
||
|
data.stop();
|
||
|
client.println( "426 Transfer aborted" );
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.println( "Transfer aborted!") ;
|
||
|
#endif
|
||
|
}
|
||
|
transferStatus = 0;
|
||
|
}
|
||
|
|
||
|
// Read a char from client connected to ftp server
|
||
|
//
|
||
|
// update cmdLine and command buffers, iCL and parameters pointers
|
||
|
//
|
||
|
// return:
|
||
|
// -2 if buffer cmdLine is full
|
||
|
// -1 if line not completed
|
||
|
// 0 if empty line received
|
||
|
// length of cmdLine (positive) if no empty line received
|
||
|
|
||
|
int8_t FtpServer::readChar()
|
||
|
{
|
||
|
int8_t rc = -1;
|
||
|
|
||
|
if( client.available())
|
||
|
{
|
||
|
char c = client.read();
|
||
|
// char c;
|
||
|
// client.readBytes((uint8_t*) c, 1);
|
||
|
#ifdef FTP_DEBUG
|
||
|
Serial.print( c);
|
||
|
#endif
|
||
|
if( c == '\\' )
|
||
|
c = '/';
|
||
|
if( c != '\r' )
|
||
|
if( c != '\n' )
|
||
|
{
|
||
|
if( iCL < FTP_CMD_SIZE )
|
||
|
cmdLine[ iCL ++ ] = c;
|
||
|
else
|
||
|
rc = -2; // Line too long
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cmdLine[ iCL ] = 0;
|
||
|
command[ 0 ] = 0;
|
||
|
parameters = NULL;
|
||
|
// empty line?
|
||
|
if( iCL == 0 )
|
||
|
rc = 0;
|
||
|
else
|
||
|
{
|
||
|
rc = iCL;
|
||
|
// search for space between command and parameters
|
||
|
parameters = strchr( cmdLine, ' ' );
|
||
|
if( parameters != NULL )
|
||
|
{
|
||
|
if( parameters - cmdLine > 4 )
|
||
|
rc = -2; // Syntax error
|
||
|
else
|
||
|
{
|
||
|
strncpy( command, cmdLine, parameters - cmdLine );
|
||
|
command[ parameters - cmdLine ] = 0;
|
||
|
|
||
|
while( * ( ++ parameters ) == ' ' )
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
else if( strlen( cmdLine ) > 4 )
|
||
|
rc = -2; // Syntax error.
|
||
|
else
|
||
|
strcpy( command, cmdLine );
|
||
|
iCL = 0;
|
||
|
}
|
||
|
}
|
||
|
if( rc > 0 )
|
||
|
for( uint8_t i = 0 ; i < strlen( command ); i ++ )
|
||
|
command[ i ] = toupper( command[ i ] );
|
||
|
if( rc == -2 )
|
||
|
{
|
||
|
iCL = 0;
|
||
|
client.println( "500 Syntax error");
|
||
|
}
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
// Make complete path/name from cwdName and parameters
|
||
|
//
|
||
|
// 3 possible cases: parameters can be absolute path, relative path or only the name
|
||
|
//
|
||
|
// parameters:
|
||
|
// fullName : where to store the path/name
|
||
|
//
|
||
|
// return:
|
||
|
// true, if done
|
||
|
|
||
|
boolean FtpServer::makePath( char * fullName )
|
||
|
{
|
||
|
return makePath( fullName, parameters );
|
||
|
}
|
||
|
|
||
|
boolean FtpServer::makePath( char * fullName, char * param )
|
||
|
{
|
||
|
if( param == NULL )
|
||
|
param = parameters;
|
||
|
|
||
|
// Root or empty?
|
||
|
if( strcmp( param, "/" ) == 0 || strlen( param ) == 0 )
|
||
|
{
|
||
|
strcpy( fullName, "/" );
|
||
|
return true;
|
||
|
}
|
||
|
// If relative path, concatenate with current dir
|
||
|
if( param[0] != '/' )
|
||
|
{
|
||
|
strcpy( fullName, cwdName );
|
||
|
if( fullName[ strlen( fullName ) - 1 ] != '/' )
|
||
|
strncat( fullName, "/", FTP_CWD_SIZE );
|
||
|
strncat( fullName, param, FTP_CWD_SIZE );
|
||
|
}
|
||
|
else
|
||
|
strcpy( fullName, param );
|
||
|
// If ends with '/', remove it
|
||
|
uint16_t strl = strlen( fullName ) - 1;
|
||
|
if( fullName[ strl ] == '/' && strl > 1 )
|
||
|
fullName[ strl ] = 0;
|
||
|
if( strlen( fullName ) < FTP_CWD_SIZE )
|
||
|
return true;
|
||
|
|
||
|
client.println( "500 Command line too long");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Calculate year, month, day, hour, minute and second
|
||
|
// from first parameter sent by MDTM command (YYYYMMDDHHMMSS)
|
||
|
//
|
||
|
// parameters:
|
||
|
// pyear, pmonth, pday, phour, pminute and psecond: pointer of
|
||
|
// variables where to store data
|
||
|
//
|
||
|
// return:
|
||
|
// 0 if parameter is not YYYYMMDDHHMMSS
|
||
|
// length of parameter + space
|
||
|
|
||
|
uint8_t FtpServer::getDateTime( uint16_t * pyear, uint8_t * pmonth, uint8_t * pday,
|
||
|
uint8_t * phour, uint8_t * pminute, uint8_t * psecond )
|
||
|
{
|
||
|
char dt[ 15 ];
|
||
|
|
||
|
// Date/time are expressed as a 14 digits long string
|
||
|
// terminated by a space and followed by name of file
|
||
|
if( strlen( parameters ) < 15 || parameters[ 14 ] != ' ' )
|
||
|
return 0;
|
||
|
for( uint8_t i = 0; i < 14; i++ )
|
||
|
if( ! isdigit( parameters[ i ]))
|
||
|
return 0;
|
||
|
|
||
|
strncpy( dt, parameters, 14 );
|
||
|
dt[ 14 ] = 0;
|
||
|
* psecond = atoi( dt + 12 );
|
||
|
dt[ 12 ] = 0;
|
||
|
* pminute = atoi( dt + 10 );
|
||
|
dt[ 10 ] = 0;
|
||
|
* phour = atoi( dt + 8 );
|
||
|
dt[ 8 ] = 0;
|
||
|
* pday = atoi( dt + 6 );
|
||
|
dt[ 6 ] = 0 ;
|
||
|
* pmonth = atoi( dt + 4 );
|
||
|
dt[ 4 ] = 0 ;
|
||
|
* pyear = atoi( dt );
|
||
|
return 15;
|
||
|
}
|
||
|
|
||
|
// Create string YYYYMMDDHHMMSS from date and time
|
||
|
//
|
||
|
// parameters:
|
||
|
// date, time
|
||
|
// tstr: where to store the string. Must be at least 15 characters long
|
||
|
//
|
||
|
// return:
|
||
|
// pointer to tstr
|
||
|
|
||
|
char * FtpServer::makeDateTimeStr( char * tstr, uint16_t date, uint16_t time )
|
||
|
{
|
||
|
sprintf( tstr, "%04u%02u%02u%02u%02u%02u",
|
||
|
(( date & 0xFE00 ) >> 9 ) + 1980, ( date & 0x01E0 ) >> 5, date & 0x001F,
|
||
|
( time & 0xF800 ) >> 11, ( time & 0x07E0 ) >> 5, ( time & 0x001F ) << 1 );
|
||
|
return tstr;
|
||
|
}
|
||
|
|