LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2396 3121 76.8 %
Date: 2026-06-16 17:24:28 Functions: 143 154 92.9 %

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

Generated by: LCOV version 1.14