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

Generated by: LCOV version 1.14