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