diff --git a/FTP/FTPClient.cpp b/FTP/FTPClient.cpp
index 849eab6..b6264f2 100644
--- a/FTP/FTPClient.cpp
+++ b/FTP/FTPClient.cpp
@@ -146,12 +146,12 @@ bool CFTPClient::CleanupSession() {
* @param [in] fnCallback callback to progress function
*
*/
-void CFTPClient::SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback) {
+void CFTPClient::SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback, const bool enable /*= true*/) {
m_ProgressStruct.pOwner = pOwner;
m_fnProgressCallback = fnCallback;
m_ProgressStruct.pCurl = m_pCurlSession;
m_ProgressStruct.dLastRunTime = 0;
- m_bProgressCallbackSet = true;
+ m_bProgressCallbackSet = enable;
}
/**
@@ -166,7 +166,7 @@ void CFTPClient::SetProxy(const std::string &strProxy) {
std::string strUri = strProxy;
std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);
- if (strUri.compare(0, 4, "HTTP") != 0)
+ if (strUri.compare(0, 5, "HTTP:") != 0)
m_strProxy = "http://" + strProxy;
else
m_strProxy = strProxy;
@@ -208,7 +208,7 @@ std::string CFTPClient::ParseURL(const std::string &strRemoteFile) const {
// boost::to_upper(strUri);
std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);
- if (strUri.compare(0, 3, "FTP") != 0 && strUri.compare(0, 4, "SFTP") != 0) {
+ if (strUri.compare(0, 4, "FTP:") != 0 && strUri.compare(0, 5, "SFTP:") != 0) {
switch (m_eFtpProtocol) {
case FTP_PROTOCOL::FTP:
case FTP_PROTOCOL::FTPES:
@@ -532,8 +532,8 @@ bool CFTPClient::Info(const std::string &strRemoteFile, struct FileInfo &oFileIn
}
res = curl_easy_getinfo(m_pCurlSession, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &oFileInfo.dFileSize);
- if (CURLE_OK == res && oFileInfo.dFileSize > 0.0) {
- bRes = true;
+ if (CURLE_OK != res || oFileInfo.dFileSize < 0.0) {
+ bRes = false;
}
} else if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_FILETIME_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res)));
@@ -781,6 +781,90 @@ bool CFTPClient::DownloadWildcard(const std::string &strLocalDir, const std::str
return bRet;
}
+/**
+ * @brief uploads a user data using readFn to a remote folder.
+ *
+ * @param [in] readFn Reading function, corresponds to CURLOPT_READFUNCTION.
+ * @param [in] userData user data passed to readFn as last parameter.
+ * @param [in] strRemoteFile Complete URN of the remote location (with the file
+ * name) encoded in UTF-8 format.
+ * @param [in] bCreateDir Enable or disable creation of remote missing
+ * directories contained in the URN.
+ *
+ * @retval true Data successfully uploaded.
+ * @retval false Data couldn't be uploaded. Check the log messages for more
+ * information.
+ */
+bool CFTPClient::UploadFile(CFTPClient::CurlReadFn readFn, void *userData, const std::string &strRemoteFile,
+ const bool &bCreateDir, curl_off_t fileSize) const {
+ if (readFn == nullptr || strRemoteFile.empty())
+ return false;
+
+ if (!m_pCurlSession) {
+ if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
+
+ return false;
+ }
+ // Reset is mandatory to avoid bad surprises
+ curl_easy_reset(m_pCurlSession);
+
+ std::string strLocalRemoteFile = ParseURL(strRemoteFile);
+
+ bool bRes = false;
+
+ /* specify target */
+ curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str());
+
+ /* we want to use our own read function */
+ curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, readFn);
+
+ /* now specify which file to upload */
+ curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, userData);
+
+ /* Set the size of the file to upload (optional). If you give a *_LARGE
+ option you MUST make sure that the type of the passed-in argument is a
+ curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must
+ make sure that to pass in a type 'long' argument. */
+ curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, fileSize);
+
+ /* enable uploading */
+ curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);
+
+ if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR);
+
+ CURLcode res = Perform();
+
+ if (res != CURLE_OK) {
+ if (m_eSettingsFlags & ENABLE_LOG)
+ m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res)));
+ } else
+ bRes = true;
+
+ return bRes;
+}
+
+/**
+ * @brief uploads data from a stream to a remote folder.
+ *
+ * @param [in] inputStream Stream containing data which will be uploaded.
+ * @param [in] strRemoteFile Complete URN of the remote location (with the file
+ * name) encoded in UTF-8 format.
+ * @param [in] bCreateDir Enable or disable creation of remote missing
+ * directories contained in the URN.
+ * @param [in] fileSize
+ *
+ * @retval true Successfully uploaded the inputStream.
+ * @retval false The inputStream couldn't be uploaded. Check the log messages for more
+ * information.
+ */
+bool CFTPClient::UploadFile(std::istream &inputStream, const std::string &strRemoteFile, const bool &bCreateDir,
+ curl_off_t fileSize) const {
+ if ( !inputStream )
+ return false;
+
+ return UploadFile(ReadFromStreamCallback, static_cast(&inputStream), strRemoteFile, bCreateDir, fileSize);
+}
+
/**
* @brief uploads a local file to a remote folder.
*
@@ -807,16 +891,7 @@ bool CFTPClient::DownloadWildcard(const std::string &strLocalDir, const std::str
bool CFTPClient::UploadFile(const std::string &strLocalFile, const std::string &strRemoteFile, const bool &bCreateDir) const {
if (strLocalFile.empty() || strRemoteFile.empty()) return false;
- if (!m_pCurlSession) {
- if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
-
- return false;
- }
- // Reset is mandatory to avoid bad surprises
- curl_easy_reset(m_pCurlSession);
-
std::ifstream InputFile;
- std::string strLocalRemoteFile = ParseURL(strRemoteFile);
struct stat file_info;
bool bRes = false;
@@ -837,35 +912,7 @@ bool CFTPClient::UploadFile(const std::string &strLocalFile, const std::string &
return false;
}
- /* specify target */
- curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str());
-
- /* we want to use our own read function */
- curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, ReadFromFileCallback);
-
- /* now specify which file to upload */
- curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &InputFile);
-
- /* Set the size of the file to upload (optional). If you give a *_LARGE
- option you MUST make sure that the type of the passed-in argument is a
- curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must
- make sure that to pass in a type 'long' argument. */
- curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, static_cast(file_info.st_size));
-
- /* enable uploading */
- curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);
-
- if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR);
-
- // TODO add the possibility to rename the file upon upload finish....
-
- CURLcode res = Perform();
-
- if (res != CURLE_OK) {
- if (m_eSettingsFlags & ENABLE_LOG)
- m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strLocalFile.c_str(), res, curl_easy_strerror(res)));
- } else
- bRes = true;
+ bRes = UploadFile(InputFile, strRemoteFile, bCreateDir, file_info.st_size);
}
InputFile.close();
@@ -919,7 +966,7 @@ bool CFTPClient::AppendFile(const std::string &strLocalFile, const size_t fileOf
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str());
/* we want to use our own read function */
- curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, ReadFromFileCallback);
+ curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, ReadFromStreamCallback);
/* now specify which file to upload */
curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &InputFile);
@@ -1161,13 +1208,13 @@ size_t CFTPClient::WriteToMemory(void *buff, size_t size, size_t nmemb, void *da
* @param ptr pointer of max size (size*nmemb) to write data to it
* @param size size parameter
* @param nmemb memblock parameter
- * @param stream pointer to user data (file stream)
+ * @param stream pointer to user data (input stream)
*
* @return (size * nmemb)
*/
-size_t CFTPClient::ReadFromFileCallback(void *ptr, size_t size, size_t nmemb, void *stream) {
- std::ifstream *pFileStream = reinterpret_cast(stream);
- if (pFileStream->is_open()) {
+size_t CFTPClient::ReadFromStreamCallback(void *ptr, size_t size, size_t nmemb, void *stream) {
+ auto *pFileStream = reinterpret_cast(stream);
+ if (!pFileStream->fail()) {
pFileStream->read(reinterpret_cast(ptr), size * nmemb);
return pFileStream->gcount();
}
diff --git a/FTP/FTPClient.h b/FTP/FTPClient.h
index 76f3854..8625630 100644
--- a/FTP/FTPClient.h
+++ b/FTP/FTPClient.h
@@ -42,6 +42,7 @@ class CFTPClient {
// Public definitions
using ProgressFnCallback = std::function;
using LogFnCallback = std::function;
+ using CurlReadFn = size_t (*) (void *, size_t, size_t, void *);
// Used to download many items at once
struct WildcardTransfersCallbackData {
@@ -118,7 +119,7 @@ class CFTPClient {
CFTPClient &operator=(CFTPClient &&) = delete;
// Setters - Getters (for unit tests)
- void SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback);
+ void SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback, const bool enable = true);
void SetProxy(const std::string &strProxy);
void SetProxyUserPwd(const std::string &strProxyUserPwd);
inline void SetTimeout(const int &iTimeout) { m_iCurlTimeout = iTimeout; }
@@ -164,6 +165,12 @@ class CFTPClient {
bool DownloadWildcard(const std::string &strLocalDir, const std::string &strRemoteWildcard) const;
+ bool UploadFile(CurlReadFn readFn, void *userData, const std::string &strRemoteFile, const bool &bCreateDir = false,
+ curl_off_t fileSize = -1) const;
+
+ bool UploadFile(std::istream &inputStream, const std::string &strRemoteFile, const bool &bCreateDir = false,
+ curl_off_t fileSize = -1) const;
+
bool UploadFile(const std::string &strLocalFile, const std::string &strRemoteFile, const bool &bCreateDir = false) const;
bool AppendFile(const std::string &strLocalFile, const size_t fileOffset, const std::string &strRemoteFile,
@@ -196,7 +203,7 @@ class CFTPClient {
// Curl callbacks
static size_t WriteInStringCallback(void *ptr, size_t size, size_t nmemb, void *data);
static size_t WriteToFileCallback(void *ptr, size_t size, size_t nmemb, void *data);
- static size_t ReadFromFileCallback(void *ptr, size_t size, size_t nmemb, void *stream);
+ static size_t ReadFromStreamCallback(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t ThrowAwayCallback(void *ptr, size_t size, size_t nmemb, void *data);
static size_t WriteToMemory(void *ptr, size_t size, size_t nmemb, void *data);
diff --git a/README.md b/README.md
index d39c05e..615ffd0 100644
--- a/README.md
+++ b/README.md
@@ -311,7 +311,7 @@ Example : (Run only FTP tests)
ftp=yes
; SFTP tests (not implemented) are disabled
sftp=no
-; HTTP Proxy tests are disabled
+; HTTP Proxy tests are enabled
http-proxy=yes
[ftp]
diff --git a/TestFTP/main.cpp b/TestFTP/main.cpp
index bd7c0c2..843285c 100644
--- a/TestFTP/main.cpp
+++ b/TestFTP/main.cpp
@@ -461,6 +461,51 @@ TEST_F(FTPClientTest, TestUploadAndRemoveFile) {
std::cout << "FTP tests are disabled !" << std::endl;
}
+TEST_F(FTPClientTest, TestUploadStreamAndRemove) {
+ if (FTP_TEST_ENABLED) {
+ // to display a beautiful progress bar on console
+ m_pFTPClient->SetProgressFnCallback(m_pFTPClient.get(), &TestUPProgressCallback);
+
+ std::ostringstream ssTimestamp;
+ TimeStampTest(ssTimestamp);
+
+ // create dummy string stream
+ std::stringstream ofTestUpload;
+ ASSERT_TRUE(static_cast(ofTestUpload));
+
+ ofTestUpload << "Unit Test 'TestUploadStreamAndRemove' executed on " + ssTimestamp.str() + "\n" +
+ "This file is uploaded via FTPClient-C++ API.\n" +
+ "If this file exists, that means that the unit test is passed.\n";
+ ASSERT_TRUE(static_cast(ofTestUpload));
+
+ // Upload steam
+ ASSERT_TRUE(static_cast(ofTestUpload));
+ ASSERT_TRUE(m_pFTPClient->UploadFile(ofTestUpload, SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt"));
+
+ std::cout << std::endl;
+
+ // Download the uploaded stream into a vector of bytes
+ {
+ std::vector uploadedFileBytes;
+ EXPECT_TRUE(m_pFTPClient->DownloadFile(SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt", uploadedFileBytes));
+
+ std::cout << std::endl;
+
+ /* check the SHA1 sum of the uploaded file */
+ std::string contentStr = ofTestUpload.str();
+ std::vector contentBytes(contentStr.begin(), contentStr.end());
+ std::string expectedSha1Sum = sha1sum(contentBytes);
+ std::string resultSha1Sum = sha1sum(uploadedFileBytes);
+
+ EXPECT_TRUE(expectedSha1Sum == resultSha1Sum);
+ }
+
+ // Remove file
+ ASSERT_TRUE(m_pFTPClient->RemoveFile(SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt"));
+ } else
+ std::cout << "FTP tests are disabled !" << std::endl;
+}
+
#ifdef WINDOWS
TEST_F(FTPClientTest, TestUploadFileNameWithAccents) {
if (FTP_TEST_ENABLED) {
@@ -815,6 +860,51 @@ TEST_F(SFTPClientTest, TestUploadAndRemoveFile) {
std::cout << "SFTP tests are disabled !" << std::endl;
}
+TEST_F(SFTPClientTest, TestUploadStreamAndRemove) {
+ if (SFTP_TEST_ENABLED) {
+ // to display a beautiful progress bar on console
+ m_pSFTPClient->SetProgressFnCallback(m_pSFTPClient.get(), &TestUPProgressCallback);
+
+ std::ostringstream ssTimestamp;
+ TimeStampTest(ssTimestamp);
+
+ // create dummy string stream
+ std::stringstream ofTestUpload;
+ ASSERT_TRUE(static_cast(ofTestUpload));
+
+ ofTestUpload << "Unit Test 'TestUploadStreamAndRemove' executed on " + ssTimestamp.str() + "\n" +
+ "This file is uploaded via FTPClient-C++ API.\n" +
+ "If this file exists, that means that the unit test is passed.\n";
+ ASSERT_TRUE(static_cast(ofTestUpload));
+
+ // Upload steam
+ ASSERT_TRUE(static_cast(ofTestUpload));
+ ASSERT_TRUE(m_pSFTPClient->UploadFile(ofTestUpload, SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt"));
+
+ std::cout << std::endl;
+
+ // Download the uploaded stream into a vector of bytes
+ {
+ std::vector uploadedFileBytes;
+ EXPECT_TRUE(m_pSFTPClient->DownloadFile(SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt", uploadedFileBytes));
+
+ std::cout << std::endl;
+
+ /* check the SHA1 sum of the uploaded file */
+ std::string contentStr = ofTestUpload.str();
+ std::vector contentBytes(contentStr.begin(), contentStr.end());
+ std::string expectedSha1Sum = sha1sum(contentBytes);
+ std::string resultSha1Sum = sha1sum(uploadedFileBytes);
+
+ EXPECT_TRUE(expectedSha1Sum == resultSha1Sum);
+ }
+
+ // Remove file
+ ASSERT_TRUE(m_pSFTPClient->RemoveFile(SFTP_REMOTE_UPLOAD_FOLDER + "test_upload_stream.txt"));
+ } else
+ std::cout << "SFTP tests are disabled !" << std::endl;
+}
+
// TODO : this unit test can't be executed elsewhere
#if 0
TEST_F(SFTPClientTest, TestAppend /*AndRemoveFile*/) {
diff --git a/TestFTP/simpleini/SimpleIni.h b/TestFTP/simpleini/SimpleIni.h
index 0da9414..8f0d322 100644
--- a/TestFTP/simpleini/SimpleIni.h
+++ b/TestFTP/simpleini/SimpleIni.h
@@ -319,7 +319,7 @@ class CSimpleIniTempl {
#endif
/** Strict less ordering by name of key only */
- struct KeyOrder : std::binary_function {
+ struct KeyOrder {
bool operator()(const Entry &lhs, const Entry &rhs) const {
const static SI_STRLESS isLess = SI_STRLESS();
return isLess(lhs.pItem, rhs.pItem);
@@ -327,7 +327,7 @@ class CSimpleIniTempl {
};
/** Strict less ordering by order, and then name of key */
- struct LoadOrder : std::binary_function {
+ struct LoadOrder {
bool operator()(const Entry &lhs, const Entry &rhs) const {
if (lhs.nOrder != rhs.nOrder) {
return lhs.nOrder < rhs.nOrder;
diff --git a/TestFTP/test_utils.cpp b/TestFTP/test_utils.cpp
index d491bc0..e198e2e 100644
--- a/TestFTP/test_utils.cpp
+++ b/TestFTP/test_utils.cpp
@@ -36,7 +36,7 @@ std::mutex g_mtxConsoleMutex;
bool GlobalTestInit(const std::string& strConfFile) {
CSimpleIniA ini;
- ini.LoadFile(strConfFile.c_str());
+ if (ini.LoadFile(strConfFile.c_str()) != SI_Error::SI_OK) return false;
std::string strTmp;
strTmp = ini.GetValue("tests", "ftp", "");