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

Generated by: LCOV version 1.14