LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2195 2949 74.4 %
Date: 2025-01-18 12:42:00 Functions: 137 154 89.0 %

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

Generated by: LCOV version 1.14