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