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