LCOV - code coverage report
Current view: top level - port - cpl_azure.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 489 585 83.6 %
Date: 2026-06-20 20:44:25 Functions: 25 28 89.3 %

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

Generated by: LCOV version 1.14