LCOV - code coverage report
Current view: top level - port - cpl_vsil_az.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 977 1140 85.7 %
Date: 2025-05-24 03:54:53 Functions: 55 59 93.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement VSI large file api for Microsoft Azure Blob Storage
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2017-2018, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "cpl_http.h"
      15             : #include "cpl_minixml.h"
      16             : #include "cpl_time.h"
      17             : #include "cpl_vsil_curl_priv.h"
      18             : #include "cpl_vsil_curl_class.h"
      19             : 
      20             : #include <errno.h>
      21             : 
      22             : #include <algorithm>
      23             : #include <set>
      24             : #include <map>
      25             : #include <memory>
      26             : 
      27             : #include "cpl_azure.h"
      28             : 
      29             : // #define DEBUG_VERBOSE 1
      30             : 
      31             : #ifndef HAVE_CURL
      32             : 
      33             : void VSIInstallAzureFileHandler(void)
      34             : {
      35             :     // Not supported
      36             : }
      37             : 
      38             : #else
      39             : 
      40             : //! @cond Doxygen_Suppress
      41             : #ifndef DOXYGEN_SKIP
      42             : 
      43             : #define ENABLE_DEBUG 0
      44             : 
      45             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      46             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      47             : 
      48             : namespace cpl
      49             : {
      50             : 
      51             : const char GDAL_MARKER_FOR_DIR[] = ".gdal_marker_for_dir";
      52             : 
      53             : /************************************************************************/
      54             : /*                             VSIDIRAz                                 */
      55             : /************************************************************************/
      56             : 
      57             : struct VSIDIRAz : public VSIDIRS3Like
      58             : {
      59          40 :     explicit VSIDIRAz(IVSIS3LikeFSHandler *poFSIn) : VSIDIRS3Like(poFSIn)
      60             :     {
      61          40 :     }
      62             : 
      63             :     bool IssueListDir() override;
      64             :     bool AnalyseAzureFileList(const std::string &osBaseURL, const char *pszXML);
      65             : };
      66             : 
      67             : /************************************************************************/
      68             : /*                        AnalyseAzureFileList()                        */
      69             : /************************************************************************/
      70             : 
      71          43 : bool VSIDIRAz::AnalyseAzureFileList(const std::string &osBaseURL,
      72             :                                     const char *pszXML)
      73             : {
      74             : #if DEBUG_VERBOSE
      75             :     CPLDebug("AZURE", "%s", pszXML);
      76             : #endif
      77             : 
      78          43 :     CPLXMLNode *psTree = CPLParseXMLString(pszXML);
      79          43 :     if (psTree == nullptr)
      80           0 :         return false;
      81             :     CPLXMLNode *psEnumerationResults =
      82          43 :         CPLGetXMLNode(psTree, "=EnumerationResults");
      83             : 
      84          43 :     bool bOK = false;
      85          43 :     if (psEnumerationResults)
      86             :     {
      87          86 :         CPLString osPrefix = CPLGetXMLValue(psEnumerationResults, "Prefix", "");
      88          43 :         if (osPrefix.empty())
      89             :         {
      90             :             // in the case of an empty bucket
      91          11 :             bOK = true;
      92             :         }
      93          32 :         else if (osPrefix.endsWith(m_osFilterPrefix))
      94             :         {
      95          32 :             osPrefix.resize(osPrefix.size() - m_osFilterPrefix.size());
      96             :         }
      97             : 
      98          43 :         CPLXMLNode *psBlobs = CPLGetXMLNode(psEnumerationResults, "Blobs");
      99          43 :         if (psBlobs == nullptr)
     100             :         {
     101           6 :             psBlobs = CPLGetXMLNode(psEnumerationResults, "Containers");
     102           6 :             if (psBlobs != nullptr)
     103           6 :                 bOK = true;
     104             :         }
     105             : 
     106          86 :         std::string GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH("/");
     107          43 :         GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH += GDAL_MARKER_FOR_DIR;
     108             : 
     109             :         // Count the number of occurrences of a path. Can be 1 or 2. 2 in the
     110             :         // case that both a filename and directory exist
     111          86 :         std::map<std::string, int> aoNameCount;
     112          43 :         for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
     113          75 :              psIter != nullptr; psIter = psIter->psNext)
     114             :         {
     115          32 :             if (psIter->eType != CXT_Element)
     116           0 :                 continue;
     117          32 :             if (strcmp(psIter->pszValue, "Blob") == 0)
     118             :             {
     119          25 :                 const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
     120          25 :                 if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
     121             :                 {
     122           9 :                     bOK = true;
     123           9 :                     if (nRecurseDepth < 0)
     124             :                     {
     125           3 :                         if (strcmp(pszKey + osPrefix.size(),
     126           3 :                                    GDAL_MARKER_FOR_DIR) == 0)
     127           1 :                             continue;
     128           2 :                         char *pszName = CPLStrdup(pszKey + osPrefix.size());
     129           2 :                         char *pszMarker = strstr(
     130             :                             pszName,
     131             :                             GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
     132           2 :                         if (pszMarker)
     133           2 :                             *pszMarker = '\0';
     134           2 :                         aoNameCount[pszName]++;
     135           2 :                         CPLFree(pszName);
     136           8 :                     }
     137             :                 }
     138          16 :                 else if (pszKey && strlen(pszKey) > osPrefix.size())
     139             :                 {
     140          16 :                     bOK = true;
     141          16 :                     aoNameCount[pszKey + osPrefix.size()]++;
     142             :                 }
     143             :             }
     144           7 :             else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
     145           4 :                      strcmp(psIter->pszValue, "Container") == 0)
     146             :             {
     147           7 :                 bOK = true;
     148             : 
     149           7 :                 const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
     150          14 :                 if (pszKey &&
     151           7 :                     strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
     152             :                 {
     153          14 :                     std::string osKey = pszKey;
     154           7 :                     if (!osKey.empty() && osKey.back() == '/')
     155           2 :                         osKey.pop_back();
     156           7 :                     if (osKey.size() > osPrefix.size())
     157             :                     {
     158           7 :                         aoNameCount[osKey.c_str() + osPrefix.size()]++;
     159             :                     }
     160             :                 }
     161             :             }
     162             :         }
     163             : 
     164          43 :         for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
     165          75 :              psIter != nullptr; psIter = psIter->psNext)
     166             :         {
     167          32 :             if (psIter->eType != CXT_Element)
     168           0 :                 continue;
     169          32 :             if (strcmp(psIter->pszValue, "Blob") == 0)
     170             :             {
     171          25 :                 const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
     172          25 :                 if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
     173             :                 {
     174           9 :                     if (nRecurseDepth < 0)
     175             :                     {
     176           3 :                         if (strcmp(pszKey + osPrefix.size(),
     177           3 :                                    GDAL_MARKER_FOR_DIR) == 0)
     178           1 :                             continue;
     179           2 :                         aoEntries.push_back(
     180           4 :                             std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
     181           2 :                         auto &entry = aoEntries.back();
     182           2 :                         entry->pszName = CPLStrdup(pszKey + osPrefix.size());
     183           2 :                         char *pszMarker = strstr(
     184           2 :                             entry->pszName,
     185             :                             GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
     186           2 :                         if (pszMarker)
     187           2 :                             *pszMarker = '\0';
     188           2 :                         entry->nMode = S_IFDIR;
     189           2 :                         entry->bModeKnown = true;
     190           8 :                     }
     191             :                 }
     192          16 :                 else if (pszKey && strlen(pszKey) > osPrefix.size())
     193             :                 {
     194          32 :                     const std::string osKeySuffix = pszKey + osPrefix.size();
     195          16 :                     if (m_bSynthetizeMissingDirectories)
     196             :                     {
     197           0 :                         const auto nLastSlashPos = osKeySuffix.rfind('/');
     198           0 :                         if (nLastSlashPos != std::string::npos &&
     199           0 :                             (m_aosSubpathsStack.empty() ||
     200           0 :                              osKeySuffix.compare(0, nLastSlashPos,
     201           0 :                                                  m_aosSubpathsStack.back()) !=
     202             :                                  0))
     203             :                         {
     204             :                             const bool bAddEntryForThisSubdir =
     205           0 :                                 nLastSlashPos != osKeySuffix.size() - 1;
     206           0 :                             SynthetizeMissingDirectories(
     207           0 :                                 osKeySuffix.substr(0, nLastSlashPos),
     208             :                                 bAddEntryForThisSubdir);
     209             :                         }
     210             :                     }
     211             : 
     212          16 :                     aoEntries.push_back(
     213          32 :                         std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
     214          16 :                     auto &entry = aoEntries.back();
     215          16 :                     entry->pszName = CPLStrdup(osKeySuffix.c_str());
     216          32 :                     entry->nSize =
     217          16 :                         static_cast<GUIntBig>(CPLAtoGIntBig(CPLGetXMLValue(
     218             :                             psIter, "Properties.Content-Length", "0")));
     219          16 :                     entry->bSizeKnown = true;
     220          16 :                     entry->nMode = S_IFREG;
     221          16 :                     entry->bModeKnown = true;
     222             : 
     223          32 :                     std::string ETag = CPLGetXMLValue(psIter, "Etag", "");
     224          16 :                     if (!ETag.empty())
     225             :                     {
     226           0 :                         entry->papszExtra = CSLSetNameValue(
     227           0 :                             entry->papszExtra, "ETag", ETag.c_str());
     228             :                     }
     229             : 
     230             :                     int nYear, nMonth, nDay, nHour, nMinute, nSecond;
     231          16 :                     if (CPLParseRFC822DateTime(
     232             :                             CPLGetXMLValue(psIter, "Properties.Last-Modified",
     233             :                                            ""),
     234             :                             &nYear, &nMonth, &nDay, &nHour, &nMinute, &nSecond,
     235          16 :                             nullptr, nullptr))
     236             :                     {
     237             :                         struct tm brokendowntime;
     238          13 :                         brokendowntime.tm_year = nYear - 1900;
     239          13 :                         brokendowntime.tm_mon = nMonth - 1;
     240          13 :                         brokendowntime.tm_mday = nDay;
     241          13 :                         brokendowntime.tm_hour = nHour;
     242          13 :                         brokendowntime.tm_min = nMinute;
     243          13 :                         brokendowntime.tm_sec = nSecond < 0 ? 0 : nSecond;
     244          13 :                         entry->nMTime = CPLYMDHMSToUnixTime(&brokendowntime);
     245          13 :                         entry->bMTimeKnown = true;
     246             :                     }
     247             : 
     248          16 :                     if (bCacheEntries)
     249             :                     {
     250          28 :                         FileProp prop;
     251          14 :                         prop.eExists = EXIST_YES;
     252          14 :                         prop.bHasComputedFileSize = true;
     253          14 :                         prop.fileSize = entry->nSize;
     254          14 :                         prop.bIsDirectory = false;
     255          14 :                         prop.mTime = static_cast<time_t>(entry->nMTime);
     256          14 :                         prop.ETag = std::move(ETag);
     257          14 :                         prop.nMode = entry->nMode;
     258             : 
     259             :                         std::string osCachedFilename =
     260          28 :                             osBaseURL + "/" + CPLAWSURLEncode(osPrefix, false) +
     261          56 :                             CPLAWSURLEncode(entry->pszName, false);
     262             : #if DEBUG_VERBOSE
     263             :                         CPLDebug("AZURE", "Cache %s", osCachedFilename.c_str());
     264             : #endif
     265          14 :                         poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
     266             :                     }
     267             :                 }
     268             :             }
     269           7 :             else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
     270           4 :                      strcmp(psIter->pszValue, "Container") == 0)
     271             :             {
     272           7 :                 const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
     273          14 :                 if (pszKey &&
     274           7 :                     strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
     275             :                 {
     276          14 :                     std::string osKey = pszKey;
     277           7 :                     if (!osKey.empty() && osKey.back() == '/')
     278           2 :                         osKey.pop_back();
     279           7 :                     if (osKey.size() > osPrefix.size())
     280             :                     {
     281           7 :                         aoEntries.push_back(
     282          14 :                             std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
     283           7 :                         auto &entry = aoEntries.back();
     284          14 :                         entry->pszName =
     285           7 :                             CPLStrdup(osKey.c_str() + osPrefix.size());
     286           7 :                         if (aoNameCount[entry->pszName] == 2)
     287             :                         {
     288             :                             // Add a / suffix to disambiguish the situation
     289             :                             // Normally we don't suffix directories with /, but
     290             :                             // we have no alternative here
     291           0 :                             std::string osTemp(entry->pszName);
     292           0 :                             osTemp += '/';
     293           0 :                             CPLFree(entry->pszName);
     294           0 :                             entry->pszName = CPLStrdup(osTemp.c_str());
     295             :                         }
     296           7 :                         entry->nMode = S_IFDIR;
     297           7 :                         entry->bModeKnown = true;
     298             : 
     299           7 :                         if (bCacheEntries)
     300             :                         {
     301          12 :                             FileProp prop;
     302           6 :                             prop.eExists = EXIST_YES;
     303           6 :                             prop.bIsDirectory = true;
     304           6 :                             prop.bHasComputedFileSize = true;
     305           6 :                             prop.fileSize = 0;
     306           6 :                             prop.mTime = 0;
     307           6 :                             prop.nMode = entry->nMode;
     308             : 
     309             :                             std::string osCachedFilename =
     310          12 :                                 osBaseURL + "/" +
     311          12 :                                 CPLAWSURLEncode(osPrefix, false) +
     312          24 :                                 CPLAWSURLEncode(entry->pszName, false);
     313             : #if DEBUG_VERBOSE
     314             :                             CPLDebug("AZURE", "Cache %s",
     315             :                                      osCachedFilename.c_str());
     316             : #endif
     317           6 :                             poFS->SetCachedFileProp(osCachedFilename.c_str(),
     318             :                                                     prop);
     319             :                         }
     320             :                     }
     321             :                 }
     322             :             }
     323             : 
     324          41 :             if (nMaxFiles > 0 &&
     325          10 :                 aoEntries.size() > static_cast<unsigned>(nMaxFiles))
     326           0 :                 break;
     327             :         }
     328             : 
     329          43 :         osNextMarker = CPLGetXMLValue(psEnumerationResults, "NextMarker", "");
     330             :         // For some containers, a list blob request can return a response
     331             :         // with no blobs, but with a non-empty NextMarker, and the following
     332             :         // request using that marker will return blobs...
     333          43 :         if (!osNextMarker.empty())
     334          14 :             bOK = true;
     335             :     }
     336          43 :     CPLDestroyXMLNode(psTree);
     337             : 
     338          43 :     return bOK;
     339             : }
     340             : 
     341             : /************************************************************************/
     342             : /*                          IssueListDir()                              */
     343             : /************************************************************************/
     344             : 
     345          53 : bool VSIDIRAz::IssueListDir()
     346             : {
     347          53 :     WriteFuncStruct sWriteFuncData;
     348         106 :     const std::string l_osNextMarker(osNextMarker);
     349          53 :     clear();
     350             : 
     351         106 :     NetworkStatisticsFileSystem oContextFS("/vsiaz/");
     352         106 :     NetworkStatisticsAction oContextAction("ListBucket");
     353             : 
     354         106 :     CPLString osMaxKeys = CPLGetConfigOption("AZURE_MAX_RESULTS", "");
     355          53 :     const int AZURE_SERVER_LIMIT_SINGLE_REQUEST = 5000;
     356          72 :     if (nMaxFiles > 0 && nMaxFiles < AZURE_SERVER_LIMIT_SINGLE_REQUEST &&
     357          19 :         (osMaxKeys.empty() || nMaxFiles < atoi(osMaxKeys.c_str())))
     358             :     {
     359          19 :         osMaxKeys.Printf("%d", nMaxFiles);
     360             :     }
     361             : 
     362          53 :     poHandleHelper->ResetQueryParameters();
     363         106 :     std::string osBaseURL(poHandleHelper->GetURLNoKVP());
     364          53 :     if (osBaseURL.back() == '/')
     365           7 :         osBaseURL.pop_back();
     366             : 
     367          53 :     CURL *hCurlHandle = curl_easy_init();
     368             : 
     369          53 :     poHandleHelper->AddQueryParameter("comp", "list");
     370          53 :     if (!l_osNextMarker.empty())
     371          13 :         poHandleHelper->AddQueryParameter("marker", l_osNextMarker);
     372          53 :     if (!osMaxKeys.empty())
     373          19 :         poHandleHelper->AddQueryParameter("maxresults", osMaxKeys);
     374             : 
     375          53 :     if (!osBucket.empty())
     376             :     {
     377          46 :         poHandleHelper->AddQueryParameter("restype", "container");
     378             : 
     379          46 :         if (nRecurseDepth == 0)
     380          40 :             poHandleHelper->AddQueryParameter("delimiter", "/");
     381          46 :         if (!osObjectKey.empty())
     382          80 :             poHandleHelper->AddQueryParameter("prefix", osObjectKey + "/" +
     383          40 :                                                             m_osFilterPrefix);
     384           6 :         else if (!m_osFilterPrefix.empty())
     385           1 :             poHandleHelper->AddQueryParameter("prefix", m_osFilterPrefix);
     386             :     }
     387             : 
     388         106 :     std::string osFilename("/vsiaz/");
     389          53 :     if (!osBucket.empty())
     390             :     {
     391          46 :         osFilename += osBucket;
     392          46 :         if (!osObjectKey.empty())
     393          40 :             osFilename += osObjectKey;
     394             :     }
     395             :     const CPLStringList aosHTTPOptions(
     396         106 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
     397             : 
     398         106 :     struct curl_slist *headers = VSICurlSetOptions(
     399          53 :         hCurlHandle, poHandleHelper->GetURL().c_str(), aosHTTPOptions.List());
     400             : 
     401          53 :     headers = VSICurlMergeHeaders(
     402          53 :         headers, poHandleHelper->GetCurlHeaders("GET", headers));
     403          53 :     unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
     404             : 
     405         106 :     CurlRequestHelper requestHelper;
     406             :     const long response_code =
     407          53 :         requestHelper.perform(hCurlHandle, headers, poFS, poHandleHelper.get());
     408             : 
     409          53 :     NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
     410             : 
     411          53 :     if (requestHelper.sWriteFuncData.pBuffer == nullptr)
     412             :     {
     413           8 :         curl_easy_cleanup(hCurlHandle);
     414           8 :         return false;
     415             :     }
     416             : 
     417          45 :     bool ret = false;
     418          45 :     if (response_code != 200)
     419             :     {
     420           2 :         CPLDebug("AZURE", "%s", requestHelper.sWriteFuncData.pBuffer);
     421             :     }
     422             :     else
     423             :     {
     424          43 :         ret = AnalyseAzureFileList(osBaseURL,
     425          43 :                                    requestHelper.sWriteFuncData.pBuffer);
     426             :     }
     427          45 :     curl_easy_cleanup(hCurlHandle);
     428          45 :     return ret;
     429             : }
     430             : 
     431             : /************************************************************************/
     432             : /*                       VSIAzureFSHandler                              */
     433             : /************************************************************************/
     434             : 
     435             : class VSIAzureFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload
     436             : {
     437             :     CPL_DISALLOW_COPY_ASSIGN(VSIAzureFSHandler)
     438             :     const std::string m_osPrefix;
     439             : 
     440             :     int CreateContainer(const std::string &osDirname);
     441             :     int DeleteContainer(const std::string &osDirname);
     442             : 
     443             :   protected:
     444             :     VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
     445             :     std::string
     446             :     GetURLFromFilename(const std::string &osFilename) const override;
     447             : 
     448             :     VSIAzureBlobHandleHelper *CreateAzHandleHelper(const char *pszURI,
     449             :                                                    bool bAllowNoObject);
     450             : 
     451          66 :     IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
     452             :                                                bool bAllowNoObject) override
     453             :     {
     454          66 :         return CreateAzHandleHelper(pszURI, bAllowNoObject);
     455             :     }
     456             : 
     457             :     char **GetFileList(const char *pszFilename, int nMaxFiles,
     458             :                        bool *pbGotFileList) override;
     459             : 
     460             :     void InvalidateRecursive(const std::string &osDirnameIn);
     461             : 
     462             :     int CopyFile(const char *pszSource, const char *pszTarget,
     463             :                  VSILFILE *fpSource, vsi_l_offset nSourceSize,
     464             :                  const char *const *papszOptions,
     465             :                  GDALProgressFunc pProgressFunc, void *pProgressData) override;
     466             : 
     467             :     int CopyObject(const char *oldpath, const char *newpath,
     468             :                    CSLConstList papszMetadata) override;
     469             :     int MkdirInternal(const char *pszDirname, long nMode,
     470             :                       bool bDoStatCheck) override;
     471             : 
     472             :     void ClearCache() override;
     473             : 
     474           4 :     bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
     475             :     {
     476           4 :         return STARTS_WITH(pszHeaderName, "x-ms-");
     477             :     }
     478             : 
     479             :     VSIVirtualHandleUniquePtr
     480             :     CreateWriteHandle(const char *pszFilename,
     481             :                       CSLConstList papszOptions) override;
     482             : 
     483             :   public:
     484        1616 :     explicit VSIAzureFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
     485             :     {
     486        1616 :     }
     487             : 
     488        2222 :     ~VSIAzureFSHandler() override = default;
     489             : 
     490        1387 :     std::string GetFSPrefix() const override
     491             :     {
     492        1387 :         return m_osPrefix;
     493             :     }
     494             : 
     495          62 :     const char *GetDebugKey() const override
     496             :     {
     497          62 :         return "AZURE";
     498             :     }
     499             : 
     500             :     int Unlink(const char *pszFilename) override;
     501             :     int *UnlinkBatch(CSLConstList papszFiles) override;
     502             : 
     503           0 :     int *DeleteObjectBatch(CSLConstList papszFilesOrDirs) override
     504             :     {
     505           0 :         return UnlinkBatch(papszFilesOrDirs);
     506             :     }
     507             : 
     508             :     int Mkdir(const char *, long) override;
     509             :     int Rmdir(const char *) override;
     510             :     int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
     511             :              int nFlags) override;
     512             : 
     513             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
     514             :                            CSLConstList papszOptions) override;
     515             : 
     516             :     bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
     517             :                          const char *pszDomain,
     518             :                          CSLConstList papszOptions) override;
     519             : 
     520             :     const char *GetOptions() override;
     521             : 
     522             :     char *GetSignedURL(const char *pszFilename,
     523             :                        CSLConstList papszOptions) override;
     524             : 
     525             :     char **GetFileList(const char *pszFilename, int nMaxFiles,
     526             :                        bool bCacheEntries, bool *pbGotFileList);
     527             : 
     528             :     VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth,
     529             :                     const char *const *papszOptions) override;
     530             : 
     531             :     // Block list upload
     532             :     std::string PutBlock(const std::string &osFilename, int nPartNumber,
     533             :                          const void *pabyBuffer, size_t nBufferSize,
     534             :                          IVSIS3LikeHandleHelper *poS3HandleHelper,
     535             :                          const CPLHTTPRetryParameters &oRetryParameters,
     536             :                          CSLConstList papszOptions);
     537             :     bool PutBlockList(const std::string &osFilename,
     538             :                       const std::vector<std::string> &aosBlockIds,
     539             :                       IVSIS3LikeHandleHelper *poS3HandleHelper,
     540             :                       const CPLHTTPRetryParameters &oRetryParameters);
     541             : 
     542             :     // Multipart upload (mapping of S3 interface to PutBlock/PutBlockList)
     543             : 
     544           3 :     std::string InitiateMultipartUpload(
     545             :         const std::string & /* osFilename */, IVSIS3LikeHandleHelper *,
     546             :         const CPLHTTPRetryParameters & /* oRetryParameters */,
     547             :         CSLConstList /* papszOptions */) override
     548             :     {
     549           3 :         return "dummy";
     550             :     }
     551             : 
     552           6 :     std::string UploadPart(const std::string &osFilename, int nPartNumber,
     553             :                            const std::string & /* osUploadID */,
     554             :                            vsi_l_offset /* nPosition */, const void *pabyBuffer,
     555             :                            size_t nBufferSize,
     556             :                            IVSIS3LikeHandleHelper *poS3HandleHelper,
     557             :                            const CPLHTTPRetryParameters &oRetryParameters,
     558             :                            CSLConstList papszOptions) override
     559             :     {
     560             :         return PutBlock(osFilename, nPartNumber, pabyBuffer, nBufferSize,
     561           6 :                         poS3HandleHelper, oRetryParameters, papszOptions);
     562             :     }
     563             : 
     564           3 :     bool CompleteMultipart(
     565             :         const std::string &osFilename, const std::string & /* osUploadID */,
     566             :         const std::vector<std::string> &aosEtags, vsi_l_offset /* nTotalSize */,
     567             :         IVSIS3LikeHandleHelper *poS3HandleHelper,
     568             :         const CPLHTTPRetryParameters &oRetryParameters) override
     569             :     {
     570           3 :         return PutBlockList(osFilename, aosEtags, poS3HandleHelper,
     571           3 :                             oRetryParameters);
     572             :     }
     573             : 
     574           0 :     bool AbortMultipart(
     575             :         const std::string & /* osFilename */,
     576             :         const std::string & /* osUploadID */,
     577             :         IVSIS3LikeHandleHelper * /*poS3HandleHelper */,
     578             :         const CPLHTTPRetryParameters & /* oRetryParameters */) override
     579             :     {
     580           0 :         return true;
     581             :     }
     582             : 
     583           1 :     bool MultipartUploadAbort(const char *, const char *, CSLConstList) override
     584             :     {
     585           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     586             :                  "MultipartUploadAbort() not supported by this file system");
     587           1 :         return false;
     588             :     }
     589             : 
     590           1 :     bool SupportsMultipartAbort() const override
     591             :     {
     592           1 :         return false;
     593             :     }
     594             : 
     595             :     std::string
     596             :     GetStreamingFilename(const std::string &osFilename) const override;
     597             : 
     598           0 :     VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
     599             :     {
     600           0 :         return new VSIAzureFSHandler(pszPrefix);
     601             :     }
     602             : 
     603             :     //! Maximum number of parts for multipart upload
     604             :     // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs
     605           3 :     int GetMaximumPartCount() override
     606             :     {
     607           3 :         return 50000;
     608             :     }
     609             : 
     610             :     //! Minimum size of a part for multipart upload (except last one), in MiB.
     611           1 :     int GetMinimumPartSizeInMiB() override
     612             :     {
     613           1 :         return 0;
     614             :     }
     615             : 
     616             :     //! Maximum size of a part for multipart upload, in MiB.
     617             :     // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs
     618           3 :     int GetMaximumPartSizeInMiB() override
     619             :     {
     620             : #if SIZEOF_VOIDP == 8
     621           3 :         return 4000;
     622             : #else
     623             :         // Cannot be larger than 4GiB, otherwise integer overflow would occur
     624             :         // 1 GiB is the maximum reasonable value on a 32-bit machine
     625             :         return 1024;
     626             : #endif
     627             :     }
     628             : };
     629             : 
     630             : /************************************************************************/
     631             : /*                          VSIAzureHandle                              */
     632             : /************************************************************************/
     633             : 
     634             : class VSIAzureHandle final : public VSICurlHandle
     635             : {
     636             :     CPL_DISALLOW_COPY_ASSIGN(VSIAzureHandle)
     637             : 
     638             :     std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
     639             : 
     640             :   protected:
     641             :     virtual struct curl_slist *
     642             :     GetCurlHeaders(const std::string &osVerb,
     643             :                    const struct curl_slist *psExistingHeaders) override;
     644             :     virtual bool IsDirectoryFromExists(const char *pszVerb,
     645             :                                        int response_code) override;
     646             : 
     647             :   public:
     648             :     VSIAzureHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
     649             :                    VSIAzureBlobHandleHelper *poHandleHelper);
     650             : };
     651             : 
     652             : /************************************************************************/
     653             : /*                          VSIAzureWriteHandle                         */
     654             : /************************************************************************/
     655             : 
     656             : class VSIAzureWriteHandle final : public VSIAppendWriteHandle
     657             : {
     658             :     CPL_DISALLOW_COPY_ASSIGN(VSIAzureWriteHandle)
     659             : 
     660             :     std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
     661             :     CPLStringList m_aosOptions{};
     662             :     CPLStringList m_aosHTTPOptions{};
     663             : 
     664             :     bool Send(bool bIsLastBlock) override;
     665             :     bool SendInternal(bool bInitOnly, bool bIsLastBlock);
     666             : 
     667             :     void InvalidateParentDirectory();
     668             : 
     669             :   public:
     670             :     VSIAzureWriteHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
     671             :                         VSIAzureBlobHandleHelper *poHandleHelper,
     672             :                         CSLConstList papszOptions);
     673             :     virtual ~VSIAzureWriteHandle();
     674             : };
     675             : 
     676             : /************************************************************************/
     677             : /*                          CreateFileHandle()                          */
     678             : /************************************************************************/
     679             : 
     680          51 : VSICurlHandle *VSIAzureFSHandler::CreateFileHandle(const char *pszFilename)
     681             : {
     682             :     VSIAzureBlobHandleHelper *poHandleHelper =
     683         102 :         VSIAzureBlobHandleHelper::BuildFromURI(
     684         153 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
     685          51 :     if (poHandleHelper == nullptr)
     686           4 :         return nullptr;
     687          47 :     return new VSIAzureHandle(this, pszFilename, poHandleHelper);
     688             : }
     689             : 
     690             : /************************************************************************/
     691             : /*                          CreateWriteHandle()                         */
     692             : /************************************************************************/
     693             : 
     694             : VSIVirtualHandleUniquePtr
     695           6 : VSIAzureFSHandler::CreateWriteHandle(const char *pszFilename,
     696             :                                      CSLConstList papszOptions)
     697             : {
     698             :     VSIAzureBlobHandleHelper *poHandleHelper =
     699          12 :         VSIAzureBlobHandleHelper::BuildFromURI(
     700          18 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
     701           6 :     if (poHandleHelper == nullptr)
     702           0 :         return nullptr;
     703           6 :     const char *pszBlobType = CSLFetchNameValue(papszOptions, "BLOB_TYPE");
     704           6 :     if (pszBlobType && EQUAL(pszBlobType, "BLOCK"))
     705             :     {
     706             :         auto poHandle = std::make_unique<VSIMultipartWriteHandle>(
     707           4 :             this, pszFilename, poHandleHelper, papszOptions);
     708           2 :         if (!poHandle->IsOK())
     709             :         {
     710           0 :             return nullptr;
     711             :         }
     712           2 :         return VSIVirtualHandleUniquePtr(poHandle.release());
     713             :     }
     714             :     else
     715             :     {
     716             :         auto poHandle = std::make_unique<VSIAzureWriteHandle>(
     717           8 :             this, pszFilename, poHandleHelper, papszOptions);
     718           4 :         if (!poHandle->IsOK())
     719             :         {
     720           0 :             return nullptr;
     721             :         }
     722           4 :         return VSIVirtualHandleUniquePtr(poHandle.release());
     723             :     }
     724             : }
     725             : 
     726             : /************************************************************************/
     727             : /*                                Stat()                                */
     728             : /************************************************************************/
     729             : 
     730          32 : int VSIAzureFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
     731             :                             int nFlags)
     732             : {
     733          32 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     734           0 :         return -1;
     735             : 
     736          32 :     if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
     737           3 :         return VSICurlFilesystemHandlerBase::Stat(pszFilename, pStatBuf,
     738           3 :                                                   nFlags);
     739             : 
     740          58 :     std::string osFilename(pszFilename);
     741             : 
     742          58 :     if ((osFilename.find('/', GetFSPrefix().size()) == std::string::npos ||
     743          63 :          osFilename.find('/', GetFSPrefix().size()) == osFilename.size() - 1) &&
     744           5 :         CPLGetConfigOption("AZURE_SAS", nullptr) != nullptr)
     745             :     {
     746             :         // On "/vsiaz/container", a HEAD or GET request fails to authenticate
     747             :         // when SAS is used, so use directory listing instead.
     748           0 :         char **papszRet = ReadDirInternal(osFilename.c_str(), 100, nullptr);
     749           0 :         int nRet = papszRet ? 0 : -1;
     750           0 :         if (nRet == 0)
     751             :         {
     752           0 :             pStatBuf->st_mtime = 0;
     753           0 :             pStatBuf->st_size = 0;
     754           0 :             pStatBuf->st_mode = S_IFDIR;
     755             : 
     756           0 :             FileProp cachedFileProp;
     757           0 :             GetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
     758             :                               cachedFileProp);
     759           0 :             cachedFileProp.eExists = EXIST_YES;
     760           0 :             cachedFileProp.bIsDirectory = true;
     761           0 :             cachedFileProp.bHasComputedFileSize = true;
     762           0 :             SetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
     763             :                               cachedFileProp);
     764             :         }
     765           0 :         CSLDestroy(papszRet);
     766           0 :         return nRet;
     767             :     }
     768             : 
     769          86 :     if (osFilename.size() > GetFSPrefix().size() &&
     770          57 :         osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
     771             :     {
     772           2 :         osFilename += "/";
     773             :     }
     774             : 
     775             :     // Special case for container
     776          58 :     std::string osFilenameWithoutEndSlash(osFilename);
     777          57 :     if (osFilename.size() > GetFSPrefix().size() &&
     778          28 :         osFilenameWithoutEndSlash.back() == '/')
     779          14 :         osFilenameWithoutEndSlash.pop_back();
     780          29 :     if (osFilenameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
     781             :         std::string::npos)
     782             :     {
     783           5 :         char **papszFileList = ReadDir(GetFSPrefix().c_str());
     784           5 :         if (osFilename.size() == GetFSPrefix().size())
     785             :         {
     786           1 :             CSLDestroy(papszFileList);
     787           1 :             if (papszFileList)
     788             :             {
     789           1 :                 pStatBuf->st_mtime = 0;
     790           1 :                 pStatBuf->st_size = 0;
     791           1 :                 pStatBuf->st_mode = S_IFDIR;
     792           1 :                 return 0;
     793             :             }
     794           0 :             return -1;
     795             :         }
     796           4 :         const int nIdx = CSLFindString(
     797             :             papszFileList,
     798           8 :             osFilenameWithoutEndSlash.substr(GetFSPrefix().size()).c_str());
     799           4 :         CSLDestroy(papszFileList);
     800           4 :         if (nIdx >= 0)
     801             :         {
     802           2 :             pStatBuf->st_mtime = 0;
     803           2 :             pStatBuf->st_size = 0;
     804           2 :             pStatBuf->st_mode = S_IFDIR;
     805           2 :             return 0;
     806             :         }
     807             :     }
     808             : 
     809          26 :     return VSICurlFilesystemHandlerBase::Stat(osFilename.c_str(), pStatBuf,
     810          26 :                                               nFlags);
     811             : }
     812             : 
     813             : /************************************************************************/
     814             : /*                          GetFileMetadata()                           */
     815             : /************************************************************************/
     816             : 
     817           4 : char **VSIAzureFSHandler::GetFileMetadata(const char *pszFilename,
     818             :                                           const char *pszDomain,
     819             :                                           CSLConstList papszOptions)
     820             : {
     821           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     822           0 :         return nullptr;
     823             : 
     824           4 :     if (pszDomain == nullptr ||
     825           4 :         (!EQUAL(pszDomain, "TAGS") && !EQUAL(pszDomain, "METADATA")))
     826             :     {
     827           1 :         return VSICurlFilesystemHandlerBase::GetFileMetadata(
     828           1 :             pszFilename, pszDomain, papszOptions);
     829             :     }
     830             : 
     831             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
     832           6 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
     833           3 :     if (poHandleHelper == nullptr)
     834             :     {
     835           0 :         return nullptr;
     836             :     }
     837             : 
     838           6 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     839           6 :     NetworkStatisticsAction oContextAction("GetFileMetadata");
     840             : 
     841             :     bool bRetry;
     842           3 :     bool bError = true;
     843             : 
     844           6 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     845           6 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     846           6 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
     847             : 
     848           3 :     CPLStringList aosMetadata;
     849           3 :     do
     850             :     {
     851           3 :         bRetry = false;
     852           3 :         CURL *hCurlHandle = curl_easy_init();
     853           3 :         if (EQUAL(pszDomain, "METADATA"))
     854           2 :             poHandleHelper->AddQueryParameter("comp", "metadata");
     855             :         else
     856           1 :             poHandleHelper->AddQueryParameter("comp", "tags");
     857             : 
     858             :         struct curl_slist *headers =
     859           3 :             VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
     860             :                               aosHTTPOptions.List());
     861             : 
     862           3 :         headers = VSICurlMergeHeaders(
     863           3 :             headers, poHandleHelper->GetCurlHeaders("GET", headers));
     864           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
     865             : 
     866           6 :         CurlRequestHelper requestHelper;
     867           3 :         const long response_code = requestHelper.perform(
     868             :             hCurlHandle, headers, this, poHandleHelper.get());
     869             : 
     870           3 :         NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
     871             : 
     872           3 :         if (response_code != 200 ||
     873           2 :             requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
     874             :         {
     875             :             // Look if we should attempt a retry
     876           1 :             if (oRetryContext.CanRetry(
     877             :                     static_cast<int>(response_code),
     878           1 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     879             :                     requestHelper.szCurlErrBuf))
     880             :             {
     881           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     882             :                          "HTTP error code: %d - %s. "
     883             :                          "Retrying again in %.1f secs",
     884             :                          static_cast<int>(response_code),
     885           0 :                          poHandleHelper->GetURL().c_str(),
     886             :                          oRetryContext.GetCurrentDelay());
     887           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     888           0 :                 bRetry = true;
     889             :             }
     890             :             else
     891             :             {
     892           1 :                 CPLDebug(GetDebugKey(), "GetFileMetadata failed on %s: %s",
     893             :                          pszFilename,
     894           1 :                          requestHelper.sWriteFuncData.pBuffer
     895             :                              ? requestHelper.sWriteFuncData.pBuffer
     896             :                              : "(null)");
     897             :             }
     898             :         }
     899             :         else
     900             :         {
     901           2 :             if (EQUAL(pszDomain, "METADATA"))
     902             :             {
     903           2 :                 char **papszHeaders = CSLTokenizeString2(
     904           1 :                     requestHelper.sWriteFuncHeaderData.pBuffer, "\r\n", 0);
     905           6 :                 for (int i = 0; papszHeaders[i]; ++i)
     906             :                 {
     907           5 :                     char *pszKey = nullptr;
     908             :                     const char *pszValue =
     909           5 :                         CPLParseNameValue(papszHeaders[i], &pszKey);
     910             :                     // Content-Length is returned as 0
     911           5 :                     if (pszKey && pszValue && !EQUAL(pszKey, "Content-Length"))
     912             :                     {
     913           3 :                         aosMetadata.SetNameValue(pszKey, pszValue);
     914             :                     }
     915           5 :                     CPLFree(pszKey);
     916             :                 }
     917           1 :                 CSLDestroy(papszHeaders);
     918             :             }
     919             :             else
     920             :             {
     921             :                 CPLXMLNode *psXML =
     922           1 :                     CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
     923           1 :                 if (psXML)
     924             :                 {
     925           1 :                     CPLXMLNode *psTagSet = CPLGetXMLNode(psXML, "=Tags.TagSet");
     926           1 :                     if (psTagSet)
     927             :                     {
     928           2 :                         for (CPLXMLNode *psIter = psTagSet->psChild; psIter;
     929           1 :                              psIter = psIter->psNext)
     930             :                         {
     931           1 :                             if (psIter->eType == CXT_Element &&
     932           1 :                                 strcmp(psIter->pszValue, "Tag") == 0)
     933             :                             {
     934             :                                 const char *pszKey =
     935           1 :                                     CPLGetXMLValue(psIter, "Key", "");
     936             :                                 const char *pszValue =
     937           1 :                                     CPLGetXMLValue(psIter, "Value", "");
     938           1 :                                 aosMetadata.SetNameValue(pszKey, pszValue);
     939             :                             }
     940             :                         }
     941             :                     }
     942           1 :                     CPLDestroyXMLNode(psXML);
     943             :                 }
     944             :             }
     945           2 :             bError = false;
     946             :         }
     947             : 
     948           3 :         curl_easy_cleanup(hCurlHandle);
     949             :     } while (bRetry);
     950           3 :     return bError ? nullptr : CSLDuplicate(aosMetadata.List());
     951             : }
     952             : 
     953             : /************************************************************************/
     954             : /*                          SetFileMetadata()                           */
     955             : /************************************************************************/
     956             : 
     957           4 : bool VSIAzureFSHandler::SetFileMetadata(const char *pszFilename,
     958             :                                         CSLConstList papszMetadata,
     959             :                                         const char *pszDomain,
     960             :                                         CSLConstList /* papszOptions */)
     961             : {
     962           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     963           0 :         return false;
     964             : 
     965           4 :     if (pszDomain == nullptr ||
     966           4 :         !(EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA") ||
     967           1 :           EQUAL(pszDomain, "TAGS")))
     968             :     {
     969           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     970             :                  "Only PROPERTIES, METADATA and TAGS domain are supported");
     971           0 :         return false;
     972             :     }
     973             : 
     974             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
     975           8 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
     976           4 :     if (poHandleHelper == nullptr)
     977             :     {
     978           0 :         return false;
     979             :     }
     980             : 
     981           8 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     982           8 :     NetworkStatisticsAction oContextAction("SetFileMetadata");
     983             : 
     984             :     bool bRetry;
     985           8 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     986           8 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     987           8 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
     988           4 :     bool bRet = false;
     989             : 
     990             :     // Compose XML content for TAGS
     991           4 :     std::string osXML;
     992           4 :     if (EQUAL(pszDomain, "TAGS"))
     993             :     {
     994           1 :         CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
     995           1 :         CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
     996           1 :         CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
     997           1 :         CPLXMLNode *psTags = CPLCreateXMLNode(nullptr, CXT_Element, "Tags");
     998           1 :         psXML->psNext = psTags;
     999           1 :         CPLXMLNode *psTagSet = CPLCreateXMLNode(psTags, CXT_Element, "TagSet");
    1000           2 :         for (int i = 0; papszMetadata && papszMetadata[i]; ++i)
    1001             :         {
    1002           1 :             char *pszKey = nullptr;
    1003           1 :             const char *pszValue = CPLParseNameValue(papszMetadata[i], &pszKey);
    1004           1 :             if (pszKey && pszValue)
    1005             :             {
    1006             :                 CPLXMLNode *psTag =
    1007           1 :                     CPLCreateXMLNode(psTagSet, CXT_Element, "Tag");
    1008           1 :                 CPLCreateXMLElementAndValue(psTag, "Key", pszKey);
    1009           1 :                 CPLCreateXMLElementAndValue(psTag, "Value", pszValue);
    1010             :             }
    1011           1 :             CPLFree(pszKey);
    1012             :         }
    1013             : 
    1014           1 :         char *pszXML = CPLSerializeXMLTree(psXML);
    1015           1 :         osXML = pszXML;
    1016           1 :         CPLFree(pszXML);
    1017           1 :         CPLDestroyXMLNode(psXML);
    1018             :     }
    1019             : 
    1020           4 :     do
    1021             :     {
    1022           4 :         bRetry = false;
    1023           4 :         CURL *hCurlHandle = curl_easy_init();
    1024             : 
    1025           4 :         if (EQUAL(pszDomain, "PROPERTIES"))
    1026           1 :             poHandleHelper->AddQueryParameter("comp", "properties");
    1027           3 :         else if (EQUAL(pszDomain, "METADATA"))
    1028           2 :             poHandleHelper->AddQueryParameter("comp", "metadata");
    1029             :         else
    1030           1 :             poHandleHelper->AddQueryParameter("comp", "tags");
    1031             : 
    1032           4 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
    1033           4 :         if (!osXML.empty())
    1034             :         {
    1035           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
    1036             :                                        osXML.c_str());
    1037             :         }
    1038             : 
    1039             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1040           4 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
    1041             :                               aosHTTPOptions.List()));
    1042             : 
    1043           8 :         CPLStringList aosList;
    1044           4 :         if (EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA"))
    1045             :         {
    1046           6 :             for (CSLConstList papszIter = papszMetadata;
    1047           6 :                  papszIter && *papszIter; ++papszIter)
    1048             :             {
    1049           3 :                 char *pszKey = nullptr;
    1050           3 :                 const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    1051           3 :                 if (pszKey && pszValue)
    1052             :                 {
    1053             :                     const char *pszHeader =
    1054           3 :                         CPLSPrintf("%s: %s", pszKey, pszValue);
    1055           3 :                     aosList.AddString(pszHeader);
    1056           3 :                     headers = curl_slist_append(headers, pszHeader);
    1057             :                 }
    1058           3 :                 CPLFree(pszKey);
    1059             :             }
    1060             :         }
    1061             : 
    1062           8 :         CPLString osContentLength;
    1063             :         osContentLength.Printf("Content-Length: %d",
    1064           4 :                                static_cast<int>(osXML.size()));
    1065           4 :         headers = curl_slist_append(headers, osContentLength.c_str());
    1066           4 :         if (!osXML.empty())
    1067             :         {
    1068           1 :             headers = curl_slist_append(
    1069             :                 headers, "Content-Type: application/xml; charset=UTF-8");
    1070           1 :             headers = VSICurlMergeHeaders(
    1071           1 :                 headers, poHandleHelper->GetCurlHeaders(
    1072           1 :                              "PUT", headers, osXML.c_str(), osXML.size()));
    1073             :         }
    1074             :         else
    1075             :         {
    1076           3 :             headers = VSICurlMergeHeaders(
    1077           3 :                 headers, poHandleHelper->GetCurlHeaders("PUT", headers));
    1078             :         }
    1079           4 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
    1080             : 
    1081           4 :         NetworkStatisticsLogger::LogPUT(osXML.size());
    1082             : 
    1083           8 :         CurlRequestHelper requestHelper;
    1084           4 :         const long response_code = requestHelper.perform(
    1085             :             hCurlHandle, headers, this, poHandleHelper.get());
    1086             : 
    1087           4 :         if (response_code != 200 && response_code != 204)
    1088             :         {
    1089             :             // Look if we should attempt a retry
    1090           1 :             if (oRetryContext.CanRetry(
    1091             :                     static_cast<int>(response_code),
    1092           1 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1093             :                     requestHelper.szCurlErrBuf))
    1094             :             {
    1095           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1096             :                          "HTTP error code: %d - %s. "
    1097             :                          "Retrying again in %.1f secs",
    1098             :                          static_cast<int>(response_code),
    1099           0 :                          poHandleHelper->GetURL().c_str(),
    1100             :                          oRetryContext.GetCurrentDelay());
    1101           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1102           0 :                 bRetry = true;
    1103             :             }
    1104             :             else
    1105             :             {
    1106           1 :                 CPLDebug(GetDebugKey(), "SetFileMetadata on %s failed: %s",
    1107             :                          pszFilename,
    1108           1 :                          requestHelper.sWriteFuncData.pBuffer
    1109             :                              ? requestHelper.sWriteFuncData.pBuffer
    1110             :                              : "(null)");
    1111             :             }
    1112             :         }
    1113             :         else
    1114             :         {
    1115           3 :             bRet = true;
    1116             :         }
    1117             : 
    1118           4 :         curl_easy_cleanup(hCurlHandle);
    1119             :     } while (bRetry);
    1120           4 :     return bRet;
    1121             : }
    1122             : 
    1123             : /************************************************************************/
    1124             : /*                      GetStreamingFilename()                          */
    1125             : /************************************************************************/
    1126             : 
    1127             : std::string
    1128           0 : VSIAzureFSHandler::GetStreamingFilename(const std::string &osFilename) const
    1129             : {
    1130           0 :     if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
    1131           0 :         return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
    1132           0 :     return osFilename;
    1133             : }
    1134             : 
    1135             : /************************************************************************/
    1136             : /*                       GetAzureAppendBufferSize()                     */
    1137             : /************************************************************************/
    1138             : 
    1139          11 : int GetAzureAppendBufferSize()
    1140             : {
    1141             :     int nBufferSize;
    1142          11 :     int nChunkSizeMB = atoi(CPLGetConfigOption("VSIAZ_CHUNK_SIZE", "4"));
    1143          11 :     if (nChunkSizeMB <= 0 || nChunkSizeMB > 4)
    1144           0 :         nBufferSize = 4 * 1024 * 1024;
    1145             :     else
    1146          11 :         nBufferSize = nChunkSizeMB * 1024 * 1024;
    1147             : 
    1148             :     // For testing only !
    1149             :     const char *pszChunkSizeBytes =
    1150          11 :         CPLGetConfigOption("VSIAZ_CHUNK_SIZE_BYTES", nullptr);
    1151          11 :     if (pszChunkSizeBytes)
    1152           3 :         nBufferSize = atoi(pszChunkSizeBytes);
    1153          11 :     if (nBufferSize <= 0 || nBufferSize > 4 * 1024 * 1024)
    1154           0 :         nBufferSize = 4 * 1024 * 1024;
    1155          11 :     return nBufferSize;
    1156             : }
    1157             : 
    1158             : /************************************************************************/
    1159             : /*                       VSIAzureWriteHandle()                          */
    1160             : /************************************************************************/
    1161             : 
    1162           4 : VSIAzureWriteHandle::VSIAzureWriteHandle(
    1163             :     VSIAzureFSHandler *poFS, const char *pszFilename,
    1164           4 :     VSIAzureBlobHandleHelper *poHandleHelper, CSLConstList papszOptions)
    1165           4 :     : VSIAppendWriteHandle(poFS, poFS->GetFSPrefix().c_str(), pszFilename,
    1166             :                            GetAzureAppendBufferSize()),
    1167             :       m_poHandleHelper(poHandleHelper), m_aosOptions(papszOptions),
    1168           8 :       m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename))
    1169             : {
    1170           4 : }
    1171             : 
    1172             : /************************************************************************/
    1173             : /*                      ~VSIAzureWriteHandle()                          */
    1174             : /************************************************************************/
    1175             : 
    1176           8 : VSIAzureWriteHandle::~VSIAzureWriteHandle()
    1177             : {
    1178           4 :     Close();
    1179           8 : }
    1180             : 
    1181             : /************************************************************************/
    1182             : /*                    InvalidateParentDirectory()                       */
    1183             : /************************************************************************/
    1184             : 
    1185           6 : void VSIAzureWriteHandle::InvalidateParentDirectory()
    1186             : {
    1187           6 :     m_poFS->InvalidateCachedData(m_poHandleHelper->GetURLNoKVP().c_str());
    1188             : 
    1189           6 :     std::string osFilenameWithoutSlash(m_osFilename);
    1190           6 :     if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
    1191           0 :         osFilenameWithoutSlash.pop_back();
    1192           6 :     m_poFS->InvalidateDirContent(
    1193          12 :         CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    1194           6 : }
    1195             : 
    1196             : /************************************************************************/
    1197             : /*                             Send()                                   */
    1198             : /************************************************************************/
    1199             : 
    1200           5 : bool VSIAzureWriteHandle::Send(bool bIsLastBlock)
    1201             : {
    1202           5 :     if (!bIsLastBlock)
    1203             :     {
    1204           1 :         CPLAssert(m_nBufferOff == m_nBufferSize);
    1205           1 :         if (m_nCurOffset == static_cast<vsi_l_offset>(m_nBufferSize))
    1206             :         {
    1207             :             // First full buffer ? Then create the blob empty
    1208           1 :             if (!SendInternal(true, false))
    1209           0 :                 return false;
    1210             :         }
    1211             :     }
    1212           5 :     return SendInternal(false, bIsLastBlock);
    1213             : }
    1214             : 
    1215             : /************************************************************************/
    1216             : /*                          SendInternal()                              */
    1217             : /************************************************************************/
    1218             : 
    1219           6 : bool VSIAzureWriteHandle::SendInternal(bool bInitOnly, bool bIsLastBlock)
    1220             : {
    1221          12 :     NetworkStatisticsFileSystem oContextFS("/vsiaz/");
    1222          12 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
    1223          12 :     NetworkStatisticsAction oContextAction("Write");
    1224             : 
    1225           6 :     bool bSuccess = true;
    1226           6 :     const bool bSingleBlock =
    1227          10 :         bIsLastBlock &&
    1228           4 :         (m_nCurOffset <= static_cast<vsi_l_offset>(m_nBufferSize));
    1229             : 
    1230           6 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
    1231           6 :     bool bHasAlreadyHandled409 = false;
    1232             :     bool bRetry;
    1233             : 
    1234          10 :     do
    1235             :     {
    1236          10 :         bRetry = false;
    1237             : 
    1238          10 :         m_nBufferOffReadCallback = 0;
    1239          10 :         CURL *hCurlHandle = curl_easy_init();
    1240             : 
    1241          10 :         m_poHandleHelper->ResetQueryParameters();
    1242          10 :         if (!bSingleBlock && !bInitOnly)
    1243             :         {
    1244           4 :             m_poHandleHelper->AddQueryParameter("comp", "appendblock");
    1245             :         }
    1246             : 
    1247          10 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    1248          10 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    1249             :                                    ReadCallBackBuffer);
    1250          10 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA,
    1251             :                                    static_cast<VSIAppendWriteHandle *>(this));
    1252             : 
    1253             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1254          10 :             CPLHTTPSetOptions(hCurlHandle, m_poHandleHelper->GetURL().c_str(),
    1255          10 :                               m_aosHTTPOptions.List()));
    1256          20 :         headers = VSICurlSetCreationHeadersFromOptions(
    1257          10 :             headers, m_aosOptions.List(), m_osFilename.c_str());
    1258             : 
    1259          20 :         CPLString osContentLength;  // leave it in this scope
    1260          10 :         if (bSingleBlock)
    1261             :         {
    1262           4 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    1263             :                                        m_nBufferOff);
    1264           4 :             if (m_nBufferOff)
    1265           2 :                 headers = curl_slist_append(headers, "Expect: 100-continue");
    1266           4 :             osContentLength.Printf("Content-Length: %d", m_nBufferOff);
    1267           4 :             headers = curl_slist_append(headers, osContentLength.c_str());
    1268           4 :             headers = curl_slist_append(headers, "x-ms-blob-type: BlockBlob");
    1269             :         }
    1270           6 :         else if (bInitOnly)
    1271             :         {
    1272           2 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
    1273           2 :             headers = curl_slist_append(headers, "Content-Length: 0");
    1274           2 :             headers = curl_slist_append(headers, "x-ms-blob-type: AppendBlob");
    1275             :         }
    1276             :         else
    1277             :         {
    1278           4 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    1279             :                                        m_nBufferOff);
    1280           4 :             osContentLength.Printf("Content-Length: %d", m_nBufferOff);
    1281           4 :             headers = curl_slist_append(headers, osContentLength.c_str());
    1282           4 :             vsi_l_offset nStartOffset = m_nCurOffset - m_nBufferOff;
    1283           4 :             const char *pszAppendPos = CPLSPrintf(
    1284             :                 "x-ms-blob-condition-appendpos: " CPL_FRMT_GUIB, nStartOffset);
    1285           4 :             headers = curl_slist_append(headers, pszAppendPos);
    1286             :         }
    1287             : 
    1288          10 :         headers = VSICurlMergeHeaders(
    1289             :             headers, m_poHandleHelper->GetCurlHeaders("PUT", headers));
    1290          10 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
    1291             : 
    1292          20 :         CurlRequestHelper requestHelper;
    1293          10 :         const long response_code = requestHelper.perform(
    1294          10 :             hCurlHandle, headers, m_poFS, m_poHandleHelper.get());
    1295             : 
    1296          10 :         NetworkStatisticsLogger::LogPUT(m_nBufferOff);
    1297             : 
    1298          10 :         if (!bHasAlreadyHandled409 && response_code == 409)
    1299             :         {
    1300           0 :             bHasAlreadyHandled409 = true;
    1301           0 :             CPLDebug(cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
    1302             :                      "%s",
    1303           0 :                      requestHelper.sWriteFuncData.pBuffer
    1304             :                          ? requestHelper.sWriteFuncData.pBuffer
    1305             :                          : "(null)");
    1306             : 
    1307             :             // The blob type is invalid for this operation
    1308             :             // Delete the file, and retry
    1309           0 :             if (cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->DeleteObject(
    1310           0 :                     m_osFilename.c_str()) == 0)
    1311             :             {
    1312           0 :                 bRetry = true;
    1313             :             }
    1314             :         }
    1315          10 :         else if (response_code != 201)
    1316             :         {
    1317             :             // Look if we should attempt a retry
    1318           4 :             if (oRetryContext.CanRetry(
    1319             :                     static_cast<int>(response_code),
    1320           4 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1321             :                     requestHelper.szCurlErrBuf))
    1322             :             {
    1323           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1324             :                          "HTTP error code: %d - %s. "
    1325             :                          "Retrying again in %.1f secs",
    1326             :                          static_cast<int>(response_code),
    1327           4 :                          m_poHandleHelper->GetURL().c_str(),
    1328             :                          oRetryContext.GetCurrentDelay());
    1329           4 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1330           4 :                 bRetry = true;
    1331             :             }
    1332             :             else
    1333             :             {
    1334           0 :                 CPLDebug(
    1335             :                     cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
    1336             :                     "%s",
    1337           0 :                     requestHelper.sWriteFuncData.pBuffer
    1338             :                         ? requestHelper.sWriteFuncData.pBuffer
    1339             :                         : "(null)");
    1340           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "PUT of %s failed",
    1341             :                          m_osFilename.c_str());
    1342           0 :                 bSuccess = false;
    1343             :             }
    1344             :         }
    1345             :         else
    1346             :         {
    1347           6 :             InvalidateParentDirectory();
    1348             :         }
    1349             : 
    1350          10 :         curl_easy_cleanup(hCurlHandle);
    1351             :     } while (bRetry);
    1352             : 
    1353          12 :     return bSuccess;
    1354             : }
    1355             : 
    1356             : /************************************************************************/
    1357             : /*                            ClearCache()                              */
    1358             : /************************************************************************/
    1359             : 
    1360         325 : void VSIAzureFSHandler::ClearCache()
    1361             : {
    1362         325 :     IVSIS3LikeFSHandler::ClearCache();
    1363             : 
    1364         325 :     VSIAzureBlobHandleHelper::ClearCache();
    1365         325 : }
    1366             : 
    1367             : /************************************************************************/
    1368             : /*                          GetURLFromFilename()                        */
    1369             : /************************************************************************/
    1370             : 
    1371             : std::string
    1372          49 : VSIAzureFSHandler::GetURLFromFilename(const std::string &osFilename) const
    1373             : {
    1374             :     std::string osFilenameWithoutPrefix =
    1375          98 :         osFilename.substr(GetFSPrefix().size());
    1376             :     auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
    1377             :         VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
    1378          98 :                                                GetFSPrefix().c_str()));
    1379          49 :     if (!poHandleHelper)
    1380           0 :         return std::string();
    1381          49 :     return poHandleHelper->GetURLNoKVP();
    1382             : }
    1383             : 
    1384             : /************************************************************************/
    1385             : /*                        CreateAzHandleHelper()                       */
    1386             : /************************************************************************/
    1387             : 
    1388             : VSIAzureBlobHandleHelper *
    1389          73 : VSIAzureFSHandler::CreateAzHandleHelper(const char *pszURI, bool)
    1390             : {
    1391          73 :     return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
    1392         146 :                                                   GetFSPrefix().c_str());
    1393             : }
    1394             : 
    1395             : /************************************************************************/
    1396             : /*                         InvalidateRecursive()                        */
    1397             : /************************************************************************/
    1398             : 
    1399           6 : void VSIAzureFSHandler::InvalidateRecursive(const std::string &osDirnameIn)
    1400             : {
    1401             :     // As Azure directories disappear as soon there is no remaining file
    1402             :     // we may need to invalidate the whole hierarchy
    1403          12 :     std::string osDirname(osDirnameIn);
    1404          16 :     while (osDirname.size() > GetFSPrefix().size())
    1405             :     {
    1406          10 :         InvalidateDirContent(osDirname.c_str());
    1407          10 :         InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
    1408          10 :         osDirname = CPLGetDirnameSafe(osDirname.c_str());
    1409             :     }
    1410           6 : }
    1411             : 
    1412             : /************************************************************************/
    1413             : /*                               Unlink()                               */
    1414             : /************************************************************************/
    1415             : 
    1416           4 : int VSIAzureFSHandler::Unlink(const char *pszFilename)
    1417             : {
    1418           4 :     int ret = IVSIS3LikeFSHandler::Unlink(pszFilename);
    1419           4 :     if (ret != 0)
    1420           1 :         return ret;
    1421             : 
    1422           3 :     InvalidateRecursive(CPLGetDirnameSafe(pszFilename));
    1423           3 :     return 0;
    1424             : }
    1425             : 
    1426             : /************************************************************************/
    1427             : /*                           UnlinkBatch()                              */
    1428             : /************************************************************************/
    1429             : 
    1430           4 : int *VSIAzureFSHandler::UnlinkBatch(CSLConstList papszFiles)
    1431             : {
    1432             :     // Implemented using
    1433             :     // https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch
    1434             : 
    1435           4 :     const char *pszFirstFilename =
    1436           4 :         papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
    1437             : 
    1438             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    1439          12 :         VSIAzureBlobHandleHelper::BuildFromURI(
    1440           4 :             "", GetFSPrefix().c_str(),
    1441          12 :             pszFirstFilename &&
    1442           8 :                     STARTS_WITH(pszFirstFilename, GetFSPrefix().c_str())
    1443          12 :                 ? pszFirstFilename + GetFSPrefix().size()
    1444           8 :                 : nullptr));
    1445             : 
    1446             :     int *panRet =
    1447           4 :         static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
    1448             : 
    1449           4 :     if (!poHandleHelper || pszFirstFilename == nullptr)
    1450           0 :         return panRet;
    1451             : 
    1452           8 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1453           8 :     NetworkStatisticsAction oContextAction("UnlinkBatch");
    1454             : 
    1455             :     const CPLStringList aosHTTPOptions(
    1456           8 :         CPLHTTPGetOptionsFromEnv(pszFirstFilename));
    1457           8 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    1458             : 
    1459             :     // For debug / testing only
    1460             :     const int nBatchSize =
    1461           4 :         std::max(1, std::min(256, atoi(CPLGetConfigOption(
    1462           4 :                                       "CPL_VSIAZ_UNLINK_BATCH_SIZE", "256"))));
    1463           4 :     std::string osPOSTContent;
    1464             : 
    1465           4 :     int nFilesInBatch = 0;
    1466           4 :     int nFirstIDInBatch = 0;
    1467             : 
    1468           6 :     const auto DoPOST = [this, panRet, &nFilesInBatch, &oRetryParameters,
    1469             :                          &aosHTTPOptions, &poHandleHelper, &osPOSTContent,
    1470          92 :                          &nFirstIDInBatch](int nLastID)
    1471             :     {
    1472           6 :         osPOSTContent += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589--\r\n";
    1473             : 
    1474             : #ifdef DEBUG_VERBOSE
    1475             :         CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
    1476             : #endif
    1477             : 
    1478             :         // Run request
    1479          12 :         CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1480             :         bool bRetry;
    1481           6 :         std::string osResponse;
    1482           6 :         do
    1483             :         {
    1484           6 :             poHandleHelper->AddQueryParameter("comp", "batch");
    1485             : 
    1486           6 :             bRetry = false;
    1487           6 :             CURL *hCurlHandle = curl_easy_init();
    1488             : 
    1489           6 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
    1490             :                                        "POST");
    1491           6 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
    1492             :                                        osPOSTContent.c_str());
    1493             : 
    1494             :             struct curl_slist *headers = static_cast<struct curl_slist *>(
    1495           6 :                 CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
    1496             :                                   aosHTTPOptions.List()));
    1497           6 :             headers = curl_slist_append(
    1498             :                 headers, "Content-Type: multipart/mixed; "
    1499             :                          "boundary=batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589");
    1500           6 :             headers = curl_slist_append(
    1501             :                 headers, CPLSPrintf("Content-Length: %d",
    1502           6 :                                     static_cast<int>(osPOSTContent.size())));
    1503           6 :             headers = VSICurlMergeHeaders(
    1504           6 :                 headers, poHandleHelper->GetCurlHeaders("POST", headers));
    1505             : 
    1506          12 :             CurlRequestHelper requestHelper;
    1507           6 :             const long response_code = requestHelper.perform(
    1508             :                 hCurlHandle, headers, this, poHandleHelper.get());
    1509             : 
    1510           6 :             NetworkStatisticsLogger::LogPOST(
    1511             :                 osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
    1512             : 
    1513           6 :             if (response_code != 202 ||
    1514           6 :                 requestHelper.sWriteFuncData.pBuffer == nullptr)
    1515             :             {
    1516             :                 // Look if we should attempt a retry
    1517           0 :                 if (oRetryContext.CanRetry(
    1518             :                         static_cast<int>(response_code),
    1519           0 :                         requestHelper.sWriteFuncHeaderData.pBuffer,
    1520             :                         requestHelper.szCurlErrBuf))
    1521             :                 {
    1522           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1523             :                              "HTTP error code: %d - %s. "
    1524             :                              "Retrying again in %.1f secs",
    1525             :                              static_cast<int>(response_code),
    1526           0 :                              poHandleHelper->GetURL().c_str(),
    1527             :                              oRetryContext.GetCurrentDelay());
    1528           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
    1529           0 :                     bRetry = true;
    1530             :                 }
    1531             :                 else
    1532             :                 {
    1533           0 :                     CPLDebug(GetDebugKey(), "%s",
    1534           0 :                              requestHelper.sWriteFuncData.pBuffer
    1535             :                                  ? requestHelper.sWriteFuncData.pBuffer
    1536             :                                  : "(null)");
    1537           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1538             :                              "DeleteObjects failed");
    1539             :                 }
    1540             :             }
    1541             :             else
    1542             :             {
    1543             : #ifdef DEBUG_VERBOSE
    1544             :                 CPLDebug(GetDebugKey(), "%s",
    1545             :                          requestHelper.sWriteFuncData.pBuffer);
    1546             : #endif
    1547           6 :                 osResponse = requestHelper.sWriteFuncData.pBuffer;
    1548             :             }
    1549             : 
    1550           6 :             curl_easy_cleanup(hCurlHandle);
    1551             :         } while (bRetry);
    1552             : 
    1553             :         // Mark deleted files
    1554          16 :         for (int j = nFirstIDInBatch; j <= nLastID; j++)
    1555             :         {
    1556          10 :             auto nPos = osResponse.find(CPLSPrintf("Content-ID: <%d>", j));
    1557          10 :             if (nPos != std::string::npos)
    1558             :             {
    1559           8 :                 nPos = osResponse.find("HTTP/1.1 ", nPos);
    1560           8 :                 if (nPos != std::string::npos)
    1561             :                 {
    1562             :                     const char *pszHTTPCode =
    1563           8 :                         osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
    1564           8 :                     panRet[j] = (atoi(pszHTTPCode) == 202) ? 1 : 0;
    1565             :                 }
    1566             :             }
    1567             :         }
    1568             : 
    1569           6 :         osPOSTContent.clear();
    1570           6 :         nFilesInBatch = 0;
    1571           6 :         nFirstIDInBatch = nLastID;
    1572           6 :     };
    1573             : 
    1574          12 :     for (int i = 0; papszFiles && papszFiles[i]; i++)
    1575             :     {
    1576           8 :         CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
    1577             : 
    1578          16 :         std::string osAuthorization;
    1579          16 :         std::string osXMSDate;
    1580             :         {
    1581             :             auto poTmpHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
    1582           8 :                 VSIAzureBlobHandleHelper::BuildFromURI(papszFiles[i] +
    1583          16 :                                                            GetFSPrefix().size(),
    1584          24 :                                                        GetFSPrefix().c_str()));
    1585             :             // x-ms-version must not be included in the subrequests...
    1586           8 :             poTmpHandleHelper->SetIncludeMSVersion(false);
    1587           8 :             CURL *hCurlHandle = curl_easy_init();
    1588             :             struct curl_slist *subrequest_headers =
    1589          16 :                 static_cast<struct curl_slist *>(CPLHTTPSetOptions(
    1590           8 :                     hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
    1591             :                     aosHTTPOptions.List()));
    1592           8 :             subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
    1593             :                 "DELETE", subrequest_headers, nullptr, 0);
    1594          24 :             for (struct curl_slist *iter = subrequest_headers; iter;
    1595          16 :                  iter = iter->next)
    1596             :             {
    1597          16 :                 if (STARTS_WITH_CI(iter->data, "Authorization: "))
    1598             :                 {
    1599           8 :                     osAuthorization = iter->data;
    1600             :                 }
    1601           8 :                 else if (STARTS_WITH_CI(iter->data, "x-ms-date: "))
    1602             :                 {
    1603           7 :                     osXMSDate = iter->data;
    1604             :                 }
    1605             :             }
    1606           8 :             curl_slist_free_all(subrequest_headers);
    1607           8 :             curl_easy_cleanup(hCurlHandle);
    1608             :         }
    1609             : 
    1610          16 :         std::string osSubrequest;
    1611           8 :         osSubrequest += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589\r\n";
    1612           8 :         osSubrequest += "Content-Type: application/http\r\n";
    1613           8 :         osSubrequest += CPLSPrintf("Content-ID: <%d>\r\n", i);
    1614           8 :         osSubrequest += "Content-Transfer-Encoding: binary\r\n";
    1615           8 :         osSubrequest += "\r\n";
    1616           8 :         osSubrequest += "DELETE /";
    1617           8 :         osSubrequest += (papszFiles[i] + GetFSPrefix().size());
    1618           8 :         osSubrequest += " HTTP/1.1\r\n";
    1619           8 :         osSubrequest += osXMSDate;
    1620           8 :         osSubrequest += "\r\n";
    1621           8 :         osSubrequest += osAuthorization;
    1622           8 :         osSubrequest += "\r\n";
    1623           8 :         osSubrequest += "Content-Length: 0\r\n";
    1624           8 :         osSubrequest += "\r\n";
    1625           8 :         osSubrequest += "\r\n";
    1626             : 
    1627             :         // The size of the body for a batch request can't exceed 4 MB.
    1628             :         // Add some margin for the end boundary delimiter.
    1629          12 :         if (i > nFirstIDInBatch &&
    1630           4 :             osPOSTContent.size() + osSubrequest.size() > 4 * 1024 * 1024 - 100)
    1631             :         {
    1632           1 :             DoPOST(i - 1);
    1633             :         }
    1634             : 
    1635           8 :         osPOSTContent += osSubrequest;
    1636           8 :         nFilesInBatch++;
    1637             : 
    1638           8 :         if (nFilesInBatch == nBatchSize || papszFiles[i + 1] == nullptr)
    1639             :         {
    1640           5 :             DoPOST(i);
    1641             :         }
    1642             :     }
    1643           4 :     return panRet;
    1644             : }
    1645             : 
    1646             : /************************************************************************/
    1647             : /*                               Mkdir()                                */
    1648             : /************************************************************************/
    1649             : 
    1650           5 : int VSIAzureFSHandler::MkdirInternal(const char *pszDirname, long /* nMode */,
    1651             :                                      bool bDoStatCheck)
    1652             : {
    1653           5 :     if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
    1654           1 :         return -1;
    1655             : 
    1656           8 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1657           8 :     NetworkStatisticsAction oContextAction("Mkdir");
    1658             : 
    1659           8 :     std::string osDirname(pszDirname);
    1660           4 :     if (!osDirname.empty() && osDirname.back() != '/')
    1661           4 :         osDirname += "/";
    1662             : 
    1663           4 :     if (bDoStatCheck)
    1664             :     {
    1665             :         VSIStatBufL sStat;
    1666           5 :         if (VSIStatL(osDirname.c_str(), &sStat) == 0 &&
    1667           1 :             sStat.st_mode == S_IFDIR)
    1668             :         {
    1669           1 :             CPLDebug(GetDebugKey(), "Directory %s already exists",
    1670             :                      osDirname.c_str());
    1671           1 :             errno = EEXIST;
    1672           1 :             return -1;
    1673             :         }
    1674             :     }
    1675             : 
    1676           6 :     std::string osDirnameWithoutEndSlash(osDirname);
    1677           3 :     osDirnameWithoutEndSlash.pop_back();
    1678           9 :     if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
    1679           6 :         osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
    1680             :             std::string::npos)
    1681             :     {
    1682           1 :         return CreateContainer(osDirnameWithoutEndSlash);
    1683             :     }
    1684             : 
    1685           2 :     InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
    1686           2 :     InvalidateCachedData(
    1687           4 :         GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
    1688           2 :     InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
    1689             : 
    1690           2 :     VSILFILE *fp = VSIFOpenL((osDirname + GDAL_MARKER_FOR_DIR).c_str(), "wb");
    1691           2 :     if (fp != nullptr)
    1692             :     {
    1693           2 :         CPLErrorReset();
    1694           2 :         VSIFCloseL(fp);
    1695           2 :         return CPLGetLastErrorType() == CPLE_None ? 0 : -1;
    1696             :     }
    1697             :     else
    1698             :     {
    1699           0 :         return -1;
    1700             :     }
    1701             : }
    1702             : 
    1703           5 : int VSIAzureFSHandler::Mkdir(const char *pszDirname, long nMode)
    1704             : {
    1705           5 :     return MkdirInternal(pszDirname, nMode, true);
    1706             : }
    1707             : 
    1708             : /************************************************************************/
    1709             : /*                        CreateContainer()                             */
    1710             : /************************************************************************/
    1711             : 
    1712           1 : int VSIAzureFSHandler::CreateContainer(const std::string &osDirname)
    1713             : {
    1714           2 :     std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
    1715             :     auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    1716           2 :         CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
    1717           1 :     if (poS3HandleHelper == nullptr)
    1718             :     {
    1719           0 :         return -1;
    1720             :     }
    1721             : 
    1722           1 :     int nRet = 0;
    1723             : 
    1724             :     bool bRetry;
    1725             : 
    1726             :     const CPLStringList aosHTTPOptions(
    1727           2 :         CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
    1728           2 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    1729           1 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1730             : 
    1731           1 :     do
    1732             :     {
    1733           1 :         poS3HandleHelper->AddQueryParameter("restype", "container");
    1734             : 
    1735           1 :         bRetry = false;
    1736           1 :         CURL *hCurlHandle = curl_easy_init();
    1737           1 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
    1738             : 
    1739             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1740           1 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1741             :                               aosHTTPOptions.List()));
    1742           1 :         headers = curl_slist_append(headers, "Content-Length: 0");
    1743           1 :         headers = VSICurlMergeHeaders(
    1744           1 :             headers, poS3HandleHelper->GetCurlHeaders("PUT", headers));
    1745           1 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
    1746             : 
    1747           2 :         CurlRequestHelper requestHelper;
    1748           1 :         const long response_code = requestHelper.perform(
    1749             :             hCurlHandle, headers, this, poS3HandleHelper.get());
    1750             : 
    1751           1 :         NetworkStatisticsLogger::LogPUT(0);
    1752             : 
    1753           1 :         if (response_code != 201)
    1754             :         {
    1755             :             // Look if we should attempt a retry
    1756           0 :             if (oRetryContext.CanRetry(
    1757             :                     static_cast<int>(response_code),
    1758           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1759             :                     requestHelper.szCurlErrBuf))
    1760             :             {
    1761           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1762             :                          "HTTP error code: %d - %s. "
    1763             :                          "Retrying again in %.1f secs",
    1764             :                          static_cast<int>(response_code),
    1765           0 :                          poS3HandleHelper->GetURL().c_str(),
    1766             :                          oRetryContext.GetCurrentDelay());
    1767           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1768           0 :                 bRetry = true;
    1769             :             }
    1770             :             else
    1771             :             {
    1772           0 :                 CPLDebug(GetDebugKey(), "%s",
    1773           0 :                          requestHelper.sWriteFuncData.pBuffer
    1774             :                              ? requestHelper.sWriteFuncData.pBuffer
    1775             :                              : "(null)");
    1776           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1777             :                          "Creation of container %s failed", osDirname.c_str());
    1778           0 :                 nRet = -1;
    1779             :             }
    1780             :         }
    1781             :         else
    1782             :         {
    1783           1 :             InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
    1784           1 :             InvalidateDirContent(GetFSPrefix().c_str());
    1785             :         }
    1786             : 
    1787           1 :         curl_easy_cleanup(hCurlHandle);
    1788             :     } while (bRetry);
    1789             : 
    1790           1 :     return nRet;
    1791             : }
    1792             : 
    1793             : /************************************************************************/
    1794             : /*                               Rmdir()                                */
    1795             : /************************************************************************/
    1796             : 
    1797           9 : int VSIAzureFSHandler::Rmdir(const char *pszDirname)
    1798             : {
    1799           9 :     if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
    1800           1 :         return -1;
    1801             : 
    1802          16 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1803          16 :     NetworkStatisticsAction oContextAction("Rmdir");
    1804             : 
    1805          16 :     std::string osDirname(pszDirname);
    1806           8 :     if (!osDirname.empty() && osDirname.back() != '/')
    1807           8 :         osDirname += "/";
    1808             : 
    1809             :     VSIStatBufL sStat;
    1810           8 :     if (VSIStatL(osDirname.c_str(), &sStat) != 0)
    1811             :     {
    1812           2 :         InvalidateCachedData(
    1813           4 :             GetURLFromFilename(osDirname.substr(0, osDirname.size() - 1))
    1814             :                 .c_str());
    1815             :         // The directory might have not been created by GDAL, and thus lacking
    1816             :         // the GDAL marker file, so do not turn non-existence as an error
    1817           2 :         return 0;
    1818             :     }
    1819           6 :     else if (sStat.st_mode != S_IFDIR)
    1820             :     {
    1821           0 :         CPLDebug(GetDebugKey(), "%s is not a directory", pszDirname);
    1822           0 :         errno = ENOTDIR;
    1823           0 :         return -1;
    1824             :     }
    1825             : 
    1826           6 :     char **papszFileList = ReadDirEx(osDirname.c_str(), 1);
    1827           6 :     bool bEmptyDir =
    1828          10 :         (papszFileList != nullptr && EQUAL(papszFileList[0], ".") &&
    1829           4 :          papszFileList[1] == nullptr);
    1830           6 :     CSLDestroy(papszFileList);
    1831           6 :     if (!bEmptyDir)
    1832             :     {
    1833           2 :         CPLDebug(GetDebugKey(), "%s is not empty", pszDirname);
    1834           2 :         errno = ENOTEMPTY;
    1835           2 :         return -1;
    1836             :     }
    1837             : 
    1838           8 :     std::string osDirnameWithoutEndSlash(osDirname);
    1839           4 :     osDirnameWithoutEndSlash.pop_back();
    1840          12 :     if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
    1841           8 :         osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
    1842             :             std::string::npos)
    1843             :     {
    1844           1 :         return DeleteContainer(osDirnameWithoutEndSlash);
    1845             :     }
    1846             : 
    1847           3 :     InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
    1848           3 :     InvalidateCachedData(
    1849           6 :         GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
    1850           3 :     InvalidateRecursive(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
    1851           3 :     if (osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
    1852             :         std::string::npos)
    1853             :     {
    1854           0 :         CPLDebug(GetDebugKey(), "%s is a container", pszDirname);
    1855           0 :         errno = ENOTDIR;
    1856           0 :         return -1;
    1857             :     }
    1858             : 
    1859           3 :     if (DeleteObject((osDirname + GDAL_MARKER_FOR_DIR).c_str()) == 0)
    1860           3 :         return 0;
    1861             :     // The directory might have not been created by GDAL, and thus lacking the
    1862             :     // GDAL marker file, so check if is there, and if not, return success.
    1863           0 :     if (VSIStatL(osDirname.c_str(), &sStat) != 0)
    1864           0 :         return 0;
    1865           0 :     return -1;
    1866             : }
    1867             : 
    1868             : /************************************************************************/
    1869             : /*                        DeleteContainer()                             */
    1870             : /************************************************************************/
    1871             : 
    1872           1 : int VSIAzureFSHandler::DeleteContainer(const std::string &osDirname)
    1873             : {
    1874           2 :     std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
    1875             :     auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    1876           2 :         CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
    1877           1 :     if (poS3HandleHelper == nullptr)
    1878             :     {
    1879           0 :         return -1;
    1880             :     }
    1881             : 
    1882           1 :     int nRet = 0;
    1883             : 
    1884             :     bool bRetry;
    1885             : 
    1886             :     const CPLStringList aosHTTPOptions(
    1887           2 :         CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
    1888           2 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    1889           1 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    1890             : 
    1891           1 :     do
    1892             :     {
    1893           1 :         poS3HandleHelper->AddQueryParameter("restype", "container");
    1894             : 
    1895           1 :         bRetry = false;
    1896           1 :         CURL *hCurlHandle = curl_easy_init();
    1897           1 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
    1898             :                                    "DELETE");
    1899             : 
    1900             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    1901           1 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    1902             :                               aosHTTPOptions.List()));
    1903           1 :         headers = curl_slist_append(headers, "Content-Length: 0");
    1904           1 :         headers = VSICurlMergeHeaders(
    1905           1 :             headers, poS3HandleHelper->GetCurlHeaders("DELETE", headers));
    1906           1 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
    1907             : 
    1908           2 :         CurlRequestHelper requestHelper;
    1909           1 :         const long response_code = requestHelper.perform(
    1910             :             hCurlHandle, headers, this, poS3HandleHelper.get());
    1911             : 
    1912           1 :         NetworkStatisticsLogger::LogPUT(0);
    1913             : 
    1914           1 :         if (response_code != 202)
    1915             :         {
    1916             :             // Look if we should attempt a retry
    1917           0 :             if (oRetryContext.CanRetry(
    1918             :                     static_cast<int>(response_code),
    1919           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    1920             :                     requestHelper.szCurlErrBuf))
    1921             :             {
    1922           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1923             :                          "HTTP error code: %d - %s. "
    1924             :                          "Retrying again in %.1f secs",
    1925             :                          static_cast<int>(response_code),
    1926           0 :                          poS3HandleHelper->GetURL().c_str(),
    1927             :                          oRetryContext.GetCurrentDelay());
    1928           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    1929           0 :                 bRetry = true;
    1930             :             }
    1931             :             else
    1932             :             {
    1933           0 :                 CPLDebug(GetDebugKey(), "%s",
    1934           0 :                          requestHelper.sWriteFuncData.pBuffer
    1935             :                              ? requestHelper.sWriteFuncData.pBuffer
    1936             :                              : "(null)");
    1937           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1938             :                          "Deletion of container %s failed", osDirname.c_str());
    1939           0 :                 nRet = -1;
    1940             :             }
    1941             :         }
    1942             :         else
    1943             :         {
    1944           1 :             InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
    1945           1 :             InvalidateDirContent(GetFSPrefix().c_str());
    1946             :         }
    1947             : 
    1948           1 :         curl_easy_cleanup(hCurlHandle);
    1949             :     } while (bRetry);
    1950             : 
    1951           1 :     return nRet;
    1952             : }
    1953             : 
    1954             : /************************************************************************/
    1955             : /*                           CopyFile()                                 */
    1956             : /************************************************************************/
    1957             : 
    1958           3 : int VSIAzureFSHandler::CopyFile(const char *pszSource, const char *pszTarget,
    1959             :                                 VSILFILE *fpSource, vsi_l_offset nSourceSize,
    1960             :                                 CSLConstList papszOptions,
    1961             :                                 GDALProgressFunc pProgressFunc,
    1962             :                                 void *pProgressData)
    1963             : {
    1964           6 :     const std::string osPrefix(GetFSPrefix());
    1965           8 :     if ((STARTS_WITH(pszSource, "/vsis3/") ||
    1966           2 :          STARTS_WITH(pszSource, "/vsigs/") ||
    1967           2 :          STARTS_WITH(pszSource, "/vsiadls/") ||
    1968           6 :          STARTS_WITH(pszSource, "/vsicurl/")) &&
    1969           1 :         STARTS_WITH(pszTarget, osPrefix.c_str()))
    1970             :     {
    1971           2 :         std::string osMsg("Copying of");
    1972           1 :         osMsg += pszSource;
    1973             : 
    1974           2 :         NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1975           1 :         NetworkStatisticsAction oContextAction("CopyFile");
    1976             : 
    1977           1 :         bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
    1978           1 :         if (bRet && pProgressFunc)
    1979             :         {
    1980           0 :             bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
    1981             :         }
    1982           1 :         return bRet ? 0 : -1;
    1983             :     }
    1984             : 
    1985           2 :     return IVSIS3LikeFSHandler::CopyFile(pszSource, pszTarget, fpSource,
    1986             :                                          nSourceSize, papszOptions,
    1987           2 :                                          pProgressFunc, pProgressData);
    1988             : }
    1989             : 
    1990             : /************************************************************************/
    1991             : /*                            CopyObject()                              */
    1992             : /************************************************************************/
    1993             : 
    1994           4 : int VSIAzureFSHandler::CopyObject(const char *oldpath, const char *newpath,
    1995             :                                   CSLConstList /* papszMetadata */)
    1996             : {
    1997           8 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    1998           8 :     NetworkStatisticsAction oContextAction("CopyObject");
    1999             : 
    2000          12 :     std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
    2001             :     auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
    2002           8 :         CreateAzHandleHelper(osTargetNameWithoutPrefix.c_str(), false));
    2003           4 :     if (poHandleHelper == nullptr)
    2004             :     {
    2005           0 :         return -1;
    2006             :     }
    2007             : 
    2008           8 :     std::string osSourceHeader("x-ms-copy-source: ");
    2009           4 :     bool bUseSourceSignedURL = true;
    2010           4 :     if (STARTS_WITH(oldpath, GetFSPrefix().c_str()))
    2011             :     {
    2012           6 :         std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
    2013             :         auto poHandleHelperSource = std::unique_ptr<VSIAzureBlobHandleHelper>(
    2014           3 :             CreateAzHandleHelper(osSourceNameWithoutPrefix.c_str(), false));
    2015           3 :         if (poHandleHelperSource == nullptr)
    2016             :         {
    2017           0 :             return -1;
    2018             :         }
    2019             :         // We can use a unsigned source URL only if
    2020             :         // the source and target are in the same bucket
    2021           3 :         if (poHandleHelper->GetStorageAccount() ==
    2022           9 :                 poHandleHelperSource->GetStorageAccount() &&
    2023           3 :             poHandleHelper->GetBucket() == poHandleHelperSource->GetBucket())
    2024             :         {
    2025           2 :             bUseSourceSignedURL = false;
    2026           2 :             osSourceHeader += poHandleHelperSource->GetURLNoKVP();
    2027             :         }
    2028             :     }
    2029             : 
    2030           4 :     if (bUseSourceSignedURL)
    2031             :     {
    2032             :         VSIStatBufL sStat;
    2033             :         // This has the effect of making sure that the S3 region is correct
    2034             :         // if copying from /vsis3/
    2035           2 :         if (VSIStatExL(oldpath, &sStat, VSI_STAT_EXISTS_FLAG) != 0)
    2036             :         {
    2037           0 :             CPLError(CE_Failure, CPLE_AppDefined, "%s does not exist", oldpath);
    2038           0 :             return -1;
    2039             :         }
    2040             : 
    2041           2 :         char *pszSignedURL = VSIGetSignedURL(oldpath, nullptr);
    2042           2 :         if (!pszSignedURL)
    2043             :         {
    2044           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2045             :                      "Cannot get signed URL for %s", oldpath);
    2046           0 :             return -1;
    2047             :         }
    2048           2 :         osSourceHeader += pszSignedURL;
    2049           2 :         VSIFree(pszSignedURL);
    2050             :     }
    2051             : 
    2052           4 :     int nRet = 0;
    2053             : 
    2054             :     bool bRetry;
    2055             : 
    2056           8 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
    2057           8 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
    2058           4 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2059             : 
    2060           4 :     do
    2061             :     {
    2062           4 :         bRetry = false;
    2063           4 :         CURL *hCurlHandle = curl_easy_init();
    2064           4 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
    2065             : 
    2066             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2067           4 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
    2068             :                               aosHTTPOptions.List()));
    2069           4 :         headers = curl_slist_append(headers, osSourceHeader.c_str());
    2070           4 :         headers = VSICurlSetContentTypeFromExt(headers, newpath);
    2071           4 :         headers = curl_slist_append(headers, "Content-Length: 0");
    2072           4 :         headers = VSICurlMergeHeaders(
    2073             :             headers, poHandleHelper->GetCurlHeaders("PUT", headers));
    2074           4 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
    2075             : 
    2076           8 :         CurlRequestHelper requestHelper;
    2077           4 :         const long response_code = requestHelper.perform(
    2078           4 :             hCurlHandle, headers, this, poHandleHelper.get());
    2079             : 
    2080           4 :         NetworkStatisticsLogger::LogPUT(0);
    2081             : 
    2082           4 :         if (response_code != 202)
    2083             :         {
    2084             :             // Look if we should attempt a retry
    2085           0 :             if (oRetryContext.CanRetry(
    2086             :                     static_cast<int>(response_code),
    2087           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2088             :                     requestHelper.szCurlErrBuf))
    2089             :             {
    2090           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2091             :                          "HTTP error code: %d - %s. "
    2092             :                          "Retrying again in %.1f secs",
    2093             :                          static_cast<int>(response_code),
    2094           0 :                          poHandleHelper->GetURL().c_str(),
    2095             :                          oRetryContext.GetCurrentDelay());
    2096           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2097           0 :                 bRetry = true;
    2098             :             }
    2099             :             else
    2100             :             {
    2101           0 :                 CPLDebug(GetDebugKey(), "%s",
    2102           0 :                          requestHelper.sWriteFuncData.pBuffer
    2103             :                              ? requestHelper.sWriteFuncData.pBuffer
    2104             :                              : "(null)");
    2105           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
    2106             :                          oldpath, newpath);
    2107           0 :                 nRet = -1;
    2108             :             }
    2109             :         }
    2110             :         else
    2111             :         {
    2112           4 :             InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
    2113             : 
    2114           4 :             std::string osFilenameWithoutSlash(newpath);
    2115           8 :             if (!osFilenameWithoutSlash.empty() &&
    2116           4 :                 osFilenameWithoutSlash.back() == '/')
    2117           0 :                 osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
    2118             :                                               1);
    2119             : 
    2120           4 :             InvalidateDirContent(
    2121           8 :                 CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
    2122             :         }
    2123             : 
    2124           4 :         curl_easy_cleanup(hCurlHandle);
    2125             :     } while (bRetry);
    2126             : 
    2127           4 :     return nRet;
    2128             : }
    2129             : 
    2130             : /************************************************************************/
    2131             : /*                             PutBlock()                               */
    2132             : /************************************************************************/
    2133             : 
    2134           6 : std::string VSIAzureFSHandler::PutBlock(
    2135             :     const std::string &osFilename, int nPartNumber, const void *pabyBuffer,
    2136             :     size_t nBufferSize, IVSIS3LikeHandleHelper *poS3HandleHelper,
    2137             :     const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions)
    2138             : {
    2139          12 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2140          12 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    2141          12 :     NetworkStatisticsAction oContextAction("PutBlock");
    2142             : 
    2143             :     bool bRetry;
    2144          12 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2145           6 :     std::string osBlockId(CPLSPrintf("%012d", nPartNumber));
    2146             : 
    2147             :     const std::string osContentLength(
    2148          12 :         CPLSPrintf("Content-Length: %d", static_cast<int>(nBufferSize)));
    2149             : 
    2150           6 :     bool bHasAlreadyHandled409 = false;
    2151             : 
    2152             :     const CPLStringList aosHTTPOptions(
    2153          12 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    2154             : 
    2155           7 :     do
    2156             :     {
    2157           7 :         bRetry = false;
    2158             : 
    2159           7 :         poS3HandleHelper->AddQueryParameter("comp", "block");
    2160           7 :         poS3HandleHelper->AddQueryParameter("blockid", osBlockId);
    2161             : 
    2162           7 :         CURL *hCurlHandle = curl_easy_init();
    2163           7 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    2164           7 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    2165             :                                    PutData::ReadCallBackBuffer);
    2166           7 :         PutData putData;
    2167           7 :         putData.pabyData = static_cast<const GByte *>(pabyBuffer);
    2168           7 :         putData.nOff = 0;
    2169           7 :         putData.nTotalSize = nBufferSize;
    2170           7 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
    2171           7 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    2172             :                                    nBufferSize);
    2173             : 
    2174             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2175           7 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    2176             :                               aosHTTPOptions.List()));
    2177           7 :         headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
    2178             :                                                        osFilename.c_str());
    2179           7 :         headers = curl_slist_append(headers, osContentLength.c_str());
    2180           7 :         headers = VSICurlMergeHeaders(
    2181             :             headers, poS3HandleHelper->GetCurlHeaders("PUT", headers,
    2182           7 :                                                       pabyBuffer, nBufferSize));
    2183             : 
    2184          14 :         CurlRequestHelper requestHelper;
    2185             :         const long response_code =
    2186           7 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    2187             : 
    2188           7 :         NetworkStatisticsLogger::LogPUT(nBufferSize);
    2189             : 
    2190           7 :         if (!bHasAlreadyHandled409 && response_code == 409)
    2191             :         {
    2192           1 :             bHasAlreadyHandled409 = true;
    2193           1 :             CPLDebug(GetDebugKey(), "%s",
    2194           1 :                      requestHelper.sWriteFuncData.pBuffer
    2195             :                          ? requestHelper.sWriteFuncData.pBuffer
    2196             :                          : "(null)");
    2197             : 
    2198             :             // The blob type is invalid for this operation
    2199             :             // Delete the file, and retry
    2200           1 :             if (DeleteObject(osFilename.c_str()) == 0)
    2201             :             {
    2202           1 :                 bRetry = true;
    2203             :             }
    2204             :         }
    2205           6 :         else if ((response_code != 200 && response_code != 201) ||
    2206           6 :                  requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
    2207             :         {
    2208             :             // Look if we should attempt a retry
    2209           0 :             if (oRetryContext.CanRetry(
    2210             :                     static_cast<int>(response_code),
    2211           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2212             :                     requestHelper.szCurlErrBuf))
    2213             :             {
    2214           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2215             :                          "HTTP error code: %d - %s. "
    2216             :                          "Retrying again in %.1f secs",
    2217             :                          static_cast<int>(response_code),
    2218           0 :                          poS3HandleHelper->GetURL().c_str(),
    2219             :                          oRetryContext.GetCurrentDelay());
    2220           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2221           0 :                 bRetry = true;
    2222             :             }
    2223             :             else
    2224             :             {
    2225           0 :                 CPLDebug(GetDebugKey(), "%s",
    2226           0 :                          requestHelper.sWriteFuncData.pBuffer
    2227             :                              ? requestHelper.sWriteFuncData.pBuffer
    2228             :                              : "(null)");
    2229           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2230             :                          "PutBlock(%d) of %s failed", nPartNumber,
    2231             :                          osFilename.c_str());
    2232           0 :                 osBlockId.clear();
    2233             :             }
    2234             :         }
    2235             : 
    2236           7 :         curl_easy_cleanup(hCurlHandle);
    2237             :     } while (bRetry);
    2238             : 
    2239          12 :     return osBlockId;
    2240             : }
    2241             : 
    2242             : /************************************************************************/
    2243             : /*                           PutBlockList()                             */
    2244             : /************************************************************************/
    2245             : 
    2246           3 : bool VSIAzureFSHandler::PutBlockList(
    2247             :     const std::string &osFilename, const std::vector<std::string> &aosBlockIds,
    2248             :     IVSIS3LikeHandleHelper *poS3HandleHelper,
    2249             :     const CPLHTTPRetryParameters &oRetryParameters)
    2250             : {
    2251           3 :     bool bSuccess = true;
    2252             : 
    2253           6 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2254           6 :     NetworkStatisticsFile oContextFile(osFilename.c_str());
    2255           6 :     NetworkStatisticsAction oContextAction("PutBlockList");
    2256             : 
    2257             :     std::string osXML =
    2258           6 :         "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<BlockList>\n";
    2259           9 :     for (const auto &osBlockId : aosBlockIds)
    2260             :     {
    2261           6 :         osXML += "<Latest>" + osBlockId + "</Latest>\n";
    2262             :     }
    2263           3 :     osXML += "</BlockList>\n";
    2264             : 
    2265             :     const std::string osContentLength(
    2266           6 :         CPLSPrintf("Content-Length: %d", static_cast<int>(osXML.size())));
    2267             : 
    2268             :     const CPLStringList aosHTTPOptions(
    2269           6 :         CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
    2270             : 
    2271           3 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
    2272             :     bool bRetry;
    2273           3 :     do
    2274             :     {
    2275           3 :         bRetry = false;
    2276             : 
    2277           3 :         poS3HandleHelper->AddQueryParameter("comp", "blocklist");
    2278             : 
    2279           3 :         PutData putData;
    2280           3 :         putData.pabyData = reinterpret_cast<const GByte *>(osXML.data());
    2281           3 :         putData.nOff = 0;
    2282           3 :         putData.nTotalSize = osXML.size();
    2283             : 
    2284           3 :         CURL *hCurlHandle = curl_easy_init();
    2285           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
    2286           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
    2287             :                                    PutData::ReadCallBackBuffer);
    2288           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
    2289           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
    2290             :                                    static_cast<int>(osXML.size()));
    2291           3 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
    2292             : 
    2293             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
    2294           3 :             CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
    2295             :                               aosHTTPOptions.List()));
    2296           3 :         headers = curl_slist_append(headers, osContentLength.c_str());
    2297           3 :         headers = VSICurlMergeHeaders(
    2298             :             headers, poS3HandleHelper->GetCurlHeaders(
    2299           3 :                          "PUT", headers, osXML.c_str(), osXML.size()));
    2300             : 
    2301           6 :         CurlRequestHelper requestHelper;
    2302             :         const long response_code =
    2303           3 :             requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
    2304             : 
    2305           3 :         NetworkStatisticsLogger::LogPUT(osXML.size());
    2306             : 
    2307           3 :         if (response_code != 201)
    2308             :         {
    2309             :             // Look if we should attempt a retry
    2310           0 :             if (oRetryContext.CanRetry(
    2311             :                     static_cast<int>(response_code),
    2312           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
    2313             :                     requestHelper.szCurlErrBuf))
    2314             :             {
    2315           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2316             :                          "HTTP error code: %d - %s. "
    2317             :                          "Retrying again in %.1f secs",
    2318             :                          static_cast<int>(response_code),
    2319           0 :                          poS3HandleHelper->GetURL().c_str(),
    2320             :                          oRetryContext.GetCurrentDelay());
    2321           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
    2322           0 :                 bRetry = true;
    2323             :             }
    2324             :             else
    2325             :             {
    2326           0 :                 CPLDebug(GetDebugKey(), "%s",
    2327           0 :                          requestHelper.sWriteFuncData.pBuffer
    2328             :                              ? requestHelper.sWriteFuncData.pBuffer
    2329             :                              : "(null)");
    2330           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2331             :                          "PutBlockList of %s  failed", osFilename.c_str());
    2332           0 :                 bSuccess = false;
    2333             :             }
    2334             :         }
    2335             : 
    2336           3 :         curl_easy_cleanup(hCurlHandle);
    2337             :     } while (bRetry);
    2338             : 
    2339           6 :     return bSuccess;
    2340             : }
    2341             : 
    2342             : /************************************************************************/
    2343             : /*                           GetFileList()                              */
    2344             : /************************************************************************/
    2345             : 
    2346          21 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
    2347             :                                       bool *pbGotFileList)
    2348             : {
    2349          21 :     return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
    2350             : }
    2351             : 
    2352          34 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
    2353             :                                       bool bCacheEntries, bool *pbGotFileList)
    2354             : {
    2355             :     if (ENABLE_DEBUG)
    2356             :         CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
    2357             : 
    2358          34 :     *pbGotFileList = false;
    2359             : 
    2360             :     char **papszOptions =
    2361          34 :         CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
    2362          34 :     papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
    2363             :                                    bCacheEntries ? "YES" : "NO");
    2364          34 :     auto dir = OpenDir(pszDirname, 0, papszOptions);
    2365          34 :     CSLDestroy(papszOptions);
    2366          34 :     if (!dir)
    2367             :     {
    2368          12 :         return nullptr;
    2369             :     }
    2370          44 :     CPLStringList aosFileList;
    2371             :     while (true)
    2372             :     {
    2373          35 :         auto entry = dir->NextDirEntry();
    2374          35 :         if (!entry)
    2375             :         {
    2376          18 :             break;
    2377             :         }
    2378          17 :         aosFileList.AddString(entry->pszName);
    2379             : 
    2380          17 :         if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
    2381           4 :             break;
    2382          13 :     }
    2383          22 :     delete dir;
    2384          22 :     *pbGotFileList = true;
    2385          22 :     return aosFileList.StealList();
    2386             : }
    2387             : 
    2388             : /************************************************************************/
    2389             : /*                           GetOptions()                               */
    2390             : /************************************************************************/
    2391             : 
    2392           2 : const char *VSIAzureFSHandler::GetOptions()
    2393             : {
    2394             :     static std::string osOptions(
    2395           2 :         std::string("<Options>") +
    2396             :         "  <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
    2397             :         "description='Connection string that contains account name and "
    2398             :         "secret key'/>"
    2399             :         "  <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
    2400             :         "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
    2401             :         "  <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
    2402             :         "description='Secret key'/>"
    2403             :         "  <Option name='AZURE_NO_SIGN_REQUEST' type='boolean' "
    2404             :         "description='Whether to disable signing of requests' default='NO'/>"
    2405             :         "  <Option name='VSIAZ_CHUNK_SIZE' type='int' "
    2406             :         "description='Size in MB for chunks of files that are uploaded' "
    2407           3 :         "default='4' min='1' max='4'/>" +
    2408           3 :         VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
    2409           2 :     return osOptions.c_str();
    2410             : }
    2411             : 
    2412             : /************************************************************************/
    2413             : /*                           GetSignedURL()                             */
    2414             : /************************************************************************/
    2415             : 
    2416           8 : char *VSIAzureFSHandler::GetSignedURL(const char *pszFilename,
    2417             :                                       CSLConstList papszOptions)
    2418             : {
    2419           8 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
    2420           0 :         return nullptr;
    2421             : 
    2422             :     VSIAzureBlobHandleHelper *poHandleHelper =
    2423          16 :         VSIAzureBlobHandleHelper::BuildFromURI(
    2424          24 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), nullptr,
    2425             :             papszOptions);
    2426           8 :     if (poHandleHelper == nullptr)
    2427             :     {
    2428           2 :         return nullptr;
    2429             :     }
    2430             : 
    2431          12 :     std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
    2432             : 
    2433           6 :     delete poHandleHelper;
    2434           6 :     return CPLStrdup(osRet.c_str());
    2435             : }
    2436             : 
    2437             : /************************************************************************/
    2438             : /*                            OpenDir()                                 */
    2439             : /************************************************************************/
    2440             : 
    2441          40 : VSIDIR *VSIAzureFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
    2442             :                                    const char *const *papszOptions)
    2443             : {
    2444          40 :     if (nRecurseDepth > 0)
    2445             :     {
    2446           0 :         return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
    2447           0 :                                              papszOptions);
    2448             :     }
    2449             : 
    2450          40 :     if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
    2451           0 :         return nullptr;
    2452             : 
    2453          80 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
    2454          80 :     NetworkStatisticsAction oContextAction("OpenDir");
    2455             : 
    2456         120 :     std::string osDirnameWithoutPrefix = pszPath + GetFSPrefix().size();
    2457          40 :     if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
    2458             :     {
    2459           0 :         osDirnameWithoutPrefix.pop_back();
    2460             :     }
    2461             : 
    2462          80 :     std::string osBucket(osDirnameWithoutPrefix);
    2463          80 :     std::string osObjectKey;
    2464          40 :     size_t nSlashPos = osDirnameWithoutPrefix.find('/');
    2465          40 :     if (nSlashPos != std::string::npos)
    2466             :     {
    2467          28 :         osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
    2468          28 :         osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
    2469             :     }
    2470             : 
    2471             :     auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
    2472          80 :         CreateHandleHelper(osBucket.c_str(), true));
    2473          40 :     if (poHandleHelper == nullptr)
    2474             :     {
    2475           0 :         return nullptr;
    2476             :     }
    2477             : 
    2478          40 :     VSIDIRAz *dir = new VSIDIRAz(this);
    2479          40 :     dir->nRecurseDepth = nRecurseDepth;
    2480          40 :     dir->poHandleHelper = std::move(poHandleHelper);
    2481          40 :     dir->osBucket = std::move(osBucket);
    2482          40 :     dir->osObjectKey = std::move(osObjectKey);
    2483          40 :     dir->nMaxFiles = atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
    2484          40 :     dir->bCacheEntries =
    2485          40 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
    2486          40 :     dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
    2487          40 :     dir->m_bSynthetizeMissingDirectories = CPLTestBool(CSLFetchNameValueDef(
    2488             :         papszOptions, "SYNTHETIZE_MISSING_DIRECTORIES", "NO"));
    2489          40 :     if (!dir->IssueListDir())
    2490             :     {
    2491          13 :         delete dir;
    2492          13 :         return nullptr;
    2493             :     }
    2494             : 
    2495          27 :     return dir;
    2496             : }
    2497             : 
    2498             : /************************************************************************/
    2499             : /*                           VSIAzureHandle()                           */
    2500             : /************************************************************************/
    2501             : 
    2502          47 : VSIAzureHandle::VSIAzureHandle(VSIAzureFSHandler *poFSIn,
    2503             :                                const char *pszFilename,
    2504          47 :                                VSIAzureBlobHandleHelper *poHandleHelper)
    2505          47 :     : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
    2506          94 :       m_poHandleHelper(poHandleHelper)
    2507             : {
    2508          47 :     m_osQueryString = poHandleHelper->GetSASQueryString();
    2509          47 : }
    2510             : 
    2511             : /************************************************************************/
    2512             : /*                          GetCurlHeaders()                            */
    2513             : /************************************************************************/
    2514             : 
    2515             : struct curl_slist *
    2516          37 : VSIAzureHandle::GetCurlHeaders(const std::string &osVerb,
    2517             :                                const struct curl_slist *psExistingHeaders)
    2518             : {
    2519          37 :     return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
    2520             : }
    2521             : 
    2522             : /************************************************************************/
    2523             : /*                         IsDirectoryFromExists()                      */
    2524             : /************************************************************************/
    2525             : 
    2526          23 : bool VSIAzureHandle::IsDirectoryFromExists(const char * /*pszVerb*/,
    2527             :                                            int response_code)
    2528             : {
    2529          23 :     if (response_code != 404)
    2530          10 :         return false;
    2531             : 
    2532          26 :     std::string osDirname(m_osFilename);
    2533          26 :     if (osDirname.size() > poFS->GetFSPrefix().size() &&
    2534          13 :         osDirname.back() == '/')
    2535          10 :         osDirname.pop_back();
    2536             :     bool bIsDir;
    2537          13 :     if (poFS->ExistsInCacheDirList(osDirname, &bIsDir))
    2538           0 :         return bIsDir;
    2539             : 
    2540          13 :     bool bGotFileList = false;
    2541             :     char **papszDirContent =
    2542          13 :         reinterpret_cast<VSIAzureFSHandler *>(poFS)->GetFileList(
    2543             :             osDirname.c_str(), 1, false, &bGotFileList);
    2544          13 :     CSLDestroy(papszDirContent);
    2545          13 :     return bGotFileList;
    2546             : }
    2547             : 
    2548             : } /* end of namespace cpl */
    2549             : 
    2550             : #endif  // DOXYGEN_SKIP
    2551             : //! @endcond
    2552             : 
    2553             : /************************************************************************/
    2554             : /*                      VSIInstallAzureFileHandler()                    */
    2555             : /************************************************************************/
    2556             : 
    2557             : /*!
    2558             :  \brief Install /vsiaz/ Microsoft Azure Blob file system handler
    2559             :  (requires libcurl)
    2560             : 
    2561             :  \verbatim embed:rst
    2562             :  See :ref:`/vsiaz/ documentation <vsiaz>`
    2563             :  \endverbatim
    2564             : 
    2565             :  @since GDAL 2.3
    2566             :  */
    2567             : 
    2568        1616 : void VSIInstallAzureFileHandler(void)
    2569             : {
    2570        1616 :     VSIFileManager::InstallHandler("/vsiaz/",
    2571        1616 :                                    new cpl::VSIAzureFSHandler("/vsiaz/"));
    2572        1616 : }
    2573             : 
    2574             : #endif /* HAVE_CURL */

Generated by: LCOV version 1.14