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