LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2294 3036 75.6 %
Date: 2026-02-11 08:43:47 Functions: 140 151 92.7 %

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

Generated by: LCOV version 1.14