LCOV - code coverage report
Current view: top level - port - cpl_vsil_az.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 987 1150 85.8 %
Date: 2025-01-18 12:42:00 Functions: 57 61 93.4 %

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

Generated by: LCOV version 1.14