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