summaryrefslogtreecommitdiff
path: root/libcurl
diff options
context:
space:
mode:
Diffstat (limited to 'libcurl')
-rw-r--r--libcurl/curl_wrap.cpp398
-rw-r--r--libcurl/curl_wrap.h158
-rw-r--r--libcurl/rest.cpp208
-rw-r--r--libcurl/rest.h52
4 files changed, 436 insertions, 380 deletions
diff --git a/libcurl/curl_wrap.cpp b/libcurl/curl_wrap.cpp
new file mode 100644
index 00000000..88bde50e
--- /dev/null
+++ b/libcurl/curl_wrap.cpp
@@ -0,0 +1,398 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "curl_wrap.h"
+#include <zen/sys_info.h>
+#include <zen/http.h>
+#include <zen/open_ssl.h>
+#include <zen/thread.h>
+ #include <fcntl.h>
+
+using namespace zen;
+
+
+namespace
+{
+int curlInitLevel = 0; //support interleaving initialization calls!
+//zero-initialized POD => not subject to static initialization order fiasco
+}
+
+void zen::libcurlInit()
+{
+ assert(runningOnMainThread()); //all OpenSSL/libssh2/libcurl require init on main thread!
+ assert(curlInitLevel >= 0);
+ if (++curlInitLevel != 1) //non-atomic => require call from main thread
+ return;
+
+
+ openSslInit();
+
+ [[maybe_unused]] const CURLcode rc2 = ::curl_global_init(CURL_GLOBAL_NOTHING /*CURL_GLOBAL_DEFAULT = CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32*/);
+ assert(rc2 == CURLE_OK);
+}
+
+
+void zen::libcurlTearDown()
+{
+ assert(runningOnMainThread()); //+ avoid race condition on "curlInitLevel"
+ assert(curlInitLevel >= 1);
+ if (--curlInitLevel != 0)
+ return;
+
+ ::curl_global_cleanup();
+ openSslTearDown();
+}
+
+
+HttpSession::HttpSession(const Zstring& server, bool useTls, const Zstring& caCertFilePath, std::chrono::seconds timeOut) : //throw SysError
+ serverPrefix_((useTls ? "https://" : "http://") + utfTo<std::string>(server)),
+ caCertFilePath_(utfTo<std::string>(caCertFilePath)),
+ timeOutSec_(timeOut) {}
+
+
+HttpSession::~HttpSession()
+{
+ if (easyHandle_)
+ ::curl_easy_cleanup(easyHandle_);
+}
+
+
+HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
+ const std::vector<std::string>& extraHeaders, const std::vector<CurlOption>& extraOptions,
+ const std::function<void (std::span<const char> buf)>& writeResponse /*throw X*/, //
+ const std::function<size_t(std::span< char> buf)>& readRequest /*throw X*/, //optional
+ const std::function<void (const std::string_view& header)>& receiveHeader /*throw X*/) //throw SysError, X
+{
+ if (!easyHandle_)
+ {
+ easyHandle_ = ::curl_easy_init();
+ if (!easyHandle_)
+ throw SysError(formatSystemError("curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), L""));
+ }
+ else
+ ::curl_easy_reset(easyHandle_);
+
+
+ std::vector<CurlOption> options;
+
+ char curlErrorBuf[CURL_ERROR_SIZE] = {};
+ options.emplace_back(CURLOPT_ERRORBUFFER, curlErrorBuf);
+
+ options.emplace_back(CURLOPT_USERAGENT, "FreeFileSync"); //default value; may be overwritten by caller
+
+ //lifetime: keep alive until after curl_easy_setopt() below
+ const std::string curlPath = serverPrefix_ + serverRelPath;
+ options.emplace_back(CURLOPT_URL, curlPath.c_str());
+
+ options.emplace_back(CURLOPT_ACCEPT_ENCODING, ""); //libcurl: generate Accept-Encoding header containing all built-in supported encodings
+ //=> usually generates "Accept-Encoding: deflate, gzip" - note: "gzip" used by Google Drive
+
+ options.emplace_back(CURLOPT_NOSIGNAL, 1); //thread-safety: https://curl.haxx.se/libcurl/c/threadsafe.html
+
+ options.emplace_back(CURLOPT_CONNECTTIMEOUT, timeOutSec_.count());
+
+ //CURLOPT_TIMEOUT: "Since this puts a hard limit for how long time a request is allowed to take, it has limited use in dynamic use cases with varying transfer times."
+ options.emplace_back(CURLOPT_LOW_SPEED_TIME, timeOutSec_.count());
+ options.emplace_back(CURLOPT_LOW_SPEED_LIMIT, 1); //[bytes], can't use "0" which means "inactive", so use some low number
+
+
+ std::exception_ptr userCallbackException;
+
+ //libcurl does *not* set FD_CLOEXEC for us! https://github.com/curl/curl/issues/2252
+ auto onSocketCreate = [&](curl_socket_t curlfd, curlsocktype purpose)
+ {
+ assert(::fcntl(curlfd, F_GETFD) == 0);
+ if (::fcntl(curlfd, F_SETFD, FD_CLOEXEC) == -1) //=> RACE-condition if other thread calls fork/execv before this thread sets FD_CLOEXEC!
+ {
+ userCallbackException = std::make_exception_ptr(SysError(formatSystemError("fcntl(FD_CLOEXEC)", errno)));
+ return CURL_SOCKOPT_ERROR;
+ }
+ return CURL_SOCKOPT_OK;
+ };
+
+ using SocketCbType = decltype(onSocketCreate);
+ using SocketCbWrapperType = int (*)(SocketCbType* clientp, curl_socket_t curlfd, curlsocktype purpose); //needed for cdecl function pointer cast
+ SocketCbWrapperType onSocketCreateWrapper = [](SocketCbType* clientp, curl_socket_t curlfd, curlsocktype purpose)
+ {
+ return (*clientp)(curlfd, purpose); //free this poor little C-API from its shackles and redirect to a proper lambda
+ };
+
+ options.emplace_back(CURLOPT_SOCKOPTFUNCTION, onSocketCreateWrapper);
+ options.emplace_back(CURLOPT_SOCKOPTDATA, &onSocketCreate);
+
+ //libcurl forwards this char-string to OpenSSL as is, which - thank god - accepts UTF8
+ if (caCertFilePath_.empty())
+ {
+ options.emplace_back(CURLOPT_CAINFO, 0); //see remarks in ftp.cpp
+ options.emplace_back(CURLOPT_SSL_VERIFYPEER, 0);
+ options.emplace_back(CURLOPT_SSL_VERIFYHOST, 0);
+ }
+ else
+ options.emplace_back(CURLOPT_CAINFO, caCertFilePath_.c_str()); //hopefully latest version from https://curl.haxx.se/docs/caextract.html
+ //CURLOPT_SSL_VERIFYPEER => already active by default
+ //CURLOPT_SSL_VERIFYHOST =>
+
+ //---------------------------------------------------
+ auto onHeaderReceived = [&](const void* buffer, size_t len)
+ {
+ try
+ {
+ receiveHeader({static_cast<const char*>(buffer), len}); //throw X
+ return len;
+ }
+ catch (...)
+ {
+ userCallbackException = std::current_exception();
+ return len + 1; //signal error condition => CURLE_WRITE_ERROR
+ }
+ };
+ using HeaderCbType = decltype(onHeaderReceived);
+ using HeaderCbWrapperType = size_t (*)(const void* buffer, size_t size, size_t nitems, HeaderCbType* callbackData); //needed for cdecl function pointer cast
+ HeaderCbWrapperType onHeaderReceivedWrapper = [](const void* buffer, size_t size, size_t nitems, HeaderCbType* callbackData)
+ {
+ return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda
+ };
+ //---------------------------------------------------
+ auto onBytesReceived = [&](const void* buffer, size_t len)
+ {
+ try
+ {
+ writeResponse({static_cast<const char*>(buffer), len}); //throw X
+ return len;
+ }
+ catch (...)
+ {
+ userCallbackException = std::current_exception();
+ return len + 1; //signal error condition => CURLE_WRITE_ERROR
+ }
+ };
+ using ReadCbType = decltype(onBytesReceived);
+ using ReadCbWrapperType = size_t (*)(const void* buffer, size_t size, size_t nitems, ReadCbType* callbackData); //needed for cdecl function pointer cast
+ ReadCbWrapperType onBytesReceivedWrapper = [](const void* buffer, size_t size, size_t nitems, ReadCbType* callbackData)
+ {
+ return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda
+ };
+ //---------------------------------------------------
+ auto getBytesToSend = [&](void* buffer, size_t len) -> size_t
+ {
+ try
+ {
+ //libcurl calls back until 0 bytes are returned (Posix read() semantics), or,
+ //if CURLOPT_INFILESIZE_LARGE was set, after exactly this amount of bytes
+ const size_t bytesRead = readRequest({static_cast<char*>(buffer), len});//throw X; return "bytesToRead" bytes unless end of stream!
+ return bytesRead;
+ }
+ catch (...)
+ {
+ userCallbackException = std::current_exception();
+ return CURL_READFUNC_ABORT; //signal error condition => CURLE_ABORTED_BY_CALLBACK
+ }
+ };
+ using WriteCbType = decltype(getBytesToSend);
+ using WriteCbWrapperType = size_t (*)(void* buffer, size_t size, size_t nitems, WriteCbType* callbackData);
+ WriteCbWrapperType getBytesToSendWrapper = [](void* buffer, size_t size, size_t nitems, WriteCbType* callbackData)
+ {
+ return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda
+ };
+ //---------------------------------------------------
+ if (receiveHeader)
+ {
+ options.emplace_back(CURLOPT_HEADERDATA, &onHeaderReceived);
+ options.emplace_back(CURLOPT_HEADERFUNCTION, onHeaderReceivedWrapper);
+ }
+ if (writeResponse)
+ {
+ options.emplace_back(CURLOPT_WRITEDATA, &onBytesReceived);
+ options.emplace_back(CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper);
+ }
+ if (readRequest)
+ {
+ if (std::all_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option != CURLOPT_POST; }))
+ /**/options.emplace_back(CURLOPT_UPLOAD, 1); //issues HTTP PUT
+ options.emplace_back(CURLOPT_READDATA, &getBytesToSend);
+ options.emplace_back(CURLOPT_READFUNCTION, getBytesToSendWrapper);
+ }
+
+ if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_WRITEFUNCTION || o.option == CURLOPT_READFUNCTION; }))
+ /**/ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //Option already used here!
+
+ if (readRequest && std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_POSTFIELDS; }))
+ /**/ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS
+
+ //---------------------------------------------------
+ curl_slist* headers = nullptr; //"libcurl will not copy the entire list so you must keep it!"
+ ZEN_ON_SCOPE_EXIT(::curl_slist_free_all(headers));
+
+ for (const std::string& headerLine : extraHeaders)
+ headers = ::curl_slist_append(headers, headerLine.c_str());
+
+ //WTF!!! 1-sec delay when server doesn't support "Expect: 100-continue"!! https://stackoverflow.com/questions/49670008/how-to-disable-expect-100-continue-in-libcurl
+ headers = ::curl_slist_append(headers, "Expect:"); //guess, what: www.googleapis.com doesn't support it! e.g. gdriveUploadFile()
+
+ if (headers)
+ options.emplace_back(CURLOPT_HTTPHEADER, headers);
+ //---------------------------------------------------
+
+ append(options, extraOptions);
+
+ applyCurlOptions(easyHandle_, options); //throw SysError
+
+ //=======================================================================================================
+ const CURLcode rcPerf = ::curl_easy_perform(easyHandle_);
+ //WTF: curl_easy_perform() considers FTP response codes 4XX, 5XX as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
+ //=> at least libcurl is aware: CURLOPT_FAILONERROR: "request failure on HTTP response >= 400"; default: "0, do not fail on error"
+ //https://curl.haxx.se/docs/faq.html#curl_doesn_t_return_error_for_HT
+ //=> Curiously Google also screws up in their REST API design and returns HTTP 4XX status for domain-level errors!
+ //=> let caller handle HTTP status to work around this mess!
+
+ if (userCallbackException)
+ std::rethrow_exception(userCallbackException); //throw X
+ //=======================================================================================================
+
+ long httpStatus = 0; //optional
+ /*const CURLcode rc = */ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatus);
+
+ if (rcPerf != CURLE_OK)
+ {
+ std::wstring errorMsg = trimCpy(utfTo<std::wstring>(curlErrorBuf)); //optional
+
+ if (httpStatus != 0) //optional
+ errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpError(httpStatus);
+#if 0
+ //utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting
+ //use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html
+ long nativeErrorCode = 0;
+ if (::curl_easy_getinfo(easyHandle, CURLINFO_OS_ERRNO, &nativeErrorCode) == CURLE_OK)
+ if (nativeErrorCode != 0)
+ errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
+#endif
+ throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
+ }
+
+ lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
+ return {static_cast<int>(httpStatus) /*, contentType ? contentType : ""*/};
+}
+
+
+std::wstring zen::formatCurlStatusCode(CURLcode sc)
+{
+ switch (sc)
+ {
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OK);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNSUPPORTED_PROTOCOL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FAILED_INIT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_URL_MALFORMAT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_NOT_BUILT_IN);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_RESOLVE_PROXY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_RESOLVE_HOST);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_CONNECT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_WEIRD_SERVER_REPLY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_ACCESS_DENIED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_ACCEPT_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_PASS_REPLY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_ACCEPT_TIMEOUT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_PASV_REPLY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_227_FORMAT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_CANT_GET_HOST);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP2);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_SET_TYPE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PARTIAL_FILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_RETR_FILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE20);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUOTE_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP_RETURNED_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_WRITE_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE24);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UPLOAD_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_READ_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OUT_OF_MEMORY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OPERATION_TIMEDOUT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE29);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_PORT_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_USE_REST);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE32);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RANGE_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP_POST_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CONNECT_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_DOWNLOAD_RESUME);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FILE_COULDNT_READ_FILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_CANNOT_BIND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_SEARCH_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE40);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FUNCTION_NOT_FOUND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_ABORTED_BY_CALLBACK);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_FUNCTION_ARGUMENT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE44);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_INTERFACE_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE46);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TOO_MANY_REDIRECTS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNKNOWN_OPTION);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SETOPT_OPTION_SYNTAX);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE50);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE51);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_GOT_NOTHING);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_NOTFOUND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_SETFAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SEND_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECV_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE57);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CERTPROBLEM);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CIPHER);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PEER_FAILED_VERIFICATION);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_CONTENT_ENCODING);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_INVALID_URL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FILESIZE_EXCEEDED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_USE_SSL_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SEND_FAIL_REWIND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_INITFAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LOGIN_DENIED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_NOTFOUND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_PERM);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_DISK_FULL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_ILLEGAL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_UNKNOWNID);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_FILE_EXISTS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_NOSUCHUSER);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CONV_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CONV_REQD);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CACERT_BADFILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_FILE_NOT_FOUND);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSH);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_SHUTDOWN_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AGAIN);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CRL_BADFILE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ISSUER_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_PRET_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RTSP_CSEQ_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RTSP_SESSION_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_BAD_FILE_LIST);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CHUNK_FAILED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_NO_CONNECTION_AVAILABLE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_PINNEDPUBKEYNOTMATCH);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_INVALIDCERTSTATUS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP2_STREAM);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECURSIVE_API_CALL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AUTH_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP3);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUIC_CONNECT_ERROR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PROXY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CLIENTCERT);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST);
+ }
+ static_assert(CURL_LAST == CURLE_SSL_CLIENTCERT + 1);
+
+ return replaceCpy<std::wstring>(L"Curl status %x", L"%x", numberTo<std::wstring>(static_cast<int>(sc)));
+}
+
+
+void zen::applyCurlOptions(CURL* easyHandle, const std::vector<CurlOption>& options) //throw SysError
+{
+ for (const CurlOption& opt : options)
+ if (const CURLcode rc = ::curl_easy_setopt(easyHandle, opt.option, opt.value);
+ rc != CURLE_OK)
+ throw SysError(formatSystemError("curl_easy_setopt(" + numberTo<std::string>(static_cast<int>(opt.option)) + ")",
+ formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+}
diff --git a/libcurl/curl_wrap.h b/libcurl/curl_wrap.h
index 810d735f..ba9fbc13 100644
--- a/libcurl/curl_wrap.h
+++ b/libcurl/curl_wrap.h
@@ -7,9 +7,11 @@
#ifndef CURL_WRAP_H_2879058325032785032789645
#define CURL_WRAP_H_2879058325032785032789645
-#include <zen/scope_guard.h>
+#include <chrono>
+#include <span>
+#include <functional>
#include <zen/sys_error.h>
-
+#include <zen/zstring.h>
//-------------------------------------------------
@@ -22,6 +24,10 @@
namespace zen
{
+void libcurlInit();
+void libcurlTearDown();
+
+
struct CurlOption
{
template <class T>
@@ -34,128 +40,40 @@ struct CurlOption
uint64_t value = 0;
};
-namespace
-{
-std::wstring formatCurlStatusCode(CURLcode sc)
+
+class HttpSession
{
- switch (sc)
+public:
+ HttpSession(const Zstring& server, bool useTls, const Zstring& caCertFilePath /*optional*/, std::chrono::seconds timeOut); //throw SysError
+ ~HttpSession();
+
+ struct Result
{
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OK);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNSUPPORTED_PROTOCOL);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FAILED_INIT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_URL_MALFORMAT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_NOT_BUILT_IN);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_RESOLVE_PROXY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_RESOLVE_HOST);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_COULDNT_CONNECT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_WEIRD_SERVER_REPLY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_ACCESS_DENIED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_ACCEPT_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_PASS_REPLY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_ACCEPT_TIMEOUT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_PASV_REPLY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_WEIRD_227_FORMAT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_CANT_GET_HOST);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP2);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_SET_TYPE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PARTIAL_FILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_RETR_FILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE20);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUOTE_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP_RETURNED_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_WRITE_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE24);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UPLOAD_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_READ_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OUT_OF_MEMORY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OPERATION_TIMEDOUT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE29);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_PORT_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_COULDNT_USE_REST);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE32);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RANGE_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP_POST_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CONNECT_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_DOWNLOAD_RESUME);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FILE_COULDNT_READ_FILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_CANNOT_BIND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_SEARCH_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE40);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FUNCTION_NOT_FOUND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_ABORTED_BY_CALLBACK);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_FUNCTION_ARGUMENT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE44);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_INTERFACE_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE46);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TOO_MANY_REDIRECTS);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNKNOWN_OPTION);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SETOPT_OPTION_SYNTAX);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE50);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE51);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_GOT_NOTHING);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_NOTFOUND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_SETFAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SEND_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECV_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_OBSOLETE57);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CERTPROBLEM);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CIPHER);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PEER_FAILED_VERIFICATION);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_BAD_CONTENT_ENCODING);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LDAP_INVALID_URL);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FILESIZE_EXCEEDED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_USE_SSL_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SEND_FAIL_REWIND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ENGINE_INITFAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_LOGIN_DENIED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_NOTFOUND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_PERM);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_DISK_FULL);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_ILLEGAL);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_UNKNOWNID);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_FILE_EXISTS);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TFTP_NOSUCHUSER);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CONV_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CONV_REQD);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CACERT_BADFILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_REMOTE_FILE_NOT_FOUND);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSH);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_SHUTDOWN_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AGAIN);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CRL_BADFILE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_ISSUER_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_PRET_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RTSP_CSEQ_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RTSP_SESSION_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_FTP_BAD_FILE_LIST);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_CHUNK_FAILED);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_NO_CONNECTION_AVAILABLE);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_PINNEDPUBKEYNOTMATCH);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_INVALIDCERTSTATUS);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP2_STREAM);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_RECURSIVE_API_CALL);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_AUTH_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP3);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUIC_CONNECT_ERROR);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PROXY);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CLIENTCERT);
- ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST);
- }
- static_assert(CURL_LAST == CURLE_SSL_CLIENTCERT + 1);
-
- return replaceCpy<std::wstring>(L"Curl status %x", L"%x", numberTo<std::wstring>(static_cast<int>(sc)));
-}
+ int statusCode = 0;
+ //std::string contentType;
+ };
+ Result perform(const std::string& serverRelPath,
+ const std::vector<std::string>& extraHeaders, const std::vector<CurlOption>& extraOptions,
+ const std::function<void (std::span<const char> buf)>& writeResponse /*throw X*/, //
+ const std::function<size_t(std::span< char> buf)>& readRequest /*throw X*/, //optional
+ const std::function<void (const std::string_view& header)>& receiveHeader /*throw X*/); //throw SysError, X
+ std::chrono::steady_clock::time_point getLastUseTime() const { return lastSuccessfulUseTime_; }
-void applyCurlOptions(CURL* easyHandle, const std::vector<CurlOption>& options) //throw SysError
-{
- for (const CurlOption& opt : options)
- if (const CURLcode rc = ::curl_easy_setopt(easyHandle, opt.option, opt.value);
- rc != CURLE_OK)
- throw SysError(formatSystemError("curl_easy_setopt(" + numberTo<std::string>(static_cast<int>(opt.option)) + ")",
- formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
-}
-}
+private:
+ HttpSession (const HttpSession&) = delete;
+ HttpSession& operator=(const HttpSession&) = delete;
+
+ const std::string serverPrefix_;
+ const std::string caCertFilePath_; //optional
+ const std::chrono::seconds timeOutSec_;
+ CURL* easyHandle_ = nullptr;
+ std::chrono::steady_clock::time_point lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
+};
+
+
+std::wstring formatCurlStatusCode(CURLcode sc);
+void applyCurlOptions(CURL* easyHandle, const std::vector<CurlOption>& options); //throw SysError
}
#else
diff --git a/libcurl/rest.cpp b/libcurl/rest.cpp
deleted file mode 100644
index e80a4961..00000000
--- a/libcurl/rest.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "rest.h"
-#include <zen/sys_info.h>
-#include <zen/http.h>
- #include <fcntl.h>
-
-using namespace zen;
-
-
-HttpSession::HttpSession(const Zstring& server, const Zstring& caCertFilePath, std::chrono::seconds timeOut) : //throw SysError
- server_(utfTo<std::string>(server)),
- caCertFilePath_(utfTo<std::string>(caCertFilePath)),
- timeOutSec_(timeOut) {}
-
-
-HttpSession::~HttpSession()
-{
- if (easyHandle_)
- ::curl_easy_cleanup(easyHandle_);
-}
-
-
-HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
- const std::vector<std::string>& extraHeaders, const std::vector<CurlOption>& extraOptions, //throw SysError
- const std::function<void (const void* buffer, size_t bytesToWrite)>& writeResponse /*throw X*/, //optional
- const std::function<size_t( void* buffer, size_t bytesToRead )>& readRequest /*throw X*/) //
-{
- if (!easyHandle_)
- {
- easyHandle_ = ::curl_easy_init();
- if (!easyHandle_)
- throw SysError(formatSystemError("curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), L""));
- }
- else
- ::curl_easy_reset(easyHandle_);
-
-
- std::vector<CurlOption> options;
-
- char curlErrorBuf[CURL_ERROR_SIZE] = {};
- options.emplace_back(CURLOPT_ERRORBUFFER, curlErrorBuf);
-
- options.emplace_back(CURLOPT_USERAGENT, "FreeFileSync"); //default value; may be overwritten by caller
-
- //lifetime: keep alive until after curl_easy_setopt() below
- std::string curlPath = "https://" + server_ + serverRelPath;
- options.emplace_back(CURLOPT_URL, curlPath.c_str());
-
- options.emplace_back(CURLOPT_ACCEPT_ENCODING, "gzip"); //won't hurt + used by Google Drive
-
- options.emplace_back(CURLOPT_NOSIGNAL, 1L); //thread-safety: https://curl.haxx.se/libcurl/c/threadsafe.html
-
- options.emplace_back(CURLOPT_CONNECTTIMEOUT, timeOutSec_.count());
-
- //CURLOPT_TIMEOUT: "Since this puts a hard limit for how long time a request is allowed to take, it has limited use in dynamic use cases with varying transfer times."
- options.emplace_back(CURLOPT_LOW_SPEED_TIME, timeOutSec_.count());
- options.emplace_back(CURLOPT_LOW_SPEED_LIMIT, 1L); //[bytes], can't use "0" which means "inactive", so use some low number
-
-
- std::exception_ptr userCallbackException;
-
- //libcurl does *not* set FD_CLOEXEC for us! https://github.com/curl/curl/issues/2252
- auto onSocketCreate = [&](curl_socket_t curlfd, curlsocktype purpose)
- {
- assert(::fcntl(curlfd, F_GETFD) == 0);
- if (::fcntl(curlfd, F_SETFD, FD_CLOEXEC) == -1) //=> RACE-condition if other thread calls fork/execv before this thread sets FD_CLOEXEC!
- {
- userCallbackException = std::make_exception_ptr(SysError(formatSystemError("fcntl(FD_CLOEXEC)", errno)));
- return CURL_SOCKOPT_ERROR;
- }
- return CURL_SOCKOPT_OK;
- };
-
- using SocketCbType = decltype(onSocketCreate);
- using SocketCbWrapperType = int (*)(SocketCbType* clientp, curl_socket_t curlfd, curlsocktype purpose); //needed for cdecl function pointer cast
- SocketCbWrapperType onSocketCreateWrapper = [](SocketCbType* clientp, curl_socket_t curlfd, curlsocktype purpose)
- {
- return (*clientp)(curlfd, purpose); //free this poor little C-API from its shackles and redirect to a proper lambda
- };
-
- options.emplace_back(CURLOPT_SOCKOPTFUNCTION, onSocketCreateWrapper);
- options.emplace_back(CURLOPT_SOCKOPTDATA, &onSocketCreate);
-
- //libcurl forwards this char-string to OpenSSL as is, which - thank god - accepts UTF8
- options.emplace_back(CURLOPT_CAINFO, caCertFilePath_.c_str()); //hopefully latest version from https://curl.haxx.se/docs/caextract.html
- //CURLOPT_SSL_VERIFYPEER => already active by default
- //CURLOPT_SSL_VERIFYHOST =>
-
- //---------------------------------------------------
- auto onBytesReceived = [&](const void* buffer, size_t len)
- {
- try
- {
- writeResponse(buffer, len); //throw X
- return len;
- }
- catch (...)
- {
- userCallbackException = std::current_exception();
- return len + 1; //signal error condition => CURLE_WRITE_ERROR
- }
- };
- using ReadCbType = decltype(onBytesReceived);
- using ReadCbWrapperType = size_t (*)(const void* buffer, size_t size, size_t nitems, ReadCbType* callbackData); //needed for cdecl function pointer cast
- ReadCbWrapperType onBytesReceivedWrapper = [](const void* buffer, size_t size, size_t nitems, ReadCbType* callbackData)
- {
- return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda
- };
- //---------------------------------------------------
- auto getBytesToSend = [&](void* buffer, size_t len) -> size_t
- {
- try
- {
- //libcurl calls back until 0 bytes are returned (Posix read() semantics), or,
- //if CURLOPT_INFILESIZE_LARGE was set, after exactly this amount of bytes
- const size_t bytesRead = readRequest(buffer, len);//throw X; return "bytesToRead" bytes unless end of stream!
- return bytesRead;
- }
- catch (...)
- {
- userCallbackException = std::current_exception();
- return CURL_READFUNC_ABORT; //signal error condition => CURLE_ABORTED_BY_CALLBACK
- }
- };
- using WriteCbType = decltype(getBytesToSend);
- using WriteCbWrapperType = size_t (*)(void* buffer, size_t size, size_t nitems, WriteCbType* callbackData);
- WriteCbWrapperType getBytesToSendWrapper = [](void* buffer, size_t size, size_t nitems, WriteCbType* callbackData)
- {
- return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda
- };
- //---------------------------------------------------
- if (writeResponse)
- {
- options.emplace_back(CURLOPT_WRITEDATA, &onBytesReceived);
- options.emplace_back(CURLOPT_WRITEFUNCTION, onBytesReceivedWrapper);
- }
- if (readRequest)
- {
- if (std::all_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option != CURLOPT_POST; }))
- /**/options.emplace_back(CURLOPT_UPLOAD, 1L); //issues HTTP PUT
- options.emplace_back(CURLOPT_READDATA, &getBytesToSend);
- options.emplace_back(CURLOPT_READFUNCTION, getBytesToSendWrapper);
- }
-
- if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_WRITEFUNCTION || o.option == CURLOPT_READFUNCTION; }))
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //Option already used here!
-
- if (readRequest && std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_POSTFIELDS; }))
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS
-
- //---------------------------------------------------
- curl_slist* headers = nullptr; //"libcurl will not copy the entire list so you must keep it!"
- ZEN_ON_SCOPE_EXIT(::curl_slist_free_all(headers));
-
- for (const std::string& headerLine : extraHeaders)
- headers = ::curl_slist_append(headers, headerLine.c_str());
-
- //WTF!!! 1-sec delay when server doesn't support "Expect: 100-continue"!! https://stackoverflow.com/questions/49670008/how-to-disable-expect-100-continue-in-libcurl
- headers = ::curl_slist_append(headers, "Expect:"); //guess, what: www.googleapis.com doesn't support it! e.g. gdriveUploadFile()
-
- if (headers)
- options.emplace_back(CURLOPT_HTTPHEADER, headers);
- //---------------------------------------------------
-
- append(options, extraOptions);
-
- applyCurlOptions(easyHandle_, options); //throw SysError
-
- //=======================================================================================================
- const CURLcode rcPerf = ::curl_easy_perform(easyHandle_);
- //WTF: curl_easy_perform() considers FTP response codes 4XX, 5XX as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
- //=> at least libcurl is aware: CURLOPT_FAILONERROR: "request failure on HTTP response >= 400"; default: "0, do not fail on error"
- //https://curl.haxx.se/docs/faq.html#curl_doesn_t_return_error_for_HT
- //=> Curiously Google also screws up in their REST API design and returns HTTP 4XX status for domain-level errors!
- //=> let caller handle HTTP status to work around this mess!
-
- if (userCallbackException)
- std::rethrow_exception(userCallbackException); //throw X
- //=======================================================================================================
-
- long httpStatus = 0; //optional
- /*const CURLcode rc = */ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatus);
-
- if (rcPerf != CURLE_OK)
- {
- std::wstring errorMsg = trimCpy(utfTo<std::wstring>(curlErrorBuf)); //optional
-
- if (httpStatus != 0) //optional
- errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpError(httpStatus);
-#if 0
- //utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting
- //use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html
- long nativeErrorCode = 0;
- if (::curl_easy_getinfo(easyHandle, CURLINFO_OS_ERRNO, &nativeErrorCode) == CURLE_OK)
- if (nativeErrorCode != 0)
- errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
-#endif
- throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
- }
-
- lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
- return {static_cast<int>(httpStatus) /*, contentType ? contentType : ""*/};
-}
diff --git a/libcurl/rest.h b/libcurl/rest.h
deleted file mode 100644
index df41a3cc..00000000
--- a/libcurl/rest.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef REST_H_07018456781346523454
-#define REST_H_07018456781346523454
-
-#include <chrono>
-#include <functional>
-#include <zen/sys_error.h>
-#include <zen/zstring.h>
-#include "curl_wrap.h" //DON'T include <curl/curl.h> directly!
-
-
-namespace zen
-{
-//Initialization requirement: 1. WSAStartup 2. OpenSSL 3. curl_global_init()
-// => use UniCounterCookie!
-
-class HttpSession
-{
-public:
- HttpSession(const Zstring& server, const Zstring& caCertFilePath, std::chrono::seconds timeOut); //throw SysError
- ~HttpSession();
-
- struct Result
- {
- int statusCode = 0;
- //std::string contentType;
- };
- Result perform(const std::string& serverRelPath,
- const std::vector<std::string>& extraHeaders, const std::vector<CurlOption>& extraOptions, //throw SysError
- const std::function<void (const void* buffer, size_t bytesToWrite)>& writeResponse /*throw X*/, //optional
- const std::function<size_t( void* buffer, size_t bytesToRead )>& readRequest /*throw X*/); //
-
- std::chrono::steady_clock::time_point getLastUseTime() const { return lastSuccessfulUseTime_; }
-
-private:
- HttpSession (const HttpSession&) = delete;
- HttpSession& operator=(const HttpSession&) = delete;
-
- const std::string server_;
- const std::string caCertFilePath_;
- const std::chrono::seconds timeOutSec_;
- CURL* easyHandle_ = nullptr;
- std::chrono::steady_clock::time_point lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
-};
-}
-
-#endif //REST_H_07018456781346523454
bgstack15