Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.DEPENDABOT_PAT }}

- name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@main
with:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Integration Tests
on:
merge_group:
pull_request:
workflow_dispatch:

jobs:
build:
Expand Down Expand Up @@ -85,7 +86,7 @@ jobs:
- name: Run cppcheck and create html
run: docker run --rm -v ${PWD}:/src facthunder/cppcheck:latest /bin/bash -c "cppcheck --xml $CPPCHECK_ARGS 2> report.xml && cppcheck-htmlreport --file=report.xml --report-dir=output"
- name: Upload report
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Cppcheck Report
path: output
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
# ESP-FTP-Server-Lib

This is a fork form https://github.com/peterus/ESP-FTP-Server-Lib, since there seems to be no maintanance anymore.
This library will provide a simple and modern FTP server for your ESP32 or ESP8266 device.
You can setup multiple users and mutliple filesystems (SD-Card, MMC-Card or/and SPIFFS).

## Examples

In the example folder you can find a very simple usage of the FTP server. You just need to setup the users, add the filesystems which you want to use, and call the handle function in the loop.
With the Compileflag -DENABLE_FTP_SANITIZATION you can enable support for special-characters like ":" or "?" by URL-Encodeing of Files.

## Known Commands to the server

Currently all kind of simple commands are known to the server:
* CDUP
* CLNT
* CWD
* DELE
* FEAT
* LISST
* MKD
* MLSD
* NLST
* OPTS
* PASV
* PORT
* PWD
* RETR
* RMD
* RNFR
* RNTO
* STAT
* STOR
* TYPE
* USER
* PASS
* SYST
* QUIT
* ABOR
* NLST
* STAT

## What is still missing / TODO

Some commands are still missing, if you need them create a ticket :)

Currently just the active mode is supported. For the passive mode you need to wait until version 1.0.0.
4 changes: 2 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

[env:ttgo-lora32]
platform = espressif32 @ 6.3.2
platform = espressif32 @ 7.0.1
board = ttgo-lora32-v1
framework = arduino
test_build_src = yes
Expand All @@ -10,7 +10,7 @@ check_flags =
check_skip_packages = yes

[env:ttgo-ESP8266]
platform = espressif8266 @ 4.0.0
platform = espressif8266 @ 4.2.1
board = esp_wroom_02
framework = arduino
test_build_src = yes
Expand Down
3 changes: 2 additions & 1 deletion src/Commands/CDUP.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"

class CDUP : public FTPCommand {
public:
Expand All @@ -12,7 +13,7 @@ class CDUP : public FTPCommand {

void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
WorkDirectory.goPathUp();
SendResponse(250, "Ok. Current directory is " + WorkDirectory.getPath());
SendResponse(FtpCodes::COMMAND_OK, "Ok. Current directory is " + WorkDirectory.getClearPath());
}
};

Expand Down
5 changes: 3 additions & 2 deletions src/Commands/CWD.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"

class CWD : public FTPCommand {
public:
Expand All @@ -20,9 +21,9 @@ class CWD : public FTPCommand {
File dir = _Filesystem->open(path.getPath());
if (dir.isDirectory()) {
WorkDirectory = path;
SendResponse(250, "Ok. Current directory is " + WorkDirectory.getPath());
SendResponse(FtpCodes::COMMAND_OK, "Ok. Current directory is " + WorkDirectory.getClearPath());
} else {
SendResponse(550, "Directory does not exist");
SendResponse(FtpCodes::FILE_ACTION_NOT_TAKEN, "Directory does not exist");
}
}
};
Expand Down
7 changes: 4 additions & 3 deletions src/Commands/DELE.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"

