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