LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2505 3260 76.8 %
Date: 2026-09-02 18:57:48 Functions: 157 170 92.4 %

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

Generated by: LCOV version 1.14