LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2326 3064 75.9 %
Date: 2026-03-25 02:32:38 Functions: 143 154 92.9 %

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

Generated by: LCOV version 1.14