LCOV - code coverage report
Current view: top level - port - cpl_azure.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 475 528 90.0 %
Date: 2025-07-09 17:50:03 Functions: 21 21 100.0 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  * Project:  CPL - Common Portability Library
       3             :  * Purpose:  Microsoft Azure Storage Blob routines
       4             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       5             :  *
       6             :  **********************************************************************
       7             :  * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
       8             :  *
       9             :  * Permission is hereby granted, free of charge, to any person obtaining a
      10             :  * copy of this software and associated documentation files (the "Software"),
      11             :  * to deal in the Software without restriction, including without limitation
      12             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      13             :  * and/or sell copies of the Software, and to permit persons to whom the
      14             :  * Software is furnished to do so, subject to the following conditions:
      15             :  *
      16             :  * The above copyright notice and this permission notice shall be included
      17             :  * in all copies or substantial portions of the Software.
      18             :  *
      19             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      20             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      21             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      22             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      23             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      24             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      25             :  * DEALINAzureBlob IN THE SOFTWARE.
      26             :  ****************************************************************************/
      27             : 
      28             : #include "cpl_azure.h"
      29             : #include "cpl_json.h"
      30             : #include "cpl_minixml.h"
      31             : #include "cpl_vsi_error.h"
      32             : #include "cpl_sha256.h"
      33             : #include "cpl_time.h"
      34             : #include "cpl_http.h"
      35             : #include "cpl_multiproc.h"
      36             : #include "cpl_vsi_virtual.h"
      37             : 
      38             : #include <mutex>
      39             : 
      40             : //! @cond Doxygen_Suppress
      41             : 
      42             : #ifdef HAVE_CURL
      43             : 
      44             : /************************************************************************/
      45             : /*                      RemoveTrailingSlash()                           */
      46             : /************************************************************************/
      47             : 
      48         572 : static std::string RemoveTrailingSlash(const std::string &osStr)
      49             : {
      50         572 :     std::string osRet(osStr);
      51         572 :     if (!osRet.empty() && osRet.back() == '/')
      52           1 :         osRet.pop_back();
      53         572 :     return osRet;
      54             : }
      55             : 
      56             : /************************************************************************/
      57             : /*                     CPLAzureGetSignature()                           */
      58             : /************************************************************************/
      59             : 
      60         218 : static std::string CPLAzureGetSignature(const std::string &osStringToSign,
      61             :                                         const std::string &osStorageKeyB64)
      62             : {
      63             : 
      64             :     /* -------------------------------------------------------------------- */
      65             :     /*      Compute signature.                                              */
      66             :     /* -------------------------------------------------------------------- */
      67             : 
      68         436 :     std::string osStorageKeyUnbase64(osStorageKeyB64);
      69         436 :     int nB64Length = CPLBase64DecodeInPlace(
      70         218 :         reinterpret_cast<GByte *>(&osStorageKeyUnbase64[0]));
      71         218 :     osStorageKeyUnbase64.resize(nB64Length);
      72             : #ifdef DEBUG_VERBOSE
      73             :     CPLDebug("AZURE", "signing key size: %d", nB64Length);
      74             : #endif
      75             : 
      76         218 :     GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
      77         436 :     CPL_HMAC_SHA256(osStorageKeyUnbase64.c_str(), nB64Length,
      78         218 :                     osStringToSign.c_str(), osStringToSign.size(),
      79             :                     abySignature);
      80             : 
      81         218 :     char *pszB64Signature = CPLBase64Encode(CPL_SHA256_HASH_SIZE, abySignature);
      82         218 :     std::string osSignature(pszB64Signature);
      83         218 :     CPLFree(pszB64Signature);
      84         436 :     return osSignature;
      85             : }
      86             : 
      87             : /************************************************************************/
      88             : /*                          GetAzureBlobHeaders()                       */
      89             : /************************************************************************/
      90             : 
      91         223 : static struct curl_slist *GetAzureBlobHeaders(
      92             :     const std::string &osVerb, const struct curl_slist *psExistingHeaders,
      93             :     const std::string &osResource,
      94             :     const std::map<std::string, std::string> &oMapQueryParameters,
      95             :     const std::string &osStorageAccount, const std::string &osStorageKeyB64,
      96             :     bool bIncludeMSVersion)
      97             : {
      98             :     /* See
      99             :      * https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services
     100             :      */
     101             : 
     102         446 :     std::string osDate = CPLGetConfigOption("CPL_AZURE_TIMESTAMP", "");
     103         223 :     if (osDate.empty())
     104             :     {
     105           8 :         osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
     106             :     }
     107         223 :     if (osStorageKeyB64.empty())
     108             :     {
     109           9 :         struct curl_slist *headers = nullptr;
     110           9 :         headers = curl_slist_append(
     111             :             headers, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
     112           9 :         return headers;
     113             :     }
     114             : 
     115         428 :     std::string osMsVersion("2019-12-12");
     116         428 :     std::map<std::string, std::string> oSortedMapMSHeaders;
     117         214 :     if (bIncludeMSVersion)
     118         207 :         oSortedMapMSHeaders["x-ms-version"] = osMsVersion;
     119         214 :     oSortedMapMSHeaders["x-ms-date"] = osDate;
     120             :     std::string osCanonicalizedHeaders(
     121             :         IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
     122         428 :             oSortedMapMSHeaders, psExistingHeaders, "x-ms-"));
     123             : 
     124         428 :     std::string osCanonicalizedResource;
     125         214 :     osCanonicalizedResource += "/" + osStorageAccount;
     126         214 :     osCanonicalizedResource += osResource;
     127             : 
     128             :     // We assume query parameters are in lower case and they are not repeated
     129             :     std::map<std::string, std::string>::const_iterator oIter =
     130         214 :         oMapQueryParameters.begin();
     131         546 :     for (; oIter != oMapQueryParameters.end(); ++oIter)
     132             :     {
     133         332 :         osCanonicalizedResource += "\n";
     134         332 :         osCanonicalizedResource += oIter->first;
     135         332 :         osCanonicalizedResource += ":";
     136         332 :         osCanonicalizedResource += oIter->second;
     137             :     }
     138             : 
     139         428 :     std::string osStringToSign;
     140         214 :     osStringToSign += osVerb + "\n";
     141             :     osStringToSign +=
     142         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-Encoding") + "\n";
     143             :     osStringToSign +=
     144         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-Language") + "\n";
     145             :     std::string osContentLength(
     146         428 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-Length"));
     147         214 :     if (osContentLength == "0")
     148          34 :         osContentLength.clear();  // since x-ms-version 2015-02-21
     149         214 :     osStringToSign += osContentLength + "\n";
     150             :     osStringToSign +=
     151         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-MD5") + "\n";
     152             :     osStringToSign +=
     153         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-Type") + "\n";
     154         214 :     osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Date") + "\n";
     155             :     osStringToSign +=
     156         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "If-Modified-Since") + "\n";
     157         214 :     osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "If-Match") + "\n";
     158             :     osStringToSign +=
     159         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "If-None-Match") + "\n";
     160             :     osStringToSign +=
     161         214 :         CPLAWSGetHeaderVal(psExistingHeaders, "If-Unmodified-Since") + "\n";
     162         214 :     osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Range") + "\n";
     163         214 :     osStringToSign += osCanonicalizedHeaders;
     164         214 :     osStringToSign += osCanonicalizedResource;
     165             : 
     166             : #ifdef DEBUG_VERBOSE
     167             :     CPLDebug("AZURE", "osStringToSign = '%s'", osStringToSign.c_str());
     168             : #endif
     169             : 
     170             :     /* -------------------------------------------------------------------- */
     171             :     /*      Compute signature.                                              */
     172             :     /* -------------------------------------------------------------------- */
     173             : 
     174             :     std::string osAuthorization(
     175         428 :         "SharedKey " + osStorageAccount + ":" +
     176         428 :         CPLAzureGetSignature(osStringToSign, osStorageKeyB64));
     177             : 
     178         214 :     struct curl_slist *headers = nullptr;
     179             :     headers =
     180         214 :         curl_slist_append(headers, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
     181         214 :     if (bIncludeMSVersion)
     182             :     {
     183         207 :         headers = curl_slist_append(
     184             :             headers, CPLSPrintf("x-ms-version: %s", osMsVersion.c_str()));
     185             :     }
     186         214 :     headers = curl_slist_append(
     187             :         headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
     188         214 :     return headers;
     189             : }
     190             : 
     191             : /************************************************************************/
     192             : /*                     VSIAzureBlobHandleHelper()                       */
     193             : /************************************************************************/
     194         298 : VSIAzureBlobHandleHelper::VSIAzureBlobHandleHelper(
     195             :     const std::string &osPathForOption, const std::string &osEndpoint,
     196             :     const std::string &osBucket, const std::string &osObjectKey,
     197             :     const std::string &osStorageAccount, const std::string &osStorageKey,
     198             :     const std::string &osSAS, const std::string &osAccessToken,
     199         298 :     bool bFromManagedIdentities)
     200             :     : m_osPathForOption(osPathForOption),
     201             :       m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, osSAS)),
     202             :       m_osEndpoint(osEndpoint), m_osBucket(osBucket),
     203             :       m_osObjectKey(osObjectKey), m_osStorageAccount(osStorageAccount),
     204             :       m_osStorageKey(osStorageKey), m_osSAS(osSAS),
     205             :       m_osAccessToken(osAccessToken),
     206         298 :       m_bFromManagedIdentities(bFromManagedIdentities)
     207             : {
     208         298 : }
     209             : 
     210             : /************************************************************************/
     211             : /*                     ~VSIAzureBlobHandleHelper()                      */
     212             : /************************************************************************/
     213             : 
     214         596 : VSIAzureBlobHandleHelper::~VSIAzureBlobHandleHelper()
     215             : {
     216         596 : }
     217             : 
     218             : /************************************************************************/
     219             : /*                       AzureCSGetParameter()                          */
     220             : /************************************************************************/
     221             : 
     222        1067 : static std::string AzureCSGetParameter(const std::string &osStr,
     223             :                                        const char *pszKey, bool bErrorIfMissing)
     224             : {
     225        3201 :     std::string osKey(pszKey + std::string("="));
     226        1067 :     size_t nPos = osStr.find(osKey);
     227        1067 :     if (nPos == std::string::npos)
     228             :     {
     229             :         const char *pszMsg =
     230          11 :             CPLSPrintf("%s missing in AZURE_STORAGE_CONNECTION_STRING", pszKey);
     231          11 :         if (bErrorIfMissing)
     232             :         {
     233           0 :             CPLDebug("AZURE", "%s", pszMsg);
     234           0 :             VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
     235             :         }
     236          11 :         return std::string();
     237             :     }
     238        1056 :     size_t nPos2 = osStr.find(";", nPos);
     239        1056 :     return osStr.substr(nPos + osKey.size(), nPos2 == std::string::npos
     240             :                                                  ? nPos2
     241        2112 :                                                  : nPos2 - nPos - osKey.size());
     242             : }
     243             : 
     244             : /************************************************************************/
     245             : /*                         CPLAzureCachedToken                          */
     246             : /************************************************************************/
     247             : 
     248             : std::mutex gMutex;
     249             : 
     250             : struct CPLAzureCachedToken
     251             : {
     252             :     std::string osAccessToken{};
     253             :     GIntBig nExpiresOn = 0;
     254             : };
     255             : 
     256             : static std::map<std::string, CPLAzureCachedToken> goMapIMDSURLToCachedToken;
     257             : 
     258             : /************************************************************************/
     259             : /*                GetConfigurationFromIMDSCredentials()                 */
     260             : /************************************************************************/
     261             : 
     262             : static bool
     263          15 : GetConfigurationFromIMDSCredentials(const std::string &osPathForOption,
     264             :                                     std::string &osAccessToken)
     265             : {
     266             :     // coverity[tainted_data]
     267             :     const std::string osRootURL(CPLGetConfigOption("CPL_AZURE_VM_API_ROOT_URL",
     268          30 :                                                    "http://169.254.169.254"));
     269          15 :     if (osRootURL == "disabled")
     270           1 :         return false;
     271             : 
     272             :     std::string osURLResource("/metadata/identity/oauth2/"
     273             :                               "token?api-version=2018-02-01&resource=https%"
     274          28 :                               "3A%2F%2Fstorage.azure.com%2F");
     275          14 :     const char *pszObjectId = VSIGetPathSpecificOption(
     276             :         osPathForOption.c_str(), "AZURE_IMDS_OBJECT_ID", nullptr);
     277          14 :     if (pszObjectId)
     278           8 :         osURLResource += "&object_id=" + CPLAWSURLEncode(pszObjectId, false);
     279          14 :     const char *pszClientId = VSIGetPathSpecificOption(
     280             :         osPathForOption.c_str(), "AZURE_IMDS_CLIENT_ID", nullptr);
     281          14 :     if (pszClientId)
     282           8 :         osURLResource += "&client_id=" + CPLAWSURLEncode(pszClientId, false);
     283          14 :     const char *pszMsiResId = VSIGetPathSpecificOption(
     284             :         osPathForOption.c_str(), "AZURE_IMDS_MSI_RES_ID", nullptr);
     285          14 :     if (pszMsiResId)
     286           8 :         osURLResource += "&msi_res_id=" + CPLAWSURLEncode(pszMsiResId, false);
     287             : 
     288          28 :     std::lock_guard<std::mutex> guard(gMutex);
     289             : 
     290             :     // Look for cached token corresponding to this IMDS request URL
     291          14 :     auto oIter = goMapIMDSURLToCachedToken.find(osURLResource);
     292          14 :     if (oIter != goMapIMDSURLToCachedToken.end())
     293             :     {
     294          10 :         const auto &oCachedToken = oIter->second;
     295             :         time_t nCurTime;
     296          10 :         time(&nCurTime);
     297             :         // Try to reuse credentials if they are still valid, but
     298             :         // keep one minute of margin...
     299          10 :         if (nCurTime < oCachedToken.nExpiresOn - 60)
     300             :         {
     301           9 :             osAccessToken = oCachedToken.osAccessToken;
     302           9 :             return true;
     303             :         }
     304             :     }
     305             : 
     306             :     // Fetch credentials
     307           5 :     CPLStringList oResponse;
     308           5 :     const char *const apszOptions[] = {"HEADERS=Metadata: true", nullptr};
     309             :     CPLHTTPResult *psResult =
     310           5 :         CPLHTTPFetch((osRootURL + osURLResource).c_str(), apszOptions);
     311           5 :     if (psResult)
     312             :     {
     313           5 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     314             :         {
     315             :             const std::string osJSon =
     316          10 :                 reinterpret_cast<char *>(psResult->pabyData);
     317           5 :             oResponse = CPLParseKeyValueJson(osJSon.c_str());
     318           5 :             if (oResponse.FetchNameValue("error"))
     319             :             {
     320           0 :                 CPLDebug("AZURE",
     321             :                          "Cannot retrieve managed identities credentials: %s",
     322             :                          osJSon.c_str());
     323             :             }
     324             :         }
     325           5 :         CPLHTTPDestroyResult(psResult);
     326             :     }
     327           5 :     osAccessToken = oResponse.FetchNameValueDef("access_token", "");
     328             :     const GIntBig nExpiresOn =
     329           5 :         CPLAtoGIntBig(oResponse.FetchNameValueDef("expires_on", ""));
     330           5 :     if (!osAccessToken.empty() && nExpiresOn > 0)
     331             :     {
     332          10 :         CPLAzureCachedToken cachedToken;
     333           5 :         cachedToken.osAccessToken = osAccessToken;
     334           5 :         cachedToken.nExpiresOn = nExpiresOn;
     335           5 :         goMapIMDSURLToCachedToken[osURLResource] = std::move(cachedToken);
     336           5 :         CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
     337             :                  osURLResource.c_str(), nExpiresOn);
     338             :     }
     339             : 
     340           5 :     return !osAccessToken.empty();
     341             : }
     342             : 
     343             : /************************************************************************/
     344             : /*                 GetConfigurationFromWorkloadIdentity()               */
     345             : /************************************************************************/
     346             : 
     347             : // Last timestamp AZURE_FEDERATED_TOKEN_FILE was read
     348             : static GIntBig gnLastReadFederatedTokenFile = 0;
     349             : static std::string gosFederatedToken{};
     350             : 
     351             : // Azure Active Directory Workload Identity, typically for Azure Kubernetes
     352             : // Cf https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/identity/azure-identity/azure/identity/_credentials/workload_identity.py
     353          23 : static bool GetConfigurationFromWorkloadIdentity(std::string &osAccessToken)
     354             : {
     355             :     const std::string AZURE_CLIENT_ID(
     356          46 :         CPLGetConfigOption("AZURE_CLIENT_ID", ""));
     357             :     const std::string AZURE_TENANT_ID(
     358          46 :         CPLGetConfigOption("AZURE_TENANT_ID", ""));
     359             :     const std::string AZURE_AUTHORITY_HOST(
     360          46 :         CPLGetConfigOption("AZURE_AUTHORITY_HOST", ""));
     361             :     const std::string AZURE_FEDERATED_TOKEN_FILE(
     362          46 :         CPLGetConfigOption("AZURE_FEDERATED_TOKEN_FILE", ""));
     363          39 :     if (AZURE_CLIENT_ID.empty() || AZURE_TENANT_ID.empty() ||
     364          39 :         AZURE_AUTHORITY_HOST.empty() || AZURE_FEDERATED_TOKEN_FILE.empty())
     365             :     {
     366          15 :         return false;
     367             :     }
     368             : 
     369          16 :     std::lock_guard<std::mutex> guard(gMutex);
     370             : 
     371             :     time_t nCurTime;
     372           8 :     time(&nCurTime);
     373             : 
     374             :     // Look for cached token corresponding to this request URL
     375           8 :     const std::string osURL(AZURE_AUTHORITY_HOST + AZURE_TENANT_ID +
     376          16 :                             "/oauth2/v2.0/token");
     377           8 :     auto oIter = goMapIMDSURLToCachedToken.find(osURL);
     378           8 :     if (oIter != goMapIMDSURLToCachedToken.end())
     379             :     {
     380           6 :         const auto &oCachedToken = oIter->second;
     381             :         // Try to reuse credentials if they are still valid, but
     382             :         // keep one minute of margin...
     383           6 :         if (nCurTime < oCachedToken.nExpiresOn - 60)
     384             :         {
     385           4 :             osAccessToken = oCachedToken.osAccessToken;
     386           4 :             return true;
     387             :         }
     388             :     }
     389             : 
     390             :     // Ingest content of AZURE_FEDERATED_TOKEN_FILE if last time was more than
     391             :     // 600 seconds.
     392           4 :     if (nCurTime - gnLastReadFederatedTokenFile > 600)
     393             :     {
     394             :         auto fp = VSIVirtualHandleUniquePtr(
     395           2 :             VSIFOpenL(AZURE_FEDERATED_TOKEN_FILE.c_str(), "rb"));
     396           2 :         if (!fp)
     397             :         {
     398           0 :             CPLDebug("AZURE", "Cannot open AZURE_FEDERATED_TOKEN_FILE = %s",
     399             :                      AZURE_FEDERATED_TOKEN_FILE.c_str());
     400           0 :             return false;
     401             :         }
     402           2 :         fp->Seek(0, SEEK_END);
     403           2 :         const auto nSize = fp->Tell();
     404           2 :         if (nSize == 0 || nSize > 100 * 1024)
     405             :         {
     406           0 :             CPLDebug(
     407             :                 "AZURE",
     408             :                 "Invalid size for AZURE_FEDERATED_TOKEN_FILE = " CPL_FRMT_GUIB,
     409             :                 static_cast<GUIntBig>(nSize));
     410           0 :             return false;
     411             :         }
     412           2 :         fp->Seek(0, SEEK_SET);
     413           2 :         gosFederatedToken.resize(static_cast<size_t>(nSize));
     414           2 :         if (fp->Read(&gosFederatedToken[0], gosFederatedToken.size(), 1) != 1)
     415             :         {
     416           0 :             CPLDebug("AZURE", "Cannot read AZURE_FEDERATED_TOKEN_FILE");
     417           0 :             return false;
     418             :         }
     419           2 :         gnLastReadFederatedTokenFile = nCurTime;
     420             :     }
     421             : 
     422             :     /* -------------------------------------------------------------------- */
     423             :     /*      Prepare POST request.                                           */
     424             :     /* -------------------------------------------------------------------- */
     425           8 :     CPLStringList aosOptions;
     426             : 
     427             :     aosOptions.AddString(
     428           4 :         "HEADERS=Content-Type: application/x-www-form-urlencoded");
     429             : 
     430           8 :     std::string osItem("POSTFIELDS=client_assertion=");
     431           4 :     osItem += CPLAWSURLEncode(gosFederatedToken);
     432             :     osItem += "&client_assertion_type=urn:ietf:params:oauth:client-assertion-"
     433           4 :               "type:jwt-bearer";
     434           4 :     osItem += "&client_id=";
     435           4 :     osItem += CPLAWSURLEncode(AZURE_CLIENT_ID);
     436           4 :     osItem += "&grant_type=client_credentials";
     437           4 :     osItem += "&scope=https://storage.azure.com/.default";
     438           4 :     aosOptions.AddString(osItem.c_str());
     439             : 
     440             :     /* -------------------------------------------------------------------- */
     441             :     /*      Submit request by HTTP.                                         */
     442             :     /* -------------------------------------------------------------------- */
     443           4 :     CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
     444           4 :     if (!psResult)
     445           0 :         return false;
     446             : 
     447           4 :     if (!psResult->pabyData || psResult->pszErrBuf)
     448             :     {
     449           0 :         if (psResult->pszErrBuf)
     450           0 :             CPLDebug("AZURE", "%s", psResult->pszErrBuf);
     451           0 :         if (psResult->pabyData)
     452           0 :             CPLDebug("AZURE", "%s", psResult->pabyData);
     453             : 
     454           0 :         CPLDebug("AZURE",
     455             :                  "Fetching OAuth2 access code from workload identity failed.");
     456           0 :         CPLHTTPDestroyResult(psResult);
     457           0 :         return false;
     458             :     }
     459             : 
     460             :     CPLStringList oResponse =
     461           4 :         CPLParseKeyValueJson(reinterpret_cast<char *>(psResult->pabyData));
     462           4 :     CPLHTTPDestroyResult(psResult);
     463             : 
     464           4 :     osAccessToken = oResponse.FetchNameValueDef("access_token", "");
     465           4 :     const int nExpiresIn = atoi(oResponse.FetchNameValueDef("expires_in", ""));
     466           4 :     if (!osAccessToken.empty() && nExpiresIn > 0)
     467             :     {
     468           8 :         CPLAzureCachedToken cachedToken;
     469           4 :         cachedToken.osAccessToken = osAccessToken;
     470           4 :         cachedToken.nExpiresOn = nCurTime + nExpiresIn;
     471           4 :         goMapIMDSURLToCachedToken[osURL] = cachedToken;
     472           4 :         CPLDebug("AZURE", "Storing credentials for %s until " CPL_FRMT_GIB,
     473             :                  osURL.c_str(), cachedToken.nExpiresOn);
     474             :     }
     475             : 
     476           4 :     return !osAccessToken.empty();
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                GetConfigurationFromManagedIdentities()               */
     481             : /************************************************************************/
     482             : 
     483             : static bool
     484          23 : GetConfigurationFromManagedIdentities(const std::string &osPathForOption,
     485             :                                       std::string &osAccessToken)
     486             : {
     487          23 :     if (GetConfigurationFromWorkloadIdentity(osAccessToken))
     488           8 :         return true;
     489          15 :     return GetConfigurationFromIMDSCredentials(osPathForOption, osAccessToken);
     490             : }
     491             : 
     492             : /************************************************************************/
     493             : /*                             ClearCache()                             */
     494             : /************************************************************************/
     495             : 
     496         654 : void VSIAzureBlobHandleHelper::ClearCache()
     497             : {
     498        1308 :     std::lock_guard<std::mutex> guard(gMutex);
     499         654 :     goMapIMDSURLToCachedToken.clear();
     500         654 :     gnLastReadFederatedTokenFile = 0;
     501         654 :     gosFederatedToken.clear();
     502         654 : }
     503             : 
     504             : /************************************************************************/
     505             : /*                    ParseStorageConnectionString()                    */
     506             : /************************************************************************/
     507             : 
     508             : static bool
     509         266 : ParseStorageConnectionString(const std::string &osStorageConnectionString,
     510             :                              const std::string &osServicePrefix,
     511             :                              bool &bUseHTTPS, std::string &osEndpoint,
     512             :                              std::string &osStorageAccount,
     513             :                              std::string &osStorageKey, std::string &osSAS)
     514             : {
     515             :     osStorageAccount =
     516         266 :         AzureCSGetParameter(osStorageConnectionString, "AccountName", false);
     517             :     osStorageKey =
     518         266 :         AzureCSGetParameter(osStorageConnectionString, "AccountKey", false);
     519             : 
     520             :     const std::string osProtocol(AzureCSGetParameter(
     521         532 :         osStorageConnectionString, "DefaultEndpointsProtocol", false));
     522         266 :     bUseHTTPS = (osProtocol != "http");
     523             : 
     524         266 :     if (osStorageAccount.empty() || osStorageKey.empty())
     525             :     {
     526           3 :         osStorageAccount.clear();
     527           3 :         osStorageKey.clear();
     528             : 
     529           3 :         std::string osBlobEndpoint = RemoveTrailingSlash(AzureCSGetParameter(
     530           6 :             osStorageConnectionString, "BlobEndpoint", false));
     531           6 :         osSAS = AzureCSGetParameter(osStorageConnectionString,
     532           3 :                                     "SharedAccessSignature", false);
     533           3 :         if (!osBlobEndpoint.empty() && !osSAS.empty())
     534             :         {
     535           2 :             osEndpoint = std::move(osBlobEndpoint);
     536           2 :             return true;
     537             :         }
     538             : 
     539           1 :         return false;
     540             :     }
     541             : 
     542             :     const std::string osBlobEndpoint =
     543         263 :         AzureCSGetParameter(osStorageConnectionString, "BlobEndpoint", false);
     544         263 :     if (!osBlobEndpoint.empty())
     545             :     {
     546         263 :         osEndpoint = RemoveTrailingSlash(osBlobEndpoint);
     547             :     }
     548             :     else
     549             :     {
     550             :         const std::string osEndpointSuffix(AzureCSGetParameter(
     551           0 :             osStorageConnectionString, "EndpointSuffix", false));
     552           0 :         if (!osEndpointSuffix.empty())
     553           0 :             osEndpoint = (bUseHTTPS ? "https://" : "http://") +
     554           0 :                          osStorageAccount + "." + osServicePrefix + "." +
     555           0 :                          RemoveTrailingSlash(osEndpointSuffix);
     556             :     }
     557             : 
     558         263 :     return true;
     559             : }
     560             : 
     561             : /************************************************************************/
     562             : /*                 GetConfigurationFromCLIConfigFile()                  */
     563             : /************************************************************************/
     564             : 
     565           9 : static bool GetConfigurationFromCLIConfigFile(
     566             :     const std::string &osPathForOption, const std::string &osServicePrefix,
     567             :     bool &bUseHTTPS, std::string &osEndpoint, std::string &osStorageAccount,
     568             :     std::string &osStorageKey, std::string &osSAS, std::string &osAccessToken,
     569             :     bool &bFromManagedIdentities)
     570             : {
     571             : #ifdef _WIN32
     572             :     const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
     573             :     constexpr char SEP_STRING[] = "\\";
     574             : #else
     575           9 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
     576           9 :     constexpr char SEP_STRING[] = "/";
     577             : #endif
     578             : 
     579          18 :     std::string osDotAzure(pszHome ? pszHome : "");
     580           9 :     osDotAzure += SEP_STRING;
     581           9 :     osDotAzure += ".azure";
     582             : 
     583             :     const char *pszAzureConfigDir =
     584           9 :         CPLGetConfigOption("AZURE_CONFIG_DIR", osDotAzure.c_str());
     585           9 :     if (pszAzureConfigDir[0] == '\0')
     586           5 :         return false;
     587             : 
     588           8 :     std::string osConfigFilename = pszAzureConfigDir;
     589           4 :     osConfigFilename += SEP_STRING;
     590           4 :     osConfigFilename += "config";
     591             : 
     592           4 :     VSILFILE *fp = VSIFOpenL(osConfigFilename.c_str(), "rb");
     593           8 :     std::string osStorageConnectionString;
     594           4 :     if (fp == nullptr)
     595           0 :         return false;
     596             : 
     597           4 :     bool bInStorageSection = false;
     598          25 :     while (const char *pszLine = CPLReadLineL(fp))
     599             :     {
     600          21 :         if (pszLine[0] == '#' || pszLine[0] == ';')
     601             :         {
     602             :             // comment line
     603             :         }
     604          21 :         else if (strcmp(pszLine, "[storage]") == 0)
     605             :         {
     606           4 :             bInStorageSection = true;
     607             :         }
     608          17 :         else if (pszLine[0] == '[')
     609             :         {
     610           4 :             bInStorageSection = false;
     611             :         }
     612          13 :         else if (bInStorageSection)
     613             :         {
     614           5 :             char *pszKey = nullptr;
     615           5 :             const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
     616           5 :             if (pszKey && pszValue)
     617             :             {
     618           5 :                 if (EQUAL(pszKey, "account"))
     619             :                 {
     620           2 :                     osStorageAccount = pszValue;
     621             :                 }
     622           3 :                 else if (EQUAL(pszKey, "connection_string"))
     623             :                 {
     624           1 :                     osStorageConnectionString = pszValue;
     625             :                 }
     626           2 :                 else if (EQUAL(pszKey, "key"))
     627             :                 {
     628           1 :                     osStorageKey = pszValue;
     629             :                 }
     630           1 :                 else if (EQUAL(pszKey, "sas_token"))
     631             :                 {
     632           1 :                     osSAS = pszValue;
     633             :                     // Az CLI apparently uses configparser with
     634             :                     // BasicInterpolation where the % character has a special
     635             :                     // meaning See
     636             :                     // https://docs.python.org/3/library/configparser.html#configparser.BasicInterpolation
     637             :                     // A token might end with %%3D which must be transformed to
     638             :                     // %3D
     639           1 :                     osSAS = CPLString(osSAS).replaceAll("%%", '%');
     640             :                 }
     641             :             }
     642           5 :             CPLFree(pszKey);
     643             :         }
     644          21 :     }
     645           4 :     VSIFCloseL(fp);
     646             : 
     647           4 :     if (!osStorageConnectionString.empty())
     648             :     {
     649           1 :         return ParseStorageConnectionString(
     650             :             osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
     651           1 :             osStorageAccount, osStorageKey, osSAS);
     652             :     }
     653             : 
     654           3 :     if (osStorageAccount.empty())
     655             :     {
     656           1 :         CPLDebug("AZURE", "Missing storage.account in %s",
     657             :                  osConfigFilename.c_str());
     658           1 :         return false;
     659             :     }
     660             : 
     661           2 :     if (osEndpoint.empty())
     662           0 :         osEndpoint = (bUseHTTPS ? "https://" : "http://") + osStorageAccount +
     663           0 :                      "." + osServicePrefix + ".core.windows.net";
     664             : 
     665           2 :     osAccessToken = CPLGetConfigOption("AZURE_STORAGE_ACCESS_TOKEN", "");
     666           2 :     if (!osAccessToken.empty())
     667           0 :         return true;
     668             : 
     669           2 :     if (osStorageKey.empty() && osSAS.empty())
     670             :     {
     671           0 :         if (CPLTestBool(CPLGetConfigOption("AZURE_NO_SIGN_REQUEST", "NO")))
     672             :         {
     673           0 :             return true;
     674             :         }
     675             : 
     676           0 :         std::string osTmpAccessToken;
     677           0 :         if (GetConfigurationFromManagedIdentities(osPathForOption,
     678             :                                                   osTmpAccessToken))
     679             :         {
     680           0 :             bFromManagedIdentities = true;
     681           0 :             return true;
     682             :         }
     683             : 
     684           0 :         CPLDebug("AZURE", "Missing storage.key or storage.sas_token in %s",
     685             :                  osConfigFilename.c_str());
     686           0 :         return false;
     687             :     }
     688             : 
     689           2 :     return true;
     690             : }
     691             : 
     692             : /************************************************************************/
     693             : /*                        GetConfiguration()                            */
     694             : /************************************************************************/
     695             : 
     696         306 : bool VSIAzureBlobHandleHelper::GetConfiguration(
     697             :     const std::string &osPathForOption, CSLConstList papszOptions,
     698             :     Service eService, bool &bUseHTTPS, std::string &osEndpoint,
     699             :     std::string &osStorageAccount, std::string &osStorageKey,
     700             :     std::string &osSAS, std::string &osAccessToken,
     701             :     bool &bFromManagedIdentities)
     702             : {
     703         306 :     bFromManagedIdentities = false;
     704             : 
     705             :     const std::string osServicePrefix(
     706         612 :         eService == Service::SERVICE_BLOB ? "blob" : "dfs");
     707         306 :     bUseHTTPS = CPLTestBool(VSIGetPathSpecificOption(
     708             :         osPathForOption.c_str(), "CPL_AZURE_USE_HTTPS", "YES"));
     709         612 :     osEndpoint = RemoveTrailingSlash(VSIGetPathSpecificOption(
     710         306 :         osPathForOption.c_str(), "CPL_AZURE_ENDPOINT", ""));
     711             : 
     712             :     const std::string osStorageConnectionString(CSLFetchNameValueDef(
     713             :         papszOptions, "AZURE_STORAGE_CONNECTION_STRING",
     714             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
     715         612 :                                  "AZURE_STORAGE_CONNECTION_STRING", "")));
     716         306 :     if (!osStorageConnectionString.empty())
     717             :     {
     718         265 :         return ParseStorageConnectionString(
     719             :             osStorageConnectionString, osServicePrefix, bUseHTTPS, osEndpoint,
     720         265 :             osStorageAccount, osStorageKey, osSAS);
     721             :     }
     722             :     else
     723             :     {
     724             :         osStorageAccount = CSLFetchNameValueDef(
     725             :             papszOptions, "AZURE_STORAGE_ACCOUNT",
     726             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
     727          41 :                                      "AZURE_STORAGE_ACCOUNT", ""));
     728          41 :         if (!osStorageAccount.empty())
     729             :         {
     730          32 :             if (osEndpoint.empty())
     731          34 :                 osEndpoint = (bUseHTTPS ? "https://" : "http://") +
     732          34 :                              osStorageAccount + "." + osServicePrefix +
     733          17 :                              ".core.windows.net";
     734             : 
     735             :             osAccessToken = CSLFetchNameValueDef(
     736             :                 papszOptions, "AZURE_STORAGE_ACCESS_TOKEN",
     737             :                 VSIGetPathSpecificOption(osPathForOption.c_str(),
     738          32 :                                          "AZURE_STORAGE_ACCESS_TOKEN", ""));
     739          32 :             if (!osAccessToken.empty())
     740           1 :                 return true;
     741             : 
     742             :             osStorageKey = CSLFetchNameValueDef(
     743             :                 papszOptions, "AZURE_STORAGE_ACCESS_KEY",
     744             :                 VSIGetPathSpecificOption(osPathForOption.c_str(),
     745          31 :                                          "AZURE_STORAGE_ACCESS_KEY", ""));
     746          31 :             if (osStorageKey.empty())
     747             :             {
     748             :                 osSAS = VSIGetPathSpecificOption(
     749             :                     osPathForOption.c_str(), "AZURE_STORAGE_SAS_TOKEN",
     750             :                     CPLGetConfigOption("AZURE_SAS",
     751          27 :                                        ""));  // AZURE_SAS for GDAL < 3.5
     752          27 :                 if (osSAS.empty())
     753             :                 {
     754          19 :                     if (CPLTestBool(VSIGetPathSpecificOption(
     755             :                             osPathForOption.c_str(), "AZURE_NO_SIGN_REQUEST",
     756             :                             "NO")))
     757             :                     {
     758           7 :                         return true;
     759             :                     }
     760             : 
     761          24 :                     std::string osTmpAccessToken;
     762          12 :                     if (GetConfigurationFromManagedIdentities(osPathForOption,
     763             :                                                               osTmpAccessToken))
     764             :                     {
     765          11 :                         bFromManagedIdentities = true;
     766          11 :                         return true;
     767             :                     }
     768             : 
     769           1 :                     const char *pszMsg =
     770             :                         "AZURE_STORAGE_ACCESS_KEY or AZURE_STORAGE_SAS_TOKEN "
     771             :                         "or AZURE_NO_SIGN_REQUEST configuration option "
     772             :                         "not defined";
     773           1 :                     CPLDebug("AZURE", "%s", pszMsg);
     774           1 :                     VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
     775           1 :                     return false;
     776             :                 }
     777             :             }
     778          12 :             return true;
     779             :         }
     780             :     }
     781             : 
     782           9 :     if (GetConfigurationFromCLIConfigFile(
     783             :             osPathForOption, osServicePrefix, bUseHTTPS, osEndpoint,
     784             :             osStorageAccount, osStorageKey, osSAS, osAccessToken,
     785             :             bFromManagedIdentities))
     786             :     {
     787           3 :         return true;
     788             :     }
     789             : 
     790           6 :     const char *pszMsg =
     791             :         "No valid Azure credentials found. "
     792             :         "For authenticated requests, you need to set "
     793             :         "AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY, "
     794             :         "AZURE_STORAGE_SAS_TOKEN, "
     795             :         "AZURE_STORAGE_CONNECTION_STRING, or other configuration "
     796             :         "options. Consult "
     797             :         "https://gdal.org/en/stable/user/"
     798             :         "virtual_file_systems.html#vsiaz-microsoft-azure-blob-files "
     799             :         "for more details. "
     800             :         "For unauthenticated requests on public resources, set the "
     801             :         "AZURE_NO_SIGN_REQUEST configuration option to YES.";
     802           6 :     CPLDebug("AZURE", "%s", pszMsg);
     803           6 :     VSIError(VSIE_InvalidCredentials, "%s", pszMsg);
     804           6 :     return false;
     805             : }
     806             : 
     807             : /************************************************************************/
     808             : /*                          BuildFromURI()                              */
     809             : /************************************************************************/
     810             : 
     811         306 : VSIAzureBlobHandleHelper *VSIAzureBlobHandleHelper::BuildFromURI(
     812             :     const char *pszURI, const char *pszFSPrefix,
     813             :     const char *pszURIForPathSpecificOption, CSLConstList papszOptions)
     814             : {
     815         306 :     if (strcmp(pszFSPrefix, "/vsiaz/") != 0 &&
     816          97 :         strcmp(pszFSPrefix, "/vsiaz_streaming/") != 0 &&
     817          91 :         strcmp(pszFSPrefix, "/vsiadls/") != 0)
     818             :     {
     819           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unsupported FS prefix");
     820           0 :         return nullptr;
     821             :     }
     822             : 
     823         612 :     const auto eService = strcmp(pszFSPrefix, "/vsiaz/") == 0 ||
     824          97 :                                   strcmp(pszFSPrefix, "/vsiaz_streaming/") == 0
     825         306 :                               ? Service::SERVICE_BLOB
     826             :                               : Service::SERVICE_ADLS;
     827             : 
     828             :     std::string osPathForOption(
     829         612 :         eService == Service::SERVICE_BLOB ? "/vsiaz/" : "/vsiadls/");
     830             :     osPathForOption +=
     831         306 :         pszURIForPathSpecificOption ? pszURIForPathSpecificOption : pszURI;
     832             : 
     833         306 :     bool bUseHTTPS = true;
     834         612 :     std::string osStorageAccount;
     835         612 :     std::string osStorageKey;
     836         612 :     std::string osEndpoint;
     837         612 :     std::string osSAS;
     838         612 :     std::string osAccessToken;
     839         306 :     bool bFromManagedIdentities = false;
     840             : 
     841         306 :     if (!GetConfiguration(osPathForOption, papszOptions, eService, bUseHTTPS,
     842             :                           osEndpoint, osStorageAccount, osStorageKey, osSAS,
     843             :                           osAccessToken, bFromManagedIdentities))
     844             :     {
     845           8 :         return nullptr;
     846             :     }
     847             : 
     848         298 :     if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
     849             :                                              "AZURE_NO_SIGN_REQUEST", "NO")))
     850             :     {
     851           8 :         osStorageKey.clear();
     852           8 :         osSAS.clear();
     853           8 :         osAccessToken.clear();
     854             :     }
     855             : 
     856             :     // pszURI == bucket/object
     857         596 :     const std::string osBucketObject(pszURI);
     858         596 :     std::string osBucket(osBucketObject);
     859         298 :     std::string osObjectKey;
     860         298 :     size_t nSlashPos = osBucketObject.find('/');
     861         298 :     if (nSlashPos != std::string::npos)
     862             :     {
     863         213 :         osBucket = osBucketObject.substr(0, nSlashPos);
     864         213 :         osObjectKey = osBucketObject.substr(nSlashPos + 1);
     865             :     }
     866             : 
     867             :     return new VSIAzureBlobHandleHelper(
     868             :         osPathForOption, osEndpoint, osBucket, osObjectKey, osStorageAccount,
     869         298 :         osStorageKey, osSAS, osAccessToken, bFromManagedIdentities);
     870             : }
     871             : 
     872             : /************************************************************************/
     873             : /*                            BuildURL()                                */
     874             : /************************************************************************/
     875             : 
     876         934 : std::string VSIAzureBlobHandleHelper::BuildURL(const std::string &osEndpoint,
     877             :                                                const std::string &osBucket,
     878             :                                                const std::string &osObjectKey,
     879             :                                                const std::string &osSAS)
     880             : {
     881         934 :     std::string osURL = osEndpoint;
     882         934 :     osURL += "/";
     883         934 :     osURL += CPLAWSURLEncode(osBucket, false);
     884         934 :     if (!osObjectKey.empty())
     885         453 :         osURL += "/" + CPLAWSURLEncode(osObjectKey, false);
     886         934 :     if (!osSAS.empty())
     887          11 :         osURL += '?' + osSAS;
     888         934 :     return osURL;
     889             : }
     890             : 
     891             : /************************************************************************/
     892             : /*                           RebuildURL()                               */
     893             : /************************************************************************/
     894             : 
     895         636 : void VSIAzureBlobHandleHelper::RebuildURL()
     896             : {
     897         636 :     m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, std::string());
     898         636 :     m_osURL += GetQueryString(false);
     899         636 :     if (!m_osSAS.empty())
     900          11 :         m_osURL += (m_oMapQueryParameters.empty() ? '?' : '&') + m_osSAS;
     901         636 : }
     902             : 
     903             : /************************************************************************/
     904             : /*                        GetSASQueryString()                           */
     905             : /************************************************************************/
     906             : 
     907          72 : std::string VSIAzureBlobHandleHelper::GetSASQueryString() const
     908             : {
     909          72 :     if (!m_osSAS.empty())
     910           4 :         return '?' + m_osSAS;
     911          68 :     return std::string();
     912             : }
     913             : 
     914             : /************************************************************************/
     915             : /*                           GetCurlHeaders()                           */
     916             : /************************************************************************/
     917             : 
     918         235 : struct curl_slist *VSIAzureBlobHandleHelper::GetCurlHeaders(
     919             :     const std::string &osVerb, const struct curl_slist *psExistingHeaders,
     920             :     const void *, size_t) const
     921             : {
     922         235 :     if (m_bFromManagedIdentities || !m_osAccessToken.empty())
     923             :     {
     924          24 :         std::string osAccessToken;
     925          12 :         if (m_bFromManagedIdentities)
     926             :         {
     927          11 :             if (!GetConfigurationFromManagedIdentities(m_osPathForOption,
     928             :                                                        osAccessToken))
     929           0 :                 return nullptr;
     930             :         }
     931             :         else
     932             :         {
     933           1 :             osAccessToken = m_osAccessToken;
     934             :         }
     935             : 
     936          12 :         struct curl_slist *headers = nullptr;
     937             : 
     938             :         // Do not use CPLSPrintf() as we could get over the 8K character limit
     939             :         // with very large SAS tokens
     940          12 :         std::string osAuthorization = "Authorization: Bearer ";
     941          12 :         osAuthorization += osAccessToken;
     942          12 :         headers = curl_slist_append(headers, osAuthorization.c_str());
     943          12 :         headers = curl_slist_append(headers, "x-ms-version: 2019-12-12");
     944          12 :         return headers;
     945             :     }
     946             : 
     947         446 :     std::string osResource;
     948         223 :     const auto nSlashSlashPos = m_osEndpoint.find("//");
     949         223 :     if (nSlashSlashPos != std::string::npos)
     950             :     {
     951         223 :         const auto nResourcePos = m_osEndpoint.find('/', nSlashSlashPos + 2);
     952         223 :         if (nResourcePos != std::string::npos)
     953         215 :             osResource = m_osEndpoint.substr(nResourcePos);
     954             :     }
     955         223 :     osResource += "/" + m_osBucket;
     956         223 :     if (!m_osObjectKey.empty())
     957         141 :         osResource += "/" + CPLAWSURLEncode(m_osObjectKey, false);
     958             : 
     959         446 :     return GetAzureBlobHeaders(osVerb, psExistingHeaders, osResource,
     960         223 :                                m_oMapQueryParameters, m_osStorageAccount,
     961         223 :                                m_osStorageKey, m_bIncludeMSVersion);
     962             : }
     963             : 
     964             : /************************************************************************/
     965             : /*                          CanRestartOnError()                         */
     966             : /************************************************************************/
     967             : 
     968          18 : bool VSIAzureBlobHandleHelper::CanRestartOnError(const char *pszErrorMsg,
     969             :                                                  const char *pszHeaders,
     970             :                                                  bool bSetError)
     971             : {
     972          18 :     if (pszErrorMsg[0] == '\xEF' && pszErrorMsg[1] == '\xBB' &&
     973           2 :         pszErrorMsg[2] == '\xBF')
     974           2 :         pszErrorMsg += 3;
     975             : 
     976             : #ifdef DEBUG_VERBOSE
     977             :     CPLDebug("AZURE", "%s", pszErrorMsg);
     978             :     CPLDebug("AZURE", "%s", pszHeaders ? pszHeaders : "");
     979             : #endif
     980             : 
     981          18 :     if (STARTS_WITH(pszErrorMsg, "HTTP/") && pszHeaders &&
     982          16 :         STARTS_WITH(pszHeaders, "HTTP/"))
     983             :     {
     984          16 :         if (bSetError)
     985             :         {
     986          32 :             std::string osMessage;
     987          32 :             std::string osTmpMessage(pszHeaders);
     988          16 :             auto nPos = osTmpMessage.find(' ');
     989          16 :             if (nPos != std::string::npos)
     990             :             {
     991          16 :                 nPos = osTmpMessage.find(' ', nPos + 1);
     992          16 :                 if (nPos != std::string::npos)
     993             :                 {
     994          16 :                     auto nPos2 = osTmpMessage.find('\r', nPos + 1);
     995          16 :                     if (nPos2 != std::string::npos)
     996             :                         osMessage =
     997          16 :                             osTmpMessage.substr(nPos + 1, nPos2 - nPos - 1);
     998             :                 }
     999             :             }
    1000          16 :             if (strstr(pszHeaders, "x-ms-error-code: BlobNotFound") ||  // vsiaz
    1001          16 :                 strstr(pszHeaders, "x-ms-error-code: PathNotFound")  // vsiadls
    1002             :             )
    1003             :             {
    1004           0 :                 VSIError(VSIE_ObjectNotFound, "%s", osMessage.c_str());
    1005             :             }
    1006          16 :             else if (strstr(pszHeaders,
    1007          16 :                             "x-ms-error-code: InvalidAuthenticationInfo") ||
    1008          16 :                      strstr(pszHeaders,
    1009             :                             "x-ms-error-code: AuthenticationFailed"))
    1010             :             {
    1011           1 :                 VSIError(VSIE_InvalidCredentials, "%s", osMessage.c_str());
    1012             :             }
    1013             :             // /vsiadls
    1014          15 :             else if (strstr(pszHeaders, "x-ms-error-code: FilesystemNotFound"))
    1015             :             {
    1016           0 :                 VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
    1017             :             }
    1018             :             else
    1019             :             {
    1020          15 :                 CPLDebug("AZURE", "%s", pszHeaders);
    1021             :             }
    1022             :         }
    1023          16 :         return false;
    1024             :     }
    1025             : 
    1026           2 :     if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
    1027           0 :         !STARTS_WITH(pszErrorMsg, "<Error>"))
    1028             :     {
    1029           0 :         if (bSetError)
    1030             :         {
    1031           0 :             VSIError(VSIE_ObjectStorageGenericError,
    1032             :                      "Invalid Azure response: %s", pszErrorMsg);
    1033             :         }
    1034           0 :         return false;
    1035             :     }
    1036             : 
    1037           4 :     auto psTree = CPLXMLTreeCloser(CPLParseXMLString(pszErrorMsg));
    1038           2 :     if (psTree == nullptr)
    1039             :     {
    1040           0 :         if (bSetError)
    1041             :         {
    1042           0 :             VSIError(VSIE_ObjectStorageGenericError,
    1043             :                      "Malformed Azure XML response: %s", pszErrorMsg);
    1044             :         }
    1045           0 :         return false;
    1046             :     }
    1047             : 
    1048           2 :     const char *pszCode = CPLGetXMLValue(psTree.get(), "=Error.Code", nullptr);
    1049           2 :     if (pszCode == nullptr)
    1050             :     {
    1051           0 :         if (bSetError)
    1052             :         {
    1053           0 :             VSIError(VSIE_ObjectStorageGenericError,
    1054             :                      "Malformed Azure XML response: %s", pszErrorMsg);
    1055             :         }
    1056           0 :         return false;
    1057             :     }
    1058             : 
    1059           2 :     if (bSetError)
    1060             :     {
    1061             :         // Translate AWS errors into VSI errors.
    1062             :         const char *pszMessage =
    1063           2 :             CPLGetXMLValue(psTree.get(), "=Error.Message", nullptr);
    1064           4 :         std::string osMessage;
    1065           2 :         if (pszMessage)
    1066             :         {
    1067           2 :             osMessage = pszMessage;
    1068           2 :             const auto nPos = osMessage.find("\nRequestId:");
    1069           2 :             if (nPos != std::string::npos)
    1070           2 :                 osMessage.resize(nPos);
    1071             :         }
    1072             : 
    1073           2 :         if (pszMessage == nullptr)
    1074             :         {
    1075           0 :             VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
    1076             :         }
    1077           2 :         else if (EQUAL(pszCode, "ContainerNotFound"))
    1078             :         {
    1079           0 :             VSIError(VSIE_BucketNotFound, "%s", osMessage.c_str());
    1080             :         }
    1081             :         else
    1082             :         {
    1083           2 :             VSIError(VSIE_ObjectStorageGenericError, "%s: %s", pszCode,
    1084             :                      pszMessage);
    1085             :         }
    1086             :     }
    1087             : 
    1088           2 :     return false;
    1089             : }
    1090             : 
    1091             : /************************************************************************/
    1092             : /*                           GetSignedURL()                             */
    1093             : /************************************************************************/
    1094             : 
    1095           7 : std::string VSIAzureBlobHandleHelper::GetSignedURL(CSLConstList papszOptions)
    1096             : {
    1097           7 :     if (m_osStorageKey.empty())
    1098           3 :         return m_osURL;
    1099             : 
    1100           8 :     std::string osStartDate(CPLGetAWS_SIGN4_Timestamp(time(nullptr)));
    1101           4 :     const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
    1102           4 :     if (pszStartDate)
    1103           2 :         osStartDate = pszStartDate;
    1104           4 :     int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
    1105           4 :     if (sscanf(osStartDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
    1106           4 :                &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
    1107             :     {
    1108           0 :         return std::string();
    1109             :     }
    1110             :     osStartDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear, nMonth,
    1111           4 :                              nDay, nHour, nMin, nSec);
    1112             : 
    1113             :     struct tm brokendowntime;
    1114           4 :     brokendowntime.tm_year = nYear - 1900;
    1115           4 :     brokendowntime.tm_mon = nMonth - 1;
    1116           4 :     brokendowntime.tm_mday = nDay;
    1117           4 :     brokendowntime.tm_hour = nHour;
    1118           4 :     brokendowntime.tm_min = nMin;
    1119           4 :     brokendowntime.tm_sec = nSec;
    1120           4 :     GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
    1121             :     GIntBig nEndDate =
    1122             :         nStartDate +
    1123           4 :         atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
    1124           4 :     CPLUnixTimeToYMDHMS(nEndDate, &brokendowntime);
    1125           4 :     nYear = brokendowntime.tm_year + 1900;
    1126           4 :     nMonth = brokendowntime.tm_mon + 1;
    1127           4 :     nDay = brokendowntime.tm_mday;
    1128           4 :     nHour = brokendowntime.tm_hour;
    1129           4 :     nMin = brokendowntime.tm_min;
    1130           4 :     nSec = brokendowntime.tm_sec;
    1131             :     std::string osEndDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", nYear,
    1132           8 :                                        nMonth, nDay, nHour, nMin, nSec);
    1133             : 
    1134           8 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
    1135             :     std::string osSignedPermissions(CSLFetchNameValueDef(
    1136             :         papszOptions, "SIGNEDPERMISSIONS",
    1137           4 :         (EQUAL(osVerb.c_str(), "GET") || EQUAL(osVerb.c_str(), "HEAD")) ? "r"
    1138          12 :                                                                         : "w"));
    1139             : 
    1140             :     std::string osSignedIdentifier(
    1141           8 :         CSLFetchNameValueDef(papszOptions, "SIGNEDIDENTIFIER", ""));
    1142             : 
    1143           8 :     const std::string osSignedVersion("2020-12-06");
    1144           8 :     const std::string osSignedProtocol("https");
    1145           8 :     const std::string osSignedResource("b");  // blob
    1146             : 
    1147           8 :     std::string osCanonicalizedResource("/blob/");
    1148           4 :     osCanonicalizedResource += CPLAWSURLEncode(m_osStorageAccount, false);
    1149           4 :     osCanonicalizedResource += '/';
    1150           4 :     osCanonicalizedResource += CPLAWSURLEncode(m_osBucket, false);
    1151           4 :     osCanonicalizedResource += '/';
    1152           4 :     osCanonicalizedResource += CPLAWSURLEncode(m_osObjectKey, false);
    1153             : 
    1154             :     // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
    1155           8 :     std::string osStringToSign;
    1156           4 :     osStringToSign += osSignedPermissions + "\n";
    1157           4 :     osStringToSign += osStartDate + "\n";
    1158           4 :     osStringToSign += osEndDate + "\n";
    1159           4 :     osStringToSign += osCanonicalizedResource + "\n";
    1160           4 :     osStringToSign += osSignedIdentifier + "\n";
    1161           4 :     osStringToSign += "\n";  // signedIP
    1162           4 :     osStringToSign += osSignedProtocol + "\n";
    1163           4 :     osStringToSign += osSignedVersion + "\n";
    1164           4 :     osStringToSign += osSignedResource + "\n";
    1165           4 :     osStringToSign += "\n";  // signedSnapshotTime
    1166           4 :     osStringToSign += "\n";  // signedEncryptionScope
    1167           4 :     osStringToSign += "\n";  // rscc
    1168           4 :     osStringToSign += "\n";  // rscd
    1169           4 :     osStringToSign += "\n";  // rsce
    1170           4 :     osStringToSign += "\n";  // rscl
    1171             : 
    1172             : #ifdef DEBUG_VERBOSE
    1173             :     CPLDebug("AZURE", "osStringToSign = %s", osStringToSign.c_str());
    1174             : #endif
    1175             : 
    1176             :     /* -------------------------------------------------------------------- */
    1177             :     /*      Compute signature.                                              */
    1178             :     /* -------------------------------------------------------------------- */
    1179             :     std::string osSignature(
    1180           8 :         CPLAzureGetSignature(osStringToSign, m_osStorageKey));
    1181             : 
    1182           4 :     ResetQueryParameters();
    1183           4 :     AddQueryParameter("sv", osSignedVersion);
    1184           4 :     AddQueryParameter("st", osStartDate);
    1185           4 :     AddQueryParameter("se", osEndDate);
    1186           4 :     AddQueryParameter("sr", osSignedResource);
    1187           4 :     AddQueryParameter("sp", osSignedPermissions);
    1188           4 :     AddQueryParameter("spr", osSignedProtocol);
    1189           4 :     AddQueryParameter("sig", osSignature);
    1190           4 :     if (!osSignedIdentifier.empty())
    1191           0 :         AddQueryParameter("si", osSignedIdentifier);
    1192           4 :     return m_osURL;
    1193             : }
    1194             : 
    1195             : #endif  // HAVE_CURL
    1196             : 
    1197             : //! @endcond

Generated by: LCOV version 1.14