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