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

Generated by: LCOV version 1.14