/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 |