├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ ├── dependabot.yml │ └── main.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── examples └── test.cpp ├── library.properties ├── platformio.ini ├── src ├── Commands │ ├── CDUP.h │ ├── CWD.h │ ├── DELE.h │ ├── LIST.h │ ├── MKD.h │ ├── MLSD.h │ ├── NLST.h │ ├── PORT.h │ ├── PWD.h │ ├── RETR.h │ ├── RMD.h │ ├── RNFR_RNTO.h │ ├── STAT.h │ ├── STOR.h │ └── TYPE.h ├── ESP-FTP-Server-Lib.cpp ├── ESP-FTP-Server-Lib.h ├── FTPCommand.h ├── FTPConnection.cpp ├── FTPConnection.h ├── FTPFilesystem.cpp ├── FTPFilesystem.h ├── FTPPath.cpp ├── FTPPath.h ├── FTPUser.h ├── common.cpp └── common.h └── test ├── FTPFilesystem └── test.cpp └── FTPPath └── test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: true 7 | AlignConsecutiveAssignments: true 8 | AlignConsecutiveBitFields: true 9 | AlignConsecutiveDeclarations: true 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: false 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: None 20 | AllowShortLambdasOnASingleLine: None 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: MultiLine 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: false 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Attach 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: false 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 500 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Preserve 74 | IncludeCategories: 75 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 76 | Priority: 2 77 | SortPriority: 0 78 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 79 | Priority: 3 80 | SortPriority: 0 81 | - Regex: '.*' 82 | Priority: 1 83 | SortPriority: 0 84 | IncludeIsMainRegex: '(Test)?$' 85 | IncludeIsMainSourceRegex: '' 86 | IndentCaseLabels: false 87 | IndentCaseBlocks: false 88 | IndentGotoLabels: true 89 | IndentPPDirectives: None 90 | IndentExternBlock: AfterExternBlock 91 | IndentWidth: 2 92 | IndentWrappedFunctionNames: false 93 | InsertTrailingCommas: None 94 | JavaScriptQuotes: Leave 95 | JavaScriptWrapImports: true 96 | KeepEmptyLinesAtTheStartOfBlocks: true 97 | MacroBlockBegin: '' 98 | MacroBlockEnd: '' 99 | MaxEmptyLinesToKeep: 1 100 | NamespaceIndentation: None 101 | ObjCBinPackProtocolList: Auto 102 | ObjCBlockIndentWidth: 2 103 | ObjCBreakBeforeNestedBlockParam: true 104 | ObjCSpaceAfterProperty: false 105 | ObjCSpaceBeforeProtocolList: true 106 | PenaltyBreakAssignment: 2 107 | PenaltyBreakBeforeFirstCallParameter: 19 108 | PenaltyBreakComment: 300 109 | PenaltyBreakFirstLessLess: 120 110 | PenaltyBreakString: 1000 111 | PenaltyBreakTemplateDeclaration: 10 112 | PenaltyExcessCharacter: 1000000 113 | PenaltyReturnTypeOnItsOwnLine: 60 114 | PointerAlignment: Right 115 | ReflowComments: true 116 | SortIncludes: true 117 | SortUsingDeclarations: true 118 | SpaceAfterCStyleCast: false 119 | SpaceAfterLogicalNot: false 120 | SpaceAfterTemplateKeyword: true 121 | SpaceBeforeAssignmentOperators: true 122 | SpaceBeforeCpp11BracedList: false 123 | SpaceBeforeCtorInitializerColon: true 124 | SpaceBeforeInheritanceColon: true 125 | SpaceBeforeParens: ControlStatements 126 | SpaceBeforeRangeBasedForLoopColon: true 127 | SpaceInEmptyBlock: false 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 1 130 | SpacesInAngles: false 131 | SpacesInConditionalStatement: false 132 | SpacesInContainerLiterals: true 133 | SpacesInCStyleCastParentheses: false 134 | SpacesInParentheses: false 135 | SpacesInSquareBrackets: false 136 | SpaceBeforeSquareBrackets: false 137 | Standard: Latest 138 | StatementMacros: 139 | - Q_UNUSED 140 | - QT_REQUIRE_VERSION 141 | TabWidth: 8 142 | UseCRLF: false 143 | UseTab: Never 144 | WhitespaceSensitiveMacros: 145 | - STRINGIZE 146 | - PP_STRINGIZE 147 | - BOOST_PP_STRINGIZE 148 | ... 149 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ['peterus'] 2 | custom: ['paypal.me/peterus07'] 3 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO Dependabot 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # Runs every day at 00:00 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | dependabot: 11 | runs-on: ubuntu-latest 12 | name: run PlatformIO Dependabot 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - name: run PlatformIO Dependabot 17 | uses: peterus/platformio_dependabot@main 18 | with: 19 | github_token: ${{ secrets.DEPENDABOT_PAT }} 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | name: Compile Firmware 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/cache@v3 13 | with: 14 | path: | 15 | ~/.cache/pip 16 | ~/.platformio/.cache 17 | key: compile-cache 18 | - uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.10' 21 | - name: Install PlatformIO 22 | shell: bash 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install --upgrade platformio 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v3 29 | - run: ln -s ../examples/test.cpp src/test.cpp 30 | - name: Build PlatformIO Project 31 | run: pio run 32 | 33 | formatting-check: 34 | name: Formatting Check 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | path: 39 | - 'src' 40 | - 'src/Commands' 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v3 44 | - name: Run clang-format style check for C/C++ programs. 45 | uses: jidicula/clang-format-action@v4.10.2 46 | with: 47 | clang-format-version: '14' 48 | check-path: ${{ matrix.path }} 49 | 50 | cppcheck: 51 | name: Run cppcheck 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/cache@v3 55 | with: 56 | path: | 57 | ~/.cache/pip 58 | ~/.platformio/.cache 59 | key: check-cache 60 | - uses: actions/setup-python@v4 61 | with: 62 | python-version: '3.10' 63 | - name: Install PlatformIO 64 | shell: bash 65 | run: | 66 | python -m pip install --upgrade pip 67 | pip install --upgrade platformio 68 | 69 | - name: Checkout code 70 | uses: actions/checkout@v3 71 | - name: Run PlatformIO Check 72 | run: pio check --fail-on-defect high --fail-on-defect medium --fail-on-defect low 73 | 74 | cppcheck-docker: 75 | name: Run cppcheck in Docker 76 | runs-on: ubuntu-latest 77 | env: 78 | CPPCHECK_ARGS: --enable=all --std=c++20 --inline-suppr src example 79 | steps: 80 | - name: checkout code 81 | uses: actions/checkout@v3 82 | - run: docker pull facthunder/cppcheck:latest 83 | - name: Run cppcheck and print result 84 | run: docker run --rm -v ${PWD}:/src facthunder/cppcheck:latest /bin/bash -c "cppcheck $CPPCHECK_ARGS" 85 | - name: Run cppcheck and create html 86 | 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" 87 | - name: Upload report 88 | uses: actions/upload-artifact@v3 89 | with: 90 | name: Cppcheck Report 91 | path: output 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide", 6 | "xaver.clang-format" 7 | ], 8 | "unwantedRecommendations": [ 9 | "ms-vscode.cpptools-extension-pack" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.insertFinalNewline": true, 3 | "editor.formatOnSave": true, 4 | "files.associations": { 5 | "*.h": "cpp", 6 | "array": "cpp", 7 | "*.tcc": "cpp", 8 | "cctype": "cpp", 9 | "clocale": "cpp", 10 | "cmath": "cpp", 11 | "cstdarg": "cpp", 12 | "cstddef": "cpp", 13 | "cstdint": "cpp", 14 | "cstdio": "cpp", 15 | "cstdlib": "cpp", 16 | "cstring": "cpp", 17 | "ctime": "cpp", 18 | "cwchar": "cpp", 19 | "cwctype": "cpp", 20 | "deque": "cpp", 21 | "list": "cpp", 22 | "unordered_map": "cpp", 23 | "unordered_set": "cpp", 24 | "vector": "cpp", 25 | "exception": "cpp", 26 | "algorithm": "cpp", 27 | "functional": "cpp", 28 | "system_error": "cpp", 29 | "tuple": "cpp", 30 | "type_traits": "cpp", 31 | "fstream": "cpp", 32 | "initializer_list": "cpp", 33 | "iomanip": "cpp", 34 | "iosfwd": "cpp", 35 | "istream": "cpp", 36 | "limits": "cpp", 37 | "memory": "cpp", 38 | "new": "cpp", 39 | "ostream": "cpp", 40 | "numeric": "cpp", 41 | "sstream": "cpp", 42 | "stdexcept": "cpp", 43 | "streambuf": "cpp", 44 | "cinttypes": "cpp", 45 | "utility": "cpp", 46 | "typeinfo": "cpp" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Buchegger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP-FTP-Server-Lib 2 | 3 | This library will provide a simple and modern FTP server for your ESP32 or ESP8266 device. 4 | You can setup multiple users and mutliple filesystems (SD-Card, MMC-Card or/and SPIFFS). 5 | 6 | ## Examples 7 | 8 | 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. 9 | 10 | ## Known Commands to the server 11 | 12 | Currently all kind of simple commands are known to the server: 13 | * CDUP 14 | * CWD 15 | * DELE 16 | * LISST 17 | * MKD 18 | * PORT 19 | * PWD 20 | * RETR 21 | * RMD 22 | * RNFR 23 | * RNTO 24 | * STOR 25 | * TYPE 26 | * SYST 27 | * QUIT 28 | * ABOR 29 | * NLST 30 | * STAT 31 | 32 | ## What is still missing / TODO 33 | 34 | Some commands are still missing, if you need them create a ticket :) 35 | 36 | Currently just the active mode is supported. For the passive mode you need to wait until version 1.0.0. 37 | -------------------------------------------------------------------------------- /examples/test.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #include 5 | #elif defined(ESP8266) 6 | #include 7 | #include 8 | #endif 9 | 10 | #include "ESP-FTP-Server-Lib.h" 11 | #include "FTPFilesystem.h" 12 | 13 | #define WIFI_SSID "SSID" 14 | #define WIFI_PASSWORD "PASSWORD" 15 | 16 | #define FTP_USER "ftp" 17 | #define FTP_PASSWORD "ftp" 18 | 19 | #ifndef UNIT_TEST 20 | FTPServer ftp; 21 | 22 | void setup() { 23 | Serial.begin(115200); 24 | #if defined(ESP32) 25 | SPIFFS.begin(true); 26 | SPI.begin(14, 2, 15); 27 | if (!SD.begin(13)) { 28 | Serial.println("SD Card Mount Failed"); 29 | } 30 | #elif defined(ESP8266) 31 | SPIFFS.begin(); 32 | #endif 33 | 34 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 35 | while (WiFi.status() != WL_CONNECTED) { 36 | delay(500); 37 | Serial.print("."); 38 | } 39 | Serial.println(""); 40 | Serial.print("Connected to "); 41 | Serial.println(WIFI_SSID); 42 | Serial.print("IP address: "); 43 | Serial.println(WiFi.localIP()); 44 | 45 | ftp.addUser(FTP_USER, FTP_PASSWORD); 46 | #if defined(ESP32) 47 | ftp.addFilesystem("SD", &SD); 48 | #endif 49 | ftp.addFilesystem("SPIFFS", &SPIFFS); 50 | 51 | ftp.begin(); 52 | 53 | Serial.println("...---'''---...---'''---...---'''---..."); 54 | } 55 | 56 | void loop() { 57 | ftp.handle(); 58 | } 59 | #endif 60 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP-FTP-Server-Lib 2 | version=0.14.1 3 | author=Peter Buchegger 4 | maintainer=Peter Buchegger 5 | sentence=Simple and modern FTP server for ESP devices. 6 | paragraph=With this library you can run a simple and modern FTP server on your ESP32 or ESP8266. You can mount multiple filesystems like SD-Card, MMC-Card or SPIFFS at the same time. 7 | category=Communication 8 | url=https://github.com/peterus/ESP-FTP-Server-Lib 9 | architectures=esp8266,esp32,arduino-esp32 10 | includes=ESP-FTP-Server-Lib.h 11 | depends= 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | 2 | [env:ttgo-lora32] 3 | platform = espressif32 @ 6.3.2 4 | board = ttgo-lora32-v1 5 | framework = arduino 6 | test_build_src = yes 7 | monitor_speed = 115200 8 | check_flags = 9 | cppcheck: --suppress=*:*.pio\* --suppress=unusedFunction 10 | check_skip_packages = yes 11 | 12 | [env:ttgo-ESP8266] 13 | platform = espressif8266 @ 4.0.0 14 | board = esp_wroom_02 15 | framework = arduino 16 | test_build_src = yes 17 | monitor_speed = 115200 18 | check_flags = 19 | cppcheck: --suppress=*:*.pio\* --suppress=unusedFunction 20 | check_skip_packages = yes 21 | -------------------------------------------------------------------------------- /src/Commands/CDUP.h: -------------------------------------------------------------------------------- 1 | #ifndef CDUP_H_ 2 | #define CDUP_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class CDUP : public FTPCommand { 9 | public: 10 | explicit CDUP(WiFiClient *const Client) : FTPCommand("CDUP", 0, Client) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | WorkDirectory.goPathUp(); 15 | SendResponse(250, "Ok. Current directory is " + WorkDirectory.getPath()); 16 | } 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/Commands/CWD.h: -------------------------------------------------------------------------------- 1 | #ifndef CWD_H_ 2 | #define CWD_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class CWD : public FTPCommand { 9 | public: 10 | explicit CWD(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("CWD", 1, Client, Filesystem) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | FTPPath path = WorkDirectory; 15 | if (Line[1] == "..") { 16 | path.goPathUp(); 17 | } else { 18 | path.changePath(Line[1]); 19 | } 20 | File dir = _Filesystem->open(path.getPath()); 21 | if (dir.isDirectory()) { 22 | WorkDirectory = path; 23 | SendResponse(250, "Ok. Current directory is " + WorkDirectory.getPath()); 24 | } else { 25 | SendResponse(550, "Directory does not exist"); 26 | } 27 | } 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/Commands/DELE.h: -------------------------------------------------------------------------------- 1 | #ifndef DELE_H_ 2 | #define DELE_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class DELE : public FTPCommand { 9 | public: 10 | explicit DELE(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("DELE", 1, Client, Filesystem) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | String filepath = WorkDirectory.getFilePath(Line[1]); 15 | if (!_Filesystem->exists(filepath)) { 16 | SendResponse(550, "File " + filepath + " not found"); 17 | return; 18 | } 19 | if (_Filesystem->remove(filepath)) { 20 | SendResponse(250, " Deleted \"" + filepath + "\""); 21 | } else { 22 | SendResponse(450, "Can't delete \"" + filepath + "\""); 23 | } 24 | } 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/Commands/LIST.h: -------------------------------------------------------------------------------- 1 | #ifndef LIST_H_ 2 | #define LIST_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | class LIST : public FTPCommand { 10 | public: 11 | explicit LIST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("LIST", 1, Client, Filesystem, DataAddress, DataPort) { 12 | } 13 | 14 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 15 | if (!ConnectDataConnection()) { 16 | return; 17 | } 18 | File dir = _Filesystem->open(WorkDirectory.getPath()); // 19 | if (!dir || !dir.isDirectory()) { 20 | CloseDataConnection(); 21 | SendResponse(550, "Can't open directory " + WorkDirectory.getPath()); 22 | return; 23 | } 24 | int cnt = 0; 25 | File f = dir.openNextFile(); 26 | while (f) { 27 | String filename = f.name(); 28 | filename.remove(0, filename.lastIndexOf('/') + 1); 29 | if (f.isDirectory()) { 30 | data_print("drwxr-xr-x"); 31 | } else { 32 | data_print("-rw-r--r--"); 33 | } 34 | String filesize = String(f.size()); 35 | data_print(" 1 owner group "); 36 | int fill_cnt = 13 - filesize.length(); 37 | for (int i = 0; i < fill_cnt; i++) { 38 | data_print(" "); 39 | } 40 | data_println(filesize + " Jan 01 1970 " + filename); 41 | cnt++; 42 | f.close(); 43 | f = dir.openNextFile(); 44 | } 45 | CloseDataConnection(); 46 | SendResponse(226, String(cnt) + " matches total"); 47 | } 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/Commands/MKD.h: -------------------------------------------------------------------------------- 1 | #ifndef MKD_H_ 2 | #define MKD_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class MKD : public FTPCommand { 9 | public: 10 | explicit MKD(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("MKD", 1, Client, Filesystem) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | String filepath = WorkDirectory.getFilePath(Line[1]); 15 | if (_Filesystem->exists(filepath)) { 16 | SendResponse(521, "Can't create \"" + filepath + "\", Directory exists"); 17 | return; 18 | } 19 | if (_Filesystem->mkdir(filepath)) { 20 | SendResponse(257, "\"" + filepath + "\" created"); 21 | } else { 22 | SendResponse(550, "Can't create \"" + filepath + "\""); 23 | } 24 | } 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/Commands/MLSD.h: -------------------------------------------------------------------------------- 1 | #ifndef MSLD_H_ 2 | #define MSLD_H_ 3 | // Copyright (c) 2020 Arkady Korotkevich 4 | // Based on LIST.h by Peter Buchegger 5 | #include 6 | 7 | #include "../FTPCommand.h" 8 | #include "../common.h" 9 | #include 10 | 11 | class MLSD : public FTPCommand { 12 | public: 13 | explicit MLSD(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("MLSD", 1, Client, Filesystem, DataAddress, DataPort) { 14 | } 15 | 16 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 17 | if (!ConnectDataConnection()) { 18 | return; 19 | } 20 | 21 | File root = _Filesystem->open(WorkDirectory.getPath(), "r"); 22 | 23 | if (!root || !root.isDirectory()) { 24 | root.close(); 25 | CloseDataConnection(); 26 | SendResponse(550, "Can't open directory " + WorkDirectory.getPath()); 27 | return; 28 | } 29 | 30 | int cnt = 0; 31 | File f = root.openNextFile(); 32 | while (f) { 33 | // type= 34 | data_print("type="); 35 | if (f.isDirectory()) 36 | data_print("dir;"); 37 | else 38 | data_print("file;"); 39 | 40 | // size= 41 | if (f.isDirectory()) 42 | data_print("sizd="); 43 | else 44 | data_print("size="); 45 | data_print(String(f.size())); 46 | data_print(";"); 47 | 48 | // modify=YYYYMMDDHHMMSS; // GMT (!!!!) 49 | char buf[128]; 50 | time_t ft = f.getLastWrite(); 51 | struct tm *t = localtime(&ft); 52 | 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); 53 | data_print(String(buf)); 54 | 55 | // UNIX.mode= 56 | data_print("UNIX.mode=0700;"); 57 | 58 | // 59 | data_print(" "); 60 | String filename = f.name(); 61 | filename.remove(0, filename.lastIndexOf('/') + 1); 62 | data_println(filename); 63 | 64 | cnt++; 65 | f = root.openNextFile(); 66 | } 67 | 68 | root.close(); 69 | CloseDataConnection(); 70 | SendResponse(226, String(cnt) + " matches total"); 71 | } 72 | }; 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/Commands/NLST.h: -------------------------------------------------------------------------------- 1 | #ifndef NLST_H_ 2 | #define NLST_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | class NLST : public FTPCommand { 10 | public: 11 | explicit NLST(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommand("NLST", 1, Client, Filesystem, DataAddress, DataPort) { 12 | } 13 | 14 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 15 | if (!ConnectDataConnection()) { 16 | return; 17 | } 18 | File dir = _Filesystem->open(WorkDirectory.getPath()); // 19 | if (!dir || !dir.isDirectory()) { 20 | CloseDataConnection(); 21 | SendResponse(550, "Can't open directory " + WorkDirectory.getPath()); 22 | return; 23 | } 24 | int cnt = 0; 25 | File f = dir.openNextFile(); 26 | while (f) { 27 | String filename = f.name(); 28 | filename.remove(0, filename.lastIndexOf('/') + 1); 29 | cnt++; 30 | f.close(); 31 | f = dir.openNextFile(); 32 | } 33 | CloseDataConnection(); 34 | SendResponse(226, String(cnt) + " matches total"); 35 | } 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/Commands/PORT.h: -------------------------------------------------------------------------------- 1 | #ifndef PORT_H_ 2 | #define PORT_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | class PORT : public FTPCommand { 10 | public: 11 | explicit PORT(WiFiClient *const Client, IPAddress *DataAddress, int *DataPort) : FTPCommand("PORT", 1, Client, 0, DataAddress, DataPort) { 12 | } 13 | 14 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 15 | std::vector connection_details = Split>(Line[1], ','); 16 | for (int i = 0; i < 4; i++) { 17 | (*_DataAddress)[i] = connection_details[i].toInt(); 18 | } 19 | *_DataPort = connection_details[4].toInt() * 256 + connection_details[5].toInt(); 20 | SendResponse(200, "PORT command successful"); 21 | } 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/Commands/PWD.h: -------------------------------------------------------------------------------- 1 | #ifndef PWD_H_ 2 | #define PWD_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class PWD : public FTPCommand { 9 | public: 10 | explicit PWD(WiFiClient *const Client) : FTPCommand("PWD", 0, Client) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | SendResponse(257, "\"" + WorkDirectory.getPath() + "\" is your current directory"); 15 | } 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/Commands/RETR.h: -------------------------------------------------------------------------------- 1 | #ifndef RETR_H_ 2 | #define RETR_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | #define FTP_BUF_SIZE 4096 10 | 11 | class RETR : public FTPCommandTransfer { 12 | public: 13 | explicit RETR(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommandTransfer("RETR", 1, Client, Filesystem, DataAddress, DataPort) { 14 | } 15 | 16 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 17 | if (trasferInProgress()) { 18 | return; 19 | } 20 | if (!ConnectDataConnection()) { 21 | return; 22 | } 23 | String path = WorkDirectory.getFilePath(Line[1]); 24 | _file = _Filesystem->open(path); 25 | if (!_file || _file.isDirectory()) { 26 | CloseDataConnection(); 27 | SendResponse(550, "Can't open " + path); 28 | return; 29 | } 30 | workOnData(); 31 | } 32 | 33 | void workOnData() override { 34 | uint8_t buffer[FTP_BUF_SIZE]; 35 | size_t nb = _file.read(buffer, FTP_BUF_SIZE); 36 | if (nb > 0) { 37 | data_send(buffer, nb); 38 | return; 39 | } 40 | CloseDataConnection(); 41 | SendResponse(226, "File successfully transferred"); 42 | _file.close(); 43 | } 44 | 45 | private: 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/Commands/RMD.h: -------------------------------------------------------------------------------- 1 | #ifndef RMD_H_ 2 | #define RMD_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class RMD : public FTPCommand { 9 | public: 10 | explicit RMD(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("RMD", 1, Client, Filesystem) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | String filepath = WorkDirectory.getFilePath(Line[1]); 15 | if (!_Filesystem->exists(filepath)) { 16 | SendResponse(550, "Folder " + filepath + " not found"); 17 | return; 18 | } 19 | if (_Filesystem->rmdir(filepath)) { 20 | SendResponse(250, " Deleted \"" + filepath + "\""); 21 | } else { 22 | SendResponse(450, "Can't delete \"" + filepath + "\""); 23 | } 24 | } 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/Commands/RNFR_RNTO.h: -------------------------------------------------------------------------------- 1 | #ifndef RNFR_H_ 2 | #define RNFR_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class RNFR_RNTO : public FTPCommand { 9 | public: 10 | explicit RNFR_RNTO(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("RN", 1, Client, Filesystem), _fromSet(false) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | if (Line[0] == "RNFR") { 15 | from(WorkDirectory, Line); 16 | } else if (Line[0] == "RNTO") { 17 | to(WorkDirectory, Line); 18 | } 19 | } 20 | 21 | void from(const FTPPath &WorkDirectory, const std::vector &Line) { 22 | String filepath = WorkDirectory.getFilePath(Line[1]); 23 | if (!_Filesystem->exists(filepath)) { 24 | SendResponse(550, "File " + Line[1] + " not found"); 25 | return; 26 | } 27 | _fromSet = true; 28 | _from = filepath; 29 | SendResponse(350, "RNFR accepted - file exists, ready for destination"); 30 | } 31 | 32 | void to(const FTPPath &WorkDirectory, const std::vector &Line) { 33 | if (!_fromSet) { 34 | SendResponse(503, "Need RNFR before RNTO"); 35 | return; 36 | } 37 | String filepath = WorkDirectory.getFilePath(Line[1]); 38 | if (_Filesystem->exists(filepath)) { 39 | SendResponse(553, "File " + Line[1] + " already exists"); 40 | return; 41 | } 42 | if (_Filesystem->rename(_from, filepath)) { 43 | SendResponse(250, "File successfully renamed or moved"); 44 | } else { 45 | SendResponse(451, "Rename/move failure"); 46 | } 47 | _fromSet = false; 48 | _from = ""; 49 | } 50 | 51 | private: 52 | bool _fromSet; 53 | String _from; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/Commands/STAT.h: -------------------------------------------------------------------------------- 1 | #ifndef STAT_H_ 2 | #define STAT_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | class STAT : public FTPCommand { 10 | public: 11 | explicit STAT(WiFiClient *const Client, FTPFilesystem *const Filesystem) : FTPCommand("STAT", 1, Client, Filesystem) { 12 | } 13 | 14 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 15 | File dir = _Filesystem->open(WorkDirectory.getPath()); // 16 | if (!dir || !dir.isDirectory()) { 17 | SendResponse(550, "Can't open directory " + WorkDirectory.getPath()); 18 | return; 19 | } 20 | int cnt = 0; 21 | File f = dir.openNextFile(); 22 | while (f) { 23 | String filename = f.name(); 24 | filename.remove(0, filename.lastIndexOf('/') + 1); 25 | if (f.isDirectory()) { 26 | data_print("drwxr-xr-x"); 27 | } else { 28 | data_print("-rw-r--r--"); 29 | } 30 | String filesize = String(f.size()); 31 | data_print(" 1 owner group "); 32 | int fill_cnt = 13 - filesize.length(); 33 | for (int i = 0; i < fill_cnt; i++) { 34 | data_print(" "); 35 | } 36 | data_println(filesize + " Jan 01 1970 " + filename); 37 | cnt++; 38 | f.close(); 39 | f = dir.openNextFile(); 40 | } 41 | SendResponse(226, String(cnt) + " matches total"); 42 | } 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/Commands/STOR.h: -------------------------------------------------------------------------------- 1 | #ifndef STOR_H_ 2 | #define STOR_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | #include "../common.h" 8 | 9 | #define FTP_BUF_SIZE 4096 10 | 11 | class STOR : public FTPCommandTransfer { 12 | public: 13 | explicit STOR(WiFiClient *const Client, FTPFilesystem *const Filesystem, IPAddress *DataAddress, int *DataPort) : FTPCommandTransfer("STOR", 1, Client, Filesystem, DataAddress, DataPort) { 14 | } 15 | 16 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 17 | if (trasferInProgress()) { 18 | return; 19 | } 20 | if (!ConnectDataConnection()) { 21 | return; 22 | } 23 | _ftpFsFilePath = WorkDirectory.getFilePath(Line[1]); 24 | _file = _Filesystem->open(_ftpFsFilePath, "w"); 25 | if (!_file) { 26 | SendResponse(451, "Can't open/create " + _ftpFsFilePath); 27 | CloseDataConnection(); 28 | return; 29 | } 30 | workOnData(); 31 | } 32 | 33 | void workOnData() override { 34 | uint8_t buffer[FTP_BUF_SIZE]; 35 | int nb = data_read(buffer, FTP_BUF_SIZE); 36 | if (nb > 0) { 37 | const auto wb = _file.write(buffer, nb); 38 | if (wb != nb) { 39 | _file.close(); 40 | this->_Filesystem->remove(_ftpFsFilePath.c_str()); 41 | 42 | SendResponse(552, "Error occured while STORing"); 43 | CloseDataConnection(); 44 | } 45 | 46 | return; 47 | } 48 | 49 | SendResponse(226, "File successfully transferred"); 50 | CloseDataConnection(); 51 | _file.close(); 52 | } 53 | 54 | private: 55 | String _ftpFsFilePath; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/Commands/TYPE.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPE_H_ 2 | #define TYPE_H_ 3 | 4 | #include 5 | 6 | #include "../FTPCommand.h" 7 | 8 | class TYPE : public FTPCommand { 9 | public: 10 | explicit TYPE(WiFiClient *const Client) : FTPCommand("TYPE", 1, Client) { 11 | } 12 | 13 | void run(FTPPath &WorkDirectory, const std::vector &Line) override { 14 | if (Line[1] == "A") { 15 | SendResponse(200, "TYPE is now ASCII"); 16 | return; 17 | } else if (Line[1] == "I") { 18 | SendResponse(200, "TYPE is now 8-bit binary"); 19 | return; 20 | } 21 | SendResponse(504, "Unknow TYPE"); 22 | } 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/ESP-FTP-Server-Lib.cpp: -------------------------------------------------------------------------------- 1 | #include "ESP-FTP-Server-Lib.h" 2 | 3 | FTPServer::FTPServer() 4 | : 5 | #if defined(ESP32) 6 | _Server(FTP_CTRL_PORT, 1) 7 | #elif defined(ESP8266) 8 | _Server(FTP_CTRL_PORT) 9 | #endif 10 | { 11 | } 12 | 13 | FTPServer::~FTPServer() { 14 | } 15 | 16 | void FTPServer::addUser(const String &Username, const String &Password) { 17 | FTPUser user(Username, Password); 18 | _UserList.push_back(user); 19 | } 20 | 21 | void FTPServer::addUser(const FTPUser &User) { 22 | _UserList.push_back(User); 23 | } 24 | 25 | void FTPServer::addFilesystem(String Name, FS *const Filesystem) { 26 | _Filesystem.addFilesystem(Name, Filesystem); 27 | } 28 | 29 | bool FTPServer::begin() { 30 | _Server.begin(); 31 | return true; 32 | } 33 | 34 | bool isNotConnected(const std::shared_ptr &con) { 35 | return !con->connected(); 36 | } 37 | 38 | void FTPServer::handle() { 39 | if (_Server.hasClient()) { 40 | std::shared_ptr connection = std::shared_ptr(new FTPConnection(_Server.available(), _UserList, _Filesystem)); 41 | _Connections.push_back(connection); 42 | } 43 | for (std::shared_ptr con : _Connections) { 44 | con->handle(); 45 | } 46 | _Connections.remove_if(isNotConnected); 47 | } 48 | 49 | size_t FTPServer::countConnections() const { 50 | return _Connections.size(); 51 | } 52 | -------------------------------------------------------------------------------- /src/ESP-FTP-Server-Lib.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP_FTP_LIB_H_ 2 | #define ESP_FTP_LIB_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "FTPConnection.h" 8 | #include "FTPFilesystem.h" 9 | #include "FTPUser.h" 10 | 11 | class FTPServer { 12 | public: 13 | FTPServer(); 14 | virtual ~FTPServer(); 15 | 16 | void addUser(const String &Username, const String &Password); 17 | void addUser(const FTPUser &User); 18 | 19 | void addFilesystem(String Name, FS *const Filesystem); 20 | 21 | bool begin(); 22 | void handle(); 23 | 24 | size_t countConnections() const; 25 | 26 | private: 27 | WiFiServer _Server; 28 | 29 | std::list _UserList; 30 | std::list> _Connections; 31 | 32 | FTPFilesystem _Filesystem; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/FTPCommand.h: -------------------------------------------------------------------------------- 1 | #ifndef FTP_COMMAND_H_ 2 | #define FTP_COMMAND_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "FTPFilesystem.h" 9 | #include "FTPPath.h" 10 | 11 | class FTPCommand { 12 | public: 13 | FTPCommand(String Name, int ExpectedArgumentCnt, WiFiClient *const Client, FTPFilesystem *const Filesystem = 0, IPAddress *DataAddress = 0, int *DataPort = 0) : _Name(Name), _ExpectedArgumentCnt(ExpectedArgumentCnt), _Filesystem(Filesystem), _DataAddress(DataAddress), _DataPort(DataPort), _Client(Client), _DataConnection(0) { 14 | } 15 | virtual ~FTPCommand() { 16 | } 17 | 18 | String getName() const { 19 | return _Name; 20 | } 21 | 22 | virtual void run(FTPPath &WorkDirectory, const std::vector &Line) = 0; 23 | 24 | void SendResponse(int Code, String Text) { 25 | _Client->print(Code); 26 | _Client->print(" "); 27 | _Client->println(Text); 28 | } 29 | 30 | bool ConnectDataConnection() { 31 | if (_DataConnection == 0) { 32 | _DataConnection = new WiFiClient(); 33 | } 34 | if (_DataConnection->connected()) { 35 | _DataConnection->stop(); 36 | } 37 | _DataConnection->connect(*_DataAddress, *_DataPort); 38 | if (!_DataConnection->connected()) { 39 | _DataConnection->stop(); 40 | SendResponse(425, "No data connection"); 41 | return false; 42 | } 43 | SendResponse(150, "Accepted data connection"); 44 | return true; 45 | } 46 | 47 | void data_print(String str) { 48 | if (_DataConnection == 0 || !_DataConnection->connected()) { 49 | return; 50 | } 51 | _DataConnection->print(str); 52 | } 53 | 54 | void data_println(String str) { 55 | if (_DataConnection == 0 || !_DataConnection->connected()) { 56 | return; 57 | } 58 | _DataConnection->println(str); 59 | } 60 | 61 | void data_send(uint8_t *c, size_t l) { 62 | if (_DataConnection == 0 || !_DataConnection->connected()) { 63 | return; 64 | } 65 | _DataConnection->write(c, l); 66 | } 67 | 68 | int data_read(uint8_t *c, size_t l) { 69 | if ((_DataConnection != 0) && (_DataConnection->available() > 0)) { 70 | return _DataConnection->readBytes(c, l); 71 | } 72 | return 0; 73 | } 74 | 75 | void CloseDataConnection() { 76 | _DataConnection->stop(); 77 | } 78 | 79 | protected: 80 | String _Name; 81 | int _ExpectedArgumentCnt; 82 | FTPFilesystem *const _Filesystem; 83 | IPAddress *const _DataAddress; 84 | int *const _DataPort; 85 | 86 | private: 87 | WiFiClient *const _Client; 88 | WiFiClient *_DataConnection; 89 | }; 90 | 91 | class FTPCommandTransfer : public FTPCommand { 92 | public: 93 | FTPCommandTransfer(String Name, int ExpectedArgumentCnt, WiFiClient *const Client, FTPFilesystem *const Filesystem = 0, IPAddress *DataAddress = 0, int *DataPort = 0) : FTPCommand(Name, ExpectedArgumentCnt, Client, Filesystem, DataAddress, DataPort) { 94 | } 95 | 96 | virtual void workOnData() = 0; 97 | 98 | bool trasferInProgress() { 99 | return _file; 100 | } 101 | 102 | void abort() { 103 | if (_file) { 104 | CloseDataConnection(); 105 | SendResponse(426, "Transfer aborted"); 106 | _file.close(); 107 | } 108 | } 109 | 110 | protected: 111 | File _file; 112 | }; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/FTPConnection.cpp: -------------------------------------------------------------------------------- 1 | #ifdef NO_GLOBAL_INSTANCES 2 | #include 3 | #endif 4 | 5 | #include "Commands/CDUP.h" 6 | #include "Commands/CWD.h" 7 | #include "Commands/DELE.h" 8 | #include "Commands/LIST.h" 9 | #include "Commands/MKD.h" 10 | #include "Commands/MLSD.h" 11 | #include "Commands/NLST.h" 12 | #include "Commands/PORT.h" 13 | #include "Commands/PWD.h" 14 | #include "Commands/RETR.h" 15 | #include "Commands/RMD.h" 16 | #include "Commands/RNFR_RNTO.h" 17 | #include "Commands/STAT.h" 18 | #include "Commands/STOR.h" 19 | #include "Commands/TYPE.h" 20 | #include "ESP-FTP-Server-Lib.h" 21 | #include "common.h" 22 | 23 | FTPConnection::FTPConnection(const WiFiClient &Client, std::list &UserList, FTPFilesystem &Filesystem) : _ClientState(Idle), _Client(Client), _Filesystem(Filesystem), _UserList(UserList), _AuthUsername("") { 24 | std::shared_ptr retr = std::shared_ptr(new RETR(&_Client, &_Filesystem, &_DataAddress, &_DataPort)); 25 | std::shared_ptr stor = std::shared_ptr(new STOR(&_Client, &_Filesystem, &_DataAddress, &_DataPort)); 26 | 27 | _FTPCommands.push_back(std::shared_ptr(new CDUP(&_Client))); 28 | _FTPCommands.push_back(std::shared_ptr(new CWD(&_Client, &_Filesystem))); 29 | _FTPCommands.push_back(std::shared_ptr(new DELE(&_Client, &_Filesystem))); 30 | _FTPCommands.push_back(std::shared_ptr(new STAT(&_Client, &_Filesystem))); 31 | _FTPCommands.push_back(std::shared_ptr(new LIST(&_Client, &_Filesystem, &_DataAddress, &_DataPort))); 32 | _FTPCommands.push_back(std::shared_ptr(new NLST(&_Client, &_Filesystem, &_DataAddress, &_DataPort))); 33 | _FTPCommands.push_back(std::shared_ptr(new MLSD(&_Client, &_Filesystem, &_DataAddress, &_DataPort))); 34 | _FTPCommands.push_back(std::shared_ptr(new MKD(&_Client, &_Filesystem))); 35 | _FTPCommands.push_back(std::shared_ptr(new PORT(&_Client, &_DataAddress, &_DataPort))); 36 | _FTPCommands.push_back(std::shared_ptr(new PWD(&_Client))); 37 | _FTPCommands.push_back(retr); 38 | _FTPCommands.push_back(std::shared_ptr(new RMD(&_Client, &_Filesystem))); 39 | _FTPCommands.push_back(std::shared_ptr(new RNFR_RNTO(&_Client, &_Filesystem))); 40 | _FTPCommands.push_back(stor); 41 | _FTPCommands.push_back(std::shared_ptr(new TYPE(&_Client))); 42 | 43 | _FTPCommandsTransfer.push_back(retr); 44 | _FTPCommandsTransfer.push_back(stor); 45 | 46 | #ifndef NO_GLOBAL_INSTANCES 47 | Serial.print("New Connection from "); 48 | Serial.print(_Client.remoteIP()); 49 | Serial.print(":"); 50 | Serial.println(_Client.remotePort()); 51 | #else 52 | logPrintI("New Connection from "); 53 | logPrintI(_Client.remoteIP().toString()); 54 | logPrintI(":"); 55 | logPrintlnI(String(_Client.remotePort())); 56 | #endif 57 | _Client.println("220--- Welcome to FTP Server for ESP32 ---"); 58 | _Client.println("220--- By Peter Buchegger ---"); 59 | _Client.println("220 -- Version 0.1 ---"); 60 | } 61 | 62 | FTPConnection::~FTPConnection() { 63 | #ifndef NO_GLOBAL_INSTANCES 64 | Serial.println("Connection closed!"); 65 | #else 66 | logPrintlnI("Connection closed!"); 67 | #endif 68 | } 69 | 70 | bool FTPConnection::readUntilLineEnd() { 71 | while (_Client.available()) { 72 | char c = _Client.read(); 73 | if (c == '\n') { 74 | uint32_t index_separator = _Line.indexOf(' '); 75 | _LineSplited = {_Line.substring(0, index_separator), _Line.substring(index_separator + 1, _Line.length())}; 76 | return true; 77 | } 78 | if (c >= 32) { 79 | _Line += c; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | bool FTPConnection::handle() { 86 | if (!_Client.connected()) { 87 | return false; 88 | } 89 | for (std::shared_ptr cmd : _FTPCommandsTransfer) { 90 | if (cmd->trasferInProgress()) { 91 | cmd->workOnData(); 92 | } 93 | } 94 | if (!readUntilLineEnd()) { 95 | return true; 96 | } 97 | // we have a new command in the queue: 98 | #ifndef NO_GLOBAL_INSTANCES 99 | Serial.println(_Line); 100 | #else 101 | logPrintlnD(_Line); 102 | #endif 103 | String command = _LineSplited[0]; 104 | 105 | // This commands are always possible: 106 | if (command == "SYST") { 107 | _Client.println("215 UNIX Type: L8"); 108 | _Line = ""; 109 | return true; 110 | } 111 | /** Additional commads begin ************************************* by Akoro */ 112 | else if (command == "OPTS") // need for Win10 ftp 113 | { 114 | if (_LineSplited[1] == "UTF8 ON") { 115 | _Client.println("200 Ok"); 116 | } else { 117 | _Client.println("500 not implemented"); 118 | } 119 | _Line = ""; 120 | return true; 121 | } else if (command == "NOOP") { 122 | _Client.println("200 Ok"); 123 | _Line = ""; 124 | return true; 125 | } else if (command == "FEAT") { 126 | _Client.println("211- Extensions suported:"); 127 | _Client.println(" UTF8"); 128 | _Client.println(" MLSD"); 129 | _Client.println("211 End."); 130 | _Line = ""; 131 | return true; 132 | } 133 | /** Additional commads end ************************************* by Akoro */ 134 | else if (command == "QUIT") { 135 | _Client.println("221 Goodbye"); 136 | _Client.stop(); 137 | _Line = ""; 138 | return true; 139 | } else if (command == "ABOR") { 140 | for (std::shared_ptr cmd : _FTPCommandsTransfer) { 141 | cmd->abort(); 142 | } 143 | _Client.println("226 Data connection closed"); 144 | _Line = ""; 145 | return true; 146 | } 147 | 148 | // Logged in? 149 | switch (_ClientState) { 150 | case Idle: 151 | if (command == "USER") { 152 | c_USER(); 153 | } else { 154 | _Client.println("500 Unknown command"); 155 | #ifndef NO_GLOBAL_INSTANCES 156 | Serial.println("USER: 500 Unknown command"); 157 | #else 158 | logPrintlnW("USER: 500 Unknown command"); 159 | #endif 160 | } 161 | break; 162 | 163 | case UsernamePass: 164 | if (command == "PASS") { 165 | c_PASS(); 166 | } else { 167 | _Client.println("500 Unknown command"); 168 | #ifndef NO_GLOBAL_INSTANCES 169 | Serial.println("PASS: 500 Unknown command"); 170 | #else 171 | logPrintlnW("PASS: 500 Unknown command"); 172 | #endif 173 | } 174 | break; 175 | 176 | case AuthPass: { 177 | std::vector>::iterator cmdIter = std::find_if(_FTPCommands.begin(), _FTPCommands.end(), [&](std::shared_ptr cmd) { 178 | if (command == cmd->getName() || (cmd->getName() == "RN" && (command == "RNFR" || command == "RNTO"))) { 179 | return true; 180 | } 181 | return false; 182 | }); 183 | if (cmdIter != _FTPCommands.end()) { 184 | (*cmdIter)->run(_WorkDirectory, _LineSplited); 185 | } else { 186 | _Client.println("500 Unknown command"); 187 | #ifndef NO_GLOBAL_INSTANCES 188 | Serial.println("500 Unknown command"); 189 | #else 190 | logPrintlnW("500 Unknown command"); 191 | #endif 192 | } 193 | } 194 | 195 | default: 196 | break; 197 | } 198 | _Line = ""; 199 | return true; 200 | } 201 | 202 | bool FTPConnection::connected() { 203 | return _Client.connected(); 204 | } 205 | 206 | void FTPConnection::c_USER() { 207 | String username = _LineSplited[1]; 208 | std::list::iterator userIter = std::find_if(_UserList.begin(), _UserList.end(), [&](const FTPUser &user) { 209 | if (username == user.Username) { 210 | return true; 211 | } 212 | return false; 213 | }); 214 | if (userIter != _UserList.end()) { 215 | _AuthUsername = username; 216 | _Client.println("331 OK. Password required"); 217 | _ClientState = UsernamePass; 218 | return; 219 | } 220 | _Client.println("530 user not found"); 221 | } 222 | 223 | void FTPConnection::c_PASS() { 224 | String password = _LineSplited[1]; 225 | String username = _AuthUsername; 226 | std::list::iterator _user = std::find_if(_UserList.begin(), _UserList.end(), [&](const FTPUser &user) { 227 | if (username == user.Username && password == user.Password) { 228 | return true; 229 | } 230 | return false; 231 | }); 232 | if (_user != _UserList.end()) { 233 | _Client.println("230 OK."); 234 | _ClientState = AuthPass; 235 | return; 236 | } 237 | _Client.println("530 password not correct"); 238 | } 239 | -------------------------------------------------------------------------------- /src/FTPConnection.h: -------------------------------------------------------------------------------- 1 | #ifndef FTP_CONNECTION_H_ 2 | #define FTP_CONNECTION_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "FTPCommand.h" 8 | #include "FTPPath.h" 9 | #include "FTPUser.h" 10 | 11 | #define FTP_CTRL_PORT 21 12 | #define FTP_USER_TIME_OUT 5 13 | #define FTP_CMD_SIZE 255 + 8 // max size of a command 14 | #define FTP_DIRNAME_SIZE 255 + 8 // max size of a directory name 15 | #define FTP_FILENAME_SIZE 255 // max size of a file name 16 | 17 | class FTPConnection { 18 | public: 19 | FTPConnection(const WiFiClient &Client, std::list &UserList, FTPFilesystem &Filesystem); 20 | virtual ~FTPConnection(); 21 | 22 | bool readUntilLineEnd(); 23 | 24 | bool handle(); 25 | 26 | bool connected(); 27 | 28 | private: 29 | enum ClientState { 30 | Idle, 31 | UsernamePass, 32 | AuthPass, 33 | }; 34 | 35 | void c_USER(); 36 | void c_PASS(); 37 | 38 | ClientState _ClientState; 39 | WiFiClient _Client; 40 | FTPFilesystem &_Filesystem; 41 | String _FilePath; 42 | 43 | String _Line; 44 | std::vector _LineSplited; 45 | 46 | std::list &_UserList; 47 | String _AuthUsername; 48 | 49 | IPAddress _DataAddress; 50 | int _DataPort; 51 | 52 | FTPPath _WorkDirectory; 53 | 54 | std::vector> _FTPCommands; 55 | std::vector> _FTPCommandsTransfer; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/FTPFilesystem.cpp: -------------------------------------------------------------------------------- 1 | #ifdef NO_GLOBAL_INSTANCES 2 | #include 3 | #endif 4 | 5 | #include "FTPFilesystem.h" 6 | #include "common.h" 7 | 8 | FTPFilesystem::FTPFilesystem() { 9 | } 10 | 11 | FTPFilesystem::~FTPFilesystem() { 12 | } 13 | 14 | void FTPFilesystem::addFilesystem(String Name, FS *const Filesystem) { 15 | _Filesystems[Name] = Filesystem; 16 | } 17 | 18 | void FTPFilesystem::clearFilesystemList() { 19 | _Filesystems.clear(); 20 | } 21 | 22 | File FTPFilesystem::open(const String &path, const char *mode) { 23 | if (path == "/") { 24 | std::shared_ptr root = std::shared_ptr(new FTPFileImpl("/")); 25 | for (auto const &f : _Filesystems) { 26 | root->addFilesystem(f.first); 27 | } 28 | return File(root); 29 | } 30 | FS *fs = getFilesystem(path); 31 | if (fs == 0) { 32 | return File(); 33 | } 34 | return fs->open(getPathWithoutFS(path), mode); 35 | } 36 | 37 | bool FTPFilesystem::exists(const String &path) { 38 | FS *fs = getFilesystem(path); 39 | if (fs == 0) { 40 | return false; 41 | } 42 | return fs->exists(getPathWithoutFS(path)); 43 | } 44 | 45 | bool FTPFilesystem::remove(const String &path) { 46 | FS *fs = getFilesystem(path); 47 | if (fs == 0) { 48 | return false; 49 | } 50 | return fs->remove(getPathWithoutFS(path)); 51 | } 52 | 53 | bool FTPFilesystem::rename(const String &pathFrom, const String &pathTo) { 54 | FS *fsFrom = getFilesystem(pathFrom); 55 | FS *fsTo = getFilesystem(pathTo); 56 | if (fsFrom == 0 || fsTo == 0) { 57 | return false; 58 | } 59 | if (fsFrom != fsTo) { 60 | // cant move/rename from one filesystem to another one! 61 | return false; 62 | } 63 | return fsFrom->rename(getPathWithoutFS(pathFrom), getPathWithoutFS(pathTo)); 64 | } 65 | 66 | bool FTPFilesystem::mkdir(const String &path) { 67 | FS *fs = getFilesystem(path); 68 | if (fs == 0) { 69 | return false; 70 | } 71 | return fs->mkdir(getPathWithoutFS(path)); 72 | } 73 | 74 | bool FTPFilesystem::rmdir(const String &path) { 75 | FS *fs = getFilesystem(path); 76 | if (fs == 0) { 77 | return false; 78 | } 79 | return fs->rmdir(getPathWithoutFS(path)); 80 | } 81 | 82 | void FTPFilesystem::printFilesystems() { 83 | for (auto const &fs : _Filesystems) { 84 | #ifndef NO_GLOBAL_INSTANCES 85 | Serial.println(fs.first); 86 | #else 87 | logPrintlnI(fs.first); 88 | #endif 89 | } 90 | } 91 | 92 | FS *FTPFilesystem::getFilesystem(String path) { 93 | std::list splitted = FTPPath::splitPath(path); 94 | String name = *(splitted.begin()); 95 | std::map::iterator iter = _Filesystems.find(name); 96 | if (iter == _Filesystems.end()) { 97 | #ifndef NO_GLOBAL_INSTANCES 98 | Serial.println("[ERROR] Filesystem not found!"); 99 | #else 100 | logPrintlnE("[ERROR] Filesystem not found!"); 101 | #endif 102 | return 0; 103 | } 104 | return iter->second; 105 | } 106 | 107 | String FTPFilesystem::getPathWithoutFS(String path) { 108 | std::list splitted = FTPPath::splitPath(path); 109 | splitted.pop_front(); 110 | String path_without = FTPPath::createPath(splitted); 111 | return path_without; 112 | } 113 | -------------------------------------------------------------------------------- /src/FTPFilesystem.h: -------------------------------------------------------------------------------- 1 | #ifndef FTP_FILESYSTEM_H_ 2 | #define FTP_FILESYSTEM_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "FTPPath.h" 9 | 10 | #ifndef FILE_READ 11 | #define FILE_READ "r" 12 | #endif 13 | 14 | class FTPFilesystem { 15 | public: 16 | FTPFilesystem(); 17 | virtual ~FTPFilesystem(); 18 | 19 | void addFilesystem(String Name, FS *const Filesystem); 20 | void clearFilesystemList(); 21 | 22 | File open(const String &path, const char *mode = FILE_READ); 23 | bool exists(const String &path); 24 | bool remove(const String &path); 25 | bool rename(const String &pathFrom, const String &pathTo); 26 | bool mkdir(const String &path); 27 | bool rmdir(const String &path); 28 | 29 | void printFilesystems(); 30 | 31 | #ifndef UNIT_TEST 32 | private: 33 | #endif 34 | FS *getFilesystem(String path); 35 | static String getPathWithoutFS(String path); 36 | 37 | std::map _Filesystems; 38 | }; 39 | 40 | class FTPFileImpl : public fs::FileImpl { 41 | public: 42 | explicit FTPFileImpl(String name) : _Name(name) { 43 | } 44 | ~FTPFileImpl() { 45 | } 46 | size_t write(const uint8_t *buf, size_t size) override { 47 | return 0; 48 | }; 49 | #if defined(ESP32) 50 | size_t read(uint8_t *buf, size_t size) override { 51 | return 0; 52 | }; 53 | #elif defined(ESP8266) 54 | int read(uint8_t *buf, size_t size) override { 55 | return 0; 56 | }; 57 | #endif 58 | void flush() override{}; 59 | bool seek(uint32_t pos, SeekMode mode) override { 60 | return false; 61 | }; 62 | size_t position() const override { 63 | return 0; 64 | }; 65 | size_t size() const override { 66 | return 0; 67 | }; 68 | void close() override{}; 69 | time_t getLastWrite() override { 70 | return 0; 71 | }; 72 | #if defined ESP_IDF_VERSION && defined ESP_IDF_VERSION_VAL 73 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) 74 | const char *path() const override { 75 | return "not implemented yet"; 76 | }; 77 | #endif 78 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 3) 79 | virtual boolean seekDir(long position) override { 80 | return false; 81 | } 82 | virtual String getNextFileName(void) override { 83 | return ""; 84 | } 85 | #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 8) 86 | virtual String getNextFileName(bool *isDir) override { 87 | *isDir = isDirectory(); 88 | return getNextFileName(); 89 | } 90 | #endif 91 | #endif 92 | #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 3) 93 | bool setBufferSize(size_t size) override { 94 | return false; 95 | }; 96 | #endif 97 | #endif 98 | const char *name() const override { 99 | return _Name.c_str(); 100 | }; 101 | #if defined(ESP32) 102 | boolean isDirectory(void) override { 103 | return true; 104 | }; 105 | #elif defined(ESP8266) 106 | boolean isDirectory(void) const override { 107 | return true; 108 | }; 109 | const char *fullName() const override { 110 | return ("/" + _Name).c_str(); 111 | }; 112 | bool isFile() const override { 113 | return true; 114 | }; 115 | bool truncate(uint32_t size) override { 116 | return true; 117 | }; 118 | #endif 119 | 120 | #if defined(ESP32) 121 | fs::FileImplPtr openNextFile(const char *mode) override 122 | #elif defined(ESP8266) 123 | fs::FileImplPtr openNextFile(const char *mode) 124 | #endif 125 | { 126 | if (_Filesystems.empty()) { 127 | return 0; 128 | } 129 | String next = _Filesystems.front(); 130 | _Filesystems.pop_front(); 131 | return fs::FileImplPtr(new FTPFileImpl(next)); 132 | } 133 | 134 | #if defined(ESP32) 135 | void rewindDirectory(void) override { 136 | return; 137 | }; 138 | 139 | operator bool() override { 140 | return true; 141 | }; 142 | #endif 143 | 144 | void addFilesystem(String name) { 145 | _Filesystems.push_back(name); 146 | } 147 | 148 | private: 149 | String _Name; 150 | std::list _Filesystems; 151 | }; 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /src/FTPPath.cpp: -------------------------------------------------------------------------------- 1 | #include "FTPPath.h" 2 | #include "common.h" 3 | 4 | FTPPath::FTPPath() { 5 | } 6 | 7 | FTPPath::FTPPath(String path) { 8 | changePath(path); 9 | } 10 | 11 | FTPPath::~FTPPath() { 12 | } 13 | 14 | void FTPPath::changePath(String path) { 15 | std::list p = splitPath(path); 16 | if (!path.isEmpty() && path[0] == '/') { 17 | _Path.assign(p.begin(), p.end()); 18 | } else { 19 | std::copy(p.begin(), p.end(), std::back_inserter(_Path)); 20 | } 21 | } 22 | 23 | void FTPPath::goPathUp() { 24 | if (_Path.size() != 0) // Added Akoro 2021-02-27 25 | _Path.pop_back(); 26 | } 27 | 28 | String FTPPath::getPath() const { 29 | return createPath(_Path); 30 | } 31 | 32 | String FTPPath::getFilePath(String filename) const { 33 | if (*filename.begin() == '/') { 34 | return filename; 35 | } 36 | if (_Path.size() == 0) { 37 | return "/" + filename; 38 | } 39 | return getPath() + "/" + filename; 40 | } 41 | 42 | std::list FTPPath::splitPath(String path) { 43 | std::list p = Split>(path, '/'); 44 | p.erase(std::remove_if(p.begin(), p.end(), 45 | [](const String &s) { 46 | if (s.isEmpty()) { 47 | return true; 48 | } 49 | return false; 50 | }), 51 | p.end()); 52 | return p; 53 | } 54 | 55 | String FTPPath::createPath(std::list path) { 56 | if (path.size() == 0) { 57 | return "/"; 58 | } 59 | String new_path; 60 | for (const String &p : path) { 61 | new_path += "/"; 62 | new_path += p; 63 | } 64 | return new_path; 65 | } 66 | -------------------------------------------------------------------------------- /src/FTPPath.h: -------------------------------------------------------------------------------- 1 | #ifndef FTP_PATH_H_ 2 | #define FTP_PATH_H_ 3 | 4 | #include 5 | #include 6 | 7 | class FTPPath { 8 | public: 9 | FTPPath(); 10 | explicit FTPPath(String path); 11 | virtual ~FTPPath(); 12 | 13 | void changePath(String path); 14 | void goPathUp(); 15 | 16 | String getPath() const; 17 | String getFilePath(String filename) const; 18 | 19 | static std::list splitPath(String path); 20 | static String createPath(std::list path); 21 | 22 | private: 23 | std::list _Path; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/FTPUser.h: -------------------------------------------------------------------------------- 1 | #ifndef FTP_USER_H_ 2 | #define FTP_USER_H_ 3 | 4 | #include 5 | 6 | class FTPUser { 7 | public: 8 | FTPUser(const String &username, const String &password) : Username(username), Password(password) { 9 | } 10 | const String Username; 11 | const String Password; 12 | }; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H_ 2 | #define COMMON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template T Split(String str, char parser) { 9 | T str_array; 10 | int last_idx = 0; 11 | int next_idx = str.indexOf(parser, last_idx); 12 | do { 13 | str_array.push_back(str.substring(last_idx, next_idx)); 14 | last_idx = next_idx + 1; 15 | next_idx = str.indexOf(parser, last_idx); 16 | if (next_idx == -1 && last_idx != 0) { 17 | str_array.push_back(str.substring(last_idx, str.length())); 18 | } 19 | } while (next_idx != -1); 20 | return str_array; 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /test/FTPFilesystem/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "FTPFilesystem.h" 5 | 6 | void get_path_without_fs() 7 | { 8 | String path = "/SD/foo/bar"; 9 | String path_without = FTPFilesystem::getPathWithoutFS(path); 10 | TEST_ASSERT_EQUAL_STRING("/foo/bar", path_without.c_str()); 11 | } 12 | 13 | void get_fs() 14 | { 15 | FTPFilesystem f; 16 | f.addFilesystem("SD", &SD); 17 | FS * sd = f.getFilesystem("/SD/foo"); 18 | TEST_ASSERT_EQUAL_PTR(&SD, sd); 19 | } 20 | 21 | void get_multiple_fs() 22 | { 23 | FTPFilesystem f; 24 | f.addFilesystem("SD", &SD); 25 | f.addFilesystem("SPIFFS", &SPIFFS); 26 | FS * sd = f.getFilesystem("/SD/foo"); 27 | TEST_ASSERT_EQUAL_PTR(&SD, sd); 28 | FS * spiffs = f.getFilesystem("/SPIFFS/foo"); 29 | TEST_ASSERT_EQUAL_PTR(&SPIFFS, spiffs); 30 | } 31 | 32 | 33 | void setup() 34 | { 35 | UNITY_BEGIN(); 36 | RUN_TEST(get_path_without_fs); 37 | RUN_TEST(get_fs); 38 | RUN_TEST(get_multiple_fs); 39 | UNITY_END(); 40 | } 41 | 42 | void loop() 43 | { 44 | } 45 | -------------------------------------------------------------------------------- /test/FTPPath/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FTPPath.h" 3 | 4 | void split_path_test_1() 5 | { 6 | String p = "/"; 7 | std::list splited = FTPPath::splitPath(p); 8 | TEST_ASSERT_EQUAL(0, splited.size()); 9 | } 10 | 11 | void split_path_test_2() 12 | { 13 | String p = "/SD/"; 14 | std::list splited = FTPPath::splitPath(p); 15 | TEST_ASSERT_EQUAL(1, splited.size()); 16 | } 17 | 18 | void split_path_test_3() 19 | { 20 | String p = "/SD/abc/"; 21 | std::list splited = FTPPath::splitPath(p); 22 | TEST_ASSERT_EQUAL(2, splited.size()); 23 | } 24 | 25 | void split_path_test_4() 26 | { 27 | String p = "/SD/abc.txt"; 28 | std::list splited = FTPPath::splitPath(p); 29 | TEST_ASSERT_EQUAL(2, splited.size()); 30 | } 31 | 32 | void split_path_test_5() 33 | { 34 | String p = "/SD/abc/abc.txt"; 35 | std::list splited = FTPPath::splitPath(p); 36 | TEST_ASSERT_EQUAL(3, splited.size()); 37 | } 38 | 39 | void set_dir() 40 | { 41 | FTPPath path("/"); 42 | TEST_ASSERT_EQUAL_STRING("/", path.getPath().c_str()); 43 | } 44 | 45 | void change_dir_1() 46 | { 47 | FTPPath path("/"); 48 | path.changePath("foo"); 49 | TEST_ASSERT_EQUAL_STRING("/foo", path.getPath().c_str()); 50 | } 51 | 52 | void change_dir_2() 53 | { 54 | FTPPath path("/"); 55 | path.changePath("foo"); 56 | path.changePath("bar"); 57 | TEST_ASSERT_EQUAL_STRING("/foo/bar", path.getPath().c_str()); 58 | } 59 | 60 | void get_file_1() 61 | { 62 | FTPPath path("/"); 63 | path.changePath("foo"); 64 | path.changePath("bar"); 65 | TEST_ASSERT_EQUAL_STRING("/foo/bar/foo.txt", path.getFilePath("foo.txt").c_str()); 66 | } 67 | 68 | void go_dir_up() 69 | { 70 | FTPPath path("/"); 71 | path.changePath("foo"); 72 | path.changePath("bar"); 73 | path.goPathUp(); 74 | TEST_ASSERT_EQUAL_STRING("/foo", path.getPath().c_str()); 75 | 76 | TEST_ASSERT_EQUAL_STRING("/foo/bar.txt", path.getFilePath("bar.txt").c_str()); 77 | } 78 | 79 | void get_file_2() 80 | { 81 | FTPPath path("/"); 82 | path.changePath("foo"); 83 | path.changePath("bar"); 84 | path.goPathUp(); 85 | TEST_ASSERT_EQUAL_STRING("/foo/bar.txt", path.getFilePath("bar.txt").c_str()); 86 | } 87 | 88 | void setup() 89 | { 90 | UNITY_BEGIN(); 91 | RUN_TEST(split_path_test_1); 92 | RUN_TEST(split_path_test_2); 93 | RUN_TEST(split_path_test_3); 94 | RUN_TEST(split_path_test_4); 95 | RUN_TEST(split_path_test_5); 96 | RUN_TEST(set_dir); 97 | RUN_TEST(change_dir_1); 98 | RUN_TEST(change_dir_2); 99 | RUN_TEST(get_file_1); 100 | RUN_TEST(go_dir_up); 101 | RUN_TEST(get_file_1); 102 | UNITY_END(); 103 | } 104 | 105 | void loop() 106 | { 107 | } 108 | --------------------------------------------------------------------------------