LCOV - code coverage report
Current view: top level - port - cpl_vsil_curl.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2139 2876 74.4 %
Date: 2024-04-28 21:03:45 Functions: 136 151 90.1 %

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

Generated by: LCOV version 1.14