Coverage Report

Created: 2026-02-14 09:00

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