LCOV - code coverage report
Current view: top level - port - cpl_vsil_az.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1003 1156 86.8 %
Date: 2026-03-04 01:45:47 Functions: 57 62 91.9 %

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

Generated by: LCOV version 1.14