Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for HTTP/FTP files
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2018, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "cpl_vsil_curl_priv.h"
15 : #include "cpl_vsil_curl_class.h"
16 :
17 : #include <algorithm>
18 : #include <array>
19 : #include <limits>
20 : #include <map>
21 : #include <memory>
22 : #include <numeric>
23 : #include <set>
24 : #include <string_view>
25 :
26 : #include "cpl_aws.h"
27 : #include "cpl_json.h"
28 : #include "cpl_json_header.h"
29 : #include "cpl_minixml.h"
30 : #include "cpl_multiproc.h"
31 : #include "cpl_string.h"
32 : #include "cpl_time.h"
33 : #include "cpl_vsi.h"
34 : #include "cpl_vsi_virtual.h"
35 : #include "cpl_http.h"
36 : #include "cpl_mem_cache.h"
37 :
38 : #ifndef S_IRUSR
39 : #define S_IRUSR 00400
40 : #define S_IWUSR 00200
41 : #define S_IXUSR 00100
42 : #define S_IRGRP 00040
43 : #define S_IWGRP 00020
44 : #define S_IXGRP 00010
45 : #define S_IROTH 00004
46 : #define S_IWOTH 00002
47 : #define S_IXOTH 00001
48 : #endif
49 :
50 : #ifndef HAVE_CURL
51 :
52 : void VSIInstallCurlFileHandler(void)
53 : {
54 : // Not supported.
55 : }
56 :
57 : void VSICurlClearCache(void)
58 : {
59 : // Not supported.
60 : }
61 :
62 : void VSICurlPartialClearCache(const char *)
63 : {
64 : // Not supported.
65 : }
66 :
67 : void VSICurlAuthParametersChanged()
68 : {
69 : // Not supported.
70 : }
71 :
72 : void VSINetworkStatsReset(void)
73 : {
74 : // Not supported
75 : }
76 :
77 : char *VSINetworkStatsGetAsSerializedJSON(char ** /* papszOptions */)
78 : {
79 : // Not supported
80 : return nullptr;
81 : }
82 :
83 : /************************************************************************/
84 : /* VSICurlInstallReadCbk() */
85 : /************************************************************************/
86 :
87 : int VSICurlInstallReadCbk(VSILFILE * /* fp */,
88 : VSICurlReadCbkFunc /* pfnReadCbk */,
89 : void * /* pfnUserData */,
90 : int /* bStopOnInterruptUntilUninstall */)
91 : {
92 : return FALSE;
93 : }
94 :
95 : /************************************************************************/
96 : /* VSICurlUninstallReadCbk() */
97 : /************************************************************************/
98 :
99 : int VSICurlUninstallReadCbk(VSILFILE * /* fp */)
100 : {
101 : return FALSE;
102 : }
103 :
104 : #else
105 :
106 : //! @cond Doxygen_Suppress
107 : #ifndef DOXYGEN_SKIP
108 :
109 : #define ENABLE_DEBUG 1
110 : #define ENABLE_DEBUG_VERBOSE 0
111 :
112 : #define unchecked_curl_easy_setopt(handle, opt, param) \
113 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
114 :
115 : constexpr const char *const VSICURL_PREFIXES[] = {"/vsicurl/", "/vsicurl?"};
116 :
117 : extern "C" bool CPL_DLL GDALIsInGlobalDestructorFromDLLMain();
118 :
119 : /***********************************************************รน************/
120 : /* VSICurlAuthParametersChanged() */
121 : /************************************************************************/
122 :
123 : static unsigned int gnGenerationAuthParameters = 0;
124 :
125 3707 : void VSICurlAuthParametersChanged()
126 : {
127 3707 : gnGenerationAuthParameters++;
128 3707 : }
129 :
130 : // Do not access those variables directly !
131 : // Use VSICURLGetDownloadChunkSize() and GetMaxRegions()
132 : static int N_MAX_REGIONS_DO_NOT_USE_DIRECTLY = 0;
133 : static int DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY = 0;
134 :
135 : /************************************************************************/
136 : /* VSICURLReadGlobalEnvVariables() */
137 : /************************************************************************/
138 :
139 471553 : static void VSICURLReadGlobalEnvVariables()
140 : {
141 : struct Initializer
142 : {
143 1105 : Initializer()
144 : {
145 1105 : constexpr int DOWNLOAD_CHUNK_SIZE_DEFAULT = 16384;
146 : const char *pszChunkSize =
147 1105 : CPLGetConfigOption("CPL_VSIL_CURL_CHUNK_SIZE", nullptr);
148 1105 : GIntBig nChunkSize = DOWNLOAD_CHUNK_SIZE_DEFAULT;
149 :
150 1105 : if (pszChunkSize)
151 : {
152 0 : if (CPLParseMemorySize(pszChunkSize, &nChunkSize, nullptr) !=
153 : CE_None)
154 : {
155 0 : CPLError(
156 : CE_Warning, CPLE_AppDefined,
157 : "Could not parse value for CPL_VSIL_CURL_CHUNK_SIZE. "
158 : "Using default value of %d instead.",
159 : DOWNLOAD_CHUNK_SIZE_DEFAULT);
160 : }
161 : }
162 :
163 1105 : constexpr int MIN_CHUNK_SIZE = 1024;
164 1105 : constexpr int MAX_CHUNK_SIZE = 10 * 1024 * 1024;
165 1105 : if (nChunkSize < MIN_CHUNK_SIZE || nChunkSize > MAX_CHUNK_SIZE)
166 : {
167 0 : nChunkSize = DOWNLOAD_CHUNK_SIZE_DEFAULT;
168 0 : CPLError(CE_Warning, CPLE_AppDefined,
169 : "Invalid value for CPL_VSIL_CURL_CHUNK_SIZE. "
170 : "Allowed range is [%d, %d]. "
171 : "Using CPL_VSIL_CURL_CHUNK_SIZE=%d instead",
172 : MIN_CHUNK_SIZE, MAX_CHUNK_SIZE,
173 : DOWNLOAD_CHUNK_SIZE_DEFAULT);
174 : }
175 1105 : DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY =
176 : static_cast<int>(nChunkSize);
177 :
178 1105 : constexpr int N_MAX_REGIONS_DEFAULT = 1000;
179 1105 : constexpr int CACHE_SIZE_DEFAULT =
180 : N_MAX_REGIONS_DEFAULT * DOWNLOAD_CHUNK_SIZE_DEFAULT;
181 :
182 : const char *pszCacheSize =
183 1105 : CPLGetConfigOption("CPL_VSIL_CURL_CACHE_SIZE", nullptr);
184 1105 : GIntBig nCacheSize = CACHE_SIZE_DEFAULT;
185 :
186 1105 : if (pszCacheSize)
187 : {
188 0 : if (CPLParseMemorySize(pszCacheSize, &nCacheSize, nullptr) !=
189 : CE_None)
190 : {
191 0 : CPLError(
192 : CE_Warning, CPLE_AppDefined,
193 : "Could not parse value for CPL_VSIL_CURL_CACHE_SIZE. "
194 : "Using default value of " CPL_FRMT_GIB " instead.",
195 : nCacheSize);
196 : }
197 : }
198 :
199 1105 : const auto nMaxRAM = CPLGetUsablePhysicalRAM();
200 1105 : const auto nMinVal = DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
201 1105 : auto nMaxVal = static_cast<GIntBig>(INT_MAX) *
202 1105 : DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
203 1105 : if (nMaxRAM > 0 && nMaxVal > nMaxRAM)
204 1105 : nMaxVal = nMaxRAM;
205 1105 : if (nCacheSize < nMinVal || nCacheSize > nMaxVal)
206 : {
207 0 : nCacheSize = nCacheSize < nMinVal ? nMinVal : nMaxVal;
208 0 : CPLError(CE_Warning, CPLE_AppDefined,
209 : "Invalid value for CPL_VSIL_CURL_CACHE_SIZE. "
210 : "Allowed range is [%d, " CPL_FRMT_GIB "]. "
211 : "Using CPL_VSIL_CURL_CACHE_SIZE=" CPL_FRMT_GIB
212 : " instead",
213 : nMinVal, nMaxVal, nCacheSize);
214 : }
215 1105 : N_MAX_REGIONS_DO_NOT_USE_DIRECTLY = std::max(
216 2210 : 1, static_cast<int>(nCacheSize /
217 1105 : DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY));
218 1105 : }
219 : };
220 :
221 471553 : static Initializer initializer;
222 471553 : }
223 :
224 : /************************************************************************/
225 : /* VSICURLGetDownloadChunkSize() */
226 : /************************************************************************/
227 :
228 314555 : int VSICURLGetDownloadChunkSize()
229 : {
230 314555 : VSICURLReadGlobalEnvVariables();
231 314555 : return DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
232 : }
233 :
234 : /************************************************************************/
235 : /* GetMaxRegions() */
236 : /************************************************************************/
237 :
238 156998 : static int GetMaxRegions()
239 : {
240 156998 : VSICURLReadGlobalEnvVariables();
241 156998 : return N_MAX_REGIONS_DO_NOT_USE_DIRECTLY;
242 : }
243 :
244 : /************************************************************************/
245 : /* VSICurlFindStringSensitiveExceptEscapeSequences() */
246 : /************************************************************************/
247 :
248 : static int
249 149 : VSICurlFindStringSensitiveExceptEscapeSequences(CSLConstList papszList,
250 : const char *pszTarget)
251 :
252 : {
253 149 : if (papszList == nullptr)
254 116 : return -1;
255 :
256 72 : for (int i = 0; papszList[i] != nullptr; i++)
257 : {
258 60 : const char *pszIter1 = papszList[i];
259 60 : const char *pszIter2 = pszTarget;
260 60 : char ch1 = '\0';
261 60 : char ch2 = '\0';
262 : /* The comparison is case-sensitive, except for escaped */
263 : /* sequences where letters of the hexadecimal sequence */
264 : /* can be uppercase or lowercase depending on the quoting algorithm */
265 : while (true)
266 : {
267 864 : ch1 = *pszIter1;
268 864 : ch2 = *pszIter2;
269 864 : if (ch1 == '\0' || ch2 == '\0')
270 : break;
271 841 : if (ch1 == '%' && ch2 == '%' && pszIter1[1] != '\0' &&
272 0 : pszIter1[2] != '\0' && pszIter2[1] != '\0' &&
273 0 : pszIter2[2] != '\0')
274 : {
275 0 : if (!EQUALN(pszIter1 + 1, pszIter2 + 1, 2))
276 0 : break;
277 0 : pszIter1 += 2;
278 0 : pszIter2 += 2;
279 : }
280 841 : if (ch1 != ch2)
281 37 : break;
282 804 : pszIter1++;
283 804 : pszIter2++;
284 : }
285 60 : if (ch1 == ch2 && ch1 == '\0')
286 21 : return i;
287 : }
288 :
289 12 : return -1;
290 : }
291 :
292 : /************************************************************************/
293 : /* VSICurlIsFileInList() */
294 : /************************************************************************/
295 :
296 141 : static int VSICurlIsFileInList(CSLConstList papszList, const char *pszTarget)
297 : {
298 : int nRet =
299 141 : VSICurlFindStringSensitiveExceptEscapeSequences(papszList, pszTarget);
300 141 : if (nRet >= 0)
301 21 : return nRet;
302 :
303 : // If we didn't find anything, try to URL-escape the target filename.
304 120 : char *pszEscaped = CPLEscapeString(pszTarget, -1, CPLES_URL);
305 120 : if (strcmp(pszTarget, pszEscaped) != 0)
306 : {
307 8 : nRet = VSICurlFindStringSensitiveExceptEscapeSequences(papszList,
308 : pszEscaped);
309 : }
310 120 : CPLFree(pszEscaped);
311 120 : return nRet;
312 : }
313 :
314 : /************************************************************************/
315 : /* StartsWithVSICurlPrefix() */
316 : /************************************************************************/
317 :
318 10405 : static bool StartsWithVSICurlPrefix(const char *pszFilename)
319 : {
320 23546 : for (const char *pszPrefix : VSICURL_PREFIXES)
321 : {
322 17125 : if (STARTS_WITH(pszFilename, pszPrefix))
323 : {
324 3984 : return true;
325 : }
326 : }
327 6421 : return false;
328 : }
329 :
330 : /************************************************************************/
331 : /* VSICurlGetURLFromFilename() */
332 : /************************************************************************/
333 :
334 3454 : static std::string VSICurlGetURLFromFilename(
335 : const char *pszFilename, CPLHTTPRetryParameters *poRetryParameters,
336 : bool *pbUseHead, bool *pbUseRedirectURLIfNoQueryStringParams,
337 : bool *pbListDir, bool *pbEmptyDir, CPLStringList *paosHTTPOptions,
338 : bool *pbPlanetaryComputerURLSigning, char **ppszPlanetaryComputerCollection)
339 : {
340 3454 : if (ppszPlanetaryComputerCollection)
341 1526 : *ppszPlanetaryComputerCollection = nullptr;
342 :
343 3454 : if (!StartsWithVSICurlPrefix(pszFilename))
344 221 : return pszFilename;
345 :
346 3233 : if (pbPlanetaryComputerURLSigning)
347 : {
348 : // It may be more convenient sometimes to store Planetary Computer URL
349 : // signing as a per-path specific option rather than capturing it in
350 : // the filename with the &pc_url_signing=yes option.
351 1526 : if (CPLTestBool(VSIGetPathSpecificOption(
352 : pszFilename, "VSICURL_PC_URL_SIGNING", "FALSE")))
353 : {
354 1 : *pbPlanetaryComputerURLSigning = true;
355 : }
356 : }
357 :
358 3233 : pszFilename += strlen("/vsicurl/");
359 3233 : if (!STARTS_WITH(pszFilename, "http://") &&
360 2579 : !STARTS_WITH(pszFilename, "https://") &&
361 124 : !STARTS_WITH(pszFilename, "ftp://") &&
362 124 : !STARTS_WITH(pszFilename, "file://"))
363 : {
364 124 : if (*pszFilename == '?')
365 0 : pszFilename++;
366 124 : char **papszTokens = CSLTokenizeString2(pszFilename, "&", 0);
367 436 : for (int i = 0; papszTokens[i] != nullptr; i++)
368 : {
369 : char *pszUnescaped =
370 312 : CPLUnescapeString(papszTokens[i], nullptr, CPLES_URL);
371 312 : CPLFree(papszTokens[i]);
372 312 : papszTokens[i] = pszUnescaped;
373 : }
374 :
375 248 : std::string osURL;
376 248 : std::string osHeaders;
377 436 : for (int i = 0; papszTokens[i]; i++)
378 : {
379 312 : char *pszKey = nullptr;
380 312 : const char *pszValue = CPLParseNameValue(papszTokens[i], &pszKey);
381 312 : if (pszKey && pszValue)
382 : {
383 312 : if (EQUAL(pszKey, "max_retry"))
384 : {
385 36 : if (poRetryParameters)
386 13 : poRetryParameters->nMaxRetry = atoi(pszValue);
387 : }
388 276 : else if (EQUAL(pszKey, "retry_delay"))
389 : {
390 16 : if (poRetryParameters)
391 4 : poRetryParameters->dfInitialDelay = CPLAtof(pszValue);
392 : }
393 260 : else if (EQUAL(pszKey, "retry_codes"))
394 : {
395 4 : if (poRetryParameters)
396 1 : poRetryParameters->osRetryCodes = pszValue;
397 : }
398 256 : else if (EQUAL(pszKey, "use_head"))
399 : {
400 24 : if (pbUseHead)
401 11 : *pbUseHead = CPLTestBool(pszValue);
402 : }
403 232 : else if (EQUAL(pszKey,
404 : "use_redirect_url_if_no_query_string_params"))
405 : {
406 : /* Undocumented. Used by PLScenes driver */
407 20 : if (pbUseRedirectURLIfNoQueryStringParams)
408 9 : *pbUseRedirectURLIfNoQueryStringParams =
409 9 : CPLTestBool(pszValue);
410 : }
411 212 : else if (EQUAL(pszKey, "list_dir"))
412 : {
413 0 : if (pbListDir)
414 0 : *pbListDir = CPLTestBool(pszValue);
415 : }
416 212 : else if (EQUAL(pszKey, "empty_dir"))
417 : {
418 20 : if (pbEmptyDir)
419 10 : *pbEmptyDir = CPLTestBool(pszValue);
420 : }
421 192 : else if (EQUAL(pszKey, "header_file"))
422 : {
423 : #if defined(CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED)
424 : constexpr bool CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED =
425 : true;
426 : #else
427 34 : constexpr bool CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED =
428 : false;
429 : #endif
430 : if (CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED)
431 : {
432 : CPLError(CE_Failure, CPLE_AppDefined,
433 : "Use of 'header_file' key-value pair in "
434 : "/vsicurl? is disabled in this build");
435 : }
436 : else
437 : {
438 34 : bool bSetValue = false;
439 34 : const char *pszAllowHeaderFileKVP = CPLGetConfigOption(
440 : "CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED", nullptr);
441 34 : if (!pszAllowHeaderFileKVP ||
442 24 : pszAllowHeaderFileKVP[0] == 0 ||
443 24 : EQUAL(pszAllowHeaderFileKVP, "ONLY_IN_TEMP"))
444 : {
445 18 : if (STARTS_WITH(pszValue, "/vsimem/"))
446 : {
447 6 : bSetValue = !CPLHasUnbalancedPathTraversal(
448 : pszValue + strlen("/vsimem/"));
449 : }
450 12 : else if (STARTS_WITH(pszValue, "/tmp/"))
451 : {
452 4 : bSetValue = !CPLHasUnbalancedPathTraversal(
453 : pszValue + strlen("/tmp/"));
454 : }
455 : else
456 : {
457 16 : for (const char *pszEnvVar : {"TEMP", "TMP"})
458 : {
459 12 : if (const char *pszTemp =
460 12 : CPLGetConfigOption(pszEnvVar,
461 : nullptr))
462 : {
463 4 : std::string osTemp = pszTemp;
464 8 : if (!osTemp.empty() &&
465 4 : (osTemp.back() == '/' ||
466 4 : osTemp.back() == '\\'))
467 0 : osTemp.pop_back();
468 4 : if (!osTemp.empty() &&
469 4 : cpl::starts_with(
470 4 : std::string_view(pszValue),
471 8 : osTemp) &&
472 4 : (pszValue[osTemp.size()] == '/' ||
473 0 : pszValue[osTemp.size()] == '\\'))
474 : {
475 4 : bSetValue =
476 4 : !CPLHasUnbalancedPathTraversal(
477 4 : pszValue + osTemp.size());
478 4 : break;
479 : }
480 : }
481 : }
482 : }
483 18 : if (!bSetValue)
484 : {
485 6 : CPLError(CE_Failure, CPLE_AppDefined,
486 : "Use of 'header_file=%s' "
487 : "key-value pair in /vsicurl? is "
488 : "disabled because it refers to a "
489 : "file stored in a non-temporary "
490 : "location. You may set the "
491 : "CPL_VSIL_CURL_HEADER_FILE_KVP_"
492 : "ENABLED configuration option to "
493 : "YES to remove that restriction.",
494 : pszValue);
495 18 : }
496 : }
497 16 : else if (CPLTestBool(pszAllowHeaderFileKVP))
498 : {
499 8 : bSetValue = true;
500 : }
501 : else
502 : {
503 8 : CPLError(CE_Failure, CPLE_AppDefined,
504 : "Use of 'header_file' key-value pair in "
505 : "/vsicurl? is disabled by the "
506 : "CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED "
507 : "configuration option");
508 : }
509 :
510 34 : if (bSetValue && paosHTTPOptions)
511 : {
512 10 : paosHTTPOptions->SetNameValue(pszKey, pszValue);
513 : }
514 : }
515 : }
516 158 : else if (EQUAL(pszKey, "useragent") ||
517 158 : EQUAL(pszKey, "referer") || EQUAL(pszKey, "cookie") ||
518 158 : EQUAL(pszKey, "unsafessl") ||
519 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
520 158 : EQUAL(pszKey, "timeout") ||
521 158 : EQUAL(pszKey, "connecttimeout") ||
522 : #endif
523 158 : EQUAL(pszKey, "low_speed_time") ||
524 158 : EQUAL(pszKey, "low_speed_limit") ||
525 158 : EQUAL(pszKey, "proxy") || EQUAL(pszKey, "proxyauth") ||
526 158 : EQUAL(pszKey, "proxyuserpwd"))
527 : {
528 : // Above names are the ones supported by
529 : // CPLHTTPSetOptions()
530 0 : if (paosHTTPOptions)
531 : {
532 0 : paosHTTPOptions->SetNameValue(pszKey, pszValue);
533 : }
534 : }
535 158 : else if (EQUAL(pszKey, "url"))
536 : {
537 124 : osURL = pszValue;
538 : }
539 34 : else if (EQUAL(pszKey, "pc_url_signing"))
540 : {
541 20 : if (pbPlanetaryComputerURLSigning)
542 10 : *pbPlanetaryComputerURLSigning = CPLTestBool(pszValue);
543 : }
544 14 : else if (EQUAL(pszKey, "pc_collection"))
545 : {
546 10 : if (ppszPlanetaryComputerCollection)
547 : {
548 5 : CPLFree(*ppszPlanetaryComputerCollection);
549 5 : *ppszPlanetaryComputerCollection = CPLStrdup(pszValue);
550 : }
551 : }
552 4 : else if (STARTS_WITH(pszKey, "header."))
553 : {
554 4 : osHeaders += (pszKey + strlen("header."));
555 4 : osHeaders += ':';
556 4 : osHeaders += pszValue;
557 4 : osHeaders += "\r\n";
558 : }
559 : else
560 : {
561 0 : CPLError(CE_Warning, CPLE_NotSupported,
562 : "Unsupported option: %s", pszKey);
563 : }
564 : }
565 312 : CPLFree(pszKey);
566 : }
567 :
568 124 : if (paosHTTPOptions && !osHeaders.empty())
569 1 : paosHTTPOptions->SetNameValue("HEADERS", osHeaders.c_str());
570 :
571 124 : CSLDestroy(papszTokens);
572 124 : if (osURL.empty())
573 : {
574 0 : CPLError(CE_Failure, CPLE_IllegalArg, "Missing url parameter");
575 0 : return pszFilename;
576 : }
577 :
578 124 : return osURL;
579 : }
580 :
581 3109 : return pszFilename;
582 : }
583 :
584 : namespace cpl
585 : {
586 :
587 : /************************************************************************/
588 : /* VSICurlHandle() */
589 : /************************************************************************/
590 :
591 1896 : VSICurlHandle::VSICurlHandle(VSICurlFilesystemHandlerBase *poFSIn,
592 1896 : const char *pszFilename, const char *pszURLIn)
593 : : poFS(poFSIn), m_osFilename(pszFilename),
594 : m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
595 1896 : m_oRetryParameters(m_aosHTTPOptions),
596 : m_bUseHead(
597 1896 : CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_USE_HEAD", "YES")))
598 : {
599 1896 : if (pszURLIn)
600 : {
601 370 : m_pszURL = CPLStrdup(pszURLIn);
602 : }
603 : else
604 : {
605 1526 : char *pszPCCollection = nullptr;
606 1526 : m_pszURL =
607 1526 : CPLStrdup(VSICurlGetURLFromFilename(
608 : pszFilename, &m_oRetryParameters, &m_bUseHead,
609 : &m_bUseRedirectURLIfNoQueryStringParams, nullptr,
610 : nullptr, &m_aosHTTPOptions,
611 : &m_bPlanetaryComputerURLSigning, &pszPCCollection)
612 : .c_str());
613 1526 : if (pszPCCollection)
614 5 : m_osPlanetaryComputerCollection = pszPCCollection;
615 1526 : CPLFree(pszPCCollection);
616 : }
617 :
618 1896 : m_bCached = poFSIn->AllowCachedDataFor(pszFilename);
619 1896 : poFS->GetCachedFileProp(m_pszURL, oFileProp);
620 1896 : }
621 :
622 : /************************************************************************/
623 : /* ~VSICurlHandle() */
624 : /************************************************************************/
625 :
626 3422 : VSICurlHandle::~VSICurlHandle()
627 : {
628 1896 : if (m_oThreadAdviseRead.joinable())
629 : {
630 5 : m_oThreadAdviseRead.join();
631 : }
632 1896 : if (m_hCurlMultiHandleForAdviseRead)
633 : {
634 5 : VSICURLMultiCleanup(m_hCurlMultiHandleForAdviseRead);
635 : }
636 :
637 1896 : if (!m_bCached)
638 : {
639 62 : poFS->InvalidateCachedData(m_pszURL);
640 62 : poFS->InvalidateDirContent(CPLGetDirnameSafe(m_osFilename.c_str()));
641 : }
642 1896 : CPLFree(m_pszURL);
643 3422 : }
644 :
645 : /************************************************************************/
646 : /* SetURL() */
647 : /************************************************************************/
648 :
649 10 : void VSICurlHandle::SetURL(const char *pszURLIn)
650 : {
651 10 : CPLFree(m_pszURL);
652 10 : m_pszURL = CPLStrdup(pszURLIn);
653 10 : }
654 :
655 : /************************************************************************/
656 : /* InstallReadCbk() */
657 : /************************************************************************/
658 :
659 3 : int VSICurlHandle::InstallReadCbk(VSICurlReadCbkFunc pfnReadCbkIn,
660 : void *pfnUserDataIn,
661 : int bStopOnInterruptUntilUninstallIn)
662 : {
663 3 : if (pfnReadCbk != nullptr)
664 0 : return FALSE;
665 :
666 3 : pfnReadCbk = pfnReadCbkIn;
667 3 : pReadCbkUserData = pfnUserDataIn;
668 3 : bStopOnInterruptUntilUninstall =
669 3 : CPL_TO_BOOL(bStopOnInterruptUntilUninstallIn);
670 3 : bInterrupted = false;
671 3 : return TRUE;
672 : }
673 :
674 : /************************************************************************/
675 : /* UninstallReadCbk() */
676 : /************************************************************************/
677 :
678 3 : int VSICurlHandle::UninstallReadCbk()
679 : {
680 3 : if (pfnReadCbk == nullptr)
681 0 : return FALSE;
682 :
683 3 : pfnReadCbk = nullptr;
684 3 : pReadCbkUserData = nullptr;
685 3 : bStopOnInterruptUntilUninstall = false;
686 3 : bInterrupted = false;
687 3 : return TRUE;
688 : }
689 :
690 : /************************************************************************/
691 : /* Seek() */
692 : /************************************************************************/
693 :
694 22241 : int VSICurlHandle::Seek(vsi_l_offset nOffset, int nWhence)
695 : {
696 22241 : if (nWhence == SEEK_SET)
697 : {
698 16938 : curOffset = nOffset;
699 : }
700 5303 : else if (nWhence == SEEK_CUR)
701 : {
702 4559 : curOffset = curOffset + nOffset;
703 : }
704 : else
705 : {
706 744 : curOffset = GetFileSize(false) + nOffset;
707 : }
708 22241 : bEOF = false;
709 22241 : return 0;
710 : }
711 :
712 : } // namespace cpl
713 :
714 : /************************************************************************/
715 : /* VSICurlGetTimeStampFromRFC822DateTime() */
716 : /************************************************************************/
717 :
718 1125 : static GIntBig VSICurlGetTimeStampFromRFC822DateTime(const char *pszDT)
719 : {
720 : // Sun, 03 Apr 2016 12:07:27 GMT
721 1125 : if (strlen(pszDT) >= 5 && pszDT[3] == ',' && pszDT[4] == ' ')
722 1125 : pszDT += 5;
723 1125 : int nDay = 0;
724 1125 : int nYear = 0;
725 1125 : int nHour = 0;
726 1125 : int nMinute = 0;
727 1125 : int nSecond = 0;
728 1125 : char szMonth[4] = {};
729 1125 : szMonth[3] = 0;
730 1125 : if (sscanf(pszDT, "%02d %03s %04d %02d:%02d:%02d GMT", &nDay, szMonth,
731 1125 : &nYear, &nHour, &nMinute, &nSecond) == 6)
732 : {
733 : static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr",
734 : "May", "Jun", "Jul", "Aug",
735 : "Sep", "Oct", "Nov", "Dec"};
736 :
737 1125 : int nMonthIdx0 = -1;
738 6730 : for (int i = 0; i < 12; i++)
739 : {
740 6730 : if (EQUAL(szMonth, aszMonthStr[i]))
741 : {
742 1125 : nMonthIdx0 = i;
743 1125 : break;
744 : }
745 : }
746 1125 : if (nMonthIdx0 >= 0)
747 : {
748 : struct tm brokendowntime;
749 1125 : brokendowntime.tm_year = nYear - 1900;
750 1125 : brokendowntime.tm_mon = nMonthIdx0;
751 1125 : brokendowntime.tm_mday = nDay;
752 1125 : brokendowntime.tm_hour = nHour;
753 1125 : brokendowntime.tm_min = nMinute;
754 1125 : brokendowntime.tm_sec = nSecond;
755 1125 : return CPLYMDHMSToUnixTime(&brokendowntime);
756 : }
757 : }
758 0 : return 0;
759 : }
760 :
761 : /************************************************************************/
762 : /* VSICURLInitWriteFuncStruct() */
763 : /************************************************************************/
764 :
765 2883 : void VSICURLInitWriteFuncStruct(cpl::WriteFuncStruct *psStruct, VSILFILE *fp,
766 : VSICurlReadCbkFunc pfnReadCbk,
767 : void *pReadCbkUserData)
768 : {
769 2883 : psStruct->pBuffer = nullptr;
770 2883 : psStruct->nSize = 0;
771 2883 : psStruct->bIsHTTP = false;
772 2883 : psStruct->bMultiRange = false;
773 2883 : psStruct->nStartOffset = 0;
774 2883 : psStruct->nEndOffset = 0;
775 2883 : psStruct->nHTTPCode = 0;
776 2883 : psStruct->nFirstHTTPCode = 0;
777 2883 : psStruct->nContentLength = 0;
778 2883 : psStruct->bFoundContentRange = false;
779 2883 : psStruct->bError = false;
780 2883 : psStruct->bDetectRangeDownloadingError = true;
781 2883 : psStruct->nTimestampDate = 0;
782 :
783 2883 : psStruct->fp = fp;
784 2883 : psStruct->pfnReadCbk = pfnReadCbk;
785 2883 : psStruct->pReadCbkUserData = pReadCbkUserData;
786 2883 : psStruct->bInterrupted = false;
787 2883 : }
788 :
789 : /************************************************************************/
790 : /* VSICurlHandleWriteFunc() */
791 : /************************************************************************/
792 :
793 29573 : size_t VSICurlHandleWriteFunc(void *buffer, size_t count, size_t nmemb,
794 : void *req)
795 : {
796 29573 : cpl::WriteFuncStruct *psStruct = static_cast<cpl::WriteFuncStruct *>(req);
797 29573 : const size_t nSize = count * nmemb;
798 :
799 29573 : if (psStruct->bInterrupted)
800 : {
801 9 : return 0;
802 : }
803 :
804 : char *pNewBuffer = static_cast<char *>(
805 29564 : VSIRealloc(psStruct->pBuffer, psStruct->nSize + nSize + 1));
806 29564 : if (pNewBuffer)
807 : {
808 29564 : psStruct->pBuffer = pNewBuffer;
809 29564 : memcpy(psStruct->pBuffer + psStruct->nSize, buffer, nSize);
810 29564 : psStruct->pBuffer[psStruct->nSize + nSize] = '\0';
811 29564 : if (psStruct->bIsHTTP)
812 : {
813 12673 : char *pszLine = psStruct->pBuffer + psStruct->nSize;
814 12673 : if (STARTS_WITH_CI(pszLine, "HTTP/"))
815 : {
816 1126 : char *pszSpace = strchr(pszLine, ' ');
817 1126 : if (pszSpace)
818 : {
819 1126 : const int nHTTPCode = atoi(pszSpace + 1);
820 1126 : if (psStruct->nFirstHTTPCode == 0)
821 994 : psStruct->nFirstHTTPCode = nHTTPCode;
822 1126 : psStruct->nHTTPCode = nHTTPCode;
823 : }
824 : }
825 11547 : else if (STARTS_WITH_CI(pszLine, "Content-Length: "))
826 : {
827 1023 : psStruct->nContentLength = CPLScanUIntBig(
828 1023 : pszLine + 16, static_cast<int>(strlen(pszLine + 16)));
829 : }
830 10524 : else if (STARTS_WITH_CI(pszLine, "Content-Range: "))
831 : {
832 363 : psStruct->bFoundContentRange = true;
833 : }
834 10161 : else if (STARTS_WITH_CI(pszLine, "Date: "))
835 : {
836 1125 : CPLString osDate = pszLine + strlen("Date: ");
837 1125 : size_t nSizeLine = osDate.size();
838 5625 : while (nSizeLine && (osDate[nSizeLine - 1] == '\r' ||
839 2250 : osDate[nSizeLine - 1] == '\n'))
840 : {
841 2250 : osDate.resize(nSizeLine - 1);
842 2250 : nSizeLine--;
843 : }
844 1125 : osDate.Trim();
845 :
846 : GIntBig nTimestampDate =
847 1125 : VSICurlGetTimeStampFromRFC822DateTime(osDate.c_str());
848 : #if DEBUG_VERBOSE
849 : CPLDebug("VSICURL", "Timestamp = " CPL_FRMT_GIB,
850 : nTimestampDate);
851 : #endif
852 1125 : psStruct->nTimestampDate = nTimestampDate;
853 : }
854 : /*if( nSize > 2 && pszLine[nSize - 2] == '\r' &&
855 : pszLine[nSize - 1] == '\n' )
856 : {
857 : pszLine[nSize - 2] = 0;
858 : CPLDebug("VSICURL", "%s", pszLine);
859 : pszLine[nSize - 2] = '\r';
860 : }*/
861 :
862 12673 : if (pszLine[0] == '\r' && pszLine[1] == '\n')
863 : {
864 : // Detect servers that don't support range downloading.
865 1126 : if (psStruct->nHTTPCode == 200 &&
866 387 : psStruct->bDetectRangeDownloadingError &&
867 148 : !psStruct->bMultiRange && !psStruct->bFoundContentRange &&
868 138 : (psStruct->nStartOffset != 0 ||
869 138 : psStruct->nContentLength >
870 138 : 10 * (psStruct->nEndOffset - psStruct->nStartOffset +
871 : 1)))
872 : {
873 0 : CPLError(CE_Failure, CPLE_AppDefined,
874 : "Range downloading not supported by this "
875 : "server!");
876 0 : psStruct->bError = true;
877 0 : return 0;
878 : }
879 : }
880 : }
881 : else
882 : {
883 16891 : if (psStruct->pfnReadCbk)
884 : {
885 1 : if (!psStruct->pfnReadCbk(psStruct->fp, buffer, nSize,
886 : psStruct->pReadCbkUserData))
887 : {
888 0 : psStruct->bInterrupted = true;
889 0 : return 0;
890 : }
891 : }
892 : }
893 29564 : psStruct->nSize += nSize;
894 29564 : return nmemb;
895 : }
896 : else
897 : {
898 0 : return 0;
899 : }
900 : }
901 :
902 : /************************************************************************/
903 : /* VSICurlIsS3LikeSignedURL() */
904 : /************************************************************************/
905 :
906 497 : static bool VSICurlIsS3LikeSignedURL(const char *pszURL)
907 : {
908 990 : return ((strstr(pszURL, ".s3.amazonaws.com/") != nullptr ||
909 493 : strstr(pszURL, ".s3.amazonaws.com:") != nullptr ||
910 493 : strstr(pszURL, ".storage.googleapis.com/") != nullptr ||
911 493 : strstr(pszURL, ".storage.googleapis.com:") != nullptr ||
912 493 : strstr(pszURL, ".cloudfront.net/") != nullptr ||
913 493 : strstr(pszURL, ".cloudfront.net:") != nullptr) &&
914 4 : (strstr(pszURL, "&Signature=") != nullptr ||
915 4 : strstr(pszURL, "?Signature=") != nullptr)) ||
916 1488 : strstr(pszURL, "&X-Amz-Signature=") != nullptr ||
917 991 : strstr(pszURL, "?X-Amz-Signature=") != nullptr;
918 : }
919 :
920 : /************************************************************************/
921 : /* VSICurlGetExpiresFromS3LikeSignedURL() */
922 : /************************************************************************/
923 :
924 5 : static GIntBig VSICurlGetExpiresFromS3LikeSignedURL(const char *pszURL)
925 : {
926 25 : const auto GetParamValue = [pszURL](const char *pszKey) -> const char *
927 : {
928 17 : for (const char *pszPrefix : {"&", "?"})
929 : {
930 14 : std::string osNeedle(pszPrefix);
931 14 : osNeedle += pszKey;
932 14 : osNeedle += '=';
933 14 : const char *pszStr = strstr(pszURL, osNeedle.c_str());
934 14 : if (pszStr)
935 8 : return pszStr + osNeedle.size();
936 : }
937 3 : return nullptr;
938 5 : };
939 :
940 : {
941 : // Expires= is a Unix timestamp
942 5 : const char *pszExpires = GetParamValue("Expires");
943 5 : if (pszExpires != nullptr)
944 2 : return CPLAtoGIntBig(pszExpires);
945 : }
946 :
947 : // X-Amz-Expires= is a delay, to be combined with X-Amz-Date=
948 3 : const char *pszAmzExpires = GetParamValue("X-Amz-Expires");
949 3 : if (pszAmzExpires == nullptr)
950 0 : return 0;
951 3 : const int nDelay = atoi(pszAmzExpires);
952 :
953 3 : const char *pszAmzDate = GetParamValue("X-Amz-Date");
954 3 : if (pszAmzDate == nullptr)
955 0 : return 0;
956 : // pszAmzDate should be YYYYMMDDTHHMMSSZ
957 3 : if (strlen(pszAmzDate) < strlen("YYYYMMDDTHHMMSSZ"))
958 0 : return 0;
959 3 : if (pszAmzDate[strlen("YYYYMMDDTHHMMSSZ") - 1] != 'Z')
960 0 : return 0;
961 : struct tm brokendowntime;
962 3 : brokendowntime.tm_year =
963 3 : atoi(std::string(pszAmzDate).substr(0, 4).c_str()) - 1900;
964 3 : brokendowntime.tm_mon =
965 3 : atoi(std::string(pszAmzDate).substr(4, 2).c_str()) - 1;
966 3 : brokendowntime.tm_mday = atoi(std::string(pszAmzDate).substr(6, 2).c_str());
967 3 : brokendowntime.tm_hour = atoi(std::string(pszAmzDate).substr(9, 2).c_str());
968 3 : brokendowntime.tm_min = atoi(std::string(pszAmzDate).substr(11, 2).c_str());
969 3 : brokendowntime.tm_sec = atoi(std::string(pszAmzDate).substr(13, 2).c_str());
970 3 : return CPLYMDHMSToUnixTime(&brokendowntime) + nDelay;
971 : }
972 :
973 : /************************************************************************/
974 : /* VSICURLMultiPerform() */
975 : /************************************************************************/
976 :
977 1465 : void VSICURLMultiPerform(CURLM *hCurlMultiHandle, CURL *hEasyHandle,
978 : std::atomic<bool> *pbInterrupt)
979 : {
980 1465 : int repeats = 0;
981 :
982 1465 : if (hEasyHandle)
983 1461 : curl_multi_add_handle(hCurlMultiHandle, hEasyHandle);
984 :
985 1465 : void *old_handler = CPLHTTPIgnoreSigPipe();
986 : while (true)
987 : {
988 : int still_running;
989 14002 : while (curl_multi_perform(hCurlMultiHandle, &still_running) ==
990 : CURLM_CALL_MULTI_PERFORM)
991 : {
992 : // loop
993 : }
994 14002 : if (!still_running)
995 : {
996 1465 : break;
997 : }
998 :
999 : #ifdef undef
1000 : CURLMsg *msg;
1001 : do
1002 : {
1003 : int msgq = 0;
1004 : msg = curl_multi_info_read(hCurlMultiHandle, &msgq);
1005 : if (msg && (msg->msg == CURLMSG_DONE))
1006 : {
1007 : CURL *e = msg->easy_handle;
1008 : }
1009 : } while (msg);
1010 : #endif
1011 :
1012 12537 : CPLMultiPerformWait(hCurlMultiHandle, repeats);
1013 :
1014 12537 : if (pbInterrupt && *pbInterrupt)
1015 0 : break;
1016 12537 : }
1017 1465 : CPLHTTPRestoreSigPipeHandler(old_handler);
1018 :
1019 1465 : if (hEasyHandle)
1020 1461 : curl_multi_remove_handle(hCurlMultiHandle, hEasyHandle);
1021 1465 : }
1022 :
1023 : /************************************************************************/
1024 : /* VSICurlDummyWriteFunc() */
1025 : /************************************************************************/
1026 :
1027 0 : static size_t VSICurlDummyWriteFunc(void *, size_t, size_t, void *)
1028 : {
1029 0 : return 0;
1030 : }
1031 :
1032 : /************************************************************************/
1033 : /* VSICURLResetHeaderAndWriterFunctions() */
1034 : /************************************************************************/
1035 :
1036 1420 : void VSICURLResetHeaderAndWriterFunctions(CURL *hCurlHandle)
1037 : {
1038 1420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
1039 : VSICurlDummyWriteFunc);
1040 1420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1041 : VSICurlDummyWriteFunc);
1042 1420 : }
1043 :
1044 : /************************************************************************/
1045 : /* Iso8601ToUnixTime() */
1046 : /************************************************************************/
1047 :
1048 6 : static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime)
1049 : {
1050 : int nYear;
1051 : int nMonth;
1052 : int nDay;
1053 : int nHour;
1054 : int nMinute;
1055 : int nSecond;
1056 6 : if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay,
1057 6 : &nHour, &nMinute, &nSecond) == 6)
1058 : {
1059 : struct tm brokendowntime;
1060 6 : brokendowntime.tm_year = nYear - 1900;
1061 6 : brokendowntime.tm_mon = nMonth - 1;
1062 6 : brokendowntime.tm_mday = nDay;
1063 6 : brokendowntime.tm_hour = nHour;
1064 6 : brokendowntime.tm_min = nMinute;
1065 6 : brokendowntime.tm_sec = nSecond;
1066 6 : *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
1067 6 : return true;
1068 : }
1069 0 : return false;
1070 : }
1071 :
1072 : namespace cpl
1073 : {
1074 :
1075 : /************************************************************************/
1076 : /* ManagePlanetaryComputerSigning() */
1077 : /************************************************************************/
1078 :
1079 11 : void VSICurlHandle::ManagePlanetaryComputerSigning() const
1080 : {
1081 : // Take global lock
1082 : static std::mutex goMutex;
1083 22 : std::lock_guard<std::mutex> oLock(goMutex);
1084 :
1085 : struct PCSigningInfo
1086 : {
1087 : std::string osQueryString{};
1088 : GIntBig nExpireTimestamp = 0;
1089 : };
1090 :
1091 22 : PCSigningInfo sSigningInfo;
1092 11 : constexpr int knExpirationDelayMargin = 60;
1093 :
1094 11 : if (!m_osPlanetaryComputerCollection.empty())
1095 : {
1096 : // key is the name of a collection
1097 5 : static lru11::Cache<std::string, PCSigningInfo> goCacheCollection{1024};
1098 :
1099 5 : if (goCacheCollection.tryGet(m_osPlanetaryComputerCollection,
1100 8 : sSigningInfo) &&
1101 3 : time(nullptr) + knExpirationDelayMargin <=
1102 3 : sSigningInfo.nExpireTimestamp)
1103 : {
1104 2 : m_osQueryString = sSigningInfo.osQueryString;
1105 : }
1106 : else
1107 : {
1108 : const auto psResult =
1109 9 : CPLHTTPFetch((std::string(CPLGetConfigOption(
1110 : "VSICURL_PC_SAS_TOKEN_URL",
1111 : "https://planetarycomputer.microsoft.com/api/"
1112 6 : "sas/v1/token/")) +
1113 3 : m_osPlanetaryComputerCollection)
1114 : .c_str(),
1115 : nullptr);
1116 3 : if (psResult)
1117 : {
1118 : const auto aosKeyVals = CPLParseKeyValueJson(
1119 6 : reinterpret_cast<const char *>(psResult->pabyData));
1120 3 : const char *pszToken = aosKeyVals.FetchNameValue("token");
1121 3 : if (pszToken)
1122 : {
1123 3 : m_osQueryString = '?';
1124 3 : m_osQueryString += pszToken;
1125 :
1126 3 : sSigningInfo.osQueryString = m_osQueryString;
1127 3 : sSigningInfo.nExpireTimestamp = 0;
1128 : const char *pszExpiry =
1129 3 : aosKeyVals.FetchNameValue("msft:expiry");
1130 3 : if (pszExpiry)
1131 : {
1132 3 : Iso8601ToUnixTime(pszExpiry,
1133 : &sSigningInfo.nExpireTimestamp);
1134 : }
1135 3 : goCacheCollection.insert(m_osPlanetaryComputerCollection,
1136 : sSigningInfo);
1137 :
1138 3 : CPLDebug("VSICURL", "Got token from Planetary Computer: %s",
1139 : m_osQueryString.c_str());
1140 : }
1141 3 : CPLHTTPDestroyResult(psResult);
1142 : }
1143 : }
1144 : }
1145 : else
1146 : {
1147 : // key is a URL
1148 6 : static lru11::Cache<std::string, PCSigningInfo> goCacheURL{1024};
1149 :
1150 10 : if (goCacheURL.tryGet(m_pszURL, sSigningInfo) &&
1151 4 : time(nullptr) + knExpirationDelayMargin <=
1152 4 : sSigningInfo.nExpireTimestamp)
1153 : {
1154 3 : m_osQueryString = sSigningInfo.osQueryString;
1155 : }
1156 : else
1157 : {
1158 : const auto psResult =
1159 9 : CPLHTTPFetch((std::string(CPLGetConfigOption(
1160 : "VSICURL_PC_SAS_SIGN_HREF_URL",
1161 : "https://planetarycomputer.microsoft.com/api/"
1162 6 : "sas/v1/sign?href=")) +
1163 3 : m_pszURL)
1164 : .c_str(),
1165 : nullptr);
1166 3 : if (psResult)
1167 : {
1168 : const auto aosKeyVals = CPLParseKeyValueJson(
1169 6 : reinterpret_cast<const char *>(psResult->pabyData));
1170 3 : const char *pszHref = aosKeyVals.FetchNameValue("href");
1171 3 : if (pszHref && STARTS_WITH(pszHref, m_pszURL))
1172 : {
1173 3 : m_osQueryString = pszHref + strlen(m_pszURL);
1174 :
1175 3 : sSigningInfo.osQueryString = m_osQueryString;
1176 3 : sSigningInfo.nExpireTimestamp = 0;
1177 : const char *pszExpiry =
1178 3 : aosKeyVals.FetchNameValue("msft:expiry");
1179 3 : if (pszExpiry)
1180 : {
1181 3 : Iso8601ToUnixTime(pszExpiry,
1182 : &sSigningInfo.nExpireTimestamp);
1183 : }
1184 3 : goCacheURL.insert(m_pszURL, sSigningInfo);
1185 :
1186 3 : CPLDebug("VSICURL",
1187 : "Got signature from Planetary Computer: %s",
1188 : m_osQueryString.c_str());
1189 : }
1190 3 : CPLHTTPDestroyResult(psResult);
1191 : }
1192 : }
1193 : }
1194 11 : }
1195 :
1196 : /************************************************************************/
1197 : /* UpdateQueryString() */
1198 : /************************************************************************/
1199 :
1200 970 : void VSICurlHandle::UpdateQueryString() const
1201 : {
1202 970 : if (m_bPlanetaryComputerURLSigning)
1203 : {
1204 11 : ManagePlanetaryComputerSigning();
1205 : }
1206 : else
1207 : {
1208 959 : const char *pszQueryString = VSIGetPathSpecificOption(
1209 : m_osFilename.c_str(), "VSICURL_QUERY_STRING", nullptr);
1210 959 : if (pszQueryString)
1211 : {
1212 4 : if (m_osFilename.back() == '?')
1213 : {
1214 2 : if (pszQueryString[0] == '?')
1215 1 : m_osQueryString = pszQueryString + 1;
1216 : else
1217 1 : m_osQueryString = pszQueryString;
1218 : }
1219 : else
1220 : {
1221 2 : if (pszQueryString[0] == '?')
1222 1 : m_osQueryString = pszQueryString;
1223 : else
1224 : {
1225 1 : m_osQueryString = "?";
1226 1 : m_osQueryString.append(pszQueryString);
1227 : }
1228 : }
1229 : }
1230 : }
1231 970 : }
1232 :
1233 : /************************************************************************/
1234 : /* GetFileSizeOrHeaders() */
1235 : /************************************************************************/
1236 :
1237 1916 : vsi_l_offset VSICurlHandle::GetFileSizeOrHeaders(bool bSetError,
1238 : bool bGetHeaders)
1239 : {
1240 1916 : if (oFileProp.bHasComputedFileSize && !bGetHeaders)
1241 1428 : return oFileProp.fileSize;
1242 :
1243 976 : NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
1244 976 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
1245 976 : NetworkStatisticsAction oContextAction("GetFileSize");
1246 :
1247 488 : oFileProp.bHasComputedFileSize = true;
1248 :
1249 488 : CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
1250 :
1251 488 : UpdateQueryString();
1252 :
1253 976 : std::string osURL(m_pszURL + m_osQueryString);
1254 488 : int nTryCount = 0;
1255 488 : bool bRetryWithGet = false;
1256 488 : bool bRetryWithLimitedRangeGet = false;
1257 488 : bool bS3LikeRedirect = false;
1258 976 : CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
1259 :
1260 499 : retry:
1261 499 : ++nTryCount;
1262 499 : CURL *hCurlHandle = curl_easy_init();
1263 :
1264 499 : struct curl_slist *headers = nullptr;
1265 499 : if (bS3LikeRedirect)
1266 : {
1267 : // Do not propagate authentication sent to the original URL to a S3-like
1268 : // redirect.
1269 2 : CPLStringList aosHTTPOptions{};
1270 4 : for (const auto &pszOption : m_aosHTTPOptions)
1271 : {
1272 2 : if (STARTS_WITH_CI(pszOption, "HTTPAUTH") ||
1273 1 : STARTS_WITH_CI(pszOption, "HTTP_BEARER"))
1274 2 : continue;
1275 0 : aosHTTPOptions.AddString(pszOption);
1276 : }
1277 2 : headers = VSICurlSetOptions(hCurlHandle, osURL.c_str(),
1278 2 : aosHTTPOptions.List());
1279 : }
1280 : else
1281 : {
1282 497 : headers = VSICurlSetOptions(hCurlHandle, osURL.c_str(),
1283 497 : m_aosHTTPOptions.List());
1284 : }
1285 :
1286 499 : WriteFuncStruct sWriteFuncHeaderData;
1287 499 : VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
1288 : nullptr);
1289 499 : sWriteFuncHeaderData.bDetectRangeDownloadingError = false;
1290 499 : sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(osURL.c_str(), "http");
1291 :
1292 499 : WriteFuncStruct sWriteFuncData;
1293 499 : VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
1294 :
1295 499 : std::string osVerb;
1296 499 : std::string osRange; // leave in this scope !
1297 499 : int nRoundedBufSize = 0;
1298 499 : const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
1299 499 : bool bHasUsedLimitedRangeGet = false;
1300 499 : if (bRetryWithLimitedRangeGet || UseLimitRangeGetInsteadOfHead())
1301 : {
1302 145 : bHasUsedLimitedRangeGet = true;
1303 145 : osVerb = "GET";
1304 : const int nBufSize = std::clamp(
1305 435 : atoi(CPLGetConfigOption("GDAL_INGESTED_BYTES_AT_OPEN", "1024")),
1306 145 : 1024, 10 * 1024 * 1024);
1307 145 : nRoundedBufSize = cpl::div_round_up(nBufSize, knDOWNLOAD_CHUNK_SIZE) *
1308 : knDOWNLOAD_CHUNK_SIZE;
1309 :
1310 : // so it gets included in Azure signature
1311 145 : osRange = CPLSPrintf("Range: bytes=0-%d", nRoundedBufSize - 1);
1312 145 : headers = curl_slist_append(headers, osRange.c_str());
1313 : }
1314 : // HACK for mbtiles driver: http://a.tiles.mapbox.com/v3/ doesn't accept
1315 : // HEAD, as it is a redirect to AWS S3 signed URL, but those are only valid
1316 : // for a given type of HTTP request, and thus GET. This is valid for any
1317 : // signed URL for AWS S3.
1318 700 : else if (bRetryWithGet ||
1319 691 : strstr(osURL.c_str(), ".tiles.mapbox.com/") != nullptr ||
1320 1045 : VSICurlIsS3LikeSignedURL(osURL.c_str()) || !m_bUseHead)
1321 : {
1322 14 : sWriteFuncData.bInterrupted = true;
1323 14 : osVerb = "GET";
1324 : }
1325 : else
1326 : {
1327 340 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
1328 340 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPGET, 0);
1329 340 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADER, 1);
1330 340 : osVerb = "HEAD";
1331 : }
1332 :
1333 499 : bRetryWithLimitedRangeGet = false;
1334 :
1335 499 : if (!AllowAutomaticRedirection())
1336 95 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
1337 :
1338 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
1339 : &sWriteFuncHeaderData);
1340 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
1341 : VSICurlHandleWriteFunc);
1342 :
1343 : // Bug with older curl versions (<=7.16.4) and FTP.
1344 : // See http://curl.haxx.se/mail/lib-2007-08/0312.html
1345 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
1346 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1347 : VSICurlHandleWriteFunc);
1348 :
1349 499 : char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
1350 499 : szCurlErrBuf[0] = '\0';
1351 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
1352 :
1353 499 : headers = GetCurlHeaders(osVerb, headers);
1354 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1355 :
1356 499 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FILETIME, 1);
1357 :
1358 499 : VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle, &m_bInterrupt);
1359 :
1360 499 : VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
1361 :
1362 499 : curl_slist_free_all(headers);
1363 :
1364 499 : oFileProp.eExists = EXIST_UNKNOWN;
1365 :
1366 499 : curl_off_t filetime = -1;
1367 499 : GIntBig mtime = 0;
1368 499 : if (curl_easy_getinfo(hCurlHandle, CURLINFO_FILETIME_T, &filetime) ==
1369 998 : CURLE_OK &&
1370 499 : filetime != -1)
1371 : {
1372 34 : mtime = static_cast<GIntBig>(filetime);
1373 : }
1374 :
1375 499 : if (osVerb == "GET")
1376 159 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
1377 : else
1378 340 : NetworkStatisticsLogger::LogHEAD();
1379 :
1380 499 : if (STARTS_WITH(osURL.c_str(), "ftp"))
1381 : {
1382 0 : if (sWriteFuncData.pBuffer != nullptr)
1383 : {
1384 : const char *pszContentLength =
1385 0 : strstr(const_cast<const char *>(sWriteFuncData.pBuffer),
1386 : "Content-Length: ");
1387 0 : if (pszContentLength)
1388 : {
1389 0 : pszContentLength += strlen("Content-Length: ");
1390 0 : oFileProp.eExists = EXIST_YES;
1391 0 : oFileProp.fileSize =
1392 0 : CPLScanUIntBig(pszContentLength,
1393 0 : static_cast<int>(strlen(pszContentLength)));
1394 : if constexpr (ENABLE_DEBUG)
1395 : {
1396 0 : CPLDebug(poFS->GetDebugKey(),
1397 : "GetFileSize(%s)=" CPL_FRMT_GUIB, osURL.c_str(),
1398 : oFileProp.fileSize);
1399 : }
1400 : }
1401 : }
1402 : }
1403 :
1404 499 : double dfSize = 0;
1405 499 : long response_code = -1;
1406 499 : if (oFileProp.eExists != EXIST_YES)
1407 : {
1408 499 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
1409 :
1410 499 : bool bAlreadyLogged = false;
1411 499 : if (response_code >= 400 && szCurlErrBuf[0] == '\0')
1412 : {
1413 : const bool bLogResponse =
1414 224 : CPLTestBool(CPLGetConfigOption("CPL_CURL_VERBOSE", "NO"));
1415 224 : if (bLogResponse && sWriteFuncData.pBuffer)
1416 : {
1417 0 : const char *pszErrorMsg =
1418 : static_cast<const char *>(sWriteFuncData.pBuffer);
1419 0 : bAlreadyLogged = true;
1420 0 : CPLDebug(
1421 0 : poFS->GetDebugKey(),
1422 : "GetFileSize(%s): response_code=%d, server error msg=%s",
1423 : osURL.c_str(), static_cast<int>(response_code),
1424 0 : pszErrorMsg[0] ? pszErrorMsg : "(no message provided)");
1425 224 : }
1426 : }
1427 275 : else if (szCurlErrBuf[0] != '\0')
1428 : {
1429 16 : bAlreadyLogged = true;
1430 16 : CPLDebug(poFS->GetDebugKey(),
1431 : "GetFileSize(%s): response_code=%d, curl error msg=%s",
1432 : osURL.c_str(), static_cast<int>(response_code),
1433 : szCurlErrBuf);
1434 : }
1435 :
1436 499 : std::string osEffectiveURL;
1437 : {
1438 499 : char *pszEffectiveURL = nullptr;
1439 499 : curl_easy_getinfo(hCurlHandle, CURLINFO_EFFECTIVE_URL,
1440 : &pszEffectiveURL);
1441 499 : if (pszEffectiveURL)
1442 499 : osEffectiveURL = pszEffectiveURL;
1443 : }
1444 :
1445 998 : if (!osEffectiveURL.empty() &&
1446 499 : strstr(osEffectiveURL.c_str(), osURL.c_str()) == nullptr)
1447 : {
1448 : // Moved permanently ?
1449 65 : if (sWriteFuncHeaderData.nFirstHTTPCode == 301 ||
1450 28 : (m_bUseRedirectURLIfNoQueryStringParams &&
1451 2 : osEffectiveURL.find('?') == std::string::npos))
1452 : {
1453 15 : CPLDebug(poFS->GetDebugKey(),
1454 : "Using effective URL %s permanently",
1455 : osEffectiveURL.c_str());
1456 15 : oFileProp.osRedirectURL = osEffectiveURL;
1457 15 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
1458 : }
1459 : else
1460 : {
1461 24 : CPLDebug(poFS->GetDebugKey(),
1462 : "Using effective URL %s temporarily",
1463 : osEffectiveURL.c_str());
1464 : }
1465 :
1466 : // Is this is a redirect to a S3 URL?
1467 42 : if (VSICurlIsS3LikeSignedURL(osEffectiveURL.c_str()) &&
1468 3 : !VSICurlIsS3LikeSignedURL(osURL.c_str()))
1469 : {
1470 : // Note that this is a redirect as we won't notice after the
1471 : // retry.
1472 3 : bS3LikeRedirect = true;
1473 :
1474 3 : if (!bRetryWithGet && osVerb == "HEAD" && response_code == 403)
1475 : {
1476 2 : CPLDebug(poFS->GetDebugKey(),
1477 : "Redirected to a AWS S3 signed URL. Retrying "
1478 : "with GET request instead of HEAD since the URL "
1479 : "might be valid only for GET");
1480 2 : bRetryWithGet = true;
1481 2 : osURL = std::move(osEffectiveURL);
1482 2 : CPLFree(sWriteFuncData.pBuffer);
1483 2 : CPLFree(sWriteFuncHeaderData.pBuffer);
1484 2 : curl_easy_cleanup(hCurlHandle);
1485 2 : goto retry;
1486 : }
1487 : }
1488 57 : else if (oFileProp.osRedirectURL.empty() && nTryCount == 1 &&
1489 42 : ((response_code >= 300 && response_code < 400) ||
1490 42 : (osVerb == "HEAD" && response_code == 403)))
1491 : {
1492 1 : if (response_code == 403)
1493 : {
1494 1 : CPLDebug(
1495 1 : poFS->GetDebugKey(),
1496 : "Retrying redirected URL with GET instead of HEAD");
1497 1 : bRetryWithGet = true;
1498 : }
1499 1 : osURL = std::move(osEffectiveURL);
1500 1 : CPLFree(sWriteFuncData.pBuffer);
1501 1 : CPLFree(sWriteFuncHeaderData.pBuffer);
1502 1 : curl_easy_cleanup(hCurlHandle);
1503 1 : goto retry;
1504 : }
1505 : }
1506 :
1507 3 : if (bS3LikeRedirect && response_code >= 200 && response_code < 300 &&
1508 3 : sWriteFuncHeaderData.nTimestampDate > 0 &&
1509 502 : !osEffectiveURL.empty() &&
1510 3 : CPLTestBool(
1511 : CPLGetConfigOption("CPL_VSIL_CURL_USE_S3_REDIRECT", "TRUE")))
1512 : {
1513 : const GIntBig nExpireTimestamp =
1514 3 : VSICurlGetExpiresFromS3LikeSignedURL(osEffectiveURL.c_str());
1515 3 : if (nExpireTimestamp > sWriteFuncHeaderData.nTimestampDate + 10)
1516 : {
1517 3 : const int nValidity = static_cast<int>(
1518 3 : nExpireTimestamp - sWriteFuncHeaderData.nTimestampDate);
1519 3 : CPLDebug(poFS->GetDebugKey(),
1520 : "Will use redirect URL for the next %d seconds",
1521 : nValidity);
1522 : // As our local clock might not be in sync with server clock,
1523 : // figure out the expiration timestamp in local time
1524 3 : oFileProp.bS3LikeRedirect = true;
1525 3 : oFileProp.nExpireTimestampLocal = time(nullptr) + nValidity;
1526 3 : oFileProp.osRedirectURL = osEffectiveURL;
1527 3 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
1528 : }
1529 : }
1530 :
1531 : // Split a string with the raw HTTP response headers as a key/value
1532 : // CPLStringList
1533 302 : const auto TokenizeHeaders = [](const char *pszHeaders) -> CPLStringList
1534 : {
1535 302 : CPLStringList aosHeaders;
1536 2405 : while (pszHeaders)
1537 : {
1538 2401 : const char *pszDelim = strchr(pszHeaders, ':');
1539 2401 : if (!pszDelim)
1540 298 : break;
1541 2103 : const char *pszValue = pszDelim + 1;
1542 :
1543 : // Skip whitespace after colon
1544 4206 : while (*pszValue == ' ' || *pszValue == '\t')
1545 2103 : ++pszValue;
1546 :
1547 : // Find end of value
1548 2103 : const char *pszEndOfValue = pszValue;
1549 109081 : while (*pszEndOfValue &&
1550 109081 : !(*pszEndOfValue == '\r' && pszEndOfValue[1] == '\n'))
1551 106978 : ++pszEndOfValue;
1552 :
1553 : aosHeaders.SetNameValue(
1554 4206 : std::string(pszHeaders, pszDelim - pszHeaders).c_str(),
1555 6309 : std::string(pszValue, pszEndOfValue - pszValue).c_str());
1556 :
1557 2103 : if (*pszEndOfValue == '\r' && pszEndOfValue[1] == '\n')
1558 2103 : pszHeaders = pszEndOfValue + 2;
1559 : else
1560 : break;
1561 : }
1562 302 : return aosHeaders;
1563 : };
1564 :
1565 496 : if (response_code < 300)
1566 : {
1567 274 : curl_off_t nSizeTmp = 0;
1568 274 : const CURLcode code = curl_easy_getinfo(
1569 : hCurlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nSizeTmp);
1570 274 : CPL_IGNORE_RET_VAL(dfSize);
1571 274 : dfSize = static_cast<double>(nSizeTmp);
1572 274 : if (code == 0)
1573 : {
1574 274 : if (dfSize < 0)
1575 : {
1576 25 : if (osVerb == "HEAD" && !bRetryWithGet &&
1577 11 : response_code == 200)
1578 : {
1579 7 : if (sWriteFuncHeaderData.pBuffer)
1580 : {
1581 : const CPLStringList aosHeaders(
1582 7 : TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1583 7 : if (strcmp(aosHeaders.FetchNameValueDef(
1584 : "accept-ranges", ""),
1585 7 : "bytes") == 0)
1586 : {
1587 3 : CPLDebug(
1588 3 : poFS->GetDebugKey(),
1589 : "HEAD did not provide file size. Retrying "
1590 : "with limited range GET");
1591 3 : bRetryWithLimitedRangeGet = true;
1592 3 : CPLFree(sWriteFuncData.pBuffer);
1593 3 : CPLFree(sWriteFuncHeaderData.pBuffer);
1594 3 : curl_easy_cleanup(hCurlHandle);
1595 3 : goto retry;
1596 : }
1597 : }
1598 :
1599 4 : CPLDebug(poFS->GetDebugKey(),
1600 : "HEAD did not provide file size. Retrying "
1601 : "with GET");
1602 4 : bRetryWithGet = true;
1603 4 : CPLFree(sWriteFuncData.pBuffer);
1604 4 : CPLFree(sWriteFuncHeaderData.pBuffer);
1605 4 : curl_easy_cleanup(hCurlHandle);
1606 4 : goto retry;
1607 : }
1608 :
1609 16 : if (poFS->GetFSPrefix() == "/vsicurl/" ||
1610 9 : poFS->GetFSPrefix() == "/vsicurl?")
1611 : {
1612 : const CPLStringList aosHeaders(
1613 10 : TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1614 5 : if (strcmp(aosHeaders.FetchNameValueDef(
1615 : "transfer-encoding", ""),
1616 5 : "chunked") == 0)
1617 : {
1618 1 : CPLError(
1619 : CE_Failure, CPLE_AppDefined,
1620 : "Server does not seem to support range "
1621 : "requests. "
1622 : "Maybe retry with /vsicurl_streaming/ if the "
1623 : "read "
1624 : "access pattern is compatible with sequential "
1625 : "reading, or download the file entirely");
1626 : }
1627 : }
1628 : }
1629 : else
1630 : {
1631 260 : oFileProp.eExists = EXIST_YES;
1632 260 : oFileProp.fileSize = static_cast<GUIntBig>(dfSize);
1633 : }
1634 : }
1635 : }
1636 :
1637 489 : if (sWriteFuncHeaderData.pBuffer != nullptr &&
1638 483 : (response_code == 200 || response_code == 206))
1639 : {
1640 : {
1641 : const CPLStringList aosHeaders(
1642 522 : TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1643 3428 : for (const auto &[pszKey, pszValue] :
1644 3689 : cpl::IterateNameValue(aosHeaders))
1645 : {
1646 1714 : if (bGetHeaders)
1647 : {
1648 17 : m_aosHeaders.SetNameValue(pszKey, pszValue);
1649 : }
1650 3448 : if (EQUAL(pszKey, "Cache-Control") &&
1651 1734 : EQUAL(pszValue, "no-cache") &&
1652 20 : CPLTestBool(CPLGetConfigOption(
1653 : "CPL_VSIL_CURL_HONOR_CACHE_CONTROL", "YES")))
1654 : {
1655 20 : m_bCached = false;
1656 : }
1657 :
1658 1694 : else if (EQUAL(pszKey, "ETag"))
1659 : {
1660 96 : std::string osValue(pszValue);
1661 95 : if (osValue.size() >= 2 && osValue.front() == '"' &&
1662 47 : osValue.back() == '"')
1663 47 : osValue = osValue.substr(1, osValue.size() - 2);
1664 48 : oFileProp.ETag = std::move(osValue);
1665 : }
1666 :
1667 : // Azure Data Lake Storage
1668 1646 : else if (EQUAL(pszKey, "x-ms-resource-type"))
1669 : {
1670 11 : if (EQUAL(pszValue, "file"))
1671 : {
1672 9 : oFileProp.nMode |= S_IFREG;
1673 : }
1674 2 : else if (EQUAL(pszValue, "directory"))
1675 : {
1676 2 : oFileProp.bIsDirectory = true;
1677 2 : oFileProp.nMode |= S_IFDIR;
1678 : }
1679 : }
1680 1635 : else if (EQUAL(pszKey, "x-ms-permissions"))
1681 : {
1682 11 : oFileProp.nMode |=
1683 11 : VSICurlParseUnixPermissions(pszValue);
1684 : }
1685 :
1686 : // https://overturemapswestus2.blob.core.windows.net/release/2024-11-13.0/theme%3Ddivisions/type%3Ddivision_area
1687 : // returns a x-ms-meta-hdi_isfolder: true header
1688 1624 : else if (EQUAL(pszKey, "x-ms-meta-hdi_isfolder") &&
1689 0 : EQUAL(pszValue, "true"))
1690 : {
1691 0 : oFileProp.bIsAzureFolder = true;
1692 0 : oFileProp.bIsDirectory = true;
1693 0 : oFileProp.nMode |= S_IFDIR;
1694 : }
1695 : }
1696 : }
1697 : }
1698 :
1699 489 : if (bHasUsedLimitedRangeGet && response_code == 206)
1700 : {
1701 29 : oFileProp.eExists = EXIST_NO;
1702 29 : oFileProp.fileSize = 0;
1703 29 : if (sWriteFuncHeaderData.pBuffer != nullptr)
1704 : {
1705 : const CPLStringList aosHeaders(
1706 58 : TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1707 : const char *pszContentRange =
1708 29 : aosHeaders.FetchNameValue("content-range");
1709 : // Trailing space in string intended
1710 29 : if (pszContentRange &&
1711 29 : STARTS_WITH_CI(pszContentRange, "bytes "))
1712 : {
1713 29 : pszContentRange += strlen("bytes ");
1714 29 : pszContentRange = strchr(pszContentRange, '/');
1715 29 : if (pszContentRange)
1716 : {
1717 29 : oFileProp.eExists = EXIST_YES;
1718 29 : oFileProp.fileSize = static_cast<GUIntBig>(
1719 29 : CPLAtoGIntBig(pszContentRange + 1));
1720 : }
1721 : }
1722 :
1723 : // Add first bytes to cache
1724 29 : if (sWriteFuncData.pBuffer != nullptr)
1725 : {
1726 29 : size_t nOffset = 0;
1727 58 : while (nOffset < sWriteFuncData.nSize)
1728 : {
1729 : const size_t nToCache =
1730 58 : std::min<size_t>(sWriteFuncData.nSize - nOffset,
1731 29 : knDOWNLOAD_CHUNK_SIZE);
1732 29 : poFS->AddRegion(m_pszURL, nOffset, nToCache,
1733 29 : sWriteFuncData.pBuffer + nOffset);
1734 29 : nOffset += nToCache;
1735 : }
1736 : }
1737 29 : }
1738 : }
1739 460 : else if (IsDirectoryFromExists(osVerb.c_str(),
1740 460 : static_cast<int>(response_code)))
1741 : {
1742 10 : oFileProp.eExists = EXIST_YES;
1743 10 : oFileProp.fileSize = 0;
1744 10 : oFileProp.bIsDirectory = true;
1745 : }
1746 : // 405 = Method not allowed
1747 450 : else if (response_code == 405 && !bRetryWithGet && osVerb == "HEAD")
1748 : {
1749 1 : CPLDebug(poFS->GetDebugKey(),
1750 : "HEAD not allowed. Retrying with GET");
1751 1 : bRetryWithGet = true;
1752 1 : CPLFree(sWriteFuncData.pBuffer);
1753 1 : CPLFree(sWriteFuncHeaderData.pBuffer);
1754 1 : curl_easy_cleanup(hCurlHandle);
1755 1 : goto retry;
1756 : }
1757 449 : else if (response_code == 416)
1758 : {
1759 0 : oFileProp.eExists = EXIST_YES;
1760 0 : oFileProp.fileSize = 0;
1761 : }
1762 449 : else if (response_code != 200)
1763 : {
1764 : // Look if we should attempt a retry
1765 217 : if (oRetryContext.CanRetry(static_cast<int>(response_code),
1766 217 : sWriteFuncHeaderData.pBuffer,
1767 : szCurlErrBuf))
1768 : {
1769 0 : CPLError(CE_Warning, CPLE_AppDefined,
1770 : "HTTP error code: %d - %s. "
1771 : "Retrying again in %.1f secs",
1772 : static_cast<int>(response_code), m_pszURL,
1773 : oRetryContext.GetCurrentDelay());
1774 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1775 0 : CPLFree(sWriteFuncData.pBuffer);
1776 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
1777 0 : curl_easy_cleanup(hCurlHandle);
1778 0 : goto retry;
1779 : }
1780 :
1781 217 : if (sWriteFuncData.pBuffer != nullptr)
1782 : {
1783 182 : if (UseLimitRangeGetInsteadOfHead() &&
1784 7 : CanRestartOnError(sWriteFuncData.pBuffer,
1785 7 : sWriteFuncHeaderData.pBuffer, bSetError))
1786 : {
1787 1 : oFileProp.bHasComputedFileSize = false;
1788 1 : CPLFree(sWriteFuncData.pBuffer);
1789 1 : CPLFree(sWriteFuncHeaderData.pBuffer);
1790 1 : curl_easy_cleanup(hCurlHandle);
1791 1 : return GetFileSizeOrHeaders(bSetError, bGetHeaders);
1792 : }
1793 : else
1794 : {
1795 174 : CPL_IGNORE_RET_VAL(CanRestartOnError(
1796 174 : sWriteFuncData.pBuffer, sWriteFuncHeaderData.pBuffer,
1797 174 : bSetError));
1798 : }
1799 : }
1800 :
1801 : // If there was no VSI error thrown in the process,
1802 : // fail by reporting the HTTP response code.
1803 216 : if (bSetError && VSIGetLastErrorNo() == 0)
1804 : {
1805 14 : if (strlen(szCurlErrBuf) > 0)
1806 : {
1807 2 : if (response_code == 0)
1808 : {
1809 2 : VSIError(VSIE_HttpError, "CURL error: %s",
1810 : szCurlErrBuf);
1811 : }
1812 : else
1813 : {
1814 0 : VSIError(VSIE_HttpError, "HTTP response code: %d - %s",
1815 : static_cast<int>(response_code), szCurlErrBuf);
1816 : }
1817 : }
1818 : else
1819 : {
1820 12 : VSIError(VSIE_HttpError, "HTTP response code: %d",
1821 : static_cast<int>(response_code));
1822 : }
1823 : }
1824 : else
1825 : {
1826 202 : if (response_code != 400 && response_code != 404)
1827 : {
1828 23 : CPLError(CE_Warning, CPLE_AppDefined,
1829 : "HTTP response code on %s: %d", osURL.c_str(),
1830 : static_cast<int>(response_code));
1831 : }
1832 : // else a CPLDebug() is emitted below
1833 : }
1834 :
1835 216 : oFileProp.eExists = EXIST_NO;
1836 216 : oFileProp.nHTTPCode = static_cast<int>(response_code);
1837 216 : oFileProp.fileSize = 0;
1838 : }
1839 232 : else if (sWriteFuncData.pBuffer != nullptr)
1840 : {
1841 207 : ProcessGetFileSizeResult(
1842 207 : reinterpret_cast<const char *>(sWriteFuncData.pBuffer));
1843 : }
1844 :
1845 : // Try to guess if this is a directory. Generally if this is a
1846 : // directory, curl will retry with an URL with slash added.
1847 487 : if (!osEffectiveURL.empty() &&
1848 487 : strncmp(osURL.c_str(), osEffectiveURL.c_str(), osURL.size()) == 0 &&
1849 976 : osEffectiveURL[osURL.size()] == '/' &&
1850 2 : oFileProp.eExists != EXIST_NO)
1851 : {
1852 1 : oFileProp.eExists = EXIST_YES;
1853 1 : oFileProp.fileSize = 0;
1854 1 : oFileProp.bIsDirectory = true;
1855 : }
1856 486 : else if (osURL.back() == '/')
1857 : {
1858 37 : oFileProp.bIsDirectory = true;
1859 : }
1860 :
1861 487 : if (!bAlreadyLogged)
1862 : {
1863 471 : CPLDebug(poFS->GetDebugKey(),
1864 : "GetFileSize(%s)=" CPL_FRMT_GUIB " response_code=%d",
1865 : osURL.c_str(), oFileProp.fileSize,
1866 : static_cast<int>(response_code));
1867 : }
1868 : }
1869 :
1870 487 : CPLFree(sWriteFuncData.pBuffer);
1871 487 : CPLFree(sWriteFuncHeaderData.pBuffer);
1872 487 : curl_easy_cleanup(hCurlHandle);
1873 :
1874 487 : oFileProp.bHasComputedFileSize = true;
1875 487 : if (mtime > 0)
1876 33 : oFileProp.mTime = mtime;
1877 : // Do not update cached file properties if cURL returned a non-HTTP error
1878 487 : if (response_code != 0)
1879 481 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
1880 :
1881 487 : return oFileProp.fileSize;
1882 : }
1883 :
1884 : /************************************************************************/
1885 : /* Exists() */
1886 : /************************************************************************/
1887 :
1888 1214 : bool VSICurlHandle::Exists(bool bSetError)
1889 : {
1890 1214 : if (oFileProp.eExists == EXIST_UNKNOWN)
1891 : {
1892 272 : GetFileSize(bSetError);
1893 : }
1894 942 : else if (oFileProp.eExists == EXIST_NO)
1895 : {
1896 : // If there was no VSI error thrown in the process,
1897 : // and we know the HTTP error code of the first request where the
1898 : // file could not be retrieved, fail by reporting the HTTP code.
1899 237 : if (bSetError && VSIGetLastErrorNo() == 0 && oFileProp.nHTTPCode)
1900 : {
1901 1 : VSIError(VSIE_HttpError, "HTTP response code: %d",
1902 : oFileProp.nHTTPCode);
1903 : }
1904 : }
1905 :
1906 1214 : return oFileProp.eExists == EXIST_YES;
1907 : }
1908 :
1909 : /************************************************************************/
1910 : /* Tell() */
1911 : /************************************************************************/
1912 :
1913 4157 : vsi_l_offset VSICurlHandle::Tell()
1914 : {
1915 4157 : return curOffset;
1916 : }
1917 :
1918 : /************************************************************************/
1919 : /* GetRedirectURLIfValid() */
1920 : /************************************************************************/
1921 :
1922 : std::string
1923 482 : VSICurlHandle::GetRedirectURLIfValid(bool &bHasExpired,
1924 : CPLStringList &aosHTTPOptions) const
1925 : {
1926 482 : bHasExpired = false;
1927 482 : poFS->GetCachedFileProp(m_pszURL, oFileProp);
1928 :
1929 482 : std::string osURL(m_pszURL + m_osQueryString);
1930 482 : if (oFileProp.bS3LikeRedirect)
1931 : {
1932 16 : if (time(nullptr) + 1 < oFileProp.nExpireTimestampLocal)
1933 : {
1934 16 : CPLDebug(poFS->GetDebugKey(),
1935 : "Using redirect URL as it looks to be still valid "
1936 : "(%d seconds left)",
1937 16 : static_cast<int>(oFileProp.nExpireTimestampLocal -
1938 16 : time(nullptr)));
1939 16 : osURL = oFileProp.osRedirectURL;
1940 : }
1941 : else
1942 : {
1943 0 : CPLDebug(poFS->GetDebugKey(),
1944 : "Redirect URL has expired. Using original URL");
1945 0 : oFileProp.bS3LikeRedirect = false;
1946 0 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
1947 0 : bHasExpired = true;
1948 : }
1949 : }
1950 466 : else if (!oFileProp.osRedirectURL.empty())
1951 : {
1952 14 : osURL = oFileProp.osRedirectURL;
1953 14 : bHasExpired = false;
1954 : }
1955 :
1956 482 : if (m_pszURL != osURL)
1957 : {
1958 31 : const char *pszAuthorizationHeaderAllowed = VSIGetPathSpecificOption(
1959 : m_osFilename.c_str(),
1960 : "CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT",
1961 : "IF_SAME_HOST");
1962 31 : if (EQUAL(pszAuthorizationHeaderAllowed, "IF_SAME_HOST"))
1963 : {
1964 50 : const auto ExtractServer = [](const std::string &s)
1965 : {
1966 50 : size_t afterHTTPPos = 0;
1967 50 : if (STARTS_WITH(s.c_str(), "http://"))
1968 26 : afterHTTPPos = strlen("http://");
1969 24 : else if (STARTS_WITH(s.c_str(), "https://"))
1970 24 : afterHTTPPos = strlen("https://");
1971 50 : const auto posSlash = s.find('/', afterHTTPPos);
1972 50 : if (posSlash != std::string::npos)
1973 50 : return s.substr(afterHTTPPos, posSlash - afterHTTPPos);
1974 : else
1975 0 : return s.substr(afterHTTPPos);
1976 : };
1977 :
1978 25 : if (ExtractServer(osURL) != ExtractServer(m_pszURL))
1979 : {
1980 : aosHTTPOptions.SetNameValue("AUTHORIZATION_HEADER_ALLOWED",
1981 20 : "NO");
1982 : }
1983 : }
1984 6 : else if (!CPLTestBool(pszAuthorizationHeaderAllowed))
1985 : {
1986 3 : aosHTTPOptions.SetNameValue("AUTHORIZATION_HEADER_ALLOWED", "NO");
1987 : }
1988 : }
1989 :
1990 482 : return osURL;
1991 : }
1992 :
1993 : /************************************************************************/
1994 : /* CurrentDownload */
1995 : /************************************************************************/
1996 :
1997 : namespace
1998 : {
1999 : struct CurrentDownload
2000 : {
2001 : VSICurlFilesystemHandlerBase *m_poFS = nullptr;
2002 : std::string m_osURL{};
2003 : vsi_l_offset m_nStartOffset = 0;
2004 : int m_nBlocks = 0;
2005 : std::string m_osAlreadyDownloadedData{};
2006 : bool m_bHasAlreadyDownloadedData = false;
2007 :
2008 402 : CurrentDownload(VSICurlFilesystemHandlerBase *poFS, const char *pszURL,
2009 : vsi_l_offset startOffset, int nBlocks)
2010 402 : : m_poFS(poFS), m_osURL(pszURL), m_nStartOffset(startOffset),
2011 402 : m_nBlocks(nBlocks)
2012 : {
2013 402 : auto res = m_poFS->NotifyStartDownloadRegion(m_osURL, m_nStartOffset,
2014 804 : m_nBlocks);
2015 402 : m_bHasAlreadyDownloadedData = res.first;
2016 402 : m_osAlreadyDownloadedData = std::move(res.second);
2017 402 : }
2018 :
2019 402 : bool HasAlreadyDownloadedData() const
2020 : {
2021 402 : return m_bHasAlreadyDownloadedData;
2022 : }
2023 :
2024 1 : const std::string &GetAlreadyDownloadedData() const
2025 : {
2026 1 : return m_osAlreadyDownloadedData;
2027 : }
2028 :
2029 396 : void SetData(const std::string &osData)
2030 : {
2031 396 : CPLAssert(!m_bHasAlreadyDownloadedData);
2032 396 : m_bHasAlreadyDownloadedData = true;
2033 396 : m_poFS->NotifyStopDownloadRegion(m_osURL, m_nStartOffset, m_nBlocks,
2034 : osData);
2035 396 : }
2036 :
2037 402 : ~CurrentDownload()
2038 402 : {
2039 402 : if (!m_bHasAlreadyDownloadedData)
2040 5 : m_poFS->NotifyStopDownloadRegion(m_osURL, m_nStartOffset, m_nBlocks,
2041 10 : std::string());
2042 402 : }
2043 :
2044 : CurrentDownload(const CurrentDownload &) = delete;
2045 : CurrentDownload &operator=(const CurrentDownload &) = delete;
2046 : };
2047 : } // namespace
2048 :
2049 : /************************************************************************/
2050 : /* NotifyStartDownloadRegion() */
2051 : /************************************************************************/
2052 :
2053 : /** Indicate intent at downloading a new region.
2054 : *
2055 : * If the region is already in download in another thread, then wait for its
2056 : * completion.
2057 : *
2058 : * Returns:
2059 : * - (false, empty string) if a new download is needed
2060 : * - (true, region_content) if we have been waiting for a download of the same
2061 : * region to be completed and got its result. Note that region_content will be
2062 : * empty if the download of that region failed.
2063 : */
2064 : std::pair<bool, std::string>
2065 402 : VSICurlFilesystemHandlerBase::NotifyStartDownloadRegion(
2066 : const std::string &osURL, vsi_l_offset startOffset, int nBlocks)
2067 : {
2068 804 : std::string osId(osURL);
2069 402 : osId += '_';
2070 402 : osId += std::to_string(startOffset);
2071 402 : osId += '_';
2072 402 : osId += std::to_string(nBlocks);
2073 :
2074 402 : m_oMutex.lock();
2075 402 : auto oIter = m_oMapRegionInDownload.find(osId);
2076 402 : if (oIter != m_oMapRegionInDownload.end())
2077 : {
2078 1 : auto ®ion = *(oIter->second);
2079 2 : std::unique_lock<std::mutex> oRegionLock(region.oMutex);
2080 1 : m_oMutex.unlock();
2081 1 : region.nWaiters++;
2082 2 : while (region.bDownloadInProgress)
2083 : {
2084 1 : region.oCond.wait(oRegionLock);
2085 : }
2086 1 : std::string osRet = region.osData;
2087 1 : region.nWaiters--;
2088 1 : region.oCond.notify_one();
2089 1 : return std::pair<bool, std::string>(true, osRet);
2090 : }
2091 : else
2092 : {
2093 401 : auto poRegionInDownload = std::make_unique<RegionInDownload>();
2094 401 : poRegionInDownload->bDownloadInProgress = true;
2095 401 : m_oMapRegionInDownload[osId] = std::move(poRegionInDownload);
2096 401 : m_oMutex.unlock();
2097 401 : return std::pair<bool, std::string>(false, std::string());
2098 : }
2099 : }
2100 :
2101 : /************************************************************************/
2102 : /* NotifyStopDownloadRegion() */
2103 : /************************************************************************/
2104 :
2105 401 : void VSICurlFilesystemHandlerBase::NotifyStopDownloadRegion(
2106 : const std::string &osURL, vsi_l_offset startOffset, int nBlocks,
2107 : const std::string &osData)
2108 : {
2109 802 : std::string osId(osURL);
2110 401 : osId += '_';
2111 401 : osId += std::to_string(startOffset);
2112 401 : osId += '_';
2113 401 : osId += std::to_string(nBlocks);
2114 :
2115 401 : m_oMutex.lock();
2116 401 : auto oIter = m_oMapRegionInDownload.find(osId);
2117 401 : CPLAssert(oIter != m_oMapRegionInDownload.end());
2118 401 : auto ®ion = *(oIter->second);
2119 : {
2120 802 : std::unique_lock<std::mutex> oRegionLock(region.oMutex);
2121 401 : if (region.nWaiters)
2122 : {
2123 1 : region.osData = osData;
2124 1 : region.bDownloadInProgress = false;
2125 1 : region.oCond.notify_all();
2126 :
2127 2 : while (region.nWaiters)
2128 : {
2129 1 : region.oCond.wait(oRegionLock);
2130 : }
2131 : }
2132 : }
2133 401 : m_oMapRegionInDownload.erase(oIter);
2134 401 : m_oMutex.unlock();
2135 401 : }
2136 :
2137 : /************************************************************************/
2138 : /* DownloadRegion() */
2139 : /************************************************************************/
2140 :
2141 402 : std::string VSICurlHandle::DownloadRegion(const vsi_l_offset startOffset,
2142 : const int nBlocks)
2143 : {
2144 402 : if (bInterrupted && bStopOnInterruptUntilUninstall)
2145 0 : return std::string();
2146 :
2147 402 : if (oFileProp.eExists == EXIST_NO)
2148 0 : return std::string();
2149 :
2150 : // Check if there is not a download of the same region in progress in
2151 : // another thread, and if so wait for it to be completed
2152 804 : CurrentDownload currentDownload(poFS, m_pszURL, startOffset, nBlocks);
2153 402 : if (currentDownload.HasAlreadyDownloadedData())
2154 : {
2155 1 : return currentDownload.GetAlreadyDownloadedData();
2156 : }
2157 :
2158 401 : begin:
2159 410 : CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
2160 :
2161 410 : UpdateQueryString();
2162 :
2163 410 : bool bHasExpired = false;
2164 :
2165 410 : CPLStringList aosHTTPOptions(m_aosHTTPOptions);
2166 410 : std::string osURL(GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
2167 410 : bool bUsedRedirect = osURL != m_pszURL;
2168 :
2169 410 : WriteFuncStruct sWriteFuncData;
2170 410 : WriteFuncStruct sWriteFuncHeaderData;
2171 410 : CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
2172 :
2173 420 : retry:
2174 420 : CURL *hCurlHandle = curl_easy_init();
2175 : struct curl_slist *headers =
2176 420 : VSICurlSetOptions(hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
2177 :
2178 420 : if (!AllowAutomaticRedirection())
2179 74 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
2180 :
2181 420 : VSICURLInitWriteFuncStruct(&sWriteFuncData, this, pfnReadCbk,
2182 : pReadCbkUserData);
2183 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
2184 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
2185 : VSICurlHandleWriteFunc);
2186 :
2187 420 : VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
2188 : nullptr);
2189 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
2190 : &sWriteFuncHeaderData);
2191 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
2192 : VSICurlHandleWriteFunc);
2193 420 : sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
2194 420 : sWriteFuncHeaderData.nStartOffset = startOffset;
2195 420 : sWriteFuncHeaderData.nEndOffset =
2196 420 : startOffset +
2197 420 : static_cast<vsi_l_offset>(nBlocks) * VSICURLGetDownloadChunkSize() - 1;
2198 : // Some servers don't like we try to read after end-of-file (#5786).
2199 420 : if (oFileProp.bHasComputedFileSize &&
2200 326 : sWriteFuncHeaderData.nEndOffset >= oFileProp.fileSize)
2201 : {
2202 120 : sWriteFuncHeaderData.nEndOffset = oFileProp.fileSize - 1;
2203 : }
2204 :
2205 420 : char rangeStr[512] = {};
2206 420 : snprintf(rangeStr, sizeof(rangeStr), CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2207 : startOffset, sWriteFuncHeaderData.nEndOffset);
2208 :
2209 : if constexpr (ENABLE_DEBUG)
2210 : {
2211 420 : CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...", rangeStr,
2212 : osURL.c_str());
2213 : }
2214 :
2215 420 : std::string osHeaderRange; // leave in this scope
2216 420 : if (sWriteFuncHeaderData.bIsHTTP)
2217 : {
2218 420 : osHeaderRange = CPLSPrintf("Range: bytes=%s", rangeStr);
2219 : // So it gets included in Azure signature
2220 420 : headers = curl_slist_append(headers, osHeaderRange.c_str());
2221 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
2222 : }
2223 : else
2224 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, rangeStr);
2225 :
2226 420 : char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
2227 420 : szCurlErrBuf[0] = '\0';
2228 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
2229 :
2230 420 : headers = GetCurlHeaders("GET", headers);
2231 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
2232 :
2233 420 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FILETIME, 1);
2234 :
2235 420 : VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle, &m_bInterrupt);
2236 :
2237 420 : VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
2238 :
2239 420 : curl_slist_free_all(headers);
2240 :
2241 420 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
2242 :
2243 420 : if (sWriteFuncData.bInterrupted || m_bInterrupt)
2244 : {
2245 0 : bInterrupted = true;
2246 :
2247 : // Notify that the download of the current region is finished
2248 0 : currentDownload.SetData(std::string());
2249 :
2250 0 : CPLFree(sWriteFuncData.pBuffer);
2251 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
2252 0 : curl_easy_cleanup(hCurlHandle);
2253 :
2254 0 : return std::string();
2255 : }
2256 :
2257 420 : long response_code = 0;
2258 420 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
2259 :
2260 420 : if (ENABLE_DEBUG && szCurlErrBuf[0] != '\0')
2261 : {
2262 3 : CPLDebug(poFS->GetDebugKey(),
2263 : "DownloadRegion(%s): response_code=%d, msg=%s", osURL.c_str(),
2264 : static_cast<int>(response_code), szCurlErrBuf);
2265 : }
2266 :
2267 420 : long mtime = 0;
2268 420 : curl_easy_getinfo(hCurlHandle, CURLINFO_FILETIME, &mtime);
2269 420 : if (mtime > 0)
2270 : {
2271 110 : oFileProp.mTime = mtime;
2272 110 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
2273 : }
2274 :
2275 : if constexpr (ENABLE_DEBUG)
2276 : {
2277 420 : CPLDebug(poFS->GetDebugKey(), "Got response_code=%ld", response_code);
2278 : }
2279 :
2280 451 : if (bUsedRedirect &&
2281 31 : (response_code == 403 ||
2282 : // Below case is in particular for
2283 : // gdalinfo
2284 : // /vsicurl/https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif
2285 : // --config GDAL_DISABLE_READDIR_ON_OPEN EMPTY_DIR --config
2286 : // GDAL_HTTP_COOKIEFILE /tmp/cookie.txt --config GDAL_HTTP_COOKIEJAR
2287 : // /tmp/cookie.txt We got the redirect URL from a HEAD request, but it
2288 : // is not valid for a GET. So retry with GET on original URL to get a
2289 : // redirect URL valid for it.
2290 29 : (response_code == 400 &&
2291 0 : osURL.find(".cloudfront.net") != std::string::npos)))
2292 : {
2293 2 : CPLDebug(poFS->GetDebugKey(),
2294 : "Got an error with redirect URL. Retrying with original one");
2295 2 : oFileProp.bS3LikeRedirect = false;
2296 2 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
2297 2 : bUsedRedirect = false;
2298 2 : osURL = m_pszURL;
2299 2 : CPLFree(sWriteFuncData.pBuffer);
2300 2 : CPLFree(sWriteFuncHeaderData.pBuffer);
2301 2 : curl_easy_cleanup(hCurlHandle);
2302 2 : goto retry;
2303 : }
2304 :
2305 418 : if (response_code == 401 && oRetryContext.CanRetry())
2306 : {
2307 0 : CPLDebug(poFS->GetDebugKey(), "Unauthorized, trying to authenticate");
2308 0 : CPLFree(sWriteFuncData.pBuffer);
2309 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
2310 0 : curl_easy_cleanup(hCurlHandle);
2311 0 : if (Authenticate(m_osFilename.c_str()))
2312 0 : goto retry;
2313 0 : return std::string();
2314 : }
2315 :
2316 418 : UpdateRedirectInfo(hCurlHandle, sWriteFuncHeaderData);
2317 :
2318 418 : if ((response_code != 200 && response_code != 206 && response_code != 225 &&
2319 22 : response_code != 226 && response_code != 426) ||
2320 396 : sWriteFuncHeaderData.bError)
2321 : {
2322 31 : if (sWriteFuncData.pBuffer != nullptr &&
2323 9 : CanRestartOnError(
2324 9 : reinterpret_cast<const char *>(sWriteFuncData.pBuffer),
2325 9 : reinterpret_cast<const char *>(sWriteFuncHeaderData.pBuffer),
2326 9 : true))
2327 : {
2328 9 : CPLFree(sWriteFuncData.pBuffer);
2329 9 : CPLFree(sWriteFuncHeaderData.pBuffer);
2330 9 : curl_easy_cleanup(hCurlHandle);
2331 9 : goto begin;
2332 : }
2333 :
2334 : // Look if we should attempt a retry
2335 13 : if (oRetryContext.CanRetry(static_cast<int>(response_code),
2336 13 : sWriteFuncHeaderData.pBuffer, szCurlErrBuf))
2337 : {
2338 8 : CPLError(CE_Warning, CPLE_AppDefined,
2339 : "HTTP error code: %d - %s. "
2340 : "Retrying again in %.1f secs",
2341 : static_cast<int>(response_code), m_pszURL,
2342 : oRetryContext.GetCurrentDelay());
2343 8 : CPLSleep(oRetryContext.GetCurrentDelay());
2344 8 : CPLFree(sWriteFuncData.pBuffer);
2345 8 : CPLFree(sWriteFuncHeaderData.pBuffer);
2346 8 : curl_easy_cleanup(hCurlHandle);
2347 8 : goto retry;
2348 : }
2349 :
2350 5 : if (response_code >= 400 && szCurlErrBuf[0] != '\0')
2351 : {
2352 0 : if (strcmp(szCurlErrBuf, "Couldn't use REST") == 0)
2353 0 : CPLError(
2354 : CE_Failure, CPLE_AppDefined,
2355 : "%d: %s, Range downloading not supported by this server!",
2356 : static_cast<int>(response_code), szCurlErrBuf);
2357 : else
2358 0 : CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
2359 : static_cast<int>(response_code), szCurlErrBuf);
2360 : }
2361 5 : else if (response_code == 416) /* Range Not Satisfiable */
2362 : {
2363 0 : if (sWriteFuncData.pBuffer)
2364 : {
2365 0 : CPLError(
2366 : CE_Failure, CPLE_AppDefined,
2367 : "%d: Range downloading not supported by this server: %s",
2368 : static_cast<int>(response_code), sWriteFuncData.pBuffer);
2369 : }
2370 : else
2371 : {
2372 0 : CPLError(CE_Failure, CPLE_AppDefined,
2373 : "%d: Range downloading not supported by this server",
2374 : static_cast<int>(response_code));
2375 : }
2376 : }
2377 5 : if (!oFileProp.bHasComputedFileSize && startOffset == 0)
2378 : {
2379 1 : oFileProp.bHasComputedFileSize = true;
2380 1 : oFileProp.fileSize = 0;
2381 1 : oFileProp.eExists = EXIST_NO;
2382 1 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
2383 : }
2384 5 : CPLFree(sWriteFuncData.pBuffer);
2385 5 : CPLFree(sWriteFuncHeaderData.pBuffer);
2386 5 : curl_easy_cleanup(hCurlHandle);
2387 5 : return std::string();
2388 : }
2389 :
2390 396 : if (!oFileProp.bHasComputedFileSize && sWriteFuncHeaderData.pBuffer)
2391 : {
2392 : // Try to retrieve the filesize from the HTTP headers
2393 : // if in the form: "Content-Range: bytes x-y/filesize".
2394 : char *pszContentRange =
2395 84 : strstr(sWriteFuncHeaderData.pBuffer, "Content-Range: bytes ");
2396 84 : if (pszContentRange == nullptr)
2397 : pszContentRange =
2398 83 : strstr(sWriteFuncHeaderData.pBuffer, "content-range: bytes ");
2399 84 : if (pszContentRange)
2400 : {
2401 1 : char *pszEOL = strchr(pszContentRange, '\n');
2402 1 : if (pszEOL)
2403 : {
2404 1 : *pszEOL = 0;
2405 1 : pszEOL = strchr(pszContentRange, '\r');
2406 1 : if (pszEOL)
2407 1 : *pszEOL = 0;
2408 1 : char *pszSlash = strchr(pszContentRange, '/');
2409 1 : if (pszSlash)
2410 : {
2411 1 : pszSlash++;
2412 1 : oFileProp.fileSize = CPLScanUIntBig(
2413 1 : pszSlash, static_cast<int>(strlen(pszSlash)));
2414 : }
2415 : }
2416 : }
2417 83 : else if (STARTS_WITH(m_pszURL, "ftp"))
2418 : {
2419 : // Parse 213 answer for FTP protocol.
2420 0 : char *pszSize = strstr(sWriteFuncHeaderData.pBuffer, "213 ");
2421 0 : if (pszSize)
2422 : {
2423 0 : pszSize += 4;
2424 0 : char *pszEOL = strchr(pszSize, '\n');
2425 0 : if (pszEOL)
2426 : {
2427 0 : *pszEOL = 0;
2428 0 : pszEOL = strchr(pszSize, '\r');
2429 0 : if (pszEOL)
2430 0 : *pszEOL = 0;
2431 :
2432 0 : oFileProp.fileSize = CPLScanUIntBig(
2433 0 : pszSize, static_cast<int>(strlen(pszSize)));
2434 : }
2435 : }
2436 : }
2437 :
2438 84 : if (oFileProp.fileSize != 0)
2439 : {
2440 1 : oFileProp.eExists = EXIST_YES;
2441 :
2442 : if constexpr (ENABLE_DEBUG)
2443 : {
2444 1 : CPLDebug(poFS->GetDebugKey(),
2445 : "GetFileSize(%s)=" CPL_FRMT_GUIB " response_code=%d",
2446 : m_pszURL, oFileProp.fileSize,
2447 : static_cast<int>(response_code));
2448 : }
2449 :
2450 1 : oFileProp.bHasComputedFileSize = true;
2451 1 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
2452 : }
2453 : }
2454 :
2455 396 : DownloadRegionPostProcess(startOffset, nBlocks, sWriteFuncData.pBuffer,
2456 : sWriteFuncData.nSize);
2457 :
2458 792 : std::string osRet;
2459 396 : osRet.assign(sWriteFuncData.pBuffer, sWriteFuncData.nSize);
2460 :
2461 : // Notify that the download of the current region is finished
2462 396 : currentDownload.SetData(osRet);
2463 :
2464 396 : CPLFree(sWriteFuncData.pBuffer);
2465 396 : CPLFree(sWriteFuncHeaderData.pBuffer);
2466 396 : curl_easy_cleanup(hCurlHandle);
2467 :
2468 396 : return osRet;
2469 : }
2470 :
2471 : /************************************************************************/
2472 : /* UpdateRedirectInfo() */
2473 : /************************************************************************/
2474 :
2475 482 : void VSICurlHandle::UpdateRedirectInfo(
2476 : CURL *hCurlHandle, const WriteFuncStruct &sWriteFuncHeaderData)
2477 : {
2478 964 : std::string osEffectiveURL;
2479 : {
2480 482 : char *pszEffectiveURL = nullptr;
2481 482 : curl_easy_getinfo(hCurlHandle, CURLINFO_EFFECTIVE_URL,
2482 : &pszEffectiveURL);
2483 482 : if (pszEffectiveURL)
2484 482 : osEffectiveURL = pszEffectiveURL;
2485 : }
2486 :
2487 950 : if (!oFileProp.bS3LikeRedirect && !osEffectiveURL.empty() &&
2488 468 : strstr(osEffectiveURL.c_str(), m_pszURL) == nullptr)
2489 : {
2490 108 : CPLDebug(poFS->GetDebugKey(), "Effective URL: %s",
2491 : osEffectiveURL.c_str());
2492 :
2493 108 : long response_code = 0;
2494 108 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
2495 108 : if (response_code >= 200 && response_code < 300 &&
2496 216 : sWriteFuncHeaderData.nTimestampDate > 0 &&
2497 108 : VSICurlIsS3LikeSignedURL(osEffectiveURL.c_str()) &&
2498 218 : !VSICurlIsS3LikeSignedURL(m_pszURL) &&
2499 2 : CPLTestBool(
2500 : CPLGetConfigOption("CPL_VSIL_CURL_USE_S3_REDIRECT", "TRUE")))
2501 : {
2502 : GIntBig nExpireTimestamp =
2503 2 : VSICurlGetExpiresFromS3LikeSignedURL(osEffectiveURL.c_str());
2504 2 : if (nExpireTimestamp > sWriteFuncHeaderData.nTimestampDate + 10)
2505 : {
2506 2 : const int nValidity = static_cast<int>(
2507 2 : nExpireTimestamp - sWriteFuncHeaderData.nTimestampDate);
2508 2 : CPLDebug(poFS->GetDebugKey(),
2509 : "Will use redirect URL for the next %d seconds",
2510 : nValidity);
2511 : // As our local clock might not be in sync with server clock,
2512 : // figure out the expiration timestamp in local time.
2513 2 : oFileProp.bS3LikeRedirect = true;
2514 2 : oFileProp.nExpireTimestampLocal = time(nullptr) + nValidity;
2515 2 : oFileProp.osRedirectURL = std::move(osEffectiveURL);
2516 2 : poFS->SetCachedFileProp(m_pszURL, oFileProp);
2517 : }
2518 : }
2519 : }
2520 482 : }
2521 :
2522 : /************************************************************************/
2523 : /* DownloadRegionPostProcess() */
2524 : /************************************************************************/
2525 :
2526 398 : void VSICurlHandle::DownloadRegionPostProcess(const vsi_l_offset startOffset,
2527 : const int nBlocks,
2528 : const char *pBuffer, size_t nSize)
2529 : {
2530 398 : const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
2531 398 : lastDownloadedOffset = startOffset + static_cast<vsi_l_offset>(nBlocks) *
2532 398 : knDOWNLOAD_CHUNK_SIZE;
2533 :
2534 398 : if (nSize > static_cast<size_t>(nBlocks) * knDOWNLOAD_CHUNK_SIZE)
2535 : {
2536 : if constexpr (ENABLE_DEBUG)
2537 : {
2538 1 : CPLDebug(
2539 1 : poFS->GetDebugKey(),
2540 : "Got more data than expected : %u instead of %u",
2541 : static_cast<unsigned int>(nSize),
2542 1 : static_cast<unsigned int>(nBlocks * knDOWNLOAD_CHUNK_SIZE));
2543 : }
2544 : }
2545 :
2546 398 : vsi_l_offset l_startOffset = startOffset;
2547 8289 : while (nSize > 0)
2548 : {
2549 : #if DEBUG_VERBOSE
2550 : if constexpr (ENABLE_DEBUG)
2551 : {
2552 : CPLDebug(poFS->GetDebugKey(), "Add region %u - %u",
2553 : static_cast<unsigned int>(startOffset),
2554 : static_cast<unsigned int>(std::min(
2555 : static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE), nSize)));
2556 : }
2557 : #endif
2558 : const size_t nChunkSize =
2559 7891 : std::min(static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE), nSize);
2560 7891 : poFS->AddRegion(m_pszURL, l_startOffset, nChunkSize, pBuffer);
2561 7891 : l_startOffset += nChunkSize;
2562 7891 : pBuffer += nChunkSize;
2563 7891 : nSize -= nChunkSize;
2564 : }
2565 398 : }
2566 :
2567 : /************************************************************************/
2568 : /* Read() */
2569 : /************************************************************************/
2570 :
2571 146567 : size_t VSICurlHandle::Read(void *const pBufferIn, size_t const nBytes)
2572 : {
2573 293134 : NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
2574 293134 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
2575 293134 : NetworkStatisticsAction oContextAction("Read");
2576 :
2577 146567 : size_t nBufferRequestSize = nBytes;
2578 146567 : if (nBufferRequestSize == 0)
2579 2 : return 0;
2580 :
2581 146565 : void *pBuffer = pBufferIn;
2582 :
2583 : #if DEBUG_VERBOSE
2584 : CPLDebug(poFS->GetDebugKey(), "offset=%d, size=%d",
2585 : static_cast<int>(curOffset), static_cast<int>(nBufferRequestSize));
2586 : #endif
2587 :
2588 146565 : vsi_l_offset iterOffset = curOffset;
2589 146565 : const int knMAX_REGIONS = GetMaxRegions();
2590 146565 : const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
2591 294863 : while (nBufferRequestSize)
2592 : {
2593 : // Don't try to read after end of file.
2594 148446 : poFS->GetCachedFileProp(m_pszURL, oFileProp);
2595 148446 : if (oFileProp.bHasComputedFileSize && iterOffset >= oFileProp.fileSize)
2596 : {
2597 10 : if (iterOffset == curOffset)
2598 : {
2599 10 : CPLDebug(poFS->GetDebugKey(),
2600 : "Request at offset " CPL_FRMT_GUIB
2601 : ", after end of file",
2602 : iterOffset);
2603 : }
2604 142 : break;
2605 : }
2606 :
2607 148436 : const vsi_l_offset nOffsetToDownload =
2608 148436 : (iterOffset / knDOWNLOAD_CHUNK_SIZE) * knDOWNLOAD_CHUNK_SIZE;
2609 148436 : std::string osRegion;
2610 : std::shared_ptr<std::string> psRegion =
2611 148436 : poFS->GetRegion(m_pszURL, nOffsetToDownload);
2612 148436 : if (psRegion != nullptr)
2613 : {
2614 148031 : osRegion = *psRegion;
2615 : }
2616 : else
2617 : {
2618 405 : if (nOffsetToDownload == lastDownloadedOffset)
2619 : {
2620 : // In case of consecutive reads (of small size), we use a
2621 : // heuristic that we will read the file sequentially, so
2622 : // we double the requested size to decrease the number of
2623 : // client/server roundtrips.
2624 50 : constexpr int MAX_CHUNK_SIZE_INCREASE_FACTOR = 128;
2625 50 : if (nBlocksToDownload < MAX_CHUNK_SIZE_INCREASE_FACTOR)
2626 42 : nBlocksToDownload *= 2;
2627 : }
2628 : else
2629 : {
2630 : // Random reads. Cancel the above heuristics.
2631 355 : nBlocksToDownload = 1;
2632 : }
2633 :
2634 : // Ensure that we will request at least the number of blocks
2635 : // to satisfy the remaining buffer size to read.
2636 405 : const vsi_l_offset nEndOffsetToDownload =
2637 405 : ((iterOffset + nBufferRequestSize + knDOWNLOAD_CHUNK_SIZE - 1) /
2638 405 : knDOWNLOAD_CHUNK_SIZE) *
2639 405 : knDOWNLOAD_CHUNK_SIZE;
2640 405 : const int nMinBlocksToDownload =
2641 405 : static_cast<int>((nEndOffsetToDownload - nOffsetToDownload) /
2642 405 : knDOWNLOAD_CHUNK_SIZE);
2643 405 : if (nBlocksToDownload < nMinBlocksToDownload)
2644 91 : nBlocksToDownload = nMinBlocksToDownload;
2645 :
2646 : // Avoid reading already cached data.
2647 : // Note: this might get evicted if concurrent reads are done, but
2648 : // this should not cause bugs. Just missed optimization.
2649 18581 : for (int i = 1; i < nBlocksToDownload; i++)
2650 : {
2651 18234 : if (poFS->GetRegion(m_pszURL, nOffsetToDownload +
2652 18234 : static_cast<vsi_l_offset>(i) *
2653 18234 : knDOWNLOAD_CHUNK_SIZE) !=
2654 : nullptr)
2655 : {
2656 58 : nBlocksToDownload = i;
2657 58 : break;
2658 : }
2659 : }
2660 :
2661 : // We can't download more than knMAX_REGIONS chunks at a time,
2662 : // otherwise the cache will not be big enough to store them and
2663 : // copy their content to the target buffer.
2664 405 : if (nBlocksToDownload > knMAX_REGIONS)
2665 5 : nBlocksToDownload = knMAX_REGIONS;
2666 :
2667 405 : osRegion = DownloadRegion(nOffsetToDownload, nBlocksToDownload);
2668 405 : if (osRegion.empty())
2669 : {
2670 6 : if (!bInterrupted)
2671 6 : bError = true;
2672 6 : return 0;
2673 : }
2674 : }
2675 :
2676 148430 : const vsi_l_offset nRegionOffset = iterOffset - nOffsetToDownload;
2677 148430 : if (osRegion.size() < nRegionOffset)
2678 : {
2679 0 : if (iterOffset == curOffset)
2680 : {
2681 0 : CPLDebug(poFS->GetDebugKey(),
2682 : "Request at offset " CPL_FRMT_GUIB
2683 : ", after end of file",
2684 : iterOffset);
2685 : }
2686 0 : break;
2687 : }
2688 :
2689 : const int nToCopy = static_cast<int>(
2690 296860 : std::min(static_cast<vsi_l_offset>(nBufferRequestSize),
2691 148430 : osRegion.size() - nRegionOffset));
2692 148430 : memcpy(pBuffer, osRegion.data() + nRegionOffset, nToCopy);
2693 148430 : pBuffer = static_cast<char *>(pBuffer) + nToCopy;
2694 148430 : iterOffset += nToCopy;
2695 148430 : nBufferRequestSize -= nToCopy;
2696 148430 : if (osRegion.size() < static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE) &&
2697 : nBufferRequestSize != 0)
2698 : {
2699 132 : break;
2700 : }
2701 : }
2702 :
2703 146559 : const size_t ret = static_cast<size_t>(iterOffset - curOffset);
2704 146559 : if (ret != nBytes)
2705 142 : bEOF = true;
2706 :
2707 146559 : curOffset = iterOffset;
2708 :
2709 146559 : return ret;
2710 : }
2711 :
2712 : /************************************************************************/
2713 : /* ReadMultiRange() */
2714 : /************************************************************************/
2715 :
2716 11 : int VSICurlHandle::ReadMultiRange(int const nRanges, void **const ppData,
2717 : const vsi_l_offset *const panOffsets,
2718 : const size_t *const panSizes)
2719 : {
2720 11 : if (bInterrupted && bStopOnInterruptUntilUninstall)
2721 0 : return FALSE;
2722 :
2723 11 : poFS->GetCachedFileProp(m_pszURL, oFileProp);
2724 11 : if (oFileProp.eExists == EXIST_NO)
2725 0 : return -1;
2726 :
2727 22 : NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
2728 22 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
2729 22 : NetworkStatisticsAction oContextAction("ReadMultiRange");
2730 :
2731 : const char *pszMultiRangeStrategy =
2732 11 : CPLGetConfigOption("GDAL_HTTP_MULTIRANGE", "");
2733 11 : if (EQUAL(pszMultiRangeStrategy, "SINGLE_GET"))
2734 : {
2735 : // Just in case someone needs it, but the interest of this mode is
2736 : // rather dubious now. We could probably remove it
2737 0 : return ReadMultiRangeSingleGet(nRanges, ppData, panOffsets, panSizes);
2738 : }
2739 11 : else if (nRanges == 1 || EQUAL(pszMultiRangeStrategy, "SERIAL"))
2740 : {
2741 9 : return VSIVirtualHandle::ReadMultiRange(nRanges, ppData, panOffsets,
2742 9 : panSizes);
2743 : }
2744 :
2745 2 : UpdateQueryString();
2746 :
2747 2 : bool bHasExpired = false;
2748 :
2749 4 : CPLStringList aosHTTPOptions(m_aosHTTPOptions);
2750 4 : std::string osURL(GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
2751 2 : if (bHasExpired)
2752 : {
2753 0 : return VSIVirtualHandle::ReadMultiRange(nRanges, ppData, panOffsets,
2754 0 : panSizes);
2755 : }
2756 :
2757 2 : CURLM *hMultiHandle = poFS->GetCurlMultiHandleFor(osURL);
2758 : #ifdef CURLPIPE_MULTIPLEX
2759 : // Enable HTTP/2 multiplexing (ignored if an older version of HTTP is
2760 : // used)
2761 : // Not that this does not enable HTTP/1.1 pipeling, which is not
2762 : // recommended for example by Google Cloud Storage.
2763 : // For HTTP/1.1, parallel connections work better since you can get
2764 : // results out of order.
2765 2 : if (CPLTestBool(CPLGetConfigOption("GDAL_HTTP_MULTIPLEX", "YES")))
2766 : {
2767 2 : curl_multi_setopt(hMultiHandle, CURLMOPT_PIPELINING,
2768 : CURLPIPE_MULTIPLEX);
2769 : }
2770 : #endif
2771 :
2772 : struct CurlErrBuffer
2773 : {
2774 : std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
2775 : };
2776 :
2777 : // Sort ranges by file offset so the merge loop below can coalesce
2778 : // adjacent ranges regardless of the order the caller passed them.
2779 : // The ppData buffer pointers travel with their offsets, so the
2780 : // distribute logic fills the correct caller buffers after reading.
2781 4 : std::vector<int> anSortOrder(nRanges);
2782 2 : std::iota(anSortOrder.begin(), anSortOrder.end(), 0);
2783 2 : std::sort(anSortOrder.begin(), anSortOrder.end(), [panOffsets](int a, int b)
2784 12 : { return panOffsets[a] < panOffsets[b]; });
2785 :
2786 4 : std::vector<void *> apSortedData(nRanges);
2787 4 : std::vector<vsi_l_offset> anSortedOffsets(nRanges);
2788 4 : std::vector<size_t> anSortedSizes(nRanges);
2789 10 : for (int i = 0; i < nRanges; ++i)
2790 : {
2791 8 : apSortedData[i] = ppData[anSortOrder[i]];
2792 8 : anSortedOffsets[i] = panOffsets[anSortOrder[i]];
2793 8 : anSortedSizes[i] = panSizes[anSortOrder[i]];
2794 : }
2795 :
2796 2 : const bool bMergeConsecutiveRanges = CPLTestBool(
2797 : CPLGetConfigOption("GDAL_HTTP_MERGE_CONSECUTIVE_RANGES", "TRUE"));
2798 :
2799 : // Build list of merged requests upfront, each with its own retry context
2800 : struct MergedRequest
2801 : {
2802 : int iFirstRange;
2803 : int iLastRange;
2804 : vsi_l_offset nStartOffset;
2805 : size_t nSize;
2806 : CPLHTTPRetryContext retryContext;
2807 : bool bToRetry = true; // true initially to trigger first attempt
2808 :
2809 8 : MergedRequest(int first, int last, vsi_l_offset start, size_t size,
2810 : const CPLHTTPRetryParameters ¶ms)
2811 8 : : iFirstRange(first), iLastRange(last), nStartOffset(start),
2812 8 : nSize(size), retryContext(params)
2813 : {
2814 8 : }
2815 : };
2816 :
2817 4 : std::vector<MergedRequest> asMergedRequests;
2818 10 : for (int i = 0; i < nRanges;)
2819 : {
2820 8 : size_t nSize = 0;
2821 8 : int iNext = i;
2822 : // Identify consecutive ranges
2823 14 : while (bMergeConsecutiveRanges && iNext + 1 < nRanges &&
2824 12 : anSortedOffsets[iNext] + anSortedSizes[iNext] ==
2825 6 : anSortedOffsets[iNext + 1])
2826 : {
2827 0 : nSize += anSortedSizes[iNext];
2828 0 : iNext++;
2829 : }
2830 8 : nSize += anSortedSizes[iNext];
2831 :
2832 8 : if (nSize == 0)
2833 : {
2834 0 : i = iNext + 1;
2835 0 : continue;
2836 : }
2837 :
2838 8 : asMergedRequests.emplace_back(i, iNext, anSortedOffsets[i], nSize,
2839 8 : m_oRetryParameters);
2840 8 : i = iNext + 1;
2841 : }
2842 :
2843 2 : if (asMergedRequests.empty())
2844 0 : return 0;
2845 :
2846 2 : int nRet = 0;
2847 2 : size_t nTotalDownloaded = 0;
2848 :
2849 : // Retry loop: re-issue only failed requests that are retryable
2850 : while (true)
2851 : {
2852 3 : const size_t nRequests = asMergedRequests.size();
2853 3 : std::vector<CURL *> aHandles(nRequests, nullptr);
2854 3 : std::vector<WriteFuncStruct> asWriteFuncData(nRequests);
2855 3 : std::vector<WriteFuncStruct> asWriteFuncHeaderData(nRequests);
2856 3 : std::vector<char *> apszRanges(nRequests, nullptr);
2857 3 : std::vector<struct curl_slist *> aHeaders(nRequests, nullptr);
2858 3 : std::vector<CurlErrBuffer> asCurlErrors(nRequests);
2859 :
2860 3 : bool bAnyHandle = false;
2861 15 : for (size_t iReq = 0; iReq < nRequests; iReq++)
2862 : {
2863 12 : if (!asMergedRequests[iReq].bToRetry)
2864 3 : continue;
2865 9 : asMergedRequests[iReq].bToRetry = false;
2866 :
2867 9 : CURL *hCurlHandle = curl_easy_init();
2868 9 : aHandles[iReq] = hCurlHandle;
2869 9 : bAnyHandle = true;
2870 :
2871 9 : struct curl_slist *headers = VSICurlSetOptions(
2872 9 : hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
2873 :
2874 9 : VSICURLInitWriteFuncStruct(&asWriteFuncData[iReq], this, pfnReadCbk,
2875 : pReadCbkUserData);
2876 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
2877 : &asWriteFuncData[iReq]);
2878 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
2879 : VSICurlHandleWriteFunc);
2880 :
2881 9 : VSICURLInitWriteFuncStruct(&asWriteFuncHeaderData[iReq], nullptr,
2882 : nullptr, nullptr);
2883 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
2884 : &asWriteFuncHeaderData[iReq]);
2885 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
2886 : VSICurlHandleWriteFunc);
2887 9 : asWriteFuncHeaderData[iReq].bIsHTTP = STARTS_WITH(m_pszURL, "http");
2888 18 : asWriteFuncHeaderData[iReq].nStartOffset =
2889 9 : asMergedRequests[iReq].nStartOffset;
2890 18 : asWriteFuncHeaderData[iReq].nEndOffset =
2891 9 : asMergedRequests[iReq].nStartOffset +
2892 9 : asMergedRequests[iReq].nSize - 1;
2893 :
2894 9 : char rangeStr[512] = {};
2895 18 : snprintf(rangeStr, sizeof(rangeStr),
2896 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2897 9 : asWriteFuncHeaderData[iReq].nStartOffset,
2898 9 : asWriteFuncHeaderData[iReq].nEndOffset);
2899 :
2900 : if constexpr (ENABLE_DEBUG)
2901 : {
2902 9 : CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
2903 : rangeStr, osURL.c_str());
2904 : }
2905 :
2906 9 : if (asWriteFuncHeaderData[iReq].bIsHTTP)
2907 : {
2908 : // So it gets included in Azure signature
2909 : char *pszRange =
2910 9 : CPLStrdup(CPLSPrintf("Range: bytes=%s", rangeStr));
2911 9 : apszRanges[iReq] = pszRange;
2912 9 : headers = curl_slist_append(headers, pszRange);
2913 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
2914 : }
2915 : else
2916 : {
2917 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
2918 : rangeStr);
2919 : }
2920 :
2921 9 : asCurlErrors[iReq].szCurlErrBuf[0] = '\0';
2922 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
2923 : &asCurlErrors[iReq].szCurlErrBuf[0]);
2924 :
2925 9 : headers = GetCurlHeaders("GET", headers);
2926 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
2927 : headers);
2928 9 : aHeaders[iReq] = headers;
2929 9 : curl_multi_add_handle(hMultiHandle, hCurlHandle);
2930 : }
2931 :
2932 3 : if (bAnyHandle)
2933 : {
2934 3 : VSICURLMultiPerform(hMultiHandle);
2935 : }
2936 :
2937 : // Process results
2938 3 : bool bRetry = false;
2939 3 : double dfMaxDelay = 0.0;
2940 15 : for (size_t iReq = 0; iReq < nRequests; iReq++)
2941 : {
2942 12 : if (!aHandles[iReq])
2943 3 : continue;
2944 :
2945 9 : long response_code = 0;
2946 9 : curl_easy_getinfo(aHandles[iReq], CURLINFO_HTTP_CODE,
2947 : &response_code);
2948 :
2949 9 : if (ENABLE_DEBUG && asCurlErrors[iReq].szCurlErrBuf[0] != '\0')
2950 : {
2951 0 : char rangeStr[512] = {};
2952 0 : snprintf(rangeStr, sizeof(rangeStr),
2953 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2954 0 : asWriteFuncHeaderData[iReq].nStartOffset,
2955 0 : asWriteFuncHeaderData[iReq].nEndOffset);
2956 :
2957 0 : const char *pszErrorMsg = &asCurlErrors[iReq].szCurlErrBuf[0];
2958 0 : CPLDebug(poFS->GetDebugKey(),
2959 : "ReadMultiRange(%s), %s: response_code=%d, msg=%s",
2960 : osURL.c_str(), rangeStr,
2961 : static_cast<int>(response_code), pszErrorMsg);
2962 : }
2963 :
2964 17 : if ((response_code != 206 && response_code != 225) ||
2965 8 : asWriteFuncHeaderData[iReq].nEndOffset + 1 !=
2966 8 : asWriteFuncHeaderData[iReq].nStartOffset +
2967 8 : asWriteFuncData[iReq].nSize)
2968 : {
2969 1 : char rangeStr[512] = {};
2970 2 : snprintf(rangeStr, sizeof(rangeStr),
2971 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2972 1 : asWriteFuncHeaderData[iReq].nStartOffset,
2973 1 : asWriteFuncHeaderData[iReq].nEndOffset);
2974 :
2975 : // Look if we should attempt a retry
2976 2 : if (asMergedRequests[iReq].retryContext.CanRetry(
2977 : static_cast<int>(response_code),
2978 1 : asWriteFuncData[iReq].pBuffer,
2979 1 : &asCurlErrors[iReq].szCurlErrBuf[0]))
2980 : {
2981 1 : CPLError(
2982 : CE_Warning, CPLE_AppDefined,
2983 : "HTTP error code for %s range %s: %d. "
2984 : "Retrying again in %.1f secs",
2985 : osURL.c_str(), rangeStr,
2986 : static_cast<int>(response_code),
2987 1 : asMergedRequests[iReq].retryContext.GetCurrentDelay());
2988 1 : dfMaxDelay = std::max(
2989 : dfMaxDelay,
2990 1 : asMergedRequests[iReq].retryContext.GetCurrentDelay());
2991 1 : asMergedRequests[iReq].bToRetry = true;
2992 1 : bRetry = true;
2993 : }
2994 : else
2995 : {
2996 0 : CPLError(CE_Failure, CPLE_AppDefined,
2997 : "Request for %s failed with response_code=%ld",
2998 : rangeStr, response_code);
2999 0 : nRet = -1;
3000 : }
3001 : }
3002 8 : else if (nRet == 0)
3003 : {
3004 8 : size_t nOffset = 0;
3005 8 : size_t nRemainingSize = asWriteFuncData[iReq].nSize;
3006 8 : nTotalDownloaded += nRemainingSize;
3007 8 : for (int iRange = asMergedRequests[iReq].iFirstRange;
3008 16 : iRange <= asMergedRequests[iReq].iLastRange; iRange++)
3009 : {
3010 8 : if (nRemainingSize < anSortedSizes[iRange])
3011 : {
3012 0 : nRet = -1;
3013 0 : break;
3014 : }
3015 :
3016 8 : if (anSortedSizes[iRange] > 0)
3017 : {
3018 8 : memcpy(apSortedData[iRange],
3019 8 : asWriteFuncData[iReq].pBuffer + nOffset,
3020 8 : anSortedSizes[iRange]);
3021 : }
3022 8 : nOffset += anSortedSizes[iRange];
3023 8 : nRemainingSize -= anSortedSizes[iRange];
3024 : }
3025 : }
3026 :
3027 9 : curl_multi_remove_handle(hMultiHandle, aHandles[iReq]);
3028 9 : VSICURLResetHeaderAndWriterFunctions(aHandles[iReq]);
3029 9 : curl_easy_cleanup(aHandles[iReq]);
3030 9 : CPLFree(apszRanges[iReq]);
3031 9 : CPLFree(asWriteFuncData[iReq].pBuffer);
3032 9 : CPLFree(asWriteFuncHeaderData[iReq].pBuffer);
3033 9 : if (aHeaders[iReq])
3034 9 : curl_slist_free_all(aHeaders[iReq]);
3035 : }
3036 :
3037 3 : if (!bRetry || nRet != 0)
3038 : break;
3039 1 : CPLSleep(dfMaxDelay);
3040 1 : }
3041 :
3042 2 : NetworkStatisticsLogger::LogGET(nTotalDownloaded);
3043 :
3044 : if constexpr (ENABLE_DEBUG)
3045 : {
3046 2 : CPLDebug(poFS->GetDebugKey(), "Download completed");
3047 : }
3048 :
3049 2 : return nRet;
3050 : }
3051 :
3052 : /************************************************************************/
3053 : /* ReadMultiRangeSingleGet() */
3054 : /************************************************************************/
3055 :
3056 : // TODO: the interest of this mode is rather dubious now. We could probably
3057 : // remove it
3058 0 : int VSICurlHandle::ReadMultiRangeSingleGet(int const nRanges,
3059 : void **const ppData,
3060 : const vsi_l_offset *const panOffsets,
3061 : const size_t *const panSizes)
3062 : {
3063 0 : std::string osRanges;
3064 0 : std::string osFirstRange;
3065 0 : std::string osLastRange;
3066 0 : int nMergedRanges = 0;
3067 0 : vsi_l_offset nTotalReqSize = 0;
3068 0 : for (int i = 0; i < nRanges; i++)
3069 : {
3070 0 : std::string osCurRange;
3071 0 : if (i != 0)
3072 0 : osRanges.append(",");
3073 0 : osCurRange = CPLSPrintf(CPL_FRMT_GUIB "-", panOffsets[i]);
3074 0 : while (i + 1 < nRanges &&
3075 0 : panOffsets[i] + panSizes[i] == panOffsets[i + 1])
3076 : {
3077 0 : nTotalReqSize += panSizes[i];
3078 0 : i++;
3079 : }
3080 0 : nTotalReqSize += panSizes[i];
3081 : osCurRange.append(
3082 0 : CPLSPrintf(CPL_FRMT_GUIB, panOffsets[i] + panSizes[i] - 1));
3083 0 : nMergedRanges++;
3084 :
3085 0 : osRanges += osCurRange;
3086 :
3087 0 : if (nMergedRanges == 1)
3088 0 : osFirstRange = osCurRange;
3089 0 : osLastRange = std::move(osCurRange);
3090 : }
3091 :
3092 : const char *pszMaxRanges =
3093 0 : CPLGetConfigOption("CPL_VSIL_CURL_MAX_RANGES", "250");
3094 0 : int nMaxRanges = atoi(pszMaxRanges);
3095 0 : if (nMaxRanges <= 0)
3096 0 : nMaxRanges = 250;
3097 0 : if (nMergedRanges > nMaxRanges)
3098 : {
3099 0 : const int nHalf = nRanges / 2;
3100 0 : const int nRet = ReadMultiRange(nHalf, ppData, panOffsets, panSizes);
3101 0 : if (nRet != 0)
3102 0 : return nRet;
3103 0 : return ReadMultiRange(nRanges - nHalf, ppData + nHalf,
3104 0 : panOffsets + nHalf, panSizes + nHalf);
3105 : }
3106 :
3107 0 : CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
3108 0 : CURL *hCurlHandle = curl_easy_init();
3109 :
3110 : struct curl_slist *headers =
3111 0 : VSICurlSetOptions(hCurlHandle, m_pszURL, m_aosHTTPOptions.List());
3112 :
3113 0 : WriteFuncStruct sWriteFuncData;
3114 0 : WriteFuncStruct sWriteFuncHeaderData;
3115 :
3116 0 : VSICURLInitWriteFuncStruct(&sWriteFuncData, this, pfnReadCbk,
3117 : pReadCbkUserData);
3118 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
3119 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3120 : VSICurlHandleWriteFunc);
3121 :
3122 0 : VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
3123 : nullptr);
3124 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3125 : &sWriteFuncHeaderData);
3126 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3127 : VSICurlHandleWriteFunc);
3128 0 : sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
3129 0 : sWriteFuncHeaderData.bMultiRange = nMergedRanges > 1;
3130 0 : if (nMergedRanges == 1)
3131 : {
3132 0 : sWriteFuncHeaderData.nStartOffset = panOffsets[0];
3133 0 : sWriteFuncHeaderData.nEndOffset = panOffsets[0] + nTotalReqSize - 1;
3134 : }
3135 :
3136 : if constexpr (ENABLE_DEBUG)
3137 : {
3138 0 : if (nMergedRanges == 1)
3139 0 : CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
3140 : osRanges.c_str(), m_pszURL);
3141 : else
3142 0 : CPLDebug(poFS->GetDebugKey(),
3143 : "Downloading %s, ..., %s (" CPL_FRMT_GUIB " bytes, %s)...",
3144 : osFirstRange.c_str(), osLastRange.c_str(),
3145 : static_cast<GUIntBig>(nTotalReqSize), m_pszURL);
3146 : }
3147 :
3148 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, osRanges.c_str());
3149 :
3150 0 : char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
3151 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
3152 :
3153 0 : headers = GetCurlHeaders("GET", headers);
3154 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
3155 :
3156 0 : VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
3157 :
3158 0 : VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
3159 :
3160 0 : curl_slist_free_all(headers);
3161 :
3162 0 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
3163 :
3164 0 : if (sWriteFuncData.bInterrupted)
3165 : {
3166 0 : bInterrupted = true;
3167 :
3168 0 : CPLFree(sWriteFuncData.pBuffer);
3169 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
3170 0 : curl_easy_cleanup(hCurlHandle);
3171 :
3172 0 : return -1;
3173 : }
3174 :
3175 0 : long response_code = 0;
3176 0 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
3177 :
3178 0 : if ((response_code != 200 && response_code != 206 && response_code != 225 &&
3179 0 : response_code != 226 && response_code != 426) ||
3180 0 : sWriteFuncHeaderData.bError)
3181 : {
3182 0 : if (response_code >= 400 && szCurlErrBuf[0] != '\0')
3183 : {
3184 0 : if (strcmp(szCurlErrBuf, "Couldn't use REST") == 0)
3185 0 : CPLError(
3186 : CE_Failure, CPLE_AppDefined,
3187 : "%d: %s, Range downloading not supported by this server!",
3188 : static_cast<int>(response_code), szCurlErrBuf);
3189 : else
3190 0 : CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
3191 : static_cast<int>(response_code), szCurlErrBuf);
3192 : }
3193 : /*
3194 : if( !bHasComputedFileSize && startOffset == 0 )
3195 : {
3196 : cachedFileProp->bHasComputedFileSize = bHasComputedFileSize = true;
3197 : cachedFileProp->fileSize = fileSize = 0;
3198 : cachedFileProp->eExists = eExists = EXIST_NO;
3199 : }
3200 : */
3201 0 : CPLFree(sWriteFuncData.pBuffer);
3202 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
3203 0 : curl_easy_cleanup(hCurlHandle);
3204 0 : return -1;
3205 : }
3206 :
3207 0 : char *pBuffer = sWriteFuncData.pBuffer;
3208 0 : size_t nSize = sWriteFuncData.nSize;
3209 :
3210 : // TODO(schwehr): Localize after removing gotos.
3211 0 : int nRet = -1;
3212 : char *pszBoundary;
3213 0 : std::string osBoundary;
3214 0 : char *pszNext = nullptr;
3215 0 : int iRange = 0;
3216 0 : int iPart = 0;
3217 0 : char *pszEOL = nullptr;
3218 :
3219 : /* -------------------------------------------------------------------- */
3220 : /* No multipart if a single range has been requested */
3221 : /* -------------------------------------------------------------------- */
3222 :
3223 0 : if (nMergedRanges == 1)
3224 : {
3225 0 : size_t nAccSize = 0;
3226 0 : if (static_cast<vsi_l_offset>(nSize) < nTotalReqSize)
3227 0 : goto end;
3228 :
3229 0 : for (int i = 0; i < nRanges; i++)
3230 : {
3231 0 : memcpy(ppData[i], pBuffer + nAccSize, panSizes[i]);
3232 0 : nAccSize += panSizes[i];
3233 : }
3234 :
3235 0 : nRet = 0;
3236 0 : goto end;
3237 : }
3238 :
3239 : /* -------------------------------------------------------------------- */
3240 : /* Extract boundary name */
3241 : /* -------------------------------------------------------------------- */
3242 :
3243 0 : pszBoundary = strstr(sWriteFuncHeaderData.pBuffer,
3244 : "Content-Type: multipart/byteranges; boundary=");
3245 0 : if (pszBoundary == nullptr)
3246 : {
3247 0 : CPLError(CE_Failure, CPLE_AppDefined, "Could not find '%s'",
3248 : "Content-Type: multipart/byteranges; boundary=");
3249 0 : goto end;
3250 : }
3251 :
3252 0 : pszBoundary += strlen("Content-Type: multipart/byteranges; boundary=");
3253 :
3254 0 : pszEOL = strchr(pszBoundary, '\r');
3255 0 : if (pszEOL)
3256 0 : *pszEOL = 0;
3257 0 : pszEOL = strchr(pszBoundary, '\n');
3258 0 : if (pszEOL)
3259 0 : *pszEOL = 0;
3260 :
3261 : /* Remove optional double-quote character around boundary name */
3262 0 : if (pszBoundary[0] == '"')
3263 : {
3264 0 : pszBoundary++;
3265 0 : char *pszLastDoubleQuote = strrchr(pszBoundary, '"');
3266 0 : if (pszLastDoubleQuote)
3267 0 : *pszLastDoubleQuote = 0;
3268 : }
3269 :
3270 0 : osBoundary = "--";
3271 0 : osBoundary += pszBoundary;
3272 :
3273 : /* -------------------------------------------------------------------- */
3274 : /* Find the start of the first chunk. */
3275 : /* -------------------------------------------------------------------- */
3276 0 : pszNext = strstr(pBuffer, osBoundary.c_str());
3277 0 : if (pszNext == nullptr)
3278 : {
3279 0 : CPLError(CE_Failure, CPLE_AppDefined, "No parts found.");
3280 0 : goto end;
3281 : }
3282 :
3283 0 : pszNext += osBoundary.size();
3284 0 : while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
3285 0 : pszNext++;
3286 0 : if (*pszNext == '\r')
3287 0 : pszNext++;
3288 0 : if (*pszNext == '\n')
3289 0 : pszNext++;
3290 :
3291 : /* -------------------------------------------------------------------- */
3292 : /* Loop over parts... */
3293 : /* -------------------------------------------------------------------- */
3294 0 : while (iPart < nRanges)
3295 : {
3296 : /* --------------------------------------------------------------------
3297 : */
3298 : /* Collect headers. */
3299 : /* --------------------------------------------------------------------
3300 : */
3301 0 : bool bExpectedRange = false;
3302 :
3303 0 : while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
3304 : {
3305 0 : pszEOL = strstr(pszNext, "\n");
3306 :
3307 0 : if (pszEOL == nullptr)
3308 : {
3309 0 : CPLError(CE_Failure, CPLE_AppDefined,
3310 : "Error while parsing multipart content (at line %d)",
3311 : __LINE__);
3312 0 : goto end;
3313 : }
3314 :
3315 0 : *pszEOL = '\0';
3316 0 : bool bRestoreAntislashR = false;
3317 0 : if (pszEOL - pszNext > 1 && pszEOL[-1] == '\r')
3318 : {
3319 0 : bRestoreAntislashR = true;
3320 0 : pszEOL[-1] = '\0';
3321 : }
3322 :
3323 0 : if (STARTS_WITH_CI(pszNext, "Content-Range: bytes "))
3324 : {
3325 0 : bExpectedRange = true; /* FIXME */
3326 : }
3327 :
3328 0 : if (bRestoreAntislashR)
3329 0 : pszEOL[-1] = '\r';
3330 0 : *pszEOL = '\n';
3331 :
3332 0 : pszNext = pszEOL + 1;
3333 : }
3334 :
3335 0 : if (!bExpectedRange)
3336 : {
3337 0 : CPLError(CE_Failure, CPLE_AppDefined,
3338 : "Error while parsing multipart content (at line %d)",
3339 : __LINE__);
3340 0 : goto end;
3341 : }
3342 :
3343 0 : if (*pszNext == '\r')
3344 0 : pszNext++;
3345 0 : if (*pszNext == '\n')
3346 0 : pszNext++;
3347 :
3348 : /* --------------------------------------------------------------------
3349 : */
3350 : /* Work out the data block size. */
3351 : /* --------------------------------------------------------------------
3352 : */
3353 0 : size_t nBytesAvail = nSize - (pszNext - pBuffer);
3354 :
3355 : while (true)
3356 : {
3357 0 : if (nBytesAvail < panSizes[iRange])
3358 : {
3359 0 : CPLError(CE_Failure, CPLE_AppDefined,
3360 : "Error while parsing multipart content (at line %d)",
3361 : __LINE__);
3362 0 : goto end;
3363 : }
3364 :
3365 0 : memcpy(ppData[iRange], pszNext, panSizes[iRange]);
3366 0 : pszNext += panSizes[iRange];
3367 0 : nBytesAvail -= panSizes[iRange];
3368 0 : if (iRange + 1 < nRanges &&
3369 0 : panOffsets[iRange] + panSizes[iRange] == panOffsets[iRange + 1])
3370 : {
3371 0 : iRange++;
3372 : }
3373 : else
3374 : {
3375 : break;
3376 : }
3377 : }
3378 :
3379 0 : iPart++;
3380 0 : iRange++;
3381 :
3382 0 : while (nBytesAvail > 0 &&
3383 0 : (*pszNext != '-' ||
3384 0 : strncmp(pszNext, osBoundary.c_str(), osBoundary.size()) != 0))
3385 : {
3386 0 : pszNext++;
3387 0 : nBytesAvail--;
3388 : }
3389 :
3390 0 : if (nBytesAvail == 0)
3391 : {
3392 0 : CPLError(CE_Failure, CPLE_AppDefined,
3393 : "Error while parsing multipart content (at line %d)",
3394 : __LINE__);
3395 0 : goto end;
3396 : }
3397 :
3398 0 : pszNext += osBoundary.size();
3399 0 : if (STARTS_WITH(pszNext, "--"))
3400 : {
3401 : // End of multipart.
3402 0 : break;
3403 : }
3404 :
3405 0 : if (*pszNext == '\r')
3406 0 : pszNext++;
3407 0 : if (*pszNext == '\n')
3408 0 : pszNext++;
3409 : else
3410 : {
3411 0 : CPLError(CE_Failure, CPLE_AppDefined,
3412 : "Error while parsing multipart content (at line %d)",
3413 : __LINE__);
3414 0 : goto end;
3415 : }
3416 : }
3417 :
3418 0 : if (iPart == nMergedRanges)
3419 0 : nRet = 0;
3420 : else
3421 0 : CPLError(CE_Failure, CPLE_AppDefined,
3422 : "Got only %d parts, where %d were expected", iPart,
3423 : nMergedRanges);
3424 :
3425 0 : end:
3426 0 : CPLFree(sWriteFuncData.pBuffer);
3427 0 : CPLFree(sWriteFuncHeaderData.pBuffer);
3428 0 : curl_easy_cleanup(hCurlHandle);
3429 :
3430 0 : return nRet;
3431 : }
3432 :
3433 : /************************************************************************/
3434 : /* PRead() */
3435 : /************************************************************************/
3436 :
3437 208 : size_t VSICurlHandle::PRead(void *pBuffer, size_t nSize,
3438 : vsi_l_offset nOffset) const
3439 : {
3440 : // Try to use AdviseRead ranges fetched asynchronously
3441 208 : if (!m_aoAdviseReadRanges.empty())
3442 : {
3443 144 : for (auto &poRange : m_aoAdviseReadRanges)
3444 : {
3445 288 : if (nOffset >= poRange->nStartOffset &&
3446 144 : nOffset + nSize <= poRange->nStartOffset + poRange->nSize)
3447 : {
3448 : {
3449 288 : std::unique_lock<std::mutex> oLock(poRange->oMutex);
3450 : // coverity[missing_lock:FALSE]
3451 257 : while (!poRange->bDone)
3452 : {
3453 113 : poRange->oCV.wait(oLock);
3454 : }
3455 : }
3456 144 : if (poRange->abyData.empty())
3457 144 : return 0;
3458 :
3459 : auto nEndOffset =
3460 144 : poRange->nStartOffset + poRange->abyData.size();
3461 144 : if (nOffset >= nEndOffset)
3462 0 : return 0;
3463 : const size_t nToCopy = static_cast<size_t>(
3464 144 : std::min<vsi_l_offset>(nSize, nEndOffset - nOffset));
3465 144 : memcpy(pBuffer,
3466 144 : poRange->abyData.data() +
3467 144 : static_cast<size_t>(nOffset - poRange->nStartOffset),
3468 : nToCopy);
3469 144 : return nToCopy;
3470 : }
3471 : }
3472 : }
3473 :
3474 : // poFS has a global mutex
3475 64 : poFS->GetCachedFileProp(m_pszURL, oFileProp);
3476 64 : if (oFileProp.eExists == EXIST_NO)
3477 0 : return static_cast<size_t>(-1);
3478 :
3479 128 : NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
3480 128 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
3481 128 : NetworkStatisticsAction oContextAction("PRead");
3482 :
3483 128 : CPLStringList aosHTTPOptions(m_aosHTTPOptions);
3484 128 : std::string osURL;
3485 : {
3486 64 : std::lock_guard<std::mutex> oLock(m_oMutex);
3487 64 : UpdateQueryString();
3488 : bool bHasExpired;
3489 64 : osURL = GetRedirectURLIfValid(bHasExpired, aosHTTPOptions);
3490 : }
3491 :
3492 64 : CURL *hCurlHandle = curl_easy_init();
3493 :
3494 : struct curl_slist *headers =
3495 64 : VSICurlSetOptions(hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
3496 :
3497 64 : WriteFuncStruct sWriteFuncData;
3498 64 : VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
3499 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
3500 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3501 : VSICurlHandleWriteFunc);
3502 :
3503 64 : WriteFuncStruct sWriteFuncHeaderData;
3504 64 : VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
3505 : nullptr);
3506 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3507 : &sWriteFuncHeaderData);
3508 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3509 : VSICurlHandleWriteFunc);
3510 64 : sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
3511 64 : sWriteFuncHeaderData.nStartOffset = nOffset;
3512 :
3513 64 : sWriteFuncHeaderData.nEndOffset = nOffset + nSize - 1;
3514 :
3515 64 : char rangeStr[512] = {};
3516 64 : snprintf(rangeStr, sizeof(rangeStr), CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3517 : sWriteFuncHeaderData.nStartOffset,
3518 : sWriteFuncHeaderData.nEndOffset);
3519 :
3520 : if constexpr (ENABLE_DEBUG)
3521 : {
3522 64 : CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...", rangeStr,
3523 : osURL.c_str());
3524 : }
3525 :
3526 64 : std::string osHeaderRange;
3527 64 : if (sWriteFuncHeaderData.bIsHTTP)
3528 : {
3529 64 : osHeaderRange = CPLSPrintf("Range: bytes=%s", rangeStr);
3530 : // So it gets included in Azure signature
3531 64 : headers = curl_slist_append(headers, osHeaderRange.data());
3532 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
3533 : }
3534 : else
3535 : {
3536 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, rangeStr);
3537 : }
3538 :
3539 : std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
3540 64 : szCurlErrBuf[0] = '\0';
3541 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
3542 : &szCurlErrBuf[0]);
3543 :
3544 : {
3545 64 : std::lock_guard<std::mutex> oLock(m_oMutex);
3546 : headers =
3547 64 : const_cast<VSICurlHandle *>(this)->GetCurlHeaders("GET", headers);
3548 : }
3549 64 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
3550 :
3551 64 : CURLM *hMultiHandle = poFS->GetCurlMultiHandleFor(osURL);
3552 64 : VSICURLMultiPerform(hMultiHandle, hCurlHandle, &m_bInterrupt);
3553 :
3554 : {
3555 128 : std::lock_guard<std::mutex> oLock(m_oMutex);
3556 64 : const_cast<VSICurlHandle *>(this)->UpdateRedirectInfo(
3557 : hCurlHandle, sWriteFuncHeaderData);
3558 : }
3559 :
3560 64 : long response_code = 0;
3561 64 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
3562 :
3563 64 : if (ENABLE_DEBUG && szCurlErrBuf[0] != '\0')
3564 : {
3565 0 : const char *pszErrorMsg = &szCurlErrBuf[0];
3566 0 : CPLDebug(poFS->GetDebugKey(), "PRead(%s), %s: response_code=%d, msg=%s",
3567 : osURL.c_str(), rangeStr, static_cast<int>(response_code),
3568 : pszErrorMsg);
3569 : }
3570 :
3571 : size_t nRet;
3572 64 : if ((response_code != 206 && response_code != 225) ||
3573 64 : sWriteFuncData.nSize == 0)
3574 : {
3575 0 : if (!m_bInterrupt)
3576 : {
3577 0 : CPLDebug(poFS->GetDebugKey(),
3578 : "Request for %s failed with response_code=%ld", rangeStr,
3579 : response_code);
3580 : }
3581 0 : nRet = static_cast<size_t>(-1);
3582 : }
3583 : else
3584 : {
3585 64 : nRet = std::min(sWriteFuncData.nSize, nSize);
3586 64 : if (nRet > 0)
3587 64 : memcpy(pBuffer, sWriteFuncData.pBuffer, nRet);
3588 : }
3589 :
3590 64 : VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
3591 64 : curl_easy_cleanup(hCurlHandle);
3592 64 : CPLFree(sWriteFuncData.pBuffer);
3593 64 : CPLFree(sWriteFuncHeaderData.pBuffer);
3594 64 : curl_slist_free_all(headers);
3595 :
3596 64 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
3597 :
3598 : #if 0
3599 : if( ENABLE_DEBUG )
3600 : CPLDebug(poFS->GetDebugKey(), "Download completed");
3601 : #endif
3602 :
3603 64 : return nRet;
3604 : }
3605 :
3606 : /************************************************************************/
3607 : /* GetAdviseReadTotalBytesLimit() */
3608 : /************************************************************************/
3609 :
3610 16 : size_t VSICurlHandle::GetAdviseReadTotalBytesLimit() const
3611 : {
3612 : return static_cast<size_t>(std::min<unsigned long long>(
3613 48 : std::numeric_limits<size_t>::max(),
3614 : // 100 MB
3615 16 : std::strtoull(
3616 : CPLGetConfigOption("CPL_VSIL_CURL_ADVISE_READ_TOTAL_BYTES_LIMIT",
3617 : "104857600"),
3618 16 : nullptr, 10)));
3619 : }
3620 :
3621 : /************************************************************************/
3622 : /* VSICURLMultiInit() */
3623 : /************************************************************************/
3624 :
3625 330 : static CURLM *VSICURLMultiInit()
3626 : {
3627 330 : CURLM *hCurlMultiHandle = curl_multi_init();
3628 :
3629 330 : if (const char *pszMAXCONNECTS =
3630 330 : CPLGetConfigOption("GDAL_HTTP_MAX_CACHED_CONNECTIONS", nullptr))
3631 : {
3632 1 : curl_multi_setopt(hCurlMultiHandle, CURLMOPT_MAXCONNECTS,
3633 : atoi(pszMAXCONNECTS));
3634 : }
3635 :
3636 330 : if (const char *pszMAX_TOTAL_CONNECTIONS =
3637 330 : CPLGetConfigOption("GDAL_HTTP_MAX_TOTAL_CONNECTIONS", nullptr))
3638 : {
3639 1 : curl_multi_setopt(hCurlMultiHandle, CURLMOPT_MAX_TOTAL_CONNECTIONS,
3640 : atoi(pszMAX_TOTAL_CONNECTIONS));
3641 : }
3642 :
3643 330 : return hCurlMultiHandle;
3644 : }
3645 :
3646 : /************************************************************************/
3647 : /* AdviseRead() */
3648 : /************************************************************************/
3649 :
3650 8 : void VSICurlHandle::AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
3651 : const size_t *panSizes)
3652 : {
3653 8 : if (!CPLTestBool(
3654 : CPLGetConfigOption("GDAL_HTTP_ENABLE_ADVISE_READ", "TRUE")))
3655 2 : return;
3656 :
3657 6 : if (m_oThreadAdviseRead.joinable())
3658 : {
3659 1 : m_oThreadAdviseRead.join();
3660 : }
3661 :
3662 : // Give up if we need to allocate too much memory
3663 6 : vsi_l_offset nMaxSize = 0;
3664 6 : const size_t nLimit = GetAdviseReadTotalBytesLimit();
3665 150 : for (int i = 0; i < nRanges; ++i)
3666 : {
3667 144 : if (panSizes[i] > nLimit - nMaxSize)
3668 : {
3669 0 : CPLDebug(poFS->GetDebugKey(),
3670 : "Trying to request too many bytes in AdviseRead()");
3671 0 : return;
3672 : }
3673 144 : nMaxSize += panSizes[i];
3674 : }
3675 :
3676 6 : UpdateQueryString();
3677 :
3678 6 : bool bHasExpired = false;
3679 6 : CPLStringList aosHTTPOptions(m_aosHTTPOptions);
3680 : const std::string l_osURL(
3681 6 : GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
3682 6 : if (bHasExpired)
3683 : {
3684 0 : return;
3685 : }
3686 :
3687 6 : const bool bMergeConsecutiveRanges = CPLTestBool(
3688 : CPLGetConfigOption("GDAL_HTTP_MERGE_CONSECUTIVE_RANGES", "TRUE"));
3689 :
3690 : try
3691 : {
3692 6 : m_aoAdviseReadRanges.clear();
3693 6 : m_aoAdviseReadRanges.reserve(nRanges);
3694 12 : for (int i = 0; i < nRanges;)
3695 : {
3696 6 : int iNext = i;
3697 : // Identify consecutive ranges
3698 6 : constexpr size_t SIZE_COG_MARKERS = 2 * sizeof(uint32_t);
3699 6 : auto nEndOffset = panOffsets[iNext] + panSizes[iNext];
3700 144 : while (bMergeConsecutiveRanges && iNext + 1 < nRanges &&
3701 138 : panOffsets[iNext + 1] > panOffsets[iNext] &&
3702 138 : panOffsets[iNext] + panSizes[iNext] + SIZE_COG_MARKERS >=
3703 282 : panOffsets[iNext + 1] &&
3704 138 : panOffsets[iNext + 1] + panSizes[iNext + 1] > nEndOffset)
3705 : {
3706 138 : iNext++;
3707 138 : nEndOffset = panOffsets[iNext] + panSizes[iNext];
3708 : }
3709 6 : CPLAssert(panOffsets[i] <= nEndOffset);
3710 6 : const size_t nSize =
3711 6 : static_cast<size_t>(nEndOffset - panOffsets[i]);
3712 :
3713 6 : if (nSize == 0)
3714 : {
3715 0 : i = iNext + 1;
3716 0 : continue;
3717 : }
3718 :
3719 : auto newAdviseReadRange =
3720 6 : std::make_unique<AdviseReadRange>(m_oRetryParameters);
3721 6 : newAdviseReadRange->nStartOffset = panOffsets[i];
3722 6 : newAdviseReadRange->nSize = nSize;
3723 6 : newAdviseReadRange->abyData.resize(nSize);
3724 6 : m_aoAdviseReadRanges.push_back(std::move(newAdviseReadRange));
3725 :
3726 6 : i = iNext + 1;
3727 : }
3728 : }
3729 0 : catch (const std::exception &)
3730 : {
3731 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
3732 : "Out of memory in VSICurlHandle::AdviseRead()");
3733 0 : m_aoAdviseReadRanges.clear();
3734 : }
3735 :
3736 6 : if (m_aoAdviseReadRanges.empty())
3737 0 : return;
3738 :
3739 : #ifdef DEBUG
3740 6 : CPLDebug(poFS->GetDebugKey(), "AdviseRead(): fetching %u ranges",
3741 6 : static_cast<unsigned>(m_aoAdviseReadRanges.size()));
3742 : #endif
3743 :
3744 12 : const auto task = [this, aosHTTPOptions = std::move(aosHTTPOptions)](
3745 477 : const std::string &osURL)
3746 : {
3747 6 : if (!m_hCurlMultiHandleForAdviseRead)
3748 5 : m_hCurlMultiHandleForAdviseRead = VSICURLMultiInit();
3749 :
3750 12 : NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
3751 12 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
3752 12 : NetworkStatisticsAction oContextAction("AdviseRead");
3753 :
3754 : #ifdef CURLPIPE_MULTIPLEX
3755 : // Enable HTTP/2 multiplexing (ignored if an older version of HTTP is
3756 : // used)
3757 : // Not that this does not enable HTTP/1.1 pipeling, which is not
3758 : // recommended for example by Google Cloud Storage.
3759 : // For HTTP/1.1, parallel connections work better since you can get
3760 : // results out of order.
3761 6 : if (CPLTestBool(CPLGetConfigOption("GDAL_HTTP_MULTIPLEX", "YES")))
3762 : {
3763 6 : curl_multi_setopt(m_hCurlMultiHandleForAdviseRead,
3764 : CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
3765 : }
3766 : #endif
3767 :
3768 6 : size_t nTotalDownloaded = 0;
3769 :
3770 : while (true)
3771 : {
3772 :
3773 8 : std::vector<CURL *> aHandles;
3774 : std::vector<WriteFuncStruct> asWriteFuncData(
3775 8 : m_aoAdviseReadRanges.size());
3776 : std::vector<WriteFuncStruct> asWriteFuncHeaderData(
3777 8 : m_aoAdviseReadRanges.size());
3778 8 : std::vector<char *> apszRanges;
3779 8 : std::vector<struct curl_slist *> aHeaders;
3780 :
3781 : struct CurlErrBuffer
3782 : {
3783 : std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
3784 : };
3785 : std::vector<CurlErrBuffer> asCurlErrors(
3786 8 : m_aoAdviseReadRanges.size());
3787 :
3788 8 : std::map<CURL *, size_t> oMapHandleToIdx;
3789 16 : for (size_t i = 0; i < m_aoAdviseReadRanges.size(); ++i)
3790 : {
3791 8 : if (!m_aoAdviseReadRanges[i]->bToRetry)
3792 : {
3793 0 : aHandles.push_back(nullptr);
3794 0 : apszRanges.push_back(nullptr);
3795 0 : aHeaders.push_back(nullptr);
3796 0 : continue;
3797 : }
3798 8 : m_aoAdviseReadRanges[i]->bToRetry = false;
3799 :
3800 8 : CURL *hCurlHandle = curl_easy_init();
3801 8 : oMapHandleToIdx[hCurlHandle] = i;
3802 8 : aHandles.push_back(hCurlHandle);
3803 :
3804 : // As the multi-range request is likely not the first one, we don't
3805 : // need to wait as we already know if pipelining is possible
3806 : // unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_PIPEWAIT, 1);
3807 :
3808 8 : struct curl_slist *headers = VSICurlSetOptions(
3809 8 : hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
3810 :
3811 8 : VSICURLInitWriteFuncStruct(&asWriteFuncData[i], this,
3812 : pfnReadCbk, pReadCbkUserData);
3813 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
3814 : &asWriteFuncData[i]);
3815 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3816 : VSICurlHandleWriteFunc);
3817 :
3818 8 : VSICURLInitWriteFuncStruct(&asWriteFuncHeaderData[i], nullptr,
3819 : nullptr, nullptr);
3820 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3821 : &asWriteFuncHeaderData[i]);
3822 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3823 : VSICurlHandleWriteFunc);
3824 16 : asWriteFuncHeaderData[i].bIsHTTP =
3825 8 : STARTS_WITH(m_pszURL, "http");
3826 16 : asWriteFuncHeaderData[i].nStartOffset =
3827 8 : m_aoAdviseReadRanges[i]->nStartOffset;
3828 :
3829 16 : asWriteFuncHeaderData[i].nEndOffset =
3830 8 : m_aoAdviseReadRanges[i]->nStartOffset +
3831 8 : m_aoAdviseReadRanges[i]->nSize - 1;
3832 :
3833 8 : char rangeStr[512] = {};
3834 16 : snprintf(rangeStr, sizeof(rangeStr),
3835 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3836 8 : asWriteFuncHeaderData[i].nStartOffset,
3837 8 : asWriteFuncHeaderData[i].nEndOffset);
3838 :
3839 : if constexpr (ENABLE_DEBUG)
3840 : {
3841 8 : CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
3842 : rangeStr, osURL.c_str());
3843 : }
3844 :
3845 8 : if (asWriteFuncHeaderData[i].bIsHTTP)
3846 : {
3847 : std::string osHeaderRange(
3848 8 : CPLSPrintf("Range: bytes=%s", rangeStr));
3849 : // So it gets included in Azure signature
3850 8 : char *pszRange = CPLStrdup(osHeaderRange.c_str());
3851 8 : apszRanges.push_back(pszRange);
3852 8 : headers = curl_slist_append(headers, pszRange);
3853 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
3854 : nullptr);
3855 : }
3856 : else
3857 : {
3858 0 : apszRanges.push_back(nullptr);
3859 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
3860 : rangeStr);
3861 : }
3862 :
3863 8 : asCurlErrors[i].szCurlErrBuf[0] = '\0';
3864 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
3865 : &asCurlErrors[i].szCurlErrBuf[0]);
3866 :
3867 8 : headers = GetCurlHeaders("GET", headers);
3868 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
3869 : headers);
3870 8 : aHeaders.push_back(headers);
3871 8 : curl_multi_add_handle(m_hCurlMultiHandleForAdviseRead,
3872 : hCurlHandle);
3873 : }
3874 :
3875 8 : const auto DealWithRequest = [this, &osURL, &nTotalDownloaded,
3876 : &oMapHandleToIdx, &asCurlErrors,
3877 : &asWriteFuncHeaderData,
3878 116 : &asWriteFuncData](CURL *hCurlHandle)
3879 : {
3880 8 : auto oIter = oMapHandleToIdx.find(hCurlHandle);
3881 8 : CPLAssert(oIter != oMapHandleToIdx.end());
3882 8 : const auto iReq = oIter->second;
3883 :
3884 8 : long response_code = 0;
3885 8 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE,
3886 : &response_code);
3887 :
3888 8 : if (ENABLE_DEBUG && asCurlErrors[iReq].szCurlErrBuf[0] != '\0')
3889 : {
3890 0 : char rangeStr[512] = {};
3891 0 : snprintf(rangeStr, sizeof(rangeStr),
3892 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3893 0 : asWriteFuncHeaderData[iReq].nStartOffset,
3894 0 : asWriteFuncHeaderData[iReq].nEndOffset);
3895 :
3896 : const char *pszErrorMsg =
3897 0 : &asCurlErrors[iReq].szCurlErrBuf[0];
3898 0 : CPLDebug(poFS->GetDebugKey(),
3899 : "ReadMultiRange(%s), %s: response_code=%d, msg=%s",
3900 : osURL.c_str(), rangeStr,
3901 : static_cast<int>(response_code), pszErrorMsg);
3902 : }
3903 :
3904 8 : bool bToRetry = false;
3905 14 : if ((response_code != 206 && response_code != 225) ||
3906 6 : asWriteFuncHeaderData[iReq].nEndOffset + 1 !=
3907 6 : asWriteFuncHeaderData[iReq].nStartOffset +
3908 6 : asWriteFuncData[iReq].nSize)
3909 : {
3910 2 : char rangeStr[512] = {};
3911 4 : snprintf(rangeStr, sizeof(rangeStr),
3912 : CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3913 2 : asWriteFuncHeaderData[iReq].nStartOffset,
3914 2 : asWriteFuncHeaderData[iReq].nEndOffset);
3915 :
3916 : // Look if we should attempt a retry
3917 4 : if (m_aoAdviseReadRanges[iReq]->retryContext.CanRetry(
3918 : static_cast<int>(response_code),
3919 2 : asWriteFuncData[iReq].pBuffer,
3920 2 : &asCurlErrors[iReq].szCurlErrBuf[0]))
3921 : {
3922 2 : CPLError(CE_Warning, CPLE_AppDefined,
3923 : "HTTP error code for %s range %s: %d. "
3924 : "Retrying again in %.1f secs",
3925 : osURL.c_str(), rangeStr,
3926 : static_cast<int>(response_code),
3927 2 : m_aoAdviseReadRanges[iReq]
3928 2 : ->retryContext.GetCurrentDelay());
3929 2 : m_aoAdviseReadRanges[iReq]->dfSleepDelay =
3930 2 : m_aoAdviseReadRanges[iReq]
3931 2 : ->retryContext.GetCurrentDelay();
3932 2 : bToRetry = true;
3933 : }
3934 : else
3935 : {
3936 0 : CPLError(CE_Failure, CPLE_AppDefined,
3937 : "Request for %s range %s failed with "
3938 : "response_code=%ld",
3939 : osURL.c_str(), rangeStr, response_code);
3940 : }
3941 : }
3942 : else
3943 : {
3944 6 : const size_t nSize = asWriteFuncData[iReq].nSize;
3945 6 : memcpy(&m_aoAdviseReadRanges[iReq]->abyData[0],
3946 6 : asWriteFuncData[iReq].pBuffer, nSize);
3947 6 : m_aoAdviseReadRanges[iReq]->abyData.resize(nSize);
3948 :
3949 6 : nTotalDownloaded += nSize;
3950 : }
3951 :
3952 8 : m_aoAdviseReadRanges[iReq]->bToRetry = bToRetry;
3953 :
3954 8 : if (!bToRetry)
3955 : {
3956 : std::lock_guard<std::mutex> oLock(
3957 12 : m_aoAdviseReadRanges[iReq]->oMutex);
3958 6 : m_aoAdviseReadRanges[iReq]->bDone = true;
3959 6 : m_aoAdviseReadRanges[iReq]->oCV.notify_all();
3960 : }
3961 8 : };
3962 :
3963 8 : int repeats = 0;
3964 :
3965 8 : void *old_handler = CPLHTTPIgnoreSigPipe();
3966 : while (true)
3967 : {
3968 : int still_running;
3969 90 : while (curl_multi_perform(m_hCurlMultiHandleForAdviseRead,
3970 90 : &still_running) ==
3971 : CURLM_CALL_MULTI_PERFORM)
3972 : {
3973 : // loop
3974 : }
3975 90 : if (!still_running)
3976 : {
3977 8 : break;
3978 : }
3979 :
3980 : CURLMsg *msg;
3981 0 : do
3982 : {
3983 82 : int msgq = 0;
3984 82 : msg = curl_multi_info_read(m_hCurlMultiHandleForAdviseRead,
3985 : &msgq);
3986 82 : if (msg && (msg->msg == CURLMSG_DONE))
3987 : {
3988 0 : DealWithRequest(msg->easy_handle);
3989 : }
3990 82 : } while (msg);
3991 :
3992 82 : CPLMultiPerformWait(m_hCurlMultiHandleForAdviseRead, repeats);
3993 82 : }
3994 8 : CPLHTTPRestoreSigPipeHandler(old_handler);
3995 :
3996 8 : bool bRetry = false;
3997 8 : double dfDelay = 0.0;
3998 16 : for (size_t i = 0; i < m_aoAdviseReadRanges.size(); ++i)
3999 : {
4000 : bool bReqDone;
4001 : {
4002 : // To please Coverity Scan
4003 : std::lock_guard<std::mutex> oLock(
4004 8 : m_aoAdviseReadRanges[i]->oMutex);
4005 8 : bReqDone = m_aoAdviseReadRanges[i]->bDone;
4006 : }
4007 8 : if (!bReqDone && !m_aoAdviseReadRanges[i]->bToRetry)
4008 : {
4009 8 : DealWithRequest(aHandles[i]);
4010 : }
4011 8 : if (m_aoAdviseReadRanges[i]->bToRetry)
4012 2 : dfDelay = std::max(dfDelay,
4013 2 : m_aoAdviseReadRanges[i]->dfSleepDelay);
4014 8 : bRetry = bRetry || m_aoAdviseReadRanges[i]->bToRetry;
4015 8 : if (aHandles[i])
4016 : {
4017 8 : curl_multi_remove_handle(m_hCurlMultiHandleForAdviseRead,
4018 8 : aHandles[i]);
4019 8 : VSICURLResetHeaderAndWriterFunctions(aHandles[i]);
4020 8 : curl_easy_cleanup(aHandles[i]);
4021 : }
4022 8 : CPLFree(apszRanges[i]);
4023 8 : CPLFree(asWriteFuncData[i].pBuffer);
4024 8 : CPLFree(asWriteFuncHeaderData[i].pBuffer);
4025 8 : if (aHeaders[i])
4026 8 : curl_slist_free_all(aHeaders[i]);
4027 : }
4028 8 : if (!bRetry)
4029 6 : break;
4030 2 : CPLSleep(dfDelay);
4031 2 : }
4032 :
4033 6 : NetworkStatisticsLogger::LogGET(nTotalDownloaded);
4034 12 : };
4035 :
4036 6 : m_oThreadAdviseRead = std::thread(task, l_osURL);
4037 : }
4038 :
4039 : /************************************************************************/
4040 : /* Write() */
4041 : /************************************************************************/
4042 :
4043 0 : size_t VSICurlHandle::Write(const void * /* pBuffer */, size_t /* nBytes */)
4044 : {
4045 0 : return 0;
4046 : }
4047 :
4048 : /************************************************************************/
4049 : /* ClearErr() */
4050 : /************************************************************************/
4051 :
4052 1 : void VSICurlHandle::ClearErr()
4053 :
4054 : {
4055 1 : bEOF = false;
4056 1 : bError = false;
4057 1 : }
4058 :
4059 : /************************************************************************/
4060 : /* Error() */
4061 : /************************************************************************/
4062 :
4063 9 : int VSICurlHandle::Error()
4064 :
4065 : {
4066 9 : return bError ? TRUE : FALSE;
4067 : }
4068 :
4069 : /************************************************************************/
4070 : /* Eof() */
4071 : /************************************************************************/
4072 :
4073 16 : int VSICurlHandle::Eof()
4074 :
4075 : {
4076 16 : return bEOF ? TRUE : FALSE;
4077 : }
4078 :
4079 : /************************************************************************/
4080 : /* Flush() */
4081 : /************************************************************************/
4082 :
4083 2 : int VSICurlHandle::Flush()
4084 : {
4085 2 : return 0;
4086 : }
4087 :
4088 : /************************************************************************/
4089 : /* Close() */
4090 : /************************************************************************/
4091 :
4092 838 : int VSICurlHandle::Close()
4093 : {
4094 838 : return 0;
4095 : }
4096 :
4097 : /************************************************************************/
4098 : /* VSICurlFilesystemHandlerBase() */
4099 : /************************************************************************/
4100 :
4101 16784 : VSICurlFilesystemHandlerBase::VSICurlFilesystemHandlerBase()
4102 16784 : : oCacheDirList{1024, 0}
4103 : {
4104 16784 : }
4105 :
4106 : /************************************************************************/
4107 : /* CachedConnection */
4108 : /************************************************************************/
4109 :
4110 : namespace
4111 : {
4112 : struct CachedConnection
4113 : {
4114 : CURLM *hCurlMultiHandle = nullptr;
4115 : void clear();
4116 :
4117 10440 : ~CachedConnection()
4118 10440 : {
4119 10440 : clear();
4120 10440 : }
4121 : };
4122 : } // namespace
4123 :
4124 : #ifdef _WIN32
4125 : // Currently thread_local and C++ objects don't work well with DLL on Windows
4126 : static void FreeCachedConnection(void *pData)
4127 : {
4128 : delete static_cast<
4129 : std::map<VSICurlFilesystemHandlerBase *, CachedConnection> *>(pData);
4130 : }
4131 :
4132 : // Per-thread and per-filesystem Curl connection cache.
4133 : static std::map<VSICurlFilesystemHandlerBase *, CachedConnection> &
4134 : GetConnectionCache()
4135 : {
4136 : static std::map<VSICurlFilesystemHandlerBase *, CachedConnection>
4137 : dummyCache;
4138 : int bMemoryErrorOccurred = false;
4139 : void *pData =
4140 : CPLGetTLSEx(CTLS_VSICURL_CACHEDCONNECTION, &bMemoryErrorOccurred);
4141 : if (bMemoryErrorOccurred)
4142 : {
4143 : return dummyCache;
4144 : }
4145 : if (pData == nullptr)
4146 : {
4147 : auto cachedConnection =
4148 : new std::map<VSICurlFilesystemHandlerBase *, CachedConnection>();
4149 : CPLSetTLSWithFreeFuncEx(CTLS_VSICURL_CACHEDCONNECTION, cachedConnection,
4150 : FreeCachedConnection, &bMemoryErrorOccurred);
4151 : if (bMemoryErrorOccurred)
4152 : {
4153 : delete cachedConnection;
4154 : return dummyCache;
4155 : }
4156 : return *cachedConnection;
4157 : }
4158 : return *static_cast<
4159 : std::map<VSICurlFilesystemHandlerBase *, CachedConnection> *>(pData);
4160 : }
4161 : #else
4162 : static thread_local std::map<VSICurlFilesystemHandlerBase *, CachedConnection>
4163 : g_tls_connectionCache;
4164 :
4165 : static std::map<VSICurlFilesystemHandlerBase *, CachedConnection> &
4166 30725 : GetConnectionCache()
4167 : {
4168 30725 : return g_tls_connectionCache;
4169 : }
4170 : #endif
4171 :
4172 : /************************************************************************/
4173 : /* clear() */
4174 : /************************************************************************/
4175 :
4176 29316 : void CachedConnection::clear()
4177 : {
4178 29316 : if (hCurlMultiHandle)
4179 : {
4180 294 : VSICURLMultiCleanup(hCurlMultiHandle);
4181 294 : hCurlMultiHandle = nullptr;
4182 : }
4183 29316 : }
4184 :
4185 : /************************************************************************/
4186 : /* ~VSICurlFilesystemHandlerBase() */
4187 : /************************************************************************/
4188 :
4189 10408 : VSICurlFilesystemHandlerBase::~VSICurlFilesystemHandlerBase()
4190 : {
4191 10408 : VSICurlFilesystemHandlerBase::ClearCache();
4192 10408 : GetConnectionCache().erase(this);
4193 :
4194 10408 : if (hMutex != nullptr)
4195 10408 : CPLDestroyMutex(hMutex);
4196 10408 : hMutex = nullptr;
4197 10408 : }
4198 :
4199 : /************************************************************************/
4200 : /* AllowCachedDataFor() */
4201 : /************************************************************************/
4202 :
4203 3825 : bool VSICurlFilesystemHandlerBase::AllowCachedDataFor(const char *pszFilename)
4204 : {
4205 3825 : bool bCachedAllowed = true;
4206 3825 : char **papszTokens = CSLTokenizeString2(
4207 : CPLGetConfigOption("CPL_VSIL_CURL_NON_CACHED", ""), ":", 0);
4208 3865 : for (int i = 0; papszTokens && papszTokens[i]; i++)
4209 : {
4210 120 : if (STARTS_WITH(pszFilename, papszTokens[i]))
4211 : {
4212 80 : bCachedAllowed = false;
4213 80 : break;
4214 : }
4215 : }
4216 3825 : CSLDestroy(papszTokens);
4217 3825 : return bCachedAllowed;
4218 : }
4219 :
4220 : /************************************************************************/
4221 : /* GetCurlMultiHandleFor() */
4222 : /************************************************************************/
4223 :
4224 1441 : CURLM *VSICurlFilesystemHandlerBase::GetCurlMultiHandleFor(
4225 : const std::string & /*osURL*/)
4226 : {
4227 1441 : auto &conn = GetConnectionCache()[this];
4228 1441 : if (conn.hCurlMultiHandle == nullptr)
4229 : {
4230 325 : conn.hCurlMultiHandle = VSICURLMultiInit();
4231 : }
4232 1441 : return conn.hCurlMultiHandle;
4233 : }
4234 :
4235 : /************************************************************************/
4236 : /* GetRegionCache() */
4237 : /************************************************************************/
4238 :
4239 : VSICurlFilesystemHandlerBase::RegionCacheType *
4240 193674 : VSICurlFilesystemHandlerBase::GetRegionCache()
4241 : {
4242 : // should be called under hMutex taken
4243 193674 : if (m_poRegionCacheDoNotUseDirectly == nullptr)
4244 : {
4245 10433 : m_poRegionCacheDoNotUseDirectly.reset(
4246 10433 : new RegionCacheType(static_cast<size_t>(GetMaxRegions())));
4247 : }
4248 193674 : return m_poRegionCacheDoNotUseDirectly.get();
4249 : }
4250 :
4251 : /************************************************************************/
4252 : /* GetRegion() */
4253 : /************************************************************************/
4254 :
4255 : std::shared_ptr<std::string>
4256 166670 : VSICurlFilesystemHandlerBase::GetRegion(const char *pszURL,
4257 : vsi_l_offset nFileOffsetStart)
4258 : {
4259 333340 : CPLMutexHolder oHolder(&hMutex);
4260 :
4261 166670 : const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
4262 166670 : nFileOffsetStart =
4263 166670 : (nFileOffsetStart / knDOWNLOAD_CHUNK_SIZE) * knDOWNLOAD_CHUNK_SIZE;
4264 :
4265 166670 : std::shared_ptr<std::string> out;
4266 333340 : if (GetRegionCache()->tryGet(
4267 333340 : FilenameOffsetPair(std::string(pszURL), nFileOffsetStart), out))
4268 : {
4269 148089 : return out;
4270 : }
4271 :
4272 18581 : return nullptr;
4273 : }
4274 :
4275 : /************************************************************************/
4276 : /* AddRegion() */
4277 : /************************************************************************/
4278 :
4279 7920 : void VSICurlFilesystemHandlerBase::AddRegion(const char *pszURL,
4280 : vsi_l_offset nFileOffsetStart,
4281 : size_t nSize, const char *pData)
4282 : {
4283 15840 : CPLMutexHolder oHolder(&hMutex);
4284 :
4285 7920 : auto value = std::make_shared<std::string>();
4286 7920 : value->assign(pData, nSize);
4287 : GetRegionCache()->insert(
4288 15840 : FilenameOffsetPair(std::string(pszURL), nFileOffsetStart),
4289 15840 : std::move(value));
4290 7920 : }
4291 :
4292 : /************************************************************************/
4293 : /* GetCachedFileProp() */
4294 : /************************************************************************/
4295 :
4296 152911 : bool VSICurlFilesystemHandlerBase::GetCachedFileProp(const char *pszURL,
4297 : FileProp &oFileProp)
4298 : {
4299 152911 : return VSICURLGetCachedFileProp(pszURL, oFileProp);
4300 : }
4301 :
4302 : /************************************************************************/
4303 : /* SetCachedFileProp() */
4304 : /************************************************************************/
4305 :
4306 1009 : void VSICurlFilesystemHandlerBase::SetCachedFileProp(const char *pszURL,
4307 : FileProp &oFileProp)
4308 : {
4309 1009 : VSICURLSetCachedFileProp(pszURL, oFileProp);
4310 1009 : }
4311 :
4312 : /************************************************************************/
4313 : /* GetCachedDirList() */
4314 : /************************************************************************/
4315 :
4316 648 : bool VSICurlFilesystemHandlerBase::GetCachedDirList(
4317 : const char *pszURL, CachedDirList &oCachedDirList)
4318 : {
4319 648 : CPLMutexHolder oHolder(&hMutex);
4320 :
4321 1469 : return oCacheDirList.tryGet(std::string(pszURL), oCachedDirList) &&
4322 : // Let a chance to use new auth parameters
4323 173 : gnGenerationAuthParameters ==
4324 1469 : oCachedDirList.nGenerationAuthParameters;
4325 : }
4326 :
4327 : /************************************************************************/
4328 : /* SetCachedDirList() */
4329 : /************************************************************************/
4330 :
4331 184 : void VSICurlFilesystemHandlerBase::SetCachedDirList(
4332 : const char *pszURL, CachedDirList &oCachedDirList)
4333 : {
4334 368 : CPLMutexHolder oHolder(&hMutex);
4335 :
4336 368 : std::string key(pszURL);
4337 368 : CachedDirList oldValue;
4338 184 : if (oCacheDirList.tryGet(key, oldValue))
4339 : {
4340 9 : nCachedFilesInDirList -= oldValue.oFileList.size();
4341 9 : oCacheDirList.remove(key);
4342 : }
4343 :
4344 184 : while ((!oCacheDirList.empty() &&
4345 65 : nCachedFilesInDirList + oCachedDirList.oFileList.size() >
4346 368 : 1024 * 1024) ||
4347 184 : oCacheDirList.size() == oCacheDirList.getMaxAllowedSize())
4348 : {
4349 0 : std::string oldestKey;
4350 0 : oCacheDirList.getOldestEntry(oldestKey, oldValue);
4351 0 : nCachedFilesInDirList -= oldValue.oFileList.size();
4352 0 : oCacheDirList.remove(oldestKey);
4353 : }
4354 184 : oCachedDirList.nGenerationAuthParameters = gnGenerationAuthParameters;
4355 :
4356 184 : nCachedFilesInDirList += oCachedDirList.oFileList.size();
4357 184 : oCacheDirList.insert(key, oCachedDirList);
4358 184 : }
4359 :
4360 : /************************************************************************/
4361 : /* ExistsInCacheDirList() */
4362 : /************************************************************************/
4363 :
4364 13 : bool VSICurlFilesystemHandlerBase::ExistsInCacheDirList(
4365 : const std::string &osDirname, bool *pbIsDir)
4366 : {
4367 26 : CachedDirList cachedDirList;
4368 13 : if (GetCachedDirList(osDirname.c_str(), cachedDirList))
4369 : {
4370 0 : if (pbIsDir)
4371 0 : *pbIsDir = !cachedDirList.oFileList.empty();
4372 0 : return false;
4373 : }
4374 : else
4375 : {
4376 13 : if (pbIsDir)
4377 13 : *pbIsDir = false;
4378 13 : return false;
4379 : }
4380 : }
4381 :
4382 : /************************************************************************/
4383 : /* InvalidateCachedData() */
4384 : /************************************************************************/
4385 :
4386 201 : void VSICurlFilesystemHandlerBase::InvalidateCachedData(const char *pszURL)
4387 : {
4388 402 : CPLMutexHolder oHolder(&hMutex);
4389 :
4390 201 : VSICURLInvalidateCachedFileProp(pszURL);
4391 :
4392 : // Invalidate all cached regions for this URL
4393 402 : std::list<FilenameOffsetPair> keysToRemove;
4394 402 : std::string osURL(pszURL);
4395 : auto lambda =
4396 2857 : [&keysToRemove,
4397 : &osURL](const lru11::KeyValuePair<FilenameOffsetPair,
4398 3636 : std::shared_ptr<std::string>> &kv)
4399 : {
4400 2857 : if (kv.key.filename_ == osURL)
4401 779 : keysToRemove.push_back(kv.key);
4402 3058 : };
4403 201 : auto *poRegionCache = GetRegionCache();
4404 201 : poRegionCache->cwalk(lambda);
4405 980 : for (const auto &key : keysToRemove)
4406 779 : poRegionCache->remove(key);
4407 201 : }
4408 :
4409 : /************************************************************************/
4410 : /* ClearCache() */
4411 : /************************************************************************/
4412 :
4413 18876 : void VSICurlFilesystemHandlerBase::ClearCache()
4414 : {
4415 18876 : CPLMutexHolder oHolder(&hMutex);
4416 :
4417 18876 : GetRegionCache()->clear();
4418 :
4419 18876 : VSICURLDestroyCacheFileProp();
4420 :
4421 18876 : oCacheDirList.clear();
4422 18876 : nCachedFilesInDirList = 0;
4423 :
4424 18876 : GetConnectionCache()[this].clear();
4425 18876 : }
4426 :
4427 : /************************************************************************/
4428 : /* PartialClearCache() */
4429 : /************************************************************************/
4430 :
4431 7 : void VSICurlFilesystemHandlerBase::PartialClearCache(
4432 : const char *pszFilenamePrefix)
4433 : {
4434 14 : CPLMutexHolder oHolder(&hMutex);
4435 :
4436 21 : std::string osURL = GetURLFromFilename(pszFilenamePrefix);
4437 : {
4438 14 : std::list<FilenameOffsetPair> keysToRemove;
4439 : auto lambda =
4440 3 : [&keysToRemove, &osURL](
4441 : const lru11::KeyValuePair<FilenameOffsetPair,
4442 8 : std::shared_ptr<std::string>> &kv)
4443 : {
4444 3 : if (strncmp(kv.key.filename_.c_str(), osURL.c_str(),
4445 3 : osURL.size()) == 0)
4446 2 : keysToRemove.push_back(kv.key);
4447 10 : };
4448 7 : auto *poRegionCache = GetRegionCache();
4449 7 : poRegionCache->cwalk(lambda);
4450 9 : for (const auto &key : keysToRemove)
4451 2 : poRegionCache->remove(key);
4452 : }
4453 :
4454 7 : VSICURLInvalidateCachedFilePropPrefix(osURL.c_str());
4455 :
4456 : {
4457 7 : const size_t nLen = strlen(pszFilenamePrefix);
4458 14 : std::list<std::string> keysToRemove;
4459 : auto lambda =
4460 2 : [this, &keysToRemove, pszFilenamePrefix,
4461 4 : nLen](const lru11::KeyValuePair<std::string, CachedDirList> &kv)
4462 : {
4463 2 : if (strncmp(kv.key.c_str(), pszFilenamePrefix, nLen) == 0)
4464 : {
4465 1 : keysToRemove.push_back(kv.key);
4466 1 : nCachedFilesInDirList -= kv.value.oFileList.size();
4467 : }
4468 9 : };
4469 7 : oCacheDirList.cwalk(lambda);
4470 8 : for (const auto &key : keysToRemove)
4471 1 : oCacheDirList.remove(key);
4472 : }
4473 7 : }
4474 :
4475 : /************************************************************************/
4476 : /* CreateFileHandle() */
4477 : /************************************************************************/
4478 :
4479 : VSICurlHandle *
4480 1526 : VSICurlFilesystemHandlerBase::CreateFileHandle(const char *pszFilename)
4481 : {
4482 1526 : return new VSICurlHandle(this, pszFilename);
4483 : }
4484 :
4485 : /************************************************************************/
4486 : /* GetActualURL() */
4487 : /************************************************************************/
4488 :
4489 5 : const char *VSICurlFilesystemHandlerBase::GetActualURL(const char *pszFilename)
4490 : {
4491 5 : VSICurlHandle *poHandle = CreateFileHandle(pszFilename);
4492 5 : if (poHandle == nullptr)
4493 0 : return pszFilename;
4494 10 : std::string osURL(poHandle->GetURL());
4495 5 : delete poHandle;
4496 5 : return CPLSPrintf("%s", osURL.c_str());
4497 : }
4498 :
4499 : /************************************************************************/
4500 : /* GetOptions() */
4501 : /************************************************************************/
4502 :
4503 : #define VSICURL_OPTIONS \
4504 : " <Option name='GDAL_HTTP_MAX_RETRY' type='int' " \
4505 : "description='Maximum number of retries' default='0'/>" \
4506 : " <Option name='GDAL_HTTP_RETRY_DELAY' type='double' " \
4507 : "description='Retry delay in seconds' default='30'/>" \
4508 : " <Option name='GDAL_HTTP_HEADER_FILE' type='string' " \
4509 : "description='Filename of a file that contains HTTP headers to " \
4510 : "forward to the server'/>" \
4511 : " <Option name='CPL_VSIL_CURL_USE_HEAD' type='boolean' " \
4512 : "description='Whether to use HTTP HEAD verb to retrieve " \
4513 : "file information' default='YES'/>" \
4514 : " <Option name='GDAL_HTTP_MULTIRANGE' type='string-select' " \
4515 : "description='Strategy to apply to run multi-range requests' " \
4516 : "default='PARALLEL'>" \
4517 : " <Value>PARALLEL</Value>" \
4518 : " <Value>SERIAL</Value>" \
4519 : " </Option>" \
4520 : " <Option name='GDAL_HTTP_MULTIPLEX' type='boolean' " \
4521 : "description='Whether to enable HTTP/2 multiplexing' default='YES'/>" \
4522 : " <Option name='GDAL_HTTP_MERGE_CONSECUTIVE_RANGES' type='boolean' " \
4523 : "description='Whether to merge consecutive ranges in multirange " \
4524 : "requests' default='YES'/>" \
4525 : " <Option name='CPL_VSIL_CURL_NON_CACHED' type='string' " \
4526 : "description='Colon-separated list of filenames whose content" \
4527 : "must not be cached across open attempts'/>" \
4528 : " <Option name='CPL_VSIL_CURL_ALLOWED_FILENAME' type='string' " \
4529 : "description='Single filename that is allowed to be opened'/>" \
4530 : " <Option name='CPL_VSIL_CURL_ALLOWED_EXTENSIONS' type='string' " \
4531 : "description='Comma or space separated list of allowed file " \
4532 : "extensions'/>" \
4533 : " <Option name='GDAL_DISABLE_READDIR_ON_OPEN' type='string-select' " \
4534 : "description='Whether to disable establishing the list of files in " \
4535 : "the directory of the current filename' default='NO'>" \
4536 : " <Value>NO</Value>" \
4537 : " <Value>YES</Value>" \
4538 : " <Value>EMPTY_DIR</Value>" \
4539 : " </Option>" \
4540 : " <Option name='VSI_CACHE' type='boolean' " \
4541 : "description='Whether to cache in memory the contents of the opened " \
4542 : "file as soon as they are read' default='NO'/>" \
4543 : " <Option name='CPL_VSIL_CURL_CHUNK_SIZE' type='integer' " \
4544 : "description='Size in bytes of the minimum amount of data read in a " \
4545 : "file' default='16384' min='1024' max='10485760'/>" \
4546 : " <Option name='CPL_VSIL_CURL_CACHE_SIZE' type='integer' " \
4547 : "description='Size in bytes of the global /vsicurl/ cache' " \
4548 : "default='16384000'/>" \
4549 : " <Option name='CPL_VSIL_CURL_IGNORE_GLACIER_STORAGE' type='boolean' " \
4550 : "description='Whether to skip files with Glacier storage class in " \
4551 : "directory listing.' default='YES'/>" \
4552 : " <Option name='CPL_VSIL_CURL_ADVISE_READ_TOTAL_BYTES_LIMIT' " \
4553 : "type='integer' description='Maximum number of bytes AdviseRead() is " \
4554 : "allowed to fetch at once' default='104857600'/>" \
4555 : " <Option name='CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED' " \
4556 : "type='string-select' description='Whether the header-file key-value " \
4557 : "pair can be used in /vsicurl? filenames' default='ONLY_IN_TEMP'>" \
4558 : " <Value>ONLY_IN_TEMP</Value>" \
4559 : " <Value>NO</Value>" \
4560 : " <Value>YES</Value>" \
4561 : " </Option>" \
4562 : " <Option name='GDAL_HTTP_MAX_CACHED_CONNECTIONS' type='integer' " \
4563 : "description='Maximum amount of connections that libcurl may keep alive " \
4564 : "in its connection cache after use'/>" \
4565 : " <Option name='GDAL_HTTP_MAX_TOTAL_CONNECTIONS' type='integer' " \
4566 : "description='Maximum number of simultaneously open connections in " \
4567 : "total'/>"
4568 :
4569 7 : const char *VSICurlFilesystemHandlerBase::GetOptionsStatic()
4570 : {
4571 7 : return VSICURL_OPTIONS;
4572 : }
4573 :
4574 2 : const char *VSICurlFilesystemHandlerBase::GetOptions()
4575 : {
4576 2 : static std::string osOptions(std::string("<Options>") + GetOptionsStatic() +
4577 3 : "</Options>");
4578 2 : return osOptions.c_str();
4579 : }
4580 :
4581 : /************************************************************************/
4582 : /* IsAllowedFilename() */
4583 : /************************************************************************/
4584 :
4585 2166 : bool VSICurlFilesystemHandlerBase::IsAllowedFilename(const char *pszFilename)
4586 : {
4587 : const char *pszAllowedFilename =
4588 2166 : CPLGetConfigOption("CPL_VSIL_CURL_ALLOWED_FILENAME", nullptr);
4589 2166 : if (pszAllowedFilename != nullptr)
4590 : {
4591 0 : return strcmp(pszFilename, pszAllowedFilename) == 0;
4592 : }
4593 :
4594 : // Consider that only the files whose extension ends up with one that is
4595 : // listed in CPL_VSIL_CURL_ALLOWED_EXTENSIONS exist on the server. This can
4596 : // speeds up dramatically open experience, in case the server cannot return
4597 : // a file list. {noext} can be used as a special token to mean file with no
4598 : // extension.
4599 : // For example:
4600 : // gdalinfo --config CPL_VSIL_CURL_ALLOWED_EXTENSIONS ".tif"
4601 : // /vsicurl/http://igskmncngs506.cr.usgs.gov/gmted/Global_tiles_GMTED/075darcsec/bln/W030/30N030W_20101117_gmted_bln075.tif
4602 : const char *pszAllowedExtensions =
4603 2166 : CPLGetConfigOption("CPL_VSIL_CURL_ALLOWED_EXTENSIONS", nullptr);
4604 2166 : if (pszAllowedExtensions)
4605 : {
4606 : char **papszExtensions =
4607 22 : CSLTokenizeString2(pszAllowedExtensions, ", ", 0);
4608 22 : const char *queryStart = strchr(pszFilename, '?');
4609 22 : char *pszFilenameWithoutQuery = nullptr;
4610 22 : if (queryStart != nullptr)
4611 : {
4612 0 : pszFilenameWithoutQuery = CPLStrdup(pszFilename);
4613 0 : pszFilenameWithoutQuery[queryStart - pszFilename] = '\0';
4614 0 : pszFilename = pszFilenameWithoutQuery;
4615 : }
4616 22 : const size_t nURLLen = strlen(pszFilename);
4617 22 : bool bFound = false;
4618 22 : for (int i = 0; papszExtensions[i] != nullptr; i++)
4619 : {
4620 22 : const size_t nExtensionLen = strlen(papszExtensions[i]);
4621 22 : if (EQUAL(papszExtensions[i], "{noext}"))
4622 : {
4623 0 : const char *pszLastSlash = strrchr(pszFilename, '/');
4624 0 : if (pszLastSlash != nullptr &&
4625 0 : strchr(pszLastSlash, '.') == nullptr)
4626 : {
4627 0 : bFound = true;
4628 0 : break;
4629 : }
4630 : }
4631 22 : else if (nURLLen > nExtensionLen &&
4632 22 : EQUAL(pszFilename + nURLLen - nExtensionLen,
4633 : papszExtensions[i]))
4634 : {
4635 22 : bFound = true;
4636 22 : break;
4637 : }
4638 : }
4639 :
4640 22 : CSLDestroy(papszExtensions);
4641 22 : if (pszFilenameWithoutQuery)
4642 : {
4643 0 : CPLFree(pszFilenameWithoutQuery);
4644 : }
4645 :
4646 22 : return bFound;
4647 : }
4648 2144 : return TRUE;
4649 : }
4650 :
4651 : /************************************************************************/
4652 : /* Open() */
4653 : /************************************************************************/
4654 :
4655 : VSIVirtualHandleUniquePtr
4656 870 : VSICurlFilesystemHandlerBase::Open(const char *pszFilename,
4657 : const char *pszAccess, bool bSetError,
4658 : CSLConstList papszOptions)
4659 : {
4660 870 : const bool bStartsWithVSICurlPrefix = StartsWithVSICurlPrefix(pszFilename);
4661 1067 : if (!bStartsWithVSICurlPrefix &&
4662 1067 : !cpl::starts_with(std::string_view(pszFilename), GetFSPrefix()))
4663 : {
4664 1 : return nullptr;
4665 : }
4666 :
4667 869 : if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
4668 : {
4669 1 : if (bSetError)
4670 : {
4671 0 : VSIError(VSIE_FileError,
4672 : "Only read-only mode is supported for /vsicurl");
4673 : }
4674 1 : return nullptr;
4675 : }
4676 873 : if (!papszOptions ||
4677 5 : !CPLTestBool(CSLFetchNameValueDef(
4678 : papszOptions, "IGNORE_FILENAME_RESTRICTIONS", "NO")))
4679 : {
4680 866 : if (!IsAllowedFilename(pszFilename))
4681 0 : return nullptr;
4682 : }
4683 :
4684 868 : bool bListDir = true;
4685 868 : bool bEmptyDir = false;
4686 : std::string osURL =
4687 : bStartsWithVSICurlPrefix
4688 : ? VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr, nullptr,
4689 : &bListDir, &bEmptyDir, nullptr, nullptr,
4690 : nullptr)
4691 1932 : : GetURLFromFilename(pszFilename);
4692 :
4693 868 : const char *pszOptionVal = CSLFetchNameValueDef(
4694 : papszOptions, "DISABLE_READDIR_ON_OPEN",
4695 : VSIGetPathSpecificOption(pszFilename, "GDAL_DISABLE_READDIR_ON_OPEN",
4696 : "NO"));
4697 868 : const bool bCache = CPLTestBool(CSLFetchNameValueDef(
4698 868 : papszOptions, "CACHE", AllowCachedDataFor(pszFilename) ? "YES" : "NO"));
4699 868 : const bool bSkipReadDir = !bListDir || bEmptyDir ||
4700 864 : EQUAL(pszOptionVal, "EMPTY_DIR") ||
4701 1736 : CPLTestBool(pszOptionVal) || !bCache;
4702 :
4703 1736 : std::string osFilename(pszFilename);
4704 868 : bool bGotFileList = !bSkipReadDir;
4705 868 : bool bForceExistsCheck = false;
4706 1736 : FileProp cachedFileProp;
4707 2550 : if (!bSkipReadDir &&
4708 814 : !(GetCachedFileProp(osURL.c_str(), cachedFileProp) &&
4709 585 : cachedFileProp.eExists == EXIST_YES) &&
4710 259 : strchr(CPLGetFilename(osFilename.c_str()), '.') != nullptr &&
4711 1823 : !STARTS_WITH(CPLGetExtensionSafe(osFilename.c_str()).c_str(), "zip") &&
4712 : // Likely a Kerchunk JSON reference file: no need to list siblings
4713 141 : !cpl::ends_with(osFilename, ".nc.zarr"))
4714 : {
4715 : // 1000 corresponds to the default page size of S3.
4716 141 : constexpr int FILE_COUNT_LIMIT = 1000;
4717 : const CPLStringList aosFileList(ReadDirInternal(
4718 282 : (CPLGetDirnameSafe(osFilename.c_str()) + '/').c_str(),
4719 141 : FILE_COUNT_LIMIT, &bGotFileList));
4720 : const bool bFound =
4721 141 : VSICurlIsFileInList(aosFileList.List(),
4722 141 : CPLGetFilename(osFilename.c_str())) != -1;
4723 141 : if (bGotFileList && !bFound && aosFileList.size() < FILE_COUNT_LIMIT)
4724 : {
4725 : // Some file servers are case insensitive, so in case there is a
4726 : // match with case difference, do a full check just in case.
4727 : // e.g.
4728 : // http://pds-geosciences.wustl.edu/mgs/mgs-m-mola-5-megdr-l3-v1/mgsl_300x/meg004/MEGA90N000CB.IMG
4729 : // that is queried by
4730 : // gdalinfo
4731 : // /vsicurl/http://pds-geosciences.wustl.edu/mgs/mgs-m-mola-5-megdr-l3-v1/mgsl_300x/meg004/mega90n000cb.lbl
4732 12 : if (aosFileList.FindString(CPLGetFilename(osFilename.c_str())) !=
4733 : -1)
4734 : {
4735 0 : bForceExistsCheck = true;
4736 : }
4737 : else
4738 : {
4739 12 : return nullptr;
4740 : }
4741 : }
4742 : }
4743 856 : if (!bStartsWithVSICurlPrefix)
4744 192 : osURL = GetURLFromFilename(pszFilename);
4745 856 : if (GetCachedFileProp(osURL.c_str(), cachedFileProp) &&
4746 856 : cachedFileProp.eExists == EXIST_YES && cachedFileProp.bIsDirectory)
4747 : {
4748 2 : return nullptr;
4749 : }
4750 :
4751 : auto poHandle =
4752 1708 : std::unique_ptr<VSICurlHandle>(CreateFileHandle(osFilename.c_str()));
4753 854 : if (poHandle == nullptr)
4754 29 : return nullptr;
4755 825 : poHandle->SetCache(bCache);
4756 825 : if (!bGotFileList || bForceExistsCheck)
4757 : {
4758 : // If we didn't get a filelist, check that the file really exists.
4759 152 : if (!poHandle->Exists(bSetError))
4760 : {
4761 66 : return nullptr;
4762 : }
4763 : }
4764 :
4765 759 : if (CPLTestBool(CPLGetConfigOption("VSI_CACHE", "FALSE")))
4766 : return VSIVirtualHandleUniquePtr(
4767 0 : VSICreateCachedFile(poHandle.release()));
4768 : else
4769 759 : return VSIVirtualHandleUniquePtr(poHandle.release());
4770 : }
4771 :
4772 : /************************************************************************/
4773 : /* VSICurlParserFindEOL() */
4774 : /* */
4775 : /* Small helper function for VSICurlPaseHTMLFileList() to find */
4776 : /* the end of a line in the directory listing. Either a <br> */
4777 : /* or newline. */
4778 : /************************************************************************/
4779 :
4780 280257 : static char *VSICurlParserFindEOL(char *pszData)
4781 :
4782 : {
4783 280257 : while (*pszData != '\0' && *pszData != '\n' &&
4784 278672 : !STARTS_WITH_CI(pszData, "<br>"))
4785 278672 : pszData++;
4786 :
4787 1585 : if (*pszData == '\0')
4788 16 : return nullptr;
4789 :
4790 1569 : return pszData;
4791 : }
4792 :
4793 : /************************************************************************/
4794 : /* ParseFileSize() */
4795 : /************************************************************************/
4796 :
4797 5 : static GUIntBig ParseFileSize(const char *pszStr)
4798 : {
4799 5 : GUIntBig nFileSize = 0;
4800 41 : while (*pszStr == ' ')
4801 36 : pszStr++;
4802 5 : if (*pszStr >= '1' && *pszStr <= '9')
4803 : {
4804 3 : const char *pszIter = pszStr + 1;
4805 15 : while (*pszIter >= '0' && *pszIter <= '9')
4806 12 : ++pszIter;
4807 3 : if (*pszIter == 0 || *pszIter == ' ' || *pszIter == '\t' ||
4808 1 : *pszIter == '\r' || *pszIter == '\n')
4809 : {
4810 : nFileSize =
4811 2 : CPLScanUIntBig(pszStr, static_cast<int>(pszIter - pszStr));
4812 : }
4813 : }
4814 5 : return nFileSize;
4815 : }
4816 :
4817 : /************************************************************************/
4818 : /* VSICurlParseHTMLDateTimeFileSize() */
4819 : /************************************************************************/
4820 :
4821 : static const char *const apszMonths[] = {
4822 : "January", "February", "March", "April", "May", "June",
4823 : "July", "August", "September", "October", "November", "December"};
4824 :
4825 22 : static bool VSICurlParseHTMLDateTimeFileSize(const char *pszStr,
4826 : struct tm &brokendowntime,
4827 : GUIntBig &nFileSize,
4828 : GIntBig &mTime)
4829 : {
4830 223 : for (int iMonth = 0; iMonth < 12; iMonth++)
4831 : {
4832 219 : nFileSize = 0;
4833 :
4834 219 : char szMonth[32] = {};
4835 219 : szMonth[0] = '-';
4836 219 : memcpy(szMonth + 1, apszMonths[iMonth], 3);
4837 219 : szMonth[4] = '-';
4838 219 : szMonth[5] = '\0';
4839 219 : const char *pszMonthFound = strstr(pszStr, szMonth);
4840 219 : if (pszMonthFound)
4841 : {
4842 : // Format of Apache, like in
4843 : // http://download.osgeo.org/gdal/data/gtiff/
4844 : // "17-May-2010 12:26"
4845 18 : const auto nMonthFoundLen = strlen(pszMonthFound);
4846 18 : if (pszMonthFound - pszStr > 2 && nMonthFoundLen > 15 &&
4847 18 : pszMonthFound[-2 + 11] == ' ' && pszMonthFound[-2 + 14] == ':')
4848 : {
4849 5 : pszMonthFound -= 2;
4850 5 : int nDay = atoi(pszMonthFound);
4851 5 : int nYear = atoi(pszMonthFound + 7);
4852 5 : int nHour = atoi(pszMonthFound + 12);
4853 5 : int nMin = atoi(pszMonthFound + 15);
4854 5 : if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4855 5 : nHour <= 24 && nMin >= 0 && nMin < 60)
4856 : {
4857 5 : brokendowntime.tm_year = nYear - 1900;
4858 5 : brokendowntime.tm_mon = iMonth;
4859 5 : brokendowntime.tm_mday = nDay;
4860 5 : brokendowntime.tm_hour = nHour;
4861 5 : brokendowntime.tm_min = nMin;
4862 5 : mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4863 :
4864 5 : if (nMonthFoundLen > 15 + 2)
4865 : {
4866 5 : const char *pszFilesize = pszMonthFound + 15 + 2;
4867 5 : nFileSize = ParseFileSize(pszFilesize);
4868 : }
4869 : }
4870 : }
4871 18 : return nFileSize > 0;
4872 : }
4873 :
4874 : /* Microsoft IIS */
4875 201 : snprintf(szMonth, sizeof(szMonth), " %s ", apszMonths[iMonth]);
4876 201 : pszMonthFound = strstr(pszStr, szMonth);
4877 201 : if (pszMonthFound)
4878 : {
4879 0 : int nLenMonth = static_cast<int>(strlen(apszMonths[iMonth]));
4880 0 : if (pszMonthFound - pszStr > 2 && pszMonthFound[-1] != ',' &&
4881 0 : pszMonthFound[-2] != ' ' &&
4882 0 : static_cast<int>(strlen(pszMonthFound - 2)) >
4883 0 : 2 + 1 + nLenMonth + 1 + 4 + 1 + 5 + 1 + 4)
4884 : {
4885 : /* Format of http://ortho.linz.govt.nz/tifs/1994_95/ */
4886 : /* " Friday, 21 April 2006 12:05 p.m. 48062343
4887 : * m35a_fy_94_95.tif" */
4888 0 : pszMonthFound -= 2;
4889 0 : int nDay = atoi(pszMonthFound);
4890 0 : int nCurOffset = 2 + 1 + nLenMonth + 1;
4891 0 : int nYear = atoi(pszMonthFound + nCurOffset);
4892 0 : nCurOffset += 4 + 1;
4893 0 : int nHour = atoi(pszMonthFound + nCurOffset);
4894 0 : if (nHour < 10)
4895 0 : nCurOffset += 1 + 1;
4896 : else
4897 0 : nCurOffset += 2 + 1;
4898 0 : const int nMin = atoi(pszMonthFound + nCurOffset);
4899 0 : nCurOffset += 2 + 1;
4900 0 : if (STARTS_WITH(pszMonthFound + nCurOffset, "p.m."))
4901 0 : nHour += 12;
4902 0 : else if (!STARTS_WITH(pszMonthFound + nCurOffset, "a.m."))
4903 0 : nHour = -1;
4904 0 : nCurOffset += 4;
4905 :
4906 0 : if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4907 0 : nHour <= 24 && nMin >= 0 && nMin < 60)
4908 : {
4909 0 : brokendowntime.tm_year = nYear - 1900;
4910 0 : brokendowntime.tm_mon = iMonth;
4911 0 : brokendowntime.tm_mday = nDay;
4912 0 : brokendowntime.tm_hour = nHour;
4913 0 : brokendowntime.tm_min = nMin;
4914 0 : mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4915 :
4916 0 : const char *pszFilesize = pszMonthFound + nCurOffset;
4917 0 : nFileSize = ParseFileSize(pszFilesize);
4918 0 : }
4919 : }
4920 0 : else if (pszMonthFound - pszStr > 1 && pszMonthFound[-1] == ',' &&
4921 0 : static_cast<int>(strlen(pszMonthFound)) >
4922 0 : 1 + nLenMonth + 1 + 2 + 1 + 1 + 4 + 1 + 5 + 1 + 2)
4923 : {
4924 : // Format of
4925 : // http://publicfiles.dep.state.fl.us/dear/BWR_GIS/2007NWFLULC/
4926 : // " Sunday, June 20, 2010 6:46 PM 233170905
4927 : // NWF2007LULCForSDE.zip"
4928 0 : pszMonthFound += 1;
4929 0 : int nCurOffset = nLenMonth + 1;
4930 0 : int nDay = atoi(pszMonthFound + nCurOffset);
4931 0 : nCurOffset += 2 + 1 + 1;
4932 0 : int nYear = atoi(pszMonthFound + nCurOffset);
4933 0 : nCurOffset += 4 + 1;
4934 0 : int nHour = atoi(pszMonthFound + nCurOffset);
4935 0 : nCurOffset += 2 + 1;
4936 0 : const int nMin = atoi(pszMonthFound + nCurOffset);
4937 0 : nCurOffset += 2 + 1;
4938 0 : if (STARTS_WITH(pszMonthFound + nCurOffset, "PM"))
4939 0 : nHour += 12;
4940 0 : else if (!STARTS_WITH(pszMonthFound + nCurOffset, "AM"))
4941 0 : nHour = -1;
4942 0 : nCurOffset += 2;
4943 :
4944 0 : if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4945 0 : nHour <= 24 && nMin >= 0 && nMin < 60)
4946 : {
4947 0 : brokendowntime.tm_year = nYear - 1900;
4948 0 : brokendowntime.tm_mon = iMonth;
4949 0 : brokendowntime.tm_mday = nDay;
4950 0 : brokendowntime.tm_hour = nHour;
4951 0 : brokendowntime.tm_min = nMin;
4952 0 : mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4953 :
4954 0 : const char *pszFilesize = pszMonthFound + nCurOffset;
4955 0 : nFileSize = ParseFileSize(pszFilesize);
4956 : }
4957 : }
4958 :
4959 0 : return nFileSize > 0;
4960 : }
4961 : }
4962 :
4963 4 : return false;
4964 : }
4965 :
4966 : /************************************************************************/
4967 : /* ParseHTMLFileList() */
4968 : /* */
4969 : /* Parse a file list document and return all the components. */
4970 : /************************************************************************/
4971 :
4972 16 : char **VSICurlFilesystemHandlerBase::ParseHTMLFileList(const char *pszFilename,
4973 : int nMaxFiles,
4974 : char *pszData,
4975 : bool *pbGotFileList)
4976 : {
4977 16 : *pbGotFileList = false;
4978 :
4979 : std::string osURL(VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr,
4980 : nullptr, nullptr, nullptr,
4981 32 : nullptr, nullptr, nullptr));
4982 16 : const char *pszDir = nullptr;
4983 16 : if (STARTS_WITH_CI(osURL.c_str(), "http://"))
4984 9 : pszDir = strchr(osURL.c_str() + strlen("http://"), '/');
4985 7 : else if (STARTS_WITH_CI(osURL.c_str(), "https://"))
4986 7 : pszDir = strchr(osURL.c_str() + strlen("https://"), '/');
4987 0 : else if (STARTS_WITH_CI(osURL.c_str(), "ftp://"))
4988 0 : pszDir = strchr(osURL.c_str() + strlen("ftp://"), '/');
4989 16 : if (pszDir == nullptr)
4990 3 : pszDir = "";
4991 :
4992 : /* Apache / Nginx */
4993 : /* Most of the time the format is <title>Index of {pszDir[/]}</title>, but
4994 : * there are special cases like https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/GEOCOLOR/
4995 : * where a CDN stuff makes that the title is <title>Index of /ma-cdn02/GOES/data/GOES18/ABI/MESO/M1/GEOCOLOR/</title>
4996 : */
4997 32 : const std::string osTitleIndexOfPrefix = "<title>Index of ";
4998 48 : const std::string osExpectedSuffix = std::string(pszDir).append("</title>");
4999 : const std::string osExpectedSuffixWithSlash =
5000 48 : std::string(pszDir).append("/</title>");
5001 : /* FTP */
5002 : const std::string osExpectedStringFTP =
5003 48 : std::string("FTP Listing of ").append(pszDir).append("/");
5004 : /* Apache 1.3.33 */
5005 : const std::string osExpectedStringOldApache =
5006 48 : std::string("<TITLE>Index of ").append(pszDir).append("</TITLE>");
5007 :
5008 : // The listing of
5009 : // http://dds.cr.usgs.gov/srtm/SRTM_image_sample/picture%20examples/
5010 : // has
5011 : // "<title>Index of /srtm/SRTM_image_sample/picture examples</title>"
5012 : // so we must try unescaped %20 also.
5013 : // Similar with
5014 : // http://datalib.usask.ca/gis/Data/Central_America_goodbutdoweown%3f/
5015 32 : std::string osExpectedString_unescaped;
5016 16 : if (strchr(pszDir, '%'))
5017 : {
5018 0 : char *pszUnescapedDir = CPLUnescapeString(pszDir, nullptr, CPLES_URL);
5019 0 : osExpectedString_unescaped = osTitleIndexOfPrefix;
5020 0 : osExpectedString_unescaped += pszUnescapedDir;
5021 0 : osExpectedString_unescaped += "</title>";
5022 0 : CPLFree(pszUnescapedDir);
5023 : }
5024 :
5025 16 : char *c = nullptr;
5026 16 : int nCount = 0;
5027 16 : int nCountTable = 0;
5028 32 : CPLStringList oFileList;
5029 16 : char *pszLine = pszData;
5030 16 : bool bIsHTMLDirList = false;
5031 :
5032 1585 : while ((c = VSICurlParserFindEOL(pszLine)) != nullptr)
5033 : {
5034 1569 : *c = '\0';
5035 :
5036 : // To avoid false positive on pages such as
5037 : // http://www.ngs.noaa.gov/PC_PROD/USGG2009BETA
5038 : // This is a heuristics, but normal HTML listing of files have not more
5039 : // than one table.
5040 1569 : if (strstr(pszLine, "<table"))
5041 : {
5042 4 : nCountTable++;
5043 4 : if (nCountTable == 2)
5044 : {
5045 0 : *pbGotFileList = false;
5046 0 : return nullptr;
5047 : }
5048 : }
5049 :
5050 3097 : if (!bIsHTMLDirList &&
5051 1528 : ((strstr(pszLine, osTitleIndexOfPrefix.c_str()) &&
5052 5 : (strstr(pszLine, osExpectedSuffix.c_str()) ||
5053 4 : strstr(pszLine, osExpectedSuffixWithSlash.c_str()))) ||
5054 1523 : strstr(pszLine, osExpectedStringFTP.c_str()) ||
5055 1523 : strstr(pszLine, osExpectedStringOldApache.c_str()) ||
5056 1523 : (!osExpectedString_unescaped.empty() &&
5057 0 : strstr(pszLine, osExpectedString_unescaped.c_str()))))
5058 : {
5059 5 : bIsHTMLDirList = true;
5060 5 : *pbGotFileList = true;
5061 : }
5062 : // Subversion HTTP listing
5063 : // or Microsoft-IIS/6.0 listing
5064 : // (e.g. http://ortho.linz.govt.nz/tifs/2005_06/) */
5065 1564 : else if (!bIsHTMLDirList && strstr(pszLine, "<title>"))
5066 : {
5067 : // Detect something like:
5068 : // <html><head><title>gdal - Revision 20739:
5069 : // /trunk/autotest/gcore/data</title></head> */ The annoying thing
5070 : // is that what is after ': ' is a subpart of what is after
5071 : // http://server/
5072 5 : char *pszSubDir = strstr(pszLine, ": ");
5073 5 : if (pszSubDir == nullptr)
5074 : // or <title>ortho.linz.govt.nz - /tifs/2005_06/</title>
5075 5 : pszSubDir = strstr(pszLine, "- ");
5076 5 : if (pszSubDir)
5077 : {
5078 0 : pszSubDir += 2;
5079 0 : char *pszTmp = strstr(pszSubDir, "</title>");
5080 0 : if (pszTmp)
5081 : {
5082 0 : if (pszTmp[-1] == '/')
5083 0 : pszTmp[-1] = 0;
5084 : else
5085 0 : *pszTmp = 0;
5086 0 : if (strstr(pszDir, pszSubDir))
5087 : {
5088 0 : bIsHTMLDirList = true;
5089 0 : *pbGotFileList = true;
5090 : }
5091 : }
5092 5 : }
5093 : }
5094 1559 : else if (bIsHTMLDirList &&
5095 41 : (strstr(pszLine, "<a href=\"") != nullptr ||
5096 15 : strstr(pszLine, "<A HREF=\"") != nullptr) &&
5097 : // Exclude absolute links, like to subversion home.
5098 26 : strstr(pszLine, "<a href=\"http://") == nullptr &&
5099 : // exclude parent directory.
5100 26 : strstr(pszLine, "Parent Directory") == nullptr)
5101 : {
5102 25 : char *beginFilename = strstr(pszLine, "<a href=\"");
5103 25 : if (beginFilename == nullptr)
5104 0 : beginFilename = strstr(pszLine, "<A HREF=\"");
5105 25 : beginFilename += strlen("<a href=\"");
5106 25 : char *endQuote = strchr(beginFilename, '"');
5107 25 : if (endQuote && !STARTS_WITH(beginFilename, "?C=") &&
5108 22 : !STARTS_WITH(beginFilename, "?N="))
5109 : {
5110 : struct tm brokendowntime;
5111 22 : memset(&brokendowntime, 0, sizeof(brokendowntime));
5112 22 : GUIntBig nFileSize = 0;
5113 22 : GIntBig mTime = 0;
5114 :
5115 22 : VSICurlParseHTMLDateTimeFileSize(pszLine, brokendowntime,
5116 : nFileSize, mTime);
5117 :
5118 22 : *endQuote = '\0';
5119 :
5120 : // Remove trailing slash, that are returned for directories by
5121 : // Apache.
5122 22 : bool bIsDirectory = false;
5123 22 : if (endQuote[-1] == '/')
5124 : {
5125 4 : bIsDirectory = true;
5126 4 : endQuote[-1] = 0;
5127 : }
5128 :
5129 : // shttpd links include slashes from the root directory.
5130 : // Skip them.
5131 22 : while (strchr(beginFilename, '/'))
5132 0 : beginFilename = strchr(beginFilename, '/') + 1;
5133 :
5134 22 : if (strcmp(beginFilename, ".") != 0 &&
5135 22 : strcmp(beginFilename, "..") != 0)
5136 : {
5137 : std::string osCachedFilename =
5138 18 : CPLSPrintf("%s/%s", osURL.c_str(), beginFilename);
5139 :
5140 18 : FileProp cachedFileProp;
5141 18 : GetCachedFileProp(osCachedFilename.c_str(), cachedFileProp);
5142 18 : cachedFileProp.eExists = EXIST_YES;
5143 18 : cachedFileProp.bIsDirectory = bIsDirectory;
5144 18 : if (mTime > 0)
5145 : {
5146 5 : cachedFileProp.mTime = static_cast<time_t>(mTime);
5147 : }
5148 18 : if (!cachedFileProp.bHasComputedFileSize)
5149 : {
5150 16 : cachedFileProp.bHasComputedFileSize = nFileSize > 0;
5151 16 : cachedFileProp.fileSize = nFileSize;
5152 : }
5153 18 : SetCachedFileProp(osCachedFilename.c_str(), cachedFileProp);
5154 :
5155 18 : oFileList.AddString(beginFilename);
5156 : if constexpr (ENABLE_DEBUG_VERBOSE)
5157 : {
5158 : CPLDebug(
5159 : GetDebugKey(),
5160 : "File[%d] = %s, is_dir = %d, size = " CPL_FRMT_GUIB
5161 : ", time = %04d/%02d/%02d %02d:%02d:%02d",
5162 : nCount, osCachedFilename.c_str(),
5163 : bIsDirectory ? 1 : 0, nFileSize,
5164 : brokendowntime.tm_year + 1900,
5165 : brokendowntime.tm_mon + 1, brokendowntime.tm_mday,
5166 : brokendowntime.tm_hour, brokendowntime.tm_min,
5167 : brokendowntime.tm_sec);
5168 : }
5169 18 : nCount++;
5170 :
5171 18 : if (nMaxFiles > 0 && oFileList.Count() > nMaxFiles)
5172 0 : break;
5173 : }
5174 : }
5175 : }
5176 1569 : pszLine = c + 1;
5177 : }
5178 :
5179 16 : return oFileList.StealList();
5180 : }
5181 :
5182 : /************************************************************************/
5183 : /* GetStreamingFilename() */
5184 : /************************************************************************/
5185 :
5186 5995 : std::string VSICurlFilesystemHandler::GetStreamingFilename(
5187 : const std::string &osFilename) const
5188 : {
5189 5995 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
5190 12015 : return "/vsicurl_streaming/" + osFilename.substr(GetFSPrefix().size());
5191 0 : return osFilename;
5192 : }
5193 :
5194 : /************************************************************************/
5195 : /* GetHintForPotentiallyRecognizedPath() */
5196 : /************************************************************************/
5197 :
5198 6008 : std::string VSICurlFilesystemHandler::GetHintForPotentiallyRecognizedPath(
5199 : const std::string &osPath)
5200 : {
5201 12014 : if (!StartsWithVSICurlPrefix(osPath.c_str()) &&
5202 12014 : !cpl::starts_with(osPath, GetStreamingFilename(GetFSPrefix())))
5203 : {
5204 17999 : for (const char *pszPrefix : {"http://", "https://"})
5205 : {
5206 12003 : if (cpl::starts_with(osPath, pszPrefix))
5207 : {
5208 2 : return GetFSPrefix() + osPath;
5209 : }
5210 : }
5211 : }
5212 6005 : return std::string();
5213 : }
5214 :
5215 : /************************************************************************/
5216 : /* VSICurlGetToken() */
5217 : /************************************************************************/
5218 :
5219 0 : static char *VSICurlGetToken(char *pszCurPtr, char **ppszNextToken)
5220 : {
5221 0 : if (pszCurPtr == nullptr)
5222 0 : return nullptr;
5223 :
5224 0 : while ((*pszCurPtr) == ' ')
5225 0 : pszCurPtr++;
5226 0 : if (*pszCurPtr == '\0')
5227 0 : return nullptr;
5228 :
5229 0 : char *pszToken = pszCurPtr;
5230 0 : while ((*pszCurPtr) != ' ' && (*pszCurPtr) != '\0')
5231 0 : pszCurPtr++;
5232 0 : if (*pszCurPtr == '\0')
5233 : {
5234 0 : *ppszNextToken = nullptr;
5235 : }
5236 : else
5237 : {
5238 0 : *pszCurPtr = '\0';
5239 0 : pszCurPtr++;
5240 0 : while ((*pszCurPtr) == ' ')
5241 0 : pszCurPtr++;
5242 0 : *ppszNextToken = pszCurPtr;
5243 : }
5244 :
5245 0 : return pszToken;
5246 : }
5247 :
5248 : /************************************************************************/
5249 : /* VSICurlParseFullFTPLine() */
5250 : /************************************************************************/
5251 :
5252 : /* Parse lines like the following ones :
5253 : -rw-r--r-- 1 10003 100 430 Jul 04 2008 COPYING
5254 : lrwxrwxrwx 1 ftp ftp 28 Jun 14 14:13 MPlayer ->
5255 : mirrors/mplayerhq.hu/MPlayer -rw-r--r-- 1 ftp ftp 725614592 May 13
5256 : 20:13 Fedora-15-x86_64-Live-KDE.iso drwxr-xr-x 280 1003 1003 6656 Aug 26
5257 : 04:17 gnu
5258 : */
5259 :
5260 0 : static bool VSICurlParseFullFTPLine(char *pszLine, char *&pszFilename,
5261 : bool &bSizeValid, GUIntBig &nSize,
5262 : bool &bIsDirectory, GIntBig &nUnixTime)
5263 : {
5264 0 : char *pszNextToken = pszLine;
5265 0 : char *pszPermissions = VSICurlGetToken(pszNextToken, &pszNextToken);
5266 0 : if (pszPermissions == nullptr || strlen(pszPermissions) != 10)
5267 0 : return false;
5268 0 : bIsDirectory = pszPermissions[0] == 'd';
5269 :
5270 0 : for (int i = 0; i < 3; i++)
5271 : {
5272 0 : if (VSICurlGetToken(pszNextToken, &pszNextToken) == nullptr)
5273 0 : return false;
5274 : }
5275 :
5276 0 : char *pszSize = VSICurlGetToken(pszNextToken, &pszNextToken);
5277 0 : if (pszSize == nullptr)
5278 0 : return false;
5279 :
5280 0 : if (pszPermissions[0] == '-')
5281 : {
5282 : // Regular file.
5283 0 : bSizeValid = true;
5284 0 : nSize = CPLScanUIntBig(pszSize, static_cast<int>(strlen(pszSize)));
5285 : }
5286 :
5287 : struct tm brokendowntime;
5288 0 : memset(&brokendowntime, 0, sizeof(brokendowntime));
5289 0 : bool bBrokenDownTimeValid = true;
5290 :
5291 0 : char *pszMonth = VSICurlGetToken(pszNextToken, &pszNextToken);
5292 0 : if (pszMonth == nullptr || strlen(pszMonth) != 3)
5293 0 : return false;
5294 :
5295 0 : int i = 0; // Used after for.
5296 0 : for (; i < 12; i++)
5297 : {
5298 0 : if (EQUALN(pszMonth, apszMonths[i], 3))
5299 0 : break;
5300 : }
5301 0 : if (i < 12)
5302 0 : brokendowntime.tm_mon = i;
5303 : else
5304 0 : bBrokenDownTimeValid = false;
5305 :
5306 0 : char *pszDay = VSICurlGetToken(pszNextToken, &pszNextToken);
5307 0 : if (pszDay == nullptr || (strlen(pszDay) != 1 && strlen(pszDay) != 2))
5308 0 : return false;
5309 0 : int nDay = atoi(pszDay);
5310 0 : if (nDay >= 1 && nDay <= 31)
5311 0 : brokendowntime.tm_mday = nDay;
5312 : else
5313 0 : bBrokenDownTimeValid = false;
5314 :
5315 0 : char *pszHourOrYear = VSICurlGetToken(pszNextToken, &pszNextToken);
5316 0 : if (pszHourOrYear == nullptr ||
5317 0 : (strlen(pszHourOrYear) != 4 && strlen(pszHourOrYear) != 5))
5318 0 : return false;
5319 0 : if (strlen(pszHourOrYear) == 4)
5320 : {
5321 0 : brokendowntime.tm_year = atoi(pszHourOrYear) - 1900;
5322 : }
5323 : else
5324 : {
5325 : time_t sTime;
5326 0 : time(&sTime);
5327 : struct tm currentBrokendowntime;
5328 0 : CPLUnixTimeToYMDHMS(static_cast<GIntBig>(sTime),
5329 : ¤tBrokendowntime);
5330 0 : brokendowntime.tm_year = currentBrokendowntime.tm_year;
5331 0 : brokendowntime.tm_hour = atoi(pszHourOrYear);
5332 0 : brokendowntime.tm_min = atoi(pszHourOrYear + 3);
5333 : }
5334 :
5335 0 : if (bBrokenDownTimeValid)
5336 0 : nUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
5337 : else
5338 0 : nUnixTime = 0;
5339 :
5340 0 : if (pszNextToken == nullptr)
5341 0 : return false;
5342 :
5343 0 : pszFilename = pszNextToken;
5344 :
5345 0 : char *pszCurPtr = pszFilename;
5346 0 : while (*pszCurPtr != '\0')
5347 : {
5348 : // In case of a link, stop before the pointed part of the link.
5349 0 : if (pszPermissions[0] == 'l' && STARTS_WITH(pszCurPtr, " -> "))
5350 : {
5351 0 : break;
5352 : }
5353 0 : pszCurPtr++;
5354 : }
5355 0 : *pszCurPtr = '\0';
5356 :
5357 0 : return true;
5358 : }
5359 :
5360 : /************************************************************************/
5361 : /* GetURLFromFilename() */
5362 : /************************************************************************/
5363 :
5364 108 : std::string VSICurlFilesystemHandlerBase::GetURLFromFilename(
5365 : const std::string &osFilename) const
5366 : {
5367 : return VSICurlGetURLFromFilename(osFilename.c_str(), nullptr, nullptr,
5368 : nullptr, nullptr, nullptr, nullptr,
5369 108 : nullptr, nullptr);
5370 : }
5371 :
5372 : /************************************************************************/
5373 : /* RegisterEmptyDir() */
5374 : /************************************************************************/
5375 :
5376 14 : void VSICurlFilesystemHandlerBase::RegisterEmptyDir(
5377 : const std::string &osDirname)
5378 : {
5379 28 : CachedDirList cachedDirList;
5380 14 : cachedDirList.bGotFileList = true;
5381 14 : cachedDirList.oFileList.AddString(".");
5382 14 : SetCachedDirList(osDirname.c_str(), cachedDirList);
5383 14 : }
5384 :
5385 : /************************************************************************/
5386 : /* GetFileList() */
5387 : /************************************************************************/
5388 :
5389 48 : char **VSICurlFilesystemHandlerBase::GetFileList(const char *pszDirname,
5390 : int nMaxFiles,
5391 : bool *pbGotFileList)
5392 : {
5393 : if constexpr (ENABLE_DEBUG)
5394 : {
5395 48 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
5396 : }
5397 :
5398 48 : *pbGotFileList = false;
5399 :
5400 48 : bool bListDir = true;
5401 48 : bool bEmptyDir = false;
5402 : std::string osURL(VSICurlGetURLFromFilename(pszDirname, nullptr, nullptr,
5403 : nullptr, &bListDir, &bEmptyDir,
5404 96 : nullptr, nullptr, nullptr));
5405 48 : if (bEmptyDir)
5406 : {
5407 1 : *pbGotFileList = true;
5408 1 : return CSLAddString(nullptr, ".");
5409 : }
5410 47 : if (!bListDir)
5411 0 : return nullptr;
5412 :
5413 : // Deal with publicly visible Azure directories.
5414 47 : if (STARTS_WITH(osURL.c_str(), "https://"))
5415 : {
5416 : const char *pszBlobCore =
5417 7 : strstr(osURL.c_str(), ".blob.core.windows.net/");
5418 7 : if (pszBlobCore)
5419 : {
5420 1 : FileProp cachedFileProp;
5421 1 : GetCachedFileProp(osURL.c_str(), cachedFileProp);
5422 1 : if (cachedFileProp.bIsAzureFolder)
5423 : {
5424 : const char *pszURLWithoutHTTPS =
5425 0 : osURL.c_str() + strlen("https://");
5426 : const std::string osStorageAccount(
5427 0 : pszURLWithoutHTTPS, pszBlobCore - pszURLWithoutHTTPS);
5428 : CPLConfigOptionSetter oSetter1("AZURE_NO_SIGN_REQUEST", "YES",
5429 0 : false);
5430 : CPLConfigOptionSetter oSetter2("AZURE_STORAGE_ACCOUNT",
5431 0 : osStorageAccount.c_str(), false);
5432 0 : const std::string osVSIAZ(std::string("/vsiaz/").append(
5433 0 : pszBlobCore + strlen(".blob.core.windows.net/")));
5434 0 : char **papszFileList = VSIReadDirEx(osVSIAZ.c_str(), nMaxFiles);
5435 0 : if (papszFileList)
5436 : {
5437 0 : *pbGotFileList = true;
5438 0 : return papszFileList;
5439 : }
5440 : }
5441 : }
5442 : }
5443 :
5444 : // HACK (optimization in fact) for MBTiles driver.
5445 47 : if (strstr(pszDirname, ".tiles.mapbox.com") != nullptr)
5446 1 : return nullptr;
5447 :
5448 46 : if (STARTS_WITH(osURL.c_str(), "ftp://"))
5449 : {
5450 0 : WriteFuncStruct sWriteFuncData;
5451 0 : sWriteFuncData.pBuffer = nullptr;
5452 :
5453 0 : std::string osDirname(osURL);
5454 0 : osDirname += '/';
5455 :
5456 0 : char **papszFileList = nullptr;
5457 :
5458 0 : CURLM *hCurlMultiHandle = GetCurlMultiHandleFor(osDirname);
5459 0 : CURL *hCurlHandle = curl_easy_init();
5460 :
5461 0 : for (int iTry = 0; iTry < 2; iTry++)
5462 : {
5463 : struct curl_slist *headers =
5464 0 : VSICurlSetOptions(hCurlHandle, osDirname.c_str(), nullptr);
5465 :
5466 : // On the first pass, we want to try fetching all the possible
5467 : // information (filename, file/directory, size). If that does not
5468 : // work, then try again with CURLOPT_DIRLISTONLY set.
5469 0 : if (iTry == 1)
5470 : {
5471 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_DIRLISTONLY, 1);
5472 : }
5473 :
5474 0 : VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr,
5475 : nullptr);
5476 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
5477 : &sWriteFuncData);
5478 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
5479 : VSICurlHandleWriteFunc);
5480 :
5481 0 : char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
5482 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
5483 : szCurlErrBuf);
5484 :
5485 0 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
5486 : headers);
5487 :
5488 0 : VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
5489 :
5490 0 : curl_slist_free_all(headers);
5491 :
5492 0 : if (sWriteFuncData.pBuffer == nullptr)
5493 : {
5494 0 : curl_easy_cleanup(hCurlHandle);
5495 0 : return nullptr;
5496 : }
5497 :
5498 0 : char *pszLine = sWriteFuncData.pBuffer;
5499 0 : char *c = nullptr;
5500 0 : int nCount = 0;
5501 :
5502 0 : if (STARTS_WITH_CI(pszLine, "<!DOCTYPE HTML") ||
5503 0 : STARTS_WITH_CI(pszLine, "<HTML>"))
5504 : {
5505 : papszFileList =
5506 0 : ParseHTMLFileList(pszDirname, nMaxFiles,
5507 : sWriteFuncData.pBuffer, pbGotFileList);
5508 0 : break;
5509 : }
5510 0 : else if (iTry == 0)
5511 : {
5512 0 : CPLStringList oFileList;
5513 0 : *pbGotFileList = true;
5514 :
5515 0 : while ((c = strchr(pszLine, '\n')) != nullptr)
5516 : {
5517 0 : *c = 0;
5518 0 : if (c - pszLine > 0 && c[-1] == '\r')
5519 0 : c[-1] = 0;
5520 :
5521 0 : char *pszFilename = nullptr;
5522 0 : bool bSizeValid = false;
5523 0 : GUIntBig nFileSize = 0;
5524 0 : bool bIsDirectory = false;
5525 0 : GIntBig mUnixTime = 0;
5526 0 : if (!VSICurlParseFullFTPLine(pszLine, pszFilename,
5527 : bSizeValid, nFileSize,
5528 : bIsDirectory, mUnixTime))
5529 0 : break;
5530 :
5531 0 : if (strcmp(pszFilename, ".") != 0 &&
5532 0 : strcmp(pszFilename, "..") != 0)
5533 : {
5534 0 : if (CPLHasUnbalancedPathTraversal(pszFilename))
5535 : {
5536 0 : CPLError(CE_Warning, CPLE_AppDefined,
5537 : "Ignoring '%s' that has a path traversal "
5538 : "pattern",
5539 : pszFilename);
5540 : }
5541 : else
5542 : {
5543 : std::string osCachedFilename =
5544 0 : CPLSPrintf("%s/%s", osURL.c_str(), pszFilename);
5545 :
5546 0 : FileProp cachedFileProp;
5547 0 : GetCachedFileProp(osCachedFilename.c_str(),
5548 : cachedFileProp);
5549 0 : cachedFileProp.eExists = EXIST_YES;
5550 0 : cachedFileProp.bIsDirectory = bIsDirectory;
5551 0 : cachedFileProp.mTime =
5552 : static_cast<time_t>(mUnixTime);
5553 0 : cachedFileProp.bHasComputedFileSize = bSizeValid;
5554 0 : cachedFileProp.fileSize = nFileSize;
5555 0 : SetCachedFileProp(osCachedFilename.c_str(),
5556 : cachedFileProp);
5557 :
5558 0 : oFileList.AddString(pszFilename);
5559 : if constexpr (ENABLE_DEBUG_VERBOSE)
5560 : {
5561 : struct tm brokendowntime;
5562 : CPLUnixTimeToYMDHMS(mUnixTime, &brokendowntime);
5563 : CPLDebug(
5564 : GetDebugKey(),
5565 : "File[%d] = %s, is_dir = %d, size "
5566 : "= " CPL_FRMT_GUIB
5567 : ", time = %04d/%02d/%02d %02d:%02d:%02d",
5568 : nCount, pszFilename, bIsDirectory ? 1 : 0,
5569 : nFileSize, brokendowntime.tm_year + 1900,
5570 : brokendowntime.tm_mon + 1,
5571 : brokendowntime.tm_mday,
5572 : brokendowntime.tm_hour,
5573 : brokendowntime.tm_min,
5574 : brokendowntime.tm_sec);
5575 : }
5576 :
5577 0 : nCount++;
5578 :
5579 0 : if (nMaxFiles > 0 && oFileList.Count() > nMaxFiles)
5580 0 : break;
5581 : }
5582 : }
5583 :
5584 0 : pszLine = c + 1;
5585 : }
5586 :
5587 0 : if (c == nullptr)
5588 : {
5589 0 : papszFileList = oFileList.StealList();
5590 0 : break;
5591 : }
5592 : }
5593 : else
5594 : {
5595 0 : CPLStringList oFileList;
5596 0 : *pbGotFileList = true;
5597 :
5598 0 : while ((c = strchr(pszLine, '\n')) != nullptr)
5599 : {
5600 0 : *c = 0;
5601 0 : if (c - pszLine > 0 && c[-1] == '\r')
5602 0 : c[-1] = 0;
5603 :
5604 0 : if (strcmp(pszLine, ".") != 0 && strcmp(pszLine, "..") != 0)
5605 : {
5606 0 : oFileList.AddString(pszLine);
5607 : if constexpr (ENABLE_DEBUG_VERBOSE)
5608 : {
5609 : CPLDebug(GetDebugKey(), "File[%d] = %s", nCount,
5610 : pszLine);
5611 : }
5612 0 : nCount++;
5613 : }
5614 :
5615 0 : pszLine = c + 1;
5616 : }
5617 :
5618 0 : papszFileList = oFileList.StealList();
5619 : }
5620 :
5621 0 : CPLFree(sWriteFuncData.pBuffer);
5622 0 : sWriteFuncData.pBuffer = nullptr;
5623 : }
5624 :
5625 0 : CPLFree(sWriteFuncData.pBuffer);
5626 0 : curl_easy_cleanup(hCurlHandle);
5627 :
5628 0 : return papszFileList;
5629 : }
5630 :
5631 : // Try to recognize HTML pages that list the content of a directory.
5632 : // Currently this supports what Apache and shttpd can return.
5633 53 : else if (STARTS_WITH(osURL.c_str(), "http://") ||
5634 7 : STARTS_WITH(osURL.c_str(), "https://"))
5635 : {
5636 92 : std::string osDirname(std::move(osURL));
5637 46 : osDirname += '/';
5638 :
5639 46 : CURLM *hCurlMultiHandle = GetCurlMultiHandleFor(osDirname);
5640 46 : CURL *hCurlHandle = curl_easy_init();
5641 :
5642 : struct curl_slist *headers =
5643 46 : VSICurlSetOptions(hCurlHandle, osDirname.c_str(), nullptr);
5644 :
5645 46 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
5646 :
5647 46 : WriteFuncStruct sWriteFuncData;
5648 46 : VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
5649 46 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
5650 : &sWriteFuncData);
5651 46 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
5652 : VSICurlHandleWriteFunc);
5653 :
5654 46 : char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
5655 46 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
5656 : szCurlErrBuf);
5657 :
5658 46 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
5659 :
5660 46 : VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
5661 :
5662 46 : curl_slist_free_all(headers);
5663 :
5664 46 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
5665 :
5666 46 : if (sWriteFuncData.pBuffer == nullptr)
5667 : {
5668 30 : curl_easy_cleanup(hCurlHandle);
5669 30 : return nullptr;
5670 : }
5671 :
5672 16 : char **papszFileList = nullptr;
5673 16 : if (STARTS_WITH_CI(sWriteFuncData.pBuffer, "<?xml") &&
5674 1 : strstr(sWriteFuncData.pBuffer, "<ListBucketResult") != nullptr)
5675 : {
5676 0 : CPLStringList osFileList;
5677 0 : std::string osBaseURL(pszDirname);
5678 0 : osBaseURL += "/";
5679 0 : bool bIsTruncated = true;
5680 0 : bool ret = AnalyseS3FileList(
5681 0 : osBaseURL, sWriteFuncData.pBuffer, osFileList, nMaxFiles,
5682 0 : GetS3IgnoredStorageClasses(), bIsTruncated);
5683 : // If the list is truncated, then don't report it.
5684 0 : if (ret && !bIsTruncated)
5685 : {
5686 0 : if (osFileList.empty())
5687 : {
5688 : // To avoid an error to be reported
5689 0 : osFileList.AddString(".");
5690 : }
5691 0 : papszFileList = osFileList.StealList();
5692 0 : *pbGotFileList = true;
5693 0 : }
5694 : }
5695 : else
5696 : {
5697 16 : papszFileList = ParseHTMLFileList(
5698 : pszDirname, nMaxFiles, sWriteFuncData.pBuffer, pbGotFileList);
5699 : }
5700 :
5701 16 : CPLFree(sWriteFuncData.pBuffer);
5702 16 : curl_easy_cleanup(hCurlHandle);
5703 16 : return papszFileList;
5704 : }
5705 :
5706 0 : return nullptr;
5707 : }
5708 :
5709 : /************************************************************************/
5710 : /* GetS3IgnoredStorageClasses() */
5711 : /************************************************************************/
5712 :
5713 67 : std::set<std::string> VSICurlFilesystemHandlerBase::GetS3IgnoredStorageClasses()
5714 : {
5715 67 : std::set<std::string> oSetIgnoredStorageClasses;
5716 : const char *pszIgnoredStorageClasses =
5717 67 : CPLGetConfigOption("CPL_VSIL_CURL_IGNORE_STORAGE_CLASSES", nullptr);
5718 : const char *pszIgnoreGlacierStorage =
5719 67 : CPLGetConfigOption("CPL_VSIL_CURL_IGNORE_GLACIER_STORAGE", nullptr);
5720 : CPLStringList aosIgnoredStorageClasses(
5721 : CSLTokenizeString2(pszIgnoredStorageClasses ? pszIgnoredStorageClasses
5722 : : "GLACIER,DEEP_ARCHIVE",
5723 134 : ",", 0));
5724 199 : for (int i = 0; i < aosIgnoredStorageClasses.size(); ++i)
5725 132 : oSetIgnoredStorageClasses.insert(aosIgnoredStorageClasses[i]);
5726 66 : if (pszIgnoredStorageClasses == nullptr &&
5727 133 : pszIgnoreGlacierStorage != nullptr &&
5728 1 : !CPLTestBool(pszIgnoreGlacierStorage))
5729 : {
5730 1 : oSetIgnoredStorageClasses.clear();
5731 : }
5732 134 : return oSetIgnoredStorageClasses;
5733 : }
5734 :
5735 : /************************************************************************/
5736 : /* Stat() */
5737 : /************************************************************************/
5738 :
5739 1094 : int VSICurlFilesystemHandlerBase::Stat(const char *pszFilename,
5740 : VSIStatBufL *pStatBuf, int nFlags)
5741 : {
5742 1169 : if (!cpl::starts_with(std::string_view(pszFilename), GetFSPrefix()) &&
5743 75 : !StartsWithVSICurlPrefix(pszFilename))
5744 : {
5745 1 : return -1;
5746 : }
5747 :
5748 1093 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
5749 :
5750 1093 : if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
5751 : {
5752 18 : cpl::FileProp oFileProp;
5753 27 : if (!GetCachedFileProp(GetURLFromFilename(pszFilename).c_str(),
5754 32 : oFileProp) ||
5755 5 : oFileProp.eExists != EXIST_YES)
5756 : {
5757 4 : return -1;
5758 : }
5759 5 : pStatBuf->st_mode = static_cast<unsigned short>(oFileProp.nMode);
5760 5 : pStatBuf->st_mtime = oFileProp.mTime;
5761 5 : pStatBuf->st_size = oFileProp.fileSize;
5762 5 : return 0;
5763 : }
5764 :
5765 2168 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5766 2168 : NetworkStatisticsAction oContextAction("Stat");
5767 :
5768 2168 : const std::string osFilename(pszFilename);
5769 :
5770 1084 : if (!IsAllowedFilename(pszFilename))
5771 0 : return -1;
5772 :
5773 1084 : bool bListDir = true;
5774 1084 : bool bEmptyDir = false;
5775 : std::string osURL(VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr,
5776 : nullptr, &bListDir, &bEmptyDir,
5777 2168 : nullptr, nullptr, nullptr));
5778 :
5779 1084 : const char *pszOptionVal = VSIGetPathSpecificOption(
5780 : pszFilename, "GDAL_DISABLE_READDIR_ON_OPEN", "NO");
5781 : const bool bSkipReadDir =
5782 1084 : !bListDir || bEmptyDir || EQUAL(pszOptionVal, "EMPTY_DIR") ||
5783 2168 : CPLTestBool(pszOptionVal) || !AllowCachedDataFor(pszFilename);
5784 :
5785 : // Does it look like a FTP directory?
5786 1084 : if (STARTS_WITH(osURL.c_str(), "ftp://") && osFilename.back() == '/' &&
5787 0 : !bSkipReadDir)
5788 : {
5789 0 : char **papszFileList = ReadDirEx(osFilename.c_str(), 0);
5790 0 : if (papszFileList)
5791 : {
5792 0 : pStatBuf->st_mode = S_IFDIR;
5793 0 : pStatBuf->st_size = 0;
5794 :
5795 0 : CSLDestroy(papszFileList);
5796 :
5797 0 : return 0;
5798 : }
5799 0 : return -1;
5800 : }
5801 1084 : else if (strchr(CPLGetFilename(osFilename.c_str()), '.') != nullptr &&
5802 2067 : !STARTS_WITH_CI(CPLGetExtensionSafe(osFilename.c_str()).c_str(),
5803 500 : "zip") &&
5804 500 : strstr(osFilename.c_str(), ".zip.") != nullptr &&
5805 3151 : strstr(osFilename.c_str(), ".ZIP.") != nullptr && !bSkipReadDir)
5806 : {
5807 0 : bool bGotFileList = false;
5808 0 : char **papszFileList = ReadDirInternal(
5809 0 : CPLGetDirnameSafe(osFilename.c_str()).c_str(), 0, &bGotFileList);
5810 : const bool bFound =
5811 0 : VSICurlIsFileInList(papszFileList,
5812 0 : CPLGetFilename(osFilename.c_str())) != -1;
5813 0 : CSLDestroy(papszFileList);
5814 0 : if (bGotFileList && !bFound)
5815 : {
5816 0 : return -1;
5817 : }
5818 : }
5819 :
5820 1084 : VSICurlHandle *poHandle = CreateFileHandle(osFilename.c_str());
5821 1084 : if (poHandle == nullptr)
5822 22 : return -1;
5823 :
5824 1427 : if (poHandle->IsKnownFileSize() ||
5825 365 : ((nFlags & VSI_STAT_SIZE_FLAG) && !poHandle->IsDirectory() &&
5826 202 : CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_SLOW_GET_SIZE", "YES"))))
5827 : {
5828 899 : pStatBuf->st_size = poHandle->GetFileSize(true);
5829 : }
5830 :
5831 : const int nRet =
5832 1062 : poHandle->Exists((nFlags & VSI_STAT_SET_ERROR_FLAG) > 0) ? 0 : -1;
5833 1062 : pStatBuf->st_mtime = poHandle->GetMTime();
5834 1062 : pStatBuf->st_mode = static_cast<unsigned short>(poHandle->GetMode());
5835 1062 : if (pStatBuf->st_mode == 0)
5836 1026 : pStatBuf->st_mode = poHandle->IsDirectory() ? S_IFDIR : S_IFREG;
5837 1062 : delete poHandle;
5838 1062 : return nRet;
5839 : }
5840 :
5841 : /************************************************************************/
5842 : /* ReadDirInternal() */
5843 : /************************************************************************/
5844 :
5845 290 : char **VSICurlFilesystemHandlerBase::ReadDirInternal(const char *pszDirname,
5846 : int nMaxFiles,
5847 : bool *pbGotFileList)
5848 : {
5849 580 : std::string osDirname(pszDirname);
5850 :
5851 : // Replace a/b/../c by a/c
5852 290 : const auto posSlashDotDot = osDirname.find("/..");
5853 290 : if (posSlashDotDot != std::string::npos && posSlashDotDot >= 1)
5854 : {
5855 : const auto posPrecedingSlash =
5856 0 : osDirname.find_last_of('/', posSlashDotDot - 1);
5857 0 : if (posPrecedingSlash != std::string::npos && posPrecedingSlash >= 1)
5858 : {
5859 0 : osDirname.erase(osDirname.begin() + posPrecedingSlash,
5860 0 : osDirname.begin() + posSlashDotDot + strlen("/.."));
5861 : }
5862 : }
5863 :
5864 580 : std::string osDirnameOri(osDirname);
5865 290 : if (osDirname + "/" == GetFSPrefix())
5866 : {
5867 0 : osDirname += "/";
5868 : }
5869 290 : else if (osDirname != GetFSPrefix())
5870 : {
5871 443 : while (!osDirname.empty() && osDirname.back() == '/')
5872 170 : osDirname.erase(osDirname.size() - 1);
5873 : }
5874 :
5875 290 : if (osDirname.size() < GetFSPrefix().size())
5876 : {
5877 0 : if (pbGotFileList)
5878 0 : *pbGotFileList = true;
5879 0 : return nullptr;
5880 : }
5881 :
5882 580 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5883 580 : NetworkStatisticsAction oContextAction("ReadDir");
5884 :
5885 580 : CPLMutexHolder oHolder(&hMutex);
5886 :
5887 : // If we know the file exists and is not a directory,
5888 : // then don't try to list its content.
5889 580 : FileProp cachedFileProp;
5890 870 : if (GetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
5891 53 : cachedFileProp) &&
5892 870 : cachedFileProp.eExists == EXIST_YES && !cachedFileProp.bIsDirectory)
5893 : {
5894 8 : if (osDirnameOri != osDirname)
5895 : {
5896 3 : if (GetCachedFileProp((GetURLFromFilename(osDirname) + "/").c_str(),
5897 1 : cachedFileProp) &&
5898 4 : cachedFileProp.eExists == EXIST_YES &&
5899 1 : !cachedFileProp.bIsDirectory)
5900 : {
5901 0 : if (pbGotFileList)
5902 0 : *pbGotFileList = true;
5903 0 : return nullptr;
5904 : }
5905 : }
5906 : else
5907 : {
5908 7 : if (pbGotFileList)
5909 0 : *pbGotFileList = true;
5910 7 : return nullptr;
5911 : }
5912 : }
5913 :
5914 566 : CachedDirList cachedDirList;
5915 283 : if (!GetCachedDirList(osDirname.c_str(), cachedDirList))
5916 : {
5917 : cachedDirList.oFileList.Assign(GetFileList(osDirname.c_str(), nMaxFiles,
5918 175 : &cachedDirList.bGotFileList),
5919 175 : true);
5920 175 : if (cachedDirList.bGotFileList && cachedDirList.oFileList.empty())
5921 : {
5922 : // To avoid an error to be reported
5923 18 : cachedDirList.oFileList.AddString(".");
5924 : }
5925 175 : if (nMaxFiles <= 0 || cachedDirList.oFileList.size() < nMaxFiles)
5926 : {
5927 : // Only cache content if we didn't hit the limitation
5928 170 : SetCachedDirList(osDirname.c_str(), cachedDirList);
5929 : }
5930 : }
5931 :
5932 283 : if (pbGotFileList)
5933 141 : *pbGotFileList = cachedDirList.bGotFileList;
5934 :
5935 283 : return CSLDuplicate(cachedDirList.oFileList.List());
5936 : }
5937 :
5938 : /************************************************************************/
5939 : /* InvalidateDirContent() */
5940 : /************************************************************************/
5941 :
5942 199 : void VSICurlFilesystemHandlerBase::InvalidateDirContent(
5943 : const std::string &osDirname)
5944 : {
5945 398 : CPLMutexHolder oHolder(&hMutex);
5946 :
5947 398 : CachedDirList oCachedDirList;
5948 199 : if (oCacheDirList.tryGet(osDirname, oCachedDirList))
5949 : {
5950 19 : nCachedFilesInDirList -= oCachedDirList.oFileList.size();
5951 19 : oCacheDirList.remove(osDirname);
5952 : }
5953 199 : }
5954 :
5955 : /************************************************************************/
5956 : /* ReadDirEx() */
5957 : /************************************************************************/
5958 :
5959 89 : char **VSICurlFilesystemHandlerBase::ReadDirEx(const char *pszDirname,
5960 : int nMaxFiles)
5961 : {
5962 89 : return ReadDirInternal(pszDirname, nMaxFiles, nullptr);
5963 : }
5964 :
5965 : /************************************************************************/
5966 : /* SiblingFiles() */
5967 : /************************************************************************/
5968 :
5969 50 : char **VSICurlFilesystemHandlerBase::SiblingFiles(const char *pszFilename)
5970 : {
5971 : /* Small optimization to avoid unnecessary stat'ing from PAux or ENVI */
5972 : /* drivers. The MBTiles driver needs no companion file. */
5973 50 : if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "mbtiles"))
5974 : {
5975 6 : return static_cast<char **>(CPLCalloc(1, sizeof(char *)));
5976 : }
5977 44 : return nullptr;
5978 : }
5979 :
5980 : /************************************************************************/
5981 : /* GetFileMetadata() */
5982 : /************************************************************************/
5983 :
5984 7 : char **VSICurlFilesystemHandlerBase::GetFileMetadata(const char *pszFilename,
5985 : const char *pszDomain,
5986 : CSLConstList)
5987 : {
5988 7 : if (pszDomain == nullptr || !EQUAL(pszDomain, "HEADERS"))
5989 3 : return nullptr;
5990 8 : std::unique_ptr<VSICurlHandle> poHandle(CreateFileHandle(pszFilename));
5991 4 : if (poHandle == nullptr)
5992 0 : return nullptr;
5993 :
5994 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5995 8 : NetworkStatisticsAction oContextAction("GetFileMetadata");
5996 :
5997 4 : poHandle->GetFileSizeOrHeaders(true, true);
5998 4 : return CSLDuplicate(poHandle->GetHeaders().List());
5999 : }
6000 :
6001 : /************************************************************************/
6002 : /* VSIAppendWriteHandle() */
6003 : /************************************************************************/
6004 :
6005 17 : VSIAppendWriteHandle::VSIAppendWriteHandle(VSICurlFilesystemHandlerBase *poFS,
6006 : const char *pszFSPrefix,
6007 : const char *pszFilename,
6008 17 : int nChunkSize)
6009 : : m_poFS(poFS), m_osFSPrefix(pszFSPrefix), m_osFilename(pszFilename),
6010 34 : m_oRetryParameters(CPLStringList(CPLHTTPGetOptionsFromEnv(pszFilename))),
6011 34 : m_nBufferSize(nChunkSize)
6012 : {
6013 17 : m_pabyBuffer = static_cast<GByte *>(VSIMalloc(m_nBufferSize));
6014 17 : if (m_pabyBuffer == nullptr)
6015 : {
6016 0 : CPLError(CE_Failure, CPLE_AppDefined,
6017 : "Cannot allocate working buffer for %s writing",
6018 : m_osFSPrefix.c_str());
6019 : }
6020 17 : }
6021 :
6022 : /************************************************************************/
6023 : /* ~VSIAppendWriteHandle() */
6024 : /************************************************************************/
6025 :
6026 17 : VSIAppendWriteHandle::~VSIAppendWriteHandle()
6027 : {
6028 : /* WARNING: implementation should call Close() themselves */
6029 : /* cannot be done safely from here, since Send() can be called. */
6030 17 : CPLFree(m_pabyBuffer);
6031 17 : }
6032 :
6033 : /************************************************************************/
6034 : /* Seek() */
6035 : /************************************************************************/
6036 :
6037 0 : int VSIAppendWriteHandle::Seek(vsi_l_offset nOffset, int nWhence)
6038 : {
6039 0 : if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) ||
6040 0 : (nWhence == SEEK_CUR && nOffset == 0) ||
6041 0 : (nWhence == SEEK_END && nOffset == 0)))
6042 : {
6043 0 : CPLError(CE_Failure, CPLE_NotSupported,
6044 : "Seek not supported on writable %s files",
6045 : m_osFSPrefix.c_str());
6046 0 : m_bError = true;
6047 0 : return -1;
6048 : }
6049 0 : return 0;
6050 : }
6051 :
6052 : /************************************************************************/
6053 : /* Tell() */
6054 : /************************************************************************/
6055 :
6056 0 : vsi_l_offset VSIAppendWriteHandle::Tell()
6057 : {
6058 0 : return m_nCurOffset;
6059 : }
6060 :
6061 : /************************************************************************/
6062 : /* Read() */
6063 : /************************************************************************/
6064 :
6065 0 : size_t VSIAppendWriteHandle::Read(void * /* pBuffer */, size_t /* nBytes */)
6066 : {
6067 0 : CPLError(CE_Failure, CPLE_NotSupported,
6068 : "Read not supported on writable %s files", m_osFSPrefix.c_str());
6069 0 : m_bError = true;
6070 0 : return 0;
6071 : }
6072 :
6073 : /************************************************************************/
6074 : /* ReadCallBackBuffer() */
6075 : /************************************************************************/
6076 :
6077 1 : size_t VSIAppendWriteHandle::ReadCallBackBuffer(char *buffer, size_t size,
6078 : size_t nitems, void *instream)
6079 : {
6080 1 : VSIAppendWriteHandle *poThis =
6081 : static_cast<VSIAppendWriteHandle *>(instream);
6082 1 : const int nSizeMax = static_cast<int>(size * nitems);
6083 : const int nSizeToWrite = std::min(
6084 1 : nSizeMax, poThis->m_nBufferOff - poThis->m_nBufferOffReadCallback);
6085 1 : memcpy(buffer, poThis->m_pabyBuffer + poThis->m_nBufferOffReadCallback,
6086 : nSizeToWrite);
6087 1 : poThis->m_nBufferOffReadCallback += nSizeToWrite;
6088 1 : return nSizeToWrite;
6089 : }
6090 :
6091 : /************************************************************************/
6092 : /* Write() */
6093 : /************************************************************************/
6094 :
6095 9 : size_t VSIAppendWriteHandle::Write(const void *pBuffer, size_t nBytes)
6096 : {
6097 9 : if (m_bError)
6098 0 : return 0;
6099 :
6100 9 : size_t nBytesToWrite = nBytes;
6101 9 : if (nBytesToWrite == 0)
6102 0 : return 0;
6103 :
6104 9 : const GByte *pabySrcBuffer = reinterpret_cast<const GByte *>(pBuffer);
6105 21 : while (nBytesToWrite > 0)
6106 : {
6107 12 : if (m_nBufferOff == m_nBufferSize)
6108 : {
6109 3 : if (!Send(false))
6110 : {
6111 0 : m_bError = true;
6112 0 : return 0;
6113 : }
6114 3 : m_nBufferOff = 0;
6115 : }
6116 :
6117 12 : const int nToWriteInBuffer = static_cast<int>(std::min(
6118 12 : static_cast<size_t>(m_nBufferSize - m_nBufferOff), nBytesToWrite));
6119 12 : memcpy(m_pabyBuffer + m_nBufferOff, pabySrcBuffer, nToWriteInBuffer);
6120 12 : pabySrcBuffer += nToWriteInBuffer;
6121 12 : m_nBufferOff += nToWriteInBuffer;
6122 12 : m_nCurOffset += nToWriteInBuffer;
6123 12 : nBytesToWrite -= nToWriteInBuffer;
6124 : }
6125 9 : return nBytes;
6126 : }
6127 :
6128 : /************************************************************************/
6129 : /* Close() */
6130 : /************************************************************************/
6131 :
6132 30 : int VSIAppendWriteHandle::Close()
6133 : {
6134 30 : int nRet = 0;
6135 30 : if (!m_bClosed)
6136 : {
6137 17 : m_bClosed = true;
6138 17 : if (!m_bError && !Send(true))
6139 4 : nRet = -1;
6140 : }
6141 30 : return nRet;
6142 : }
6143 :
6144 : /************************************************************************/
6145 : /* CurlRequestHelper() */
6146 : /************************************************************************/
6147 :
6148 387 : CurlRequestHelper::CurlRequestHelper()
6149 : {
6150 387 : VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
6151 387 : VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
6152 : nullptr);
6153 387 : }
6154 :
6155 : /************************************************************************/
6156 : /* ~CurlRequestHelper() */
6157 : /************************************************************************/
6158 :
6159 774 : CurlRequestHelper::~CurlRequestHelper()
6160 : {
6161 387 : CPLFree(sWriteFuncData.pBuffer);
6162 387 : CPLFree(sWriteFuncHeaderData.pBuffer);
6163 387 : }
6164 :
6165 : /************************************************************************/
6166 : /* perform() */
6167 : /************************************************************************/
6168 :
6169 387 : long CurlRequestHelper::perform(CURL *hCurlHandle, struct curl_slist *headers,
6170 : VSICurlFilesystemHandlerBase *poFS,
6171 : IVSIS3LikeHandleHelper *poS3HandleHelper)
6172 : {
6173 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
6174 :
6175 387 : poS3HandleHelper->ResetQueryParameters();
6176 :
6177 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
6178 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
6179 : VSICurlHandleWriteFunc);
6180 :
6181 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
6182 : &sWriteFuncHeaderData);
6183 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
6184 : VSICurlHandleWriteFunc);
6185 :
6186 387 : szCurlErrBuf[0] = '\0';
6187 387 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
6188 :
6189 387 : VSICURLMultiPerform(poFS->GetCurlMultiHandleFor(poS3HandleHelper->GetURL()),
6190 : hCurlHandle);
6191 :
6192 387 : VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
6193 :
6194 387 : curl_slist_free_all(headers);
6195 :
6196 387 : long response_code = 0;
6197 387 : curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
6198 387 : return response_code;
6199 : }
6200 :
6201 : /************************************************************************/
6202 : /* NetworkStatisticsLogger */
6203 : /************************************************************************/
6204 :
6205 : // Global variable
6206 : NetworkStatisticsLogger NetworkStatisticsLogger::gInstance{};
6207 : int NetworkStatisticsLogger::gnEnabled = -1; // unknown state
6208 :
6209 0 : static void ShowNetworkStats()
6210 : {
6211 0 : printf("Network statistics:\n%s\n", // ok
6212 0 : NetworkStatisticsLogger::GetReportAsSerializedJSON().c_str());
6213 0 : }
6214 :
6215 7 : void NetworkStatisticsLogger::ReadEnabled()
6216 : {
6217 : const bool bShowNetworkStats =
6218 7 : CPLTestBool(CPLGetConfigOption("CPL_VSIL_SHOW_NETWORK_STATS", "NO"));
6219 7 : gnEnabled =
6220 7 : (bShowNetworkStats || CPLTestBool(CPLGetConfigOption(
6221 : "CPL_VSIL_NETWORK_STATS_ENABLED", "NO")))
6222 14 : ? TRUE
6223 : : FALSE;
6224 7 : if (bShowNetworkStats)
6225 : {
6226 : static bool bRegistered = false;
6227 0 : if (!bRegistered)
6228 : {
6229 0 : bRegistered = true;
6230 0 : atexit(ShowNetworkStats);
6231 : }
6232 : }
6233 7 : }
6234 :
6235 149357 : void NetworkStatisticsLogger::EnterFileSystem(const char *pszName)
6236 : {
6237 149357 : if (!IsEnabled())
6238 149356 : return;
6239 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6240 2 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6241 2 : ContextPathItem(ContextPathType::FILESYSTEM, pszName));
6242 : }
6243 :
6244 149357 : void NetworkStatisticsLogger::LeaveFileSystem()
6245 : {
6246 149357 : if (!IsEnabled())
6247 149356 : return;
6248 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6249 1 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6250 : }
6251 :
6252 147258 : void NetworkStatisticsLogger::EnterFile(const char *pszName)
6253 : {
6254 147258 : if (!IsEnabled())
6255 147257 : return;
6256 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6257 2 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6258 2 : ContextPathItem(ContextPathType::FILE, pszName));
6259 : }
6260 :
6261 147258 : void NetworkStatisticsLogger::LeaveFile()
6262 : {
6263 147258 : if (!IsEnabled())
6264 147257 : return;
6265 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6266 1 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6267 : }
6268 :
6269 149357 : void NetworkStatisticsLogger::EnterAction(const char *pszName)
6270 : {
6271 149357 : if (!IsEnabled())
6272 149356 : return;
6273 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6274 2 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6275 2 : ContextPathItem(ContextPathType::ACTION, pszName));
6276 : }
6277 :
6278 149357 : void NetworkStatisticsLogger::LeaveAction()
6279 : {
6280 149357 : if (!IsEnabled())
6281 149356 : return;
6282 1 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6283 1 : gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6284 : }
6285 :
6286 : std::vector<NetworkStatisticsLogger::Counters *>
6287 1 : NetworkStatisticsLogger::GetCountersForContext()
6288 : {
6289 1 : std::vector<Counters *> v;
6290 1 : const auto &contextPath = gInstance.m_mapThreadIdToContextPath[CPLGetPID()];
6291 :
6292 1 : Stats *curStats = &m_stats;
6293 1 : v.push_back(&(curStats->counters));
6294 :
6295 1 : bool inFileSystem = false;
6296 1 : bool inFile = false;
6297 1 : bool inAction = false;
6298 4 : for (const auto &item : contextPath)
6299 : {
6300 3 : if (item.eType == ContextPathType::FILESYSTEM)
6301 : {
6302 1 : if (inFileSystem)
6303 0 : continue;
6304 1 : inFileSystem = true;
6305 : }
6306 2 : else if (item.eType == ContextPathType::FILE)
6307 : {
6308 1 : if (inFile)
6309 0 : continue;
6310 1 : inFile = true;
6311 : }
6312 1 : else if (item.eType == ContextPathType::ACTION)
6313 : {
6314 1 : if (inAction)
6315 0 : continue;
6316 1 : inAction = true;
6317 : }
6318 :
6319 3 : curStats = &(curStats->children[item]);
6320 3 : v.push_back(&(curStats->counters));
6321 : }
6322 :
6323 1 : return v;
6324 : }
6325 :
6326 895 : void NetworkStatisticsLogger::LogGET(size_t nDownloadedBytes)
6327 : {
6328 895 : if (!IsEnabled())
6329 895 : return;
6330 0 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6331 0 : for (auto counters : gInstance.GetCountersForContext())
6332 : {
6333 0 : counters->nGET++;
6334 0 : counters->nGETDownloadedBytes += nDownloadedBytes;
6335 : }
6336 : }
6337 :
6338 132 : void NetworkStatisticsLogger::LogPUT(size_t nUploadedBytes)
6339 : {
6340 132 : if (!IsEnabled())
6341 131 : return;
6342 2 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6343 5 : for (auto counters : gInstance.GetCountersForContext())
6344 : {
6345 4 : counters->nPUT++;
6346 4 : counters->nPUTUploadedBytes += nUploadedBytes;
6347 : }
6348 : }
6349 :
6350 346 : void NetworkStatisticsLogger::LogHEAD()
6351 : {
6352 346 : if (!IsEnabled())
6353 346 : return;
6354 0 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6355 0 : for (auto counters : gInstance.GetCountersForContext())
6356 : {
6357 0 : counters->nHEAD++;
6358 : }
6359 : }
6360 :
6361 37 : void NetworkStatisticsLogger::LogPOST(size_t nUploadedBytes,
6362 : size_t nDownloadedBytes)
6363 : {
6364 37 : if (!IsEnabled())
6365 37 : return;
6366 0 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6367 0 : for (auto counters : gInstance.GetCountersForContext())
6368 : {
6369 0 : counters->nPOST++;
6370 0 : counters->nPOSTUploadedBytes += nUploadedBytes;
6371 0 : counters->nPOSTDownloadedBytes += nDownloadedBytes;
6372 : }
6373 : }
6374 :
6375 44 : void NetworkStatisticsLogger::LogDELETE()
6376 : {
6377 44 : if (!IsEnabled())
6378 44 : return;
6379 0 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6380 0 : for (auto counters : gInstance.GetCountersForContext())
6381 : {
6382 0 : counters->nDELETE++;
6383 : }
6384 : }
6385 :
6386 2 : void NetworkStatisticsLogger::Reset()
6387 : {
6388 2 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6389 2 : gInstance.m_stats = Stats();
6390 2 : gnEnabled = -1;
6391 2 : }
6392 :
6393 4 : void NetworkStatisticsLogger::Stats::AsJSON(CPLJSONObject &oJSON) const
6394 : {
6395 8 : CPLJSONObject oMethods;
6396 4 : if (counters.nHEAD)
6397 0 : oMethods.Add("HEAD/count", counters.nHEAD);
6398 4 : if (counters.nGET)
6399 0 : oMethods.Add("GET/count", counters.nGET);
6400 4 : if (counters.nGETDownloadedBytes)
6401 0 : oMethods.Add("GET/downloaded_bytes", counters.nGETDownloadedBytes);
6402 4 : if (counters.nPUT)
6403 4 : oMethods.Add("PUT/count", counters.nPUT);
6404 4 : if (counters.nPUTUploadedBytes)
6405 4 : oMethods.Add("PUT/uploaded_bytes", counters.nPUTUploadedBytes);
6406 4 : if (counters.nPOST)
6407 0 : oMethods.Add("POST/count", counters.nPOST);
6408 4 : if (counters.nPOSTUploadedBytes)
6409 0 : oMethods.Add("POST/uploaded_bytes", counters.nPOSTUploadedBytes);
6410 4 : if (counters.nPOSTDownloadedBytes)
6411 0 : oMethods.Add("POST/downloaded_bytes", counters.nPOSTDownloadedBytes);
6412 4 : if (counters.nDELETE)
6413 0 : oMethods.Add("DELETE/count", counters.nDELETE);
6414 4 : oJSON.Add("methods", oMethods);
6415 8 : CPLJSONObject oFiles;
6416 4 : bool bFilesAdded = false;
6417 7 : for (const auto &kv : children)
6418 : {
6419 6 : CPLJSONObject childJSON;
6420 3 : kv.second.AsJSON(childJSON);
6421 3 : if (kv.first.eType == ContextPathType::FILESYSTEM)
6422 : {
6423 1 : std::string osName(kv.first.osName);
6424 1 : if (!osName.empty() && osName[0] == '/')
6425 1 : osName = osName.substr(1);
6426 1 : if (!osName.empty() && osName.back() == '/')
6427 1 : osName.pop_back();
6428 1 : oJSON.Add(("handlers/" + osName).c_str(), childJSON);
6429 : }
6430 2 : else if (kv.first.eType == ContextPathType::FILE)
6431 : {
6432 1 : if (!bFilesAdded)
6433 : {
6434 1 : bFilesAdded = true;
6435 1 : oJSON.Add("files", oFiles);
6436 : }
6437 1 : oFiles.AddNoSplitName(kv.first.osName.c_str(), childJSON);
6438 : }
6439 1 : else if (kv.first.eType == ContextPathType::ACTION)
6440 : {
6441 1 : oJSON.Add(("actions/" + kv.first.osName).c_str(), childJSON);
6442 : }
6443 : }
6444 4 : }
6445 :
6446 1 : std::string NetworkStatisticsLogger::GetReportAsSerializedJSON()
6447 : {
6448 2 : std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6449 :
6450 2 : CPLJSONObject oJSON;
6451 1 : gInstance.m_stats.AsJSON(oJSON);
6452 2 : return oJSON.Format(CPLJSONObject::PrettyFormat::Pretty);
6453 : }
6454 :
6455 : } /* end of namespace cpl */
6456 :
6457 : /************************************************************************/
6458 : /* VSICurlParseUnixPermissions() */
6459 : /************************************************************************/
6460 :
6461 23 : int VSICurlParseUnixPermissions(const char *pszPermissions)
6462 : {
6463 23 : if (strlen(pszPermissions) != 9)
6464 12 : return 0;
6465 11 : int nMode = 0;
6466 11 : if (pszPermissions[0] == 'r')
6467 11 : nMode |= S_IRUSR;
6468 11 : if (pszPermissions[1] == 'w')
6469 11 : nMode |= S_IWUSR;
6470 11 : if (pszPermissions[2] == 'x')
6471 11 : nMode |= S_IXUSR;
6472 11 : if (pszPermissions[3] == 'r')
6473 11 : nMode |= S_IRGRP;
6474 11 : if (pszPermissions[4] == 'w')
6475 11 : nMode |= S_IWGRP;
6476 11 : if (pszPermissions[5] == 'x')
6477 11 : nMode |= S_IXGRP;
6478 11 : if (pszPermissions[6] == 'r')
6479 11 : nMode |= S_IROTH;
6480 11 : if (pszPermissions[7] == 'w')
6481 11 : nMode |= S_IWOTH;
6482 11 : if (pszPermissions[8] == 'x')
6483 11 : nMode |= S_IXOTH;
6484 11 : return nMode;
6485 : }
6486 :
6487 : /************************************************************************/
6488 : /* Cache of file properties. */
6489 : /************************************************************************/
6490 :
6491 : static std::mutex oCacheFilePropMutex;
6492 : static lru11::Cache<std::string, cpl::FileProp> *poCacheFileProp = nullptr;
6493 :
6494 : /************************************************************************/
6495 : /* VSICURLGetCachedFileProp() */
6496 : /************************************************************************/
6497 :
6498 153108 : bool VSICURLGetCachedFileProp(const char *pszURL, cpl::FileProp &oFileProp)
6499 : {
6500 153108 : std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6501 457832 : return poCacheFileProp != nullptr &&
6502 457270 : poCacheFileProp->tryGet(std::string(pszURL), oFileProp) &&
6503 : // Let a chance to use new auth parameters
6504 151054 : !(oFileProp.eExists == cpl::EXIST_NO &&
6505 306542 : gnGenerationAuthParameters != oFileProp.nGenerationAuthParameters);
6506 : }
6507 :
6508 : /************************************************************************/
6509 : /* VSICURLSetCachedFileProp() */
6510 : /************************************************************************/
6511 :
6512 1280 : void VSICURLSetCachedFileProp(const char *pszURL, cpl::FileProp &oFileProp)
6513 : {
6514 1280 : std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6515 1280 : if (poCacheFileProp == nullptr)
6516 211 : poCacheFileProp =
6517 211 : new lru11::Cache<std::string, cpl::FileProp>(100 * 1024);
6518 1280 : oFileProp.nGenerationAuthParameters = gnGenerationAuthParameters;
6519 1280 : poCacheFileProp->insert(std::string(pszURL), oFileProp);
6520 1280 : }
6521 :
6522 : /************************************************************************/
6523 : /* VSICURLInvalidateCachedFileProp() */
6524 : /************************************************************************/
6525 :
6526 273 : void VSICURLInvalidateCachedFileProp(const char *pszURL)
6527 : {
6528 546 : std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6529 273 : if (poCacheFileProp != nullptr)
6530 155 : poCacheFileProp->remove(std::string(pszURL));
6531 273 : }
6532 :
6533 : /************************************************************************/
6534 : /* VSICURLInvalidateCachedFilePropPrefix() */
6535 : /************************************************************************/
6536 :
6537 7 : void VSICURLInvalidateCachedFilePropPrefix(const char *pszURL)
6538 : {
6539 14 : std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6540 7 : if (poCacheFileProp != nullptr)
6541 : {
6542 6 : std::list<std::string> keysToRemove;
6543 3 : const size_t nURLSize = strlen(pszURL);
6544 : auto lambda =
6545 10 : [&keysToRemove, &pszURL, nURLSize](
6546 14 : const lru11::KeyValuePair<std::string, cpl::FileProp> &kv)
6547 : {
6548 10 : if (strncmp(kv.key.c_str(), pszURL, nURLSize) == 0)
6549 4 : keysToRemove.push_back(kv.key);
6550 13 : };
6551 3 : poCacheFileProp->cwalk(lambda);
6552 7 : for (const auto &key : keysToRemove)
6553 4 : poCacheFileProp->remove(key);
6554 : }
6555 7 : }
6556 :
6557 : /************************************************************************/
6558 : /* VSICURLDestroyCacheFileProp() */
6559 : /************************************************************************/
6560 :
6561 20177 : void VSICURLDestroyCacheFileProp()
6562 : {
6563 20177 : std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6564 20177 : delete poCacheFileProp;
6565 20177 : poCacheFileProp = nullptr;
6566 20177 : }
6567 :
6568 : /************************************************************************/
6569 : /* VSICURLMultiCleanup() */
6570 : /************************************************************************/
6571 :
6572 300 : void VSICURLMultiCleanup(CURLM *hCurlMultiHandle)
6573 : {
6574 : #if defined(CURL_AT_LEAST_VERSION) && defined(_WIN32)
6575 : // Since curl 8.20.0, auxiliary threads are used for DNS resolution
6576 : // Trying to join them when detaching the DLL results in a hang.
6577 : // See https://github.com/curl/curl/issues/21466#issuecomment-4372138595
6578 : #if CURL_AT_LEAST_VERSION(8, 20, 0)
6579 : if (GDALIsInGlobalDestructorFromDLLMain())
6580 : curl_multi_setopt(hCurlMultiHandle, CURLMOPT_QUICK_EXIT, 1L);
6581 : #endif
6582 : #endif
6583 :
6584 300 : void *old_handler = CPLHTTPIgnoreSigPipe();
6585 300 : curl_multi_cleanup(hCurlMultiHandle);
6586 300 : CPLHTTPRestoreSigPipeHandler(old_handler);
6587 300 : }
6588 :
6589 : /************************************************************************/
6590 : /* VSICurlInstallReadCbk() */
6591 : /************************************************************************/
6592 :
6593 3 : int VSICurlInstallReadCbk(VSILFILE *fp, VSICurlReadCbkFunc pfnReadCbk,
6594 : void *pfnUserData, int bStopOnInterruptUntilUninstall)
6595 : {
6596 3 : return reinterpret_cast<cpl::VSICurlHandle *>(fp)->InstallReadCbk(
6597 3 : pfnReadCbk, pfnUserData, bStopOnInterruptUntilUninstall);
6598 : }
6599 :
6600 : /************************************************************************/
6601 : /* VSICurlUninstallReadCbk() */
6602 : /************************************************************************/
6603 :
6604 3 : int VSICurlUninstallReadCbk(VSILFILE *fp)
6605 : {
6606 3 : return reinterpret_cast<cpl::VSICurlHandle *>(fp)->UninstallReadCbk();
6607 : }
6608 :
6609 : /************************************************************************/
6610 : /* VSICurlSetOptions() */
6611 : /************************************************************************/
6612 :
6613 1351 : struct curl_slist *VSICurlSetOptions(CURL *hCurlHandle, const char *pszURL,
6614 : const char *const *papszOptions)
6615 : {
6616 : struct curl_slist *headers = static_cast<struct curl_slist *>(
6617 1351 : CPLHTTPSetOptions(hCurlHandle, pszURL, papszOptions));
6618 :
6619 1351 : long option = CURLFTPMETHOD_SINGLECWD;
6620 1351 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FTP_FILEMETHOD, option);
6621 :
6622 : // ftp://ftp2.cits.rncan.gc.ca/pub/cantopo/250k_tif/
6623 : // doesn't like EPSV command,
6624 1351 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FTP_USE_EPSV, 0);
6625 :
6626 1351 : return headers;
6627 : }
6628 :
6629 : /************************************************************************/
6630 : /* VSICurlSetContentTypeFromExt() */
6631 : /************************************************************************/
6632 :
6633 96 : struct curl_slist *VSICurlSetContentTypeFromExt(struct curl_slist *poList,
6634 : const char *pszPath)
6635 : {
6636 96 : struct curl_slist *iter = poList;
6637 134 : while (iter != nullptr)
6638 : {
6639 38 : if (STARTS_WITH_CI(iter->data, "Content-Type"))
6640 : {
6641 0 : return poList;
6642 : }
6643 38 : iter = iter->next;
6644 : }
6645 :
6646 : static const struct
6647 : {
6648 : const char *ext;
6649 : const char *mime;
6650 : } aosExtMimePairs[] = {
6651 : {"txt", "text/plain"}, {"json", "application/json"},
6652 : {"tif", "image/tiff"}, {"tiff", "image/tiff"},
6653 : {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"},
6654 : {"jp2", "image/jp2"}, {"jpx", "image/jp2"},
6655 : {"j2k", "image/jp2"}, {"jpc", "image/jp2"},
6656 : {"png", "image/png"},
6657 : };
6658 :
6659 96 : const std::string osExt = CPLGetExtensionSafe(pszPath);
6660 96 : if (!osExt.empty())
6661 : {
6662 658 : for (const auto &pair : aosExtMimePairs)
6663 : {
6664 605 : if (EQUAL(osExt.c_str(), pair.ext))
6665 : {
6666 :
6667 : const std::string osContentType(
6668 32 : CPLSPrintf("Content-Type: %s", pair.mime));
6669 16 : poList = curl_slist_append(poList, osContentType.c_str());
6670 : #ifdef DEBUG_VERBOSE
6671 : CPLDebug("HTTP", "Setting %s, based on lookup table.",
6672 : osContentType.c_str());
6673 : #endif
6674 16 : break;
6675 : }
6676 : }
6677 : }
6678 :
6679 96 : return poList;
6680 : }
6681 :
6682 : /************************************************************************/
6683 : /* VSICurlSetCreationHeadersFromOptions() */
6684 : /************************************************************************/
6685 :
6686 83 : struct curl_slist *VSICurlSetCreationHeadersFromOptions(
6687 : struct curl_slist *headers, CSLConstList papszOptions, const char *pszPath)
6688 : {
6689 83 : bool bContentTypeFound = false;
6690 93 : for (CSLConstList papszIter = papszOptions; papszIter && *papszIter;
6691 : ++papszIter)
6692 : {
6693 10 : char *pszKey = nullptr;
6694 10 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
6695 10 : if (pszKey && pszValue)
6696 : {
6697 10 : if (EQUAL(pszKey, "Content-Type"))
6698 : {
6699 2 : bContentTypeFound = true;
6700 : }
6701 10 : headers = curl_slist_append(headers,
6702 : CPLSPrintf("%s: %s", pszKey, pszValue));
6703 : }
6704 10 : CPLFree(pszKey);
6705 : }
6706 :
6707 : // If Content-type not found in papszOptions, try to set it from the
6708 : // filename exstension.
6709 83 : if (!bContentTypeFound)
6710 : {
6711 81 : headers = VSICurlSetContentTypeFromExt(headers, pszPath);
6712 : }
6713 :
6714 83 : return headers;
6715 : }
6716 :
6717 : #endif // DOXYGEN_SKIP
6718 : //! @endcond
6719 :
6720 : /************************************************************************/
6721 : /* VSIInstallCurlFileHandler() */
6722 : /************************************************************************/
6723 :
6724 : /*!
6725 : \brief Install /vsicurl/ HTTP/FTP file system handler (requires libcurl)
6726 :
6727 : \verbatim embed:rst
6728 : See :ref:`/vsicurl/ documentation <vsicurl>`
6729 : \endverbatim
6730 :
6731 : */
6732 2098 : void VSIInstallCurlFileHandler(void)
6733 : {
6734 4196 : auto poHandler = std::make_shared<cpl::VSICurlFilesystemHandler>();
6735 6294 : for (const char *pszPrefix : VSICURL_PREFIXES)
6736 : {
6737 4196 : VSIFileManager::InstallHandler(pszPrefix, poHandler);
6738 : }
6739 2098 : }
6740 :
6741 : /************************************************************************/
6742 : /* VSICurlClearCache() */
6743 : /************************************************************************/
6744 :
6745 : /**
6746 : * \brief Clean local cache associated with /vsicurl/ (and related file systems)
6747 : *
6748 : * /vsicurl (and related file systems like /vsis3/, /vsigs/, /vsiaz/, /vsioss/,
6749 : * /vsiswift/) cache a number of
6750 : * metadata and data for faster execution in read-only scenarios. But when the
6751 : * content on the server-side may change during the same process, those
6752 : * mechanisms can prevent opening new files, or give an outdated version of
6753 : * them.
6754 : *
6755 : */
6756 :
6757 408 : void VSICurlClearCache(void)
6758 : {
6759 : // FIXME ? Currently we have different filesystem instances for
6760 : // vsicurl/, /vsis3/, /vsigs/ . So each one has its own cache of regions.
6761 : // File properties cache are now shared
6762 408 : char **papszPrefix = VSIFileManager::GetPrefixes();
6763 12648 : for (size_t i = 0; papszPrefix && papszPrefix[i]; ++i)
6764 : {
6765 0 : auto poFSHandler = dynamic_cast<cpl::VSICurlFilesystemHandlerBase *>(
6766 12240 : VSIFileManager::GetHandler(papszPrefix[i]));
6767 :
6768 12240 : if (poFSHandler)
6769 3264 : poFSHandler->ClearCache();
6770 : }
6771 408 : CSLDestroy(papszPrefix);
6772 :
6773 408 : VSICurlStreamingClearCache();
6774 408 : }
6775 :
6776 : /************************************************************************/
6777 : /* VSICurlPartialClearCache() */
6778 : /************************************************************************/
6779 :
6780 : /**
6781 : * \brief Clean local cache associated with /vsicurl/ (and related file systems)
6782 : * for a given filename (and its subfiles and subdirectories if it is a
6783 : * directory)
6784 : *
6785 : * /vsicurl (and related file systems like /vsis3/, /vsigs/, /vsiaz/, /vsioss/,
6786 : * /vsiswift/) cache a number of
6787 : * metadata and data for faster execution in read-only scenarios. But when the
6788 : * content on the server-side may change during the same process, those
6789 : * mechanisms can prevent opening new files, or give an outdated version of
6790 : * them.
6791 : *
6792 : * The filename prefix must start with the name of a known virtual file system
6793 : * (such as "/vsicurl/", "/vsis3/")
6794 : *
6795 : * VSICurlPartialClearCache("/vsis3/b") will clear all cached state for any file
6796 : * or directory starting with that prefix, so potentially "/vsis3/bucket",
6797 : * "/vsis3/basket/" or "/vsis3/basket/object".
6798 : *
6799 : * @param pszFilenamePrefix Filename prefix
6800 : */
6801 :
6802 5 : void VSICurlPartialClearCache(const char *pszFilenamePrefix)
6803 : {
6804 0 : auto poFSHandler = dynamic_cast<cpl::VSICurlFilesystemHandlerBase *>(
6805 5 : VSIFileManager::GetHandler(pszFilenamePrefix));
6806 :
6807 5 : if (poFSHandler)
6808 4 : poFSHandler->PartialClearCache(pszFilenamePrefix);
6809 5 : }
6810 :
6811 : /************************************************************************/
6812 : /* VSINetworkStatsReset() */
6813 : /************************************************************************/
6814 :
6815 : /**
6816 : * \brief Clear network related statistics.
6817 : *
6818 : * The effect of the CPL_VSIL_NETWORK_STATS_ENABLED configuration option
6819 : * will also be reset. That is, that the next network access will check its
6820 : * value again.
6821 : *
6822 : * @since GDAL 3.2.0
6823 : */
6824 :
6825 2 : void VSINetworkStatsReset(void)
6826 : {
6827 2 : cpl::NetworkStatisticsLogger::Reset();
6828 2 : }
6829 :
6830 : /************************************************************************/
6831 : /* VSINetworkStatsGetAsSerializedJSON() */
6832 : /************************************************************************/
6833 :
6834 : /**
6835 : * \brief Return network related statistics, as a JSON serialized object.
6836 : *
6837 : * Statistics collecting should be enabled with the
6838 : CPL_VSIL_NETWORK_STATS_ENABLED
6839 : * configuration option set to YES before any network activity starts
6840 : * (for efficiency, reading it is cached on first access, until
6841 : VSINetworkStatsReset() is called)
6842 : *
6843 : * Statistics can also be emitted on standard output at process termination if
6844 : * the CPL_VSIL_SHOW_NETWORK_STATS configuration option is set to YES.
6845 : *
6846 : * Example of output:
6847 : * \code{.js}
6848 : * {
6849 : * "methods":{
6850 : * "GET":{
6851 : * "count":6,
6852 : * "downloaded_bytes":40825
6853 : * },
6854 : * "PUT":{
6855 : * "count":1,
6856 : * "uploaded_bytes":35472
6857 : * }
6858 : * },
6859 : * "handlers":{
6860 : * "vsigs":{
6861 : * "methods":{
6862 : * "GET":{
6863 : * "count":2,
6864 : * "downloaded_bytes":446
6865 : * },
6866 : * "PUT":{
6867 : * "count":1,
6868 : * "uploaded_bytes":35472
6869 : * }
6870 : * },
6871 : * "files":{
6872 : * "\/vsigs\/spatialys\/byte.tif":{
6873 : * "methods":{
6874 : * "PUT":{
6875 : * "count":1,
6876 : * "uploaded_bytes":35472
6877 : * }
6878 : * },
6879 : * "actions":{
6880 : * "Write":{
6881 : * "methods":{
6882 : * "PUT":{
6883 : * "count":1,
6884 : * "uploaded_bytes":35472
6885 : * }
6886 : * }
6887 : * }
6888 : * }
6889 : * }
6890 : * },
6891 : * "actions":{
6892 : * "Stat":{
6893 : * "methods":{
6894 : * "GET":{
6895 : * "count":2,
6896 : * "downloaded_bytes":446
6897 : * }
6898 : * },
6899 : * "files":{
6900 : * "\/vsigs\/spatialys\/byte.tif\/":{
6901 : * "methods":{
6902 : * "GET":{
6903 : * "count":1,
6904 : * "downloaded_bytes":181
6905 : * }
6906 : * }
6907 : * }
6908 : * }
6909 : * }
6910 : * }
6911 : * },
6912 : * "vsis3":{
6913 : * [...]
6914 : * }
6915 : * }
6916 : * }
6917 : * \endcode
6918 : *
6919 : * @param papszOptions Unused.
6920 : * @return a JSON serialized string to free with VSIFree(), or nullptr
6921 : * @since GDAL 3.2.0
6922 : */
6923 :
6924 1 : char *VSINetworkStatsGetAsSerializedJSON(CPL_UNUSED char **papszOptions)
6925 : {
6926 1 : return CPLStrdup(
6927 2 : cpl::NetworkStatisticsLogger::GetReportAsSerializedJSON().c_str());
6928 : }
6929 :
6930 : #endif /* HAVE_CURL */
6931 :
6932 : #undef ENABLE_DEBUG
|