LCOV - code coverage report
Current view: top level - port - cpl_vsil_az.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 991 1163 85.2 %
Date: 2024-04-29 01:40:10 Functions: 53 56 94.6 %

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

Generated by: LCOV version 1.14