class DELE : public FTPCommand {
public:
Expand All @@ -13,13 +14,13 @@ class DELE : public FTPCommand {
void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
String filepath = WorkDirectory.getFilePath(Line[1]);
if (!_Filesystem->exists(filepath)) {
SendResponse(550, "File " + filepath + " not found");
SendResponse(FtpCodes::FILE_NOT_FOUND, "File " + filepath + " not found");
return;
}
if (_Filesystem->remove(filepath)) {
SendResponse(250, " Deleted \"" + filepath + "\"");
SendResponse(FtpCodes::COMMAND_OK, " Deleted \"" + filepath + "\"");
} else {
SendResponse(450, "Can't delete \"" + filepath + "\"");
SendResponse(FtpCodes::FILE_ACTION_ABORTED, "Can't delete \"" + filepath + "\"");
}
}
};
Expand Down
31 changes: 25 additions & 6 deletions src/Commands/LIST.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,43 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"
#include "../common.h"

class LIST : public FTPCommand {
public:
explicit LIST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("LIST", 1, Client, Filesystem, DataAddress, DataPort) {
explicit LIST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort, WiFiServer **PassiveServer = 0, bool *PassiveMode = 0) : FTPCommand("LIST", 1, Client, Filesystem, DataAddress, DataPort, PassiveServer, PassiveMode) {
}

void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
FTPPath listPath = WorkDirectory;

// 1. Check if we have arguments
if (Line.size() > 1) {
String args = Line[1];
args.trim(); // Modifies 'args' in place

if (!args.isEmpty()) {
String path = ExtractPathFromOptions(args);
if (!path.isEmpty()) {
listPath.changePath(path);
}
}
}

if (!ConnectDataConnection()) {
return;
}
File dir = _Filesystem->open(WorkDirectory.getPath()); //
File dir = _Filesystem->open(listPath.getPath()); //
if (!dir || !dir.isDirectory()) {
CloseDataConnection();
SendResponse(550, "Can't open directory " + WorkDirectory.getPath());
SendResponse(FtpCodes::FILE_NOT_FOUND, "Can't open directory " + listPath.getClearPath());
return;
}
int cnt = 0;
File f = dir.openNextFile();
int cnt = 2;
data_println("drwxr-xr-x 1 owner group 0 Jan 01 1970 .");
data_println("drwxr-xr-x 1 owner group 0 Jan 01 1970 ..");
File f = dir.openNextFile();
while (f) {
String filename = f.name();
filename.remove(0, filename.lastIndexOf('/') + 1);
Expand All @@ -37,13 +55,14 @@ class LIST : public FTPCommand {
for (int i = 0; i < fill_cnt; i++) {
data_print(" ");
}
filename = listPath.reparse(filename);
data_println(filesize + " Jan 01 1970 " + filename);
cnt++;
f.close();
f = dir.openNextFile();
}
CloseDataConnection();
SendResponse(226, String(cnt) + " matches total");
SendResponse(FtpCodes::TRANSFER_COMPLETE, String(cnt) + " matches total");
}
};

Expand Down
7 changes: 4 additions & 3 deletions src/Commands/MKD.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"

class MKD : public FTPCommand {
public:
Expand All @@ -13,13 +14,13 @@ class MKD : public FTPCommand {
void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
String filepath = WorkDirectory.getFilePath(Line[1]);
if (_Filesystem->exists(filepath)) {
SendResponse(521, "Can't create \"" + filepath + "\", Directory exists");
SendResponse(FtpCodes::FILE_ACTION_NOT_TAKEN, "Can't create \"" + filepath + "\", Directory exists");
return;
}
if (_Filesystem->mkdir(filepath)) {
SendResponse(257, "\"" + filepath + "\" created");
SendResponse(FtpCodes::PATHNAME_CREATED, "\"" + filepath + "\" created");
} else {
SendResponse(550, "Can't create \"" + filepath + "\"");
SendResponse(FtpCodes::FILE_ACTION_NOT_TAKEN, "Can't create \"" + filepath + "\"");
}
}
};
Expand Down
30 changes: 23 additions & 7 deletions src/Commands/MLSD.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,40 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"
#include "../common.h"
#include <time.h>

