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