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

Generated by: LCOV version 1.14