LCOV - code coverage report
Current view: top level - core/network - SPNetworkSetup.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 267 432 61.8 %
Date: 2024-05-12 00:16:13 Functions: 47 117 40.2 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       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
      13             : all 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
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "SPNetworkContext.h"
      25             : #include "SPNetworkData.h"
      26             : #include "curl/curl.h"
      27             : 
      28             : #ifndef SP_NETWORK_LOG
      29             : #define SP_NETWORK_LOG(...)
      30             : #endif
      31             : 
      32             : namespace STAPPLER_VERSIONIZED stappler::network {
      33             : 
      34             : #define SP_TERMINATED_DATA(view) (view.terminated()?view.data():view.str<Interface>().data())
      35             : 
      36             : static constexpr auto SP_NETWORK_PROGRESS_TIMEOUT = TimeInterval::microseconds(250000ull);
      37             : static constexpr auto s_UserAgent = "Stappler/1 CURL";
      38             : 
      39             : struct CurlHandle;
      40             : 
      41             : SPUNUSED static StringView getCABundle();
      42             : 
      43             : SPUNUSED static CURL * CurlHandle_getHandle(bool reuse, memory::pool_t *pool);
      44             : SPUNUSED static void CurlHandle_releaseHandle(CURL *curl, bool reuse, bool success, memory::pool_t *pool);
      45             : 
      46          50 : static size_t _writeDummy(const void *data, size_t size, size_t nmemb, void *userptr) {
      47          50 :         return size * nmemb;
      48             : }
      49             : 
      50             : template <typename Interface>
      51           0 : static size_t _writeDebug(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
      52           0 :         auto task = static_cast<HandleData<Interface> *>(userptr);
      53           0 :         task->process.debugData.write(data, size);
      54           0 :         return size;
      55             : }
      56             : 
      57             : template <typename Interface>
      58        3012 : static size_t _writeData(char *data, size_t size, size_t nmemb, void *userptr) {
      59        3012 :         auto task = static_cast<HandleData<Interface> *>(userptr);
      60             : 
      61        6024 :         return std::visit([&] (auto &&arg) {
      62             :                 using T = std::decay_t<decltype(arg)>;
      63             :                 if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
      64        3012 :                         return arg(data, size * nmemb);
      65             :                 }
      66           0 :                 return size_t(size * nmemb);
      67        6024 :         }, task->receive.data);
      68             : }
      69             : 
      70             : template <typename Interface>
      71       24050 : static size_t _writeHeaders(char *data, size_t size, size_t nmemb, void *userptr) {
      72       24050 :         auto task = static_cast<HandleData<Interface> *>(userptr);
      73             : 
      74       24050 :         StringView reader(data, size * nmemb);
      75       24050 :         if (!reader.is("\r\n")) {
      76       20900 :                 if (task->send.method != Method::Smtp) {
      77       20900 :                         if (!reader.is("HTTP/")) {
      78       17750 :                                 auto name = reader.readUntil<StringView::Chars<':'>>();
      79       17750 :                                 reader ++;
      80             : 
      81       17750 :                                 name.trimChars<StringView::WhiteSpace>();
      82       17750 :                                 reader.trimChars<StringView::WhiteSpace>();
      83             : 
      84       17750 :                                 auto nameStr = string::tolower<Interface>(name);
      85       17750 :                                 auto valueStr = reader.str<Interface>();
      86             : 
      87       17750 :                                 if (task->receive.headerCallback) {
      88         250 :                                         task->receive.headerCallback(nameStr, valueStr);
      89             :                                 }
      90       17750 :                                 task->receive.parsed.emplace(std::move(nameStr), std::move(valueStr));
      91       17750 :                         } else {
      92        3150 :                                 reader.skipUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
      93        3150 :                                 reader.skipUntil<StringView::CharGroup<CharGroupId::Numbers>>();
      94        6300 :                                 reader.readInteger().unwrap([&] (int64_t code) {
      95        3150 :                                         task->process.responseCode = code;
      96             :                                 });
      97             :                         }
      98             :                 }
      99             : 
     100       20900 :                 task->receive.headers.emplace_back(StringView(data, size).str<Interface>());
     101             :         }
     102             : 
     103       24050 :     return size * nmemb;
     104             : }
     105             : 
     106             : template <typename Interface>
     107           0 : static size_t _readData(char *data, size_t size, size_t nmemb, void *userptr) {
     108           0 :         if (userptr != NULL) {
     109           0 :                 auto task = static_cast<HandleData<Interface> *>(userptr);
     110             : 
     111           0 :                 return std::visit([&] (auto &&arg) {
     112             :                         using T = std::decay_t<decltype(arg)>;
     113             :                         if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
     114           0 :                                 return arg(data, size * nmemb);
     115             :                         } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::Bytes>) {
     116           0 :                                 size_t remains = task->send.size;
     117           0 :                                 if (size * nmemb <= remains) {
     118           0 :                                         memcpy(data, arg.data() + (arg.size() - task->send.size), size * nmemb);
     119           0 :                                         task->send.size -= size * nmemb;
     120           0 :                                         return size * nmemb;
     121             :                                 } else {
     122           0 :                                         memcpy(data, arg.data() + (arg.size() - task->send.size), remains);
     123           0 :                                         task->send.size = 0;
     124           0 :                                         return remains;
     125             :                                 }
     126             :                         }
     127           0 :                         return size_t(0);
     128           0 :                 }, task->send.data);
     129             :         } else {
     130           0 :                 return 0;
     131             :         }
     132             : }
     133             : 
     134             : template <typename Interface>
     135        2651 : static int _progress(void *userptr, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
     136        2651 :         auto task = static_cast<HandleData<Interface> *>(userptr);
     137        2651 :         auto timing = Time::now();
     138             : 
     139        2651 :         int uProgress = 0;
     140        5302 :         if (task->process.uploadProgress && ulnow != task->process.uploadProgressValue
     141        5302 :                         && (!task->process.uploadProgressTiming || (timing - task->process.uploadProgressTiming > SP_NETWORK_PROGRESS_TIMEOUT))) {
     142           0 :                 task->process.uploadProgressValue = ulnow;
     143           0 :                 task->process.uploadProgressTiming = timing;
     144           0 :                 uProgress = task->process.uploadProgress(ultotal, ulnow);
     145             :         }
     146             : 
     147        2651 :         int dProgress = 0;
     148        5302 :         if (task->process.downloadProgress && dlnow != task->process.downloadProgressValue
     149        5302 :                         && (!task->process.downloadProgressTiming || (timing - task->process.downloadProgressTiming > SP_NETWORK_PROGRESS_TIMEOUT))) {
     150          25 :                 task->process.downloadProgressValue = dlnow;
     151          25 :                 task->process.downloadProgressTiming = timing;
     152          25 :                 return task->process.downloadProgress(dltotal + task->receive.offset, dlnow + task->receive.offset);
     153             :         }
     154             : 
     155        2626 :         if (ultotal == ulnow || ultotal == 0) {
     156        2626 :                 return dProgress;
     157             :         } else {
     158           0 :                 return uProgress;
     159             :         }
     160             : }
     161             : 
     162             : template <typename Interface>
     163           0 : Pair<FILE *, uint64_t> _openFile(const StringView &filename, bool readOnly, bool resume = false) {
     164           0 :         uint64_t pos = 0;
     165           0 :         FILE *file = nullptr;
     166           0 :         auto path = filepath::absolute<Interface>(filename);
     167           0 :         if (filesystem::exists(path)) {
     168           0 :                 filesystem::Stat stat;
     169           0 :                 if (filesystem::stat(path, stat) && stat.size) {
     170           0 :                         pos = stat.size;
     171           0 :                         if (!readOnly) {
     172           0 :                                 if (!resume) {
     173           0 :                                         filesystem::remove(path);
     174           0 :                                         file = filesystem::native::fopen_fn(path, "w+b");
     175             :                                 } else {
     176           0 :                                         file = filesystem::native::fopen_fn(path, "a+b");
     177             :                                 }
     178             :                         } else {
     179           0 :                                 file = filesystem::native::fopen_fn(path, "rb");
     180             :                         }
     181             :                 }
     182             :         } else {
     183           0 :                 if (!readOnly) {
     184           0 :                         file = filesystem::native::fopen_fn(path, "w+b");
     185             :                 }
     186             :         }
     187           0 :     return pair(file, pos);
     188           0 : }
     189             : 
     190             : template <typename K, typename T>
     191       91425 : inline void SetOpt(bool &check, CURL *curl, K opt, const T &value) {
     192             : #ifdef DEBUG
     193       91425 :         int err = CURLE_OK;
     194       91425 :         if (check) {
     195       91425 :                 err = curl_easy_setopt(curl, opt, value);
     196       91425 :                 if (err != CURLE_OK) {
     197             :                         SP_NETWORK_LOG("curl_easy_setopt failed: %d %s %s", err, #name, #value);
     198           0 :                         check = false;
     199             :                 }
     200             :         }
     201             : #else
     202             :         check = (check) ? curl_easy_setopt(curl, opt, value) == CURLE_OK : false;
     203             : #endif
     204       91425 : }
     205             : 
     206             : template <typename Interface>
     207        3025 : static bool _setupCurl(const HandleData<Interface> &iface, CURL *curl, char *errorBuffer) {
     208        3025 :         bool check = true;
     209             : 
     210        3025 :         auto CABundle = getCABundle();
     211        3025 :         static struct curl_blob blob { (void *)CABundle.data(), CABundle.size(), CURL_BLOB_NOCOPY };
     212             : 
     213        3025 :         SetOpt(check, curl, CURLOPT_CAINFO_BLOB, &blob);
     214             : 
     215        3025 :         SetOpt(check, curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
     216             : 
     217        3025 :         SetOpt(check, curl, CURLOPT_NOSIGNAL, 1);
     218        3025 :         SetOpt(check, curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
     219             : 
     220        3025 :         SetOpt(check, curl, CURLOPT_ERRORBUFFER, errorBuffer);
     221        3025 :         SetOpt(check, curl, CURLOPT_LOW_SPEED_TIME, iface.process.lowSpeedTime);
     222        3025 :         SetOpt(check, curl, CURLOPT_LOW_SPEED_LIMIT, iface.process.lowSpeedLimit);
     223             :         //SetOpt(check, curl, CURLOPT_TIMEOUT, SP_NW_READ_TIMEOUT);
     224        3025 :         SetOpt(check, curl, CURLOPT_CONNECTTIMEOUT, iface.process.connectTimeout);
     225             : 
     226        3025 :         if (iface.process.verifyTsl) {
     227        3000 :                 SetOpt(check, curl, CURLOPT_SSL_VERIFYPEER, 1L);
     228        3000 :                 SetOpt(check, curl, CURLOPT_SSL_VERIFYHOST, 2L);
     229             :         } else {
     230          25 :                 SetOpt(check, curl, CURLOPT_SSL_VERIFYPEER, 0L);
     231          25 :                 SetOpt(check, curl, CURLOPT_SSL_VERIFYHOST, 0L);
     232             :         }
     233             : 
     234        3025 :         SetOpt(check, curl, CURLOPT_URL, iface.send.url.data());
     235        3025 :         SetOpt(check, curl, CURLOPT_RESUME_FROM, 0); // drop byte-ranged GET
     236             : 
     237        3025 :         SetOpt(check, curl, CURLOPT_WRITEFUNCTION, _writeDummy);
     238        3025 :         SetOpt(check, curl, CURLOPT_WRITEDATA, nullptr);
     239             : 
     240             :         /* enable all supported built-in compressions */
     241        3025 :         SetOpt(check, curl, CURLOPT_ACCEPT_ENCODING, "");
     242             : 
     243        3025 :         return check;
     244             : }
     245             : 
     246             : template <typename Interface>
     247        3025 : static bool _setupDebug(const HandleData<Interface> &iface, CURL *curl, bool debug) {
     248        3025 :         bool check = true;
     249        3025 :         if (debug) {
     250           0 :                 SetOpt(check, curl, CURLOPT_VERBOSE, 1);
     251           0 :                 SetOpt(check, curl, CURLOPT_DEBUGFUNCTION, _writeDebug<Interface>);
     252           0 :                 SetOpt(check, curl, CURLOPT_DEBUGDATA, &iface);
     253             :         }
     254        3025 :         return check;
     255             : }
     256             : 
     257             : template <typename Interface>
     258        3025 : static bool _setupHeaders(const HandleData<Interface> &iface, Context<Interface> *ctx,
     259             :                 const typename HandleData<Interface>::HeaderMap &vec, curl_slist **headers) {
     260        3025 :         bool check = true;
     261        3025 :         StringView keySign;
     262        3025 :         if (iface.auth.authMethod == AuthMethod::PKey) {
     263           0 :                 if (auto sign = std::get_if<typename HandleData<Interface>::String>(&iface.auth.data)) {
     264           0 :                         keySign = StringView(*sign);
     265             :                 }
     266             :         }
     267             : 
     268        3025 :         ctx->headersData.reserve(vec.size());
     269        3725 :         for (auto &it : vec) {
     270         700 :                 if (iface.send.method == Method::Get || iface.send.method == Method::Head || iface.send.method == Method::Delete) {
     271          50 :                         if (it.first == "Content-Type") {
     272           0 :                                 continue;
     273             :                         }
     274             :                 }
     275             : 
     276         700 :                 if (it.first != "Authorization" || keySign.empty()) {
     277         700 :                         ctx->headersData.emplace_back(string::toString<Interface>(it.first, ": ", it.second));
     278             :                 }
     279             :         }
     280             : 
     281        3025 :         if (!keySign.empty()) {
     282           0 :                 ctx->headersData.emplace_back(string::toString<Interface>("Authorization: pkey ", keySign));
     283           0 :                 *headers = curl_slist_append(*headers, string::toString<Interface>("Authorization: pkey ", keySign).data());
     284             :         }
     285             : 
     286        3725 :         for (auto &it : ctx->headersData) {
     287         700 :                 *headers = curl_slist_append(*headers, it.data());
     288             :         }
     289             : 
     290        3025 :         if (!ctx->headersData.empty() || *headers) {
     291         700 :                 SetOpt(check, ctx->curl, CURLOPT_HTTPHEADER, *headers);
     292             :         }
     293             : 
     294        3025 :         SetOpt(check, ctx->curl, CURLOPT_HEADERFUNCTION, _writeHeaders<Interface>);
     295        3025 :         SetOpt(check, ctx->curl, CURLOPT_HEADERDATA, &iface);
     296             : 
     297        3025 :         return check;
     298             : }
     299             : 
     300             : template <typename Interface>
     301        3025 : static bool _setupUserAgent(const HandleData<Interface> &iface, CURL *curl, const StringView &agent) {
     302        3025 :         bool check = true;
     303        3025 :         if (!agent.empty()) {
     304          25 :                 SetOpt(check, curl, CURLOPT_USERAGENT, SP_TERMINATED_DATA(agent));
     305             :         } else {
     306        3000 :                 SetOpt(check, curl, CURLOPT_USERAGENT, s_UserAgent);
     307             :         }
     308        3025 :         return check;
     309             : }
     310             : 
     311             : template <typename Interface>
     312        3025 : static bool _setupUser(const HandleData<Interface> &iface, CURL *curl,
     313             :                 const StringView &user, const StringView &password, AuthMethod m) {
     314        3025 :         bool check = true;
     315        3025 :         if (!user.empty()) {
     316         400 :                 switch (m) {
     317         400 :                 case AuthMethod::Basic:
     318         400 :                         SetOpt(check, curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
     319         400 :                         break;
     320           0 :                 case AuthMethod::Digest:
     321           0 :                         SetOpt(check, curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
     322           0 :                         break;
     323           0 :                 default:
     324           0 :                         return false;
     325             :                         break;
     326             :                 }
     327         400 :                 SetOpt(check, curl, CURLOPT_USERNAME, SP_TERMINATED_DATA(user));
     328         400 :                 if (!password.empty()) {
     329         400 :                         SetOpt(check, curl, CURLOPT_PASSWORD, SP_TERMINATED_DATA(password));
     330             :                 }
     331             :         }
     332        3025 :         return check;
     333             : }
     334             : 
     335             : template <typename Interface>
     336           0 : static bool _setupFrom(const HandleData<Interface> &iface, CURL *curl, const StringView &from) {
     337           0 :         bool check = true;
     338           0 :         SetOpt(check, curl, CURLOPT_MAIL_FROM, SP_TERMINATED_DATA(from));
     339           0 :         return check;
     340             : }
     341             : 
     342             : template <typename Interface>
     343           0 : static bool _setupRecv(const HandleData<Interface> &iface, CURL *curl,
     344             :                 const typename Interface::template VectorType<typename Interface::StringType> &vec, curl_slist **mailTo) {
     345           0 :         bool check = true;
     346           0 :         if (vec.size() > 0) {
     347           0 :                 for (const auto &str : vec) {
     348           0 :                         *mailTo = curl_slist_append(*mailTo, str.c_str());
     349             :                 }
     350           0 :                 SetOpt(check, curl, CURLOPT_MAIL_RCPT, *mailTo);
     351             :         }
     352           0 :         return check;
     353             : }
     354             : 
     355             : template <typename Interface>
     356        3025 : static bool _setupProgress(const HandleData<Interface> &iface, CURL *curl) {
     357        3025 :         bool check = true;
     358        3025 :         if (iface.send.method != Method::Head && (iface.process.uploadProgress || iface.process.downloadProgress)) {
     359          25 :                 SetOpt(check, curl, CURLOPT_NOPROGRESS, 0);
     360             :         } else {
     361        3000 :                 SetOpt(check, curl, CURLOPT_NOPROGRESS, 1);
     362             :         }
     363        3025 :         SetOpt(check, curl, CURLOPT_XFERINFOFUNCTION, _progress<Interface>);
     364        3025 :         SetOpt(check, curl, CURLOPT_XFERINFODATA, &iface);
     365        3025 :         return check;
     366             : }
     367             : 
     368             : template <typename Interface>
     369        3025 : static bool _setupCookies(const HandleData<Interface> &iface, CURL *curl, const StringView &cookiePath) {
     370        3025 :         bool check = true;
     371        3025 :         if (!cookiePath.empty()) {
     372         700 :                 SetOpt(check, curl, CURLOPT_COOKIEFILE, SP_TERMINATED_DATA(cookiePath));
     373         700 :                 SetOpt(check, curl, CURLOPT_COOKIEJAR, SP_TERMINATED_DATA(cookiePath));
     374             :         }
     375        3025 :         return check;
     376             : }
     377             : 
     378             : template <typename Interface>
     379        3025 : static bool _setupProxy(const HandleData<Interface> &iface, CURL *curl, const StringView &proxy, const StringView &auth) {
     380        3025 :         bool check = true;
     381        3025 :         if (!proxy.empty()) {
     382           0 :                 SetOpt(check, curl, CURLOPT_PROXY, SP_TERMINATED_DATA(proxy));
     383             :         } else {
     384        3025 :                 SetOpt(check, curl, CURLOPT_PROXY, nullptr);
     385             :         }
     386             : 
     387        3025 :         if (!auth.empty()) {
     388           0 :                 SetOpt(check, curl, CURLOPT_PROXYUSERPWD, SP_TERMINATED_DATA(auth));
     389             :         } else {
     390        3025 :                 SetOpt(check, curl, CURLOPT_PROXYUSERPWD, nullptr);
     391             :         }
     392             : 
     393        3025 :         return true;
     394             : }
     395             : 
     396             : template <typename Interface>
     397        3025 : static bool _setupReceive(HandleData<Interface> &iface, CURL *curl, FILE * & inputFile, uint64_t &inputPos) {
     398        3025 :         bool check = true;
     399        3025 :         if (iface.send.method != Method::Head) {
     400        6000 :         std::visit([&] (auto &&arg) {
     401             :                         using T = std::decay_t<decltype(arg)>;
     402             :                         if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
     403           0 :                                 iface.receive.offset = 0;
     404           0 :                                 std::tie(inputFile, inputPos) = _openFile<Interface>(arg, false, iface.receive.resumeDownload);
     405           0 :                                 if (inputFile) {
     406           0 :                                         SetOpt(check, curl, CURLOPT_WRITEFUNCTION, (void *)NULL);
     407           0 :                                         SetOpt(check, curl, CURLOPT_WRITEDATA, inputFile);
     408           0 :                                         if (inputPos != 0 && iface.receive.resumeDownload) {
     409           0 :                                                 iface.receive.offset = inputPos;
     410           0 :                                                 SetOpt(check, curl, CURLOPT_RESUME_FROM_LARGE, inputPos);
     411             :                                         }
     412             :                                 }
     413             :                         } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
     414        2850 :                                 SetOpt(check, curl, CURLOPT_WRITEFUNCTION, _writeData<Interface>);
     415        2850 :                                 SetOpt(check, curl, CURLOPT_WRITEDATA, &iface);
     416        2850 :                                 if (iface.receive.offset > 0) {
     417           0 :                                         SetOpt(check, curl, CURLOPT_RESUME_FROM_LARGE, iface.receive.offset);
     418             :                                 }
     419             :                         }
     420        3000 :                 }, iface.receive.data);
     421             :         }
     422        3025 :         return check;
     423             : }
     424             : 
     425             : template <typename Interface>
     426        2200 : static bool _setupMethodGet(const HandleData<Interface> &iface, CURL *curl) {
     427        2200 :         bool check = true;
     428        2200 :     SetOpt(check, curl, CURLOPT_HTTPGET, 1);
     429        2200 :     SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
     430        2200 :         return check;
     431             : }
     432             : 
     433             : template <typename Interface>
     434          25 : static bool _setupMethodHead(const HandleData<Interface> &iface, CURL *curl) {
     435          25 :         bool check = true;
     436          25 :     SetOpt(check, curl, CURLOPT_HTTPGET, 1);
     437          25 :     SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
     438          25 :     SetOpt(check, curl, CURLOPT_NOBODY, 1);
     439          25 :         return check;
     440             : }
     441             : 
     442             : template <typename Interface>
     443         650 : static void _setupSendData(bool &check, const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
     444        4550 :     std::visit([&] (auto &&arg) {
     445             :                 using T = std::decay_t<decltype(arg)>;
     446             :                 if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
     447             :                         size_t size;
     448           0 :                         std::tie(outputFile, size) = _openFile<Interface>(arg, true);
     449           0 :                         if (outputFile) {
     450           0 :                     SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
     451           0 :                     SetOpt(check, curl, CURLOPT_READDATA, outputFile);
     452           0 :                     SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, size);
     453           0 :                         SetOpt(check, curl, CURLOPT_INFILESIZE, size);
     454             :                         }
     455             :                 } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
     456           0 :                         SetOpt(check, curl, CURLOPT_READFUNCTION, _readData<Interface>);
     457           0 :                         SetOpt(check, curl, CURLOPT_READDATA, &iface);
     458           0 :                         SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, iface.send.size);
     459           0 :             SetOpt(check, curl, CURLOPT_INFILESIZE, iface.send.size);
     460             :                 } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::Bytes>) {
     461         650 :                         SetOpt(check, curl, CURLOPT_POSTFIELDS, arg.data());
     462         650 :                         SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, arg.size());
     463         650 :             SetOpt(check, curl, CURLOPT_INFILESIZE, arg.size());
     464             :                 }
     465         650 :         }, iface.send.data);
     466         650 : }
     467             : 
     468             : 
     469             : template <typename Interface>
     470         500 : static bool _setupMethodPost(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
     471         500 :         bool check = true;
     472         500 :     SetOpt(check, curl, CURLOPT_POST, 1);
     473             : 
     474         500 :         SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
     475         500 :         SetOpt(check, curl, CURLOPT_READDATA, (void *)NULL);
     476         500 :     SetOpt(check, curl, CURLOPT_POSTFIELDS, (void *)NULL);
     477         500 :     SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, 0);
     478             : 
     479         500 :     _setupSendData(check, iface, curl, outputFile);
     480             : 
     481         500 :         return check;
     482             : }
     483             : 
     484             : template <typename Interface>
     485         150 : static bool _setupMethodPut(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
     486         150 :         bool check = true;
     487             : 
     488         150 :         SetOpt(check, curl, CURLOPT_UPLOAD, 1);
     489         150 :         SetOpt(check, curl, CURLOPT_READFUNCTION, (void*) NULL);
     490         150 :         SetOpt(check, curl, CURLOPT_READDATA, (void*) NULL);
     491         150 :         SetOpt(check, curl, CURLOPT_CUSTOMREQUEST, "PUT");
     492             : 
     493         150 :         _setupSendData(check, iface, curl, outputFile);
     494             : 
     495         150 :         return check;
     496             : }
     497             : 
     498             : template <typename Interface>
     499         150 : static bool _setupMethodDelete(const HandleData<Interface> &iface, CURL *curl) {
     500         150 :         bool check = true;
     501         150 :         SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
     502         150 :         SetOpt(check, curl, CURLOPT_CUSTOMREQUEST, "DELETE");
     503         150 :         return check;
     504             : }
     505             : 
     506             : template <typename Interface>
     507           0 : static bool _setupMethodSmpt(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
     508           0 :         bool check = true;
     509             : 
     510           0 :     SetOpt(check, curl, CURLOPT_UPLOAD, 1);
     511           0 :         SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
     512           0 :         SetOpt(check, curl, CURLOPT_READDATA, (void *)NULL);
     513           0 :     SetOpt(check, curl, CURLOPT_INFILESIZE, 0);
     514             : 
     515           0 :     _setupSendData(check, iface, curl, outputFile);
     516             : 
     517           0 :     SetOpt(check, curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
     518           0 :         return check;
     519             : }
     520             : 
     521             : template <typename Interface>
     522        3025 : bool prepare(HandleData<Interface> &iface, Context<Interface> *ctx, const Callback<bool(CURL *)> &onBeforePerform) {
     523        3025 :         if (iface.process.debug) {
     524           0 :                 iface.process.debugData = typename Interface::StringStreamType();
     525             :         }
     526             : 
     527        3025 :         iface.receive.parsed.clear();
     528        3025 :         iface.receive.headers.clear();
     529             : 
     530        3025 :         ctx->handle = &iface;
     531             : 
     532        3025 :         bool check = true;
     533             : 
     534        3025 :         if (ctx->share) {
     535           0 :                 SetOpt(check, ctx->curl, CURLOPT_SHARE, iface.process.sharedHandle);
     536        3025 :         } else if (iface.process.shared) {
     537           0 :                 if (!iface.process.sharedHandle) {
     538           0 :                         iface.process.sharedHandle = curl_share_init();
     539           0 :                         curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
     540           0 :                         curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
     541           0 :                         curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
     542           0 :                         curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
     543             :                 }
     544           0 :                 SetOpt(check, ctx->curl, CURLOPT_COOKIEFILE, "/undefined");
     545           0 :                 SetOpt(check, ctx->curl, CURLOPT_SHARE, iface.process.sharedHandle);
     546             :         } else {
     547        3025 :                 SetOpt(check, ctx->curl, CURLOPT_SHARE, nullptr);
     548             :         }
     549             : 
     550        3025 :         check = (check) ? _setupCurl(iface, ctx->curl, ctx->error.data()) : false;
     551        3025 :         check = (check) ? _setupDebug(iface, ctx->curl, iface.process.debug) : false;
     552        3025 :         check = (check) ? _setupHeaders(iface, ctx, iface.send.headers, &ctx->headers) : false;
     553        3025 :         check = (check) ? _setupUserAgent(iface, ctx->curl, iface.send.userAgent) : false;
     554        3025 :         if (auto u = std::get_if<Pair<typename HandleData<Interface>::String, typename HandleData<Interface>::String>>(&iface.auth.data)) {
     555        3025 :                 check = (check) ? _setupUser(iface, ctx->curl, u->first, u->second, iface.auth.authMethod) : false;
     556             :         }
     557        3025 :         check = (check) ? _setupProgress(iface, ctx->curl) : false;
     558        3025 :         check = (check) ? _setupCookies(iface, ctx->curl, iface.process.cookieFile) : false;
     559        3025 :         check = (check) ? _setupProxy(iface, ctx->curl, iface.auth.proxyAddress, iface.auth.proxyAuth) : false;
     560        3025 :         check = (check) ? _setupReceive(iface, ctx->curl, ctx->inputFile, ctx->inputPos) : false;
     561             : 
     562        3025 :         switch (iface.send.method) {
     563        2200 :                 case Method::Get:
     564        2200 :                         check = (check) ? _setupMethodGet(iface, ctx->curl) : false;
     565        2200 :                         break;
     566          25 :                 case Method::Head:
     567          25 :                         check = (check) ? _setupMethodHead(iface, ctx->curl) : false;
     568          25 :                         break;
     569         500 :                 case Method::Post:
     570         500 :                         check = (check) ? _setupMethodPost(iface, ctx->curl, ctx->outputFile) : false;
     571         500 :                         break;
     572         150 :                 case Method::Put:
     573         150 :                         check = (check) ? _setupMethodPut(iface, ctx->curl, ctx->outputFile) : false;
     574         150 :                         break;
     575         150 :                 case Method::Delete:
     576         150 :                         check = (check) ? _setupMethodDelete(iface, ctx->curl) : false;
     577         150 :                         break;
     578           0 :                 case Method::Smtp:
     579           0 :                         check = (check) ? _setupRecv(iface, ctx->curl, iface.send.recipients, &ctx->mailTo) : false;
     580           0 :                         check = (check) ? _setupFrom(iface, ctx->curl, iface.send.from) : false;
     581           0 :                         check = (check) ? _setupMethodSmpt(iface, ctx->curl, ctx->outputFile) : false;
     582           0 :                         break;
     583           0 :                 default:
     584           0 :                         break;
     585             :         }
     586             : 
     587        3025 :         if (!check) {
     588           0 :                 if (!iface.process.silent) {
     589           0 :                         log::error("CURL", "Fail to setup: ", iface.send.url.data());
     590             :                 }
     591           0 :                 return false;
     592             :         }
     593             : 
     594        3025 :         if (onBeforePerform) {
     595           0 :                 if (!onBeforePerform(ctx->curl)) {
     596           0 :                         if (!iface.process.silent) {
     597           0 :                                 log::error("CURL", "onBeforePerform failed");
     598             :                         }
     599           0 :                         return false;
     600             :                 }
     601             :         }
     602             : 
     603        3025 :         iface.process.debugData.clear();
     604        3025 :         iface.receive.parsed.clear();
     605        3025 :         iface.receive.headers.clear();
     606        3025 :         return true;
     607             : }
     608             : 
     609             : template <typename Interface>
     610        3025 : bool finalize(HandleData<Interface> &iface, Context<Interface> *ctx, const Callback<bool(CURL *)> &onAfterPerform) {
     611        3025 :         iface.process.errorCode = ctx->code;
     612        3025 :         if (ctx->headers) {
     613         700 :                 curl_slist_free_all(ctx->headers);
     614         700 :                 ctx->headers = nullptr;
     615             :         }
     616             : 
     617        3025 :         if (ctx->mailTo) {
     618           0 :                 curl_slist_free_all(ctx->mailTo);
     619           0 :                 ctx->mailTo = nullptr;
     620             :         }
     621             : 
     622        3025 :         if (CURLE_RANGE_ERROR == iface.process.errorCode && iface.send.method == Method::Get) {
     623           0 :                 size_t allowedRange = size_t(iface.getReceivedHeaderInt("X-Range"));
     624           0 :                 if (allowedRange == ctx->inputPos) {
     625           0 :                         if (!iface.process.silent) {
     626           0 :                                 log::warn("CURL", "Get 0-range is not an error, fixed error code to CURLE_OK");
     627             :                         }
     628           0 :                         ctx->success = true;
     629           0 :                         iface.process.errorCode = CURLE_OK;
     630             :                 }
     631             :         }
     632             : 
     633        3025 :         if (CURLE_OK == iface.process.errorCode) {
     634        3025 :                 iface.process.performed = true;
     635        3025 :                 if (iface.send.method != Method::Smtp) {
     636        3025 :                         const char *ct = NULL;
     637        3025 :                         long code = 200;
     638             : 
     639        3025 :                         curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &code);
     640        3025 :                         curl_easy_getinfo(ctx->curl, CURLINFO_CONTENT_TYPE, &ct);
     641        3025 :                         if (ct) {
     642        2975 :                                 iface.receive.contentType = ct;
     643             : #if LINUX
     644        2975 :                                 if (ctx->inputFile && !iface.receive.contentType.empty()) {
     645           0 :                                         fflush(ctx->inputFile);
     646           0 :                                         network_setUserAttributes(ctx->inputFile, iface.receive.contentType, Time::microseconds(iface.getReceivedHeaderInt("X-FileModificationTime")));
     647           0 :                                         fclose(ctx->inputFile);
     648           0 :                                         ctx->inputFile = nullptr;
     649             :                                 }
     650             : #endif
     651             :                         }
     652             : 
     653        3025 :                         iface.process.responseCode = (long) code;
     654             : 
     655             :                         SP_NETWORK_LOG("performed: %d %s %ld", (int) _method, _url.c_str(), _responseCode);
     656             : 
     657        3025 :                         if (iface.process.responseCode == 416) {
     658           0 :                                 size_t allowedRange = size_t(iface.getReceivedHeaderInt("X-Range"));
     659           0 :                                 if (allowedRange == ctx->inputPos) {
     660           0 :                                         iface.process.responseCode = 200;
     661           0 :                                         if (!iface.process.silent) {
     662           0 :                                                 log::warn("CURL", iface.send.url, ": Get 0-range is not an error, fixed response code to 200");
     663             :                                         }
     664             :                                 }
     665             :                         }
     666             : 
     667        3025 :                         if (iface.process.responseCode >= 200 && iface.process.responseCode < 400) {
     668        2925 :                                 ctx->success = true;
     669             :                         } else {
     670         100 :                                 ctx->success = false;
     671             :                         }
     672             :                 } else {
     673           0 :                         ctx->success = true;
     674             :                 }
     675             :         } else {
     676           0 :                 if (!iface.process.silent) {
     677           0 :                         log::format(log::Error, "CURL", "fail to perform %s: (%ld) %s", iface.send.url.data(), iface.process.errorCode, ctx->error.data());
     678             :                 }
     679           0 :                 iface.process.error = ctx->error.data();
     680           0 :                 if (iface.process.debug) {
     681           0 :                 std::visit([&] (auto &&arg) {
     682             :                                 using T = std::decay_t<decltype(arg)>;
     683             :                                 if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
     684           0 :                                         std::cout << "Input file: " << arg << "\n";
     685             :                                 }
     686           0 :                         }, iface.receive.data);
     687             :                 }
     688           0 :                 ctx->success = false;
     689             :         }
     690             : 
     691        3025 :         if (!iface.process.cookieFile.empty()) {
     692         700 :                 auto it = iface.receive.parsed.find("set-cookie");
     693         700 :                 if (it != iface.receive.parsed.end()) {
     694          75 :                         iface.process.invalidate = true;
     695             :                 }
     696             :         }
     697             : 
     698        3025 :         if (onAfterPerform) {
     699           0 :                 if (!onAfterPerform(ctx->curl)) {
     700           0 :                         ctx->success = false;
     701             :                 }
     702             :         }
     703             : 
     704        3025 :         if (ctx->inputFile) {
     705           0 :                 fflush(ctx->inputFile);
     706           0 :                 fclose(ctx->inputFile);
     707           0 :                 ctx->inputFile = nullptr;
     708             :         }
     709        3025 :         if (ctx->outputFile) {
     710           0 :                 fclose(ctx->outputFile);
     711           0 :                 ctx->outputFile = nullptr;
     712             :         }
     713        3025 :         return ctx->success;
     714             : }
     715             : 
     716             : template <typename Interface>
     717        3000 : static bool _perform(Context<Interface> &ctx, HandleData<Interface> &iface,
     718             :                 const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
     719             : 
     720        3000 :         iface.process.performed = false;
     721        3000 :         iface.process.errorCode = CURLE_OK;
     722        3000 :         iface.process.responseCode = -1;
     723             : 
     724        3000 :         if (!ctx.curl) {
     725           0 :                 return false;
     726             :         }
     727             : 
     728        3000 :         if (!prepare(iface, &ctx, onBeforePerform)) {
     729           0 :                 return false;
     730             :         }
     731             : 
     732        3000 :         ctx.code = curl_easy_perform(ctx.curl);
     733        3000 :         return finalize(iface, &ctx, onAfterPerform);
     734             : }
     735             : 
     736             : template <>
     737           0 : bool perform(HandleData<memory::PoolInterface> &iface, const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
     738           0 :         auto p = memory::pool::acquire();
     739           0 :         Context<memory::PoolInterface> ctx;
     740           0 :         ctx.curl = CurlHandle_getHandle(iface.process.reuse, p);
     741           0 :         auto ret = _perform<memory::PoolInterface>(ctx, iface, onBeforePerform, onAfterPerform);
     742           0 :         CurlHandle_releaseHandle(ctx.curl, iface.process.reuse, !iface.process.invalidate && ctx.code == CURLE_OK, p);
     743           0 :         return ret;
     744           0 : }
     745             : 
     746             : template <>
     747        3000 : bool perform(HandleData<memory::StandartInterface> &iface, const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
     748        3000 :         Context<memory::StandartInterface> ctx;
     749        3000 :         ctx.curl = CurlHandle_getHandle(iface.process.reuse, nullptr);
     750        3000 :         auto ret = _perform<memory::StandartInterface>(ctx, iface, onBeforePerform, onAfterPerform);
     751        3000 :         CurlHandle_releaseHandle(ctx.curl, iface.process.reuse, !iface.process.invalidate && ctx.code == CURLE_OK, nullptr);
     752        3000 :         return ret;
     753        3000 : }
     754             : 
     755             : }

Generated by: LCOV version 1.14