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