diff --git a/CMakeLists.txt b/CMakeLists.txt index 7037f6d..c9d03e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required (VERSION 3.10.0) set(CMAKE_CXX_STANDARD 14) @@ -6,15 +6,28 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lib) +# Read version from file VERSION +FILE(READ "VERSION" project_VERSION) +STRING(STRIP "${project_VERSION}" project_VERSION) + +SET(LICENSE "MIT") +cmake_policy(SET CMP0022 NEW) + +PROJECT(HTTPClient VERSION ${project_VERSION} LANGUAGES CXX) + if(NOT MSVC) add_definitions(-DLINUX) else() add_definitions(-DWINDOWS) endif() +option(SKIP_TESTS_BUILD "Skip tests build" ON) + include_directories(HTTP) add_subdirectory(HTTP) + +if(NOT SKIP_TESTS_BUILD) add_subdirectory(TestHTTP) include(CTest) @@ -28,3 +41,4 @@ IF (NOT TEST_INI_FILE) ENDIF() add_test (NAME HttpClientTest COMMAND test_httpclient ${TEST_INI_FILE}) +endif(NOT SKIP_TESTS_BUILD) diff --git a/HTTP/CMakeLists.txt b/HTTP/CMakeLists.txt index 985dbbe..2a03e89 100644 --- a/HTTP/CMakeLists.txt +++ b/HTTP/CMakeLists.txt @@ -1,9 +1,8 @@ -cmake_minimum_required(VERSION 2.6) - -project(HTTPClient) IF(MSVC OR NOT CMAKE_BUILD_TYPE MATCHES Coverage) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + # Locate libcURL find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) @@ -11,4 +10,6 @@ include_directories(${CURL_INCLUDE_DIRS}) file(GLOB_RECURSE source_files ./*) add_library(httpclient STATIC ${source_files}) +install(TARGETS httpclient) + ENDIF() diff --git a/HTTP/CurlHandle.cpp b/HTTP/CurlHandle.cpp new file mode 100644 index 0000000..c6af406 --- /dev/null +++ b/HTTP/CurlHandle.cpp @@ -0,0 +1,18 @@ +#include "CurlHandle.h" + +#include +#include + +CurlHandle::CurlHandle() { + const auto eCode = curl_global_init(CURL_GLOBAL_ALL); + if (eCode != CURLE_OK) { + throw std::runtime_error{"Error initializing libCURL"}; + } +} + +CurlHandle::~CurlHandle() { curl_global_cleanup(); } + +CurlHandle &CurlHandle::instance() { + static CurlHandle inst{}; + return inst; +} diff --git a/HTTP/CurlHandle.h b/HTTP/CurlHandle.h new file mode 100644 index 0000000..fbe8caf --- /dev/null +++ b/HTTP/CurlHandle.h @@ -0,0 +1,20 @@ +#ifndef INCLUDE_CURLHANDLE_H_ +#define INCLUDE_CURLHANDLE_H_ + +class CurlHandle { + public: + static CurlHandle &instance(); + + CurlHandle(CurlHandle const &) = delete; + CurlHandle(CurlHandle &&) = delete; + + CurlHandle &operator=(CurlHandle const &) = delete; + CurlHandle &operator=(CurlHandle &&) = delete; + + ~CurlHandle(); + + private: + CurlHandle(); +}; + +#endif diff --git a/HTTP/HTTPClient.cpp b/HTTP/HTTPClient.cpp index 462170c..65bdc1b 100644 --- a/HTTP/HTTPClient.cpp +++ b/HTTP/HTTPClient.cpp @@ -7,9 +7,7 @@ #include "HTTPClient.h" // Static members initialization -volatile int CHTTPClient::s_iCurlSession = 0; std::string CHTTPClient::s_strCertificationAuthorityFile; -std::mutex CHTTPClient::s_mtxCurlSession; #ifdef DEBUG_CURL std::string CHTTPClient::s_strCurlTraceLogDirectory; @@ -29,15 +27,10 @@ CHTTPClient::CHTTPClient(LogFnCallback Logger) : m_bProgressCallbackSet(false), m_eSettingsFlags(ALL_FLAGS), m_pCurlSession(nullptr), - m_pHeaderlist(nullptr) + m_pHeaderlist(nullptr), + m_curlHandle(CurlHandle::instance()) { - s_mtxCurlSession.lock(); - if (s_iCurlSession++ == 0) - { - /* In windows, this will init the winsock stuff */ - curl_global_init(CURL_GLOBAL_ALL); - } - s_mtxCurlSession.unlock(); + } /** @@ -53,13 +46,6 @@ CHTTPClient::~CHTTPClient() CleanupSession(); } - - s_mtxCurlSession.lock(); - if (--s_iCurlSession <= 0) - { - curl_global_cleanup(); - } - s_mtxCurlSession.unlock(); } /** @@ -184,7 +170,7 @@ const bool CHTTPClient::CleanupSession() * * @param [in] strURL user URI */ -inline void CHTTPClient::CheckURL(const std::string& strURL) +inline void CHTTPClient::UpdateURL(const std::string& strURL) { std::string strTmp = strURL; @@ -256,18 +242,14 @@ const CURLcode CHTTPClient::Perform() curl_easy_setopt(m_pCurlSession, CURLOPT_NOPROGRESS, 0L); } - // SSL if (m_bHTTPS) { - curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, CURLUSESSL_ALL); + // SSL (TLS) + curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, CURLUSESSL_ALL); + curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, (m_eSettingsFlags & VERIFY_PEER) ? 1L : 0L); + curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, (m_eSettingsFlags & CURLOPT_SSL_VERIFYHOST) ? 2L : 0L); } - if (m_bHTTPS && !(m_eSettingsFlags & VERIFY_PEER)) - curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, 0L); - - if (m_bHTTPS && !(m_eSettingsFlags & VERIFY_HOST)) - curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYHOST, 0L); // use 2L for strict name check - if (m_bHTTPS && !s_strCertificationAuthorityFile.empty()) curl_easy_setopt(m_pCurlSession, CURLOPT_CAINFO, s_strCertificationAuthorityFile.c_str()); @@ -303,7 +285,7 @@ const CURLcode CHTTPClient::Perform() /** * @brief requests the content of a URI * - * @param [in] strURL URI of the remote location (with the file name). + * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format. * @param [out] strOutput reference to an output string. * @param [out] lHTTPStatusCode HTTP Status code of the response. * @@ -338,7 +320,7 @@ const bool CHTTPClient::GetText(const std::string& strURL, // Reset is mandatory to avoid bad surprises curl_easy_reset(m_pCurlSession); - CheckURL(strURL); + UpdateURL(strURL); curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L); curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteInStringCallback); @@ -364,8 +346,8 @@ const bool CHTTPClient::GetText(const std::string& strURL, /** * @brief Downloads a remote file to a local file. * - * @param [in] strLocalFile Complete path of the local file to download. - * @param [in] strURL URI of the remote location (with the file name). + * @param [in] strLocalFile Complete path of the local file to download in UTF-8 format. + * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format. * @param [out] lHTTPStatusCode HTTP Status code of the response. * * @retval true Successfully downloaded the file. @@ -388,10 +370,16 @@ const bool CHTTPClient::DownloadFile(const std::string& strLocalFile, // Reset is mandatory to avoid bad surprises curl_easy_reset(m_pCurlSession); - CheckURL(strURL); + UpdateURL(strURL); std::ofstream ofsOutput; - ofsOutput.open(strLocalFile, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); + ofsOutput.open( +#ifdef LINUX + strLocalFile, // UTF-8 +#else + Utf8ToUtf16(strLocalFile), +#endif + std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (ofsOutput) { @@ -431,11 +419,61 @@ const bool CHTTPClient::DownloadFile(const std::string& strLocalFile, return true; } +/** + * @brief downloads a remote file to memory + * + * @param [out] data vector of bytes + * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format. + * @param [out] lHTTPStatusCode HTTP Status code of the response. + * + * @retval true Successfully downloaded the file. + * @retval false The content couldn't be downloaded. Check the log messages for + * more information. + */ +const bool CHTTPClient::DownloadFile(std::vector& data, const std::string& strURL, long& lHTTPStatusCode) { + if (strURL.empty()) + return false; + + if (!m_pCurlSession) + { + if (m_eSettingsFlags & ENABLE_LOG) + m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG); + + return false; + } + + data.clear(); + + // Reset is mandatory to avoid bad surprises + curl_easy_reset(m_pCurlSession); + + UpdateURL(strURL); + + curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToMemoryCallback); + curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data); + + CURLcode res = Perform(); + + curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode); + + if (res != CURLE_OK) + { + if (m_eSettingsFlags & ENABLE_LOG) + m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, "Download to a byte buffer", + strURL.c_str(), res, curl_easy_strerror(res), lHTTPStatusCode)); + + return false; + } + + return true; +} + /** * @brief uploads a POST form * * - * @param [in] strURL URL to which the form will be posted. + * @param [in] strURL URL to which the form will be posted encoded in UTF-8 format. * @param [in] data post form information * @param [out] lHTTPStatusCode HTTP Status code of the response. * @@ -463,7 +501,7 @@ const bool CHTTPClient::UploadForm(const std::string& strURL, // Reset is mandatory to avoid bad surprises curl_easy_reset(m_pCurlSession); - CheckURL(strURL); + UpdateURL(strURL); /** Now specify we want to POST data */ curl_easy_setopt(m_pCurlSession, CURLOPT_POST, 1L); @@ -521,8 +559,8 @@ CHTTPClient::PostFormInfo::~PostFormInfo() /** * @brief set the name and the value of the HTML "file" form's input * - * @param fieldName name of the "file" input - * @param fieldValue path to the file to upload + * @param fieldName name of the "file" input encoded in UTF8. + * @param fieldValue path to the file to upload encoded in UTF8. */ void CHTTPClient::PostFormInfo::AddFormFile(const std::string& strFieldName, const std::string& strFieldValue) @@ -537,8 +575,8 @@ void CHTTPClient::PostFormInfo::AddFormFile(const std::string& strFieldName, * @brief set the name and the value of an HTML form's input * (other than "file" like "text", "hidden" or "submit") * - * @param fieldName name of the input element - * @param fieldValue value to be assigned to the input element + * @param fieldName name of the input element encoded in UTF8 for Linux and in ANSI for Windows (so the file gets located and uploaded). + * @param fieldValue value to be assigned to the input element encoded in UTF8 for Linux and in ANSI for Windows. */ void CHTTPClient::PostFormInfo::AddFormContent(const std::string& strFieldName, const std::string& strFieldValue) @@ -556,6 +594,7 @@ void CHTTPClient::PostFormInfo::AddFormContent(const std::string& strFieldName, * some common operations to REST requests are performed here, * the others are performed in Perform method * +* @param [in] strUrl URI encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data */ @@ -580,7 +619,7 @@ inline const bool CHTTPClient::InitRestRequest(const std::string& strUrl, // Reset is mandatory to avoid bad surprises curl_easy_reset(m_pCurlSession); - CheckURL(strUrl); + UpdateURL(strUrl); // set the received body's callback function curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, &CHTTPClient::RestWriteCallback); @@ -637,7 +676,7 @@ inline const bool CHTTPClient::PostRestRequest(const CURLcode ePerformCode, /** * @brief performs a HEAD request * -* @param [in] strUrl url to request +* @param [in] strUrl url to request encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data * @@ -665,7 +704,7 @@ const bool CHTTPClient::Head(const std::string& strUrl, /** * @brief performs a GET request * -* @param [in] strUrl url to request +* @param [in] strUrl url to request encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data * @@ -692,7 +731,7 @@ const bool CHTTPClient::Get(const std::string& strUrl, /** * @brief performs a DELETE request * -* @param [in] strUrl url to request +* @param [in] strUrl url to request encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data * @@ -740,7 +779,7 @@ const bool CHTTPClient::Post(const std::string& strUrl, /** * @brief performs a PUT request with a string * -* @param [in] strUrl url to request +* @param [in] strUrl url to request encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data * @@ -780,7 +819,7 @@ const bool CHTTPClient::Put(const std::string& strUrl, const CHTTPClient::Header /** * @brief performs a PUT request with a byte buffer (vector of char) * -* @param [in] strUrl url to request +* @param [in] strUrl url to request encoded in UTF-8 format. * @param [in] Headers headers to send * @param [out] Response response data * @@ -820,52 +859,24 @@ const bool CHTTPClient::Put(const std::string& strUrl, const CHTTPClient::Header // STRING HELPERS /** -* @brief returns a formatted string -* -* @param [in] strFormat string with one or many format specifiers -* @param [in] parameters to be placed in the format specifiers of strFormat -* -* @retval string formatted string -*/ -std::string CHTTPClient::StringFormat(const std::string strFormat, ...) -{ - int n = (static_cast(strFormat.size())) * 2; // Reserve two times as much as the length of the strFormat - - std::unique_ptr pFormatted; - - va_list ap; - - while(true) - { - pFormatted.reset(new char[n]); // Wrap the plain char array into the unique_ptr - strcpy(&pFormatted[0], strFormat.c_str()); - - va_start(ap, strFormat); - int iFinaln = vsnprintf(&pFormatted[0], n, strFormat.c_str(), ap); - va_end(ap); - - if (iFinaln < 0 || iFinaln >= n) - { - n += abs(iFinaln - n + 1); - } - else - { - break; - } - } - - return std::string(pFormatted.get()); -} -/*std::string CHTTPClient::StringFormat(const std::string strFormat, ...) -{ - char buffer[1024]; - va_list args; - va_start(args, strFormat); - vsnprintf(buffer, 1024, strFormat.c_str(), args); - va_end (args); - return std::string(buffer); + * @brief returns a formatted string + * + * @param [in] strFormat string with one or many format specifiers + * @param [in] parameters to be placed in the format specifiers of strFormat + * + * @retval string formatted string + */ +std::string CHTTPClient::StringFormat(std::string strFormat, ...) { + va_list args; + va_start(args, strFormat); + size_t len = std::vsnprintf(NULL, 0, strFormat.c_str(), args); + va_end(args); + std::vector vec(len + 1); + va_start(args, strFormat); + std::vsnprintf(&vec[0], len + 1, strFormat.c_str(), args); + va_end(args); + return &vec[0]; } -*/ /** * @brief removes leading and trailing whitespace from a string @@ -943,6 +954,27 @@ size_t CHTTPClient::WriteToFileCallback(void* buff, size_t size, size_t nmemb, v return size * nmemb; } +/** + * @brief stores the server response in std::vector + * + * @param buff pointer of max size (size*nmemb) to read data from it + * @param size size parameter + * @param nmemb memblock parameter + * @param userdata pointer to user data (file stream) + * + * @return (size * nmemb) + */ +size_t CHTTPClient::WriteToMemoryCallback(void* buff, size_t size, size_t nmemb, void* data) { + if ((size == 0) || (nmemb == 0) || (data == nullptr)) return 0; + + auto* vec = reinterpret_cast *>(data); + size_t ssize = size * nmemb; + std::copy(reinterpret_cast(buff), reinterpret_cast(buff) + ssize, + std::back_inserter(*vec)); + + return ssize; +} + /** * @brief reads the content of an already opened file stream * used by UploadFile() @@ -1166,3 +1198,29 @@ void CHTTPClient::EndCurlDebug() const } } #endif + +#ifdef WINDOWS +std::string CHTTPClient::AnsiToUtf8(const std::string& codepage_str) { + // Transcode Windows ANSI to UTF-16 + int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, codepage_str.c_str(), codepage_str.length(), nullptr, 0); + std::wstring utf16_str(size, '\0'); + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, codepage_str.c_str(), codepage_str.length(), &utf16_str[0], size); + + // Transcode UTF-16 to UTF-8 + int utf8_size = WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(), utf16_str.length(), nullptr, 0, nullptr, nullptr); + std::string utf8_str(utf8_size, '\0'); + WideCharToMultiByte(CP_UTF8, 0, utf16_str.c_str(), utf16_str.length(), &utf8_str[0], utf8_size, nullptr, nullptr); + + return utf8_str; +} + +std::wstring CHTTPClient::Utf8ToUtf16(const std::string& str) { + std::wstring ret; + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0); + if (len > 0) { + ret.resize(len); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &ret[0], len); + } + return ret; +} +#endif diff --git a/HTTP/HTTPClient.h b/HTTP/HTTPClient.h index d7b87b7..b3f9015 100644 --- a/HTTP/HTTPClient.h +++ b/HTTP/HTTPClient.h @@ -34,6 +34,8 @@ #include #include +#include "CurlHandle.h" + class CHTTPClient { public: @@ -120,7 +122,6 @@ class CHTTPClient const bool InitSession(const bool& bHTTPS = false, const SettingsFlag& SettingsFlags = ALL_FLAGS); virtual const bool CleanupSession(); - static int GetCurlSessionCount() { return s_iCurlSession; } const CURL* GetCurlPointer() const { return m_pCurlSession; } // HTTP requests @@ -132,6 +133,8 @@ class CHTTPClient const std::string& strURL, long& lHTTPStatusCode); + const bool DownloadFile(std::vector& data, const std::string& strURL, long& lHTTPStatusCode); + const bool UploadForm(const std::string& strURL, const PostFormInfo& data, long& lHTTPStatusCode); @@ -169,6 +172,11 @@ class CHTTPClient static void SetCurlTraceLogDirectory(const std::string& strPath); #endif +#ifdef WINDOWS + static std::string AnsiToUtf8(const std::string& ansiStr); + static std::wstring Utf8ToUtf16(const std::string& str); +#endif + protected: // payload to upload on POST requests. struct UploadObject @@ -180,7 +188,7 @@ class CHTTPClient /* common operations are performed here */ inline const CURLcode Perform(); - inline void CheckURL(const std::string& strURL); + inline void UpdateURL(const std::string& strURL); inline const bool InitRestRequest(const std::string& strUrl, const HeadersMap& Headers, HttpResponse& Response); inline const bool PostRestRequest(const CURLcode ePerformCode, HttpResponse& Response); @@ -188,6 +196,7 @@ class CHTTPClient // 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 WriteToMemoryCallback(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 ThrowAwayCallback(void* ptr, size_t size, size_t nmemb, void* data); static size_t RestWriteCallback(void* ptr, size_t size, size_t nmemb, void* userdata); @@ -219,9 +228,6 @@ class CHTTPClient std::string m_strSSLCertFile; std::string m_strSSLKeyFile; std::string m_strSSLKeyPwd; - - static std::mutex s_mtxCurlSession; // mutex used to manage API global operations - volatile static int s_iCurlSession; // Count of the actual sessions CURL* m_pCurlSession; int m_iCurlTimeout; @@ -239,8 +245,14 @@ class CHTTPClient static std::string s_strCurlTraceLogDirectory; mutable std::ofstream m_ofFileCurlTrace; #endif + + CurlHandle& m_curlHandle; }; +inline CHTTPClient::SettingsFlag operator|(CHTTPClient::SettingsFlag a, CHTTPClient::SettingsFlag b) { + return static_cast(static_cast(a) | static_cast(b)); +} + // Logs messages #define LOG_ERROR_EMPTY_HOST_MSG "[HTTPClient][Error] Empty hostname." #define LOG_WARNING_OBJECT_NOT_CLEANED "[HTTPClient][Warning] Object was freed before calling " \ diff --git a/README.md b/README.md index ec07ae4..c4b60f0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Compilation has been tested with: Underlying libraries: - [libcurl](http://curl.haxx.se/libcurl/) +Windows Users : vcpkg (Microsoft C++ Library Manager) can be used to easily install libcurl 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. + ## Usage Create an object and provide to its constructor a callable object (for log printing) having this signature : @@ -170,11 +172,6 @@ The unit test "TestDownloadFile" demonstrates how to use a progress function to ## Thread Safety -A mutex is used to increment/decrement atomically the count of CHTTPClient objects. - -`curl_global_init` is called when the count of CHTTPClient objects equals to zero (when instanciating the first object). -`curl_global_cleanup` is called when the count of CHTTPClient objects become zero (when all CHTTPClient objects are destroyed). - Do not share CHTTPClient objects across threads as this would mean accessing libcurl handles from multiple threads at the same time which is not allowed. @@ -232,7 +229,35 @@ To directly run the unit test binary, you must indicate the path of the INI conf ### Building under Windows via Visual Studio -This can be enhanced in future... +1. New Procedure (with vcpkg) : + +Install [vcpkg](https://github.com/microsoft/vcpkg) then install libcurl (use 'x86-windows' for the 32-bit version) : +```Shell +.\vcpkg install curl curl[openssl] --triplet=x64-windows +``` + +If you have a french Visual Studio version, don't forget to install the english language pack (vcpkg will tell you this anyway). + +Download and install the latest version of CMake : https://cmake.org/download/ (e.g. Windows win64-x64 Installer) + +Open CMake (cmake-gui) + +In "Where is the source code", put the httpclient-cpp path (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/httpclient-cpp), where the main CMakeLists.txt file exist. + +In "Where to build binaries", paste the directory where you want to build the project (e.g. C:/Users/Amine/Documents/Work/PROJECTS/GitHub/httpclient_build) + +Click on "Configure". + +Select your Visual Studio version (if it isn't already set). +In "Optional platform for generator", you can leave it empty (x64 by default) or choose another value. + +Click on the radio button "Specify toolchain file for cross-compiling, then hit the "Next" button. + +In "Specify the toolchain file", browse to vcpkg toolchain file (vcpkg/scripts/buildsystems/vcpkg.cmake) and select it. + +Press "Finish", wait until CMake configures the project then hit "Generate" to create the Visual Studio solution (library and unit test binary). + +2. Old Procedure (without vcpkg) : First of all, build libcurl using this fork of build-libcurl-windows : https://github.com/ribtoks/build-libcurl-windows @@ -376,3 +401,9 @@ Try to preserve the existing coding style (Hungarian notation, indentation etc.. If you compile the test program with the preprocessor macro DEBUG_CURL, to enable curl debug informations, the static library used must also be compiled with that macro. Don't forget to mention a path where to store log files in the INI file if you want to use that feature in the unit test program (curl_logs_folder under [local]) + +### File names format when compiling with Visual Studio (Windows users) + +It is assumed that the FTP server is supporting 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 iy can represent certain other Windows code pages on other systems, ANSI is just an extension for ASCII). Look at the unit tests for examples (look for the preprocessor macro WINDOWS to find them quickly). + +If you limit to ASCII characters, you don't need to convert your ANSI strings to UTF-8. diff --git a/TestHTTP/CMakeLists.txt b/TestHTTP/CMakeLists.txt index 0eadc39..7e18db8 100644 --- a/TestHTTP/CMakeLists.txt +++ b/TestHTTP/CMakeLists.txt @@ -1,7 +1,3 @@ -cmake_minimum_required(VERSION 2.6) - -project(TestHTTPClient) - # Code coverage setup IF(CMAKE_BUILD_TYPE MATCHES Coverage) INCLUDE(CodeCoverage.cmake) @@ -13,43 +9,9 @@ ENDIF(CMAKE_BUILD_TYPE MATCHES Coverage) find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) -# For Windows -# https://crascit.com/2015/07/25/cmake-gtest/ -if (MSVC) - # Download and unpack googletest at configure time - configure_file(CMakeLists.txt.in "${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt") - execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" ) - execute_process(COMMAND "${CMAKE_COMMAND}" --build . - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" ) - - # Prevent GoogleTest from overriding our compiler/linker options - # when building with Visual Studio - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - # Add googletest directly to our build. This adds - # the following targets: gtest, gtest_main, gmock - # and gmock_main - add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src" - "${CMAKE_BINARY_DIR}/googletest-build") - - # The gtest/gmock targets carry header search path - # dependencies automatically when using CMake 2.8.11 or - # later. Otherwise we have to add them here ourselves. - if(CMAKE_VERSION VERSION_LESS 2.8.11) - include_directories("${gtest_SOURCE_DIR}/include" - "${gmock_SOURCE_DIR}/include") - endif() - - # Now simply link your own targets against gtest, gmock, - # etc. as appropriate -endif() - # Locate GTest -if(NOT MSVC) - find_package(GTest REQUIRED) - include_directories(${GTEST_INCLUDE_DIRS}) -endif() +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) # useless but test before removing it include_directories(../HTTP) include_directories(./simpleini) @@ -87,7 +49,7 @@ add_executable(test_httpclient main.cpp test_utils.cpp) if(NOT MSVC) target_link_libraries(test_httpclient httpclient ${GTEST_LIBRARIES} pthread curl) else() - target_link_libraries(test_httpclient httpclient gtest ${CURL_LIBRARIES}) + target_link_libraries(test_httpclient httpclient ${GTEST_LIBRARIES} ${CURL_LIBRARIES}) endif() ENDIF() diff --git a/TestHTTP/CMakeLists.txt.in b/TestHTTP/CMakeLists.txt.in deleted file mode 100644 index e700161..0000000 --- a/TestHTTP/CMakeLists.txt.in +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 2.8.2) - -project(googletest-download-for-windows NONE) - -include(ExternalProject) -ExternalProject_Add(googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG master - SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" - BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/TestHTTP/main.cpp b/TestHTTP/main.cpp index f07f4c5..ca8d9ed 100644 --- a/TestHTTP/main.cpp +++ b/TestHTTP/main.cpp @@ -121,10 +121,10 @@ TEST(HTTPClient, TestSession) EXPECT_EQ(CHTTPClient::SettingsFlag::ALL_FLAGS, HTTPClient.GetSettingsFlags()); /* after init. a session */ - ASSERT_TRUE(HTTPClient.InitSession(true, CHTTPClient::ENABLE_LOG)); + ASSERT_TRUE(HTTPClient.InitSession(true, CHTTPClient::ENABLE_LOG | CHTTPClient::VERIFY_PEER)); // check that the parameters provided to InitSession - EXPECT_EQ(CHTTPClient::ENABLE_LOG, HTTPClient.GetSettingsFlags()); + EXPECT_EQ(CHTTPClient::ENABLE_LOG | CHTTPClient::VERIFY_PEER, HTTPClient.GetSettingsFlags()); EXPECT_TRUE(HTTPClient.GetHTTPS()); EXPECT_TRUE(HTTPClient.GetCurlPointer() != nullptr); @@ -180,7 +180,6 @@ TEST(HTTPClient, TestCleanUpWithoutInit) TEST(HTTPClient, TestMultithreading) { const char* arrDataArray[3] = { "Thread 1", "Thread 2", "Thread 3" }; - unsigned uInitialCount = CHTTPClient::GetCurlSessionCount(); auto ThreadFunction = [](const char* pszThreadName) { @@ -201,8 +200,6 @@ TEST(HTTPClient, TestMultithreading) FirstThread.join(); // pauses until first finishes SecondThread.join(); // pauses until second finishes ThirdThread.join(); // pauses until third finishes - - ASSERT_EQ(uInitialCount, CHTTPClient::GetCurlSessionCount()); } // HTTP tests using a fixture @@ -226,6 +223,7 @@ TEST_F(HTTPTest, TestDownloadFile) // to display a beautiful progress bar on console m_pHTTPClient->SetProgressFnCallback(nullptr, &TestProgressCallback); +#ifdef LINUX ASSERT_TRUE(m_pHTTPClient->DownloadFile("test.pem", "https://curl.haxx.se/ca/cacert.pem", lHTTPSCode)); /* to properly show the progress bar */ std::cout << std::endl; @@ -236,6 +234,36 @@ TEST_F(HTTPTest, TestDownloadFile) /* delete test file */ EXPECT_TRUE(remove("test.pem") == 0); +#else + // Convert file name from ANSI to UTF8 + std::string localFile = CHTTPClient::AnsiToUtf8("test_nom_accentué.pem"); + + ASSERT_TRUE(m_pHTTPClient->DownloadFile(localFile, "https://curl.haxx.se/ca/cacert.pem", lHTTPSCode)); + /* to properly show the progress bar */ + std::cout << std::endl; + + /* TODO : we can check the SHA1 of the downloaded file with a value provided in the INI file */ + + EXPECT_EQ(200, lHTTPSCode); + + /* delete test file */ + EXPECT_TRUE(remove("test_nom_accentué.pem") == 0); +#endif +} + +TEST_F(HTTPTest, TestDownloadFileToMemory) +{ + std::vector output; + long lHTTPSCode = 0; + + // to display a beautiful progress bar on console + m_pHTTPClient->SetProgressFnCallback(nullptr, &TestProgressCallback); + + ASSERT_TRUE(m_pHTTPClient->DownloadFile(output, "https://curl.haxx.se/ca/cacert.pem", lHTTPSCode)); + /* to properly show the progress bar */ + std::cout << std::endl; + + EXPECT_EQ(200, lHTTPSCode); } // check for failure: inexistant file @@ -353,7 +381,7 @@ TEST_F(RestClientTest, TestRestClientGETBodyCode) rapidjson::Value::MemberIterator itTokenUrl = document.FindMember("url"); ASSERT_TRUE(itTokenUrl != document.MemberEnd()); ASSERT_TRUE(itTokenUrl->value.IsString()); - EXPECT_STREQ("https://httpbin.org/get", itTokenUrl->value.GetString()); + EXPECT_STREQ("http://httpbin.org/get", itTokenUrl->value.GetString()); rapidjson::Value::MemberIterator itTokenHeaders = document.FindMember("headers"); ASSERT_TRUE(itTokenHeaders != document.MemberEnd()); @@ -428,7 +456,7 @@ TEST_F(RestClientTest, TestRestClientPOSTBody) rapidjson::Value::MemberIterator itTokenUrl = document.FindMember("url"); ASSERT_TRUE(itTokenUrl != document.MemberEnd()); ASSERT_TRUE(itTokenUrl->value.IsString()); - EXPECT_STREQ("https://httpbin.org/post", itTokenUrl->value.GetString()); + EXPECT_STREQ("http://httpbin.org/post", itTokenUrl->value.GetString()); rapidjson::Value::MemberIterator itTokenHeaders = document.FindMember("headers"); ASSERT_TRUE(itTokenHeaders != document.MemberEnd()); @@ -473,7 +501,7 @@ TEST_F(RestClientTest, TestRestClientPUTString) rapidjson::Value::MemberIterator itTokenUrl = document.FindMember("url"); ASSERT_TRUE(itTokenUrl != document.MemberEnd()); ASSERT_TRUE(itTokenUrl->value.IsString()); - EXPECT_STREQ("https://httpbin.org/put", itTokenUrl->value.GetString()); + EXPECT_STREQ("http://httpbin.org/put", itTokenUrl->value.GetString()); rapidjson::Value::MemberIterator itTokenHeaders = document.FindMember("headers"); ASSERT_TRUE(itTokenHeaders != document.MemberEnd()); @@ -505,7 +533,7 @@ TEST_F(RestClientTest, TestRestClientPUTBuffer) rapidjson::Value::MemberIterator itTokenUrl = document.FindMember("url"); ASSERT_TRUE(itTokenUrl != document.MemberEnd()); ASSERT_TRUE(itTokenUrl->value.IsString()); - EXPECT_STREQ("https://httpbin.org/put", itTokenUrl->value.GetString()); + EXPECT_STREQ("http://httpbin.org/put", itTokenUrl->value.GetString()); rapidjson::Value::MemberIterator itTokenHeaders = document.FindMember("headers"); ASSERT_TRUE(itTokenHeaders != document.MemberEnd()); @@ -552,7 +580,7 @@ TEST_F(RestClientTest, TestRestClientDeleteBody) rapidjson::Value::MemberIterator itTokenUrl = document.FindMember("url"); ASSERT_TRUE(itTokenUrl != document.MemberEnd()); ASSERT_TRUE(itTokenUrl->value.IsString()); - EXPECT_STREQ("https://httpbin.org/delete", itTokenUrl->value.GetString()); + EXPECT_STREQ("http://httpbin.org/delete", itTokenUrl->value.GetString()); rapidjson::Value::MemberIterator itTokenHeaders = document.FindMember("headers"); ASSERT_TRUE(itTokenHeaders != document.MemberEnd()); diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0