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