LCOV - code coverage report
Current view: top level - port - cpl_http.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 763 1045 73.0 %
Date: 2025-09-10 17:48:50 Functions: 34 41 82.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  libcurl based HTTP client
       4             :  * Purpose:  libcurl based HTTP client
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2006, Frank Warmerdam
       9             :  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "cpl_http.h"
      16             : 
      17             : #include <cstddef>
      18             : #include <cstring>
      19             : 
      20             : #include <algorithm>
      21             : #include <array>
      22             : #include <map>
      23             : #include <mutex>
      24             : #include <string>
      25             : #include <vector>
      26             : 
      27             : #include "cpl_http.h"
      28             : #include "cpl_error.h"
      29             : #include "cpl_multiproc.h"
      30             : #include "cpl_vsi_virtual.h"
      31             : #include "cpl_vsil_curl_class.h"
      32             : 
      33             : // gcc or clang complains about C-style cast in #define like
      34             : // CURL_ZERO_TERMINATED
      35             : #if defined(__GNUC__)
      36             : #pragma GCC diagnostic push
      37             : #pragma GCC diagnostic ignored "-Wold-style-cast"
      38             : #ifdef HAVE_WFLAG_CAST_FUNCTION_TYPE
      39             : #pragma GCC diagnostic ignored "-Wcast-function-type"
      40             : #endif
      41             : #endif
      42             : 
      43             : #ifdef HAVE_CURL
      44             : 
      45             : #include "cpl_curl_priv.h"
      46             : 
      47             : #ifdef HAVE_OPENSSL_CRYPTO
      48             : #include <openssl/err.h>
      49             : #include <openssl/ssl.h>
      50             : #include <openssl/x509v3.h>
      51             : 
      52             : #if defined(_WIN32)
      53             : #include <wincrypt.h>
      54             : #endif
      55             : 
      56             : #endif
      57             : 
      58             : #ifdef HAVE_SIGACTION
      59             : #include <signal.h>
      60             : #endif
      61             : 
      62             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      63             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      64             : 
      65             : #endif  // HAVE_CURL
      66             : 
      67             : // list of named persistent http sessions
      68             : 
      69             : #ifdef HAVE_CURL
      70             : static std::map<CPLString, CURL *> *poSessionMap = nullptr;
      71             : static std::map<CPLString, CURLM *> *poSessionMultiMap = nullptr;
      72             : static CPLMutex *hSessionMapMutex = nullptr;
      73             : static bool bHasCheckVersion = false;
      74             : static bool bSupportGZip = false;
      75             : static bool bSupportHTTP2 = false;
      76             : #if defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
      77             : static std::vector<X509 *> *poWindowsCertificateList = nullptr;
      78             : 
      79             : #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
      80             : #define EVP_PKEY_get0_RSA(x) (x->pkey.rsa)
      81             : #define EVP_PKEY_get0_DSA(x) (x->pkey.dsa)
      82             : #define X509_get_extension_flags(x) (x->ex_flags)
      83             : #define X509_get_key_usage(x) (x->ex_kusage)
      84             : #define X509_get_extended_key_usage(x) (x->ex_xkusage)
      85             : #endif
      86             : 
      87             : #endif  // defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
      88             : 
      89             : #if defined(HAVE_OPENSSL_CRYPTO) && OPENSSL_VERSION_NUMBER < 0x10100000
      90             : 
      91             : // Ported from https://curl.haxx.se/libcurl/c/opensslthreadlock.html
      92             : static CPLMutex **pahSSLMutex = nullptr;
      93             : 
      94             : static void CPLOpenSSLLockingFunction(int mode, int n, const char * /*file*/,
      95             :                                       int /*line*/)
      96             : {
      97             :     if (mode & CRYPTO_LOCK)
      98             :     {
      99             :         CPLAcquireMutex(pahSSLMutex[n], 3600.0);
     100             :     }
     101             :     else
     102             :     {
     103             :         CPLReleaseMutex(pahSSLMutex[n]);
     104             :     }
     105             : }
     106             : 
     107             : static unsigned long CPLOpenSSLIdCallback(void)
     108             : {
     109             :     return static_cast<unsigned long>(CPLGetPID());
     110             : }
     111             : 
     112             : static void CPLOpenSSLInit()
     113             : {
     114             :     if (strstr(curl_version(), "OpenSSL") &&
     115             :         CPLTestBool(CPLGetConfigOption("CPL_OPENSSL_INIT_ENABLED", "YES")) &&
     116             :         CRYPTO_get_id_callback() == nullptr)
     117             :     {
     118             :         pahSSLMutex = static_cast<CPLMutex **>(
     119             :             CPLMalloc(CRYPTO_num_locks() * sizeof(CPLMutex *)));
     120             :         for (int i = 0; i < CRYPTO_num_locks(); i++)
     121             :         {
     122             :             pahSSLMutex[i] = CPLCreateMutex();
     123             :             CPLReleaseMutex(pahSSLMutex[i]);
     124             :         }
     125             :         CRYPTO_set_id_callback(CPLOpenSSLIdCallback);
     126             :         CRYPTO_set_locking_callback(CPLOpenSSLLockingFunction);
     127             :     }
     128             : }
     129             : 
     130             : static void CPLOpenSSLCleanup()
     131             : {
     132             :     if (pahSSLMutex)
     133             :     {
     134             :         for (int i = 0; i < CRYPTO_num_locks(); i++)
     135             :         {
     136             :             CPLDestroyMutex(pahSSLMutex[i]);
     137             :         }
     138             :         CPLFree(pahSSLMutex);
     139             :         pahSSLMutex = nullptr;
     140             :         CRYPTO_set_id_callback(nullptr);
     141             :         CRYPTO_set_locking_callback(nullptr);
     142             :     }
     143             : }
     144             : 
     145             : #endif
     146             : 
     147             : #if defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
     148             : 
     149             : /************************************************************************/
     150             : /*                    CPLWindowsCertificateListCleanup()                */
     151             : /************************************************************************/
     152             : 
     153             : static void CPLWindowsCertificateListCleanup()
     154             : {
     155             :     if (poWindowsCertificateList)
     156             :     {
     157             :         for (auto &&pX509 : *poWindowsCertificateList)
     158             :         {
     159             :             X509_free(pX509);
     160             :         }
     161             :         delete poWindowsCertificateList;
     162             :         poWindowsCertificateList = nullptr;
     163             :     }
     164             : }
     165             : 
     166             : /************************************************************************/
     167             : /*                       LoadCAPICertificates()                         */
     168             : /************************************************************************/
     169             : 
     170             : static CPLErr LoadCAPICertificates(const char *pszName,
     171             :                                    std::vector<X509 *> *poCertificateList)
     172             : {
     173             :     CPLAssert(pszName);
     174             :     CPLAssert(poCertificateList);
     175             : 
     176             :     HCERTSTORE pCertStore = CertOpenSystemStore(
     177             :         reinterpret_cast<HCRYPTPROV_LEGACY>(nullptr), pszName);
     178             :     if (pCertStore == nullptr)
     179             :     {
     180             :         CPLError(CE_Failure, CPLE_AppDefined,
     181             :                  "CPLLoadCAPICertificates(): Unable open system "
     182             :                  "certificate store %s.",
     183             :                  pszName);
     184             :         return CE_Failure;
     185             :     }
     186             : 
     187             :     PCCERT_CONTEXT pCertificate =
     188             :         CertEnumCertificatesInStore(pCertStore, nullptr);
     189             :     while (pCertificate != nullptr)
     190             :     {
     191             :         X509 *pX509 = d2i_X509(
     192             :             nullptr,
     193             :             const_cast<unsigned char const **>(&pCertificate->pbCertEncoded),
     194             :             pCertificate->cbCertEncoded);
     195             :         if (pX509 == nullptr)
     196             :         {
     197             :             CPLError(CE_Warning, CPLE_AppDefined,
     198             :                      "CPLLoadCAPICertificates(): CertEnumCertificatesInStore() "
     199             :                      "returned a null certificate, skipping.");
     200             :         }
     201             :         else
     202             :         {
     203             : #ifdef DEBUG_VERBOSE
     204             :             char szSubject[256] = {0};
     205             :             CPLString osSubject;
     206             :             X509_NAME *pName = X509_get_subject_name(pX509);
     207             :             if (pName)
     208             :             {
     209             :                 X509_NAME_oneline(pName, szSubject, sizeof(szSubject));
     210             :                 osSubject = szSubject;
     211             :             }
     212             :             if (!osSubject.empty())
     213             :                 CPLDebug("HTTP", "SSL Certificate: %s", osSubject.c_str());
     214             : #endif
     215             :             poCertificateList->push_back(pX509);
     216             :         }
     217             :         pCertificate = CertEnumCertificatesInStore(pCertStore, pCertificate);
     218             :     }
     219             :     CertCloseStore(pCertStore, 0);
     220             :     return CE_None;
     221             : }
     222             : 
     223             : /************************************************************************/
     224             : /*                       CPL_ssl_ctx_callback()                         */
     225             : /************************************************************************/
     226             : 
     227             : // Load certificates from Windows Crypto API store.
     228             : static CURLcode CPL_ssl_ctx_callback(CURL *, void *pSSL, void *)
     229             : {
     230             :     SSL_CTX *pSSL_CTX = static_cast<SSL_CTX *>(pSSL);
     231             :     if (pSSL_CTX == nullptr)
     232             :     {
     233             :         CPLError(CE_Failure, CPLE_AppDefined,
     234             :                  "CPL_ssl_ctx_callback(): OpenSSL context pointer is NULL.");
     235             :         return CURLE_ABORTED_BY_CALLBACK;
     236             :     }
     237             : 
     238             :     static std::mutex goMutex;
     239             :     {
     240             :         std::lock_guard<std::mutex> oLock(goMutex);
     241             :         if (poWindowsCertificateList == nullptr)
     242             :         {
     243             :             poWindowsCertificateList = new std::vector<X509 *>();
     244             :             if (!poWindowsCertificateList)
     245             :             {
     246             :                 CPLError(CE_Failure, CPLE_AppDefined,
     247             :                          "CPL_ssl_ctx_callback(): Unable to allocate "
     248             :                          "structure to hold certificates.");
     249             :                 return CURLE_FAILED_INIT;
     250             :             }
     251             : 
     252             :             const std::array<const char *, 3> aszStores{
     253             :                 {"CA", "AuthRoot", "ROOT"}};
     254             :             for (auto &&pszStore : aszStores)
     255             :             {
     256             :                 if (LoadCAPICertificates(pszStore, poWindowsCertificateList) ==
     257             :                     CE_Failure)
     258             :                 {
     259             :                     CPLError(
     260             :                         CE_Failure, CPLE_AppDefined,
     261             :                         "CPL_ssl_ctx_callback(): Unable to load certificates "
     262             :                         "from '%s' store.",
     263             :                         pszStore);
     264             :                     return CURLE_FAILED_INIT;
     265             :                 }
     266             :             }
     267             : 
     268             :             CPLDebug("HTTP", "Loading %d certificates from Windows store.",
     269             :                      static_cast<int>(poWindowsCertificateList->size()));
     270             :         }
     271             :     }
     272             : 
     273             :     X509_STORE *pX509Store = SSL_CTX_get_cert_store(pSSL_CTX);
     274             :     for (X509 *x509 : *poWindowsCertificateList)
     275             :         X509_STORE_add_cert(pX509Store, x509);
     276             : 
     277             :     return CURLE_OK;
     278             : }
     279             : 
     280             : #endif  // defined(_WIN32) && defined (HAVE_OPENSSL_CRYPTO)
     281             : 
     282             : /************************************************************************/
     283             : /*                       CheckCurlFeatures()                            */
     284             : /************************************************************************/
     285             : 
     286        2602 : static void CheckCurlFeatures()
     287             : {
     288        5204 :     CPLMutexHolder oHolder(&hSessionMapMutex);
     289        2602 :     if (!bHasCheckVersion)
     290             :     {
     291           7 :         const char *pszVersion = curl_version();
     292           7 :         CPLDebug("HTTP", "%s", pszVersion);
     293           7 :         bSupportGZip = strstr(pszVersion, "zlib/") != nullptr;
     294           7 :         bSupportHTTP2 = strstr(curl_version(), "nghttp2/") != nullptr;
     295           7 :         bHasCheckVersion = true;
     296             : 
     297           7 :         curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
     298           7 :         if (data->version_num < LIBCURL_VERSION_NUM)
     299             :         {
     300           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     301             :                      "GDAL was built against curl %d.%d.%d, but is "
     302             :                      "running against %s. Runtime failure is likely !",
     303             :                      LIBCURL_VERSION_MAJOR, LIBCURL_VERSION_MINOR,
     304             :                      LIBCURL_VERSION_PATCH, data->version);
     305             :         }
     306           7 :         else if (data->version_num > LIBCURL_VERSION_NUM)
     307             :         {
     308           0 :             CPLDebug("HTTP",
     309             :                      "GDAL was built against curl %d.%d.%d, but is "
     310             :                      "running against %s.",
     311             :                      LIBCURL_VERSION_MAJOR, LIBCURL_VERSION_MINOR,
     312             :                      LIBCURL_VERSION_PATCH, data->version);
     313             :         }
     314             : 
     315             : #if defined(HAVE_OPENSSL_CRYPTO) && OPENSSL_VERSION_NUMBER < 0x10100000
     316             :         CPLOpenSSLInit();
     317             : #endif
     318             :     }
     319        2602 : }
     320             : 
     321             : /************************************************************************/
     322             : /*                            CPLWriteFct()                             */
     323             : /*                                                                      */
     324             : /*      Append incoming text to our collection buffer, reallocating     */
     325             : /*      it larger as needed.                                            */
     326             : /************************************************************************/
     327             : 
     328             : class CPLHTTPResultWithLimit
     329             : {
     330             :   public:
     331             :     CPLHTTPResult *psResult = nullptr;
     332             :     int nMaxFileSize = 0;
     333             : };
     334             : 
     335       12718 : static size_t CPLWriteFct(void *buffer, size_t size, size_t nmemb,
     336             :                           void *reqInfo)
     337             : 
     338             : {
     339       12718 :     CPLHTTPResultWithLimit *psResultWithLimit =
     340             :         static_cast<CPLHTTPResultWithLimit *>(reqInfo);
     341       12718 :     CPLHTTPResult *psResult = psResultWithLimit->psResult;
     342             : 
     343       12718 :     int nBytesToWrite = static_cast<int>(nmemb) * static_cast<int>(size);
     344       12718 :     int nNewSize = psResult->nDataLen + nBytesToWrite + 1;
     345       12718 :     if (nNewSize > psResult->nDataAlloc)
     346             :     {
     347        1637 :         psResult->nDataAlloc = static_cast<int>(nNewSize * 1.25 + 100);
     348             :         GByte *pabyNewData = static_cast<GByte *>(
     349        1637 :             VSIRealloc(psResult->pabyData, psResult->nDataAlloc));
     350        1637 :         if (pabyNewData == nullptr)
     351             :         {
     352           0 :             VSIFree(psResult->pabyData);
     353           0 :             psResult->pabyData = nullptr;
     354           0 :             psResult->pszErrBuf = CPLStrdup(CPLString().Printf(
     355             :                 "Out of memory allocating %d bytes for HTTP data buffer.",
     356           0 :                 psResult->nDataAlloc));
     357           0 :             psResult->nDataAlloc = psResult->nDataLen = 0;
     358             : 
     359           0 :             return 0;
     360             :         }
     361        1637 :         psResult->pabyData = pabyNewData;
     362             :     }
     363             : 
     364       12718 :     memcpy(psResult->pabyData + psResult->nDataLen, buffer, nBytesToWrite);
     365             : 
     366       12718 :     psResult->nDataLen += nBytesToWrite;
     367       12718 :     psResult->pabyData[psResult->nDataLen] = 0;
     368             : 
     369       12718 :     if (psResultWithLimit->nMaxFileSize > 0 &&
     370           8 :         psResult->nDataLen > psResultWithLimit->nMaxFileSize)
     371             :     {
     372           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Maximum file size reached");
     373           1 :         return 0;
     374             :     }
     375             : 
     376       12717 :     return nmemb;
     377             : }
     378             : 
     379             : /************************************************************************/
     380             : /*                           CPLHdrWriteFct()                           */
     381             : /************************************************************************/
     382        7594 : static size_t CPLHdrWriteFct(void *buffer, size_t size, size_t nmemb,
     383             :                              void *reqInfo)
     384             : {
     385        7594 :     CPLHTTPResult *psResult = static_cast<CPLHTTPResult *>(reqInfo);
     386             :     // Copy the buffer to a char* and initialize with zeros (zero
     387             :     // terminate as well).
     388        7594 :     size_t nBytes = size * nmemb;
     389        7594 :     char *pszHdr = static_cast<char *>(CPLCalloc(1, nBytes + 1));
     390        7594 :     memcpy(pszHdr, buffer, nBytes);
     391        7594 :     size_t nIdx = nBytes - 1;
     392             :     // Remove end of line characters
     393       21611 :     while (nIdx > 0 && (pszHdr[nIdx] == '\r' || pszHdr[nIdx] == '\n'))
     394             :     {
     395       14017 :         pszHdr[nIdx] = 0;
     396       14017 :         nIdx--;
     397             :     }
     398        7594 :     char *pszKey = nullptr;
     399        7594 :     const char *pszValue = CPLParseNameValue(pszHdr, &pszKey);
     400        7594 :     if (pszKey && pszValue)
     401             :     {
     402        5264 :         psResult->papszHeaders =
     403        5264 :             CSLAddNameValue(psResult->papszHeaders, pszKey, pszValue);
     404             :     }
     405        7594 :     CPLFree(pszHdr);
     406        7594 :     CPLFree(pszKey);
     407        7594 :     return nmemb;
     408             : }
     409             : 
     410             : /************************************************************************/
     411             : /*                        CPLHTTPReadFunction()                         */
     412             : /************************************************************************/
     413           0 : static size_t CPLHTTPReadFunction(char *buffer, size_t size, size_t nitems,
     414             :                                   void *arg)
     415             : {
     416           0 :     return VSIFReadL(buffer, size, nitems, static_cast<VSILFILE *>(arg));
     417             : }
     418             : 
     419             : /************************************************************************/
     420             : /*                        CPLHTTPSeekFunction()                         */
     421             : /************************************************************************/
     422           0 : static int CPLHTTPSeekFunction(void *arg, curl_off_t offset, int origin)
     423             : {
     424           0 :     if (VSIFSeekL(static_cast<VSILFILE *>(arg), offset, origin) == 0)
     425           0 :         return CURL_SEEKFUNC_OK;
     426             :     else
     427           0 :         return CURL_SEEKFUNC_FAIL;
     428             : }
     429             : 
     430             : /************************************************************************/
     431             : /*                        CPLHTTPFreeFunction()                         */
     432             : /************************************************************************/
     433           0 : static void CPLHTTPFreeFunction(void *arg)
     434             : {
     435           0 :     VSIFCloseL(static_cast<VSILFILE *>(arg));
     436           0 : }
     437             : 
     438             : typedef struct
     439             : {
     440             :     GDALProgressFunc pfnProgress;
     441             :     void *pProgressArg;
     442             : } CurlProcessData, *CurlProcessDataL;
     443             : 
     444         362 : static int NewProcessFunction(void *p, curl_off_t dltotal, curl_off_t dlnow,
     445             :                               curl_off_t ultotal, curl_off_t ulnow)
     446             : {
     447         362 :     CurlProcessDataL pData = static_cast<CurlProcessDataL>(p);
     448         362 :     if (nullptr != pData && pData->pfnProgress)
     449             :     {
     450         362 :         if (dltotal > 0)
     451             :         {
     452           0 :             const double dfDone = double(dlnow) / dltotal;
     453           0 :             return pData->pfnProgress(dfDone, "Downloading ...",
     454             :                                       pData->pProgressArg) == TRUE
     455           0 :                        ? 0
     456           0 :                        : 1;
     457             :         }
     458         362 :         else if (ultotal > 0)
     459             :         {
     460           0 :             const double dfDone = double(ulnow) / ultotal;
     461           0 :             return pData->pfnProgress(dfDone, "Uploading ...",
     462             :                                       pData->pProgressArg) == TRUE
     463           0 :                        ? 0
     464           0 :                        : 1;
     465             :         }
     466             :     }
     467         362 :     return 0;
     468             : }
     469             : 
     470             : #endif /* def HAVE_CURL */
     471             : 
     472             : /************************************************************************/
     473             : /*                     CPLHTTPSetDefaultUserAgent()                     */
     474             : /************************************************************************/
     475             : 
     476             : static std::string gosDefaultUserAgent;
     477             : 
     478             : /**
     479             :  * \brief Set the default user agent.
     480             :  *
     481             :  * GDAL core will by default call this method with "GDAL/x.y.z" where x.y.z
     482             :  * is the GDAL version number (during driver initialization). Applications may
     483             :  * override it.
     484             :  *
     485             :  * @param pszUserAgent String (or nullptr to cancel the default user agent)
     486             :  *
     487             :  * @since GDAL 3.7
     488             :  */
     489        1743 : void CPLHTTPSetDefaultUserAgent(const char *pszUserAgent)
     490             : {
     491        1743 :     gosDefaultUserAgent = pszUserAgent ? pszUserAgent : "";
     492        1743 : }
     493             : 
     494             : /************************************************************************/
     495             : /*                       CPLHTTPGetOptionsFromEnv()                     */
     496             : /************************************************************************/
     497             : 
     498             : typedef struct
     499             : {
     500             :     const char *pszEnvVar;
     501             :     const char *pszOptionName;
     502             : } TupleEnvVarOptionName;
     503             : 
     504             : constexpr TupleEnvVarOptionName asAssocEnvVarOptionName[] = {
     505             :     {"GDAL_HTTP_VERSION", "HTTP_VERSION"},
     506             :     {"GDAL_HTTP_CONNECTTIMEOUT", "CONNECTTIMEOUT"},
     507             :     {"GDAL_HTTP_TIMEOUT", "TIMEOUT"},
     508             :     {"GDAL_HTTP_LOW_SPEED_TIME", "LOW_SPEED_TIME"},
     509             :     {"GDAL_HTTP_LOW_SPEED_LIMIT", "LOW_SPEED_LIMIT"},
     510             :     {"GDAL_HTTP_USERPWD", "USERPWD"},
     511             :     {"GDAL_HTTP_PROXY", "PROXY"},
     512             :     {"GDAL_HTTPS_PROXY", "HTTPS_PROXY"},
     513             :     {"GDAL_HTTP_PROXYUSERPWD", "PROXYUSERPWD"},
     514             :     {"GDAL_PROXY_AUTH", "PROXYAUTH"},
     515             :     {"GDAL_HTTP_NETRC", "NETRC"},
     516             :     {"GDAL_HTTP_NETRC_FILE", "NETRC_FILE"},
     517             :     {"GDAL_HTTP_MAX_RETRY", "MAX_RETRY"},
     518             :     {"GDAL_HTTP_RETRY_DELAY", "RETRY_DELAY"},
     519             :     {"GDAL_HTTP_RETRY_CODES", "RETRY_CODES"},
     520             :     {"GDAL_CURL_CA_BUNDLE", "CAINFO"},
     521             :     {"CURL_CA_BUNDLE", "CAINFO"},
     522             :     {"SSL_CERT_FILE", "CAINFO"},
     523             :     {"GDAL_HTTP_CAPATH", "CAPATH"},
     524             :     {"GDAL_HTTP_SSL_VERIFYSTATUS", "SSL_VERIFYSTATUS"},
     525             :     {"GDAL_HTTP_USE_CAPI_STORE", "USE_CAPI_STORE"},
     526             :     {"GDAL_HTTP_HEADERS", "HEADERS"},
     527             :     {"GDAL_HTTP_HEADER_FILE", "HEADER_FILE"},
     528             :     {"GDAL_HTTP_AUTH", "HTTPAUTH"},
     529             :     {"GDAL_GSSAPI_DELEGATION", "GSSAPI_DELEGATION"},
     530             :     {"GDAL_HTTP_BEARER", "HTTP_BEARER"},
     531             :     {"GDAL_HTTP_COOKIE", "COOKIE"},
     532             :     {"GDAL_HTTP_COOKIEFILE", "COOKIEFILE"},
     533             :     {"GDAL_HTTP_COOKIEJAR", "COOKIEJAR"},
     534             :     {"GDAL_HTTP_MAX_RETRY", "MAX_RETRY"},
     535             :     {"GDAL_HTTP_RETRY_DELAY", "RETRY_DELAY"},
     536             :     {"GDAL_HTTP_TCP_KEEPALIVE", "TCP_KEEPALIVE"},
     537             :     {"GDAL_HTTP_TCP_KEEPIDLE", "TCP_KEEPIDLE"},
     538             :     {"GDAL_HTTP_TCP_KEEPINTVL", "TCP_KEEPINTVL"},
     539             :     {"GDAL_HTTP_PATH_VERBATIM", "PATH_VERBATIM"},
     540             : };
     541             : 
     542        1588 : char **CPLHTTPGetOptionsFromEnv(const char *pszFilename)
     543             : {
     544        3176 :     CPLStringList aosOptions;
     545        3176 :     std::string osNonStreamingFilename;
     546        1588 :     if (pszFilename && STARTS_WITH(pszFilename, "/vsi"))
     547             :     {
     548             :         VSIFilesystemHandler *poFSHandler =
     549        1586 :             VSIFileManager::GetHandler(pszFilename);
     550             :         osNonStreamingFilename =
     551        1586 :             poFSHandler->GetNonStreamingFilename(pszFilename);
     552        1586 :         if (osNonStreamingFilename == pszFilename)
     553             :         {
     554        1521 :             osNonStreamingFilename.clear();
     555             :         }
     556             :         else
     557             :         {
     558             :             // CPLDebug("HTTP", "Non-streaming filename for %s: %s", pszFilename, osNonStreamingFilename.c_str());
     559             :         }
     560             :     }
     561       57168 :     for (const auto &sTuple : asAssocEnvVarOptionName)
     562             :     {
     563       55580 :         const char *pszVal = nullptr;
     564       55580 :         if (pszFilename)
     565             :         {
     566       55580 :             pszVal = VSIGetPathSpecificOption(pszFilename, sTuple.pszEnvVar,
     567             :                                               nullptr);
     568       55580 :             if (!pszVal && !osNonStreamingFilename.empty())
     569             :             {
     570        2263 :                 pszVal = VSIGetPathSpecificOption(
     571        2263 :                     osNonStreamingFilename.c_str(), sTuple.pszEnvVar, nullptr);
     572             :             }
     573             :         }
     574       55580 :         if (!pszVal)
     575             :         {
     576       55420 :             pszVal = CPLGetConfigOption(sTuple.pszEnvVar, nullptr);
     577             :         }
     578       55579 :         if (pszVal)
     579             :         {
     580         160 :             aosOptions.AddNameValue(sTuple.pszOptionName, pszVal);
     581             :         }
     582             :     }
     583        3176 :     return aosOptions.StealList();
     584             : }
     585             : 
     586             : /************************************************************************/
     587             : /*                      CPLHTTPGetNewRetryDelay()                       */
     588             : /************************************************************************/
     589             : 
     590             : /** Return the new retry delay.
     591             :  *
     592             :  * This takes into account the HTTP response code, the previous delay, the
     593             :  * HTTP payload error message, the Curl error message and a potential list of
     594             :  * retriable HTTP codes.
     595             :  *
     596             :  * @param response_code HTTP response code (e.g. 400)
     597             :  * @param dfOldDelay Previous delay (nominally in second)
     598             :  * @param pszErrBuf HTTP response body of the failed request (may be NULL)
     599             :  * @param pszCurlError Curl error as returned by CURLOPT_ERRORBUFFER (may be NULL)
     600             :  * @param pszRetriableCodes nullptr to limit to the default hard-coded scenarios,
     601             :  * "ALL" to ask to retry for all non-200 codes, or a comma-separated list of
     602             :  * HTTP codes (e.g. "400,500") that are accepted for retry.
     603             :  * @return the new delay, or 0 if no retry should be attempted.
     604             :  */
     605         231 : static double CPLHTTPGetNewRetryDelay(int response_code, double dfOldDelay,
     606             :                                       const char *pszErrBuf,
     607             :                                       const char *pszCurlError,
     608             :                                       const char *pszRetriableCodes)
     609             : {
     610         231 :     bool bRetry = false;
     611         231 :     if (pszRetriableCodes && pszRetriableCodes[0])
     612             :     {
     613           5 :         bRetry = EQUAL(pszRetriableCodes, "ALL") ||
     614           2 :                  strstr(pszRetriableCodes, CPLSPrintf("%d", response_code));
     615             :     }
     616         228 :     else if (response_code == 429 || response_code == 500 ||
     617         218 :              (response_code >= 502 && response_code <= 504) ||
     618             :              // S3 sends some client timeout errors as 400 Client Error
     619           6 :              (response_code == 400 && pszErrBuf &&
     620         206 :               strstr(pszErrBuf, "RequestTimeout")) ||
     621         206 :              (pszCurlError &&
     622         206 :               (strstr(pszCurlError, "Connection timed out") ||
     623         206 :                strstr(pszCurlError, "Operation timed out") ||
     624         206 :                strstr(pszCurlError, "Connection reset by peer") ||
     625         206 :                strstr(pszCurlError, "Connection was reset") ||
     626         202 :                strstr(pszCurlError, "SSL connection timeout"))))
     627             :     {
     628          26 :         bRetry = true;
     629             :     }
     630         231 :     if (bRetry)
     631             :     {
     632             :         // 'Operation timed out': seen during some long running operation 'hang'
     633             :         // no error but no response from server and we are in the cURL loop
     634             :         // infinitely.
     635             : 
     636             :         // 'Connection was reset': was found with Azure: server resets
     637             :         // connection during TLS handshake (10054 error code). It seems like
     638             :         // the server process crashed or something forced TCP reset;
     639             :         // the request succeeds on retry.
     640             : 
     641             :         // Use an exponential backoff factor of 2 plus some random jitter
     642             :         // We don't care about cryptographic quality randomness, hence:
     643             : #ifndef __COVERITY__
     644          28 :         return dfOldDelay * (2 + rand() * 0.5 / RAND_MAX);
     645             : #else
     646             :         return dfOldDelay * 2;
     647             : #endif
     648             :     }
     649             :     else
     650             :     {
     651         203 :         return 0;
     652             :     }
     653             : }
     654             : 
     655             : /*! @cond Doxygen_Suppress */
     656             : 
     657             : /************************************************************************/
     658             : /*                      CPLHTTPRetryParameters()                        */
     659             : /************************************************************************/
     660             : 
     661             : /** Constructs a CPLHTTPRetryParameters instance from configuration
     662             :  * options or path-specific options.
     663             :  *
     664             :  * @param aosHTTPOptions HTTP options returned by CPLHTTPGetOptionsFromEnv()
     665             :  */
     666        1299 : CPLHTTPRetryParameters::CPLHTTPRetryParameters(
     667        1299 :     const CPLStringList &aosHTTPOptions)
     668        1299 :     : nMaxRetry(atoi(aosHTTPOptions.FetchNameValueDef(
     669             :           "MAX_RETRY", CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)))),
     670        1299 :       dfInitialDelay(CPLAtof(aosHTTPOptions.FetchNameValueDef(
     671             :           "RETRY_DELAY", CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)))),
     672        2598 :       osRetryCodes(aosHTTPOptions.FetchNameValueDef("RETRY_CODES", ""))
     673             : {
     674        1299 : }
     675             : 
     676             : /************************************************************************/
     677             : /*                        CPLHTTPRetryContext()                         */
     678             : /************************************************************************/
     679             : 
     680             : /** Constructor */
     681        1090 : CPLHTTPRetryContext::CPLHTTPRetryContext(const CPLHTTPRetryParameters &oParams)
     682        1090 :     : m_oParameters(oParams), m_dfNextDelay(oParams.dfInitialDelay)
     683             : {
     684        1090 : }
     685             : 
     686             : /************************************************************************/
     687             : /*                     CPLHTTPRetryContext::CanRetry()                  */
     688             : /************************************************************************/
     689             : 
     690             : /** Returns whether we can attempt a new retry, based on the retry counter,
     691             :  * and increment that counter.
     692             :  */
     693           0 : bool CPLHTTPRetryContext::CanRetry()
     694             : {
     695           0 :     if (m_nRetryCount >= m_oParameters.nMaxRetry)
     696           0 :         return false;
     697           0 :     m_nRetryCount++;
     698           0 :     return true;
     699             : }
     700             : 
     701             : /** Returns whether we can attempt a new retry, based on the retry counter,
     702             :  * the response code, payload and curl error buffers.
     703             :  *
     704             :  * If successful, the retry counter is incremented, and GetCurrentDelay()
     705             :  * returns the delay to apply with CPLSleep().
     706             :  */
     707         245 : bool CPLHTTPRetryContext::CanRetry(int response_code, const char *pszErrBuf,
     708             :                                    const char *pszCurlError)
     709             : {
     710         245 :     if (m_nRetryCount >= m_oParameters.nMaxRetry)
     711         224 :         return false;
     712          21 :     m_dfCurDelay = m_dfNextDelay;
     713          21 :     m_dfNextDelay = CPLHTTPGetNewRetryDelay(response_code, m_dfNextDelay,
     714             :                                             pszErrBuf, pszCurlError,
     715             :                                             m_oParameters.osRetryCodes.c_str());
     716          21 :     if (m_dfNextDelay == 0.0)
     717           2 :         return false;
     718          19 :     m_nRetryCount++;
     719          19 :     return true;
     720             : }
     721             : 
     722             : /************************************************************************/
     723             : /*                CPLHTTPRetryContext::GetCurrentDelay()                */
     724             : /************************************************************************/
     725             : 
     726             : /** Returns the delay to apply. Only valid after a successful call to CanRetry() */
     727          38 : double CPLHTTPRetryContext::GetCurrentDelay() const
     728             : {
     729          38 :     if (m_nRetryCount == 0)
     730           0 :         CPLDebug("CPL",
     731             :                  "GetCurrentDelay() should only be called after CanRetry()");
     732          38 :     return m_dfCurDelay;
     733             : }
     734             : 
     735             : /*! @endcond Doxygen_Suppress */
     736             : 
     737             : #ifdef HAVE_CURL
     738             : 
     739             : /************************************************************************/
     740             : /*                      CPLHTTPEmitFetchDebug()                         */
     741             : /************************************************************************/
     742             : 
     743        1175 : static void CPLHTTPEmitFetchDebug(const char *pszURL,
     744             :                                   const char *pszExtraDebug = "")
     745             : {
     746        1175 :     const char *pszArobase = strchr(pszURL, '@');
     747        1175 :     const char *pszSlash = strchr(pszURL, '/');
     748        1175 :     const char *pszColon = (pszSlash) ? strchr(pszSlash, ':') : nullptr;
     749        1175 :     if (pszArobase != nullptr && pszColon != nullptr &&
     750           0 :         pszArobase - pszColon > 0)
     751             :     {
     752             :         /* http://user:password@www.example.com */
     753           0 :         char *pszSanitizedURL = CPLStrdup(pszURL);
     754           0 :         pszSanitizedURL[pszColon - pszURL] = 0;
     755           0 :         CPLDebug("HTTP", "Fetch(%s:#password#%s%s)", pszSanitizedURL,
     756             :                  pszArobase, pszExtraDebug);
     757           0 :         CPLFree(pszSanitizedURL);
     758             :     }
     759             :     else
     760             :     {
     761        1175 :         CPLDebug("HTTP", "Fetch(%s%s)", pszURL, pszExtraDebug);
     762             :     }
     763        1175 : }
     764             : 
     765             : #endif
     766             : 
     767             : #ifdef HAVE_CURL
     768             : 
     769             : /************************************************************************/
     770             : /*                      class CPLHTTPPostFields                         */
     771             : /************************************************************************/
     772             : 
     773             : class CPLHTTPPostFields
     774             : {
     775             :   public:
     776        1175 :     CPLHTTPPostFields() = default;
     777             :     CPLHTTPPostFields &operator=(const CPLHTTPPostFields &) = delete;
     778             :     CPLHTTPPostFields(const CPLHTTPPostFields &) = delete;
     779             : 
     780        1175 :     CPLErr Fill(CURL *http_handle, CSLConstList papszOptions)
     781             :     {
     782             :         // Fill POST form if present
     783             :         const char *pszFormFilePath =
     784        1175 :             CSLFetchNameValue(papszOptions, "FORM_FILE_PATH");
     785             :         const char *pszParametersCount =
     786        1175 :             CSLFetchNameValue(papszOptions, "FORM_ITEM_COUNT");
     787             : 
     788        1175 :         if (pszFormFilePath != nullptr || pszParametersCount != nullptr)
     789             :         {
     790           2 :             mime = curl_mime_init(http_handle);
     791           2 :             curl_mimepart *mimepart = curl_mime_addpart(mime);
     792           2 :             if (pszFormFilePath != nullptr)
     793             :             {
     794             :                 const char *pszFormFileName =
     795           1 :                     CSLFetchNameValue(papszOptions, "FORM_FILE_NAME");
     796           1 :                 const char *pszFilename = CPLGetFilename(pszFormFilePath);
     797           1 :                 if (pszFormFileName == nullptr)
     798             :                 {
     799           1 :                     pszFormFileName = pszFilename;
     800             :                 }
     801             : 
     802             :                 VSIStatBufL sStat;
     803           1 :                 if (VSIStatL(pszFormFilePath, &sStat) == 0)
     804             :                 {
     805           0 :                     VSILFILE *mime_fp = VSIFOpenL(pszFormFilePath, "rb");
     806           0 :                     if (mime_fp != nullptr)
     807             :                     {
     808           0 :                         curl_mime_name(mimepart, pszFormFileName);
     809           0 :                         CPL_IGNORE_RET_VAL(
     810           0 :                             curl_mime_filename(mimepart, pszFilename));
     811           0 :                         curl_mime_data_cb(
     812             :                             mimepart, sStat.st_size, CPLHTTPReadFunction,
     813             :                             CPLHTTPSeekFunction, CPLHTTPFreeFunction, mime_fp);
     814             :                     }
     815             :                     else
     816             :                     {
     817             :                         osErrMsg = CPLSPrintf("Failed to open file %s",
     818           0 :                                               pszFormFilePath);
     819           1 :                         return CE_Failure;
     820             :                     }
     821             : 
     822           0 :                     CPLDebug("HTTP", "Send file: %s, COPYNAME: %s",
     823             :                              pszFormFilePath, pszFormFileName);
     824             :                 }
     825             :                 else
     826             :                 {
     827             :                     osErrMsg =
     828           1 :                         CPLSPrintf("File '%s' not found", pszFormFilePath);
     829           1 :                     return CE_Failure;
     830             :                 }
     831             :             }
     832             : 
     833           1 :             int nParametersCount = 0;
     834           1 :             if (pszParametersCount != nullptr)
     835             :             {
     836           1 :                 nParametersCount = atoi(pszParametersCount);
     837             :             }
     838             : 
     839           2 :             for (int i = 0; i < nParametersCount; ++i)
     840             :             {
     841           2 :                 const char *pszKey = CSLFetchNameValue(
     842             :                     papszOptions, CPLSPrintf("FORM_KEY_%d", i));
     843           2 :                 const char *pszValue = CSLFetchNameValue(
     844             :                     papszOptions, CPLSPrintf("FORM_VALUE_%d", i));
     845             : 
     846           2 :                 if (nullptr == pszKey)
     847             :                 {
     848             :                     osErrMsg = CPLSPrintf("Key #%d is not exists. Maybe wrong "
     849             :                                           "count of form items",
     850           1 :                                           i);
     851           1 :                     return CE_Failure;
     852             :                 }
     853             : 
     854           1 :                 if (nullptr == pszValue)
     855             :                 {
     856             :                     osErrMsg = CPLSPrintf("Value #%d is not exists. Maybe "
     857             :                                           "wrong count of form items",
     858           0 :                                           i);
     859           0 :                     return CE_Failure;
     860             :                 }
     861             : 
     862           1 :                 mimepart = curl_mime_addpart(mime);
     863           1 :                 curl_mime_name(mimepart, pszKey);
     864           1 :                 CPL_IGNORE_RET_VAL(
     865           1 :                     curl_mime_data(mimepart, pszValue, CURL_ZERO_TERMINATED));
     866             : 
     867           1 :                 CPLDebug("HTTP", "COPYNAME: %s, COPYCONTENTS: %s", pszKey,
     868             :                          pszValue);
     869             :             }
     870             : 
     871           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_MIMEPOST, mime);
     872             :         }
     873        1173 :         return CE_None;
     874             :     }
     875             : 
     876        1175 :     ~CPLHTTPPostFields()
     877        1175 :     {
     878        1175 :         if (mime != nullptr)
     879             :         {
     880           2 :             curl_mime_free(mime);
     881             :         }
     882        1175 :     }
     883             : 
     884           2 :     std::string GetErrorMessage() const
     885             :     {
     886           2 :         return osErrMsg;
     887             :     }
     888             : 
     889             :   private:
     890             :     curl_mime *mime = nullptr;
     891             :     std::string osErrMsg{};
     892             : };
     893             : 
     894             : /************************************************************************/
     895             : /*                       CPLHTTPFetchCleanup()                          */
     896             : /************************************************************************/
     897             : 
     898        1175 : static void CPLHTTPFetchCleanup(CURL *http_handle, struct curl_slist *headers,
     899             :                                 const char *pszPersistent,
     900             :                                 CSLConstList papszOptions)
     901             : {
     902        1175 :     if (CSLFetchNameValue(papszOptions, "POSTFIELDS"))
     903         159 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_POST, 0);
     904        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER, nullptr);
     905             : 
     906        1175 :     if (!pszPersistent)
     907         673 :         curl_easy_cleanup(http_handle);
     908             : 
     909        1175 :     curl_slist_free_all(headers);
     910        1175 : }
     911             : #endif  // HAVE_CURL
     912             : 
     913             : struct CPLHTTPFetchContext
     914             : {
     915             :     std::vector<std::pair<CPLHTTPFetchCallbackFunc, void *>> stack{};
     916             : };
     917             : 
     918             : /************************************************************************/
     919             : /*                        GetHTTPFetchContext()                         */
     920             : /************************************************************************/
     921             : 
     922        1368 : static CPLHTTPFetchContext *GetHTTPFetchContext(bool bAlloc)
     923             : {
     924        1368 :     int bError = FALSE;
     925             :     CPLHTTPFetchContext *psCtx = static_cast<CPLHTTPFetchContext *>(
     926        1368 :         CPLGetTLSEx(CTLS_HTTPFETCHCALLBACK, &bError));
     927        1368 :     if (bError)
     928           0 :         return nullptr;
     929             : 
     930        1368 :     if (psCtx == nullptr && bAlloc)
     931             :     {
     932           1 :         const auto FreeFunc = [](void *pData)
     933           1 :         { delete static_cast<CPLHTTPFetchContext *>(pData); };
     934           1 :         psCtx = new CPLHTTPFetchContext();
     935           1 :         CPLSetTLSWithFreeFuncEx(CTLS_HTTPFETCHCALLBACK, psCtx, FreeFunc,
     936             :                                 &bError);
     937           1 :         if (bError)
     938             :         {
     939           0 :             delete psCtx;
     940           0 :             psCtx = nullptr;
     941             :         }
     942             :     }
     943        1368 :     return psCtx;
     944             : }
     945             : 
     946             : /************************************************************************/
     947             : /*                      CPLHTTPSetFetchCallback()                       */
     948             : /************************************************************************/
     949             : 
     950             : static CPLHTTPFetchCallbackFunc gpsHTTPFetchCallbackFunc = nullptr;
     951             : static void *gpHTTPFetchCallbackUserData = nullptr;
     952             : 
     953             : /** Installs an alternate callback to the default implementation of
     954             :  * CPLHTTPFetchEx().
     955             :  *
     956             :  * This callback will be used by all threads, unless contextual callbacks are
     957             :  * installed with CPLHTTPPushFetchCallback().
     958             :  *
     959             :  * It is the responsibility of the caller to make sure this function is not
     960             :  * called concurrently, or during CPLHTTPFetchEx() execution.
     961             :  *
     962             :  * @param pFunc Callback function to be called with CPLHTTPFetchEx() is called
     963             :  *              (or NULL to restore default handler)
     964             :  * @param pUserData Last argument to provide to the pFunc callback.
     965             :  *
     966             :  * @since GDAL 3.2
     967             :  */
     968           2 : void CPLHTTPSetFetchCallback(CPLHTTPFetchCallbackFunc pFunc, void *pUserData)
     969             : {
     970           2 :     gpsHTTPFetchCallbackFunc = pFunc;
     971           2 :     gpHTTPFetchCallbackUserData = pUserData;
     972           2 : }
     973             : 
     974             : /************************************************************************/
     975             : /*                      CPLHTTPPushFetchCallback()                      */
     976             : /************************************************************************/
     977             : 
     978             : /** Installs an alternate callback to the default implementation of
     979             :  * CPLHTTPFetchEx().
     980             :  *
     981             :  * This callback will only be used in the thread where this function has been
     982             :  * called. It must be un-installed by CPLHTTPPopFetchCallback(), which must also
     983             :  * be called from the same thread.
     984             :  *
     985             :  * @param pFunc Callback function to be called with CPLHTTPFetchEx() is called.
     986             :  * @param pUserData Last argument to provide to the pFunc callback.
     987             :  * @return TRUE in case of success.
     988             :  *
     989             :  * @since GDAL 3.2
     990             :  */
     991           1 : int CPLHTTPPushFetchCallback(CPLHTTPFetchCallbackFunc pFunc, void *pUserData)
     992             : {
     993           1 :     auto psCtx = GetHTTPFetchContext(true);
     994           1 :     if (psCtx == nullptr)
     995           0 :         return false;
     996             :     psCtx->stack.emplace_back(
     997           1 :         std::pair<CPLHTTPFetchCallbackFunc, void *>(pFunc, pUserData));
     998           1 :     return true;
     999             : }
    1000             : 
    1001             : /************************************************************************/
    1002             : /*                       CPLHTTPPopFetchCallback()                      */
    1003             : /************************************************************************/
    1004             : 
    1005             : /** Uninstalls a callback set by CPLHTTPPushFetchCallback().
    1006             :  *
    1007             :  * @see CPLHTTPPushFetchCallback()
    1008             :  * @return TRUE in case of success.
    1009             :  * @since GDAL 3.2
    1010             :  */
    1011           2 : int CPLHTTPPopFetchCallback(void)
    1012             : {
    1013           2 :     auto psCtx = GetHTTPFetchContext(false);
    1014           2 :     if (psCtx == nullptr || psCtx->stack.empty())
    1015             :     {
    1016           1 :         CPLError(
    1017             :             CE_Failure, CPLE_AppDefined,
    1018             :             "CPLHTTPPushFetchCallback / CPLHTTPPopFetchCallback not balanced");
    1019           1 :         return false;
    1020             :     }
    1021             :     else
    1022             :     {
    1023           1 :         psCtx->stack.pop_back();
    1024           1 :         return true;
    1025             :     }
    1026             : }
    1027             : 
    1028             : /************************************************************************/
    1029             : /*                           CPLHTTPFetch()                             */
    1030             : /************************************************************************/
    1031             : 
    1032             : // NOTE: when adding an option below, add it in asAssocEnvVarOptionName[]
    1033             : 
    1034             : // clang-format off
    1035             : /**
    1036             :  * \brief Fetch a document from an url and return in a string.
    1037             :  *
    1038             :  * Different options may be passed through the papszOptions function parameter,
    1039             :  * or for most of them through a configuration option:
    1040             :  * <ul>
    1041             :  * <li>CONNECTTIMEOUT=val, where
    1042             :  * val is in seconds (possibly with decimals). This is the maximum delay for the
    1043             :  * connection to be established before being aborted (GDAL >= 2.2).
    1044             :  * Corresponding configuration option: GDAL_HTTP_CONNECTTIMEOUT.
    1045             :  * </li>
    1046             :  * <li>TIMEOUT=val, where val is in seconds. This is the maximum delay for the
    1047             :  * whole request to complete before being aborted.
    1048             :  * Corresponding configuration option: GDAL_HTTP_TIMEOUT.
    1049             :  * </li>
    1050             :  * <li>LOW_SPEED_TIME=val,
    1051             :  * where val is in seconds. This is the maximum time where the transfer speed
    1052             :  * should be below the LOW_SPEED_LIMIT (if not specified 1b/s), before the
    1053             :  * transfer to be considered too slow and aborted. (GDAL >= 2.1).
    1054             :  * Corresponding configuration option: GDAL_HTTP_LOW_SPEED_TIME.
    1055             :  * </li>
    1056             :  * <li>LOW_SPEED_LIMIT=val, where val is in bytes/second. See LOW_SPEED_TIME.
    1057             :  * Has only effect if LOW_SPEED_TIME is specified too. (GDAL >= 2.1).
    1058             :  * Corresponding configuration option: GDAL_HTTP_LOW_SPEED_LIMIT.
    1059             :  * </li>
    1060             :  * <li>HEADERS=val, where val is an extra header to use when getting a web page.
    1061             :  *                  For example "Accept: application/x-ogcwkt"
    1062             :  * Corresponding configuration option: GDAL_HTTP_HEADERS.
    1063             :  * Starting with GDAL 3.6, the GDAL_HTTP_HEADERS configuration option can also
    1064             :  * be used to specify a comma separated list of key: value pairs. This is an
    1065             :  * alternative to the GDAL_HTTP_HEADER_FILE mechanism. If a comma or a
    1066             :  * double-quote character is needed in the value, then the key: value pair must
    1067             :  * be enclosed in double-quote characters. In that situation, backslash and
    1068             :  * double quote character must be backslash-escaped. e.g GDAL_HTTP_HEADERS=Foo:
    1069             :  * Bar,"Baz: escaped backslash \\, escaped double-quote \", end of
    1070             :  * value",Another: Header
    1071             :  * </li>
    1072             :  * <li>HEADER_FILE=filename: filename of a text file with "key: value" headers.
    1073             :  *     The content of the file is not cached, and thus it is read again before
    1074             :  *     issuing each HTTP request. (GDAL >= 2.2)
    1075             :  * Corresponding configuration option: GDAL_HTTP_HEADER_FILE.
    1076             :  * </li>
    1077             :  * <li>HTTPAUTH=[BASIC/NTLM/NEGOTIATE/ANY/ANYSAFE/BEARER] to specify an
    1078             :  * authentication scheme to use.
    1079             :  * Corresponding configuration option: GDAL_HTTP_AUTH.
    1080             :  * </li>
    1081             :  * <li>USERPWD=userid:password to specify a user and password for
    1082             :  * authentication.
    1083             :  * Corresponding configuration option: GDAL_HTTP_USERPWD.
    1084             :  * </li>
    1085             :  * <li>GSSAPI_DELEGATION=[NONE/POLICY/ALWAYS] set allowed
    1086             :  * GSS-API delegation. Relevant only with HTTPAUTH=NEGOTIATE (GDAL >= 3.3).
    1087             :  * Corresponding configuration option: GDAL_GSSAPI_DELEGATION (note: no "HTTP_" in the name)
    1088             :  * </li>
    1089             :  * <li>HTTP_BEARER=val set OAuth 2.0 Bearer Access Token.
    1090             :  * Relevant only with HTTPAUTH=BEARER (GDAL >= 3.9).
    1091             :  * Corresponding configuration option: GDAL_HTTP_BEARER
    1092             :  * </li>
    1093             :  * <li>POSTFIELDS=val, where val is a nul-terminated string to be passed to the
    1094             :  * server with a POST request.
    1095             :  * No Corresponding configuration option.
    1096             :  * </li>
    1097             :  * <li>PROXY=val, to make requests go through a
    1098             :  * proxy server, where val is of the form proxy.server.com:port_number. This
    1099             :  * option affects both HTTP and HTTPS URLs.
    1100             :  * Corresponding configuration option: GDAL_HTTP_PROXY.
    1101             :  * </li>
    1102             :  * <li>HTTPS_PROXY=val (GDAL
    1103             :  * >= 2.4), the same meaning as PROXY, but this option is taken into account
    1104             :  * only for HTTPS URLs.
    1105             :  * Corresponding configuration option: GDAL_HTTPS_PROXY.
    1106             :  * </li>
    1107             :  * <li>PROXYUSERPWD=val, where val is of the form username:password.
    1108             :  * Corresponding configuration option: GDAL_HTTP_PROXYUSERPWD
    1109             :  * </li>
    1110             :  * <li>PROXYAUTH=[BASIC/NTLM/DIGEST/NEGOTIATE/ANY/ANYSAFE] to
    1111             :  * specify an proxy authentication scheme to use..
    1112             :  * Corresponding configuration option: GDAL_PROXYAUTH (note: no "HTTP_" in the name)
    1113             :  * </li>
    1114             :  * <li>NETRC=[YES/NO] to
    1115             :  * enable or disable use of $HOME/.netrc (or NETRC_FILE), default YES.
    1116             :  * Corresponding configuration option: GDAL_HTTP_NETRC.
    1117             :  * </li>
    1118             :  * <li>NETRC_FILE=file name to read .netrc info from  (GDAL >= 3.7).
    1119             :  * Corresponding configuration option: GDAL_HTTP_NETRC_FILE.
    1120             :  * </li>
    1121             :  * <li>CUSTOMREQUEST=val, where val is GET, PUT, POST, DELETE, etc...
    1122             :  * No corresponding configuration option.
    1123             :  * </li>
    1124             :  * <li>FORM_FILE_NAME=val, where val is upload file name. If this
    1125             :  * option and FORM_FILE_PATH present, request type will set to POST.
    1126             :  * No corresponding configuration option.
    1127             :  * </li>
    1128             :  * <li>FORM_FILE_PATH=val, where val is upload file path.
    1129             :  * No corresponding configuration option.
    1130             :  * </li>
    1131             :  * <li>FORM_KEY_0=val...FORM_KEY_N, where val is name of form item.
    1132             :  * No corresponding configuration option.
    1133             :  * </li>
    1134             :  * <li>FORM_VALUE_0=val...FORM_VALUE_N, where val is value of the form
    1135             :  * item.
    1136             :  * No corresponding configuration option.
    1137             :  * </li>
    1138             :  * <li>FORM_ITEM_COUNT=val, where val is count of form items.
    1139             :  * No corresponding configuration option.
    1140             :  * </li>
    1141             :  * <li>COOKIE=val, where val is formatted as COOKIE1=VALUE1; COOKIE2=VALUE2;...
    1142             :  * Corresponding configuration option: GDAL_HTTP_COOKIE.</li>
    1143             :  * <li>COOKIEFILE=val, where val is file name to read cookies from
    1144             :  * (GDAL >= 2.4).
    1145             :  * Corresponding configuration option: GDAL_HTTP_COOKIEFILE.</li>
    1146             :  * <li>COOKIEJAR=val, where val is file name to store cookies to (GDAL >= 2.4).
    1147             :  * Corresponding configuration option: GDAL_HTTP_COOKIEJAR.</li>
    1148             :  * <li>MAX_RETRY=val, where val is the maximum number of
    1149             :  * retry attempts, when a retry is allowed (cf RETRY_CODES option).
    1150             :  * Default is 0, meaning no retry.
    1151             :  * Corresponding configuration option: GDAL_HTTP_MAX_RETRY.
    1152             :  * </li>
    1153             :  * <li>RETRY_DELAY=val, where val is the number of seconds
    1154             :  * between retry attempts. Default is 30.
    1155             :  * Corresponding configuration option: GDAL_HTTP_RETRY_DELAY.
    1156             :  * <li>RETRY_CODES=val, where val is "ALL" or a comma-separated list of HTTP
    1157             :  * codes that are considered for retry. By default, 429, 500, 502, 503 or 504
    1158             :  * HTTP errors are considered, as well as other situations with a particular
    1159             :  * HTTP or Curl error message. (GDAL >= 3.10).
    1160             :  * Corresponding configuration option: GDAL_HTTP_RETRY_CODES.
    1161             :  * </li>
    1162             :  * <li>MAX_FILE_SIZE=val, where val is a number of bytes (GDAL >= 2.2)
    1163             :  * No corresponding configuration option.
    1164             :  * </li>
    1165             :  * <li>CAINFO=/path/to/bundle.crt. This is path to Certificate Authority (CA)
    1166             :  *     bundle file. By default, it will be looked for in a system location. If
    1167             :  *     the CAINFO option is not defined, GDAL will also look in the
    1168             :  *     CURL_CA_BUNDLE and SSL_CERT_FILE environment variables respectively
    1169             :  *     and use the first one found as the CAINFO value (GDAL >= 2.1.3). The
    1170             :  *     GDAL_CURL_CA_BUNDLE environment variable may also be used to set the
    1171             :  *     CAINFO value in GDAL >= 3.2.</li>
    1172             :  * <li>HTTP_VERSION=1.0/1.1/2/2TLS (GDAL >= 2.3)/2PRIOR_KNOWLEDGE (GDAL >= 3.10).
    1173             :  *     Specify HTTP version to use.
    1174             :  *     Will default to 1.1 generally (except on some controlled environments,
    1175             :  *     like Google Compute Engine VMs, where 2TLS will be the default).
    1176             :  *     Support for HTTP/2 requires curl 7.33 or later, built against nghttp2.
    1177             :  *     "2TLS" means that HTTP/2 will be attempted for HTTPS connections only.
    1178             :  *     Whereas "2" means that HTTP/2 will be attempted for HTTP or HTTPS.
    1179             :  *     "2PRIOR_KNOWLEDGE" means that the server will be assumed to support
    1180             :  *     HTTP/2.
    1181             :  *     Corresponding configuration option: GDAL_HTTP_VERSION.
    1182             :  * </li>
    1183             :  * <li>SSL_VERIFYSTATUS=YES/NO (GDAL >= 2.3, and curl >= 7.41): determines
    1184             :  * whether the status of the server cert using the "Certificate Status Request"
    1185             :  * TLS extension (aka. OCSP stapling) should be checked. If this option is
    1186             :  * enabled but the server does not support the TLS extension, the verification
    1187             :  * will fail. Default to NO.
    1188             :  * Corresponding configuration option: GDAL_HTTP_SSL_VERIFYSTATUS.
    1189             :  * </li>
    1190             :  * <li>USE_CAPI_STORE=YES/NO (GDAL >= 2.3,
    1191             :  * Windows only): whether CA certificates from the Windows certificate store.
    1192             :  * Defaults to NO.
    1193             :  * Corresponding configuration option: GDAL_HTTP_USE_CAPI_STORE.
    1194             :  * </li>
    1195             :  * <li>TCP_KEEPALIVE=YES/NO (GDAL >= 3.6): whether to
    1196             :  * enable TCP keep-alive. Defaults to NO.
    1197             :  * Corresponding configuration option: GDAL_HTTP_TCP_KEEPALIVE.
    1198             :  * </li>
    1199             :  * <li>TCP_KEEPIDLE=integer, in
    1200             :  * seconds (GDAL >= 3.6): keep-alive idle time. Defaults to 60. Only taken into
    1201             :  * account if TCP_KEEPALIVE=YES.
    1202             :  * Corresponding configuration option: GDAL_HTTP_TCP_KEEPIDLE.
    1203             :  * </li>
    1204             :  * <li>TCP_KEEPINTVL=integer, in seconds
    1205             :  * (GDAL >= 3.6): interval time between keep-alive probes. Defaults to 60. Only
    1206             :  * taken into account if TCP_KEEPALIVE=YES.
    1207             :  * Corresponding configuration option: GDAL_HTTP_TCP_KEEPINTVL.
    1208             :  * </li>
    1209             :  * <li>USERAGENT=string: value of User-Agent header. Starting with GDAL 3.7,
    1210             :  * GDAL core sets it by default (during driver initialization) to GDAL/x.y.z
    1211             :  * where x.y.z is the GDAL version number. Applications may override it with the
    1212             :  * CPLHTTPSetDefaultUserAgent() function.
    1213             :  * Corresponding configuration option: GDAL_HTTP_USERAGENT.
    1214             :  * </li>
    1215             :  * <li>SSLCERT=filename (GDAL >= 3.7): Filename of the the SSL client certificate.
    1216             :  * Cf https://curl.se/libcurl/c/CURLOPT_SSLCERT.html.
    1217             :  * Corresponding configuration option: GDAL_HTTP_SSLCERT.
    1218             :  * </li>
    1219             :  * <li>SSLCERTTYPE=string (GDAL >= 3.7): Format of the SSL certificate: "PEM"
    1220             :  * or "DER". Cf https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html.
    1221             :  * Corresponding configuration option: GDAL_HTTP_SSLCERTTYPE.
    1222             :  * </li>
    1223             :  * <li>SSLKEY=filename (GDAL >= 3.7): Private key file for TLS and SSL client
    1224             :  * certificate. Cf https://curl.se/libcurl/c/CURLOPT_SSLKEY.html.
    1225             :  * Corresponding configuration option: GDAL_HTTP_SSLKEY.
    1226             :  * </li>
    1227             :  * <li>KEYPASSWD=string (GDAL >= 3.7): Passphrase to private key.
    1228             :  * Cf https://curl.se/libcurl/c/CURLOPT_KEYPASSWD.html.
    1229             :  * Corresponding configuration option: GDAL_HTTP_KEYPASSWD.
    1230             :  * <li>PATH_VERBATIM=[YES/NO] Can be set to YES so that sequences of "/../" or "/./"
    1231             :  * that may exist in the URL's path part are kept as it. Otherwise, by default,
    1232             :  * they are squashed, according to RFC 3986 section 5.2.4
    1233             :  * Cf https://curl.se/libcurl/c/CURLOPT_PATH_AS_IS.html
    1234             :  * Corresponding configuration option: GDAL_HTTP_PATH_VERBATIM.
    1235             :  * </li>
    1236             :  * </ul>
    1237             :  *
    1238             :  * If an option is specified through papszOptions and as a configuration option,
    1239             :  * the former takes precedence over the later.
    1240             :  *
    1241             :  * Starting with GDAL 3.7, the above configuration options can also be specified
    1242             :  * as path-specific options with VSISetPathSpecificOption().
    1243             :  *
    1244             :  * @param pszURL valid URL recognized by underlying download library (libcurl)
    1245             :  * @param papszOptions option list as a NULL-terminated array of strings. May be
    1246             :  * NULL.
    1247             :  *
    1248             :  * @return a CPLHTTPResult* structure that must be freed by
    1249             :  * CPLHTTPDestroyResult(), or NULL if libcurl support is disabled
    1250             :  */
    1251             : // clang-format on
    1252        2131 : CPLHTTPResult *CPLHTTPFetch(const char *pszURL, CSLConstList papszOptions)
    1253             : {
    1254        2131 :     return CPLHTTPFetchEx(pszURL, papszOptions, nullptr, nullptr, nullptr,
    1255        2131 :                           nullptr);
    1256             : }
    1257             : 
    1258             : /**
    1259             :  * Fetch a document from an url and return in a string.
    1260             :  * @param  pszURL       Url to fetch document from web.
    1261             :  * @param  papszOptions Option list as a NULL-terminated array of strings.
    1262             :  * Available keys see in CPLHTTPFetch.
    1263             :  * @param  pfnProgress  Callback for reporting algorithm progress matching the
    1264             :  * GDALProgressFunc() semantics. May be NULL.
    1265             :  * @param  pProgressArg Callback argument passed to pfnProgress.
    1266             :  * @param  pfnWrite     Write function pointer matching the CPLHTTPWriteFunc()
    1267             :  * semantics. May be NULL.
    1268             :  * @param  pWriteArg    Argument which will pass to a write function.
    1269             :  * @return              A CPLHTTPResult* structure that must be freed by
    1270             :  * CPLHTTPDestroyResult(), or NULL if libcurl support is disabled.
    1271             :  */
    1272        2161 : CPLHTTPResult *CPLHTTPFetchEx(const char *pszURL, CSLConstList papszOptions,
    1273             :                               GDALProgressFunc pfnProgress, void *pProgressArg,
    1274             :                               CPLHTTPFetchWriteFunc pfnWrite, void *pWriteArg)
    1275             : 
    1276             : {
    1277        2982 :     if (STARTS_WITH(pszURL, "/vsimem/") &&
    1278             :         // Disabled by default for potential security issues.
    1279         821 :         CPLTestBool(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")))
    1280             :     {
    1281         796 :         CPLString osURL(pszURL);
    1282             :         const char *pszCustomRequest =
    1283         796 :             CSLFetchNameValue(papszOptions, "CUSTOMREQUEST");
    1284         796 :         if (pszCustomRequest != nullptr)
    1285             :         {
    1286           5 :             osURL += "&CUSTOMREQUEST=";
    1287           5 :             osURL += pszCustomRequest;
    1288             :         }
    1289         796 :         const char *pszUserPwd = CSLFetchNameValue(papszOptions, "USERPWD");
    1290         796 :         if (pszUserPwd != nullptr)
    1291             :         {
    1292           1 :             osURL += "&USERPWD=";
    1293           1 :             osURL += pszUserPwd;
    1294             :         }
    1295         796 :         const char *pszPost = CSLFetchNameValue(papszOptions, "POSTFIELDS");
    1296         796 :         if (pszPost != nullptr)  // Hack: We append post content to filename.
    1297             :         {
    1298         281 :             osURL += "&POSTFIELDS=";
    1299         281 :             osURL += pszPost;
    1300             :         }
    1301         796 :         const char *pszHeaders = CSLFetchNameValue(papszOptions, "HEADERS");
    1302         928 :         if (pszHeaders != nullptr &&
    1303         132 :             CPLTestBool(
    1304             :                 CPLGetConfigOption("CPL_CURL_VSIMEM_PRINT_HEADERS", "FALSE")))
    1305             :         {
    1306           5 :             osURL += "&HEADERS=";
    1307           5 :             osURL += pszHeaders;
    1308             :         }
    1309         796 :         vsi_l_offset nLength = 0;
    1310             :         CPLHTTPResult *psResult =
    1311         796 :             static_cast<CPLHTTPResult *>(CPLCalloc(1, sizeof(CPLHTTPResult)));
    1312         796 :         GByte *pabyData = VSIGetMemFileBuffer(osURL, &nLength, FALSE);
    1313         796 :         if (pabyData == nullptr)
    1314             :         {
    1315         132 :             CPLDebug("HTTP", "Cannot find %s", osURL.c_str());
    1316         132 :             psResult->nStatus = 1;
    1317         132 :             psResult->pszErrBuf =
    1318         132 :                 CPLStrdup(CPLSPrintf("HTTP error code : %d", 404));
    1319         132 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf);
    1320             :         }
    1321         664 :         else if (nLength != 0)
    1322             :         {
    1323         621 :             psResult->nDataLen = static_cast<int>(nLength);
    1324         621 :             psResult->pabyData = static_cast<GByte *>(
    1325         621 :                 CPLMalloc(static_cast<size_t>(nLength) + 1));
    1326         621 :             memcpy(psResult->pabyData, pabyData, static_cast<size_t>(nLength));
    1327         621 :             psResult->pabyData[static_cast<size_t>(nLength)] = 0;
    1328             :         }
    1329             : 
    1330         796 :         if (psResult->pabyData != nullptr &&
    1331         621 :             STARTS_WITH(reinterpret_cast<char *>(psResult->pabyData),
    1332             :                         "Content-Type: "))
    1333             :         {
    1334           7 :             const char *pszContentType =
    1335           7 :                 reinterpret_cast<char *>(psResult->pabyData) +
    1336             :                 strlen("Content-type: ");
    1337           7 :             const char *pszEOL = strchr(pszContentType, '\r');
    1338           7 :             if (pszEOL)
    1339           7 :                 pszEOL = strchr(pszContentType, '\n');
    1340           7 :             if (pszEOL)
    1341             :             {
    1342           7 :                 size_t nContentLength = pszEOL - pszContentType;
    1343           7 :                 psResult->pszContentType =
    1344           7 :                     static_cast<char *>(CPLMalloc(nContentLength + 1));
    1345           7 :                 memcpy(psResult->pszContentType, pszContentType,
    1346             :                        nContentLength);
    1347           7 :                 psResult->pszContentType[nContentLength] = 0;
    1348             :             }
    1349             :         }
    1350             : 
    1351         796 :         return psResult;
    1352             :     }
    1353             : 
    1354             :     // Try to user alternate network layer if set.
    1355        1365 :     auto pCtx = GetHTTPFetchContext(false);
    1356        1365 :     if (pCtx)
    1357             :     {
    1358           3 :         for (size_t i = pCtx->stack.size(); i > 0;)
    1359             :         {
    1360           1 :             --i;
    1361           1 :             const auto &cbk = pCtx->stack[i];
    1362           1 :             auto cbkFunc = cbk.first;
    1363           1 :             auto pUserData = cbk.second;
    1364           1 :             auto res = cbkFunc(pszURL, papszOptions, pfnProgress, pProgressArg,
    1365             :                                pfnWrite, pWriteArg, pUserData);
    1366           1 :             if (res)
    1367             :             {
    1368           1 :                 if (CSLFetchNameValue(papszOptions, "CLOSE_PERSISTENT"))
    1369             :                 {
    1370           0 :                     CPLHTTPDestroyResult(res);
    1371           0 :                     res = nullptr;
    1372             :                 }
    1373           1 :                 return res;
    1374             :             }
    1375             :         }
    1376             :     }
    1377             : 
    1378        1364 :     if (gpsHTTPFetchCallbackFunc)
    1379             :     {
    1380           1 :         auto res = gpsHTTPFetchCallbackFunc(pszURL, papszOptions, pfnProgress,
    1381             :                                             pProgressArg, pfnWrite, pWriteArg,
    1382             :                                             gpHTTPFetchCallbackUserData);
    1383           1 :         if (res)
    1384             :         {
    1385           1 :             if (CSLFetchNameValue(papszOptions, "CLOSE_PERSISTENT"))
    1386             :             {
    1387           0 :                 CPLHTTPDestroyResult(res);
    1388           0 :                 res = nullptr;
    1389             :             }
    1390           1 :             return res;
    1391             :         }
    1392             :     }
    1393             : 
    1394             : #ifndef HAVE_CURL
    1395             :     CPLError(CE_Failure, CPLE_NotSupported,
    1396             :              "GDAL/OGR not compiled with libcurl support, "
    1397             :              "remote requests not supported.");
    1398             :     return nullptr;
    1399             : #else
    1400             : 
    1401             :     /* -------------------------------------------------------------------- */
    1402             :     /*      Are we using a persistent named session?  If so, search for     */
    1403             :     /*      or create it.                                                   */
    1404             :     /*                                                                      */
    1405             :     /*      Currently this code does not attempt to protect against         */
    1406             :     /*      multiple threads asking for the same named session.  If that    */
    1407             :     /*      occurs it will be in use in multiple threads at once, which     */
    1408             :     /*      will lead to potential crashes in libcurl.                      */
    1409             :     /* -------------------------------------------------------------------- */
    1410        1363 :     CURL *http_handle = nullptr;
    1411             : 
    1412        1363 :     const char *pszPersistent = CSLFetchNameValue(papszOptions, "PERSISTENT");
    1413             :     const char *pszClosePersistent =
    1414        1363 :         CSLFetchNameValue(papszOptions, "CLOSE_PERSISTENT");
    1415        1363 :     if (pszPersistent)
    1416             :     {
    1417        1004 :         CPLString osSessionName = pszPersistent;
    1418         502 :         CPLMutexHolder oHolder(&hSessionMapMutex);
    1419             : 
    1420         502 :         if (poSessionMap == nullptr)
    1421         136 :             poSessionMap = new std::map<CPLString, CURL *>;
    1422         502 :         if (poSessionMap->count(osSessionName) == 0)
    1423             :         {
    1424         158 :             (*poSessionMap)[osSessionName] = curl_easy_init();
    1425         158 :             CPLDebug("HTTP", "Establish persistent session named '%s'.",
    1426             :                      osSessionName.c_str());
    1427             :         }
    1428             : 
    1429         502 :         http_handle = (*poSessionMap)[osSessionName];
    1430             :     }
    1431             :     /* -------------------------------------------------------------------- */
    1432             :     /*      Are we requested to close a persistent named session?           */
    1433             :     /* -------------------------------------------------------------------- */
    1434         861 :     else if (pszClosePersistent)
    1435             :     {
    1436         376 :         CPLString osSessionName = pszClosePersistent;
    1437         188 :         CPLMutexHolder oHolder(&hSessionMapMutex);
    1438             : 
    1439         188 :         if (poSessionMap)
    1440             :         {
    1441             :             std::map<CPLString, CURL *>::iterator oIter =
    1442         158 :                 poSessionMap->find(osSessionName);
    1443         158 :             if (oIter != poSessionMap->end())
    1444             :             {
    1445         158 :                 curl_easy_cleanup(oIter->second);
    1446         158 :                 poSessionMap->erase(oIter);
    1447         158 :                 if (poSessionMap->empty())
    1448             :                 {
    1449         136 :                     delete poSessionMap;
    1450         136 :                     poSessionMap = nullptr;
    1451             :                 }
    1452         158 :                 CPLDebug("HTTP", "Ended persistent session named '%s'.",
    1453             :                          osSessionName.c_str());
    1454             :             }
    1455             :             else
    1456             :             {
    1457           0 :                 CPLDebug("HTTP",
    1458             :                          "Could not find persistent session named '%s'.",
    1459             :                          osSessionName.c_str());
    1460             :             }
    1461             :         }
    1462             : 
    1463         188 :         return nullptr;
    1464             :     }
    1465             :     else
    1466         673 :         http_handle = curl_easy_init();
    1467             : 
    1468             :     /* -------------------------------------------------------------------- */
    1469             :     /*      Setup the request.                                              */
    1470             :     /* -------------------------------------------------------------------- */
    1471        1175 :     char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
    1472             : 
    1473        1175 :     CPLHTTPEmitFetchDebug(pszURL);
    1474             : 
    1475             :     CPLHTTPResult *psResult =
    1476        1175 :         static_cast<CPLHTTPResult *>(CPLCalloc(1, sizeof(CPLHTTPResult)));
    1477             : 
    1478             :     struct curl_slist *headers = reinterpret_cast<struct curl_slist *>(
    1479        1175 :         CPLHTTPSetOptions(http_handle, pszURL, papszOptions));
    1480        1175 :     if (headers != nullptr)
    1481         581 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER, headers);
    1482             : 
    1483             :     // Are we making a head request.
    1484        1175 :     const char *pszNoBody = nullptr;
    1485        1175 :     if ((pszNoBody = CSLFetchNameValue(papszOptions, "NO_BODY")) != nullptr)
    1486             :     {
    1487           0 :         if (CPLTestBool(pszNoBody))
    1488             :         {
    1489           0 :             CPLDebug("HTTP", "HEAD Request: %s", pszURL);
    1490           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_NOBODY, 1L);
    1491             :         }
    1492             :     }
    1493             : 
    1494             :     // Capture response headers.
    1495        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_HEADERDATA, psResult);
    1496        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_HEADERFUNCTION,
    1497             :                                CPLHdrWriteFct);
    1498             : 
    1499        1175 :     CPLHTTPResultWithLimit sResultWithLimit;
    1500        1175 :     if (nullptr == pfnWrite)
    1501             :     {
    1502        1174 :         pfnWrite = CPLWriteFct;
    1503             : 
    1504        1174 :         sResultWithLimit.psResult = psResult;
    1505        1174 :         sResultWithLimit.nMaxFileSize = 0;
    1506             :         const char *pszMaxFileSize =
    1507        1174 :             CSLFetchNameValue(papszOptions, "MAX_FILE_SIZE");
    1508        1174 :         if (pszMaxFileSize != nullptr)
    1509             :         {
    1510          10 :             sResultWithLimit.nMaxFileSize = atoi(pszMaxFileSize);
    1511             :             // Only useful if size is returned by server before actual download.
    1512          10 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXFILESIZE,
    1513             :                                        sResultWithLimit.nMaxFileSize);
    1514             :         }
    1515        1174 :         pWriteArg = &sResultWithLimit;
    1516             :     }
    1517             : 
    1518        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, pWriteArg);
    1519        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, pfnWrite);
    1520             : 
    1521        1175 :     CurlProcessData stProcessData = {pfnProgress, pProgressArg};
    1522        1175 :     if (nullptr != pfnProgress)
    1523             :     {
    1524          24 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_XFERINFOFUNCTION,
    1525             :                                    NewProcessFunction);
    1526          24 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_XFERINFODATA,
    1527             :                                    &stProcessData);
    1528          24 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_NOPROGRESS, 0L);
    1529             :     }
    1530             : 
    1531        1175 :     szCurlErrBuf[0] = '\0';
    1532             : 
    1533        1175 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
    1534             : 
    1535        1175 :     bool bGZipRequested = false;
    1536        1175 :     if (bSupportGZip && CPLTestBool(CPLGetConfigOption("CPL_CURL_GZIP", "YES")))
    1537             :     {
    1538        1175 :         bGZipRequested = true;
    1539        1175 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_ENCODING, "gzip");
    1540             :     }
    1541             : 
    1542        2350 :     CPLHTTPPostFields oPostFields;
    1543        1175 :     if (oPostFields.Fill(http_handle, papszOptions) != CE_None)
    1544             :     {
    1545           2 :         psResult->nStatus = 34;  // CURLE_HTTP_POST_ERROR
    1546           2 :         psResult->pszErrBuf = CPLStrdup(oPostFields.GetErrorMessage().c_str());
    1547           2 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf);
    1548           2 :         CPLHTTPFetchCleanup(http_handle, headers, pszPersistent, papszOptions);
    1549           2 :         return psResult;
    1550             :     }
    1551             : 
    1552             :     /* -------------------------------------------------------------------- */
    1553             :     /*      Depending on status code, retry this HTTP call until max        */
    1554             :     /*      retry has been reached                                          */
    1555             :     /* -------------------------------------------------------------------- */
    1556        1173 :     const char *pszRetryDelay = CSLFetchNameValue(papszOptions, "RETRY_DELAY");
    1557        1173 :     if (pszRetryDelay == nullptr)
    1558        1173 :         pszRetryDelay = CPLGetConfigOption(
    1559             :             "GDAL_HTTP_RETRY_DELAY", CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY));
    1560        1173 :     const char *pszMaxRetries = CSLFetchNameValue(papszOptions, "MAX_RETRY");
    1561        1173 :     if (pszMaxRetries == nullptr)
    1562        1109 :         pszMaxRetries = CPLGetConfigOption(
    1563             :             "GDAL_HTTP_MAX_RETRY", CPLSPrintf("%d", CPL_HTTP_MAX_RETRY));
    1564             :     // coverity[tainted_data]
    1565        1173 :     double dfRetryDelaySecs = CPLAtof(pszRetryDelay);
    1566        1173 :     int nMaxRetries = atoi(pszMaxRetries);
    1567        1173 :     const char *pszRetryCodes = CSLFetchNameValue(papszOptions, "RETRY_CODES");
    1568        1173 :     if (!pszRetryCodes)
    1569        1173 :         pszRetryCodes = CPLGetConfigOption("GDAL_HTTP_RETRY_CODES", nullptr);
    1570        1173 :     int nRetryCount = 0;
    1571             : 
    1572             :     while (true)
    1573             :     {
    1574             :         /* --------------------------------------------------------------------
    1575             :          */
    1576             :         /*      Execute the request, waiting for results. */
    1577             :         /* --------------------------------------------------------------------
    1578             :          */
    1579        1173 :         void *old_handler = CPLHTTPIgnoreSigPipe();
    1580        1173 :         psResult->nStatus = static_cast<int>(curl_easy_perform(http_handle));
    1581        1173 :         CPLHTTPRestoreSigPipeHandler(old_handler);
    1582             : 
    1583             :         /* --------------------------------------------------------------------
    1584             :          */
    1585             :         /*      Fetch content-type if possible. */
    1586             :         /* --------------------------------------------------------------------
    1587             :          */
    1588        1173 :         psResult->pszContentType = nullptr;
    1589        1173 :         curl_easy_getinfo(http_handle, CURLINFO_CONTENT_TYPE,
    1590             :                           &(psResult->pszContentType));
    1591        1173 :         if (psResult->pszContentType != nullptr)
    1592         532 :             psResult->pszContentType = CPLStrdup(psResult->pszContentType);
    1593             : 
    1594        1173 :         long response_code = 0;
    1595        1173 :         curl_easy_getinfo(http_handle, CURLINFO_RESPONSE_CODE, &response_code);
    1596        1173 :         if (response_code != 200)
    1597             :         {
    1598         420 :             const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
    1599             :                 static_cast<int>(response_code), dfRetryDelaySecs,
    1600         210 :                 reinterpret_cast<const char *>(psResult->pabyData),
    1601             :                 szCurlErrBuf, pszRetryCodes);
    1602         210 :             if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetries)
    1603             :             {
    1604           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1605             :                          "HTTP error code: %d - %s. "
    1606             :                          "Retrying again in %.1f secs",
    1607             :                          static_cast<int>(response_code), pszURL,
    1608             :                          dfRetryDelaySecs);
    1609           0 :                 CPLSleep(dfRetryDelaySecs);
    1610           0 :                 dfRetryDelaySecs = dfNewRetryDelay;
    1611           0 :                 nRetryCount++;
    1612             : 
    1613           0 :                 CPLFree(psResult->pszContentType);
    1614           0 :                 psResult->pszContentType = nullptr;
    1615           0 :                 CSLDestroy(psResult->papszHeaders);
    1616           0 :                 psResult->papszHeaders = nullptr;
    1617           0 :                 CPLFree(psResult->pabyData);
    1618           0 :                 psResult->pabyData = nullptr;
    1619           0 :                 psResult->nDataLen = 0;
    1620           0 :                 psResult->nDataAlloc = 0;
    1621             : 
    1622           0 :                 continue;
    1623             :             }
    1624             :         }
    1625             : 
    1626             :         /* --------------------------------------------------------------------
    1627             :          */
    1628             :         /*      Have we encountered some sort of error? */
    1629             :         /* --------------------------------------------------------------------
    1630             :          */
    1631        1173 :         if (strlen(szCurlErrBuf) > 0)
    1632             :         {
    1633           8 :             bool bSkipError = false;
    1634             :             const char *pszContentLength =
    1635           8 :                 CSLFetchNameValue(psResult->papszHeaders, "Content-Length");
    1636             :             // Some servers such as
    1637             :             // http://115.113.193.14/cgi-bin/world/qgis_mapserv.fcgi?VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities
    1638             :             // invalidly return Content-Length as the uncompressed size, with
    1639             :             // makes curl to wait for more data and time-out finally. If we got
    1640             :             // the expected data size, then we don't emit an error but turn off
    1641             :             // GZip requests.
    1642           8 :             if (bGZipRequested &&
    1643           8 :                 strstr(szCurlErrBuf, "transfer closed with") &&
    1644           0 :                 strstr(szCurlErrBuf, "bytes remaining to read"))
    1645             :             {
    1646           0 :                 if (pszContentLength && psResult->nDataLen != 0 &&
    1647           0 :                     atoi(pszContentLength) == psResult->nDataLen)
    1648             :                 {
    1649             :                     const char *pszCurlGZIPOption =
    1650           0 :                         CPLGetConfigOption("CPL_CURL_GZIP", nullptr);
    1651           0 :                     if (pszCurlGZIPOption == nullptr)
    1652             :                     {
    1653           0 :                         CPLSetConfigOption("CPL_CURL_GZIP", "NO");
    1654           0 :                         CPLDebug("HTTP",
    1655             :                                  "Disabling CPL_CURL_GZIP, "
    1656             :                                  "because %s doesn't support it properly",
    1657             :                                  pszURL);
    1658             :                     }
    1659           0 :                     psResult->nStatus = 0;
    1660           0 :                     bSkipError = true;
    1661           0 :                 }
    1662             :             }
    1663             : 
    1664             :             // Ignore SSL errors about non-properly terminated connection,
    1665             :             // often due to HTTP proxies
    1666           8 :             else if (pszContentLength == nullptr &&
    1667             :                      // Cf https://github.com/curl/curl/pull/3148
    1668           8 :                      (strstr(szCurlErrBuf,
    1669             :                              "GnuTLS recv error (-110): The TLS connection was "
    1670           8 :                              "non-properly terminated") != nullptr ||
    1671             :                       // Cf https://github.com/curl/curl/issues/9024
    1672           8 :                       strstr(szCurlErrBuf,
    1673             :                              "SSL_read: error:0A000126:SSL "
    1674             :                              "routines::unexpected eof while reading") !=
    1675             :                           nullptr))
    1676             :             {
    1677           0 :                 psResult->nStatus = 0;
    1678           0 :                 bSkipError = true;
    1679             :             }
    1680           8 :             else if (CPLTestBool(
    1681             :                          CPLGetConfigOption("CPL_CURL_IGNORE_ERROR", "NO")))
    1682             :             {
    1683           0 :                 psResult->nStatus = 0;
    1684           0 :                 bSkipError = true;
    1685             :             }
    1686             : 
    1687           8 :             if (!bSkipError)
    1688             :             {
    1689           8 :                 psResult->pszErrBuf = CPLStrdup(szCurlErrBuf);
    1690           8 :                 if (psResult->nDataLen > 0)
    1691             :                 {
    1692           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1693             :                              "%s. You may set the CPL_CURL_IGNORE_ERROR "
    1694             :                              "configuration option to YES to try to ignore it.",
    1695             :                              szCurlErrBuf);
    1696             :                 }
    1697             :                 else
    1698             :                 {
    1699           7 :                     CPLError(CE_Failure, CPLE_AppDefined, "%s", szCurlErrBuf);
    1700             :                 }
    1701             :             }
    1702             :         }
    1703             :         else
    1704             :         {
    1705        1165 :             if (response_code >= 400 && response_code < 600)
    1706             :             {
    1707         191 :                 psResult->pszErrBuf = CPLStrdup(CPLSPrintf(
    1708             :                     "HTTP error code : %d", static_cast<int>(response_code)));
    1709         191 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s",
    1710             :                          psResult->pszErrBuf);
    1711             :             }
    1712             :         }
    1713        1173 :         break;
    1714           0 :     }
    1715             : 
    1716        1173 :     CPLHTTPFetchCleanup(http_handle, headers, pszPersistent, papszOptions);
    1717             : 
    1718        1173 :     return psResult;
    1719             : #endif /* def HAVE_CURL */
    1720             : }
    1721             : 
    1722             : #ifdef HAVE_CURL
    1723             : /************************************************************************/
    1724             : /*                       CPLMultiPerformWait()                          */
    1725             : /************************************************************************/
    1726             : 
    1727        3637 : bool CPLMultiPerformWait(void *hCurlMultiHandleIn, int & /*repeats*/)
    1728             : {
    1729        3637 :     CURLM *hCurlMultiHandle = static_cast<CURLM *>(hCurlMultiHandleIn);
    1730             : 
    1731             :     // Wait for events on the sockets
    1732             : 
    1733             :     // Using curl_multi_poll() is preferred to avoid hitting the 1024 file
    1734             :     // descriptor limit
    1735             : 
    1736        3637 :     int numfds = 0;
    1737        3637 :     if (curl_multi_poll(hCurlMultiHandle, nullptr, 0, 1000, &numfds) !=
    1738             :         CURLM_OK)
    1739             :     {
    1740           0 :         CPLError(CE_Failure, CPLE_AppDefined, "curl_multi_poll() failed");
    1741           0 :         return false;
    1742             :     }
    1743        3638 :     return true;
    1744             : }
    1745             : 
    1746             : class CPLHTTPErrorBuffer
    1747             : {
    1748             :   public:
    1749             :     char szBuffer[CURL_ERROR_SIZE + 1];
    1750             : 
    1751           0 :     CPLHTTPErrorBuffer()
    1752           0 :     {
    1753           0 :         szBuffer[0] = '\0';
    1754           0 :     }
    1755             : };
    1756             : 
    1757             : #endif  // HAVE_CURL
    1758             : 
    1759             : /************************************************************************/
    1760             : /*                           CPLHTTPMultiFetch()                        */
    1761             : /************************************************************************/
    1762             : 
    1763             : /**
    1764             :  * \brief Fetch several documents at once
    1765             :  *
    1766             :  * @param papszURL array of valid URLs recognized by underlying download library
    1767             :  * (libcurl)
    1768             :  * @param nURLCount number of URLs of papszURL
    1769             :  * @param nMaxSimultaneous maximum number of downloads to issue simultaneously.
    1770             :  *                         Any negative or zer value means unlimited.
    1771             :  * @param papszOptions option list as a NULL-terminated array of strings. May be
    1772             :  * NULL. Refer to CPLHTTPFetch() for valid options.
    1773             :  * @return an array of CPLHTTPResult* structures that must be freed by
    1774             :  * CPLHTTPDestroyMultiResult() or NULL if libcurl support is disabled
    1775             :  *
    1776             :  * @since GDAL 2.3
    1777             :  */
    1778           0 : CPLHTTPResult **CPLHTTPMultiFetch(const char *const *papszURL, int nURLCount,
    1779             :                                   int nMaxSimultaneous,
    1780             :                                   CSLConstList papszOptions)
    1781             : {
    1782             : #ifndef HAVE_CURL
    1783             :     (void)papszURL;
    1784             :     (void)nURLCount;
    1785             :     (void)nMaxSimultaneous;
    1786             :     (void)papszOptions;
    1787             : 
    1788             :     CPLError(CE_Failure, CPLE_NotSupported,
    1789             :              "GDAL/OGR not compiled with libcurl support, "
    1790             :              "remote requests not supported.");
    1791             :     return nullptr;
    1792             : #else  /* def HAVE_CURL */
    1793             : 
    1794             :     /* -------------------------------------------------------------------- */
    1795             :     /*      Are we using a persistent named session?  If so, search for     */
    1796             :     /*      or create it.                                                   */
    1797             :     /*                                                                      */
    1798             :     /*      Currently this code does not attempt to protect against         */
    1799             :     /*      multiple threads asking for the same named session.  If that    */
    1800             :     /*      occurs it will be in use in multiple threads at once, which     */
    1801             :     /*      will lead to potential crashes in libcurl.                      */
    1802             :     /* -------------------------------------------------------------------- */
    1803           0 :     CURLM *hCurlMultiHandle = nullptr;
    1804             : 
    1805           0 :     const char *pszPersistent = CSLFetchNameValue(papszOptions, "PERSISTENT");
    1806             :     const char *pszClosePersistent =
    1807           0 :         CSLFetchNameValue(papszOptions, "CLOSE_PERSISTENT");
    1808           0 :     if (pszPersistent)
    1809             :     {
    1810           0 :         CPLString osSessionName = pszPersistent;
    1811           0 :         CPLMutexHolder oHolder(&hSessionMapMutex);
    1812             : 
    1813           0 :         if (poSessionMultiMap == nullptr)
    1814           0 :             poSessionMultiMap = new std::map<CPLString, CURLM *>;
    1815           0 :         if (poSessionMultiMap->count(osSessionName) == 0)
    1816             :         {
    1817           0 :             (*poSessionMultiMap)[osSessionName] = curl_multi_init();
    1818           0 :             CPLDebug("HTTP", "Establish persistent session named '%s'.",
    1819             :                      osSessionName.c_str());
    1820             :         }
    1821             : 
    1822           0 :         hCurlMultiHandle = (*poSessionMultiMap)[osSessionName];
    1823             :     }
    1824             :     /* -------------------------------------------------------------------- */
    1825             :     /*      Are we requested to close a persistent named session?           */
    1826             :     /* -------------------------------------------------------------------- */
    1827           0 :     else if (pszClosePersistent)
    1828             :     {
    1829           0 :         CPLString osSessionName = pszClosePersistent;
    1830           0 :         CPLMutexHolder oHolder(&hSessionMapMutex);
    1831             : 
    1832           0 :         if (poSessionMultiMap)
    1833             :         {
    1834           0 :             auto oIter = poSessionMultiMap->find(osSessionName);
    1835           0 :             if (oIter != poSessionMultiMap->end())
    1836             :             {
    1837           0 :                 VSICURLMultiCleanup(oIter->second);
    1838           0 :                 poSessionMultiMap->erase(oIter);
    1839           0 :                 if (poSessionMultiMap->empty())
    1840             :                 {
    1841           0 :                     delete poSessionMultiMap;
    1842           0 :                     poSessionMultiMap = nullptr;
    1843             :                 }
    1844           0 :                 CPLDebug("HTTP", "Ended persistent session named '%s'.",
    1845             :                          osSessionName.c_str());
    1846             :             }
    1847             :             else
    1848             :             {
    1849           0 :                 CPLDebug("HTTP",
    1850             :                          "Could not find persistent session named '%s'.",
    1851             :                          osSessionName.c_str());
    1852             :             }
    1853             :         }
    1854             : 
    1855           0 :         return nullptr;
    1856             :     }
    1857             :     else
    1858             :     {
    1859           0 :         hCurlMultiHandle = curl_multi_init();
    1860             :     }
    1861             : 
    1862             :     CPLHTTPResult **papsResults = static_cast<CPLHTTPResult **>(
    1863           0 :         CPLCalloc(nURLCount, sizeof(CPLHTTPResult *)));
    1864           0 :     std::vector<CURL *> asHandles;
    1865           0 :     std::vector<CPLHTTPResultWithLimit> asResults;
    1866           0 :     asResults.resize(nURLCount);
    1867           0 :     std::vector<struct curl_slist *> aHeaders;
    1868           0 :     aHeaders.resize(nURLCount);
    1869           0 :     std::vector<CPLHTTPErrorBuffer> asErrorBuffers;
    1870           0 :     asErrorBuffers.resize(nURLCount);
    1871             : 
    1872           0 :     for (int i = 0; i < nURLCount; i++)
    1873             :     {
    1874           0 :         papsResults[i] =
    1875           0 :             static_cast<CPLHTTPResult *>(CPLCalloc(1, sizeof(CPLHTTPResult)));
    1876             : 
    1877           0 :         const char *pszURL = papszURL[i];
    1878           0 :         CURL *http_handle = curl_easy_init();
    1879             : 
    1880           0 :         aHeaders[i] = reinterpret_cast<struct curl_slist *>(
    1881           0 :             CPLHTTPSetOptions(http_handle, pszURL, papszOptions));
    1882             : 
    1883             :         // Set Headers.
    1884           0 :         const char *pszHeaders = CSLFetchNameValue(papszOptions, "HEADERS");
    1885           0 :         if (pszHeaders != nullptr)
    1886             :         {
    1887             :             char **papszTokensHeaders =
    1888           0 :                 CSLTokenizeString2(pszHeaders, "\r\n", 0);
    1889           0 :             for (int j = 0; papszTokensHeaders[j] != nullptr; ++j)
    1890           0 :                 aHeaders[i] =
    1891           0 :                     curl_slist_append(aHeaders[i], papszTokensHeaders[j]);
    1892           0 :             CSLDestroy(papszTokensHeaders);
    1893             :         }
    1894             : 
    1895           0 :         if (aHeaders[i] != nullptr)
    1896           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER,
    1897             :                                        aHeaders[i]);
    1898             : 
    1899             :         // Capture response headers.
    1900           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HEADERDATA,
    1901             :                                    papsResults[i]);
    1902           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HEADERFUNCTION,
    1903             :                                    CPLHdrWriteFct);
    1904             : 
    1905           0 :         asResults[i].psResult = papsResults[i];
    1906             :         const char *pszMaxFileSize =
    1907           0 :             CSLFetchNameValue(papszOptions, "MAX_FILE_SIZE");
    1908           0 :         if (pszMaxFileSize != nullptr)
    1909             :         {
    1910           0 :             asResults[i].nMaxFileSize = atoi(pszMaxFileSize);
    1911             :             // Only useful if size is returned by server before actual download.
    1912           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXFILESIZE,
    1913             :                                        asResults[i].nMaxFileSize);
    1914             :         }
    1915             : 
    1916           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEDATA,
    1917             :                                    &asResults[i]);
    1918           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION,
    1919             :                                    CPLWriteFct);
    1920             : 
    1921           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_ERRORBUFFER,
    1922             :                                    asErrorBuffers[i].szBuffer);
    1923             : 
    1924           0 :         if (bSupportGZip &&
    1925           0 :             CPLTestBool(CPLGetConfigOption("CPL_CURL_GZIP", "YES")))
    1926             :         {
    1927           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_ENCODING, "gzip");
    1928             :         }
    1929             : 
    1930           0 :         asHandles.push_back(http_handle);
    1931             :     }
    1932             : 
    1933           0 :     int iCurRequest = 0;
    1934           0 :     for (;
    1935           0 :          iCurRequest <
    1936           0 :          std::min(nURLCount, nMaxSimultaneous > 0 ? nMaxSimultaneous : INT_MAX);
    1937             :          iCurRequest++)
    1938             :     {
    1939           0 :         CPLHTTPEmitFetchDebug(papszURL[iCurRequest],
    1940             :                               CPLSPrintf(" %d/%d", iCurRequest + 1, nURLCount));
    1941           0 :         curl_multi_add_handle(hCurlMultiHandle, asHandles[iCurRequest]);
    1942             :     }
    1943             : 
    1944           0 :     int repeats = 0;
    1945           0 :     void *old_handler = CPLHTTPIgnoreSigPipe();
    1946             :     while (true)
    1947             :     {
    1948           0 :         int still_running = 0;
    1949           0 :         while (curl_multi_perform(hCurlMultiHandle, &still_running) ==
    1950             :                CURLM_CALL_MULTI_PERFORM)
    1951             :         {
    1952             :             // loop
    1953             :         }
    1954           0 :         if (!still_running && iCurRequest == nURLCount)
    1955             :         {
    1956           0 :             break;
    1957             :         }
    1958             : 
    1959           0 :         bool bRequestsAdded = false;
    1960             :         CURLMsg *msg;
    1961           0 :         do
    1962             :         {
    1963           0 :             int msgq = 0;
    1964           0 :             msg = curl_multi_info_read(hCurlMultiHandle, &msgq);
    1965           0 :             if (msg && (msg->msg == CURLMSG_DONE))
    1966             :             {
    1967           0 :                 if (iCurRequest < nURLCount)
    1968             :                 {
    1969           0 :                     CPLHTTPEmitFetchDebug(
    1970           0 :                         papszURL[iCurRequest],
    1971             :                         CPLSPrintf(" %d/%d", iCurRequest + 1, nURLCount));
    1972           0 :                     curl_multi_add_handle(hCurlMultiHandle,
    1973           0 :                                           asHandles[iCurRequest]);
    1974           0 :                     iCurRequest++;
    1975           0 :                     bRequestsAdded = true;
    1976             :                 }
    1977             :             }
    1978           0 :         } while (msg);
    1979             : 
    1980           0 :         if (!bRequestsAdded)
    1981           0 :             CPLMultiPerformWait(hCurlMultiHandle, repeats);
    1982           0 :     }
    1983           0 :     CPLHTTPRestoreSigPipeHandler(old_handler);
    1984             : 
    1985           0 :     for (int i = 0; i < nURLCount; i++)
    1986             :     {
    1987           0 :         if (asErrorBuffers[i].szBuffer[0] != '\0')
    1988           0 :             papsResults[i]->pszErrBuf = CPLStrdup(asErrorBuffers[i].szBuffer);
    1989             :         else
    1990             :         {
    1991           0 :             long response_code = 0;
    1992           0 :             curl_easy_getinfo(asHandles[i], CURLINFO_RESPONSE_CODE,
    1993             :                               &response_code);
    1994             : 
    1995           0 :             if (response_code >= 400 && response_code < 600)
    1996             :             {
    1997           0 :                 papsResults[i]->pszErrBuf = CPLStrdup(CPLSPrintf(
    1998             :                     "HTTP error code : %d", static_cast<int>(response_code)));
    1999             :             }
    2000             :         }
    2001             : 
    2002           0 :         curl_easy_getinfo(asHandles[i], CURLINFO_CONTENT_TYPE,
    2003             :                           &(papsResults[i]->pszContentType));
    2004           0 :         if (papsResults[i]->pszContentType != nullptr)
    2005           0 :             papsResults[i]->pszContentType =
    2006           0 :                 CPLStrdup(papsResults[i]->pszContentType);
    2007             : 
    2008           0 :         curl_multi_remove_handle(hCurlMultiHandle, asHandles[i]);
    2009           0 :         curl_easy_cleanup(asHandles[i]);
    2010             :     }
    2011             : 
    2012           0 :     if (!pszPersistent)
    2013           0 :         VSICURLMultiCleanup(hCurlMultiHandle);
    2014             : 
    2015           0 :     for (size_t i = 0; i < aHeaders.size(); i++)
    2016           0 :         curl_slist_free_all(aHeaders[i]);
    2017             : 
    2018           0 :     return papsResults;
    2019             : #endif /* def HAVE_CURL */
    2020             : }
    2021             : 
    2022             : /************************************************************************/
    2023             : /*                      CPLHTTPDestroyMultiResult()                     */
    2024             : /************************************************************************/
    2025             : /**
    2026             :  * \brief Clean the memory associated with the return value of
    2027             :  * CPLHTTPMultiFetch()
    2028             :  *
    2029             :  * @param papsResults pointer to the return value of CPLHTTPMultiFetch()
    2030             :  * @param nCount value of the nURLCount parameter passed to CPLHTTPMultiFetch()
    2031             :  * @since GDAL 2.3
    2032             :  */
    2033           0 : void CPLHTTPDestroyMultiResult(CPLHTTPResult **papsResults, int nCount)
    2034             : {
    2035           0 :     if (papsResults)
    2036             :     {
    2037           0 :         for (int i = 0; i < nCount; i++)
    2038             :         {
    2039           0 :             CPLHTTPDestroyResult(papsResults[i]);
    2040             :         }
    2041           0 :         CPLFree(papsResults);
    2042             :     }
    2043           0 : }
    2044             : 
    2045             : #ifdef HAVE_CURL
    2046             : 
    2047             : #ifdef _WIN32
    2048             : 
    2049             : #include <windows.h>
    2050             : 
    2051             : /************************************************************************/
    2052             : /*                     CPLFindWin32CurlCaBundleCrt()                    */
    2053             : /************************************************************************/
    2054             : 
    2055             : static const char *CPLFindWin32CurlCaBundleCrt()
    2056             : {
    2057             :     DWORD nResLen;
    2058             :     const DWORD nBufSize = MAX_PATH + 1;
    2059             :     char *pszFilePart = nullptr;
    2060             : 
    2061             :     char *pszPath = static_cast<char *>(CPLCalloc(1, nBufSize));
    2062             : 
    2063             :     nResLen = SearchPathA(nullptr, "curl-ca-bundle.crt", nullptr, nBufSize,
    2064             :                           pszPath, &pszFilePart);
    2065             :     if (nResLen > 0)
    2066             :     {
    2067             :         const char *pszRet = CPLSPrintf("%s", pszPath);
    2068             :         CPLFree(pszPath);
    2069             :         return pszRet;
    2070             :     }
    2071             :     CPLFree(pszPath);
    2072             :     return nullptr;
    2073             : }
    2074             : 
    2075             : #endif  // WIN32
    2076             : 
    2077             : /************************************************************************/
    2078             : /*                     CPLHTTPCurlDebugFunction()                       */
    2079             : /************************************************************************/
    2080             : 
    2081          16 : static int CPLHTTPCurlDebugFunction(CURL *handle, curl_infotype type,
    2082             :                                     char *data, size_t size, void *userp)
    2083             : {
    2084             :     (void)handle;
    2085             :     (void)userp;
    2086             : 
    2087          16 :     const char *pszDebugKey = nullptr;
    2088          16 :     if (type == CURLINFO_TEXT)
    2089             :     {
    2090          10 :         pszDebugKey = "CURL_INFO_TEXT";
    2091             :     }
    2092           6 :     else if (type == CURLINFO_HEADER_OUT)
    2093             :     {
    2094           1 :         pszDebugKey = "CURL_INFO_HEADER_OUT";
    2095             :     }
    2096           5 :     else if (type == CURLINFO_HEADER_IN)
    2097             :     {
    2098           5 :         pszDebugKey = "CURL_INFO_HEADER_IN";
    2099             :     }
    2100           0 :     else if (type == CURLINFO_DATA_IN &&
    2101           0 :              CPLTestBool(CPLGetConfigOption("CPL_CURL_VERBOSE_DATA_IN", "NO")))
    2102             :     {
    2103           0 :         pszDebugKey = "CURL_INFO_DATA_IN";
    2104             :     }
    2105             : 
    2106          16 :     if (pszDebugKey)
    2107             :     {
    2108          32 :         std::string osMsg(data, size);
    2109          16 :         if (!osMsg.empty() && osMsg.back() == '\n')
    2110          16 :             osMsg.pop_back();
    2111          16 :         CPLDebug(pszDebugKey, "%s", osMsg.c_str());
    2112             :     }
    2113          16 :     return 0;
    2114             : }
    2115             : 
    2116             : /************************************************************************/
    2117             : /*                         CPLHTTPSetOptions()                          */
    2118             : /************************************************************************/
    2119             : 
    2120             : // Note: papszOptions must be kept alive until curl_easy/multi_perform()
    2121             : // has completed, and we must be careful not to set short lived strings
    2122             : // with unchecked_curl_easy_setopt(), as long as we need to support curl < 7.17
    2123             : // see https://curl.haxx.se/libcurl/c/unchecked_curl_easy_setopt.html
    2124             : // caution: if we remove that assumption, we'll needto use
    2125             : // CURLOPT_COPYPOSTFIELDS
    2126             : 
    2127        2602 : void *CPLHTTPSetOptions(void *pcurl, const char *pszURL,
    2128             :                         const char *const *papszOptions)
    2129             : {
    2130        2602 :     CheckCurlFeatures();
    2131             : 
    2132        2602 :     CURL *http_handle = reinterpret_cast<CURL *>(pcurl);
    2133             : 
    2134        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_URL, pszURL);
    2135             : 
    2136        2602 :     if (CPLTestBool(CPLGetConfigOption("CPL_CURL_VERBOSE", "NO")))
    2137             :     {
    2138           1 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_VERBOSE, 1);
    2139             : 
    2140           1 :         if (CPLIsDebugEnabled())
    2141             :         {
    2142           1 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_DEBUGFUNCTION,
    2143             :                                        CPLHTTPCurlDebugFunction);
    2144             :         }
    2145             :     }
    2146             : 
    2147        2602 :     const char *pszPathAsIt = CSLFetchNameValue(papszOptions, "PATH_VERBATIM");
    2148        2602 :     if (pszPathAsIt == nullptr)
    2149        2601 :         pszPathAsIt = CPLGetConfigOption("GDAL_HTTP_PATH_VERBATIM", nullptr);
    2150        2602 :     if (pszPathAsIt && CPLTestBool(pszPathAsIt))
    2151             :     {
    2152           1 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PATH_AS_IS, 1L);
    2153             :     }
    2154             : 
    2155             :     const char *pszHttpVersion =
    2156        2602 :         CSLFetchNameValue(papszOptions, "HTTP_VERSION");
    2157        2602 :     if (pszHttpVersion == nullptr)
    2158        2602 :         pszHttpVersion = CPLGetConfigOption("GDAL_HTTP_VERSION", nullptr);
    2159        2602 :     if (pszHttpVersion && strcmp(pszHttpVersion, "1.0") == 0)
    2160           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
    2161             :                                    CURL_HTTP_VERSION_1_0);
    2162        2602 :     else if (pszHttpVersion && strcmp(pszHttpVersion, "1.1") == 0)
    2163           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
    2164             :                                    CURL_HTTP_VERSION_1_1);
    2165        2602 :     else if (pszHttpVersion && (strcmp(pszHttpVersion, "2") == 0 ||
    2166           0 :                                 strcmp(pszHttpVersion, "2.0") == 0))
    2167             :     {
    2168           0 :         if (bSupportHTTP2)
    2169             :         {
    2170             :             // Try HTTP/2 both for HTTP and HTTPS. With fallback to HTTP/1.1
    2171           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
    2172             :                                        CURL_HTTP_VERSION_2_0);
    2173             :         }
    2174             :     }
    2175        2602 :     else if (pszHttpVersion && strcmp(pszHttpVersion, "2PRIOR_KNOWLEDGE") == 0)
    2176             :     {
    2177           0 :         if (bSupportHTTP2)
    2178             :         {
    2179             :             // Assume HTTP/2 is supported by the server. The cURL docs indicate
    2180             :             // that it makes no difference for HTTPS, but it does seem to work
    2181             :             // in practice.
    2182           0 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
    2183             :                                        CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
    2184             :         }
    2185             :     }
    2186        2602 :     else if (pszHttpVersion == nullptr || strcmp(pszHttpVersion, "2TLS") == 0)
    2187             :     {
    2188        2602 :         if (bSupportHTTP2)
    2189             :         {
    2190             :             // Only enable this mode if explicitly required, or if the
    2191             :             // machine is a GCE instance. On other networks, requesting a
    2192             :             // file in HTTP/2 is found to be significantly slower than HTTP/1.1
    2193             :             // for unknown reasons.
    2194        2602 :             if (pszHttpVersion != nullptr || CPLIsMachineForSureGCEInstance())
    2195             :             {
    2196             :                 static bool bDebugEmitted = false;
    2197           0 :                 if (!bDebugEmitted)
    2198             :                 {
    2199           0 :                     CPLDebug("HTTP", "Using HTTP/2 for HTTPS when possible");
    2200           0 :                     bDebugEmitted = true;
    2201             :                 }
    2202             : 
    2203             :                 // CURL_HTTP_VERSION_2TLS means for HTTPS connection, try to
    2204             :                 // negotiate HTTP/2 with the server (and fallback to HTTP/1.1
    2205             :                 // otherwise), and for HTTP connection do HTTP/1
    2206           0 :                 unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION,
    2207             :                                            CURL_HTTP_VERSION_2TLS);
    2208             :             }
    2209        2602 :         }
    2210             :     }
    2211             :     else
    2212             :     {
    2213           0 :         CPLError(CE_Warning, CPLE_NotSupported, "HTTP_VERSION=%s not supported",
    2214             :                  pszHttpVersion);
    2215             :     }
    2216             : 
    2217             :     // Default value is 1 since curl 7.50.2. But worth applying it on
    2218             :     // previous versions as well.
    2219             :     const char *pszTCPNoDelay =
    2220        2602 :         CSLFetchNameValueDef(papszOptions, "TCP_NODELAY", "1");
    2221        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_TCP_NODELAY,
    2222             :                                atoi(pszTCPNoDelay));
    2223             : 
    2224             :     /* Support control over HTTPAUTH */
    2225        2602 :     const char *pszHttpAuth = CSLFetchNameValue(papszOptions, "HTTPAUTH");
    2226        2602 :     if (pszHttpAuth == nullptr)
    2227        2592 :         pszHttpAuth = CPLGetConfigOption("GDAL_HTTP_AUTH", nullptr);
    2228        2602 :     if (pszHttpAuth == nullptr)
    2229             :     {
    2230             :         /* do nothing */;
    2231             :     }
    2232          15 :     else if (EQUAL(pszHttpAuth, "BASIC"))
    2233           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
    2234             :                                    CURLAUTH_BASIC);
    2235          15 :     else if (EQUAL(pszHttpAuth, "NTLM"))
    2236           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
    2237             :                                    CURLAUTH_NTLM);
    2238          15 :     else if (EQUAL(pszHttpAuth, "ANY"))
    2239           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    2240          15 :     else if (EQUAL(pszHttpAuth, "ANYSAFE"))
    2241           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
    2242             :                                    CURLAUTH_ANYSAFE);
    2243          15 :     else if (EQUAL(pszHttpAuth, "BEARER"))
    2244             :     {
    2245          15 :         const char *pszAuthorizationHeaderAllowed = CSLFetchNameValueDef(
    2246             :             papszOptions, "AUTHORIZATION_HEADER_ALLOWED", "YES");
    2247             :         const bool bAuthorizationHeaderAllowed =
    2248          15 :             CPLTestBool(pszAuthorizationHeaderAllowed);
    2249          15 :         if (bAuthorizationHeaderAllowed)
    2250             :         {
    2251             :             const char *pszBearer =
    2252          12 :                 CSLFetchNameValue(papszOptions, "HTTP_BEARER");
    2253          12 :             if (pszBearer == nullptr)
    2254           5 :                 pszBearer = CPLGetConfigOption("GDAL_HTTP_BEARER", nullptr);
    2255          12 :             if (pszBearer != nullptr)
    2256          12 :                 unchecked_curl_easy_setopt(http_handle, CURLOPT_XOAUTH2_BEARER,
    2257             :                                            pszBearer);
    2258          12 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
    2259             :                                        CURLAUTH_BEARER);
    2260             :         }
    2261             :     }
    2262           0 :     else if (EQUAL(pszHttpAuth, "NEGOTIATE"))
    2263           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH,
    2264             :                                    CURLAUTH_NEGOTIATE);
    2265             :     else
    2266             :     {
    2267           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2268             :                  "Unsupported HTTPAUTH value '%s', ignored.", pszHttpAuth);
    2269             :     }
    2270             : 
    2271             :     const char *pszGssDelegation =
    2272        2602 :         CSLFetchNameValue(papszOptions, "GSSAPI_DELEGATION");
    2273        2602 :     if (pszGssDelegation == nullptr)
    2274             :         pszGssDelegation =
    2275        2602 :             CPLGetConfigOption("GDAL_GSSAPI_DELEGATION", nullptr);
    2276        2602 :     if (pszGssDelegation == nullptr)
    2277             :     {
    2278             :     }
    2279           0 :     else if (EQUAL(pszGssDelegation, "NONE"))
    2280             :     {
    2281           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_GSSAPI_DELEGATION,
    2282             :                                    CURLGSSAPI_DELEGATION_NONE);
    2283             :     }
    2284           0 :     else if (EQUAL(pszGssDelegation, "POLICY"))
    2285             :     {
    2286           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_GSSAPI_DELEGATION,
    2287             :                                    CURLGSSAPI_DELEGATION_POLICY_FLAG);
    2288             :     }
    2289           0 :     else if (EQUAL(pszGssDelegation, "ALWAYS"))
    2290             :     {
    2291           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_GSSAPI_DELEGATION,
    2292             :                                    CURLGSSAPI_DELEGATION_FLAG);
    2293             :     }
    2294             :     else
    2295             :     {
    2296           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2297             :                  "Unsupported GSSAPI_DELEGATION value '%s', ignored.",
    2298             :                  pszGssDelegation);
    2299             :     }
    2300             : 
    2301             :     // Support use of .netrc - default enabled.
    2302        2602 :     const char *pszHttpNetrc = CSLFetchNameValue(papszOptions, "NETRC");
    2303        2602 :     if (pszHttpNetrc == nullptr)
    2304        2602 :         pszHttpNetrc = CPLGetConfigOption("GDAL_HTTP_NETRC", "YES");
    2305        2602 :     if (pszHttpNetrc == nullptr || CPLTestBool(pszHttpNetrc))
    2306        2602 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_NETRC, 1L);
    2307             : 
    2308             :     // Custom .netrc file location
    2309             :     const char *pszHttpNetrcFile =
    2310        2602 :         CSLFetchNameValue(papszOptions, "NETRC_FILE");
    2311        2602 :     if (pszHttpNetrcFile == nullptr)
    2312        2602 :         pszHttpNetrcFile = CPLGetConfigOption("GDAL_HTTP_NETRC_FILE", nullptr);
    2313        2602 :     if (pszHttpNetrcFile)
    2314           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_NETRC_FILE,
    2315             :                                    pszHttpNetrcFile);
    2316             : 
    2317             :     // Support setting userid:password.
    2318        2602 :     const char *pszUserPwd = CSLFetchNameValue(papszOptions, "USERPWD");
    2319        2602 :     if (pszUserPwd == nullptr)
    2320        2564 :         pszUserPwd = CPLGetConfigOption("GDAL_HTTP_USERPWD", nullptr);
    2321        2602 :     if (pszUserPwd != nullptr)
    2322          38 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_USERPWD, pszUserPwd);
    2323             : 
    2324             :     // Set Proxy parameters.
    2325        2602 :     const char *pszProxy = CSLFetchNameValue(papszOptions, "PROXY");
    2326        2602 :     if (pszProxy == nullptr)
    2327        2602 :         pszProxy = CPLGetConfigOption("GDAL_HTTP_PROXY", nullptr);
    2328        2602 :     if (pszProxy)
    2329           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXY, pszProxy);
    2330             : 
    2331        2602 :     const char *pszHttpsProxy = CSLFetchNameValue(papszOptions, "HTTPS_PROXY");
    2332        2602 :     if (pszHttpsProxy == nullptr)
    2333        2602 :         pszHttpsProxy = CPLGetConfigOption("GDAL_HTTPS_PROXY", nullptr);
    2334        2602 :     if (pszHttpsProxy && (STARTS_WITH(pszURL, "https")))
    2335           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXY, pszHttpsProxy);
    2336             : 
    2337             :     const char *pszProxyUserPwd =
    2338        2602 :         CSLFetchNameValue(papszOptions, "PROXYUSERPWD");
    2339        2602 :     if (pszProxyUserPwd == nullptr)
    2340        2602 :         pszProxyUserPwd = CPLGetConfigOption("GDAL_HTTP_PROXYUSERPWD", nullptr);
    2341        2602 :     if (pszProxyUserPwd)
    2342           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYUSERPWD,
    2343             :                                    pszProxyUserPwd);
    2344             : 
    2345             :     // Support control over PROXYAUTH.
    2346        2602 :     const char *pszProxyAuth = CSLFetchNameValue(papszOptions, "PROXYAUTH");
    2347        2602 :     if (pszProxyAuth == nullptr)
    2348        2602 :         pszProxyAuth = CPLGetConfigOption("GDAL_PROXY_AUTH", nullptr);
    2349        2602 :     if (pszProxyAuth == nullptr)
    2350             :     {
    2351             :         // Do nothing.
    2352             :     }
    2353           0 :     else if (EQUAL(pszProxyAuth, "BASIC"))
    2354           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2355             :                                    CURLAUTH_BASIC);
    2356           0 :     else if (EQUAL(pszProxyAuth, "NTLM"))
    2357           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2358             :                                    CURLAUTH_NTLM);
    2359           0 :     else if (EQUAL(pszProxyAuth, "DIGEST"))
    2360           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2361             :                                    CURLAUTH_DIGEST);
    2362           0 :     else if (EQUAL(pszProxyAuth, "ANY"))
    2363           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2364             :                                    CURLAUTH_ANY);
    2365           0 :     else if (EQUAL(pszProxyAuth, "ANYSAFE"))
    2366           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2367             :                                    CURLAUTH_ANYSAFE);
    2368           0 :     else if (EQUAL(pszProxyAuth, "NEGOTIATE"))
    2369           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH,
    2370             :                                    CURLAUTH_NEGOTIATE);
    2371             :     else
    2372             :     {
    2373           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2374             :                  "Unsupported PROXYAUTH value '%s', ignored.", pszProxyAuth);
    2375             :     }
    2376             : 
    2377        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_SUPPRESS_CONNECT_HEADERS,
    2378             :                                1L);
    2379             : 
    2380        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1);
    2381        2602 :     const char *pszUnrestrictedAuth = CPLGetConfigOption(
    2382             :         "CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT",
    2383             :         "IF_SAME_HOST");
    2384        2620 :     if (!EQUAL(pszUnrestrictedAuth, "IF_SAME_HOST") &&
    2385          18 :         CPLTestBool(pszUnrestrictedAuth))
    2386             :     {
    2387           9 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_UNRESTRICTED_AUTH, 1);
    2388             :     }
    2389             : 
    2390        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_MAXREDIRS, 10);
    2391        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_POSTREDIR,
    2392             :                                CURL_REDIR_POST_ALL);
    2393             : 
    2394             :     // Set connect timeout.
    2395             :     const char *pszConnectTimeout =
    2396        2602 :         CSLFetchNameValue(papszOptions, "CONNECTTIMEOUT");
    2397        2602 :     if (pszConnectTimeout == nullptr)
    2398             :         pszConnectTimeout =
    2399        2602 :             CPLGetConfigOption("GDAL_HTTP_CONNECTTIMEOUT", nullptr);
    2400        2602 :     if (pszConnectTimeout != nullptr)
    2401             :     {
    2402             :         // coverity[tainted_data]
    2403           1 :         unchecked_curl_easy_setopt(
    2404             :             http_handle, CURLOPT_CONNECTTIMEOUT_MS,
    2405             :             static_cast<int>(1000 * CPLAtof(pszConnectTimeout)));
    2406             :     }
    2407             : 
    2408             :     // Set timeout.
    2409        2602 :     const char *pszTimeout = CSLFetchNameValue(papszOptions, "TIMEOUT");
    2410        2602 :     if (pszTimeout == nullptr)
    2411        2308 :         pszTimeout = CPLGetConfigOption("GDAL_HTTP_TIMEOUT", nullptr);
    2412        2602 :     if (pszTimeout != nullptr)
    2413             :     {
    2414             :         // coverity[tainted_data]
    2415         303 :         unchecked_curl_easy_setopt(
    2416             :             http_handle, CURLOPT_TIMEOUT_MS,
    2417             :             static_cast<int>(1000 * CPLAtof(pszTimeout)));
    2418             :     }
    2419             : 
    2420             :     // Set low speed time and limit.
    2421             :     const char *pszLowSpeedTime =
    2422        2602 :         CSLFetchNameValue(papszOptions, "LOW_SPEED_TIME");
    2423        2602 :     if (pszLowSpeedTime == nullptr)
    2424             :         pszLowSpeedTime =
    2425        2602 :             CPLGetConfigOption("GDAL_HTTP_LOW_SPEED_TIME", nullptr);
    2426        2602 :     if (pszLowSpeedTime != nullptr)
    2427             :     {
    2428           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_LOW_SPEED_TIME,
    2429             :                                    atoi(pszLowSpeedTime));
    2430             : 
    2431             :         const char *pszLowSpeedLimit =
    2432           0 :             CSLFetchNameValue(papszOptions, "LOW_SPEED_LIMIT");
    2433           0 :         if (pszLowSpeedLimit == nullptr)
    2434             :             pszLowSpeedLimit =
    2435           0 :                 CPLGetConfigOption("GDAL_HTTP_LOW_SPEED_LIMIT", "1");
    2436           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_LOW_SPEED_LIMIT,
    2437             :                                    atoi(pszLowSpeedLimit));
    2438             :     }
    2439             : 
    2440             :     /* Disable some SSL verification */
    2441        2602 :     const char *pszUnsafeSSL = CSLFetchNameValue(papszOptions, "UNSAFESSL");
    2442        2602 :     if (pszUnsafeSSL == nullptr)
    2443        2579 :         pszUnsafeSSL = CPLGetConfigOption("GDAL_HTTP_UNSAFESSL", nullptr);
    2444        2602 :     if (pszUnsafeSSL != nullptr && CPLTestBool(pszUnsafeSSL))
    2445             :     {
    2446          23 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYPEER, 0L);
    2447          23 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYHOST, 0L);
    2448             :     }
    2449             : 
    2450             :     const char *pszUseCAPIStore =
    2451        2602 :         CSLFetchNameValue(papszOptions, "USE_CAPI_STORE");
    2452        2602 :     if (pszUseCAPIStore == nullptr)
    2453        2602 :         pszUseCAPIStore = CPLGetConfigOption("GDAL_HTTP_USE_CAPI_STORE", "NO");
    2454        2602 :     if (CPLTestBool(pszUseCAPIStore))
    2455             :     {
    2456             : #if defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
    2457             :         // Use certificates from Windows certificate store; requires
    2458             :         // crypt32.lib, OpenSSL crypto and ssl libraries.
    2459             :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSL_CTX_FUNCTION,
    2460             :                                    *CPL_ssl_ctx_callback);
    2461             : #else   // defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
    2462           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    2463             :                  "GDAL_HTTP_USE_CAPI_STORE requested, but libcurl too old, "
    2464             :                  "non-Windows platform or OpenSSL missing.");
    2465             : #endif  // defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
    2466             :     }
    2467             : 
    2468             :     // Enable OCSP stapling if requested.
    2469             :     const char *pszSSLVerifyStatus =
    2470        2602 :         CSLFetchNameValue(papszOptions, "SSL_VERIFYSTATUS");
    2471        2602 :     if (pszSSLVerifyStatus == nullptr)
    2472             :         pszSSLVerifyStatus =
    2473        2602 :             CPLGetConfigOption("GDAL_HTTP_SSL_VERIFYSTATUS", "NO");
    2474        2602 :     if (CPLTestBool(pszSSLVerifyStatus))
    2475             :     {
    2476           1 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYSTATUS, 1L);
    2477             :     }
    2478             : 
    2479             :     // Custom path to SSL certificates.
    2480        2602 :     const char *pszCAInfo = CSLFetchNameValue(papszOptions, "CAINFO");
    2481        2602 :     if (pszCAInfo == nullptr)
    2482             :         // Name of GDAL environment variable for the CA Bundle path
    2483        2602 :         pszCAInfo = CPLGetConfigOption("GDAL_CURL_CA_BUNDLE", nullptr);
    2484        2602 :     if (pszCAInfo == nullptr)
    2485             :         // Name of environment variable used by the curl binary
    2486        2602 :         pszCAInfo = CPLGetConfigOption("CURL_CA_BUNDLE", nullptr);
    2487        2602 :     if (pszCAInfo == nullptr)
    2488             :         // Name of environment variable used by the curl binary (tested
    2489             :         // after CURL_CA_BUNDLE
    2490        2602 :         pszCAInfo = CPLGetConfigOption("SSL_CERT_FILE", nullptr);
    2491             : #ifdef _WIN32
    2492             :     if (pszCAInfo == nullptr)
    2493             :     {
    2494             :         pszCAInfo = CPLFindWin32CurlCaBundleCrt();
    2495             :     }
    2496             : #endif
    2497        2602 :     if (pszCAInfo != nullptr)
    2498             :     {
    2499           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_CAINFO, pszCAInfo);
    2500             :     }
    2501             : 
    2502        2602 :     const char *pszCAPath = CSLFetchNameValue(papszOptions, "CAPATH");
    2503        2602 :     if (pszCAPath != nullptr)
    2504             :     {
    2505           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_CAPATH, pszCAPath);
    2506             :     }
    2507             : 
    2508             :     // Support for SSL client certificates
    2509             : 
    2510             :     // Filename of the the client certificate
    2511        2602 :     const char *pszSSLCert = CSLFetchNameValue(papszOptions, "SSLCERT");
    2512        2602 :     if (!pszSSLCert)
    2513        2602 :         pszSSLCert = CPLGetConfigOption("GDAL_HTTP_SSLCERT", nullptr);
    2514        2602 :     if (pszSSLCert)
    2515           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSLCERT, pszSSLCert);
    2516             : 
    2517             :     // private key file for TLS and SSL client cert
    2518        2602 :     const char *pszSSLKey = CSLFetchNameValue(papszOptions, "SSLKEY");
    2519        2602 :     if (!pszSSLKey)
    2520        2602 :         pszSSLKey = CPLGetConfigOption("GDAL_HTTP_SSLKEY", nullptr);
    2521        2602 :     if (pszSSLKey)
    2522           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSLKEY, pszSSLKey);
    2523             : 
    2524             :     // type of client SSL certificate ("PEM", "DER", ...)
    2525        2602 :     const char *pszSSLCertType = CSLFetchNameValue(papszOptions, "SSLCERTTYPE");
    2526        2602 :     if (!pszSSLCertType)
    2527        2602 :         pszSSLCertType = CPLGetConfigOption("GDAL_HTTP_SSLCERTTYPE", nullptr);
    2528        2602 :     if (pszSSLCertType)
    2529           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_SSLCERTTYPE,
    2530             :                                    pszSSLCertType);
    2531             : 
    2532             :     // passphrase to private key
    2533        2602 :     const char *pszKeyPasswd = CSLFetchNameValue(papszOptions, "KEYPASSWD");
    2534        2602 :     if (!pszKeyPasswd)
    2535        2602 :         pszKeyPasswd = CPLGetConfigOption("GDAL_HTTP_KEYPASSWD", nullptr);
    2536        2602 :     if (pszKeyPasswd)
    2537           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_KEYPASSWD,
    2538             :                                    pszKeyPasswd);
    2539             : 
    2540             :     /* Set Referer */
    2541        2602 :     const char *pszReferer = CSLFetchNameValue(papszOptions, "REFERER");
    2542        2602 :     if (pszReferer != nullptr)
    2543           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_REFERER, pszReferer);
    2544             : 
    2545             :     /* Set User-Agent */
    2546        2602 :     const char *pszUserAgent = CSLFetchNameValue(papszOptions, "USERAGENT");
    2547        2602 :     if (pszUserAgent == nullptr)
    2548        2514 :         pszUserAgent = CPLGetConfigOption("GDAL_HTTP_USERAGENT",
    2549             :                                           gosDefaultUserAgent.c_str());
    2550        2602 :     if (pszUserAgent != nullptr && !EQUAL(pszUserAgent, ""))
    2551             :     {
    2552        2602 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_USERAGENT,
    2553             :                                    pszUserAgent);
    2554             :     }
    2555             : 
    2556             :     /* NOSIGNAL should be set to true for timeout to work in multithread
    2557             :      * environments on Unix, requires libcurl 7.10 or more recent.
    2558             :      * (this force avoiding the use of signal handlers)
    2559             :      */
    2560        2602 :     unchecked_curl_easy_setopt(http_handle, CURLOPT_NOSIGNAL, 1);
    2561             : 
    2562             :     const char *pszFormFilePath =
    2563        2602 :         CSLFetchNameValue(papszOptions, "FORM_FILE_PATH");
    2564             :     const char *pszParametersCount =
    2565        2602 :         CSLFetchNameValue(papszOptions, "FORM_ITEM_COUNT");
    2566        2602 :     if (pszFormFilePath == nullptr && pszParametersCount == nullptr)
    2567             :     {
    2568             :         /* Set POST mode */
    2569        2600 :         const char *pszPost = CSLFetchNameValue(papszOptions, "POSTFIELDS");
    2570        2600 :         if (pszPost != nullptr)
    2571             :         {
    2572         159 :             CPLDebug("HTTP", "These POSTFIELDS were sent:%.4000s", pszPost);
    2573         159 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_POST, 1);
    2574         159 :             unchecked_curl_easy_setopt(http_handle, CURLOPT_POSTFIELDS,
    2575             :                                        pszPost);
    2576             :         }
    2577             :     }
    2578             : 
    2579             :     const char *pszCustomRequest =
    2580        2602 :         CSLFetchNameValue(papszOptions, "CUSTOMREQUEST");
    2581        2602 :     if (pszCustomRequest != nullptr)
    2582             :     {
    2583         138 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_CUSTOMREQUEST,
    2584             :                                    pszCustomRequest);
    2585             :     }
    2586             : 
    2587        2602 :     const char *pszCookie = CSLFetchNameValue(papszOptions, "COOKIE");
    2588        2602 :     if (pszCookie == nullptr)
    2589        2602 :         pszCookie = CPLGetConfigOption("GDAL_HTTP_COOKIE", nullptr);
    2590        2602 :     if (pszCookie != nullptr)
    2591           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_COOKIE, pszCookie);
    2592             : 
    2593        2602 :     const char *pszCookieFile = CSLFetchNameValue(papszOptions, "COOKIEFILE");
    2594        2602 :     if (pszCookieFile == nullptr)
    2595        2602 :         pszCookieFile = CPLGetConfigOption("GDAL_HTTP_COOKIEFILE", nullptr);
    2596        2602 :     if (pszCookieFile != nullptr)
    2597           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_COOKIEFILE,
    2598             :                                    pszCookieFile);
    2599             : 
    2600        2602 :     const char *pszCookieJar = CSLFetchNameValue(papszOptions, "COOKIEJAR");
    2601        2602 :     if (pszCookieJar == nullptr)
    2602        2602 :         pszCookieJar = CPLGetConfigOption("GDAL_HTTP_COOKIEJAR", nullptr);
    2603        2602 :     if (pszCookieJar != nullptr)
    2604           0 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_COOKIEJAR,
    2605             :                                    pszCookieJar);
    2606             : 
    2607             :     // TCP keep-alive
    2608             :     const char *pszTCPKeepAlive =
    2609        2602 :         CSLFetchNameValue(papszOptions, "TCP_KEEPALIVE");
    2610        2602 :     if (pszTCPKeepAlive == nullptr)
    2611        2602 :         pszTCPKeepAlive = CPLGetConfigOption("GDAL_HTTP_TCP_KEEPALIVE", "YES");
    2612        2602 :     if (pszTCPKeepAlive != nullptr && CPLTestBool(pszTCPKeepAlive))
    2613             :     {
    2614             :         // Set keep-alive interval.
    2615        2602 :         int nKeepAliveInterval = 60;
    2616             :         const char *pszKeepAliveInterval =
    2617        2602 :             CSLFetchNameValue(papszOptions, "TCP_KEEPINTVL");
    2618        2602 :         if (pszKeepAliveInterval == nullptr)
    2619             :             pszKeepAliveInterval =
    2620        2602 :                 CPLGetConfigOption("GDAL_HTTP_TCP_KEEPINTVL", nullptr);
    2621        2602 :         if (pszKeepAliveInterval != nullptr)
    2622           1 :             nKeepAliveInterval = atoi(pszKeepAliveInterval);
    2623             : 
    2624             :         // Set keep-alive idle wait time.
    2625        2602 :         int nKeepAliveIdle = 60;
    2626             :         const char *pszKeepAliveIdle =
    2627        2602 :             CSLFetchNameValue(papszOptions, "TCP_KEEPIDLE");
    2628        2602 :         if (pszKeepAliveIdle == nullptr)
    2629             :             pszKeepAliveIdle =
    2630        2602 :                 CPLGetConfigOption("GDAL_HTTP_TCP_KEEPIDLE", nullptr);
    2631        2602 :         if (pszKeepAliveIdle != nullptr)
    2632           1 :             nKeepAliveIdle = atoi(pszKeepAliveIdle);
    2633             : 
    2634        2602 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_TCP_KEEPALIVE, 1L);
    2635        2602 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_TCP_KEEPINTVL,
    2636             :                                    nKeepAliveInterval);
    2637        2602 :         unchecked_curl_easy_setopt(http_handle, CURLOPT_TCP_KEEPIDLE,
    2638             :                                    nKeepAliveIdle);
    2639             :     }
    2640             : 
    2641        2602 :     struct curl_slist *headers = nullptr;
    2642        2602 :     const char *pszAccept = CSLFetchNameValue(papszOptions, "ACCEPT");
    2643        2602 :     if (pszAccept)
    2644             :     {
    2645           4 :         headers =
    2646           4 :             curl_slist_append(headers, CPLSPrintf("Accept: %s", pszAccept));
    2647             :     }
    2648             : 
    2649        1694 :     const auto AddHeader = [&headers, pszAccept](const char *pszHeader)
    2650             :     {
    2651         676 :         if (STARTS_WITH_CI(pszHeader, "Accept:") && pszAccept)
    2652             :         {
    2653           1 :             const char *pszVal = pszHeader + strlen("Accept:");
    2654           2 :             while (*pszVal == ' ')
    2655           1 :                 ++pszVal;
    2656           1 :             if (!EQUAL(pszVal, pszAccept))
    2657             :             {
    2658             :                 // Cf https://github.com/OSGeo/gdal/issues/7691#issuecomment-2873711603
    2659           1 :                 CPLDebug(
    2660             :                     "HTTP",
    2661             :                     "Ignoring '%s' since ACCEPT option = '%s' is specified",
    2662             :                     pszHeader, pszAccept);
    2663           1 :             }
    2664             :         }
    2665             :         else
    2666             :         {
    2667         675 :             headers = curl_slist_append(headers, pszHeader);
    2668             :         }
    2669        3278 :     };
    2670             : 
    2671        2602 :     const char *pszHeaderFile = CSLFetchNameValue(papszOptions, "HEADER_FILE");
    2672        2602 :     if (pszHeaderFile == nullptr)
    2673        2600 :         pszHeaderFile = CPLGetConfigOption("GDAL_HTTP_HEADER_FILE", nullptr);
    2674        2602 :     if (pszHeaderFile != nullptr)
    2675             :     {
    2676           2 :         VSILFILE *fp = nullptr;
    2677             :         // Do not allow /vsicurl/ access from /vsicurl because of
    2678             :         // GetCurlHandleFor() e.g. "/vsicurl/,HEADER_FILE=/vsicurl/,url= " would
    2679             :         // cause use of memory after free
    2680           2 :         if (!STARTS_WITH(pszHeaderFile, "/vsi") ||
    2681           2 :             STARTS_WITH(pszHeaderFile, "/vsimem/"))
    2682             :         {
    2683           2 :             fp = VSIFOpenL(pszHeaderFile, "rb");
    2684             :         }
    2685           2 :         if (fp == nullptr)
    2686             :         {
    2687           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot read %s", pszHeaderFile);
    2688             :         }
    2689             :         else
    2690             :         {
    2691           2 :             const char *pszLine = nullptr;
    2692           4 :             while ((pszLine = CPLReadLineL(fp)) != nullptr)
    2693             :             {
    2694           2 :                 AddHeader(pszLine);
    2695             :             }
    2696           2 :             VSIFCloseL(fp);
    2697             :         }
    2698             :     }
    2699             : 
    2700        2602 :     const char *pszHeaders = CSLFetchNameValue(papszOptions, "HEADERS");
    2701        2602 :     if (pszHeaders == nullptr)
    2702        2000 :         pszHeaders = CPLGetConfigOption("GDAL_HTTP_HEADERS", nullptr);
    2703        2602 :     if (pszHeaders)
    2704             :     {
    2705         611 :         bool bHeadersDone = false;
    2706             :         // Compatibility hack for "HEADERS=Accept: text/plain, application/json"
    2707         611 :         if (strstr(pszHeaders, "\r\n") == nullptr)
    2708             :         {
    2709         552 :             const char *pszComma = strchr(pszHeaders, ',');
    2710         552 :             if (pszComma != nullptr && strchr(pszComma, ':') == nullptr)
    2711             :             {
    2712         211 :                 AddHeader(pszHeaders);
    2713         211 :                 bHeadersDone = true;
    2714             :             }
    2715             :         }
    2716         611 :         if (!bHeadersDone)
    2717             :         {
    2718         400 :             const char *pszAuthorizationHeaderAllowed = CSLFetchNameValueDef(
    2719             :                 papszOptions, "AUTHORIZATION_HEADER_ALLOWED", "YES");
    2720             :             const bool bAuthorizationHeaderAllowed =
    2721         400 :                 CPLTestBool(pszAuthorizationHeaderAllowed);
    2722             : 
    2723             :             // We accept both raw headers with \r\n as a separator, or as
    2724             :             // a comma separated list of foo: bar values.
    2725             :             const CPLStringList aosTokens(
    2726         400 :                 strstr(pszHeaders, "\r\n")
    2727          59 :                     ? CSLTokenizeString2(pszHeaders, "\r\n", 0)
    2728         859 :                     : CSLTokenizeString2(pszHeaders, ",", CSLT_HONOURSTRINGS));
    2729         867 :             for (int i = 0; i < aosTokens.size(); ++i)
    2730             :             {
    2731         471 :                 if (bAuthorizationHeaderAllowed ||
    2732           4 :                     !STARTS_WITH_CI(aosTokens[i], "Authorization:"))
    2733             :                 {
    2734         463 :                     AddHeader(aosTokens[i]);
    2735             :                 }
    2736             :             }
    2737             :         }
    2738             :     }
    2739             : 
    2740        2602 :     return headers;
    2741             : }
    2742             : 
    2743             : /************************************************************************/
    2744             : /*                         CPLHTTPIgnoreSigPipe()                       */
    2745             : /************************************************************************/
    2746             : 
    2747             : /* If using OpenSSL with Curl, openssl can cause SIGPIPE to be triggered */
    2748             : /* As we set CURLOPT_NOSIGNAL = 1, we must manually handle this situation */
    2749             : 
    2750        2820 : void *CPLHTTPIgnoreSigPipe()
    2751             : {
    2752             : #if defined(SIGPIPE) && defined(HAVE_SIGACTION)
    2753             :     struct sigaction old_pipe_act;
    2754             :     struct sigaction action;
    2755             :     /* Get previous handler */
    2756        2820 :     memset(&old_pipe_act, 0, sizeof(struct sigaction));
    2757        2820 :     sigaction(SIGPIPE, nullptr, &old_pipe_act);
    2758             : 
    2759             :     /* Install new handler */
    2760        2820 :     action = old_pipe_act;
    2761        2820 :     action.sa_handler = SIG_IGN;
    2762        2820 :     sigaction(SIGPIPE, &action, nullptr);
    2763             : 
    2764        2820 :     void *ret = CPLMalloc(sizeof(old_pipe_act));
    2765        2820 :     memcpy(ret, &old_pipe_act, sizeof(old_pipe_act));
    2766        2820 :     return ret;
    2767             : #else
    2768             :     return nullptr;
    2769             : #endif
    2770             : }
    2771             : 
    2772             : /************************************************************************/
    2773             : /*                     CPLHTTPRestoreSigPipeHandler()                   */
    2774             : /************************************************************************/
    2775             : 
    2776        2820 : void CPLHTTPRestoreSigPipeHandler(void *old_handler)
    2777             : {
    2778             : #if defined(SIGPIPE) && defined(HAVE_SIGACTION)
    2779        2820 :     sigaction(SIGPIPE, static_cast<struct sigaction *>(old_handler), nullptr);
    2780        2820 :     CPLFree(old_handler);
    2781             : #else
    2782             :     (void)old_handler;
    2783             : #endif
    2784        2820 : }
    2785             : 
    2786             : #endif  // def HAVE_CURL
    2787             : 
    2788             : /************************************************************************/
    2789             : /*                           CPLHTTPEnabled()                           */
    2790             : /************************************************************************/
    2791             : 
    2792             : /**
    2793             :  * \brief Return if CPLHTTP services can be useful
    2794             :  *
    2795             :  * Those services depend on GDAL being build with libcurl support.
    2796             :  *
    2797             :  * @return TRUE if libcurl support is enabled
    2798             :  */
    2799           9 : int CPLHTTPEnabled()
    2800             : 
    2801             : {
    2802             : #ifdef HAVE_CURL
    2803           9 :     return TRUE;
    2804             : #else
    2805             :     return FALSE;
    2806             : #endif
    2807             : }
    2808             : 
    2809             : /************************************************************************/
    2810             : /*                           CPLHTTPCleanup()                           */
    2811             : /************************************************************************/
    2812             : 
    2813             : /**
    2814             :  * \brief Cleanup function to call at application termination
    2815             :  */
    2816        1121 : void CPLHTTPCleanup()
    2817             : 
    2818             : {
    2819             : #ifdef HAVE_CURL
    2820        1121 :     if (!hSessionMapMutex)
    2821        1120 :         return;
    2822             : 
    2823             :     {
    2824           2 :         CPLMutexHolder oHolder(&hSessionMapMutex);
    2825           1 :         if (poSessionMap)
    2826             :         {
    2827           0 :             for (auto &kv : *poSessionMap)
    2828             :             {
    2829           0 :                 curl_easy_cleanup(kv.second);
    2830             :             }
    2831           0 :             delete poSessionMap;
    2832           0 :             poSessionMap = nullptr;
    2833             :         }
    2834           1 :         if (poSessionMultiMap)
    2835             :         {
    2836           0 :             for (auto &kv : *poSessionMultiMap)
    2837             :             {
    2838           0 :                 VSICURLMultiCleanup(kv.second);
    2839             :             }
    2840           0 :             delete poSessionMultiMap;
    2841           0 :             poSessionMultiMap = nullptr;
    2842             :         }
    2843             :     }
    2844             : 
    2845             :     // Not quite a safe sequence.
    2846           1 :     CPLDestroyMutex(hSessionMapMutex);
    2847           1 :     hSessionMapMutex = nullptr;
    2848             : 
    2849             : #if defined(_WIN32) && defined(HAVE_OPENSSL_CRYPTO)
    2850             :     // This cleanup must be absolutely done before CPLOpenSSLCleanup()
    2851             :     // for some unknown reason, but otherwise X509_free() in
    2852             :     // CPLWindowsCertificateListCleanup() will crash.
    2853             :     CPLWindowsCertificateListCleanup();
    2854             : #endif
    2855             : 
    2856             : #if defined(HAVE_OPENSSL_CRYPTO) && OPENSSL_VERSION_NUMBER < 0x10100000
    2857             :     CPLOpenSSLCleanup();
    2858             : #endif
    2859             : 
    2860             : #endif
    2861             : }
    2862             : 
    2863             : /************************************************************************/
    2864             : /*                        CPLHTTPDestroyResult()                        */
    2865             : /************************************************************************/
    2866             : 
    2867             : /**
    2868             :  * \brief Clean the memory associated with the return value of CPLHTTPFetch()
    2869             :  *
    2870             :  * @param psResult pointer to the return value of CPLHTTPFetch()
    2871             :  */
    2872        2203 : void CPLHTTPDestroyResult(CPLHTTPResult *psResult)
    2873             : 
    2874             : {
    2875        2203 :     if (psResult)
    2876             :     {
    2877        2015 :         CPLFree(psResult->pabyData);
    2878        2015 :         CPLFree(psResult->pszErrBuf);
    2879        2015 :         CPLFree(psResult->pszContentType);
    2880        2015 :         CSLDestroy(psResult->papszHeaders);
    2881             : 
    2882        2072 :         for (int i = 0; i < psResult->nMimePartCount; i++)
    2883             :         {
    2884          57 :             CSLDestroy(psResult->pasMimePart[i].papszHeaders);
    2885             :         }
    2886        2015 :         CPLFree(psResult->pasMimePart);
    2887             : 
    2888        2015 :         CPLFree(psResult);
    2889             :     }
    2890        2203 : }
    2891             : 
    2892             : /************************************************************************/
    2893             : /*                     CPLHTTPParseMultipartMime()                      */
    2894             : /************************************************************************/
    2895             : 
    2896             : /**
    2897             :  * \brief Parses a MIME multipart message.
    2898             :  *
    2899             :  * This function will iterate over each part and put it in a separate
    2900             :  * element of the pasMimePart array of the provided psResult structure.
    2901             :  *
    2902             :  * @param psResult pointer to the return value of CPLHTTPFetch()
    2903             :  * @return TRUE if the message contains MIME multipart message.
    2904             :  */
    2905          45 : int CPLHTTPParseMultipartMime(CPLHTTPResult *psResult)
    2906             : 
    2907             : {
    2908             :     /* -------------------------------------------------------------------- */
    2909             :     /*      Is it already done?                                             */
    2910             :     /* -------------------------------------------------------------------- */
    2911          45 :     if (psResult->nMimePartCount > 0)
    2912           5 :         return TRUE;
    2913             : 
    2914             :     /* -------------------------------------------------------------------- */
    2915             :     /*      Find the boundary setting in the content type.                  */
    2916             :     /* -------------------------------------------------------------------- */
    2917          40 :     const char *pszBound = nullptr;
    2918             : 
    2919          40 :     if (psResult->pszContentType != nullptr)
    2920          38 :         pszBound = strstr(psResult->pszContentType, "boundary=");
    2921             : 
    2922          40 :     if (pszBound == nullptr)
    2923             :     {
    2924           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    2925             :                  "Unable to parse multi-part mime, no boundary setting.");
    2926           2 :         return FALSE;
    2927             :     }
    2928             : 
    2929          76 :     CPLString osBoundary;
    2930             :     char **papszTokens =
    2931          38 :         CSLTokenizeStringComplex(pszBound + 9, "\n ;", TRUE, FALSE);
    2932             : 
    2933          38 :     if (CSLCount(papszTokens) == 0 || strlen(papszTokens[0]) == 0)
    2934             :     {
    2935           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2936             :                  "Unable to parse multi-part mime, boundary not parsable.");
    2937           1 :         CSLDestroy(papszTokens);
    2938           1 :         return FALSE;
    2939             :     }
    2940             : 
    2941          37 :     osBoundary = "--";
    2942          37 :     osBoundary += papszTokens[0];
    2943          37 :     CSLDestroy(papszTokens);
    2944             : 
    2945             :     /* -------------------------------------------------------------------- */
    2946             :     /*      Find the start of the first chunk.                              */
    2947             :     /* -------------------------------------------------------------------- */
    2948          37 :     char *pszNext = psResult->pabyData
    2949          37 :                         ? strstr(reinterpret_cast<char *>(psResult->pabyData),
    2950             :                                  osBoundary.c_str())
    2951          37 :                         : nullptr;
    2952             : 
    2953          37 :     if (pszNext == nullptr)
    2954             :     {
    2955           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No parts found.");
    2956           1 :         return FALSE;
    2957             :     }
    2958             : 
    2959          36 :     pszNext += osBoundary.size();
    2960         146 :     while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
    2961         110 :         pszNext++;
    2962          36 :     if (*pszNext == '\r')
    2963          30 :         pszNext++;
    2964          36 :     if (*pszNext == '\n')
    2965          36 :         pszNext++;
    2966             : 
    2967             :     /* -------------------------------------------------------------------- */
    2968             :     /*      Loop over parts...                                              */
    2969             :     /* -------------------------------------------------------------------- */
    2970             :     while (true)
    2971             :     {
    2972          57 :         psResult->nMimePartCount++;
    2973          57 :         psResult->pasMimePart = static_cast<CPLMimePart *>(
    2974         114 :             CPLRealloc(psResult->pasMimePart,
    2975          57 :                        sizeof(CPLMimePart) * psResult->nMimePartCount));
    2976             : 
    2977          57 :         CPLMimePart *psPart =
    2978          57 :             psResult->pasMimePart + psResult->nMimePartCount - 1;
    2979             : 
    2980          57 :         memset(psPart, 0, sizeof(CPLMimePart));
    2981             : 
    2982             :         /* --------------------------------------------------------------------
    2983             :          */
    2984             :         /*      Collect headers. */
    2985             :         /* --------------------------------------------------------------------
    2986             :          */
    2987         145 :         while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
    2988             :         {
    2989          90 :             if (!STARTS_WITH(pszNext, "Content-"))
    2990             :             {
    2991           1 :                 break;
    2992             :             }
    2993          89 :             char *pszEOL = strstr(pszNext, "\n");
    2994             : 
    2995          89 :             if (pszEOL == nullptr)
    2996             :             {
    2997           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2998             :                          "Error while parsing multipart content (at line %d)",
    2999             :                          __LINE__);
    3000           1 :                 return FALSE;
    3001             :             }
    3002             : 
    3003          88 :             *pszEOL = '\0';
    3004          88 :             bool bRestoreAntislashR = false;
    3005          88 :             if (pszEOL - pszNext > 1 && pszEOL[-1] == '\r')
    3006             :             {
    3007          80 :                 bRestoreAntislashR = true;
    3008          80 :                 pszEOL[-1] = '\0';
    3009             :             }
    3010          88 :             char *pszKey = nullptr;
    3011          88 :             const char *pszValue = CPLParseNameValue(pszNext, &pszKey);
    3012          88 :             if (pszKey && pszValue)
    3013             :             {
    3014          88 :                 psPart->papszHeaders =
    3015          88 :                     CSLSetNameValue(psPart->papszHeaders, pszKey, pszValue);
    3016             :             }
    3017          88 :             CPLFree(pszKey);
    3018          88 :             if (bRestoreAntislashR)
    3019          80 :                 pszEOL[-1] = '\r';
    3020          88 :             *pszEOL = '\n';
    3021             : 
    3022          88 :             pszNext = pszEOL + 1;
    3023             :         }
    3024             : 
    3025          56 :         if (*pszNext == '\r')
    3026          54 :             pszNext++;
    3027          56 :         if (*pszNext == '\n')
    3028          54 :             pszNext++;
    3029             : 
    3030             :         /* --------------------------------------------------------------------
    3031             :          */
    3032             :         /*      Work out the data block size. */
    3033             :         /* --------------------------------------------------------------------
    3034             :          */
    3035          56 :         psPart->pabyData = reinterpret_cast<GByte *>(pszNext);
    3036             : 
    3037          56 :         int nBytesAvail = psResult->nDataLen -
    3038          56 :                           static_cast<int>(pszNext - reinterpret_cast<char *>(
    3039          56 :                                                          psResult->pabyData));
    3040             : 
    3041     6579040 :         while (nBytesAvail > 0 &&
    3042     3289520 :                (*pszNext != '-' ||
    3043         109 :                 strncmp(pszNext, osBoundary, osBoundary.size()) != 0))
    3044             :         {
    3045     3289470 :             pszNext++;
    3046     3289470 :             nBytesAvail--;
    3047             :         }
    3048             : 
    3049          56 :         if (nBytesAvail == 0)
    3050             :         {
    3051           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    3052             :                      "Error while parsing multipart content (at line %d)",
    3053             :                      __LINE__);
    3054           2 :             return FALSE;
    3055             :         }
    3056             : 
    3057          54 :         psPart->nDataLen = static_cast<int>(
    3058          54 :             pszNext - reinterpret_cast<char *>(psPart->pabyData));
    3059             :         // Normally the part should end with "\r\n--boundary_marker"
    3060          54 :         if (psPart->nDataLen >= 2 && pszNext[-2] == '\r' && pszNext[-1] == '\n')
    3061             :         {
    3062          46 :             psPart->nDataLen -= 2;
    3063             :         }
    3064             : 
    3065          54 :         pszNext += osBoundary.size();
    3066             : 
    3067          54 :         if (STARTS_WITH(pszNext, "--"))
    3068             :         {
    3069          31 :             break;
    3070             :         }
    3071             : 
    3072          23 :         if (*pszNext == '\r')
    3073          19 :             pszNext++;
    3074          23 :         if (*pszNext == '\n')
    3075          21 :             pszNext++;
    3076             :         else
    3077             :         {
    3078           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    3079             :                      "Error while parsing multipart content (at line %d)",
    3080             :                      __LINE__);
    3081           2 :             return FALSE;
    3082             :         }
    3083          21 :     }
    3084             : 
    3085          31 :     return TRUE;
    3086             : }
    3087             : 
    3088             : #if defined(__GNUC__)
    3089             : #pragma GCC diagnostic pop
    3090             : #endif

Generated by: LCOV version 1.14