├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── FTP ├── CMakeLists.txt ├── CurlHandle.cpp ├── CurlHandle.h ├── FTPClient.cpp └── FTPClient.h ├── LICENSE ├── README.md ├── TestFTP ├── CMakeLists.txt ├── CodeCoverage.cmake ├── main.cpp ├── sha1 │ ├── SHA1.cpp │ └── SHA1.h ├── simpleini │ ├── ConvertUTF.c │ ├── ConvertUTF.h │ ├── LICENCE.txt │ ├── README.md │ ├── SimpleIni.h │ ├── SimpleIni.sln │ ├── SimpleIni.vcproj │ ├── ini.syn │ ├── package.cmd │ ├── simpleini.doxy │ ├── simpleini.dsp │ ├── simpleini.dsw │ ├── snippets.cpp │ ├── test.cmd │ ├── test1-expected.ini │ ├── test1-input.ini │ ├── test1.cpp │ ├── testsi-EUCJP.ini │ ├── testsi-SJIS.ini │ ├── testsi-UTF8.ini │ └── testsi.cpp ├── template_test_conf.ini ├── test_utils.cpp └── test_utils.h ├── VERSION └── conanfile.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 3 4 | AlignConsecutiveAssignments: true 5 | AlignOperands: true 6 | AlignTrailingComments: true 7 | ColumnLimit: 140 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CMake Build Matrix 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.config.name }} 8 | runs-on: ${{ matrix.config.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | config: 13 | - { 14 | name: "Windows Latest MSVC", artifact: "Windows.tar.xz", 15 | os: windows-latest, 16 | build_type: "Release", cc: "cl", cxx: "cl", 17 | environment_script: "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" 18 | } 19 | - { 20 | name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz", 21 | os: ubuntu-latest, 22 | build_type: "Release", cc: "gcc", cxx: "g++" 23 | } 24 | - { 25 | name: "macOS Latest Clang", artifact: "macOS.tar.xz", 26 | os: macos-latest, 27 | build_type: "Release", cc: "clang", cxx: "clang++" 28 | } 29 | 30 | steps: 31 | - uses: actions/checkout@v1 32 | 33 | - name: Download Ninja and CMake 34 | id: cmake_and_ninja 35 | shell: cmake -P {0} 36 | run: | 37 | set(ninja_version "1.9.0") 38 | set(cmake_version "3.16.2") 39 | 40 | message(STATUS "Using host CMake version: ${CMAKE_VERSION}") 41 | 42 | if ("${{ runner.os }}" STREQUAL "Windows") 43 | set(ninja_suffix "win.zip") 44 | set(cmake_suffix "win64-x64.zip") 45 | set(cmake_dir "cmake-${cmake_version}-win64-x64/bin") 46 | elseif ("${{ runner.os }}" STREQUAL "Linux") 47 | set(ninja_suffix "linux.zip") 48 | set(cmake_suffix "Linux-x86_64.tar.gz") 49 | set(cmake_dir "cmake-${cmake_version}-Linux-x86_64/bin") 50 | elseif ("${{ runner.os }}" STREQUAL "macOS") 51 | set(ninja_suffix "mac.zip") 52 | set(cmake_suffix "Darwin-x86_64.tar.gz") 53 | set(cmake_dir "cmake-${cmake_version}-Darwin-x86_64/CMake.app/Contents/bin") 54 | endif() 55 | 56 | set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}") 57 | file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS) 58 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip) 59 | 60 | set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}") 61 | file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS) 62 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip) 63 | 64 | # Save the path for other steps 65 | file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir) 66 | message("::set-output name=cmake_dir::${cmake_dir}") 67 | 68 | if (NOT "${{ runner.os }}" STREQUAL "Windows") 69 | execute_process( 70 | COMMAND chmod +x ninja 71 | COMMAND chmod +x ${cmake_dir}/cmake 72 | ) 73 | endif() 74 | 75 | - name: Configure 76 | shell: cmake -P {0} 77 | run: | 78 | set(ENV{CC} ${{ matrix.config.cc }}) 79 | set(ENV{CXX} ${{ matrix.config.cxx }}) 80 | 81 | if ("${{ runner.os }}" STREQUAL "Linux") 82 | execute_process( 83 | COMMAND sudo apt-get install libcurl4-openssl-dev libgtest-dev 84 | ) 85 | elseif ("${{ runner.os }}" STREQUAL "Windows") 86 | set(vcpkg_url "https://github.com/microsoft/vcpkg/archive/refs/tags/2022.02.02.zip") 87 | file(DOWNLOAD "${vcpkg_url}" ./vcpkg.zip SHOW_PROGRESS) 88 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ./vcpkg.zip) 89 | execute_process(COMMAND cmd /C cd vcpkg-2022.02.02) 90 | execute_process(COMMAND cmd /C bootstrap-vcpkg.bat) 91 | execute_process(COMMAND cmd /C vcpkg.exe install curl:x64-windows-static) 92 | execute_process(COMMAND cmd /C vcpkg.exe integrate install) 93 | endif() 94 | 95 | if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") 96 | execute_process( 97 | COMMAND "${{ matrix.config.environment_script }}" && set 98 | OUTPUT_FILE environment_script_output.txt 99 | ) 100 | file(STRINGS environment_script_output.txt output_lines) 101 | foreach(line IN LISTS output_lines) 102 | if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") 103 | set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") 104 | endif() 105 | endforeach() 106 | endif() 107 | 108 | file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/ninja" ninja_program) 109 | 110 | if ("${{ runner.os }}" STREQUAL "Windows") 111 | execute_process( 112 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake 113 | -S . 114 | -B build 115 | -D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }} 116 | -D CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake 117 | -D VCPKG_TARGET_TRIPLET=x64-windows-static 118 | -G Ninja 119 | -D CMAKE_MAKE_PROGRAM=${ninja_program} 120 | RESULT_VARIABLE result 121 | ) 122 | if (NOT result EQUAL 0) 123 | message(FATAL_ERROR "Bad exit status") 124 | endif() 125 | endif() 126 | if (NOT "${{ runner.os }}" STREQUAL "Windows") 127 | execute_process( 128 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake 129 | -S . 130 | -B build 131 | -D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }} 132 | -G Ninja 133 | -D CMAKE_MAKE_PROGRAM=${ninja_program} 134 | RESULT_VARIABLE result 135 | ) 136 | if (NOT result EQUAL 0) 137 | message(FATAL_ERROR "Bad exit status") 138 | endif() 139 | endif() 140 | 141 | - name: Build 142 | shell: cmake -P {0} 143 | run: | 144 | set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ") 145 | 146 | if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") 147 | file(STRINGS environment_script_output.txt output_lines) 148 | foreach(line IN LISTS output_lines) 149 | if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") 150 | set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") 151 | endif() 152 | endforeach() 153 | endif() 154 | 155 | execute_process( 156 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --build build 157 | RESULT_VARIABLE result 158 | ) 159 | if (NOT result EQUAL 0) 160 | message(FATAL_ERROR "Bad exit status") 161 | endif() 162 | 163 | 164 | - name: Run tests 165 | shell: cmake -P {0} 166 | run: | 167 | include(ProcessorCount) 168 | ProcessorCount(N) 169 | 170 | execute_process( 171 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/ctest -j ${N} 172 | WORKING_DIRECTORY build 173 | RESULT_VARIABLE result 174 | ) 175 | if (NOT result EQUAL 0) 176 | message(FATAL_ERROR "Running tests failed!") 177 | endif() 178 | 179 | 180 | - name: Install Strip 181 | run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --install build --prefix instdir --strip 182 | 183 | 184 | - name: Pack 185 | working-directory: instdir 186 | run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv ../${{ matrix.config.artifact }} . 187 | 188 | 189 | - name: Upload 190 | uses: actions/upload-artifact@v1 191 | with: 192 | path: ./${{ matrix.config.artifact }} 193 | name: ${{ matrix.config.artifact }} 194 | 195 | release: 196 | if: contains(github.ref, 'tags/v') 197 | runs-on: ubuntu-latest 198 | needs: build 199 | 200 | steps: 201 | - name: Create Release 202 | id: create_release 203 | uses: actions/create-release@v1.0.0 204 | env: 205 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 206 | with: 207 | tag_name: ${{ github.ref }} 208 | release_name: Release ${{ github.ref }} 209 | draft: false 210 | prerelease: false 211 | 212 | - name: Store Release url 213 | run: | 214 | echo "${{ steps.create_release.outputs.upload_url }}" > ./upload_url 215 | 216 | - uses: actions/upload-artifact@v1 217 | with: 218 | path: ./upload_url 219 | name: upload_url 220 | 221 | publish: 222 | if: contains(github.ref, 'tags/v') 223 | name: ${{ matrix.config.name }} 224 | runs-on: ${{ matrix.config.os }} 225 | strategy: 226 | fail-fast: false 227 | matrix: 228 | config: 229 | - { 230 | name: "Windows Latest MSVC", artifact: "Windows.tar.xz", 231 | os: ubuntu-latest 232 | } 233 | - { 234 | name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz", 235 | os: ubuntu-latest 236 | } 237 | - { 238 | name: "macOS Latest Clang", artifact: "macOS.tar.xz", 239 | os: ubuntu-latest 240 | } 241 | needs: release 242 | 243 | steps: 244 | - name: Download artifact 245 | uses: actions/download-artifact@v1 246 | with: 247 | name: ${{ matrix.config.artifact }} 248 | path: ./ 249 | 250 | - name: Download URL 251 | uses: actions/download-artifact@v1 252 | with: 253 | name: upload_url 254 | path: ./ 255 | - id: set_upload_url 256 | run: | 257 | upload_url=`cat ./upload_url` 258 | echo ::set-output name=upload_url::$upload_url 259 | 260 | - name: Upload to Release 261 | id: upload_to_release 262 | uses: actions/upload-release-asset@v1.0.1 263 | env: 264 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 265 | with: 266 | upload_url: ${{ steps.set_upload_url.outputs.upload_url }} 267 | asset_path: ./${{ matrix.config.artifact }} 268 | asset_name: ${{ matrix.config.artifact }} 269 | asset_content_type: application/x-gtar 270 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .vscode/* 3 | .project 4 | .settings/* 5 | CMakeCache.txt 6 | CMakeLists.txt.user 7 | CMakeFiles/ 8 | FTP/.cproject 9 | FTP/.project 10 | FTP/.settings/ 11 | Makefile 12 | TestFTP.xml 13 | TestFTP/.cproject 14 | TestFTP/.project 15 | TestFTP/.settings/* 16 | TestFTP/CMakeCache.txt 17 | TestFTP/CMakeFiles/* 18 | TestFTP/Makefile 19 | TestFTP/bin/* 20 | TestFTP/cmake_install.cmake 21 | TestFTP/coverage/* 22 | TestFTP/Wildcard/* 23 | cmake_install.cmake 24 | lib/* 25 | 26 | #Windows 27 | .vs/* 28 | FTP.suo 29 | FTP/FTP_VS14.vcxproj.user 30 | FTP_VS14.VC.db 31 | TestFTP/LastCoverageResults.log 32 | TestFTP/TestFTP_VS14.vcxproj.user 33 | *build* 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10.0) 2 | 3 | # Read version from file VERSION 4 | FILE(READ "VERSION" project_VERSION) 5 | STRING(STRIP "${project_VERSION}" project_VERSION) 6 | 7 | SET(LICENSE "MIT") 8 | cmake_policy(SET CMP0022 NEW) 9 | 10 | PROJECT(FTPClient VERSION ${project_VERSION} LANGUAGES CXX) 11 | 12 | set(CMAKE_CXX_STANDARD 14) 13 | 14 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/bin) 15 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lib) 16 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lib) 17 | 18 | if(NOT MSVC) 19 | add_definitions(-DLINUX) 20 | else() 21 | add_definitions(-DWINDOWS) 22 | endif() 23 | 24 | option(SKIP_TESTS_BUILD "Skip tests build" ON) 25 | 26 | include_directories(FTP) 27 | 28 | add_subdirectory(FTP) 29 | 30 | if(NOT SKIP_TESTS_BUILD) 31 | add_subdirectory(TestFTP) 32 | 33 | include(CTest) 34 | enable_testing () 35 | 36 | # test if the test INI file exist, otherwise default it to the one in TestFTP folder 37 | IF (NOT TEST_INI_FILE) 38 | SET(TEST_INI_FILE "./TestFTP/template_test_conf.ini") 39 | MESSAGE(WARNING "You didn't provide an INI test configuration file.\ 40 | Defaulting TEST_INI_FILE to ./TestFTP/template_test_conf.ini") 41 | ENDIF() 42 | 43 | add_test (NAME FtpClientTest COMMAND test_ftpclient ${TEST_INI_FILE}) 44 | endif(NOT SKIP_TESTS_BUILD) 45 | -------------------------------------------------------------------------------- /FTP/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | IF(MSVC OR NOT CMAKE_BUILD_TYPE MATCHES Coverage) 3 | 4 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 5 | 6 | #Locate libcURL 7 | find_package(CURL REQUIRED) 8 | include_directories(${CURL_INCLUDE_DIRS}) 9 | 10 | file(GLOB_RECURSE source_files ./*) 11 | add_library(ftpclient STATIC ${source_files}) 12 | 13 | install(TARGETS ftpclient) 14 | 15 | ENDIF() 16 | -------------------------------------------------------------------------------- /FTP/CurlHandle.cpp: -------------------------------------------------------------------------------- 1 | #include "CurlHandle.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace embeddedmz { 7 | 8 | CurlHandle::CurlHandle() { 9 | const auto eCode = curl_global_init(CURL_GLOBAL_ALL); 10 | if (eCode != CURLE_OK) { 11 | throw std::runtime_error{"Error initializing libCURL"}; 12 | } 13 | } 14 | 15 | CurlHandle::~CurlHandle() { curl_global_cleanup(); } 16 | 17 | CurlHandle &CurlHandle::instance() { 18 | static CurlHandle inst{}; 19 | return inst; 20 | } 21 | 22 | } // namespace embeddedmz 23 | -------------------------------------------------------------------------------- /FTP/CurlHandle.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_CURLHANDLE_H_ 2 | #define INCLUDE_CURLHANDLE_H_ 3 | 4 | namespace embeddedmz { 5 | 6 | class CurlHandle { 7 | public: 8 | static CurlHandle &instance(); 9 | 10 | CurlHandle(CurlHandle const &) = delete; 11 | CurlHandle(CurlHandle &&) = delete; 12 | 13 | CurlHandle &operator=(CurlHandle const &) = delete; 14 | CurlHandle &operator=(CurlHandle &&) = delete; 15 | 16 | ~CurlHandle(); 17 | 18 | private: 19 | CurlHandle(); 20 | }; 21 | 22 | } // namespace embeddedmz 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /FTP/FTPClient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file FTPClient.cpp 3 | * @brief implementation of the FTP client class 4 | * @author Mohamed Amine Mzoughi 5 | */ 6 | 7 | #include "FTPClient.h" 8 | 9 | #include 10 | #include 11 | 12 | #define UNUSED(x) static_cast(x); 13 | 14 | namespace embeddedmz { 15 | 16 | // Static members initialization 17 | 18 | #ifdef DEBUG_CURL 19 | std::string CFTPClient::s_strCurlTraceLogDirectory; 20 | #endif 21 | 22 | /** 23 | * @brief constructor of the FTP client object 24 | * 25 | * @param Logger - a callabck to a logger function void(const std::string&) 26 | * 27 | */ 28 | CFTPClient::CFTPClient(LogFnCallback Logger) 29 | : 30 | m_bActive(false), 31 | m_bNoSignal(true), 32 | m_bInsecure(false), 33 | m_uPort(0), 34 | m_eFtpProtocol(FTP_PROTOCOL::FTP), 35 | m_eSettingsFlags(NO_FLAGS), 36 | m_pCurlSession(nullptr), 37 | m_iCurlTimeout(0), 38 | m_bProgressCallbackSet(false), 39 | m_oLog(std::move(Logger)), 40 | m_curlHandle(CurlHandle::instance()) 41 | { 42 | if (!m_oLog) { 43 | throw std::logic_error{"Invalid logger clb applied"}; 44 | } 45 | } 46 | 47 | 48 | 49 | /** 50 | * @brief destructor of the FTP client object 51 | * 52 | */ 53 | CFTPClient::~CFTPClient() { 54 | if (m_pCurlSession != nullptr) { 55 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_WARNING_OBJECT_NOT_CLEANED); 56 | 57 | CleanupSession(); 58 | } 59 | } 60 | 61 | /** 62 | * @brief starts a new FTP session, initializes the cURL API session 63 | * 64 | * if a new session was already started, the method has no effect. 65 | * 66 | * @param [in] strHost server address 67 | * @param [in] uPort the remote port 68 | * @param [in] strLogin username 69 | * @param [in] strPassword password 70 | * @param [in] eFtpProtocol the protocol used to connect to the server (FTP, 71 | * FTPS, FTPES, SFTP) 72 | * @param [in] eSettingsFlags optional use | operator to choose multiple options 73 | * 74 | * @retval true Successfully initialized the session. 75 | * @retval false The session is already initialized. 76 | * call CleanupSession() before initializing a new one or the Curl API is not 77 | * initialized. 78 | * 79 | * Example Usage: 80 | * @code 81 | * m_pFTPClient->InitSession("ftp://127.0.0.1", 21, "username", "password"); 82 | * @endcode 83 | */ 84 | bool CFTPClient::InitSession(const std::string &strHost, const unsigned &uPort, const std::string &strLogin, 85 | const std::string &strPassword, const FTP_PROTOCOL &eFtpProtocol /* = FTP */, 86 | const SettingsFlag &eSettingsFlags /* = NO_FLAGS */) { 87 | if (strHost.empty()) { 88 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_EMPTY_HOST_MSG); 89 | 90 | return false; 91 | } 92 | 93 | if (m_pCurlSession) { 94 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_ALREADY_INIT_MSG); 95 | 96 | return false; 97 | } 98 | m_pCurlSession = curl_easy_init(); 99 | 100 | m_strServer = strHost; 101 | m_uPort = uPort; 102 | m_strUserName = strLogin; 103 | m_strPassword = strPassword; 104 | m_eFtpProtocol = eFtpProtocol; 105 | m_eSettingsFlags = eSettingsFlags; 106 | 107 | return (m_pCurlSession != nullptr); 108 | } 109 | 110 | /** 111 | * @brief cleans the current FTP session 112 | * 113 | * if a session was not already started, the method has no effect 114 | * 115 | * @retval true Successfully cleaned the current session. 116 | * @retval false The session is not initialized. 117 | * 118 | * Example Usage: 119 | * @code 120 | * objFTPClient.CleanupSession(); 121 | * @endcode 122 | */ 123 | bool CFTPClient::CleanupSession() { 124 | if (!m_pCurlSession) { 125 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 126 | 127 | return false; 128 | } 129 | 130 | #ifdef DEBUG_CURL 131 | if (m_ofFileCurlTrace.is_open()) { 132 | m_ofFileCurlTrace.close(); 133 | } 134 | #endif 135 | 136 | curl_easy_cleanup(m_pCurlSession); 137 | m_pCurlSession = nullptr; 138 | 139 | return true; 140 | } 141 | 142 | /** 143 | * @brief sets the progress function callback and the owner of the client 144 | * 145 | * @param [in] pOwner pointer to the object owning the client, nullptr otherwise 146 | * @param [in] fnCallback callback to progress function 147 | * 148 | */ 149 | void CFTPClient::SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback, const bool enable /*= true*/) { 150 | m_ProgressStruct.pOwner = pOwner; 151 | m_fnProgressCallback = fnCallback; 152 | m_ProgressStruct.pCurl = m_pCurlSession; 153 | m_ProgressStruct.dLastRunTime = 0; 154 | m_bProgressCallbackSet = enable; 155 | } 156 | 157 | /** 158 | * @brief sets the HTTP Proxy address to tunnel the operation through it 159 | * 160 | * @param [in] strProxy URI of the HTTP Proxy 161 | * 162 | */ 163 | void CFTPClient::SetProxy(const std::string &strProxy) { 164 | if (strProxy.empty()) return; 165 | 166 | std::string strUri = strProxy; 167 | std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper); 168 | 169 | if (strUri.compare(0, 5, "HTTP:") != 0) 170 | m_strProxy = "http://" + strProxy; 171 | else 172 | m_strProxy = strProxy; 173 | }; 174 | 175 | /** 176 | * @brief sets the HTTP Proxy user and password 177 | * 178 | * @param [in] strProxyUserPwd string of the HTTP Proxy user:password 179 | * 180 | */ 181 | void CFTPClient::SetProxyUserPwd(const std::string &strProxyUserPwd) { 182 | m_strProxyUserPwd = strProxyUserPwd; 183 | }; 184 | 185 | /** 186 | * @brief generates a URI 187 | * 188 | * '/' will be duplicated to be interpreted correctly by the Curl API. 189 | * 190 | * @param [in] strRemoteFile URL of the file. 191 | * 192 | * @retval std::string A complete URI containing the requested resource. 193 | * 194 | * Example Usage: 195 | * @code 196 | * ParseURL("documents/info.txt"); //returns 197 | * "ftp://127.0.0.1//documents//info.txt" 198 | * @endcode 199 | */ 200 | std::string CFTPClient::ParseURL(const std::string &strRemoteFile) const { 201 | std::string strURL = m_strServer + "/" + strRemoteFile; 202 | 203 | ReplaceString(strURL, "/", "//"); 204 | ReplaceString(strURL, " ", "%20"); //fixes folders with spaces not working 205 | 206 | std::string strUri = strURL; 207 | 208 | // boost::to_upper(strUri); 209 | std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper); 210 | 211 | if (strUri.compare(0, 4, "FTP:") != 0 && strUri.compare(0, 5, "SFTP:") != 0) { 212 | switch (m_eFtpProtocol) { 213 | case FTP_PROTOCOL::FTP: 214 | case FTP_PROTOCOL::FTPES: 215 | default: 216 | strURL = "ftp://" + strURL; 217 | break; 218 | 219 | case FTP_PROTOCOL::FTPS: 220 | strURL = "ftps://" + strURL; 221 | break; 222 | 223 | case FTP_PROTOCOL::SFTP: 224 | strURL = "sftp://" + strURL; 225 | break; 226 | } 227 | } 228 | 229 | return strURL; 230 | } 231 | 232 | /** 233 | * @brief creates a remote directory 234 | * 235 | * @param [in] strNewDir the remote direcotry to be created encoded in UTF-8 format. 236 | * 237 | * @retval true Successfully created a directory. 238 | * @retval false The directory couldn't be created. 239 | * 240 | * Example Usage: 241 | * @code 242 | * m_pFTPClient->CreateDir("upload/bookmarks"); 243 | * // Will create bookmarks directory under the upload dir (must exist). 244 | * @endcode 245 | */ 246 | bool CFTPClient::CreateDir(const std::string &strNewDir) const { 247 | if (strNewDir.empty()) return false; 248 | 249 | if (!m_pCurlSession) { 250 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 251 | 252 | return false; 253 | } 254 | // Reset is mandatory to avoid bad surprises 255 | curl_easy_reset(m_pCurlSession); 256 | 257 | struct curl_slist *headerlist = nullptr; 258 | 259 | std::string strRemoteFolder; 260 | std::string strRemoteNewFolderName; 261 | std::string strBuf; 262 | bool bRet = false; 263 | 264 | if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) { 265 | strRemoteFolder = ParseURL(""); 266 | strRemoteNewFolderName = strNewDir; 267 | 268 | // Append the rmdir command 269 | strBuf += "mkdir "; 270 | } else { 271 | // Splitting folder name 272 | std::size_t uFound = strNewDir.find_last_of("/"); 273 | if (uFound != std::string::npos) { 274 | strRemoteFolder = ParseURL(strNewDir.substr(0, uFound)) + "//"; 275 | strRemoteNewFolderName = strNewDir.substr(uFound + 1); 276 | } else // the dir. to be created is located in the root directory 277 | { 278 | strRemoteFolder = ParseURL(""); 279 | strRemoteNewFolderName = strNewDir; 280 | } 281 | // Append the MKD command 282 | strBuf += "MKD "; 283 | } 284 | 285 | // Specify target 286 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str()); 287 | 288 | strBuf += strRemoteNewFolderName; 289 | headerlist = curl_slist_append(headerlist, strBuf.c_str()); 290 | 291 | curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist); 292 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L); 293 | curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L); 294 | curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR); 295 | 296 | /* enable TCP keep-alive for this transfer */ 297 | curl_easy_setopt(m_pCurlSession, CURLOPT_TCP_KEEPALIVE, 0L); 298 | 299 | CURLcode res = Perform(); 300 | 301 | // Check for errors 302 | if (res != CURLE_OK) { 303 | if (m_eSettingsFlags & ENABLE_LOG) 304 | m_oLog(StringFormat(LOG_ERROR_CURL_MKDIR_FORMAT, strRemoteNewFolderName.c_str(), res, curl_easy_strerror(res))); 305 | } else 306 | bRet = true; 307 | 308 | // clean up the FTP commands list 309 | curl_slist_free_all(headerlist); 310 | 311 | return bRet; 312 | } 313 | 314 | /** 315 | * @brief removes an empty remote directory 316 | * 317 | * if the remote directory ain't empty, the method will fail. 318 | * yhe user must use RemoveFile() on all directory's files then use RemoveDir to 319 | * delete the latter. 320 | * 321 | * @param [in] strDir the remote direcotry to be removed encoded in UTF-8 format. 322 | * 323 | * @retval true Successfully removed a directory. 324 | * @retval false The directory couldn't be removed. 325 | * 326 | * Example Usage: 327 | * @code 328 | * m_pFTPClient->RemoveDir("upload/bookmarks"); 329 | * // Will remove bookmarks directory, upload must exist. 330 | * @endcode 331 | */ 332 | bool CFTPClient::RemoveDir(const std::string &strDir) const { 333 | if (strDir.empty()) return false; 334 | 335 | if (!m_pCurlSession) { 336 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 337 | 338 | return false; 339 | } 340 | // Reset is mandatory to avoid bad surprises 341 | curl_easy_reset(m_pCurlSession); 342 | 343 | struct curl_slist *headerlist = nullptr; 344 | 345 | std::string strRemoteFolder; 346 | std::string strRemoteFolderName; 347 | std::string strBuf; 348 | bool bRet = false; 349 | 350 | if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) { 351 | strRemoteFolder = ParseURL(""); 352 | strRemoteFolderName = strDir; 353 | 354 | // Append the rmdir command 355 | strBuf += "rmdir "; 356 | } else { 357 | // Splitting folder name 358 | std::size_t uFound = strDir.find_last_of("/"); 359 | if (uFound != std::string::npos) { 360 | strRemoteFolder = ParseURL(strDir.substr(0, uFound)) + "//"; 361 | strRemoteFolderName = strDir.substr(uFound + 1); 362 | } else // the dir. to be removed is located in the root directory 363 | { 364 | strRemoteFolder = ParseURL(""); 365 | strRemoteFolderName = strDir; 366 | } 367 | 368 | // Append the rmd command 369 | strBuf += "RMD "; 370 | } 371 | 372 | // Specify target 373 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str()); 374 | 375 | strBuf += strRemoteFolderName; 376 | headerlist = curl_slist_append(headerlist, strBuf.c_str()); 377 | 378 | curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist); 379 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L); 380 | curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L); 381 | 382 | CURLcode res = Perform(); 383 | 384 | if (res != CURLE_OK) { 385 | if (m_eSettingsFlags & ENABLE_LOG) 386 | m_oLog(StringFormat(LOG_ERROR_CURL_RMDIR_FORMAT, strRemoteFolderName.c_str(), res, curl_easy_strerror(res))); 387 | } else 388 | bRet = true; 389 | 390 | // clean up the FTP commands list 391 | curl_slist_free_all(headerlist); 392 | 393 | return bRet; 394 | } 395 | 396 | /** 397 | * @brief deletes a remote file 398 | * 399 | * @param [in] strRemoteFile the URL of the remote file encoded in UTF-8 format. 400 | * 401 | * @retval true Successfully deleted the file. 402 | * @retval false The file couldn't be deleted. 403 | * 404 | * Example Usage: 405 | * @code 406 | * m_pFTPClient->RemoveFile("documents/Config.txt"); 407 | * // e.g. : Will delete ftp://127.0.0.1/documents/Config.txt 408 | * @endcode 409 | */ 410 | bool CFTPClient::RemoveFile(const std::string &strRemoteFile) const { 411 | if (strRemoteFile.empty()) return false; 412 | 413 | if (!m_pCurlSession) { 414 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 415 | 416 | return false; 417 | } 418 | // Reset is mandatory to avoid bad surprises 419 | curl_easy_reset(m_pCurlSession); 420 | 421 | struct curl_slist *headerlist = nullptr; 422 | 423 | std::string strRemoteFolder; 424 | std::string strRemoteFileName; 425 | std::string strBuf; 426 | 427 | bool bRet = false; 428 | 429 | if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) { 430 | strRemoteFolder = ParseURL(""); 431 | strRemoteFileName = strRemoteFile; 432 | 433 | // Append the rm command 434 | strBuf += "rm "; 435 | } else { 436 | // Splitting file name 437 | std::size_t uFound = strRemoteFile.find_last_of("/"); 438 | if (uFound != std::string::npos) { 439 | strRemoteFolder = ParseURL(strRemoteFile.substr(0, uFound)) + "//"; 440 | strRemoteFileName = strRemoteFile.substr(uFound + 1); 441 | } else // the file to be deleted is located in the root directory 442 | { 443 | strRemoteFolder = ParseURL(""); 444 | strRemoteFileName = strRemoteFile; 445 | } 446 | 447 | // Append the delete command 448 | strBuf += "DELE "; 449 | } 450 | 451 | // Specify target 452 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str()); 453 | 454 | strBuf += strRemoteFileName; 455 | headerlist = curl_slist_append(headerlist, strBuf.c_str()); 456 | 457 | curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist); 458 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L); 459 | curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L); 460 | 461 | CURLcode res = Perform(); 462 | 463 | // Check for errors 464 | if (res != CURLE_OK) { 465 | if (m_eSettingsFlags & ENABLE_LOG) 466 | m_oLog(StringFormat(LOG_ERROR_CURL_REMOVE_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res))); 467 | } else 468 | bRet = true; 469 | 470 | // clean up the FTP commands list 471 | curl_slist_free_all(headerlist); 472 | 473 | return bRet; 474 | } 475 | 476 | /** 477 | * @brief requests the mtime (epoch) and the size of a remote file. 478 | * 479 | * @param [in] strRemoteFile URN of the remote file encoded in UTF-8 format. 480 | * @param [out] oFileInfo time_t will be updated with the file's mtime and size. 481 | * 482 | * @retval true Successfully gathered the file info time and updated 483 | * "oFileInfo" 484 | * @retval false The infos couldn't be requested. 485 | * 486 | * Example Usage: 487 | * @code 488 | * struct FileInfo& oFileInfo; 489 | * bool bRes = oFTPClient.GetFileTime("pictures/muahahaha.jpg", tFileTime); 490 | * if (bRes) { assert(oFileInfo.tFileMTime > 0); assert(oFileInfo.dFileSize > 491 | * 0); } std::cout << ctime(oFileInfo.tFileMTime) << std::endl; // Sat Aug 06 492 | * 15:04:45 2016 493 | * @endcode 494 | */ 495 | bool CFTPClient::Info(const std::string &strRemoteFile, struct FileInfo &oFileInfo) const { 496 | if (strRemoteFile.empty()) return false; 497 | 498 | if (!m_pCurlSession) { 499 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 500 | 501 | return false; 502 | } 503 | // Reset is mandatory to avoid bad surprises 504 | curl_easy_reset(m_pCurlSession); 505 | 506 | bool bRes = false; 507 | 508 | oFileInfo.tFileMTime = 0; 509 | oFileInfo.dFileSize = 0.0; 510 | 511 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, ParseURL(strRemoteFile).c_str()); 512 | 513 | /* No download if the file */ 514 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L); 515 | 516 | /* Ask for filetime */ 517 | curl_easy_setopt(m_pCurlSession, CURLOPT_FILETIME, 1L); 518 | 519 | /* No header output: TODO 14.1 http-style HEAD output for ftp */ 520 | curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERFUNCTION, ThrowAwayCallback); 521 | curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 0L); 522 | 523 | CURLcode res = Perform(); 524 | 525 | if (CURLE_OK == res) { 526 | long lFileTime = -1; 527 | 528 | res = curl_easy_getinfo(m_pCurlSession, CURLINFO_FILETIME, &lFileTime); 529 | if (CURLE_OK == res && lFileTime >= 0) { 530 | oFileInfo.tFileMTime = static_cast(lFileTime); 531 | bRes = true; 532 | } 533 | 534 | res = curl_easy_getinfo(m_pCurlSession, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &oFileInfo.dFileSize); 535 | if (CURLE_OK != res || oFileInfo.dFileSize < 0.0) { 536 | bRes = false; 537 | } 538 | } else if (m_eSettingsFlags & ENABLE_LOG) 539 | m_oLog(StringFormat(LOG_ERROR_CURL_FILETIME_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res))); 540 | 541 | return bRes; 542 | } 543 | 544 | /** 545 | * @brief lists a remote folder 546 | * the list can contain only names or can be detailed 547 | * entries/names will be separated with LF ('\n') 548 | * 549 | * @param [in] strRemoteFolder URL of the remote location to be listed encoded in UTF-8 format. 550 | * @param [out] strList will contain the directory entries (detailed or not) encoded in UTF-8 format. 551 | * @param [in] bOnlyNames detailed list or only names 552 | * 553 | * @retval true Successfully listed the remote folder and updated FtpFiles. 554 | * @retval false The remote folder couldn't be listed. 555 | * 556 | * Example Usage: 557 | * @code 558 | * std::string strList; 559 | * m_pFTPClient->GetFileList("/", strList, false); 560 | * // lists the root of the remote FTP server with all the infos. 561 | * @endcode 562 | */ 563 | bool CFTPClient::List(const std::string &strRemoteFolder, std::string &strList, bool bOnlyNames /* = true */) const { 564 | if (strRemoteFolder.empty()) return false; 565 | 566 | if (!m_pCurlSession) { 567 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 568 | 569 | return false; 570 | } 571 | // Reset is mandatory to avoid bad surprises 572 | curl_easy_reset(m_pCurlSession); 573 | 574 | bool bRet = false; 575 | 576 | std::string strRemoteFile = ParseURL(strRemoteFolder); 577 | 578 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFile.c_str()); 579 | 580 | if (bOnlyNames) curl_easy_setopt(m_pCurlSession, CURLOPT_DIRLISTONLY, 1L); 581 | 582 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteInStringCallback); 583 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &strList); 584 | 585 | CURLcode res = Perform(); 586 | 587 | if (CURLE_OK == res) 588 | bRet = true; 589 | else if (m_eSettingsFlags & ENABLE_LOG) 590 | m_oLog(StringFormat(LOG_ERROR_CURL_FILELIST_FORMAT, strRemoteFolder.c_str(), res, curl_easy_strerror(res))); 591 | 592 | return bRet; 593 | } 594 | 595 | /** 596 | * @brief downloads a remote file 597 | * 598 | * @param [in] strLocalFile complete path of the downloaded file encoded in UTF-8 format. 599 | * @param [in] strRemoteFile URL of the remote file encoded in UTF-8 format. 600 | * 601 | * @retval true Successfully downloaded the file. 602 | * @retval false The file couldn't be downloaded. Check the log messages for 603 | * more information. 604 | * 605 | * Example Usage: 606 | * @code 607 | * m_pFTPClient->DownloadFile("C:\\Items_To_Upload\\imagination.jpg", 608 | * "upload/pictures/imagination.jpg"); 609 | * @endcode 610 | */ 611 | bool CFTPClient::DownloadFile(const std::string &strLocalFile, const std::string &strRemoteFile) const { 612 | if (strLocalFile.empty() || strRemoteFile.empty()) return false; 613 | 614 | if (!m_pCurlSession) { 615 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 616 | 617 | return false; 618 | } 619 | // Reset is mandatory to avoid bad surprises 620 | curl_easy_reset(m_pCurlSession); 621 | 622 | bool bRet = false; 623 | 624 | std::string strFile = ParseURL(strRemoteFile); 625 | 626 | std::ofstream ofsOutput; 627 | ofsOutput.open( 628 | #ifdef LINUX 629 | strLocalFile, // UTF-8 630 | #else 631 | Utf8ToUtf16(strLocalFile), 632 | #endif 633 | std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); 634 | 635 | if (ofsOutput) { 636 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strFile.c_str()); 637 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToFileCallback); 638 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &ofsOutput); 639 | 640 | CURLcode res = Perform(); 641 | 642 | if (res != CURLE_OK) { 643 | if (m_eSettingsFlags & ENABLE_LOG) 644 | m_oLog(StringFormat(LOG_ERROR_CURL_GETFILE_FORMAT, m_strServer.c_str(), strRemoteFile.c_str(), res, curl_easy_strerror(res))); 645 | } else 646 | bRet = true; 647 | 648 | ofsOutput.close(); 649 | 650 | if (!bRet) remove(strLocalFile.c_str()); 651 | } else if (m_eSettingsFlags & ENABLE_LOG) 652 | m_oLog(LOG_ERROR_FILE_GETFILE_FORMAT); 653 | 654 | return bRet; 655 | } 656 | /** 657 | * @brief downloads a remote file to memory 658 | * 659 | * @param [in] strRemoteFile URI of remote file encoded in UTF-8 format. 660 | * @param [out] data vector of bytes 661 | * 662 | * @retval true Successfully downloaded the file. 663 | * @retval false The file couldn't be downloaded. Check the log messages for 664 | * more information. 665 | */ 666 | bool CFTPClient::DownloadFile(const std::string &strRemoteFile, std::vector &data) const { 667 | if (strRemoteFile.empty()) return false; 668 | if (!m_pCurlSession) { 669 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 670 | return false; 671 | } 672 | curl_easy_reset(m_pCurlSession); 673 | std::string strFile = ParseURL(strRemoteFile); 674 | 675 | data.clear(); 676 | 677 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strFile.c_str()); 678 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToMemory); 679 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data); 680 | 681 | CURLcode res = Perform(); 682 | 683 | if (res != CURLE_OK) { 684 | if (m_eSettingsFlags & ENABLE_LOG) 685 | m_oLog(StringFormat(LOG_ERROR_CURL_GETFILE_FORMAT, m_strServer.c_str(), strRemoteFile.c_str(), res, curl_easy_strerror(res))); 686 | return false; 687 | } else 688 | return true; 689 | } 690 | 691 | /** 692 | * @brief downloads all elements according that match the wildcarded URL 693 | * 694 | * @param [in] strLocalFile Complete path where the elements will be downloaded encoded in UTF-8 format. 695 | * @param [in] strRemoteWildcard Wildcarded pattern to be downloaded encoded in UTF-8 format. 696 | * 697 | * @retval true All the elements have been downloaded. 698 | * @retval false Some or all files or dir have not been downloaded or resp. 699 | * created. 700 | * 701 | * Example Usage: 702 | * @code 703 | * m_pFTPClient->DownloadWildcard("", ""); 704 | * @endcode 705 | */ 706 | bool CFTPClient::DownloadWildcard(const std::string &strLocalDir, const std::string &strRemoteWildcard) const { 707 | if (strLocalDir.empty() || strRemoteWildcard.empty()) return false; 708 | 709 | if (!m_pCurlSession) { 710 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 711 | 712 | return false; 713 | } 714 | // Reset is mandatory to avoid bad surprises 715 | curl_easy_reset(m_pCurlSession); 716 | 717 | bool bRet = false; 718 | 719 | WildcardTransfersCallbackData data; 720 | #ifdef LINUX 721 | data.strOutputPath = strLocalDir + ((strLocalDir.back() != '/') ? "/" : ""); 722 | #else 723 | data.strOutputPath = strLocalDir + ((strLocalDir.back() != '\\') ? "\\" : ""); 724 | #endif 725 | 726 | std::string strPattern = ParseURL(strRemoteWildcard); 727 | 728 | struct stat info; 729 | if (stat(data.strOutputPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR)) // S_ISDIR() doesn't exist on windows 730 | { 731 | /* 0. turn on wildcard matching */ 732 | curl_easy_setopt(m_pCurlSession, CURLOPT_WILDCARDMATCH, 1L); 733 | 734 | /* 1. callback is called before download of concrete file started */ 735 | curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_BGN_FUNCTION, FileIsComingCallback); 736 | 737 | /* 2. this callback will write contents into files */ 738 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteItCallback); 739 | 740 | /* 3. callback is called after data from the file have been transferred */ 741 | curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_END_FUNCTION, FileIsDownloadedCallback); 742 | 743 | /* put transfer data into callbacks */ 744 | curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_DATA, &data); 745 | curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data); 746 | /* curl_easy_setopt(m_pCurlSession, CURLOPT_VERBOSE, 1L); */ 747 | 748 | /* set an URL containing wildcard pattern (only in the last part) */ 749 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strPattern.c_str()); 750 | 751 | /* and start transfer! */ 752 | CURLcode res = Perform(); 753 | 754 | /* in case we have an empty FTP folder, error 78 will be returned */ 755 | if (res != CURLE_OK && res != CURLE_REMOTE_FILE_NOT_FOUND) { 756 | if (m_eSettingsFlags & ENABLE_LOG) 757 | m_oLog( 758 | StringFormat(LOG_ERROR_CURL_GETWILD_FORMAT, m_strServer.c_str(), strRemoteWildcard.c_str(), res, curl_easy_strerror(res))); 759 | } 760 | /* folders need to be copied integrally */ 761 | else if (!data.vecDirList.empty() && strRemoteWildcard.back() == '*') { 762 | std::string strBaseUrl = strRemoteWildcard.substr(0, strRemoteWildcard.length() - 1); 763 | 764 | if (!strBaseUrl.empty() && strBaseUrl.back() != '/') strBaseUrl += "/"; 765 | 766 | // recursively download directories 767 | bRet = true; 768 | for (const auto &Dir : data.vecDirList) { 769 | if ((Dir == ".") || (Dir == "..")) continue; 770 | if (!DownloadWildcard(data.strOutputPath + Dir, strBaseUrl + Dir + "/*")) { 771 | m_oLog( 772 | StringFormat(LOG_ERROR_CURL_GETWILD_REC_FORMAT, (strBaseUrl + Dir + "/*").c_str(), (data.strOutputPath + Dir).c_str())); 773 | bRet = false; 774 | } 775 | } 776 | } else 777 | bRet = true; 778 | } else if (m_eSettingsFlags & ENABLE_LOG) 779 | m_oLog(StringFormat(LOG_ERROR_DIR_GETWILD_FORMAT, data.strOutputPath.c_str())); 780 | 781 | return bRet; 782 | } 783 | 784 | /** 785 | * @brief uploads a user data using readFn to a remote folder. 786 | * 787 | * @param [in] readFn Reading function, corresponds to CURLOPT_READFUNCTION. 788 | * @param [in] userData user data passed to readFn as last parameter. 789 | * @param [in] strRemoteFile Complete URN of the remote location (with the file 790 | * name) encoded in UTF-8 format. 791 | * @param [in] bCreateDir Enable or disable creation of remote missing 792 | * directories contained in the URN. 793 | * 794 | * @retval true Data successfully uploaded. 795 | * @retval false Data couldn't be uploaded. Check the log messages for more 796 | * information. 797 | */ 798 | bool CFTPClient::UploadFile(CFTPClient::CurlReadFn readFn, void *userData, const std::string &strRemoteFile, 799 | const bool &bCreateDir, curl_off_t fileSize) const { 800 | if (readFn == nullptr || strRemoteFile.empty()) 801 | return false; 802 | 803 | if (!m_pCurlSession) { 804 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 805 | 806 | return false; 807 | } 808 | // Reset is mandatory to avoid bad surprises 809 | curl_easy_reset(m_pCurlSession); 810 | 811 | std::string strLocalRemoteFile = ParseURL(strRemoteFile); 812 | 813 | bool bRes = false; 814 | 815 | /* specify target */ 816 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str()); 817 | 818 | /* we want to use our own read function */ 819 | curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, readFn); 820 | 821 | /* now specify which file to upload */ 822 | curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, userData); 823 | 824 | /* Set the size of the file to upload (optional). If you give a *_LARGE 825 | option you MUST make sure that the type of the passed-in argument is a 826 | curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must 827 | make sure that to pass in a type 'long' argument. */ 828 | curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, fileSize); 829 | 830 | /* enable uploading */ 831 | curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L); 832 | 833 | if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR); 834 | 835 | CURLcode res = Perform(); 836 | 837 | if (res != CURLE_OK) { 838 | if (m_eSettingsFlags & ENABLE_LOG) 839 | m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res))); 840 | } else 841 | bRes = true; 842 | 843 | return bRes; 844 | } 845 | 846 | /** 847 | * @brief uploads data from a stream to a remote folder. 848 | * 849 | * @param [in] inputStream Stream containing data which will be uploaded. 850 | * @param [in] strRemoteFile Complete URN of the remote location (with the file 851 | * name) encoded in UTF-8 format. 852 | * @param [in] bCreateDir Enable or disable creation of remote missing 853 | * directories contained in the URN. 854 | * @param [in] fileSize 855 | * 856 | * @retval true Successfully uploaded the inputStream. 857 | * @retval false The inputStream couldn't be uploaded. Check the log messages for more 858 | * information. 859 | */ 860 | bool CFTPClient::UploadFile(std::istream &inputStream, const std::string &strRemoteFile, const bool &bCreateDir, 861 | curl_off_t fileSize) const { 862 | if ( !inputStream ) 863 | return false; 864 | 865 | return UploadFile(ReadFromStreamCallback, static_cast(&inputStream), strRemoteFile, bCreateDir, fileSize); 866 | } 867 | 868 | /** 869 | * @brief uploads a local file to a remote folder. 870 | * 871 | * @param [in] strLocalFile Complete path of the file to upload encoded in UTF-8 format. 872 | * @param [in] strRemoteFile Complete URN of the remote location (with the file 873 | * name) encoded in UTF-8 format. 874 | * @param [in] bCreateDir Enable or disable creation of remote missing 875 | * directories contained in the URN. 876 | * 877 | * @retval true Successfully uploaded the file. 878 | * @retval false The file couldn't be uploaded. Check the log messages for more 879 | * information. 880 | * 881 | * Example Usage: 882 | * @code 883 | * m_pFTPClient->UploadFile("C:\\Items_To_Upload\\imagination.jpg", 884 | * "upload/pictures/imagination.jpg", true); 885 | * // C:\\Items_To_Upload\\imagination.jpg will be uploaded to 886 | * ftp://127.0.0.1/upload/pictures/imagination.jpg 887 | * // "upload" and "pictures" remote directories will be created 888 | * // if they don't exist and if the connected user has the proper rights. 889 | * @endcode 890 | */ 891 | bool CFTPClient::UploadFile(const std::string &strLocalFile, const std::string &strRemoteFile, const bool &bCreateDir) const { 892 | if (strLocalFile.empty() || strRemoteFile.empty()) return false; 893 | 894 | std::ifstream InputFile; 895 | 896 | struct stat file_info; 897 | bool bRes = false; 898 | 899 | /* get the file size of the local file */ 900 | #ifdef LINUX 901 | if (stat(strLocalFile.c_str(), &file_info) == 0) { 902 | InputFile.open(strLocalFile, std::ifstream::in | std::ifstream::binary); 903 | #else 904 | static_assert(sizeof(struct stat) == sizeof(struct _stat64i32), "Oh oh !"); 905 | std::wstring wstrLocalFile = Utf8ToUtf16(strLocalFile); 906 | if (_wstat64i32(wstrLocalFile.c_str(), reinterpret_cast(&file_info)) == 0) { 907 | InputFile.open(wstrLocalFile, std::ifstream::in | std::ifstream::binary); 908 | #endif 909 | if (!InputFile) { 910 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(StringFormat(LOG_ERROR_FILE_UPLOAD_FORMAT, strLocalFile.c_str())); 911 | 912 | return false; 913 | } 914 | 915 | bRes = UploadFile(InputFile, strRemoteFile, bCreateDir, file_info.st_size); 916 | } 917 | InputFile.close(); 918 | 919 | return bRes; 920 | } 921 | 922 | bool CFTPClient::AppendFile(const std::string &strLocalFile, const size_t fileOffset, const std::string &strRemoteFile, 923 | const bool &bCreateDir) const { 924 | if (strLocalFile.empty() || strRemoteFile.empty()) return false; 925 | 926 | if (!m_pCurlSession) { 927 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); 928 | 929 | return false; 930 | } 931 | // Reset is mandatory to avoid bad surprises 932 | curl_easy_reset(m_pCurlSession); 933 | 934 | std::ifstream InputFile; 935 | std::string strLocalRemoteFile = ParseURL(strRemoteFile); 936 | 937 | struct stat file_info; 938 | bool bRes = false; 939 | 940 | /* get the file size of the local file */ 941 | #ifdef LINUX 942 | if (stat(strLocalFile.c_str(), &file_info) == 0) { 943 | InputFile.open(strLocalFile, std::ifstream::in | std::ifstream::binary); 944 | #else 945 | static_assert(sizeof(struct stat) == sizeof(struct _stat64i32), "Oh oh !"); 946 | std::wstring wstrLocalFile = Utf8ToUtf16(strLocalFile); 947 | if (_wstat64i32(wstrLocalFile.c_str(), reinterpret_cast(&file_info)) == 0) { 948 | InputFile.open(wstrLocalFile, std::ifstream::in | std::ifstream::binary); 949 | #endif 950 | if (!InputFile) { 951 | if (m_eSettingsFlags & ENABLE_LOG) m_oLog(StringFormat(LOG_ERROR_FILE_UPLOAD_FORMAT, strLocalFile.c_str())); 952 | 953 | return false; 954 | } 955 | 956 | // check of the offset is less than the file size 957 | if (fileOffset >= file_info.st_size) { 958 | if (m_eSettingsFlags & ENABLE_LOG) 959 | m_oLog("ERROR Incorrect offset !"); // if this code is OK use existing coding style for log msgs 960 | return false; 961 | } 962 | 963 | InputFile.seekg(fileOffset, InputFile.beg); // Sets the position of the next character to be extracted from the input stream. 964 | 965 | /* specify target */ 966 | curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str()); 967 | 968 | /* we want to use our own read function */ 969 | curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, ReadFromStreamCallback); 970 | 971 | /* now specify which file to upload */ 972 | curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &InputFile); 973 | 974 | /* Set the size of the file to upload (optional). If you give a *_LARGE 975 | option you MUST make sure that the type of the passed-in argument is a 976 | curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must 977 | make sure that to pass in a type 'long' argument. */ 978 | curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, static_cast(file_info.st_size - fileOffset)); // Important ! 979 | 980 | /* enable uploading */ 981 | curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L); 982 | curl_easy_setopt(m_pCurlSession, CURLOPT_APPEND, 1L); 983 | 984 | if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR); 985 | 986 | // TODO add the possibility to rename the file upon upload finish.... 987 | 988 | CURLcode res = Perform(); 989 | 990 | if (res != CURLE_OK) { 991 | if (m_eSettingsFlags & ENABLE_LOG) 992 | m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strLocalFile.c_str(), res, curl_easy_strerror(res))); 993 | } else 994 | bRes = true; 995 | } 996 | InputFile.close(); 997 | 998 | return bRes; 999 | } 1000 | 1001 | /** 1002 | * @brief performs the chosen FTP request 1003 | * sets up the common settings (Timeout, proxy,...) 1004 | * 1005 | * 1006 | * @retval true Successfully performed the request. 1007 | * @retval false The request couldn't be performed. 1008 | * 1009 | */ 1010 | CURLcode CFTPClient::Perform() const { 1011 | CURLcode res = CURLE_OK; 1012 | 1013 | curl_easy_setopt(m_pCurlSession, CURLOPT_PORT, m_uPort); 1014 | curl_easy_setopt(m_pCurlSession, CURLOPT_USERPWD, (m_strUserName + ":" + m_strPassword).c_str()); 1015 | 1016 | if (m_bActive) curl_easy_setopt(m_pCurlSession, CURLOPT_FTPPORT, "-"); 1017 | 1018 | if (m_iCurlTimeout > 0) { 1019 | curl_easy_setopt(m_pCurlSession, CURLOPT_TIMEOUT, m_iCurlTimeout); 1020 | } 1021 | if (m_bNoSignal) { 1022 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1L); 1023 | } 1024 | 1025 | if (!m_strProxy.empty()) { 1026 | curl_easy_setopt(m_pCurlSession, CURLOPT_PROXY, m_strProxy.c_str()); 1027 | curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPPROXYTUNNEL, 1L); 1028 | 1029 | if (!m_strProxyUserPwd.empty()) { 1030 | curl_easy_setopt(m_pCurlSession, CURLOPT_PROXYUSERPWD, m_strProxyUserPwd.c_str()); 1031 | } 1032 | 1033 | // use only plain PASV 1034 | if (!m_bActive) { 1035 | curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_USE_EPSV, 1L); 1036 | } 1037 | } 1038 | 1039 | if (m_bProgressCallbackSet) { 1040 | curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSFUNCTION, *GetProgressFnCallback()); 1041 | curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSDATA, &m_ProgressStruct); 1042 | curl_easy_setopt(m_pCurlSession, CURLOPT_NOPROGRESS, 0L); 1043 | } 1044 | 1045 | if (m_eFtpProtocol == FTP_PROTOCOL::FTPS || m_eFtpProtocol == FTP_PROTOCOL::FTPES) 1046 | /* We activate SSL and we require it for both control and data */ 1047 | curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, CURLUSESSL_ALL); 1048 | 1049 | if (m_eFtpProtocol == FTP_PROTOCOL::SFTP && m_eSettingsFlags & ENABLE_SSH_AGENT) 1050 | /* We activate ssh agent. For this to work you need 1051 | to have ssh-agent running (type set | grep SSH_AGENT to check) or 1052 | pageant on Windows (there is an icon in systray if so) */ 1053 | curl_easy_setopt(m_pCurlSession, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_AGENT); 1054 | 1055 | // SSL 1056 | if (!m_strSSLCertFile.empty()) curl_easy_setopt(m_pCurlSession, CURLOPT_SSLCERT, m_strSSLCertFile.c_str()); 1057 | 1058 | if (!m_strSSLKeyFile.empty()) curl_easy_setopt(m_pCurlSession, CURLOPT_SSLKEY, m_strSSLKeyFile.c_str()); 1059 | 1060 | if (!m_strSSLKeyPwd.empty()) curl_easy_setopt(m_pCurlSession, CURLOPT_KEYPASSWD, m_strSSLKeyPwd.c_str()); 1061 | 1062 | curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYHOST, (m_bInsecure) ? 0L : 2L); 1063 | curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, (m_bInsecure) ? 0L : 1L); 1064 | 1065 | #ifdef DEBUG_CURL 1066 | StartCurlDebug(); 1067 | #endif 1068 | 1069 | // Perform the requested operation 1070 | res = curl_easy_perform(m_pCurlSession); 1071 | 1072 | #ifdef DEBUG_CURL 1073 | EndCurlDebug(); 1074 | #endif 1075 | 1076 | return res; 1077 | } 1078 | 1079 | // STRING HELPERS 1080 | 1081 | /** 1082 | * @brief returns a formatted string 1083 | * 1084 | * @param [in] strFormat string with one or many format specifiers 1085 | * @param [in] parameters to be placed in the format specifiers of strFormat 1086 | * 1087 | * @retval string formatted string 1088 | */ 1089 | std::string CFTPClient::StringFormat(std::string strFormat, ...) { 1090 | va_list args; 1091 | va_start(args, strFormat); 1092 | size_t len = std::vsnprintf(NULL, 0, strFormat.c_str(), args); 1093 | va_end(args); 1094 | std::vector vec(len + 1); 1095 | va_start(args, strFormat); 1096 | std::vsnprintf(&vec[0], len + 1, strFormat.c_str(), args); 1097 | va_end(args); 1098 | return &vec[0]; 1099 | } 1100 | 1101 | void CFTPClient::ReplaceString(std::string &strSubject, const std::string &strSearch, const std::string &strReplace) { 1102 | if (strSearch.empty()) return; 1103 | 1104 | size_t pos = 0; 1105 | while ((pos = strSubject.find(strSearch, pos)) != std::string::npos) { 1106 | strSubject.replace(pos, strSearch.length(), strReplace); 1107 | pos += strReplace.length(); 1108 | } 1109 | } 1110 | /* 1111 | void CFTPClient::ReplaceString(std::string& str, const std::string& from, const 1112 | std::string& to) 1113 | { 1114 | if(from.empty()) 1115 | return; 1116 | 1117 | string wsRet; 1118 | wsRet.reserve(str.length()); 1119 | size_t start_pos = 0, pos; 1120 | 1121 | while((pos = str.find(from, start_pos)) != std::string::npos) 1122 | { 1123 | wsRet += str.substr(start_pos, pos - start_pos); 1124 | wsRet += to; 1125 | pos += from.length(); 1126 | start_pos = pos; 1127 | } 1128 | 1129 | wsRet += str.substr(start_pos); 1130 | str.swap(wsRet); // faster than str = wsRet; 1131 | } 1132 | */ 1133 | 1134 | // CURL CALLBACKS 1135 | 1136 | size_t CFTPClient::ThrowAwayCallback(void *ptr, size_t size, size_t nmemb, void *data) { 1137 | /* we are not interested in the headers itself, 1138 | so we only return the size we would have saved ... */ 1139 | 1140 | UNUSED(ptr) 1141 | UNUSED(data) 1142 | return size * nmemb; 1143 | } 1144 | 1145 | /** 1146 | * @brief stores the server response in a string 1147 | * 1148 | * @param ptr pointer of max size (size*nmemb) to read data from it 1149 | * @param size size parameter 1150 | * @param nmemb memblock parameter 1151 | * @param data pointer to user data (string) 1152 | * 1153 | * @return (size * nmemb) 1154 | */ 1155 | size_t CFTPClient::WriteInStringCallback(void *ptr, size_t size, size_t nmemb, void *data) { 1156 | std::string *strWriteHere = reinterpret_cast(data); 1157 | if (strWriteHere != nullptr) { 1158 | strWriteHere->append(reinterpret_cast(ptr), size * nmemb); 1159 | return size * nmemb; 1160 | } 1161 | return 0; 1162 | } 1163 | 1164 | /** 1165 | * @brief stores the server response in an already opened file stream 1166 | * used by DownloadFile() 1167 | * 1168 | * @param buff pointer of max size (size*nmemb) to read data from it 1169 | * @param size size parameter 1170 | * @param nmemb memblock parameter 1171 | * @param userdata pointer to user data (file stream) 1172 | * 1173 | * @return (size * nmemb) 1174 | */ 1175 | size_t CFTPClient::WriteToFileCallback(void *buff, size_t size, size_t nmemb, void *data) { 1176 | if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1) || (data == nullptr)) return 0; 1177 | 1178 | std::ofstream *pFileStream = reinterpret_cast(data); 1179 | if (pFileStream->is_open()) { 1180 | pFileStream->write(reinterpret_cast(buff), size * nmemb); 1181 | } 1182 | 1183 | return size * nmemb; 1184 | } 1185 | /** 1186 | * @brief stores the server response in std::vector 1187 | * 1188 | * @param buff pointer of max size (size*nmemb) to read data from it 1189 | * @param size size parameter 1190 | * @param nmemb memblock parameter 1191 | * @param userdata pointer to user data (file stream) 1192 | * 1193 | * @return (size * nmemb) 1194 | */ 1195 | size_t CFTPClient::WriteToMemory(void *buff, size_t size, size_t nmemb, void *data) { 1196 | if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1) || (data == nullptr)) return 0; 1197 | 1198 | auto *vec = reinterpret_cast *>(data); 1199 | size_t ssize = size * nmemb; 1200 | std::copy((char *)buff, (char *)buff + ssize, std::back_inserter(*vec)); 1201 | 1202 | return ssize; 1203 | } 1204 | /** 1205 | * @brief reads the content of an already opened file stream 1206 | * used by UploadFile() 1207 | * 1208 | * @param ptr pointer of max size (size*nmemb) to write data to it 1209 | * @param size size parameter 1210 | * @param nmemb memblock parameter 1211 | * @param stream pointer to user data (input stream) 1212 | * 1213 | * @return (size * nmemb) 1214 | */ 1215 | size_t CFTPClient::ReadFromStreamCallback(void *ptr, size_t size, size_t nmemb, void *stream) { 1216 | auto *pFileStream = reinterpret_cast(stream); 1217 | if (!pFileStream->fail()) { 1218 | pFileStream->read(reinterpret_cast(ptr), size * nmemb); 1219 | return pFileStream->gcount(); 1220 | } 1221 | return 0; 1222 | } 1223 | 1224 | // WILDCARD DOWNLOAD CALLBACKS 1225 | 1226 | /** 1227 | * @brief will be called before processing an incoming item (file or dir) 1228 | * 1229 | * @param finfo 1230 | * @param data 1231 | * @param remains 1232 | * 1233 | * @return CURL_CHUNK_BGN_FUNC_FAIL (abort perform) or CURL_CHUNK_BGN_FUNC_OK 1234 | * (continue) 1235 | */ 1236 | long CFTPClient::FileIsComingCallback(struct curl_fileinfo *finfo, WildcardTransfersCallbackData *data, int remains) { 1237 | // printf("%3d %40s %10luB ", remains, finfo->filename, (unsigned 1238 | // long)finfo->size); 1239 | UNUSED(remains) 1240 | 1241 | switch (finfo->filetype) { 1242 | case CURLFILETYPE_DIRECTORY: 1243 | // printf(" DIR\n"); 1244 | data->vecDirList.push_back(finfo->filename); 1245 | #ifdef LINUX 1246 | if (mkdir((data->strOutputPath + finfo->filename).c_str(), ACCESSPERMS) != 0 && errno != EEXIST) 1247 | #else 1248 | if (_mkdir((data->strOutputPath + finfo->filename).c_str()) != 0 && errno != EEXIST) 1249 | #endif 1250 | { 1251 | std::cerr << "Problem creating directory (errno=" << errno << "): " << data->strOutputPath + finfo->filename << std::endl; 1252 | return CURL_CHUNK_BGN_FUNC_FAIL; 1253 | } 1254 | /*else 1255 | { 1256 | printf("Directory '%s' was successfully created\n", finfo->filename); 1257 | #ifdef LINUX 1258 | system("ls \\testtmp"); 1259 | #else 1260 | system("dir \\testtmp"); 1261 | #endif 1262 | #ifdef LINUX 1263 | if (rmdir((data->strOutputPath + finfo->filename).c_str()) == 0) 1264 | #else 1265 | if (_rmdir((data->strOutputPath + finfo->filename).c_str()) == 0) 1266 | #endif 1267 | printf("Directory '%s' was successfully removed\n", 1268 | (data->strOutputPath + finfo->filename).c_str()); else printf("Problem 1269 | removing directory '%s'\n", (data->strOutputPath + 1270 | finfo->filename).c_str()); 1271 | }*/ 1272 | break; 1273 | case CURLFILETYPE_FILE: 1274 | // printf("FILE "); 1275 | /* do not transfer files >= 50B */ 1276 | // if (finfo->size > 50) 1277 | //{ 1278 | // printf("SKIPPED\n"); 1279 | // return CURL_CHUNK_BGN_FUNC_SKIP; 1280 | //} 1281 | data->ofsOutput.open( 1282 | #ifdef LINUX 1283 | data->strOutputPath + finfo->filename, 1284 | #else 1285 | Utf8ToUtf16(data->strOutputPath + finfo->filename), 1286 | #endif 1287 | std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); 1288 | if (!data->ofsOutput.is_open()) { 1289 | return CURL_CHUNK_BGN_FUNC_FAIL; 1290 | } 1291 | break; 1292 | default: 1293 | // printf("OTHER\n"); 1294 | break; 1295 | } 1296 | 1297 | return CURL_CHUNK_BGN_FUNC_OK; 1298 | } 1299 | 1300 | /** 1301 | * @brief will be called before after downloading a file 1302 | * 1303 | * @param data 1304 | * 1305 | * @return CURL_CHUNK_END_FUNC_OK (continue performing the request) 1306 | */ 1307 | long CFTPClient::FileIsDownloadedCallback(WildcardTransfersCallbackData *data) { 1308 | if (data->ofsOutput.is_open()) { 1309 | // printf("DOWNLOADED\n"); 1310 | data->ofsOutput.close(); 1311 | } 1312 | return CURL_CHUNK_END_FUNC_OK; 1313 | } 1314 | 1315 | /** 1316 | * @brief will be called to write a chunk of the file before after downloading a 1317 | * file 1318 | * 1319 | * @param buff pointer of max size (size*nmemb) to read data from it 1320 | * @param size size parameter 1321 | * @param nmemb memblock parameter 1322 | * @param cb_data pointer to user data (string) 1323 | * 1324 | * @return number of written bytes 1325 | */ 1326 | size_t CFTPClient::WriteItCallback(char *buff, size_t size, size_t nmemb, void *cb_data) { 1327 | WildcardTransfersCallbackData *data = reinterpret_cast(cb_data); 1328 | size_t written = 0; 1329 | 1330 | if (data->ofsOutput.is_open()) { 1331 | data->ofsOutput.write(buff, size * nmemb); 1332 | written = nmemb; 1333 | } 1334 | /*else 1335 | { 1336 | std::cout.write(buff, size * nmemb); 1337 | written = nmemb; 1338 | }*/ 1339 | return written; 1340 | } 1341 | 1342 | // CURL DEBUG INFO CALLBACKS 1343 | 1344 | #ifdef DEBUG_CURL 1345 | void CFTPClient::SetCurlTraceLogDirectory(const std::string &strPath) { 1346 | s_strCurlTraceLogDirectory = strPath; 1347 | 1348 | if (!s_strCurlTraceLogDirectory.empty() 1349 | #ifdef WINDOWS 1350 | && s_strCurlTraceLogDirectory.back() != '\\') { 1351 | s_strCurlTraceLogDirectory += '\\'; 1352 | } 1353 | #else 1354 | && s_strCurlTraceLogDirectory.back() != '/') { 1355 | s_strCurlTraceLogDirectory += '/'; 1356 | } 1357 | #endif 1358 | } 1359 | 1360 | int CFTPClient::DebugCallback(CURL *curl, curl_infotype curl_info_type, char *pszTrace, size_t usSize, void *pFile) { 1361 | std::string strText; 1362 | std::string strTrace(pszTrace, usSize); 1363 | 1364 | switch (curl_info_type) { 1365 | case CURLINFO_TEXT: 1366 | strText = "# Information : "; 1367 | break; 1368 | case CURLINFO_HEADER_OUT: 1369 | strText = "-> Sending header : "; 1370 | break; 1371 | case CURLINFO_DATA_OUT: 1372 | strText = "-> Sending data : "; 1373 | break; 1374 | case CURLINFO_SSL_DATA_OUT: 1375 | strText = "-> Sending SSL data : "; 1376 | break; 1377 | case CURLINFO_HEADER_IN: 1378 | strText = "<- Receiving header : "; 1379 | break; 1380 | case CURLINFO_DATA_IN: 1381 | strText = "<- Receiving unencrypted data : "; 1382 | break; 1383 | case CURLINFO_SSL_DATA_IN: 1384 | strText = "<- Receiving SSL data : "; 1385 | break; 1386 | default: 1387 | break; 1388 | } 1389 | 1390 | std::ofstream *pofTraceFile = reinterpret_cast(pFile); 1391 | if (pofTraceFile == nullptr) { 1392 | std::cout << "[DEBUG] cURL debug log [" << curl_info_type << "]: " 1393 | << " - " << strTrace; 1394 | } else { 1395 | (*pofTraceFile) << strText << strTrace; 1396 | } 1397 | 1398 | return 0; 1399 | } 1400 | 1401 | void CFTPClient::StartCurlDebug() const { 1402 | if (!m_ofFileCurlTrace.is_open()) { 1403 | curl_easy_setopt(m_pCurlSession, CURLOPT_VERBOSE, 1L); 1404 | curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGFUNCTION, DebugCallback); 1405 | 1406 | std::string strFileCurlTraceFullName(s_strCurlTraceLogDirectory); 1407 | if (!strFileCurlTraceFullName.empty()) { 1408 | char szDate[32]; 1409 | memset(szDate, 0, 32); 1410 | time_t tNow; 1411 | time(&tNow); 1412 | // new trace file for each hour 1413 | strftime(szDate, 32, "%Y%m%d_%H", localtime(&tNow)); 1414 | strFileCurlTraceFullName += "TraceLog_"; 1415 | strFileCurlTraceFullName += szDate; 1416 | strFileCurlTraceFullName += ".txt"; 1417 | 1418 | m_ofFileCurlTrace.open(strFileCurlTraceFullName, std::ifstream::app | std::ifstream::binary); 1419 | 1420 | if (m_ofFileCurlTrace) curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGDATA, &m_ofFileCurlTrace); 1421 | } 1422 | } 1423 | } 1424 | 1425 | void CFTPClient::EndCurlDebug() const { 1426 | if (m_ofFileCurlTrace && m_ofFileCurlTrace.is_open()) { 1427 | m_ofFileCurlTrace << "###########################################" << std::endl; 1428 | m_ofFileCurlTrace.close(); 1429 | } 1430 | } 1431 | #endif 1432 | 1433 | #ifdef WINDOWS 1434 | std::string CFTPClient::AnsiToUtf8(const std::string &codepage_str) { 1435 | // Transcode Windows ANSI to UTF-16 1436 | int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, codepage_str.c_str(), codepage_str.length(), nullptr, 0); 1437 | std::wstring utf16_str(size, '\0'); 1438 | MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, codepage_str.c_str(), codepage_str.length(), &utf16_str[0], size); 1439 | 1440 | // Transcode UTF-16 to UTF-8 1441 | int utf8_size = WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(), utf16_str.length(), nullptr, 0, nullptr, nullptr); 1442 | std::string utf8_str(utf8_size, '\0'); 1443 | WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(), utf16_str.length(), &utf8_str[0], utf8_size, nullptr, nullptr); 1444 | 1445 | return utf8_str; 1446 | } 1447 | 1448 | std::wstring CFTPClient::Utf8ToUtf16(const std::string &str) { 1449 | std::wstring ret; 1450 | int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0); 1451 | if (len > 0) { 1452 | ret.resize(len); 1453 | MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len); 1454 | } 1455 | return ret; 1456 | } 1457 | #endif 1458 | 1459 | } // namespace embeddedmz 1460 | -------------------------------------------------------------------------------- /FTP/FTPClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * @file FTPClient.h 3 | * @brief libcurl wrapper for FTP requests 4 | * 5 | * @author Mohamed Amine Mzoughi 6 | * @date 2017-01-17 7 | */ 8 | 9 | #ifndef INCLUDE_FTPCLIENT_H_ 10 | #define INCLUDE_FTPCLIENT_H_ 11 | 12 | #define FTPCLIENT_VERSION "FTPCLIENT_VERSION_1.0.0" 13 | 14 | #include 15 | #include 16 | #include 17 | #include // std::size_t 18 | #include // snprintf 19 | #include 20 | #include // strerror, strlen, memcpy, strcpy 21 | #include 22 | #ifndef LINUX 23 | #include // mkdir 24 | #endif 25 | #include // va_start, etc. 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "CurlHandle.h" 37 | 38 | namespace embeddedmz { 39 | 40 | class CFTPClient { 41 | public: 42 | // Public definitions 43 | using ProgressFnCallback = std::function; 44 | using LogFnCallback = std::function; 45 | using CurlReadFn = size_t (*) (void *, size_t, size_t, void *); 46 | 47 | // Used to download many items at once 48 | struct WildcardTransfersCallbackData { 49 | std::ofstream ofsOutput; 50 | std::string strOutputPath; 51 | std::vector vecDirList; 52 | // will be used to call GetWildcard recursively to download subdirectories 53 | // content... 54 | }; 55 | 56 | // Progress Function Data Object - parameter void* of ProgressFnCallback 57 | // references it 58 | struct ProgressFnStruct { 59 | ProgressFnStruct() : dLastRunTime(0), pCurl(nullptr), pOwner(nullptr) {} 60 | double dLastRunTime; 61 | CURL *pCurl; 62 | /* owner of the CFTPClient object. can be used in the body of the progress 63 | * function to send signals to the owner (e.g. to update a GUI's progress 64 | * bar) 65 | */ 66 | void *pOwner; 67 | }; 68 | 69 | // See Info method. 70 | struct FileInfo { 71 | time_t tFileMTime; 72 | double dFileSize; 73 | }; 74 | 75 | enum SettingsFlag { 76 | NO_FLAGS = 0x00, 77 | ENABLE_LOG = 0x01, 78 | ENABLE_SSH_AGENT = 0x02, // only for SFTP, can cause auth. failure in some cases (along with m_bInsecure set to false if there's no certs) 79 | ALL_FLAGS = 0xFF 80 | }; 81 | 82 | enum class FTP_PROTOCOL : unsigned char { 83 | // These three protocols below should not be confused with the SFTP 84 | // protocol. SFTP is an entirely different file transfer protocol 85 | // that runs over SSH2. 86 | FTP, // Plain, unencrypted FTP that defaults over port 21. Most web browsers 87 | // support basic FTP. 88 | 89 | FTPS, /* Implicit SSL/TLS encrypted FTP that works just like HTTPS. 90 | * Security is enabled with SSL as soon as the connection starts. 91 | * The default FTPS port is 990. This protocol was the first version 92 | * of encrypted FTP available, and while considered deprecated, is 93 | * still widely used. None of the major web browsers support FTPS. */ 94 | 95 | FTPES, /* Explicit FTP over SSL/TLS. This starts out as plain FTP over port 96 | * 21, but through special FTP commands is upgraded to TLS/SSL 97 | * encryption. This upgrade usually occurs before the user 98 | * credentials are sent over the connection. FTPES is a somewhat 99 | * newer form of encrypted FTP (although still over a decade old), 100 | * and is considered the preferred way to establish encrypted 101 | * connections because it can be more firewall friendly. None of the 102 | * major web browsers support FTPES. */ 103 | 104 | SFTP 105 | }; 106 | 107 | /* Please provide your logger thread-safe routine, otherwise, you can turn off 108 | * error log messages printing by not using the flag ALL_FLAGS or ENABLE_LOG 109 | */ 110 | explicit CFTPClient(LogFnCallback oLogger = [](const std::string &) {}); 111 | virtual ~CFTPClient(); 112 | 113 | // copy constructor and assignment operator are disabled 114 | CFTPClient(const CFTPClient &) = delete; 115 | CFTPClient &operator=(const CFTPClient &) = delete; 116 | 117 | // allow constructor and assignment operator are disabled 118 | CFTPClient(CFTPClient &&) = delete; 119 | CFTPClient &operator=(CFTPClient &&) = delete; 120 | 121 | // Setters - Getters (for unit tests) 122 | void SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback, const bool enable = true); 123 | void SetProxy(const std::string &strProxy); 124 | void SetProxyUserPwd(const std::string &strProxyUserPwd); 125 | inline void SetTimeout(const int &iTimeout) { m_iCurlTimeout = iTimeout; } 126 | inline void SetActive(const bool &bEnable) { m_bActive = bEnable; } 127 | inline void SetNoSignal(const bool &bNoSignal) { m_bNoSignal = bNoSignal; } 128 | inline void SetInsecure(const bool &bInsecure) { m_bInsecure = bInsecure; } 129 | inline auto GetProgressFnCallback() const { return m_fnProgressCallback.target(); } 130 | inline void *GetProgressFnCallbackOwner() const { return m_ProgressStruct.pOwner; } 131 | inline std::string GetProxy() const { return m_strProxy; } 132 | inline std::string GetProxyUserPwd() const { return m_strProxyUserPwd; } 133 | inline int GetTimeout() const { return m_iCurlTimeout; } 134 | inline unsigned GetPort() const { return m_uPort; } 135 | inline bool GetActive() { return m_bActive; } 136 | inline bool GetNoSignal() const { return m_bNoSignal; } 137 | inline bool GetInsecure() const { return m_bInsecure; } 138 | inline std::string GetURL() const { return m_strServer; } 139 | inline std::string GetUsername() const { return m_strUserName; } 140 | inline std::string GetPassword() const { return m_strPassword; } 141 | inline unsigned char GetSettingsFlags() const { return m_eSettingsFlags; } 142 | inline FTP_PROTOCOL GetProtocol() const { return m_eFtpProtocol; } 143 | 144 | // Session 145 | bool InitSession(const std::string &strHost, const unsigned &uPort, const std::string &strLogin, const std::string &strPassword, 146 | const FTP_PROTOCOL &eFtpProtocol = FTP_PROTOCOL::FTP, const SettingsFlag &SettingsFlags = NO_FLAGS); 147 | virtual bool CleanupSession(); 148 | const CURL *GetCurlPointer() const { return m_pCurlSession; } 149 | 150 | // FTP requests 151 | bool CreateDir(const std::string &strNewDir) const; 152 | 153 | bool RemoveDir(const std::string &strDir) const; 154 | 155 | bool RemoveFile(const std::string &strRemoteFile) const; 156 | 157 | /* Checks a single file's size and mtime from an FTP server */ 158 | bool Info(const std::string &strRemoteFile, struct FileInfo &oFileInfo) const; 159 | 160 | bool List(const std::string &strRemoteFolder, std::string &strList, bool bOnlyNames = true) const; 161 | 162 | bool DownloadFile(const std::string &strLocalFile, const std::string &strRemoteFile) const; 163 | 164 | bool DownloadFile(const std::string &strRemoteFile, std::vector &data) const; 165 | 166 | bool DownloadWildcard(const std::string &strLocalDir, const std::string &strRemoteWildcard) const; 167 | 168 | bool UploadFile(CurlReadFn readFn, void *userData, const std::string &strRemoteFile, const bool &bCreateDir = false, 169 | curl_off_t fileSize = -1) const; 170 | 171 | bool UploadFile(std::istream &inputStream, const std::string &strRemoteFile, const bool &bCreateDir = false, 172 | curl_off_t fileSize = -1) const; 173 | 174 | bool UploadFile(const std::string &strLocalFile, const std::string &strRemoteFile, const bool &bCreateDir = false) const; 175 | 176 | bool AppendFile(const std::string &strLocalFile, const size_t fileOffset, const std::string &strRemoteFile, 177 | const bool &bCreateDir = false) const; 178 | 179 | // SSL certs 180 | void SetSSLCertFile(const std::string &strPath) { m_strSSLCertFile = strPath; } 181 | std::string GetSSLCertFile() const { return m_strSSLCertFile; } 182 | 183 | void SetSSLKeyFile(const std::string &strPath) { m_strSSLKeyFile = strPath; } 184 | std::string GetSSLKeyFile() const { return m_strSSLKeyFile; } 185 | 186 | void SetSSLKeyPassword(const std::string &strPwd) { m_strSSLKeyPwd = strPwd; } 187 | std::string GetSSLKeyPwd() const { return m_strSSLKeyPwd; } 188 | 189 | #ifdef DEBUG_CURL 190 | static void SetCurlTraceLogDirectory(const std::string &strPath); 191 | #endif 192 | 193 | #ifdef WINDOWS 194 | static std::string AnsiToUtf8(const std::string& ansiStr); 195 | static std::wstring Utf8ToUtf16(const std::string &str); 196 | #endif 197 | 198 | private: 199 | /* common operations are performed here */ 200 | inline CURLcode Perform() const; 201 | inline std::string ParseURL(const std::string &strURL) const; 202 | 203 | // Curl callbacks 204 | static size_t WriteInStringCallback(void *ptr, size_t size, size_t nmemb, void *data); 205 | static size_t WriteToFileCallback(void *ptr, size_t size, size_t nmemb, void *data); 206 | static size_t ReadFromStreamCallback(void *ptr, size_t size, size_t nmemb, void *stream); 207 | static size_t ThrowAwayCallback(void *ptr, size_t size, size_t nmemb, void *data); 208 | static size_t WriteToMemory(void *ptr, size_t size, size_t nmemb, void *data); 209 | 210 | // Wildcard transfers callbacks 211 | static long FileIsComingCallback(struct curl_fileinfo *finfo, WildcardTransfersCallbackData *data, int remains); 212 | static long FileIsDownloadedCallback(WildcardTransfersCallbackData *data); 213 | static size_t WriteItCallback(char *buff, size_t size, size_t nmemb, void *cb_data); 214 | 215 | // String Helpers 216 | static std::string StringFormat(std::string strFormat, ...); 217 | static void ReplaceString(std::string &strSubject, const std::string &strSearch, const std::string &strReplace); 218 | 219 | // Curl Debug informations 220 | #ifdef DEBUG_CURL 221 | static int DebugCallback(CURL *curl, curl_infotype curl_info_type, char *strace, size_t nSize, void *pFile); 222 | inline void StartCurlDebug() const; 223 | inline void EndCurlDebug() const; 224 | #endif 225 | 226 | std::string m_strUserName; 227 | std::string m_strPassword; 228 | std::string m_strServer; 229 | std::string m_strProxy; 230 | std::string m_strProxyUserPwd; 231 | 232 | bool m_bActive; // For active FTP connections 233 | bool m_bNoSignal; 234 | bool m_bInsecure; 235 | unsigned m_uPort; 236 | 237 | FTP_PROTOCOL m_eFtpProtocol; 238 | SettingsFlag m_eSettingsFlags; 239 | 240 | // SSL 241 | std::string m_strSSLCertFile; 242 | std::string m_strSSLKeyFile; 243 | std::string m_strSSLKeyPwd; 244 | 245 | mutable CURL *m_pCurlSession; 246 | int m_iCurlTimeout; 247 | 248 | // Progress function 249 | ProgressFnCallback m_fnProgressCallback; 250 | ProgressFnStruct m_ProgressStruct; 251 | bool m_bProgressCallbackSet; 252 | 253 | // Log printer callback 254 | LogFnCallback m_oLog; 255 | 256 | #ifdef DEBUG_CURL 257 | static std::string s_strCurlTraceLogDirectory; 258 | mutable std::ofstream m_ofFileCurlTrace; 259 | #endif 260 | CurlHandle &m_curlHandle; 261 | }; 262 | 263 | inline CFTPClient::SettingsFlag operator|(CFTPClient::SettingsFlag a, CFTPClient::SettingsFlag b) { 264 | return static_cast(static_cast(a) | static_cast(b)); 265 | } 266 | 267 | } // namespace embeddedmz 268 | 269 | // Log messages 270 | 271 | #define LOG_WARNING_OBJECT_NOT_CLEANED \ 272 | "[FTPClient][Warning] Object was freed before calling " \ 273 | "CFTPClient::CleanupSession()." \ 274 | " The API session was cleaned though." 275 | #define LOG_ERROR_EMPTY_HOST_MSG "[FTPClient][Error] Empty hostname." 276 | #define LOG_ERROR_CURL_ALREADY_INIT_MSG \ 277 | "[FTPClient][Error] Curl session is already initialized ! " \ 278 | "Use CleanupSession() to clean the present one." 279 | #define LOG_ERROR_CURL_NOT_INIT_MSG \ 280 | "[FTPClient][Error] Curl session is not initialized !" \ 281 | " Use InitSession() before." 282 | #define LOG_ERROR_CURL_REMOVE_FORMAT "[FTPClient][Error] Unable to remove file %s (Error = %d | %s)." 283 | #define LOG_ERROR_CURL_VERIFYURL_FORMAT \ 284 | "[FTPClient][Error] Unable to connect to the remote folder %s (Error = %d " \ 285 | "| %s)." 286 | #define LOG_ERROR_CURL_FILETIME_FORMAT "[FTPClient][Error] Unable to get file %s's info (Error = %d | %s)." 287 | #define LOG_ERROR_CURL_GETFILE_FORMAT "[FTPClient][Error] Unable to import remote File %s/%s (Error = %d | %s)." 288 | #define LOG_ERROR_CURL_UPLOAD_FORMAT "[FTPClient][Error] Unable to upload file %s (Error = %d | %s)." 289 | #define LOG_ERROR_CURL_FILELIST_FORMAT \ 290 | "[FTPClient][Error] Unable to connect to remote folder %s (Error = %d | " \ 291 | "%s)." 292 | #define LOG_ERROR_CURL_GETWILD_FORMAT "[FTPClient][Error] Unable to import elements %s/%s (Error = %d | %s)." 293 | #define LOG_ERROR_CURL_GETWILD_REC_FORMAT "[FTPClient][Error] Encountered a problem while importing %s to %s." 294 | #define LOG_ERROR_CURL_MKDIR_FORMAT "[FTPClient][Error] Unable to create directory %s (Error = %d | %s)." 295 | #define LOG_ERROR_CURL_RMDIR_FORMAT "[FTPClient][Error] Unable to remove directory %s (Error = %d | %s)." 296 | 297 | #define LOG_ERROR_FILE_UPLOAD_FORMAT \ 298 | "[FTPClient][Error] Unable to open local file %s in " \ 299 | "CFTPClient::UploadFile()." 300 | #define LOG_ERROR_FILE_GETFILE_FORMAT \ 301 | "[FTPClient][Error] Unable to open local file %s in " \ 302 | "CFTPClient::DownloadFile()." 303 | #define LOG_ERROR_DIR_GETWILD_FORMAT \ 304 | "[FTPClient][Error] %s is not a directory or it doesn't exist " \ 305 | "in CFTPClient::DownloadWildcard()." 306 | 307 | #endif 308 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mohamed Amine Mzoughi 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 | # FTP client for C++ 2 | [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/embeddedmz/ftpclient-cpp/CMake%20Build%20Matrix) ![img](https://img.shields.io/badge/platform-linux%20%7C%20osx%20%7C%20win-red) ![GitHub all releases](https://img.shields.io/github/downloads/embeddedmz/ftpclient-cpp/total) 3 | 4 | ## About 5 | This is a simple FTP client for C++. It wraps libcurl for FTP requests and meant to be a portable 6 | and easy-to-use API to perform FTP related operations. 7 | 8 | Compilation has been tested with: 9 | - GCC 4.8.5 (Centos 7) 10 | - GCC 5.4.0/7.4.0/9.2.1 (GNU/Linux Ubuntu 16.04/18.04/20.04 LTS) 11 | - Microsoft Visual Studio 2015/2017/2019 (Windows 10) 12 | 13 | Underlying libraries: 14 | - [libcurl](http://curl.haxx.se/libcurl/) 15 | 16 | Windows Users : vcpkg (Microsoft C++ Library Manager) can be used to easily install libcurl, Google Test (GTest) for the unit tests program and generate the Visual Studio solution with CMake. With vcpkg, no need to manually copy the DLL in the output directory, vcpkg handles all that ! Look at "Building under Windows via Visual Studio" section, for instructions. 17 | 18 | ## Usage 19 | Create an object and provide to its constructor a callable object (for log printing) having this signature : 20 | 21 | ```cpp 22 | void(const std::string&) 23 | ``` 24 | 25 | Later, you can enable log printing by using the flag CFTPClient::SettingsFlag::ENABLE_LOG when initializing a session. 26 | 27 | Make sure that Libcurl is compiled with SSH (https://github.com/embeddedmz/ftpclient-cpp/issues/12). 28 | 29 | ```cpp 30 | #include "FTPClient.h" 31 | 32 | CFTPClient FTPClient([](const std::string& strLogMsg){ std::cout << strLogMsg << std::endl; }); 33 | ``` 34 | 35 | Before performing one or more requests, a session must be initialized with server parameters (don't prefix the address with an FTP protocol scheme e.g. ftp://). 36 | 37 | ```cpp 38 | // Classic FTP client 39 | FTPClient.InitSession("127.0.0.1", 21, "username", "password"); 40 | 41 | // For SFTP : 42 | SFTPClient.InitSession("127.0.0.1", 22, "username", "password", CFTPClient::FTP_PROTOCOL::SFTP); 43 | 44 | /* You might need to set insecure to true to turn off peer/host verification - this is the case if you don't use a CA file */ 45 | SFTPClient.SetInsecure(true); 46 | ``` 47 | 48 | With SFTP protocol, all API commands will work except DownloadWildcard. 49 | 50 | You can also set parameters such as the time out (in seconds + enable/disable signal handling), the HTTP proxy server etc... before sending your request. 51 | 52 | To enable FTP Active Mode, use this setter (I didn't test it but it should work fine, create an issue if it doesn't work) : 53 | 54 | ```cpp 55 | FTPClient.SetActive(true); 56 | ``` 57 | 58 | To create and remove a remote empty directory : 59 | 60 | ```cpp 61 | /* creates a directory "bookmarks" under ftp://127.0.0.1:21/documents/ */ 62 | FTPClient.CreateDir("/document/bookmarks"); 63 | 64 | /* removes the "empty" directory "bookmarks" created above 65 | * the directory must be empty, otherwise the method will fail */ 66 | FTPClient.RemoveDir("/document/bookmarks"); 67 | ``` 68 | 69 | To download a file : 70 | 71 | ```cpp 72 | /* download ftp://127.0.0.1:21/info.txt to "C:\downloaded_info.txt" */ 73 | FTPClient.DownloadFile("C:\\downloaded_info.txt", "info.txt"); 74 | ``` 75 | 76 | To download a whole directory with the wildcard '*' : 77 | 78 | ```cpp 79 | /* download all the elements of ftp://127.0.0.1:21/pictures/ to WildcardTest/ */ 80 | FTPClient.DownloadWildcard("/home/amine/WildcardTest", "pictures/*"); 81 | ``` 82 | 83 | To upload and remove a file : 84 | 85 | ```cpp 86 | /* upload C:\test_upload.txt to ftp://127.0.0.1:21/upload/documents/test_upload.txt */ 87 | /* if /upload/documents/ doesn't exist, you can set the third parameter bCreateDir to true 88 | to create missing directories */ 89 | FTPClient.UploadFile("C:\\test_upload.txt", "/upload/documents/test_upload.txt"); 90 | 91 | /* remove the above uploaded file */ 92 | FTPClient.RemoveFile("/upload/documents/test_upload.txt"); 93 | ``` 94 | 95 | You also have a method to append data to a remote file (Issue #34). 96 | 97 | To list a remote directory: 98 | 99 | ```cpp 100 | std::string strList; 101 | 102 | /* list root directory 103 | * a third parameter can be set to false to request a detailed list */ 104 | 105 | FTPClient.List("/", strList); 106 | ``` 107 | 108 | To request a remote file's size and mtime: 109 | 110 | ```cpp 111 | /* create a helper object to receive file's info */ 112 | CFTPClient::FileInfo ResFileInfo = { 0, 0.0 }; 113 | 114 | /* requests ftp://127.0.0.1:21/info.txt file size and mtime */ 115 | FTPClient.Info("info.txt", ResFileInfo)); 116 | 117 | /* if the operation succeeds, true will be returned. */ 118 | 119 | cout << ResFileInfo.dFileSize << endl; // file size of "/info.txt" 120 | cout << ResFileInfo.tFileMTime << endl; // file mtime (epoch) of "/info.txt" 121 | ``` 122 | 123 | Always check that the methods above return true, otherwise, that means that the request wasn't properly 124 | executed. 125 | 126 | Finally cleanup can be done automatically if the object goes out of scope. If the log printing is enabled, 127 | a warning message will be printed. Or you can cleanup manually by calling CleanupSession() : 128 | 129 | ```cpp 130 | FTPClient.CleanupSession(); 131 | ``` 132 | 133 | After cleaning the session, if you want to reuse the object, you need to re-initialize it with the 134 | proper method. 135 | 136 | ## Callback to a Progress Function 137 | 138 | A pointer to a callback progress meter function or a callable object (lambda, functor etc...), which should match the prototype shown below, can be passed. 139 | 140 | ```cpp 141 | int ProgCallback(void* ptr, double dTotalToDownload, double dNowDownloaded, double dTotalToUpload, double dNowUploaded); 142 | ``` 143 | 144 | This function gets called by libcurl instead of its internal equivalent with a frequent interval. While data is being transferred it will be called very frequently, and during slow periods like when nothing is being transferred it can slow down to about one call per second. 145 | 146 | Returning a non-zero value from this callback will cause libcurl to abort the transfer. 147 | 148 | The unit tests "TestDownloadFile" and "TestUploadAndRemoveFile" demonstrate how to use a progress function to display a progress bar on console when downloading or uploading a file. 149 | 150 | ## Thread Safety 151 | 152 | Do not share CFTPClient objects across threads as this would mean accessing libcurl handles from multiple threads at the same time which is not allowed. 153 | 154 | The method SetNoSignal can be used to skip all signal handling. This is important in multi-threaded applications as DNS resolution timeouts use signals. The signal handlers quite readily get executed on other threads. 155 | 156 | ## HTTP Proxy Tunneling Support 157 | 158 | An HTTP Proxy can be set to use for the upcoming request. 159 | To specify a port number, append :[port] to the end of the host name. If not specified, `libcurl` will default to using port 1080 for proxies. The proxy string may be prefixed with `http://` or `https://`. If no HTTP(S) scheme is specified, the address provided to `libcurl` will be prefixed with `http://` to specify an HTTP proxy. A proxy host string can embed user + password. 160 | The operation will be tunneled through the proxy as curl option `CURLOPT_HTTPPROXYTUNNEL` is enabled by default. 161 | A numerical IPv6 address must be written within [brackets]. 162 | 163 | ```cpp 164 | FTPClient.SetProxy("https://37.187.100.23:3128"); 165 | 166 | /* the following request will be tunneled through the proxy */ 167 | std::string strList; 168 | 169 | FTPClient.List("/", strList); 170 | ``` 171 | 172 | if you need to specify a user and password 173 | 174 | ```cpp 175 | FTPClient.SetProxyUserPwd("user:password"); 176 | ``` 177 | 178 | ## Installation 179 | 180 | You will need CMake to generate a makefile for the static library or to build the tests/code coverage program. 181 | 182 | Also make sure you have libcurl and Google Test installed. 183 | 184 | You can follow this script https://gist.github.com/fideloper/f72997d2e2c9fbe66459 to install libcurl. 185 | 186 | This tutorial will help you installing properly Google Test on Ubuntu: https://www.eriksmistad.no/getting-started-with-google-test-on-ubuntu/ 187 | 188 | The CMake script located in the tree will produce Makefiles for the creation of the static library and for the unit tests program. 189 | 190 | To create a debug static library and a test binary, change directory to the one containing the first CMakeLists.txt and : 191 | 192 | ```Shell 193 | mkdir build 194 | cd build 195 | cmake .. -DCMAKE_BUILD_TYPE:STRING=Debug 196 | make 197 | ``` 198 | 199 | To create a release static library, just change "Debug" by "Release". 200 | 201 | The library will be found under "build/[Debug|Release]/lib/libftpclient.a" whereas the test program will be located in "build/[Debug|Release]/bin/test_ftpclient" 202 | 203 | To directly run the unit test binary, you must indicate the path of the INI conf file (see the section below) 204 | ```Shell 205 | ./[Debug|Release]/bin/test_ftpclient /path_to_your_ini_file/conf.ini 206 | ``` 207 | 208 | ### Building under Windows via Visual Studio 209 | 210 | 1. New Procedure (with vcpkg) : 211 | 212 | Install [vcpkg](https://github.com/microsoft/vcpkg) then install libcurl (use 'x86-windows' for the 32-bit version) and Google Test (GTest) : 213 | ```Shell 214 | .\vcpkg install curl curl[openssl] curl[ssh] gtest --triplet=x64-windows 215 | ``` 216 | 217 | If you have a french Visual Studio version, don't forget to install the english language pack (vcpkg will tell you this anyway). 218 | 219 | Download and install the latest version of CMake : https://cmake.org/download/ (e.g. Windows win64-x64 Installer) 220 | 221 | Open CMake (cmake-gui) 222 | 223 | In "Where is the source code", put the ftpclient-cpp path (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/ftpclient-cpp), where the main CMakeLists.txt file exist. 224 | 225 | In "Where to build binaries", paste the directory where you want to build the project (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/ftpclient_build) 226 | 227 | Click on "Configure". 228 | 229 | Select your Visual Studio version (if it isn't already set). 230 | In "Optional platform for generator", you can leave it empty (x64 by default) or choose another value. 231 | 232 | Click on the radio button "Specify toolchain file for cross-compiling, then hit the "Next" button. 233 | 234 | In "Specify the toolchain file", browse to vcpkg toolchain file (vcpkg/scripts/buildsystems/vcpkg.cmake) and select it. 235 | 236 | Press "Finish", wait until CMake configures the project then hit "Generate" to create the Visual Studio solution (library and unit test binary). 237 | 238 | 2. Old Procedure (without vcpkg) : 239 | 240 | First of all, build libcurl using this fork of build-libcurl-windows : https://github.com/ribtoks/build-libcurl-windows 241 | 242 | In fact, this fork will allow you to build libcurl under Visual Studio 2017. 243 | 244 | ```Shell 245 | git clone https://github.com/ribtoks/build-libcurl-windows.git 246 | ``` 247 | 248 | Then just build libcurl using : 249 | 250 | ```Shell 251 | build.bat 252 | ``` 253 | 254 | This batch script will automatically download the latest libcurl source code and build it using the most recent Visual Studio compiler 255 | that it will find on your computer. For a particular version, you can modify the batch script... 256 | 257 | Under YOUR_DIRECTORY\build-libcurl-windows\third-party\libcurl, you will find the curl include directory and a lib directory containing different type of libraries : dynamic and static x86 and x64 libraries compiled in Debug and Release mode (8 libraries). Later, we will be using the libraries located in lib\dll-debug-x64 and lib\dll-release-x64 as an example. 258 | 259 | Concerning Google Test, the library will be downloaded and built automatically from its github repository. Someone with enough free time can do the same for libcurl and submit a pull request... 260 | 261 | Download and install the latest version of CMake : https://cmake.org/download/ (e.g. Windows win64-x64 Installer) 262 | 263 | Open CMake (cmake-gui) 264 | 265 | In "Where is the source code", put the ftpclient-cpp path (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/ftpclient-cpp), where the main CMakeLists.txt file exist. 266 | 267 | In "Where to build binaries", paste the directory where you want to build the project (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/ftpclient_build) 268 | 269 | Click on "Configure". After some time, an error message will be shown, that's normal as CMake is unable to find libcurl. 270 | 271 | Make sure "Advanced" checkbox is checked, in the list, locate the variables prefixed with "CURL_" and update them with the path of libcurl include directory and libraries, for example : 272 | 273 | CURL_INCLUDE_DIR C:\LIBS\build-libcurl-windows\third-party\libcurl\include 274 | CURL_LIBRARY_DEBUG C:\LIBS\build-libcurl-windows\third-party\libcurl\lib\dll-debug-x64\libcurl_debug.lib 275 | CURL_LIBRARY_RELEASE C:\LIBS\build-libcurl-windows\third-party\libcurl\lib\dll-release-x64\libcurl.lib 276 | 277 | Click on "Configure" again ! You should not have errors this time. You can ignore the warning related to the test configuration file. 278 | 279 | Then click on "Generate", you can choose a Visual Studio version if it is not done before (e.g. Visual Studio 15 2017 Win64, I think it should be the same version used to build libcurl...) 280 | 281 | Finally, click on "Open Project" to open the solution in Visual Studio. 282 | 283 | In Visual Studio, you can change the build type (Debug -> Release). Build the solution (press F7). It must succeed without any errors. You can close Visual Studio. 284 | 285 | The library will be found under C:\Users\Amine\Documents\Work\PROJECTS\GitHub\ftpclient_build\lib\Release]\ftpclient.lib 286 | 287 | After building a program using "ftpclient.lib", do not forget to copy libcurl DLL in the directory where the program binary is located. 288 | 289 | For example, in the build directory (e.g. C:\Users\Amine\Documents\Work\PROJECTS\GitHub\ftpclient_build), under "bin", directory, you may find "Debug", "Release" or both according to the build type used during the build in Visual Studio, and in it, the test program "test_ftpclient.exe". Before executing it, make sure to copy the libcurl DLL in the same directory (e.g. copy C:\LIBS\build-libcurl-windows\third-party\libcurl\lib\dll-release-x64\libcurl.dll and the PDB file too if you want, do not change the name of the DLL !) The type of the library MUST correspond to the type of the .lib file fed to CMake-gui ! 290 | 291 | If you want to run the test program, in the command line, launch test_ftpclient.exe with the path of you test configuration file (INI) : 292 | 293 | ```Shell 294 | C:\Users\Amine\Documents\Work\PROJECTS\GitHub\ftpclient_build\bin\[Debug | Release]\test_ftpclient.exe PATH_TO_YOUR_TEST_CONF_FILE\conf.ini 295 | ``` 296 | 297 | ## Run Unit Tests 298 | 299 | First of all, the CMake option SKIP_TESTS_BUILD must be set to "OFF" (via CMake-GUI then pressing on the "Configure" button or if you are using a command line via the argument -DSKIP_TESTS_BUILD=OFF). 300 | 301 | [simpleini](https://github.com/brofield/simpleini) is used to gather unit tests parameters from 302 | an INI configuration file. You need to fill that file with FTP and/or SFTP parameters. 303 | You can also disable some tests (HTTP proxy for instance) and indicate 304 | parameters only for the enabled tests. A template of the INI file already exists under TestFTP/ 305 | 306 | Example : (Run only FTP tests) 307 | 308 | ```ini 309 | [tests] 310 | ; FTP tests are enabled 311 | ftp=yes 312 | ; SFTP tests (not implemented) are disabled 313 | sftp=no 314 | ; HTTP Proxy tests are enabled 315 | http-proxy=yes 316 | 317 | [ftp] 318 | host=127.0.0.1 319 | port=21 320 | username=foo 321 | password=bar 322 | ; remote elements below must exist 323 | remote_file=info.txt 324 | ; for the download/info tests 325 | remote_upload_folder=/upload/documents 326 | ; for upload/create dir tests 327 | remote_download_folder=pictures/ 328 | ; for the test TestWildcardedURL to download an entire remote directory 329 | ; with the files and directories 330 | ``` 331 | 332 | You can also generate an XML file of test results by adding --getst_output argument when calling the test program 333 | 334 | ```Shell 335 | ./[Debug|Release]/bin/test_ftpclient /path_to_your_ini_file/conf.ini --gtest_output="xml:./TestFTP.xml" 336 | ``` 337 | 338 | An alternative way to compile and run unit tests : 339 | 340 | ```Shell 341 | mkdir build 342 | cd build 343 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DTEST_INI_FILE="full_or_relative_path_to_your_test_conf.ini" 344 | make 345 | make test 346 | ``` 347 | 348 | You may use a tool like https://github.com/adarmalik/gtest2html to convert your XML test result in an HTML file. 349 | 350 | ## Memory Leak Check 351 | 352 | Visual Leak Detector has been used to check memory leaks with the Windows build (Visual Sutdio 2015) 353 | You can download it here: https://vld.codeplex.com/ 354 | 355 | To perform a leak check with the Linux build, you can do so : 356 | 357 | ```Shell 358 | valgrind --leak-check=full ./Debug/bin/test_ftpclient /path_to_ini_file/conf.ini 359 | ``` 360 | 361 | ## Code Coverage 362 | 363 | The code coverage build doesn't use the static library but compiles and uses directly the 364 | FTPClient-C++ API in the test program. 365 | 366 | ```Shell 367 | mkdir build 368 | cd build 369 | cmake .. -DCMAKE_BUILD_TYPE=Coverage -DCOVERAGE_INI_FILE:STRING="full_path_to_your_test_conf.ini" 370 | make 371 | make coverage_ftpclient 372 | ``` 373 | 374 | If everything is OK, the results will be found under ./TestFTP/coverage/index.html 375 | 376 | Make sure you feed CMake with a full path to your test conf INI file, otherwise, the coverage test 377 | will be useless. 378 | 379 | Under Visual Studio, you can simply use OpenCppCoverage (https://opencppcoverage.codeplex.com/) 380 | 381 | ## Contribute 382 | All contributions are highly appreciated. This includes updating documentation, writing code and unit tests 383 | to increase code coverage and enhance tools. 384 | 385 | Try to preserve the existing coding style (Hungarian notation, indentation etc...). 386 | 387 | ## Issues 388 | 389 | ### Compiling with the macro DEBUG_CURL 390 | 391 | If you compile the test program with the preprocessor macro DEBUG_CURL, to enable curl debug informations, 392 | the static library used must also be compiled with that macro. Don't forget to mention a path where to store 393 | log files in the INI file if you want to use that feature in the unit test program (curl_logs_folder under [local]) 394 | 395 | ### File names format when compiling with Visual Studio (Windows users) 396 | 397 | It is assumed that the FTP servers you intend to connect with support UTF-8. You must feed the FTP client API with paths/file names encoded in UTF-8 and NOT in ANSI (Windows-1252 on Western/U.S. systems but it can represent certain other Windows code pages on other systems, ANSI is just an extension for ASCII). 398 | 399 | For example, take look at the unit test "TestDownloadFile", the static helper method CFTPClient::AnsiToUtf8 can help you in converting ANSI encoded strings to UTF8. 400 | 401 | If you restrict yourself to ASCII characters in your ANSI string, you don't need to convert your ANSI strings to UTF-8. 402 | 403 | ### Using ftpclient-cpp inside a DLL on Windows 404 | 405 | According to this libcurl documentation [page](https://curl.se/libcurl/c/curl_global_init.html) I quote : 406 | "If you are initializing libcurl from a Windows DLL you should not initialize it from DllMain or a static initializer because Windows holds the loader lock during that time and it could cause a deadlock." 407 | 408 | So, please avoid to use this library in a DLL or if it's not possible you will have just to made the global libcurl initialization/cleaup stuff yourself (look at CurlHandle class and just call the curl_global_init() once before the beginning of the logic of your program in the main() function and curl_global_cleanup at the end of it and don't forget to remove the instance of that class in the CFTPClient class to fix this issue). 409 | -------------------------------------------------------------------------------- /TestFTP/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Code coverage setup 2 | IF(CMAKE_BUILD_TYPE MATCHES Coverage) 3 | INCLUDE(CodeCoverage.cmake) 4 | SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 5 | SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 6 | ENDIF(CMAKE_BUILD_TYPE MATCHES Coverage) 7 | 8 | # Locate libcURL 9 | find_package(CURL REQUIRED) 10 | include_directories(${CURL_INCLUDE_DIRS}) 11 | 12 | # Locate GTest 13 | find_package(GTest REQUIRED) 14 | include_directories(${GTEST_INCLUDE_DIRS}) # useless but test before removing it 15 | 16 | include_directories(../FTP) 17 | include_directories(./sha1) 18 | include_directories(./simpleini) 19 | 20 | include_directories(./) 21 | 22 | IF(NOT MSVC AND CMAKE_BUILD_TYPE MATCHES Coverage) 23 | 24 | file(GLOB_RECURSE ftp_source_files ../FTP/*) 25 | 26 | #Output Setup 27 | add_executable(test_ftpclient main.cpp test_utils.cpp ${ftp_source_files} sha1/SHA1.cpp) 28 | 29 | #Link setup 30 | target_link_libraries(test_ftpclient ${GTEST_LIBRARIES} pthread curl) 31 | 32 | SETUP_TARGET_FOR_COVERAGE( 33 | coverage_ftpclient # Name for custom target. 34 | test_ftpclient # Name of the test driver executable that runs the tests. 35 | # NOTE! This should always have a ZERO as exit code 36 | # otherwise the coverage generation will not complete. 37 | coverage # Name of output directory. 38 | ${COVERAGE_INI_FILE} # Optional fourth parameter is passed as arguments to _testrunner 39 | # Pass them in list form, e.g.: "-j;2" for -j 2 40 | ) 41 | 42 | ELSE() 43 | 44 | #link_directories(${CMAKE_BINARY_DIR}/lib) 45 | 46 | #Output Setup 47 | add_executable(test_ftpclient main.cpp test_utils.cpp sha1/SHA1.cpp) 48 | 49 | #Link setup 50 | if(NOT MSVC) 51 | target_link_libraries(test_ftpclient ftpclient ${GTEST_LIBRARIES} pthread curl) 52 | else() 53 | target_link_libraries(test_ftpclient ftpclient ${GTEST_LIBRARIES} ${CURL_LIBRARIES}) 54 | endif() 55 | 56 | ENDIF() 57 | -------------------------------------------------------------------------------- /TestFTP/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 - 2015, Lars Bilke 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, 5 | # are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors 15 | # may be used to endorse or promote products derived from this software without 16 | # specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # 30 | # 31 | # 2012-01-31, Lars Bilke 32 | # - Enable Code Coverage 33 | # 34 | # 2013-09-17, Joakim Söderberg 35 | # - Added support for Clang. 36 | # - Some additional usage instructions. 37 | # 38 | # USAGE: 39 | 40 | # 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: 41 | # http://stackoverflow.com/a/22404544/80480 42 | # 43 | # 1. Copy this file into your cmake modules path. 44 | # 45 | # 2. Add the following line to your CMakeLists.txt: 46 | # INCLUDE(CodeCoverage) 47 | # 48 | # 3. Set compiler flags to turn off optimization and enable coverage: 49 | # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 50 | # SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 51 | # 52 | # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target 53 | # which runs your test executable and produces a lcov code coverage report: 54 | # Example: 55 | # SETUP_TARGET_FOR_COVERAGE( 56 | # my_coverage_target # Name for custom target. 57 | # test_driver # Name of the test driver executable that runs the tests. 58 | # # NOTE! This should always have a ZERO as exit code 59 | # # otherwise the coverage generation will not complete. 60 | # coverage # Name of output directory. 61 | # ) 62 | # 63 | # 4. Build a Debug build: 64 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 65 | # make 66 | # make my_coverage_target 67 | # 68 | # 69 | 70 | # Check prereqs 71 | FIND_PROGRAM( GCOV_PATH gcov ) 72 | FIND_PROGRAM( LCOV_PATH lcov ) 73 | FIND_PROGRAM( GENHTML_PATH genhtml ) 74 | FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) 75 | 76 | IF(NOT GCOV_PATH) 77 | MESSAGE(FATAL_ERROR "gcov not found! Aborting...") 78 | ENDIF() # NOT GCOV_PATH 79 | 80 | IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") 81 | IF("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) 82 | MESSAGE(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") 83 | ENDIF() 84 | ELSEIF(NOT CMAKE_COMPILER_IS_GNUCXX) 85 | MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 86 | ENDIF() # CHECK VALID COMPILER 87 | 88 | SET(CMAKE_CXX_FLAGS_COVERAGE 89 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 90 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 91 | FORCE ) 92 | SET(CMAKE_C_FLAGS_COVERAGE 93 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 94 | CACHE STRING "Flags used by the C compiler during coverage builds." 95 | FORCE ) 96 | SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE 97 | "" 98 | CACHE STRING "Flags used for linking binaries during coverage builds." 99 | FORCE ) 100 | SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 101 | "" 102 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 103 | FORCE ) 104 | MARK_AS_ADVANCED( 105 | CMAKE_CXX_FLAGS_COVERAGE 106 | CMAKE_C_FLAGS_COVERAGE 107 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 108 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 109 | 110 | IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) 111 | MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) 112 | ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 113 | 114 | 115 | # Param _targetname The name of new the custom make target 116 | # Param _testrunner The name of the target which runs the tests. 117 | # MUST return ZERO always, even on errors. 118 | # If not, no coverage report will be created! 119 | # Param _outputname lcov output is generated as _outputname.info 120 | # HTML report is generated in _outputname/index.html 121 | # Optional fourth parameter is passed as arguments to _testrunner 122 | # Pass them in list form, e.g.: "-j;2" for -j 2 123 | FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) 124 | 125 | IF(NOT LCOV_PATH) 126 | MESSAGE(FATAL_ERROR "lcov not found! Aborting...") 127 | ENDIF() # NOT LCOV_PATH 128 | 129 | IF(NOT GENHTML_PATH) 130 | MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") 131 | ENDIF() # NOT GENHTML_PATH 132 | 133 | SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") 134 | SET(coverage_cleaned "${coverage_info}.cleaned") 135 | 136 | SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") 137 | 138 | # Setup target 139 | ADD_CUSTOM_TARGET(${_targetname} 140 | 141 | # Cleanup lcov 142 | ${LCOV_PATH} --directory . --zerocounters 143 | 144 | # Run tests 145 | COMMAND ${test_command} ${ARGV3} 146 | 147 | # Capturing lcov counters and generating report 148 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} 149 | COMMAND ${LCOV_PATH} --remove ${coverage_info} 'tests/*' '/usr/*' 'simpleini/*' --output-file ${coverage_cleaned} 150 | COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} 151 | COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned} 152 | 153 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 154 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 155 | ) 156 | 157 | # Show info where to find the report 158 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 159 | COMMAND ; 160 | COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." 161 | ) 162 | 163 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE 164 | 165 | # Param _targetname The name of new the custom make target 166 | # Param _testrunner The name of the target which runs the tests 167 | # Param _outputname cobertura output is generated as _outputname.xml 168 | # Optional fourth parameter is passed as arguments to _testrunner 169 | # Pass them in list form, e.g.: "-j;2" for -j 2 170 | FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) 171 | 172 | IF(NOT PYTHON_EXECUTABLE) 173 | MESSAGE(FATAL_ERROR "Python not found! Aborting...") 174 | ENDIF() # NOT PYTHON_EXECUTABLE 175 | 176 | IF(NOT GCOVR_PATH) 177 | MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") 178 | ENDIF() # NOT GCOVR_PATH 179 | 180 | ADD_CUSTOM_TARGET(${_targetname} 181 | 182 | # Run tests 183 | ${_testrunner} ${ARGV3} 184 | 185 | # Running gcovr 186 | COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml 187 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 188 | COMMENT "Running gcovr to produce Cobertura code coverage report." 189 | ) 190 | 191 | # Show info where to find the report 192 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 193 | COMMAND ; 194 | COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." 195 | ) 196 | 197 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA 198 | -------------------------------------------------------------------------------- /TestFTP/main.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embeddedmz/ftpclient-cpp/794fd90e81048452a435ebf57b58195e026c7328/TestFTP/main.cpp -------------------------------------------------------------------------------- /TestFTP/sha1/SHA1.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 100% free public domain implementation of the SHA-1 algorithm 3 | by Dominik Reichl 4 | Web: http://www.dominik-reichl.de/ 5 | 6 | See header file for version history and test vectors. 7 | */ 8 | 9 | // If compiling with MFC, you might want to add #include "StdAfx.h" 10 | 11 | #define _CRT_SECURE_NO_WARNINGS 12 | #include "SHA1.h" 13 | 14 | #define SHA1_MAX_FILE_BUFFER (32 * 20 * 820) 15 | 16 | // Rotate p_val32 by p_nBits bits to the left 17 | #ifndef ROL32 18 | #ifdef _MSC_VER 19 | #define ROL32(p_val32, p_nBits) _rotl(p_val32, p_nBits) 20 | #else 21 | #define ROL32(p_val32, p_nBits) (((p_val32) << (p_nBits)) | ((p_val32) >> (32 - (p_nBits)))) 22 | #endif 23 | #endif 24 | 25 | #ifdef SHA1_LITTLE_ENDIAN 26 | #define SHABLK0(i) (m_block->l[i] = (ROL32(m_block->l[i], 24) & 0xFF00FF00) | (ROL32(m_block->l[i], 8) & 0x00FF00FF)) 27 | #else 28 | #define SHABLK0(i) (m_block->l[i]) 29 | #endif 30 | 31 | #define SHABLK(i) \ 32 | (m_block->l[i & 15] = ROL32(m_block->l[(i + 13) & 15] ^ m_block->l[(i + 8) & 15] ^ m_block->l[(i + 2) & 15] ^ m_block->l[i & 15], 1)) 33 | 34 | // SHA-1 rounds 35 | #define S_R0(v, w, x, y, z, i) \ 36 | { \ 37 | z += ((w & (x ^ y)) ^ y) + SHABLK0(i) + 0x5A827999 + ROL32(v, 5); \ 38 | w = ROL32(w, 30); \ 39 | } 40 | #define S_R1(v, w, x, y, z, i) \ 41 | { \ 42 | z += ((w & (x ^ y)) ^ y) + SHABLK(i) + 0x5A827999 + ROL32(v, 5); \ 43 | w = ROL32(w, 30); \ 44 | } 45 | #define S_R2(v, w, x, y, z, i) \ 46 | { \ 47 | z += (w ^ x ^ y) + SHABLK(i) + 0x6ED9EBA1 + ROL32(v, 5); \ 48 | w = ROL32(w, 30); \ 49 | } 50 | #define S_R3(v, w, x, y, z, i) \ 51 | { \ 52 | z += (((w | x) & y) | (w & x)) + SHABLK(i) + 0x8F1BBCDC + ROL32(v, 5); \ 53 | w = ROL32(w, 30); \ 54 | } 55 | #define S_R4(v, w, x, y, z, i) \ 56 | { \ 57 | z += (w ^ x ^ y) + SHABLK(i) + 0xCA62C1D6 + ROL32(v, 5); \ 58 | w = ROL32(w, 30); \ 59 | } 60 | 61 | #pragma warning(push) 62 | // Disable compiler warning 'Conditional expression is constant' 63 | #pragma warning(disable : 4127) 64 | 65 | CSHA1::CSHA1() { 66 | m_block = (SHA1_WORKSPACE_BLOCK*)m_workspace; 67 | 68 | Reset(); 69 | } 70 | 71 | #ifdef SHA1_WIPE_VARIABLES 72 | CSHA1::~CSHA1() { Reset(); } 73 | #endif 74 | 75 | void CSHA1::Reset() { 76 | // SHA1 initialization constants 77 | m_state[0] = 0x67452301; 78 | m_state[1] = 0xEFCDAB89; 79 | m_state[2] = 0x98BADCFE; 80 | m_state[3] = 0x10325476; 81 | m_state[4] = 0xC3D2E1F0; 82 | 83 | m_count[0] = 0; 84 | m_count[1] = 0; 85 | } 86 | 87 | void CSHA1::Transform(UINT_32* pState, const UINT_8* pBuffer) { 88 | UINT_32 a = pState[0], b = pState[1], c = pState[2], d = pState[3], e = pState[4]; 89 | 90 | memcpy(m_block, pBuffer, 64); 91 | 92 | // 4 rounds of 20 operations each, loop unrolled 93 | S_R0(a, b, c, d, e, 0); 94 | S_R0(e, a, b, c, d, 1); 95 | S_R0(d, e, a, b, c, 2); 96 | S_R0(c, d, e, a, b, 3); 97 | S_R0(b, c, d, e, a, 4); 98 | S_R0(a, b, c, d, e, 5); 99 | S_R0(e, a, b, c, d, 6); 100 | S_R0(d, e, a, b, c, 7); 101 | S_R0(c, d, e, a, b, 8); 102 | S_R0(b, c, d, e, a, 9); 103 | S_R0(a, b, c, d, e, 10); 104 | S_R0(e, a, b, c, d, 11); 105 | S_R0(d, e, a, b, c, 12); 106 | S_R0(c, d, e, a, b, 13); 107 | S_R0(b, c, d, e, a, 14); 108 | S_R0(a, b, c, d, e, 15); 109 | S_R1(e, a, b, c, d, 16); 110 | S_R1(d, e, a, b, c, 17); 111 | S_R1(c, d, e, a, b, 18); 112 | S_R1(b, c, d, e, a, 19); 113 | S_R2(a, b, c, d, e, 20); 114 | S_R2(e, a, b, c, d, 21); 115 | S_R2(d, e, a, b, c, 22); 116 | S_R2(c, d, e, a, b, 23); 117 | S_R2(b, c, d, e, a, 24); 118 | S_R2(a, b, c, d, e, 25); 119 | S_R2(e, a, b, c, d, 26); 120 | S_R2(d, e, a, b, c, 27); 121 | S_R2(c, d, e, a, b, 28); 122 | S_R2(b, c, d, e, a, 29); 123 | S_R2(a, b, c, d, e, 30); 124 | S_R2(e, a, b, c, d, 31); 125 | S_R2(d, e, a, b, c, 32); 126 | S_R2(c, d, e, a, b, 33); 127 | S_R2(b, c, d, e, a, 34); 128 | S_R2(a, b, c, d, e, 35); 129 | S_R2(e, a, b, c, d, 36); 130 | S_R2(d, e, a, b, c, 37); 131 | S_R2(c, d, e, a, b, 38); 132 | S_R2(b, c, d, e, a, 39); 133 | S_R3(a, b, c, d, e, 40); 134 | S_R3(e, a, b, c, d, 41); 135 | S_R3(d, e, a, b, c, 42); 136 | S_R3(c, d, e, a, b, 43); 137 | S_R3(b, c, d, e, a, 44); 138 | S_R3(a, b, c, d, e, 45); 139 | S_R3(e, a, b, c, d, 46); 140 | S_R3(d, e, a, b, c, 47); 141 | S_R3(c, d, e, a, b, 48); 142 | S_R3(b, c, d, e, a, 49); 143 | S_R3(a, b, c, d, e, 50); 144 | S_R3(e, a, b, c, d, 51); 145 | S_R3(d, e, a, b, c, 52); 146 | S_R3(c, d, e, a, b, 53); 147 | S_R3(b, c, d, e, a, 54); 148 | S_R3(a, b, c, d, e, 55); 149 | S_R3(e, a, b, c, d, 56); 150 | S_R3(d, e, a, b, c, 57); 151 | S_R3(c, d, e, a, b, 58); 152 | S_R3(b, c, d, e, a, 59); 153 | S_R4(a, b, c, d, e, 60); 154 | S_R4(e, a, b, c, d, 61); 155 | S_R4(d, e, a, b, c, 62); 156 | S_R4(c, d, e, a, b, 63); 157 | S_R4(b, c, d, e, a, 64); 158 | S_R4(a, b, c, d, e, 65); 159 | S_R4(e, a, b, c, d, 66); 160 | S_R4(d, e, a, b, c, 67); 161 | S_R4(c, d, e, a, b, 68); 162 | S_R4(b, c, d, e, a, 69); 163 | S_R4(a, b, c, d, e, 70); 164 | S_R4(e, a, b, c, d, 71); 165 | S_R4(d, e, a, b, c, 72); 166 | S_R4(c, d, e, a, b, 73); 167 | S_R4(b, c, d, e, a, 74); 168 | S_R4(a, b, c, d, e, 75); 169 | S_R4(e, a, b, c, d, 76); 170 | S_R4(d, e, a, b, c, 77); 171 | S_R4(c, d, e, a, b, 78); 172 | S_R4(b, c, d, e, a, 79); 173 | 174 | // Add the working vars back into state 175 | pState[0] += a; 176 | pState[1] += b; 177 | pState[2] += c; 178 | pState[3] += d; 179 | pState[4] += e; 180 | 181 | // Wipe variables 182 | #ifdef SHA1_WIPE_VARIABLES 183 | a = b = c = d = e = 0; 184 | #endif 185 | } 186 | 187 | void CSHA1::Update(const UINT_8* pbData, UINT_32 uLen) { 188 | UINT_32 j = ((m_count[0] >> 3) & 0x3F); 189 | 190 | if ((m_count[0] += (uLen << 3)) < (uLen << 3)) ++m_count[1]; // Overflow 191 | 192 | m_count[1] += (uLen >> 29); 193 | 194 | UINT_32 i; 195 | if ((j + uLen) > 63) { 196 | i = 64 - j; 197 | memcpy(&m_buffer[j], pbData, i); 198 | Transform(m_state, m_buffer); 199 | 200 | for (; (i + 63) < uLen; i += 64) Transform(m_state, &pbData[i]); 201 | 202 | j = 0; 203 | } else 204 | i = 0; 205 | 206 | if ((uLen - i) != 0) memcpy(&m_buffer[j], &pbData[i], uLen - i); 207 | } 208 | 209 | #ifdef SHA1_UTILITY_FUNCTIONS 210 | bool CSHA1::HashFile(const TCHAR* tszFileName) { 211 | if (tszFileName == NULL) return false; 212 | 213 | FILE* fpIn = _tfopen(tszFileName, _T("rb")); 214 | if (fpIn == NULL) return false; 215 | 216 | UINT_8* pbData = new UINT_8[SHA1_MAX_FILE_BUFFER]; 217 | if (pbData == NULL) { 218 | fclose(fpIn); 219 | return false; 220 | } 221 | 222 | bool bSuccess = true; 223 | while (true) { 224 | const size_t uRead = fread(pbData, 1, SHA1_MAX_FILE_BUFFER, fpIn); 225 | 226 | if (uRead > 0) Update(pbData, static_cast(uRead)); 227 | 228 | if (uRead < SHA1_MAX_FILE_BUFFER) { 229 | if (feof(fpIn) == 0) bSuccess = false; 230 | break; 231 | } 232 | } 233 | 234 | fclose(fpIn); 235 | delete[] pbData; 236 | return bSuccess; 237 | } 238 | #endif 239 | 240 | void CSHA1::Final() { 241 | UINT_32 i; 242 | 243 | UINT_8 pbFinalCount[8]; 244 | for (i = 0; i < 8; ++i) 245 | pbFinalCount[i] = static_cast((m_count[((i >= 4) ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF); // Endian independent 246 | 247 | Update((UINT_8*)"\200", 1); 248 | 249 | while ((m_count[0] & 504) != 448) Update((UINT_8*)"\0", 1); 250 | 251 | Update(pbFinalCount, 8); // Cause a Transform() 252 | 253 | for (i = 0; i < 20; ++i) m_digest[i] = static_cast((m_state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF); 254 | 255 | // Wipe variables for security reasons 256 | #ifdef SHA1_WIPE_VARIABLES 257 | memset(m_buffer, 0, 64); 258 | memset(m_state, 0, 20); 259 | memset(m_count, 0, 8); 260 | memset(pbFinalCount, 0, 8); 261 | Transform(m_state, m_buffer); 262 | #endif 263 | } 264 | 265 | #ifdef SHA1_UTILITY_FUNCTIONS 266 | bool CSHA1::ReportHash(TCHAR* tszReport, REPORT_TYPE rtReportType) const { 267 | if (tszReport == NULL) return false; 268 | 269 | TCHAR tszTemp[16]; 270 | 271 | if ((rtReportType == REPORT_HEX) || (rtReportType == REPORT_HEX_SHORT)) { 272 | _sntprintf(tszTemp, 15, _T("%02X"), m_digest[0]); 273 | _tcscpy(tszReport, tszTemp); 274 | 275 | const TCHAR* lpFmt = ((rtReportType == REPORT_HEX) ? _T(" %02X") : _T("%02X")); 276 | for (size_t i = 1; i < 20; ++i) { 277 | _sntprintf(tszTemp, 15, lpFmt, m_digest[i]); 278 | _tcscat(tszReport, tszTemp); 279 | } 280 | } else if (rtReportType == REPORT_DIGIT) { 281 | _sntprintf(tszTemp, 15, _T("%u"), m_digest[0]); 282 | _tcscpy(tszReport, tszTemp); 283 | 284 | for (size_t i = 1; i < 20; ++i) { 285 | _sntprintf(tszTemp, 15, _T(" %u"), m_digest[i]); 286 | _tcscat(tszReport, tszTemp); 287 | } 288 | } else 289 | return false; 290 | 291 | return true; 292 | } 293 | #endif 294 | 295 | #ifdef SHA1_STL_FUNCTIONS 296 | bool CSHA1::ReportHashStl(std::basic_string& strOut, REPORT_TYPE rtReportType) const { 297 | TCHAR tszOut[84]; 298 | const bool bResult = ReportHash(tszOut, rtReportType); 299 | if (bResult) strOut = tszOut; 300 | return bResult; 301 | } 302 | #endif 303 | 304 | bool CSHA1::GetHash(UINT_8* pbDest20) const { 305 | if (pbDest20 == NULL) return false; 306 | memcpy(pbDest20, m_digest, 20); 307 | return true; 308 | } 309 | 310 | #pragma warning(pop) 311 | -------------------------------------------------------------------------------- /TestFTP/sha1/SHA1.h: -------------------------------------------------------------------------------- 1 | /* 2 | 100% free public domain implementation of the SHA-1 algorithm 3 | by Dominik Reichl 4 | Web: http://www.dominik-reichl.de/ 5 | 6 | Version 2.1 - 2012-06-19 7 | - Deconstructor (resetting internal variables) is now only 8 | implemented if SHA1_WIPE_VARIABLES is defined (which is the 9 | default). 10 | - Renamed inclusion guard to contain a GUID. 11 | - Demo application is now using C++/STL objects and functions. 12 | - Unicode build of the demo application now outputs the hashes of both 13 | the ANSI and Unicode representations of strings. 14 | - Various other demo application improvements. 15 | 16 | Version 2.0 - 2012-06-14 17 | - Added 'limits.h' include. 18 | - Renamed inclusion guard and macros for compliancy (names beginning 19 | with an underscore are reserved). 20 | 21 | Version 1.9 - 2011-11-10 22 | - Added Unicode test vectors. 23 | - Improved support for hashing files using the HashFile method that 24 | are larger than 4 GB. 25 | - Improved file hashing performance (by using a larger buffer). 26 | - Disabled unnecessary compiler warnings. 27 | - Internal variables are now private. 28 | 29 | Version 1.8 - 2009-03-16 30 | - Converted project files to Visual Studio 2008 format. 31 | - Added Unicode support for HashFile utility method. 32 | - Added support for hashing files using the HashFile method that are 33 | larger than 2 GB. 34 | - HashFile now returns an error code instead of copying an error 35 | message into the output buffer. 36 | - GetHash now returns an error code and validates the input parameter. 37 | - Added ReportHashStl STL utility method. 38 | - Added REPORT_HEX_SHORT reporting mode. 39 | - Improved Linux compatibility of test program. 40 | 41 | Version 1.7 - 2006-12-21 42 | - Fixed buffer underrun warning that appeared when compiling with 43 | Borland C Builder (thanks to Rex Bloom and Tim Gallagher for the 44 | patch). 45 | - Breaking change: ReportHash writes the final hash to the start 46 | of the buffer, i.e. it's not appending it to the string anymore. 47 | - Made some function parameters const. 48 | - Added Visual Studio 2005 project files to demo project. 49 | 50 | Version 1.6 - 2005-02-07 (thanks to Howard Kapustein for patches) 51 | - You can set the endianness in your files, no need to modify the 52 | header file of the CSHA1 class anymore. 53 | - Aligned data support. 54 | - Made support/compilation of the utility functions (ReportHash and 55 | HashFile) optional (useful when bytes count, for example in embedded 56 | environments). 57 | 58 | Version 1.5 - 2005-01-01 59 | - 64-bit compiler compatibility added. 60 | - Made variable wiping optional (define SHA1_WIPE_VARIABLES). 61 | - Removed unnecessary variable initializations. 62 | - ROL32 improvement for the Microsoft compiler (using _rotl). 63 | 64 | Version 1.4 - 2004-07-22 65 | - CSHA1 now compiles fine with GCC 3.3 under Mac OS X (thanks to Larry 66 | Hastings). 67 | 68 | Version 1.3 - 2003-08-17 69 | - Fixed a small memory bug and made a buffer array a class member to 70 | ensure correct working when using multiple CSHA1 class instances at 71 | one time. 72 | 73 | Version 1.2 - 2002-11-16 74 | - Borlands C++ compiler seems to have problems with string addition 75 | using sprintf. Fixed the bug which caused the digest report function 76 | not to work properly. CSHA1 is now Borland compatible. 77 | 78 | Version 1.1 - 2002-10-11 79 | - Removed two unnecessary header file includes and changed BOOL to 80 | bool. Fixed some minor bugs in the web page contents. 81 | 82 | Version 1.0 - 2002-06-20 83 | - First official release. 84 | 85 | ================ Test Vectors ================ 86 | 87 | SHA1("abc" in ANSI) = 88 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 89 | SHA1("abc" in Unicode LE) = 90 | 9F04F41A 84851416 2050E3D6 8C1A7ABB 441DC2B5 91 | 92 | SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 93 | in ANSI) = 94 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 95 | SHA1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 96 | in Unicode LE) = 97 | 51D7D876 9AC72C40 9C5B0E3F 69C60ADC 9A039014 98 | 99 | SHA1(A million repetitions of "a" in ANSI) = 100 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 101 | SHA1(A million repetitions of "a" in Unicode LE) = 102 | C4609560 A108A0C6 26AA7F2B 38A65566 739353C5 103 | */ 104 | 105 | #ifndef SHA1_H_A545E61D43E9404E8D736869AB3CBFE7 106 | #define SHA1_H_A545E61D43E9404E8D736869AB3CBFE7 107 | 108 | #if !defined(SHA1_UTILITY_FUNCTIONS) && !defined(SHA1_NO_UTILITY_FUNCTIONS) 109 | #define SHA1_UTILITY_FUNCTIONS 110 | #endif 111 | 112 | #if !defined(SHA1_STL_FUNCTIONS) && !defined(SHA1_NO_STL_FUNCTIONS) 113 | #define SHA1_STL_FUNCTIONS 114 | #if !defined(SHA1_UTILITY_FUNCTIONS) 115 | #error STL functions require SHA1_UTILITY_FUNCTIONS. 116 | #endif 117 | #endif 118 | 119 | #include 120 | #include 121 | 122 | #include 123 | 124 | #ifdef SHA1_UTILITY_FUNCTIONS 125 | #include 126 | #include 127 | #endif 128 | 129 | #ifdef SHA1_STL_FUNCTIONS 130 | #include 131 | #endif 132 | 133 | #ifdef _MSC_VER 134 | #include 135 | #endif 136 | 137 | // You can define the endian mode in your files without modifying the SHA-1 138 | // source files. Just #define SHA1_LITTLE_ENDIAN or #define SHA1_BIG_ENDIAN 139 | // in your files, before including the SHA1.h header file. If you don't 140 | // define anything, the class defaults to little endian. 141 | #if !defined(SHA1_LITTLE_ENDIAN) && !defined(SHA1_BIG_ENDIAN) 142 | #define SHA1_LITTLE_ENDIAN 143 | #endif 144 | 145 | // If you want variable wiping, #define SHA1_WIPE_VARIABLES, if not, 146 | // #define SHA1_NO_WIPE_VARIABLES. If you don't define anything, it 147 | // defaults to wiping. 148 | #if !defined(SHA1_WIPE_VARIABLES) && !defined(SHA1_NO_WIPE_VARIABLES) 149 | #define SHA1_WIPE_VARIABLES 150 | #endif 151 | 152 | #if defined(SHA1_HAS_TCHAR) 153 | #include 154 | #else 155 | #if (defined _MSC_VER && defined MD5Calculator_UNICODE) 156 | #error WARNING UNICODE 157 | #include 158 | #else 159 | #ifndef TCHAR 160 | #define TCHAR char 161 | #endif 162 | #ifndef _T 163 | #define _T(__x) (__x) 164 | #define _tmain main 165 | #define _tprintf printf 166 | #define _getts gets 167 | #define _tcslen strlen 168 | #define _tfopen fopen 169 | #define _tcscpy strcpy 170 | #define _tcscat strcat 171 | #define _sntprintf snprintf 172 | #endif 173 | #endif 174 | #endif 175 | 176 | #if defined(_MSC_VER) && _MSC_VER < 1900 177 | 178 | #define snprintf c99_snprintf 179 | #define vsnprintf c99_vsnprintf 180 | 181 | __inline int c99_vsnprintf(char* outBuf, size_t size, const char* format, va_list ap) { 182 | int count = -1; 183 | 184 | if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); 185 | if (count == -1) count = _vscprintf(format, ap); 186 | 187 | return count; 188 | } 189 | 190 | __inline int c99_snprintf(char* outBuf, size_t size, const char* format, ...) { 191 | int count; 192 | va_list ap; 193 | 194 | va_start(ap, format); 195 | count = c99_vsnprintf(outBuf, size, format, ap); 196 | va_end(ap); 197 | 198 | return count; 199 | } 200 | 201 | #endif 202 | 203 | /////////////////////////////////////////////////////////////////////////// 204 | // Define variable types 205 | 206 | #ifndef UINT_8 207 | #ifdef _MSC_VER // Compiling with Microsoft compiler 208 | #define UINT_8 unsigned __int8 209 | #else // !_MSC_VER 210 | #define UINT_8 unsigned char 211 | #endif // _MSC_VER 212 | #endif 213 | 214 | #ifndef UINT_32 215 | #ifdef _MSC_VER // Compiling with Microsoft compiler 216 | #define UINT_32 unsigned __int32 217 | #else // !_MSC_VER 218 | #if (ULONG_MAX == 0xFFFFFFFFUL) 219 | #define UINT_32 unsigned long 220 | #else 221 | #define UINT_32 unsigned int 222 | #endif 223 | #endif // _MSC_VER 224 | #endif // UINT_32 225 | 226 | #ifndef INT_64 227 | #ifdef _MSC_VER // Compiling with Microsoft compiler 228 | #define INT_64 __int64 229 | #else // !_MSC_VER 230 | #define INT_64 long long 231 | #endif // _MSC_VER 232 | #endif // INT_64 233 | 234 | #ifndef UINT_64 235 | #ifdef _MSC_VER // Compiling with Microsoft compiler 236 | #define UINT_64 unsigned __int64 237 | #else // !_MSC_VER 238 | #define UINT_64 unsigned long long 239 | #endif // _MSC_VER 240 | #endif // UINT_64 241 | 242 | /////////////////////////////////////////////////////////////////////////// 243 | // Declare SHA-1 workspace 244 | 245 | typedef union { 246 | UINT_8 c[64]; 247 | UINT_32 l[16]; 248 | } SHA1_WORKSPACE_BLOCK; 249 | 250 | class CSHA1 { 251 | public: 252 | #ifdef SHA1_UTILITY_FUNCTIONS 253 | // Different formats for ReportHash(Stl) 254 | enum REPORT_TYPE { REPORT_HEX = 0, REPORT_DIGIT = 1, REPORT_HEX_SHORT = 2 }; 255 | #endif 256 | 257 | // Constructor and destructor 258 | CSHA1(); 259 | 260 | #ifdef SHA1_WIPE_VARIABLES 261 | ~CSHA1(); 262 | #endif 263 | 264 | void Reset(); 265 | 266 | // Hash in binary data and strings 267 | void Update(const UINT_8* pbData, UINT_32 uLen); 268 | 269 | #ifdef SHA1_UTILITY_FUNCTIONS 270 | // Hash in file contents 271 | bool HashFile(const TCHAR* tszFileName); 272 | #endif 273 | 274 | // Finalize hash; call it before using ReportHash(Stl) 275 | void Final(); 276 | 277 | #ifdef SHA1_UTILITY_FUNCTIONS 278 | bool ReportHash(TCHAR* tszReport, REPORT_TYPE rtReportType = REPORT_HEX) const; 279 | #endif 280 | 281 | #ifdef SHA1_STL_FUNCTIONS 282 | bool ReportHashStl(std::basic_string& strOut, REPORT_TYPE rtReportType = REPORT_HEX) const; 283 | #endif 284 | 285 | // Get the raw message digest (20 bytes) 286 | bool GetHash(UINT_8* pbDest20) const; 287 | 288 | private: 289 | // Private SHA-1 transformation 290 | void Transform(UINT_32* pState, const UINT_8* pBuffer); 291 | 292 | // Member variables 293 | UINT_32 m_state[5]; 294 | UINT_32 m_count[2]; 295 | UINT_32 m_reserved0[1]; // Memory alignment padding 296 | UINT_8 m_buffer[64]; 297 | UINT_8 m_digest[20]; 298 | UINT_32 m_reserved1[3]; // Memory alignment padding 299 | 300 | UINT_8 m_workspace[64]; 301 | SHA1_WORKSPACE_BLOCK* m_block; // SHA1 pointer to the byte array above 302 | }; 303 | 304 | #endif // SHA1_H_A545E61D43E9404E8D736869AB3CBFE7 305 | -------------------------------------------------------------------------------- /TestFTP/simpleini/ConvertUTF.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2004 Unicode, Inc. 3 | * 4 | * Disclaimer 5 | * 6 | * This source code is provided as is by Unicode, Inc. No claims are 7 | * made as to fitness for any particular purpose. No warranties of any 8 | * kind are expressed or implied. The recipient agrees to determine 9 | * applicability of information provided. If this file has been 10 | * purchased on magnetic or optical media from Unicode, Inc., the 11 | * sole remedy for any claim will be exchange of defective media 12 | * within 90 days of receipt. 13 | * 14 | * Limitations on Rights to Redistribute This Code 15 | * 16 | * Unicode, Inc. hereby grants the right to freely use the information 17 | * supplied in this file in the creation of products supporting the 18 | * Unicode Standard, and to make copies of this file in any form 19 | * for internal or external distribution as long as this notice 20 | * remains attached. 21 | */ 22 | 23 | /* --------------------------------------------------------------------- 24 | 25 | Conversions between UTF32, UTF-16, and UTF-8. Source code file. 26 | Author: Mark E. Davis, 1994. 27 | Rev History: Rick McGowan, fixes & updates May 2001. 28 | Sept 2001: fixed const & error conditions per 29 | mods suggested by S. Parent & A. Lillich. 30 | June 2002: Tim Dodd added detection and handling of incomplete 31 | source sequences, enhanced error detection, added casts 32 | to eliminate compiler warnings. 33 | July 2003: slight mods to back out aggressive FFFE detection. 34 | Jan 2004: updated switches in from-UTF8 conversions. 35 | Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. 36 | 37 | See the header file "ConvertUTF.h" for complete documentation. 38 | 39 | ------------------------------------------------------------------------ */ 40 | 41 | 42 | #include "ConvertUTF.h" 43 | #ifdef CVTUTF_DEBUG 44 | #include 45 | #endif 46 | 47 | static const int halfShift = 10; /* used for shifting by 10 bits */ 48 | 49 | static const UTF32 halfBase = 0x0010000UL; 50 | static const UTF32 halfMask = 0x3FFUL; 51 | 52 | #define UNI_SUR_HIGH_START (UTF32)0xD800 53 | #define UNI_SUR_HIGH_END (UTF32)0xDBFF 54 | #define UNI_SUR_LOW_START (UTF32)0xDC00 55 | #define UNI_SUR_LOW_END (UTF32)0xDFFF 56 | #define false 0 57 | #define true 1 58 | 59 | /* --------------------------------------------------------------------- */ 60 | 61 | ConversionResult ConvertUTF32toUTF16 ( 62 | const UTF32** sourceStart, const UTF32* sourceEnd, 63 | UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { 64 | ConversionResult result = conversionOK; 65 | const UTF32* source = *sourceStart; 66 | UTF16* target = *targetStart; 67 | while (source < sourceEnd) { 68 | UTF32 ch; 69 | if (target >= targetEnd) { 70 | result = targetExhausted; break; 71 | } 72 | ch = *source++; 73 | if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ 74 | /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ 75 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 76 | if (flags == strictConversion) { 77 | --source; /* return to the illegal value itself */ 78 | result = sourceIllegal; 79 | break; 80 | } else { 81 | *target++ = UNI_REPLACEMENT_CHAR; 82 | } 83 | } else { 84 | *target++ = (UTF16)ch; /* normal case */ 85 | } 86 | } else if (ch > UNI_MAX_LEGAL_UTF32) { 87 | if (flags == strictConversion) { 88 | result = sourceIllegal; 89 | } else { 90 | *target++ = UNI_REPLACEMENT_CHAR; 91 | } 92 | } else { 93 | /* target is a character in range 0xFFFF - 0x10FFFF. */ 94 | if (target + 1 >= targetEnd) { 95 | --source; /* Back up source pointer! */ 96 | result = targetExhausted; break; 97 | } 98 | ch -= halfBase; 99 | *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); 100 | *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); 101 | } 102 | } 103 | *sourceStart = source; 104 | *targetStart = target; 105 | return result; 106 | } 107 | 108 | /* --------------------------------------------------------------------- */ 109 | 110 | ConversionResult ConvertUTF16toUTF32 ( 111 | const UTF16** sourceStart, const UTF16* sourceEnd, 112 | UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { 113 | ConversionResult result = conversionOK; 114 | const UTF16* source = *sourceStart; 115 | UTF32* target = *targetStart; 116 | UTF32 ch, ch2; 117 | while (source < sourceEnd) { 118 | const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ 119 | ch = *source++; 120 | /* If we have a surrogate pair, convert to UTF32 first. */ 121 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { 122 | /* If the 16 bits following the high surrogate are in the source buffer... */ 123 | if (source < sourceEnd) { 124 | ch2 = *source; 125 | /* If it's a low surrogate, convert to UTF32. */ 126 | if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { 127 | ch = ((ch - UNI_SUR_HIGH_START) << halfShift) 128 | + (ch2 - UNI_SUR_LOW_START) + halfBase; 129 | ++source; 130 | } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ 131 | --source; /* return to the illegal value itself */ 132 | result = sourceIllegal; 133 | break; 134 | } 135 | } else { /* We don't have the 16 bits following the high surrogate. */ 136 | --source; /* return to the high surrogate */ 137 | result = sourceExhausted; 138 | break; 139 | } 140 | } else if (flags == strictConversion) { 141 | /* UTF-16 surrogate values are illegal in UTF-32 */ 142 | if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { 143 | --source; /* return to the illegal value itself */ 144 | result = sourceIllegal; 145 | break; 146 | } 147 | } 148 | if (target >= targetEnd) { 149 | source = oldSource; /* Back up source pointer! */ 150 | result = targetExhausted; break; 151 | } 152 | *target++ = ch; 153 | } 154 | *sourceStart = source; 155 | *targetStart = target; 156 | #ifdef CVTUTF_DEBUG 157 | if (result == sourceIllegal) { 158 | fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); 159 | fflush(stderr); 160 | } 161 | #endif 162 | return result; 163 | } 164 | 165 | /* --------------------------------------------------------------------- */ 166 | 167 | /* 168 | * Index into the table below with the first byte of a UTF-8 sequence to 169 | * get the number of trailing bytes that are supposed to follow it. 170 | * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is 171 | * left as-is for anyone who may want to do such conversion, which was 172 | * allowed in earlier algorithms. 173 | */ 174 | static const char trailingBytesForUTF8[256] = { 175 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 176 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 177 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 178 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 179 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 180 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 181 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 182 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 183 | }; 184 | 185 | /* 186 | * Magic values subtracted from a buffer value during UTF8 conversion. 187 | * This table contains as many values as there might be trailing bytes 188 | * in a UTF-8 sequence. 189 | */ 190 | static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 191 | 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; 192 | 193 | /* 194 | * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed 195 | * into the first byte, depending on how many bytes follow. There are 196 | * as many entries in this table as there are UTF-8 sequence types. 197 | * (I.e., one byte sequence, two byte... etc.). Remember that sequencs 198 | * for *legal* UTF-8 will be 4 or fewer bytes total. 199 | */ 200 | static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; 201 | 202 | /* --------------------------------------------------------------------- */ 203 | 204 | /* The interface converts a whole buffer to avoid function-call overhead. 205 | * Constants have been gathered. Loops & conditionals have been removed as 206 | * much as possible for efficiency, in favor of drop-through switches. 207 | * (See "Note A" at the bottom of the file for equivalent code.) 208 | * If your compiler supports it, the "isLegalUTF8" call can be turned 209 | * into an inline function. 210 | */ 211 | 212 | /* --------------------------------------------------------------------- */ 213 | 214 | ConversionResult ConvertUTF16toUTF8 ( 215 | const UTF16** sourceStart, const UTF16* sourceEnd, 216 | UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { 217 | ConversionResult result = conversionOK; 218 | const UTF16* source = *sourceStart; 219 | UTF8* target = *targetStart; 220 | while (source < sourceEnd) { 221 | UTF32 ch; 222 | unsigned short bytesToWrite = 0; 223 | const UTF32 byteMask = 0xBF; 224 | const UTF32 byteMark = 0x80; 225 | const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ 226 | ch = *source++; 227 | /* If we have a surrogate pair, convert to UTF32 first. */ 228 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { 229 | /* If the 16 bits following the high surrogate are in the source buffer... */ 230 | if (source < sourceEnd) { 231 | UTF32 ch2 = *source; 232 | /* If it's a low surrogate, convert to UTF32. */ 233 | if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { 234 | ch = ((ch - UNI_SUR_HIGH_START) << halfShift) 235 | + (ch2 - UNI_SUR_LOW_START) + halfBase; 236 | ++source; 237 | } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ 238 | --source; /* return to the illegal value itself */ 239 | result = sourceIllegal; 240 | break; 241 | } 242 | } else { /* We don't have the 16 bits following the high surrogate. */ 243 | --source; /* return to the high surrogate */ 244 | result = sourceExhausted; 245 | break; 246 | } 247 | } else if (flags == strictConversion) { 248 | /* UTF-16 surrogate values are illegal in UTF-32 */ 249 | if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { 250 | --source; /* return to the illegal value itself */ 251 | result = sourceIllegal; 252 | break; 253 | } 254 | } 255 | /* Figure out how many bytes the result will require */ 256 | if (ch < (UTF32)0x80) { bytesToWrite = 1; 257 | } else if (ch < (UTF32)0x800) { bytesToWrite = 2; 258 | } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; 259 | } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; 260 | } else { bytesToWrite = 3; 261 | ch = UNI_REPLACEMENT_CHAR; 262 | } 263 | 264 | target += bytesToWrite; 265 | if (target > targetEnd) { 266 | source = oldSource; /* Back up source pointer! */ 267 | target -= bytesToWrite; result = targetExhausted; break; 268 | } 269 | switch (bytesToWrite) { /* note: everything falls through. */ 270 | case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 271 | case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 272 | case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 273 | case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); 274 | } 275 | target += bytesToWrite; 276 | } 277 | *sourceStart = source; 278 | *targetStart = target; 279 | return result; 280 | } 281 | 282 | /* --------------------------------------------------------------------- */ 283 | 284 | /* 285 | * Utility routine to tell whether a sequence of bytes is legal UTF-8. 286 | * This must be called with the length pre-determined by the first byte. 287 | * If not calling this from ConvertUTF8to*, then the length can be set by: 288 | * length = trailingBytesForUTF8[*source]+1; 289 | * and the sequence is illegal right away if there aren't that many bytes 290 | * available. 291 | * If presented with a length > 4, this returns false. The Unicode 292 | * definition of UTF-8 goes up to 4-byte sequences. 293 | */ 294 | 295 | static Boolean isLegalUTF8(const UTF8 *source, int length) { 296 | UTF8 a; 297 | const UTF8 *srcptr = source+length; 298 | switch (length) { 299 | default: return false; 300 | /* Everything else falls through when "true"... */ 301 | case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; 302 | case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; 303 | case 2: if ((a = (*--srcptr)) > 0xBF) return false; 304 | 305 | switch (*source) { 306 | /* no fall-through in this inner switch */ 307 | case 0xE0: if (a < 0xA0) return false; break; 308 | case 0xED: if (a > 0x9F) return false; break; 309 | case 0xF0: if (a < 0x90) return false; break; 310 | case 0xF4: if (a > 0x8F) return false; break; 311 | default: if (a < 0x80) return false; 312 | } 313 | 314 | case 1: if (*source >= 0x80 && *source < 0xC2) return false; 315 | } 316 | if (*source > 0xF4) return false; 317 | return true; 318 | } 319 | 320 | /* --------------------------------------------------------------------- */ 321 | 322 | /* 323 | * Exported function to return whether a UTF-8 sequence is legal or not. 324 | * This is not used here; it's just exported. 325 | */ 326 | Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { 327 | int length = trailingBytesForUTF8[*source]+1; 328 | if (source+length > sourceEnd) { 329 | return false; 330 | } 331 | return isLegalUTF8(source, length); 332 | } 333 | 334 | /* --------------------------------------------------------------------- */ 335 | 336 | ConversionResult ConvertUTF8toUTF16 ( 337 | const UTF8** sourceStart, const UTF8* sourceEnd, 338 | UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { 339 | ConversionResult result = conversionOK; 340 | const UTF8* source = *sourceStart; 341 | UTF16* target = *targetStart; 342 | while (source < sourceEnd) { 343 | UTF32 ch = 0; 344 | unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; 345 | if (source + extraBytesToRead >= sourceEnd) { 346 | result = sourceExhausted; break; 347 | } 348 | /* Do this check whether lenient or strict */ 349 | if (! isLegalUTF8(source, extraBytesToRead+1)) { 350 | result = sourceIllegal; 351 | break; 352 | } 353 | /* 354 | * The cases all fall through. See "Note A" below. 355 | */ 356 | switch (extraBytesToRead) { 357 | case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ 358 | case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ 359 | case 3: ch += *source++; ch <<= 6; 360 | case 2: ch += *source++; ch <<= 6; 361 | case 1: ch += *source++; ch <<= 6; 362 | case 0: ch += *source++; 363 | } 364 | ch -= offsetsFromUTF8[extraBytesToRead]; 365 | 366 | if (target >= targetEnd) { 367 | source -= (extraBytesToRead+1); /* Back up source pointer! */ 368 | result = targetExhausted; break; 369 | } 370 | if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ 371 | /* UTF-16 surrogate values are illegal in UTF-32 */ 372 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 373 | if (flags == strictConversion) { 374 | source -= (extraBytesToRead+1); /* return to the illegal value itself */ 375 | result = sourceIllegal; 376 | break; 377 | } else { 378 | *target++ = UNI_REPLACEMENT_CHAR; 379 | } 380 | } else { 381 | *target++ = (UTF16)ch; /* normal case */ 382 | } 383 | } else if (ch > UNI_MAX_UTF16) { 384 | if (flags == strictConversion) { 385 | result = sourceIllegal; 386 | source -= (extraBytesToRead+1); /* return to the start */ 387 | break; /* Bail out; shouldn't continue */ 388 | } else { 389 | *target++ = UNI_REPLACEMENT_CHAR; 390 | } 391 | } else { 392 | /* target is a character in range 0xFFFF - 0x10FFFF. */ 393 | if (target + 1 >= targetEnd) { 394 | source -= (extraBytesToRead+1); /* Back up source pointer! */ 395 | result = targetExhausted; break; 396 | } 397 | ch -= halfBase; 398 | *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); 399 | *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); 400 | } 401 | } 402 | *sourceStart = source; 403 | *targetStart = target; 404 | return result; 405 | } 406 | 407 | /* --------------------------------------------------------------------- */ 408 | 409 | ConversionResult ConvertUTF32toUTF8 ( 410 | const UTF32** sourceStart, const UTF32* sourceEnd, 411 | UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { 412 | ConversionResult result = conversionOK; 413 | const UTF32* source = *sourceStart; 414 | UTF8* target = *targetStart; 415 | while (source < sourceEnd) { 416 | UTF32 ch; 417 | unsigned short bytesToWrite = 0; 418 | const UTF32 byteMask = 0xBF; 419 | const UTF32 byteMark = 0x80; 420 | ch = *source++; 421 | if (flags == strictConversion ) { 422 | /* UTF-16 surrogate values are illegal in UTF-32 */ 423 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 424 | --source; /* return to the illegal value itself */ 425 | result = sourceIllegal; 426 | break; 427 | } 428 | } 429 | /* 430 | * Figure out how many bytes the result will require. Turn any 431 | * illegally large UTF32 things (> Plane 17) into replacement chars. 432 | */ 433 | if (ch < (UTF32)0x80) { bytesToWrite = 1; 434 | } else if (ch < (UTF32)0x800) { bytesToWrite = 2; 435 | } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; 436 | } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; 437 | } else { bytesToWrite = 3; 438 | ch = UNI_REPLACEMENT_CHAR; 439 | result = sourceIllegal; 440 | } 441 | 442 | target += bytesToWrite; 443 | if (target > targetEnd) { 444 | --source; /* Back up source pointer! */ 445 | target -= bytesToWrite; result = targetExhausted; break; 446 | } 447 | switch (bytesToWrite) { /* note: everything falls through. */ 448 | case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 449 | case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 450 | case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; 451 | case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); 452 | } 453 | target += bytesToWrite; 454 | } 455 | *sourceStart = source; 456 | *targetStart = target; 457 | return result; 458 | } 459 | 460 | /* --------------------------------------------------------------------- */ 461 | 462 | ConversionResult ConvertUTF8toUTF32 ( 463 | const UTF8** sourceStart, const UTF8* sourceEnd, 464 | UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { 465 | ConversionResult result = conversionOK; 466 | const UTF8* source = *sourceStart; 467 | UTF32* target = *targetStart; 468 | while (source < sourceEnd) { 469 | UTF32 ch = 0; 470 | unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; 471 | if (source + extraBytesToRead >= sourceEnd) { 472 | result = sourceExhausted; break; 473 | } 474 | /* Do this check whether lenient or strict */ 475 | if (! isLegalUTF8(source, extraBytesToRead+1)) { 476 | result = sourceIllegal; 477 | break; 478 | } 479 | /* 480 | * The cases all fall through. See "Note A" below. 481 | */ 482 | switch (extraBytesToRead) { 483 | case 5: ch += *source++; ch <<= 6; 484 | case 4: ch += *source++; ch <<= 6; 485 | case 3: ch += *source++; ch <<= 6; 486 | case 2: ch += *source++; ch <<= 6; 487 | case 1: ch += *source++; ch <<= 6; 488 | case 0: ch += *source++; 489 | } 490 | ch -= offsetsFromUTF8[extraBytesToRead]; 491 | 492 | if (target >= targetEnd) { 493 | source -= (extraBytesToRead+1); /* Back up the source pointer! */ 494 | result = targetExhausted; break; 495 | } 496 | if (ch <= UNI_MAX_LEGAL_UTF32) { 497 | /* 498 | * UTF-16 surrogate values are illegal in UTF-32, and anything 499 | * over Plane 17 (> 0x10FFFF) is illegal. 500 | */ 501 | if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { 502 | if (flags == strictConversion) { 503 | source -= (extraBytesToRead+1); /* return to the illegal value itself */ 504 | result = sourceIllegal; 505 | break; 506 | } else { 507 | *target++ = UNI_REPLACEMENT_CHAR; 508 | } 509 | } else { 510 | *target++ = ch; 511 | } 512 | } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ 513 | result = sourceIllegal; 514 | *target++ = UNI_REPLACEMENT_CHAR; 515 | } 516 | } 517 | *sourceStart = source; 518 | *targetStart = target; 519 | return result; 520 | } 521 | 522 | /* --------------------------------------------------------------------- 523 | 524 | Note A. 525 | The fall-through switches in UTF-8 reading code save a 526 | temp variable, some decrements & conditionals. The switches 527 | are equivalent to the following loop: 528 | { 529 | int tmpBytesToRead = extraBytesToRead+1; 530 | do { 531 | ch += *source++; 532 | --tmpBytesToRead; 533 | if (tmpBytesToRead) ch <<= 6; 534 | } while (tmpBytesToRead > 0); 535 | } 536 | In UTF-8 writing code, the switches on "bytesToWrite" are 537 | similarly unrolled loops. 538 | 539 | --------------------------------------------------------------------- */ 540 | -------------------------------------------------------------------------------- /TestFTP/simpleini/ConvertUTF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2004 Unicode, Inc. 3 | * 4 | * Disclaimer 5 | * 6 | * This source code is provided as is by Unicode, Inc. No claims are 7 | * made as to fitness for any particular purpose. No warranties of any 8 | * kind are expressed or implied. The recipient agrees to determine 9 | * applicability of information provided. If this file has been 10 | * purchased on magnetic or optical media from Unicode, Inc., the 11 | * sole remedy for any claim will be exchange of defective media 12 | * within 90 days of receipt. 13 | * 14 | * Limitations on Rights to Redistribute This Code 15 | * 16 | * Unicode, Inc. hereby grants the right to freely use the information 17 | * supplied in this file in the creation of products supporting the 18 | * Unicode Standard, and to make copies of this file in any form 19 | * for internal or external distribution as long as this notice 20 | * remains attached. 21 | */ 22 | 23 | /* --------------------------------------------------------------------- 24 | 25 | Conversions between UTF32, UTF-16, and UTF-8. Header file. 26 | 27 | Several funtions are included here, forming a complete set of 28 | conversions between the three formats. UTF-7 is not included 29 | here, but is handled in a separate source file. 30 | 31 | Each of these routines takes pointers to input buffers and output 32 | buffers. The input buffers are const. 33 | 34 | Each routine converts the text between *sourceStart and sourceEnd, 35 | putting the result into the buffer between *targetStart and 36 | targetEnd. Note: the end pointers are *after* the last item: e.g. 37 | *(sourceEnd - 1) is the last item. 38 | 39 | The return result indicates whether the conversion was successful, 40 | and if not, whether the problem was in the source or target buffers. 41 | (Only the first encountered problem is indicated.) 42 | 43 | After the conversion, *sourceStart and *targetStart are both 44 | updated to point to the end of last text successfully converted in 45 | the respective buffers. 46 | 47 | Input parameters: 48 | sourceStart - pointer to a pointer to the source buffer. 49 | The contents of this are modified on return so that 50 | it points at the next thing to be converted. 51 | targetStart - similarly, pointer to pointer to the target buffer. 52 | sourceEnd, targetEnd - respectively pointers to the ends of the 53 | two buffers, for overflow checking only. 54 | 55 | These conversion functions take a ConversionFlags argument. When this 56 | flag is set to strict, both irregular sequences and isolated surrogates 57 | will cause an error. When the flag is set to lenient, both irregular 58 | sequences and isolated surrogates are converted. 59 | 60 | Whether the flag is strict or lenient, all illegal sequences will cause 61 | an error return. This includes sequences such as: , , 62 | or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code 63 | must check for illegal sequences. 64 | 65 | When the flag is set to lenient, characters over 0x10FFFF are converted 66 | to the replacement character; otherwise (when the flag is set to strict) 67 | they constitute an error. 68 | 69 | Output parameters: 70 | The value "sourceIllegal" is returned from some routines if the input 71 | sequence is malformed. When "sourceIllegal" is returned, the source 72 | value will point to the illegal value that caused the problem. E.g., 73 | in UTF-8 when a sequence is malformed, it points to the start of the 74 | malformed sequence. 75 | 76 | Author: Mark E. Davis, 1994. 77 | Rev History: Rick McGowan, fixes & updates May 2001. 78 | Fixes & updates, Sept 2001. 79 | 80 | ------------------------------------------------------------------------ */ 81 | 82 | /* --------------------------------------------------------------------- 83 | The following 4 definitions are compiler-specific. 84 | The C standard does not guarantee that wchar_t has at least 85 | 16 bits, so wchar_t is no less portable than unsigned short! 86 | All should be unsigned values to avoid sign extension during 87 | bit mask & shift operations. 88 | ------------------------------------------------------------------------ */ 89 | 90 | typedef unsigned int UTF32; /* at least 32 bits */ 91 | typedef unsigned short UTF16; /* at least 16 bits */ 92 | typedef unsigned char UTF8; /* typically 8 bits */ 93 | typedef unsigned char Boolean; /* 0 or 1 */ 94 | 95 | /* Some fundamental constants */ 96 | #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD 97 | #define UNI_MAX_BMP (UTF32)0x0000FFFF 98 | #define UNI_MAX_UTF16 (UTF32)0x0010FFFF 99 | #define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF 100 | #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF 101 | 102 | typedef enum { 103 | conversionOK, /* conversion successful */ 104 | sourceExhausted, /* partial character in source, but hit end */ 105 | targetExhausted, /* insuff. room in target for conversion */ 106 | sourceIllegal /* source sequence is illegal/malformed */ 107 | } ConversionResult; 108 | 109 | typedef enum { strictConversion = 0, lenientConversion } ConversionFlags; 110 | 111 | /* This is for C++ and does no harm in C */ 112 | #ifdef __cplusplus 113 | extern "C" { 114 | #endif 115 | 116 | ConversionResult ConvertUTF8toUTF16(const UTF8** sourceStart, const UTF8* sourceEnd, UTF16** targetStart, UTF16* targetEnd, 117 | ConversionFlags flags); 118 | 119 | ConversionResult ConvertUTF16toUTF8(const UTF16** sourceStart, const UTF16* sourceEnd, UTF8** targetStart, UTF8* targetEnd, 120 | ConversionFlags flags); 121 | 122 | ConversionResult ConvertUTF8toUTF32(const UTF8** sourceStart, const UTF8* sourceEnd, UTF32** targetStart, UTF32* targetEnd, 123 | ConversionFlags flags); 124 | 125 | ConversionResult ConvertUTF32toUTF8(const UTF32** sourceStart, const UTF32* sourceEnd, UTF8** targetStart, UTF8* targetEnd, 126 | ConversionFlags flags); 127 | 128 | ConversionResult ConvertUTF16toUTF32(const UTF16** sourceStart, const UTF16* sourceEnd, UTF32** targetStart, UTF32* targetEnd, 129 | ConversionFlags flags); 130 | 131 | ConversionResult ConvertUTF32toUTF16(const UTF32** sourceStart, const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, 132 | ConversionFlags flags); 133 | 134 | Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd); 135 | 136 | #ifdef __cplusplus 137 | } 138 | #endif 139 | 140 | /* --------------------------------------------------------------------- */ 141 | -------------------------------------------------------------------------------- /TestFTP/simpleini/LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2006-2013 Brodie Thiesfield 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /TestFTP/simpleini/README.md: -------------------------------------------------------------------------------- 1 | simpleini 2 | ========= 3 | 4 | A cross-platform library that provides a simple API to read and write INI-style configuration files. It supports data files in ASCII, MBCS and Unicode. It is designed explicitly to be portable to any platform and has been tested on Windows, WinCE and Linux. Released as open-source and free using the MIT licence. 5 | 6 | # Feature Summary 7 | 8 | - MIT Licence allows free use in all software (including GPL and commercial) 9 | - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) 10 | - loading and saving of INI-style configuration files 11 | - configuration files can have any newline format on all platforms 12 | - liberal acceptance of file format 13 | * key/values with no section 14 | * removal of whitespace around sections, keys and values 15 | - support for multi-line values (values with embedded newline characters) 16 | - optional support for multiple keys with the same name 17 | - optional case-insensitive sections and keys (for ASCII characters only) 18 | - saves files with sections and keys in the same order as they were loaded 19 | - preserves comments on the file, section and keys where possible. 20 | - supports both char or wchar_t programming interfaces 21 | - supports both MBCS (system locale) and UTF-8 file encodings 22 | - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file 23 | - support for non-ASCII characters in section, keys, values and comments 24 | - support for non-standard character types or file encodings via user-written converter classes 25 | - support for adding/modifying values programmatically 26 | - compiles cleanly in the following compilers: 27 | * Windows/VC6 (warning level 3) 28 | * Windows/VC.NET 2003 (warning level 4) 29 | * Windows/VC 2005 (warning level 4) 30 | * Linux/gcc (-Wall) 31 | * Windows/MinGW GCC 32 | 33 | # Documentation 34 | 35 | Full documentation of the interface is available in doxygen format. 36 | 37 | # Examples 38 | 39 | These snippets are included with the distribution in the file snippets.cpp. 40 | 41 | ### SIMPLE USAGE 42 | 43 | ```c++ 44 | CSimpleIniA ini; 45 | ini.SetUnicode(); 46 | ini.LoadFile("myfile.ini"); 47 | const char * pVal = ini.GetValue("section", "key", "default"); 48 | ini.SetValue("section", "key", "newvalue"); 49 | ``` 50 | 51 | ### LOADING DATA 52 | 53 | ```c++ 54 | // load from a data file 55 | CSimpleIniA ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine); 56 | SI_Error rc = ini.LoadFile(a_pszFile); 57 | if (rc < 0) return false; 58 | 59 | // load from a string 60 | std::string strData; 61 | rc = ini.LoadData(strData.c_str(), strData.size()); 62 | if (rc < 0) return false; 63 | ``` 64 | 65 | ### GETTING SECTIONS AND KEYS 66 | 67 | ```c++ 68 | // get all sections 69 | CSimpleIniA::TNamesDepend sections; 70 | ini.GetAllSections(sections); 71 | 72 | // get all keys in a section 73 | CSimpleIniA::TNamesDepend keys; 74 | ini.GetAllKeys("section-name", keys); 75 | ``` 76 | 77 | ### GETTING VALUES 78 | 79 | ```c++ 80 | // get the value of a key 81 | const char * pszValue = ini.GetValue("section-name", 82 | "key-name", NULL /*default*/); 83 | 84 | // get the value of a key which may have multiple 85 | // values. If bHasMultipleValues is true, then just 86 | // one value has been returned 87 | bool bHasMultipleValues; 88 | pszValue = ini.GetValue("section-name", "key-name", 89 | NULL /*default*/, &bHasMultipleValues); 90 | 91 | // get all values of a key with multiple values 92 | CSimpleIniA::TNamesDepend values; 93 | ini.GetAllValues("section-name", "key-name", values); 94 | 95 | // sort the values into the original load order 96 | values.sort(CSimpleIniA::Entry::LoadOrder()); 97 | 98 | // output all of the items 99 | CSimpleIniA::TNamesDepend::const_iterator i; 100 | for (i = values.begin(); i != values.end(); ++i) { 101 | printf("key-name = '%s'\n", i->pItem); 102 | } 103 | ``` 104 | 105 | ### MODIFYING DATA 106 | 107 | ```c++ 108 | // adding a new section 109 | rc = ini.SetValue("new-section", NULL, NULL); 110 | if (rc < 0) return false; 111 | printf("section: %s\n", rc == SI_INSERTED ? 112 | "inserted" : "updated"); 113 | 114 | // adding a new key ("new-section" will be added 115 | // automatically if it doesn't already exist) 116 | rc = ini.SetValue("new-section", "new-key", "value"); 117 | if (rc < 0) return false; 118 | printf("key: %s\n", rc == SI_INSERTED ? 119 | "inserted" : "updated"); 120 | 121 | // changing the value of a key 122 | rc = ini.SetValue("section", "key", "updated-value"); 123 | if (rc < 0) return false; 124 | printf("key: %s\n", rc == SI_INSERTED ? 125 | "inserted" : "updated"); 126 | ``` 127 | 128 | ### DELETING DATA 129 | 130 | ```c++ 131 | // deleting a key from a section. Optionally the entire 132 | // section may be deleted if it is now empty. 133 | ini.Delete("section-name", "key-name", 134 | true /*delete the section if empty*/); 135 | 136 | // deleting an entire section and all keys in it 137 | ini.Delete("section-name", NULL); 138 | ``` 139 | 140 | ### SAVING DATA 141 | 142 | ```c++ 143 | // save the data to a string 144 | rc = ini.Save(strData); 145 | if (rc < 0) return false; 146 | 147 | // save the data back to the file 148 | rc = ini.SaveFile(a_pszFile); 149 | if (rc < 0) return false; 150 | ``` 151 | -------------------------------------------------------------------------------- /TestFTP/simpleini/SimpleIni.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 8.00 2 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleIni", "SimpleIni.vcproj", "{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}" 3 | ProjectSection(ProjectDependencies) = postProject 4 | EndProjectSection 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfiguration) = preSolution 8 | Debug = Debug 9 | Debug Unicode = Debug Unicode 10 | Release = Release 11 | Release Unicode = Release Unicode 12 | EndGlobalSection 13 | GlobalSection(ProjectDependencies) = postSolution 14 | EndGlobalSection 15 | GlobalSection(ProjectConfiguration) = postSolution 16 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug.ActiveCfg = Debug|Win32 17 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug.Build.0 = Debug|Win32 18 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug Unicode.ActiveCfg = Debug Unicode|Win32 19 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug Unicode.Build.0 = Debug Unicode|Win32 20 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release.ActiveCfg = Release|Win32 21 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release.Build.0 = Release|Win32 22 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release Unicode.ActiveCfg = Release Unicode|Win32 23 | {C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release Unicode.Build.0 = Release Unicode|Win32 24 | EndGlobalSection 25 | GlobalSection(ExtensibilityGlobals) = postSolution 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityAddIns) = postSolution 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /TestFTP/simpleini/SimpleIni.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 12 | 13 | 14 | 20 | 31 | 33 | 41 | 43 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 62 | 68 | 76 | 78 | 87 | 89 | 91 | 93 | 95 | 97 | 99 | 101 | 103 | 105 | 107 | 108 | 114 | 125 | 127 | 135 | 137 | 139 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 156 | 162 | 170 | 172 | 181 | 183 | 185 | 187 | 189 | 191 | 193 | 195 | 197 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 210 | 212 | 213 | 215 | 216 | 218 | 219 | 220 | 224 | 226 | 227 | 228 | 232 | 234 | 235 | 237 | 238 | 240 | 241 | 243 | 244 | 245 | 248 | 250 | 251 | 253 | 254 | 255 | 258 | 260 | 262 | 272 | 273 | 274 | 275 | 278 | 280 | 281 | 283 | 284 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /TestFTP/simpleini/ini.syn: -------------------------------------------------------------------------------- 1 | ; Syntax file for ini files - contributed by Brodie Thiesfield 2 | ; 3 | ; Suggested Colors: 4 | ; Comments (;#) Comments, Comments 2 Green 5 | ; Sections Characters Red 6 | ; Values Strings Blue 7 | 8 | C=1 9 | 10 | [Syntax] 11 | Namespace1 = 6 12 | IgnoreCase = Yes 13 | KeyWordLength = 1 14 | BracketChars = 15 | OperatorChars = 16 | PreprocStart = 17 | SyntaxStart = 18 | SyntaxEnd = 19 | HexPrefix = 20 | CommentStart = 21 | CommentEnd = 22 | CommentStartAlt = 23 | CommentEndAlt = 24 | SingleComment = # 25 | SingleCommentCol = 26 | SingleCommentAlt = ; 27 | SingleCommentColAlt = 28 | SingleCommentEsc = 29 | StringsSpanLines = No 30 | StringStart = 31 | StringEnd = 32 | StringAlt = = 33 | StringEsc = 34 | CharStart = [ 35 | CharEnd = ] 36 | CharEsc = 37 | -------------------------------------------------------------------------------- /TestFTP/simpleini/package.cmd: -------------------------------------------------------------------------------- 1 | set VERSION=4.15 2 | 3 | set SEVENZIP="C:\Program Files\7-Zip\7z.exe" 4 | 5 | FOR /F "tokens=*" %%G IN ('DIR /AD /B /S Debug*') DO ( 6 | DEL /S /Q "%%G" 7 | RD "%%G" 8 | ) 9 | FOR /F "tokens=*" %%G IN ('DIR /AD /B /S Release*') DO ( 10 | DEL /S /Q "%%G" 11 | RD "%%G" 12 | ) 13 | DEL /Q "SimpleIni.ncb" 14 | ATTRIB -H "SimpleIni.suo" 15 | DEL /Q "SimpleIni.suo" 16 | DEL /Q "SimpleIni.opt" 17 | DEL /Q testsi-out*.ini 18 | DEL /Q test1-blah.ini 19 | DEL /Q test1-output.ini 20 | START "Generate documentation" /WAIT "C:\Program Files (x86)\doxygen\bin\doxygen.exe" SimpleIni.doxy 21 | cd .. 22 | del simpleini-%VERSION%.zip 23 | %SEVENZIP% a -tzip -r- -x!simpleini\.svn simpleini-%VERSION%.zip simpleini\* 24 | del simpleini-doc.zip 25 | %SEVENZIP% a -tzip -r simpleini-doc.zip simpleini-doc\* 26 | cd simpleini 27 | -------------------------------------------------------------------------------- /TestFTP/simpleini/simpleini.dsp: -------------------------------------------------------------------------------- 1 | # Microsoft Developer Studio Project File - Name="simpleini" - Package Owner=<4> 2 | # Microsoft Developer Studio Generated Build File, Format Version 6.00 3 | # ** DO NOT EDIT ** 4 | 5 | # TARGTYPE "Win32 (x86) Console Application" 0x0103 6 | 7 | CFG=simpleini - Win32 Debug Unicode 8 | !MESSAGE This is not a valid makefile. To build this project using NMAKE, 9 | !MESSAGE use the Export Makefile command and run 10 | !MESSAGE 11 | !MESSAGE NMAKE /f "simpleini.mak". 12 | !MESSAGE 13 | !MESSAGE You can specify a configuration when running NMAKE 14 | !MESSAGE by defining the macro CFG on the command line. For example: 15 | !MESSAGE 16 | !MESSAGE NMAKE /f "simpleini.mak" CFG="simpleini - Win32 Debug Unicode" 17 | !MESSAGE 18 | !MESSAGE Possible choices for configuration are: 19 | !MESSAGE 20 | !MESSAGE "simpleini - Win32 Release" (based on "Win32 (x86) Console Application") 21 | !MESSAGE "simpleini - Win32 Debug" (based on "Win32 (x86) Console Application") 22 | !MESSAGE "simpleini - Win32 Debug Unicode" (based on "Win32 (x86) Console Application") 23 | !MESSAGE "simpleini - Win32 Release Unicode" (based on "Win32 (x86) Console Application") 24 | !MESSAGE 25 | 26 | # Begin Project 27 | # PROP AllowPerConfigDependencies 0 28 | # PROP Scc_ProjName "" 29 | # PROP Scc_LocalPath "" 30 | CPP=cl.exe 31 | RSC=rc.exe 32 | 33 | !IF "$(CFG)" == "simpleini - Win32 Release" 34 | 35 | # PROP BASE Use_MFC 0 36 | # PROP BASE Use_Debug_Libraries 0 37 | # PROP BASE Output_Dir "Release" 38 | # PROP BASE Intermediate_Dir "Release" 39 | # PROP BASE Target_Dir "" 40 | # PROP Use_MFC 0 41 | # PROP Use_Debug_Libraries 0 42 | # PROP Output_Dir "Release" 43 | # PROP Intermediate_Dir "Release" 44 | # PROP Ignore_Export_Lib 0 45 | # PROP Target_Dir "" 46 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 47 | # ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c 48 | # SUBTRACT CPP /YX 49 | # ADD BASE RSC /l 0xc09 /d "NDEBUG" 50 | # ADD RSC /l 0xc09 /d "NDEBUG" 51 | BSC32=bscmake.exe 52 | # ADD BASE BSC32 /nologo 53 | # ADD BSC32 /nologo 54 | LINK32=link.exe 55 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 56 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"Release/testsi.exe" 57 | 58 | !ELSEIF "$(CFG)" == "simpleini - Win32 Debug" 59 | 60 | # PROP BASE Use_MFC 0 61 | # PROP BASE Use_Debug_Libraries 1 62 | # PROP BASE Output_Dir "simpleini___Win32_Debug" 63 | # PROP BASE Intermediate_Dir "simpleini___Win32_Debug" 64 | # PROP BASE Target_Dir "" 65 | # PROP Use_MFC 0 66 | # PROP Use_Debug_Libraries 1 67 | # PROP Output_Dir "Debug" 68 | # PROP Intermediate_Dir "Debug" 69 | # PROP Ignore_Export_Lib 0 70 | # PROP Target_Dir "" 71 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 72 | # ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c 73 | # SUBTRACT CPP /Fr /YX 74 | # ADD BASE RSC /l 0xc09 /d "_DEBUG" 75 | # ADD RSC /l 0xc09 /d "_DEBUG" 76 | BSC32=bscmake.exe 77 | # ADD BASE BSC32 /nologo 78 | # ADD BSC32 /nologo 79 | LINK32=link.exe 80 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 81 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"Debug/testsi.exe" /pdbtype:sept 82 | 83 | !ELSEIF "$(CFG)" == "simpleini - Win32 Debug Unicode" 84 | 85 | # PROP BASE Use_MFC 0 86 | # PROP BASE Use_Debug_Libraries 1 87 | # PROP BASE Output_Dir "Debug Unicode" 88 | # PROP BASE Intermediate_Dir "Debug Unicode" 89 | # PROP BASE Target_Dir "" 90 | # PROP Use_MFC 0 91 | # PROP Use_Debug_Libraries 1 92 | # PROP Output_Dir "Debug Unicode" 93 | # PROP Intermediate_Dir "Debug Unicode" 94 | # PROP Ignore_Export_Lib 0 95 | # PROP Target_Dir "" 96 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c 97 | # ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "SI_USE_GENERIC_CONVERSION" /FD /GZ /c 98 | # SUBTRACT CPP /YX 99 | # ADD BASE RSC /l 0xc09 /d "_DEBUG" 100 | # ADD RSC /l 0xc09 /d "_DEBUG" 101 | BSC32=bscmake.exe 102 | # ADD BASE BSC32 /nologo 103 | # ADD BSC32 /nologo 104 | LINK32=link.exe 105 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept 106 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"Debug Unicode/testsi.exe" /pdbtype:sept 107 | 108 | !ELSEIF "$(CFG)" == "simpleini - Win32 Release Unicode" 109 | 110 | # PROP BASE Use_MFC 0 111 | # PROP BASE Use_Debug_Libraries 0 112 | # PROP BASE Output_Dir "Release Unicode" 113 | # PROP BASE Intermediate_Dir "Release Unicode" 114 | # PROP BASE Target_Dir "" 115 | # PROP Use_MFC 0 116 | # PROP Use_Debug_Libraries 0 117 | # PROP Output_Dir "Release Unicode" 118 | # PROP Intermediate_Dir "Release Unicode" 119 | # PROP Ignore_Export_Lib 0 120 | # PROP Target_Dir "" 121 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c 122 | # ADD CPP /nologo /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "SI_USE_GENERIC_CONVERSION" /FD /c 123 | # SUBTRACT CPP /YX 124 | # ADD BASE RSC /l 0xc09 /d "NDEBUG" 125 | # ADD RSC /l 0xc09 /d "NDEBUG" 126 | BSC32=bscmake.exe 127 | # ADD BASE BSC32 /nologo 128 | # ADD BSC32 /nologo 129 | LINK32=link.exe 130 | # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 131 | # ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"Release Unicode/testsi.exe" 132 | 133 | !ENDIF 134 | 135 | # Begin Target 136 | 137 | # Name "simpleini - Win32 Release" 138 | # Name "simpleini - Win32 Debug" 139 | # Name "simpleini - Win32 Debug Unicode" 140 | # Name "simpleini - Win32 Release Unicode" 141 | # Begin Group "Source Files" 142 | 143 | # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" 144 | # Begin Source File 145 | 146 | SOURCE=.\snippets.cpp 147 | # End Source File 148 | # Begin Source File 149 | 150 | SOURCE=.\test1.cpp 151 | # End Source File 152 | # Begin Source File 153 | 154 | SOURCE=.\testsi.cpp 155 | # End Source File 156 | # End Group 157 | # Begin Group "Library Files" 158 | 159 | # PROP Default_Filter "" 160 | # Begin Source File 161 | 162 | SOURCE=.\SimpleIni.h 163 | # End Source File 164 | # End Group 165 | # Begin Group "Generic Files" 166 | 167 | # PROP Default_Filter "" 168 | # Begin Source File 169 | 170 | SOURCE=.\ConvertUTF.c 171 | # End Source File 172 | # Begin Source File 173 | 174 | SOURCE=.\ConvertUTF.h 175 | # End Source File 176 | # End Group 177 | # End Target 178 | # End Project 179 | -------------------------------------------------------------------------------- /TestFTP/simpleini/simpleini.dsw: -------------------------------------------------------------------------------- 1 | Microsoft Developer Studio Workspace File, Format Version 6.00 2 | # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! 3 | 4 | ############################################################################### 5 | 6 | Project: "simpleini"=.\simpleini.dsp - Package Owner=<4> 7 | 8 | Package=<5> 9 | {{{ 10 | }}} 11 | 12 | Package=<4> 13 | {{{ 14 | }}} 15 | 16 | ############################################################################### 17 | 18 | Global: 19 | 20 | Package=<5> 21 | {{{ 22 | }}} 23 | 24 | Package=<3> 25 | {{{ 26 | }}} 27 | 28 | ############################################################################### 29 | 30 | -------------------------------------------------------------------------------- /TestFTP/simpleini/snippets.cpp: -------------------------------------------------------------------------------- 1 | // File: snippets.cpp 2 | // Library: SimpleIni 3 | // Author: Brodie Thiesfield 4 | // Source: http://code.jellycan.com/simpleini/ 5 | // 6 | // Snippets that are used on the website 7 | 8 | #ifdef _WIN32 9 | #pragma warning(disable : 4786) 10 | #endif 11 | 12 | #ifndef _WIN32 13 | #include 14 | #endif 15 | #include 16 | 17 | #define SI_SUPPORT_IOSTREAMS 18 | #include "SimpleIni.h" 19 | 20 | bool snippets(const char* a_pszFile, bool a_bIsUtf8, bool a_bUseMultiKey, bool a_bUseMultiLine) { 21 | // LOADING DATA 22 | 23 | // load from a data file 24 | CSimpleIniA ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine); 25 | SI_Error rc = ini.LoadFile(a_pszFile); 26 | if (rc < 0) return false; 27 | 28 | // load from a string 29 | std::string strData; 30 | rc = ini.LoadData(strData.c_str(), strData.size()); 31 | if (rc < 0) return false; 32 | 33 | // GETTING SECTIONS AND KEYS 34 | 35 | // get all sections 36 | CSimpleIniA::TNamesDepend sections; 37 | ini.GetAllSections(sections); 38 | 39 | // get all keys in a section 40 | CSimpleIniA::TNamesDepend keys; 41 | ini.GetAllKeys("section-name", keys); 42 | 43 | // GETTING VALUES 44 | 45 | // get the value of a key 46 | const char* pszValue = ini.GetValue("section-name", "key-name", NULL /*default*/); 47 | 48 | // get the value of a key which may have multiple 49 | // values. If bHasMultipleValues is true, then just 50 | // one value has been returned 51 | bool bHasMultipleValues; 52 | pszValue = ini.GetValue("section-name", "key-name", NULL /*default*/, &bHasMultipleValues); 53 | 54 | // get all values of a key with multiple values 55 | CSimpleIniA::TNamesDepend values; 56 | ini.GetAllValues("section-name", "key-name", values); 57 | 58 | // sort the values into the original load order 59 | #if defined(_MSC_VER) && _MSC_VER <= 1200 60 | /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ 61 | values.sort(); 62 | #else 63 | values.sort(CSimpleIniA::Entry::LoadOrder()); 64 | #endif 65 | 66 | // output all of the items 67 | CSimpleIniA::TNamesDepend::const_iterator i; 68 | for (i = values.begin(); i != values.end(); ++i) { 69 | printf("key-name = '%s'\n", i->pItem); 70 | } 71 | 72 | // MODIFYING DATA 73 | 74 | // adding a new section 75 | rc = ini.SetValue("new-section", NULL, NULL); 76 | if (rc < 0) return false; 77 | printf("section: %s\n", rc == SI_INSERTED ? "inserted" : "updated"); 78 | 79 | // adding a new key ("new-section" will be added 80 | // automatically if it doesn't already exist. 81 | rc = ini.SetValue("new-section", "new-key", "value"); 82 | if (rc < 0) return false; 83 | printf("key: %s\n", rc == SI_INSERTED ? "inserted" : "updated"); 84 | 85 | // changing the value of a key 86 | rc = ini.SetValue("section", "key", "updated-value"); 87 | if (rc < 0) return false; 88 | printf("key: %s\n", rc == SI_INSERTED ? "inserted" : "updated"); 89 | 90 | // DELETING DATA 91 | 92 | // deleting a key with a value from a section. 93 | // Optionally the entire section may be deleted if 94 | // it is now empty. 95 | ini.DeleteValue("section-name", "key-name", "value", true /*delete the section if empty*/); 96 | 97 | // deleting a key with any value from a section. 98 | ini.Delete("section-name", "key-name", true /*delete the section if empty*/); 99 | 100 | // deleting an entire section and all keys in it 101 | ini.Delete("section-name", NULL); 102 | 103 | // SAVING DATA 104 | 105 | // save the data to a string 106 | rc = ini.Save(strData); 107 | if (rc < 0) return false; 108 | 109 | // save the data back to the file 110 | rc = ini.SaveFile(a_pszFile); 111 | if (rc < 0) return false; 112 | 113 | return true; 114 | } 115 | -------------------------------------------------------------------------------- /TestFTP/simpleini/test.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | Debug\testsi.exe -u -m -l test1-input.ini > test1-blah.ini 4 | fc test1-expected.ini test1-output.ini 5 | if errorlevel 1 goto error 6 | 7 | "Debug Unicode\testsi.exe" -u -m -l test1-input.ini > test1-blah.ini 8 | fc test1-expected.ini test1-output.ini 9 | if errorlevel 1 goto error 10 | 11 | Release\testsi.exe -u -m -l test1-input.ini > test1-blah.ini 12 | fc test1-expected.ini test1-output.ini 13 | if errorlevel 1 goto error 14 | 15 | "Release Unicode\testsi.exe" -u -m -l test1-input.ini > test1-blah.ini 16 | fc test1-expected.ini test1-output.ini 17 | if errorlevel 1 goto error 18 | 19 | exit /b 0 20 | 21 | :error 22 | echo Failed during test run. Output file doesn't match expected file. 23 | pause 24 | exit /b 1 25 | -------------------------------------------------------------------------------- /TestFTP/simpleini/test1-expected.ini: -------------------------------------------------------------------------------- 1 | ; testsi-UTF8-std.ini : standard UTF-8 test file for SimpleIni automated testing 2 | ; 3 | ; The number after a section or key is the order that it is defined in this file 4 | ; to make it easier to see if it has been written out correctly. This file should 5 | ; be loaded with Unicode / MultiKey / MultiLine turned on. 6 | 7 | 8 | 9 | ; This comment should be joined on to the one below it about the key 10 | ; with no section. 11 | 12 | ; Key with no section 13 | lonely-key = nosection 14 | another = nosection either 15 | 16 | ; This key has no value 17 | empty = 18 | 19 | 20 | ; This should be joined with the comment below about japanese. 21 | ; Another line which will be un-indented. 22 | 23 | ; This is a section of keys showing the word Japanese in different syllabies. 24 | [ordered-1] 25 | a-1 = blah 26 | 27 | ; this is in kanji 28 | japanese-2 = 日本語 29 | 30 | ; this is in hiragana 31 | japanese-3 = にほんご 32 | 33 | ; this is in katakana 34 | japanese-4 = ニホンゴ 35 | 36 | ; this is in romaji 37 | japanese-5 = nihongo 38 | 39 | ; kanji as the key 40 | 日本語-6 = japanese 41 | 42 | 43 | [multi-2] 44 | 45 | ; value a 46 | test = a 47 | 48 | ; value b 49 | test = b 50 | 51 | ; value c 52 | test = c 53 | 54 | ; value d 55 | test = d 56 | 57 | 58 | [multiline-3] 59 | 60 | ; This is obviously a multi-line entry 61 | multiline-1 = << 4 | // Source: http://code.jellycan.com/simpleini/ 5 | // 6 | // Automated testing for SimpleIni streams 7 | 8 | #ifdef _WIN32 9 | #pragma warning(disable : 4786) 10 | #endif 11 | 12 | #ifdef _WIN32 13 | #include 14 | #define DELETE_FILE DeleteFileA 15 | #else 16 | #include 17 | #define DELETE_FILE unlink 18 | #endif 19 | #include 20 | 21 | #define SI_SUPPORT_IOSTREAMS 22 | #include "SimpleIni.h" 23 | 24 | class Test { 25 | std::string m_strTest; 26 | 27 | public: 28 | Test(const char* a_pszName) : m_strTest(a_pszName) { printf("%s: test starting\n", m_strTest.c_str()); } 29 | 30 | bool Success() { 31 | printf("%s: test succeeded\n", m_strTest.c_str()); 32 | return false; 33 | } 34 | 35 | bool Failure(const char* pszReason) { 36 | printf("%s: test FAILED (%s)\n", m_strTest.c_str(), pszReason); 37 | return false; 38 | } 39 | }; 40 | 41 | bool FileComparisonTest(const char* a_pszFile1, const char* a_pszFile2) { 42 | // ensure that the two files are the same 43 | try { 44 | std::string strFile1, strFile2; 45 | 46 | char szBuf[1024]; 47 | FILE* fp = NULL; 48 | 49 | #if __STDC_WANT_SECURE_LIB__ 50 | fopen_s(&fp, a_pszFile1, "rb"); 51 | #else 52 | fp = fopen(a_pszFile1, "rb"); 53 | #endif 54 | if (!fp) throw false; 55 | while (!feof(fp)) { 56 | size_t n = fread(szBuf, 1, sizeof(szBuf), fp); 57 | strFile1.append(szBuf, n); 58 | } 59 | fclose(fp); 60 | 61 | fp = NULL; 62 | #if __STDC_WANT_SECURE_LIB__ 63 | fopen_s(&fp, a_pszFile2, "rb"); 64 | #else 65 | fp = fopen(a_pszFile2, "rb"); 66 | #endif 67 | if (!fp) throw false; 68 | while (!feof(fp)) { 69 | size_t n = fread(szBuf, 1, sizeof(szBuf), fp); 70 | strFile2.append(szBuf, n); 71 | } 72 | fclose(fp); 73 | 74 | if (strFile1 != strFile2) throw false; 75 | } catch (...) { 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | bool FileLoadTest(const char* a_pszFile1, const char* a_pszFile2) { 83 | // ensure that the two files load into simpleini the same 84 | CSimpleIniA ini(true, true, true); 85 | bool b; 86 | try { 87 | ini.Reset(); 88 | if (ini.LoadFile(a_pszFile1) < 0) throw "Load failed for file 1"; 89 | if (ini.SaveFile("test1.ini") < 0) throw "Save failed for file 1"; 90 | 91 | ini.Reset(); 92 | if (ini.LoadFile(a_pszFile2) < 0) throw "Load failed for file 2"; 93 | if (ini.SaveFile("test2.ini") < 0) throw "Save failed for file 2"; 94 | 95 | b = FileComparisonTest("test1.ini", "test2.ini"); 96 | DELETE_FILE("test1.ini"); 97 | DELETE_FILE("test2.ini"); 98 | if (!b) throw "File comparison failed in FileLoadTest"; 99 | } catch (...) { 100 | return false; 101 | } 102 | 103 | return true; 104 | } 105 | 106 | bool TestStreams() { 107 | const char* rgszTestFile[3] = {"test1-input.ini", "test1-output.ini", "test1-expected.ini"}; 108 | 109 | Test oTest("TestStreams"); 110 | 111 | CSimpleIniW ini; 112 | ini.SetUnicode(true); 113 | ini.SetMultiKey(true); 114 | ini.SetMultiLine(true); 115 | 116 | // load the file 117 | try { 118 | std::ifstream instream; 119 | instream.open(rgszTestFile[0], std::ifstream::in | std::ifstream::binary); 120 | if (ini.LoadData(instream) < 0) throw false; 121 | instream.close(); 122 | } catch (...) { 123 | return oTest.Failure("Failed to load file"); 124 | } 125 | 126 | // standard contents test 127 | // if (!StandardContentsTest(ini, oTest)) { 128 | // return false; 129 | //} 130 | 131 | // save the file 132 | try { 133 | std::ofstream outfile; 134 | outfile.open(rgszTestFile[1], std::ofstream::out | std::ofstream::binary); 135 | if (ini.Save(outfile, true) < 0) throw false; 136 | outfile.close(); 137 | } catch (...) { 138 | return oTest.Failure("Failed to save file"); 139 | } 140 | 141 | // file comparison test 142 | if (!FileComparisonTest(rgszTestFile[1], rgszTestFile[2])) { 143 | return oTest.Failure("Failed file comparison"); 144 | } 145 | if (!FileLoadTest(rgszTestFile[1], rgszTestFile[2])) { 146 | return oTest.Failure("Failed file load comparison"); 147 | } 148 | 149 | return oTest.Success(); 150 | } 151 | -------------------------------------------------------------------------------- /TestFTP/simpleini/testsi-EUCJP.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embeddedmz/ftpclient-cpp/794fd90e81048452a435ebf57b58195e026c7328/TestFTP/simpleini/testsi-EUCJP.ini -------------------------------------------------------------------------------- /TestFTP/simpleini/testsi-SJIS.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embeddedmz/ftpclient-cpp/794fd90e81048452a435ebf57b58195e026c7328/TestFTP/simpleini/testsi-SJIS.ini -------------------------------------------------------------------------------- /TestFTP/simpleini/testsi-UTF8.ini: -------------------------------------------------------------------------------- 1 | ; test file for SimpleIni 2 | 3 | whitespace = ok 4 | nosection=ok 5 | NOSECTION=still ok 6 | 7 | [standard] 8 | foo=foo1 9 | standard-1=foo 10 | 日本語=ok1 11 | 12 | [Standard] 13 | Foo=foo2 14 | standard-2=foo 15 | 日本語=ok2 16 | 17 | [ Whitespace ] 18 | 19 | a= 20 | 21 | [ whitespace in section name ] 22 | whitespace in key name = whitespace in value name 23 | 24 | ; comments 25 | ; more comments 26 | 27 | invalid 28 | =invalid 29 | ====invalid 30 | 31 | [Japanese] 32 | nihongo = 日本語 33 | 日本語 = 日本語 34 | 35 | [日本語] 36 | nihongo = 日本語 37 | 日本語 = 日本語 38 | 39 | [] 40 | more=no section name 41 | 42 | [MultiLine] 43 | single = This is a single line. 44 | multi = << 4 | // Source: http://code.jellycan.com/simpleini/ 5 | // 6 | // Demo of usage 7 | 8 | #ifdef _WIN32 9 | #pragma warning(disable : 4786) 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #define SI_SUPPORT_IOSTREAMS 17 | #if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE) 18 | #include 19 | #endif 20 | 21 | //#define SI_CONVERT_GENERIC 22 | //#define SI_CONVERT_ICU 23 | //#define SI_CONVERT_WIN32 24 | #include "SimpleIni.h" 25 | 26 | #ifdef SI_CONVERT_ICU 27 | // if converting using ICU then we need the ICU library 28 | #pragma comment(lib, "icuuc.lib") 29 | #endif 30 | 31 | #ifdef _WIN32 32 | #include 33 | #else // !_WIN32 34 | #define TCHAR char 35 | #define _T(x) x 36 | #define _tprintf printf 37 | #define _tmain main 38 | #endif // _WIN32 39 | 40 | static void Test(CSimpleIni &ini) { 41 | const TCHAR *pszSection = 0; 42 | const TCHAR *pItem = 0; 43 | const TCHAR *pszVal = 0; 44 | 45 | // get the value of the key "foo" in section "standard" 46 | bool bHasMulti; 47 | pszVal = ini.GetValue(_T("standard"), _T("foo"), 0, &bHasMulti); 48 | _tprintf(_T("\n-- Value of standard::foo is '%s' (hasMulti = %d)\n"), pszVal ? pszVal : _T("(null)"), bHasMulti); 49 | 50 | // set the value of the key "foo" in section "standard" 51 | ini.SetValue(_T("standard"), _T("foo"), _T("wibble")); 52 | pszVal = ini.GetValue(_T("standard"), _T("foo"), 0, &bHasMulti); 53 | _tprintf(_T("\n-- Value of standard::foo is '%s' (hasMulti = %d)\n"), pszVal ? pszVal : _T("(null)"), bHasMulti); 54 | 55 | // get all values of the key "foo" in section "standard" 56 | CSimpleIni::TNamesDepend values; 57 | if (ini.GetAllValues(_T("standard"), _T("foo"), values)) { 58 | _tprintf(_T("\n-- Values of standard::foo are:\n")); 59 | CSimpleIni::TNamesDepend::const_iterator i = values.begin(); 60 | for (; i != values.end(); ++i) { 61 | pszVal = i->pItem; 62 | _tprintf(_T(" -> '%s'\n"), pszVal); 63 | } 64 | } 65 | 66 | // get the size of the section [standard] 67 | _tprintf(_T("\n-- Number of keys in section [standard] = %d\n"), ini.GetSectionSize(_T("standard"))); 68 | 69 | // delete the key "foo" in section "standard", if it has value "bar" 70 | ini.DeleteValue(_T("standard"), _T("foo"), _T("bar")); 71 | pszVal = ini.GetValue(_T("standard"), _T("foo"), 0); 72 | _tprintf(_T("\n-- Value of standard::foo is now '%s'\n"), pszVal ? pszVal : _T("(null)")); 73 | 74 | // delete the key "foo" in section "standard" 75 | ini.Delete(_T("standard"), _T("foo")); 76 | pszVal = ini.GetValue(_T("standard"), _T("foo"), 0); 77 | _tprintf(_T("\n-- Value of standard::foo is now '%s'\n"), pszVal ? pszVal : _T("(null)")); 78 | 79 | // get the size of the section [standard] 80 | _tprintf(_T("\n-- Number of keys in section [standard] = %d\n"), ini.GetSectionSize(_T("standard"))); 81 | 82 | // get the list of all key names for the section "standard" 83 | _tprintf(_T("\n-- Dumping keys of section: [standard]\n")); 84 | CSimpleIni::TNamesDepend keys; 85 | ini.GetAllKeys(_T("standard"), keys); 86 | 87 | // dump all of the key names 88 | CSimpleIni::TNamesDepend::const_iterator iKey = keys.begin(); 89 | for (; iKey != keys.end(); ++iKey) { 90 | pItem = iKey->pItem; 91 | _tprintf(_T("Key: %s\n"), pItem); 92 | } 93 | 94 | // add a decimal value 95 | ini.SetLongValue(_T("integer"), _T("dec"), 42, NULL, false); 96 | ini.SetLongValue(_T("integer"), _T("hex"), 42, NULL, true); 97 | 98 | // add some bool values 99 | ini.SetBoolValue(_T("bool"), _T("t"), true); 100 | ini.SetBoolValue(_T("bool"), _T("f"), false); 101 | 102 | // get the values back 103 | assert(42 == ini.GetLongValue(_T("integer"), _T("dec"))); 104 | assert(42 == ini.GetLongValue(_T("integer"), _T("hex"))); 105 | assert(true == ini.GetBoolValue(_T("bool"), _T("t"))); 106 | assert(false == ini.GetBoolValue(_T("bool"), _T("f"))); 107 | 108 | // delete the section "standard" 109 | ini.Delete(_T("standard"), NULL); 110 | _tprintf(_T("\n-- Number of keys in section [standard] = %d\n"), ini.GetSectionSize(_T("standard"))); 111 | 112 | // iterate through every section in the file 113 | _tprintf(_T("\n-- Dumping all sections\n")); 114 | CSimpleIni::TNamesDepend sections; 115 | ini.GetAllSections(sections); 116 | CSimpleIni::TNamesDepend::const_iterator iSection = sections.begin(); 117 | for (; iSection != sections.end(); ++iSection) { 118 | pszSection = iSection->pItem; 119 | 120 | // print the section name 121 | printf("\n"); 122 | if (*pszSection) { 123 | _tprintf(_T("[%s]\n"), pszSection); 124 | } 125 | 126 | // if there are keys and values... 127 | const CSimpleIni::TKeyVal *pSectionData = ini.GetSection(pszSection); 128 | if (pSectionData) { 129 | // iterate over all keys and dump the key name and value 130 | CSimpleIni::TKeyVal::const_iterator iKeyVal = pSectionData->begin(); 131 | for (; iKeyVal != pSectionData->end(); ++iKeyVal) { 132 | pItem = iKeyVal->first.pItem; 133 | pszVal = iKeyVal->second; 134 | _tprintf(_T("%s=%s\n"), pItem, pszVal); 135 | } 136 | } 137 | } 138 | } 139 | 140 | #if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE) 141 | static bool TestStreams(const TCHAR *a_pszFile, bool a_bIsUtf8, bool a_bUseMultiKey, bool a_bUseMultiLine) { 142 | // load the file 143 | CSimpleIni ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine); 144 | _tprintf(_T("Loading file: %s\n"), a_pszFile); 145 | std::ifstream instream; 146 | instream.open(a_pszFile, std::ifstream::in | std::ifstream::binary); 147 | SI_Error rc = ini.LoadData(instream); 148 | instream.close(); 149 | if (rc < 0) { 150 | printf("Failed to open file.\n"); 151 | return false; 152 | } 153 | 154 | Test(ini); 155 | 156 | // save the file (simple) 157 | _tprintf(_T("\n-- Saving file to: testsi-out-streams.ini\n")); 158 | std::ofstream outstream; 159 | outstream.open("testsi-out-streams.ini", std::ofstream::out | std::ofstream::binary); 160 | ini.Save(outstream); 161 | outstream.close(); 162 | 163 | return true; 164 | } 165 | #endif // SI_SUPPORT_IOSTREAMS 166 | 167 | static bool TestFile(const TCHAR *a_pszFile, bool a_bIsUtf8, bool a_bUseMultiKey, bool a_bUseMultiLine) { 168 | // load the file 169 | CSimpleIni ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine); 170 | _tprintf(_T("Loading file: %s\n"), a_pszFile); 171 | SI_Error rc = ini.LoadFile(a_pszFile); 172 | if (rc < 0) { 173 | printf("Failed to open file.\n"); 174 | return false; 175 | } 176 | 177 | // run the tests 178 | Test(ini); 179 | 180 | // save the file (simple) 181 | _tprintf(_T("\n-- Saving file to: testsi-out.ini\n")); 182 | ini.SaveFile("testsi-out.ini"); 183 | 184 | // save the file (with comments) 185 | // Note: to save the file and add a comment to the beginning, use 186 | // code such as the following. 187 | _tprintf(_T("\n-- Saving file to: testsi-out-comment.ini\n")); 188 | FILE *fp = NULL; 189 | #if __STDC_WANT_SECURE_LIB__ 190 | fopen_s(&fp, "testsi-out-comment.ini", "wb"); 191 | #else 192 | fp = fopen("testsi-out-comment.ini", "wb"); 193 | #endif 194 | if (fp) { 195 | CSimpleIni::FileWriter writer(fp); 196 | if (a_bIsUtf8) { 197 | writer.Write(SI_UTF8_SIGNATURE); 198 | } 199 | 200 | // add a string to the file in the correct text format 201 | CSimpleIni::Converter convert = ini.GetConverter(); 202 | convert.ConvertToStore(_T("; output from testsi.cpp test program") SI_NEWLINE SI_NEWLINE); 203 | writer.Write(convert.Data()); 204 | 205 | ini.Save(writer, false); 206 | fclose(fp); 207 | } 208 | 209 | return true; 210 | } 211 | 212 | static bool ParseCommandLine(int argc, TCHAR *argv[], const TCHAR *&a_pszFile, bool &a_bIsUtf8, bool &a_bUseMultiKey, 213 | bool &a_bUseMultiLine) { 214 | a_pszFile = 0; 215 | a_bIsUtf8 = false; 216 | a_bUseMultiKey = false; 217 | a_bUseMultiLine = false; 218 | for (--argc; argc > 0; --argc) { 219 | if (argv[argc][0] == '-') { 220 | switch (argv[argc][1]) { 221 | case TCHAR('u'): 222 | a_bIsUtf8 = true; 223 | break; 224 | case TCHAR('m'): 225 | a_bUseMultiKey = true; 226 | break; 227 | case TCHAR('l'): 228 | a_bUseMultiLine = true; 229 | break; 230 | } 231 | } else { 232 | a_pszFile = argv[argc]; 233 | } 234 | } 235 | if (!a_pszFile) { 236 | _tprintf( 237 | _T("Usage: testsi [-u] [-m] [-l] iniFile\n") 238 | _T(" -u Load file as UTF-8 (Default is to use system locale)\n") 239 | _T(" -m Enable multiple keys\n") 240 | _T(" -l Enable multiple line values\n")); 241 | return false; 242 | } 243 | 244 | return true; 245 | } 246 | 247 | extern bool TestStreams(); 248 | 249 | int _tmain(int argc, TCHAR *argv[]) { 250 | setlocale(LC_ALL, ""); 251 | 252 | // start of automated testing... 253 | TestStreams(); 254 | 255 | // parse the command line 256 | const TCHAR *pszFile; 257 | bool bIsUtf8, bUseMultiKey, bUseMultiLine; 258 | if (!ParseCommandLine(argc, argv, pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) { 259 | return 1; 260 | } 261 | 262 | // run the test 263 | if (!TestFile(pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) { 264 | return 1; 265 | } 266 | #if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE) 267 | if (!TestStreams(pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) { 268 | return 1; 269 | } 270 | #endif 271 | 272 | return 0; 273 | } 274 | -------------------------------------------------------------------------------- /TestFTP/template_test_conf.ini: -------------------------------------------------------------------------------- 1 | [tests] 2 | ftp=yes 3 | sftp=no 4 | http-proxy=no 5 | 6 | [local] 7 | curl_logs_folder= 8 | ; only useful if you have compiled with DEBUG_CURL macro 9 | ssl_cert_file= 10 | ssl_key_file= 11 | ssl_key_pwd= 12 | 13 | [ftp] 14 | host=127.0.0.1 15 | port=21 16 | username= 17 | password= 18 | remote_file=info.txt 19 | remote_file_sha1sum=6b1b185eb4c8baccf8ed10bb17b235bd8d9a3c75 20 | ; for the download/info tests 21 | remote_upload_folder=/upload/documents 22 | ; for upload/create dir tests 23 | remote_download_folder=pictures/ 24 | ; for the test TestWildcardedURL to download an entire remote directory 25 | ; with the files and directories 26 | 27 | [sftp] 28 | host= 29 | port=22 30 | username= 31 | password= 32 | remote_file=info.txt 33 | remote_file_sha1sum= 34 | remote_upload_folder= 35 | remote_download_folder= 36 | 37 | [http-proxy] 38 | host=192.168.0.2:8080 39 | host_invalid=127.0.0.1:6666 40 | -------------------------------------------------------------------------------- /TestFTP/test_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "test_utils.h" 2 | 3 | // Test configuration constants (to be loaded from an INI file) 4 | bool FTP_TEST_ENABLED; 5 | bool SFTP_TEST_ENABLED; 6 | bool HTTP_PROXY_TEST_ENABLED; 7 | 8 | std::string CURL_LOG_FOLDER; 9 | 10 | std::string SSL_CERT_FILE; 11 | std::string SSL_KEY_FILE; 12 | std::string SSL_KEY_PWD; 13 | 14 | std::string FTP_SERVER; 15 | unsigned FTP_SERVER_PORT; 16 | std::string FTP_USERNAME; 17 | std::string FTP_PASSWORD; 18 | std::string FTP_REMOTE_FILE; 19 | std::string FTP_REMOTE_FILE_SHA1SUM; 20 | std::string FTP_REMOTE_UPLOAD_FOLDER; 21 | std::string FTP_REMOTE_DOWNLOAD_FOLDER; 22 | 23 | std::string SFTP_SERVER; 24 | unsigned SFTP_SERVER_PORT; 25 | std::string SFTP_USERNAME; 26 | std::string SFTP_PASSWORD; 27 | std::string SFTP_REMOTE_FILE; 28 | std::string SFTP_REMOTE_FILE_SHA1SUM; 29 | std::string SFTP_REMOTE_UPLOAD_FOLDER; 30 | std::string SFTP_REMOTE_DOWNLOAD_FOLDER; 31 | 32 | std::string PROXY_SERVER; 33 | std::string PROXY_SERVER_FAKE; 34 | 35 | std::mutex g_mtxConsoleMutex; 36 | 37 | bool GlobalTestInit(const std::string& strConfFile) { 38 | CSimpleIniA ini; 39 | if (ini.LoadFile(strConfFile.c_str()) != SI_Error::SI_OK) return false; 40 | 41 | std::string strTmp; 42 | strTmp = ini.GetValue("tests", "ftp", ""); 43 | std::transform(strTmp.begin(), strTmp.end(), strTmp.begin(), ::toupper); 44 | FTP_TEST_ENABLED = (strTmp == "YES") ? true : false; 45 | 46 | strTmp = ini.GetValue("tests", "sftp", ""); 47 | std::transform(strTmp.begin(), strTmp.end(), strTmp.begin(), ::toupper); 48 | SFTP_TEST_ENABLED = (strTmp == "YES") ? true : false; 49 | 50 | strTmp = ini.GetValue("tests", "http-proxy", ""); 51 | std::transform(strTmp.begin(), strTmp.end(), strTmp.begin(), ::toupper); 52 | HTTP_PROXY_TEST_ENABLED = (strTmp == "YES") ? true : false; 53 | 54 | // required when a build is generated with the macro DEBUG_CURL 55 | CURL_LOG_FOLDER = ini.GetValue("local", "curl_logs_folder", ""); 56 | 57 | SSL_CERT_FILE = ini.GetValue("local", "ssl_cert_file", ""); 58 | SSL_KEY_FILE = ini.GetValue("local", "ssl_key_file", ""); 59 | SSL_KEY_PWD = ini.GetValue("local", "ssl_key_pwd", ""); 60 | 61 | PROXY_SERVER = ini.GetValue("http-proxy", "host", ""); 62 | PROXY_SERVER_FAKE = ini.GetValue("http-proxy", "host_invalid", ""); 63 | 64 | FTP_SERVER = ini.GetValue("ftp", "host", ""); 65 | FTP_SERVER_PORT = atoi(ini.GetValue("ftp", "port", "0")); 66 | FTP_USERNAME = ini.GetValue("ftp", "username", ""); 67 | FTP_PASSWORD = ini.GetValue("ftp", "password", ""); 68 | FTP_REMOTE_FILE = ini.GetValue("ftp", "remote_file", ""); 69 | FTP_REMOTE_FILE_SHA1SUM = ini.GetValue("ftp", "remote_file_sha1sum", ""); 70 | std::transform(FTP_REMOTE_FILE_SHA1SUM.begin(), FTP_REMOTE_FILE_SHA1SUM.end(), FTP_REMOTE_FILE_SHA1SUM.begin(), ::tolower); 71 | FTP_REMOTE_UPLOAD_FOLDER = ini.GetValue("ftp", "remote_upload_folder", ""); 72 | FTP_REMOTE_DOWNLOAD_FOLDER = ini.GetValue("ftp", "remote_download_folder", ""); 73 | 74 | SFTP_SERVER = ini.GetValue("sftp", "host", ""); 75 | SFTP_SERVER_PORT = atoi(ini.GetValue("sftp", "port", "0")); 76 | SFTP_USERNAME = ini.GetValue("sftp", "username", ""); 77 | SFTP_PASSWORD = ini.GetValue("sftp", "password", ""); 78 | SFTP_REMOTE_FILE = ini.GetValue("sftp", "remote_file", ""); 79 | SFTP_REMOTE_FILE = ini.GetValue("sftp", "remote_file", ""); 80 | SFTP_REMOTE_FILE_SHA1SUM = ini.GetValue("sftp", "remote_file_sha1sum", ""); 81 | std::transform(SFTP_REMOTE_FILE_SHA1SUM.begin(), SFTP_REMOTE_FILE_SHA1SUM.end(), SFTP_REMOTE_FILE_SHA1SUM.begin(), ::tolower); 82 | SFTP_REMOTE_UPLOAD_FOLDER = ini.GetValue("sftp", "remote_upload_folder", ""); 83 | SFTP_REMOTE_DOWNLOAD_FOLDER = ini.GetValue("sftp", "remote_download_folder", ""); 84 | 85 | if (!FTP_REMOTE_UPLOAD_FOLDER.empty() && FTP_REMOTE_UPLOAD_FOLDER.at(FTP_REMOTE_UPLOAD_FOLDER.length() - 1) != '/') { 86 | FTP_REMOTE_UPLOAD_FOLDER += '/'; 87 | } 88 | 89 | if (!FTP_REMOTE_DOWNLOAD_FOLDER.empty()) { 90 | if (FTP_REMOTE_DOWNLOAD_FOLDER.at(FTP_REMOTE_DOWNLOAD_FOLDER.length() - 1) != '/') 91 | FTP_REMOTE_DOWNLOAD_FOLDER += "/*"; 92 | else 93 | FTP_REMOTE_DOWNLOAD_FOLDER += "*"; 94 | } 95 | 96 | if (!SFTP_REMOTE_UPLOAD_FOLDER.empty() && SFTP_REMOTE_UPLOAD_FOLDER.at(SFTP_REMOTE_UPLOAD_FOLDER.length() - 1) != '/') { 97 | SFTP_REMOTE_UPLOAD_FOLDER += '/'; 98 | } 99 | 100 | if (!SFTP_REMOTE_DOWNLOAD_FOLDER.empty()) { 101 | if (SFTP_REMOTE_DOWNLOAD_FOLDER.at(SFTP_REMOTE_DOWNLOAD_FOLDER.length() - 1) != '/') 102 | SFTP_REMOTE_DOWNLOAD_FOLDER += "/*"; 103 | else 104 | SFTP_REMOTE_DOWNLOAD_FOLDER += "*"; 105 | } 106 | 107 | if ((FTP_TEST_ENABLED && (FTP_SERVER.empty() || FTP_SERVER_PORT == 0)) || 108 | (SFTP_TEST_ENABLED && (SFTP_SERVER.empty() || SFTP_SERVER_PORT == 0)) || 109 | (HTTP_PROXY_TEST_ENABLED && (PROXY_SERVER.empty() || PROXY_SERVER_FAKE.empty()))) { 110 | std::clog << "[ERROR] Check your INI file parameters." 111 | " Disable tests that don't have a server/port value." 112 | << std::endl; 113 | return false; 114 | } 115 | 116 | return true; 117 | } 118 | 119 | void GlobalTestCleanUp(void) { return; } 120 | 121 | void TimeStampTest(std::ostringstream& ssTimestamp) { 122 | time_t tRawTime; 123 | tm* tmTimeInfo; 124 | time(&tRawTime); 125 | tmTimeInfo = localtime(&tRawTime); 126 | 127 | ssTimestamp << (tmTimeInfo->tm_year) + 1900 << "/" << tmTimeInfo->tm_mon + 1 << "/" << tmTimeInfo->tm_mday << " at " 128 | << tmTimeInfo->tm_hour << ":" << tmTimeInfo->tm_min << ":" << tmTimeInfo->tm_sec; 129 | } 130 | 131 | // for uplaod prog. callback, just use DL one and inverse download parameters with upload ones... 132 | int TestUPProgressCallback(void* ptr, double dTotalToDownload, double dNowDownloaded, double dTotalToUpload, double dNowUploaded) { 133 | return TestDLProgressCallback(ptr, dTotalToUpload, dNowUploaded, dTotalToDownload, dNowDownloaded); 134 | } 135 | int TestDLProgressCallback(void* ptr, double dTotalToDownload, double dNowDownloaded, double dTotalToUpload, double dNowUploaded) { 136 | // ensure that the file to be downloaded is not empty 137 | // because that would cause a division by zero error later on 138 | if (dTotalToDownload <= 0.0) return 0; 139 | 140 | // how wide you want the progress meter to be 141 | const int iTotalDots = 20; 142 | double dFractionDownloaded = dNowDownloaded / dTotalToDownload; 143 | // part of the progressmeter that's already "full" 144 | int iDots = static_cast(round(dFractionDownloaded * iTotalDots)); 145 | 146 | // create the "meter" 147 | int iDot = 0; 148 | std::cout << static_cast(dFractionDownloaded * 100) << "% ["; 149 | 150 | // part that's full already 151 | for (; iDot < iDots; iDot++) std::cout << "="; 152 | 153 | // remaining part (spaces) 154 | for (; iDot < iTotalDots; iDot++) std::cout << " "; 155 | 156 | // and back to line begin - do not forget the fflush to avoid output buffering problems! 157 | std::cout << "] \r" << std::flush; 158 | 159 | // if you don't return 0, the transfer will be aborted - see the documentation 160 | return 0; 161 | } 162 | 163 | long GetGMTOffset() { 164 | time_t now = time(nullptr); 165 | 166 | struct tm gm = *gmtime(&now); 167 | time_t gmt = mktime(&gm); 168 | 169 | struct tm loc = *localtime(&now); 170 | time_t local = mktime(&loc); 171 | 172 | return static_cast(difftime(local, gmt)); 173 | } 174 | 175 | bool GetFileTime(const char* const& pszFilePath, time_t& tLastModificationTime) { 176 | FILE* pFile = fopen(pszFilePath, "rb"); 177 | 178 | if (pFile != nullptr) { 179 | struct stat file_info; 180 | 181 | #ifndef LINUX 182 | if (fstat(_fileno(pFile), &file_info) == 0) 183 | #else 184 | if (fstat(fileno(pFile), &file_info) == 0) 185 | #endif 186 | { 187 | tLastModificationTime = file_info.st_mtime; 188 | return true; 189 | } 190 | } 191 | 192 | return false; 193 | } 194 | 195 | long long GetFileSize(const std::string& filename) { 196 | struct stat stat_buf; 197 | int rc = stat(filename.c_str(), &stat_buf); 198 | return rc == 0 ? stat_buf.st_size : -1; 199 | } 200 | 201 | long long FdGetFileSize(int fd) { 202 | struct stat stat_buf; 203 | int rc = fstat(fd, &stat_buf); 204 | return rc == 0 ? stat_buf.st_size : -1; 205 | } 206 | 207 | std::string sha1sum(const std::vector& memData) { 208 | CSHA1 sha1; 209 | sha1.Update(reinterpret_cast(memData.data()), memData.size()); 210 | sha1.Final(); 211 | 212 | std::string strHash; 213 | sha1.ReportHashStl(strHash, CSHA1::REPORT_HEX_SHORT); 214 | 215 | return strHash; 216 | } 217 | 218 | std::string sha1sum(const std::string& filename) { 219 | long long fileSize = GetFileSize(filename); 220 | if (fileSize <= 0) { 221 | return std::string(); 222 | } 223 | 224 | std::ifstream file(filename, std::ios::binary); 225 | if (!file) { 226 | return std::string(); 227 | } 228 | 229 | CSHA1 sha1; 230 | 231 | char* pFileBuffer; 232 | static const size_t s_bufferSize = 32 * 20 * 820; 233 | try { 234 | pFileBuffer = new char[s_bufferSize]; 235 | } catch (...) { 236 | return std::string(); 237 | } 238 | 239 | // size_t bytesProcessed = 0; 240 | while (true) { 241 | file.read(pFileBuffer, s_bufferSize); 242 | const size_t readBytes = file.gcount(); 243 | 244 | if (readBytes > 0) { 245 | sha1.Update(reinterpret_cast(pFileBuffer), readBytes); 246 | } else { 247 | break; 248 | } 249 | 250 | // bytesProcessed += readBytes; 251 | } 252 | delete[] pFileBuffer; 253 | 254 | // Finalize hash 255 | sha1.Final(); 256 | 257 | std::string strHash; 258 | sha1.ReportHashStl(strHash, CSHA1::REPORT_HEX_SHORT); 259 | 260 | return strHash; 261 | } 262 | -------------------------------------------------------------------------------- /TestFTP/test_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_TEST_UTILS_H_ 2 | #define INCLUDE_TEST_UTILS_H_ 3 | 4 | #ifdef WINDOWS 5 | #include 6 | #ifdef _DEBUG 7 | #ifdef _USE_VLD_ 8 | #include 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "SHA1.h" 33 | #include "SimpleIni.h" 34 | 35 | bool GlobalTestInit(const std::string& strConfFile); 36 | void GlobalTestCleanUp(void); 37 | 38 | void TimeStampTest(std::ostringstream& ssTimestamp); 39 | 40 | int TestDLProgressCallback(void* ptr, double dTotalToDownload, double dNowDownloaded, double dTotalToUpload, double dNowUploaded); 41 | int TestUPProgressCallback(void* ptr, double dTotalToDownload, double dNowDownloaded, double dTotalToUpload, double dNowUploaded); 42 | 43 | long GetGMTOffset(); 44 | bool GetFileTime(const char* const& pszFilePath, time_t& tLastModificationTime); 45 | long long GetFileSize(const std::string& filename); 46 | long long FdGetFileSize(int fd); 47 | 48 | std::string sha1sum(const std::vector& memData); 49 | std::string sha1sum(const std::string& filename); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | import pathlib 3 | 4 | class FTPClientCPPConan(ConanFile): 5 | name = "ftpclient-cpp" 6 | version = "0.1.0" 7 | license = "MIT" 8 | author = "embeddedmz https://github.com/embeddedmz" 9 | url = "https://github.com/embeddedmz/ftpclient-cpp" 10 | description = "C++ client for making FTP requests" 11 | settings = "os", "compiler", "build_type", "arch" 12 | options = {"shared": [False]} 13 | default_options = {"shared": False} 14 | requires = "libcurl/7.79.0" 15 | generators = "cmake_find_package" 16 | exports_sources = "*" 17 | 18 | def build(self): 19 | cmake = self._configure() 20 | cmake.build() 21 | 22 | def _configure(self): 23 | cmake = CMake(self) 24 | cmake.configure(source_folder=".") 25 | return cmake 26 | 27 | def package(self): 28 | self.copy("*.h", "include", src="FTP") 29 | self.copy("*.a", dst="lib", src="", keep_path=False) 30 | self.copy("*.so", dst="lib", src="", keep_path=False) 31 | self.copy("*.lib", dst="lib", src="", keep_path=False) 32 | self.copy("*.dll", dst="lib", src="", keep_path=False) 33 | self.copy("LICENSE") 34 | 35 | def package_info(self): 36 | self.cpp_info.libs = ["ftpclient"] 37 | --------------------------------------------------------------------------------