class MLSD : public FTPCommand {
public:
explicit MLSD(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("MLSD", 1, Client, Filesystem, DataAddress, DataPort) {
explicit MLSD(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort, WiFiServer **PassiveServer = 0, bool *PassiveMode = 0) : FTPCommand("MLSD", 1, Client, Filesystem, DataAddress, DataPort, PassiveServer, PassiveMode) {
}

void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
FTPPath listPath = WorkDirectory;
// 1. Check if we have arguments
if (Line.size() > 1) {
String args = Line[1];
args.trim(); // Modifies 'args' in place

if (!args.isEmpty()) {
String path = ExtractPathFromOptions(args);
if (!path.isEmpty()) {
listPath.changePath(path);
}
}
}

if (!ConnectDataConnection()) {
return;
}

File root = _Filesystem->open(WorkDirectory.getPath(), "r");
File root = _Filesystem->open(listPath.getPath(), "r");

if (!root || !root.isDirectory()) {
root.close();
CloseDataConnection();
SendResponse(550, "Can't open directory " + WorkDirectory.getPath());
SendResponse(FtpCodes::FILE_NOT_FOUND, "Can't open directory " + listPath.getClearPath());
return;
}

Expand All @@ -46,9 +61,9 @@ class MLSD : public FTPCommand {
data_print(";");

// modify=YYYYMMDDHHMMSS; // GMT (!!!!)
char buf[128];
time_t ft = f.getLastWrite();
struct tm *t = localtime(&ft);
char buf[128];
time_t ft = f.getLastWrite();
const struct tm *t = localtime(&ft);
sprintf(buf, "modify=%4d%02d%02d%02d%02d%02d;", (t->tm_year) + 1900, (t->tm_mon) + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
data_print(String(buf));

Expand All @@ -59,6 +74,7 @@ class MLSD : public FTPCommand {
data_print(" ");
String filename = f.name();
filename.remove(0, filename.lastIndexOf('/') + 1);
filename = listPath.reparse(filename);
data_println(filename);

cnt++;
Expand All @@ -67,7 +83,7 @@ class MLSD : public FTPCommand {

root.close();
CloseDataConnection();
SendResponse(226, String(cnt) + " matches total");
SendResponse(FtpCodes::TRANSFER_COMPLETE, String(cnt) + " matches total");
}
};

Expand Down
14 changes: 9 additions & 5 deletions src/Commands/NLST.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
#include <WiFiClient.h>

#include "../FTPCommand.h"
#include "../FTPResponseCodes.h"
#include "../common.h"

class NLST : public FTPCommand {
public:
explicit NLST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("NLST", 1, Client, Filesystem, DataAddress, DataPort) {
explicit NLST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort, WiFiServer **PassiveServer = 0, bool *PassiveMode = 0) : FTPCommand("NLST", 1, Client, Filesystem, DataAddress, DataPort, PassiveServer, PassiveMode) {
}

void run(FTPPath &WorkDirectory, const std::vector<String> &Line) override {
Expand All @@ -18,20 +19,23 @@ class NLST : public FTPCommand {
File dir = _Filesystem->open(WorkDirectory.getPath()); //
if (!dir || !dir.isDirectory()) {
CloseDataConnection();
SendResponse(550, "Can't open directory " + WorkDirectory.getPath());
SendResponse(FtpCodes::FILE_NOT_FOUND, "Can't open directory " + WorkDirectory.getClearPath());
return;
}
int cnt = 0;
File f = dir.openNextFile();
int cnt = 2;
data_println(".");
data_println("..");
File f = dir.openNextFile();
while (f) {
String filename = f.name();
filename.remove(0, filename.lastIndexOf('/') + 1);
data_println(WorkDirectory.reparse(filename));
cnt++;
f.close();
f = dir.openNextFile();
}
CloseDataConnection();
SendResponse(226, String(cnt) + " matches total");
SendResponse(FtpCodes::TRANSFER_COMPLETE, String(cnt) + " matches total");
}
};

Expand Down
Loading
Loading