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

Generated by: LCOV version 1.14