LCOV - code coverage report
Current view: top level - port - cpl_vsil_az.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 982 1148 85.5 %
Date: 2025-07-09 17:50:03 Functions: 56 61 91.8 %

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

Generated by: LCOV version 1.14