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