LCOV - code coverage report
Current view: top level - port - cpl_aws.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1087 1175 92.5 %
Date: 2025-08-19 18:03:11 Functions: 50 50 100.0 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Name:     cpl_aws.cpp
       4             :  * Project:  CPL - Common Portability Library
       5             :  * Purpose:  Amazon Web Services routines
       6             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  **********************************************************************
       9             :  * Copyright (c) 2015, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : //! @cond Doxygen_Suppress
      15             : 
      16             : #include "cpl_aws.h"
      17             : #include "cpl_json.h"
      18             : #include "cpl_vsi_error.h"
      19             : #include "cpl_sha1.h"
      20             : #include "cpl_sha256.h"
      21             : #include "cpl_time.h"
      22             : #include "cpl_minixml.h"
      23             : #include "cpl_multiproc.h"
      24             : #include "cpl_http.h"
      25             : #include <algorithm>
      26             : 
      27             : // #define DEBUG_VERBOSE 1
      28             : 
      29             : #ifdef _WIN32
      30             : #if defined(HAVE_ATLBASE_H)
      31             : bool CPLFetchWindowsProductUUID(
      32             :     std::string &osStr);  // defined in cpl_aws_win32.cpp
      33             : #endif
      34             : const char *CPLGetWineVersion();  // defined in cpl_vsil_win32.cpp
      35             : #endif
      36             : 
      37             : #ifdef HAVE_CURL
      38             : static CPLMutex *ghMutex = nullptr;
      39             : static std::string gosIAMRole;
      40             : static std::string gosGlobalAccessKeyId;
      41             : static std::string gosGlobalSecretAccessKey;
      42             : static std::string gosGlobalSessionToken;
      43             : static GIntBig gnGlobalExpiration = 0;
      44             : static std::string gosRegion;
      45             : 
      46             : // The below variables are used for credentials retrieved through a STS
      47             : // AssumedRole operation
      48             : static std::string gosRoleArn;
      49             : static std::string gosExternalId;
      50             : static std::string gosMFASerial;
      51             : static std::string gosRoleSessionName;
      52             : static std::string gosSourceProfileAccessKeyId;
      53             : static std::string gosSourceProfileSecretAccessKey;
      54             : static std::string gosSourceProfileSessionToken;
      55             : 
      56             : // The below variables are used for web identity settings in aws/config
      57             : static std::string gosRoleArnWebIdentity;
      58             : static std::string gosWebIdentityTokenFile;
      59             : 
      60             : // The below variables are used for SSO authentication
      61             : static std::string gosSSOStartURL;
      62             : static std::string gosSSOAccountID;
      63             : static std::string gosSSORoleName;
      64             : 
      65             : constexpr const char *AWS_DEBUG_KEY = "AWS";
      66             : 
      67             : /************************************************************************/
      68             : /*                         CPLGetLowerCaseHex()                         */
      69             : /************************************************************************/
      70             : 
      71        1012 : static std::string CPLGetLowerCaseHex(const GByte *pabyData, size_t nBytes)
      72             : 
      73             : {
      74        1012 :     std::string osRet;
      75        1012 :     osRet.resize(nBytes * 2);
      76             : 
      77        1012 :     constexpr char achHex[] = "0123456789abcdef";
      78             : 
      79       33384 :     for (size_t i = 0; i < nBytes; ++i)
      80             :     {
      81       32372 :         const int nLow = pabyData[i] & 0x0f;
      82       32372 :         const int nHigh = (pabyData[i] & 0xf0) >> 4;
      83             : 
      84       32372 :         osRet[i * 2] = achHex[nHigh];
      85       32372 :         osRet[i * 2 + 1] = achHex[nLow];
      86             :     }
      87             : 
      88        2024 :     return osRet;
      89             : }
      90             : 
      91             : /************************************************************************/
      92             : /*                       CPLGetLowerCaseHexSHA256()                     */
      93             : /************************************************************************/
      94             : 
      95         677 : std::string CPLGetLowerCaseHexSHA256(const void *pabyData, size_t nBytes)
      96             : {
      97         677 :     GByte hash[CPL_SHA256_HASH_SIZE] = {};
      98         677 :     CPL_SHA256(static_cast<const GByte *>(pabyData), nBytes, hash);
      99        1354 :     return CPLGetLowerCaseHex(hash, CPL_SHA256_HASH_SIZE);
     100             : }
     101             : 
     102             : /************************************************************************/
     103             : /*                       CPLGetLowerCaseHexSHA256()                     */
     104             : /************************************************************************/
     105             : 
     106         339 : std::string CPLGetLowerCaseHexSHA256(const std::string &osStr)
     107             : {
     108         339 :     return CPLGetLowerCaseHexSHA256(osStr.c_str(), osStr.size());
     109             : }
     110             : 
     111             : /************************************************************************/
     112             : /*                       CPLAWSURLEncode()                              */
     113             : /************************************************************************/
     114             : 
     115        6267 : std::string CPLAWSURLEncode(const std::string &osURL, bool bEncodeSlash)
     116             : {
     117        6267 :     std::string osRet;
     118    12100600 :     for (size_t i = 0; i < osURL.size(); i++)
     119             :     {
     120    12094300 :         char ch = osURL[i];
     121    12094300 :         if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
     122       40962 :             (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' ||
     123             :             ch == '.')
     124             :         {
     125    12090500 :             osRet += ch;
     126             :         }
     127        3825 :         else if (ch == '/')
     128             :         {
     129        3549 :             if (bEncodeSlash)
     130         627 :                 osRet += "%2F";
     131             :             else
     132        2922 :                 osRet += ch;
     133             :         }
     134             :         else
     135             :         {
     136         276 :             osRet += CPLSPrintf("%%%02X", static_cast<unsigned char>(ch));
     137             :         }
     138             :     }
     139        6267 :     return osRet;
     140             : }
     141             : 
     142             : /************************************************************************/
     143             : /*                         CPLAWSGetHeaderVal()                         */
     144             : /************************************************************************/
     145             : 
     146        2562 : std::string CPLAWSGetHeaderVal(const struct curl_slist *psExistingHeaders,
     147             :                                const char *pszKey)
     148             : {
     149        5124 :     std::string osKey(pszKey);
     150        2562 :     osKey += ":";
     151        2562 :     const struct curl_slist *psIter = psExistingHeaders;
     152        3927 :     for (; psIter != nullptr; psIter = psIter->next)
     153             :     {
     154        1451 :         if (STARTS_WITH(psIter->data, osKey.c_str()))
     155         172 :             return CPLString(psIter->data + osKey.size()).Trim();
     156             :     }
     157        2476 :     return std::string();
     158             : }
     159             : 
     160             : /************************************************************************/
     161             : /*                 CPLGetAWS_SIGN4_Signature()                          */
     162             : /************************************************************************/
     163             : 
     164             : // See:
     165             : // http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
     166         334 : static std::string CPLGetAWS_SIGN4_Signature(
     167             :     const std::string &osSecretAccessKey, const std::string &osRegion,
     168             :     const std::string &osService, const std::string &osVerb,
     169             :     struct curl_slist *&psHeaders, const std::string &osHost,
     170             :     const std::string &osCanonicalURI,
     171             :     const std::string &osCanonicalQueryString,
     172             :     const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
     173             :     const std::string &osTimestamp, std::string &osSignedHeaders)
     174             : {
     175             :     /* -------------------------------------------------------------------- */
     176             :     /*      Compute canonical request string.                               */
     177             :     /* -------------------------------------------------------------------- */
     178         668 :     std::string osCanonicalRequest = osVerb + "\n";
     179             : 
     180         334 :     osCanonicalRequest += osCanonicalURI + "\n";
     181             : 
     182         334 :     osCanonicalRequest += osCanonicalQueryString + "\n";
     183             : 
     184         668 :     std::map<std::string, std::string> oSortedMapHeaders;
     185         334 :     oSortedMapHeaders["host"] = osHost;
     186         334 :     if (osXAMZContentSHA256 != "UNSIGNED-PAYLOAD" && bAddHeaderAMZContentSHA256)
     187             :     {
     188         324 :         oSortedMapHeaders["x-amz-content-sha256"] = osXAMZContentSHA256;
     189         324 :         oSortedMapHeaders["x-amz-date"] = osTimestamp;
     190             :     }
     191             :     std::string osCanonicalizedHeaders(
     192             :         IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(oSortedMapHeaders,
     193         668 :                                                           psHeaders, "x-amz-"));
     194             : 
     195         334 :     osCanonicalRequest += osCanonicalizedHeaders + "\n";
     196             : 
     197         334 :     osSignedHeaders.clear();
     198             :     std::map<std::string, std::string>::const_iterator oIter =
     199         334 :         oSortedMapHeaders.begin();
     200        1350 :     for (; oIter != oSortedMapHeaders.end(); ++oIter)
     201             :     {
     202        1016 :         if (!osSignedHeaders.empty())
     203         682 :             osSignedHeaders += ";";
     204        1016 :         osSignedHeaders += oIter->first;
     205             :     }
     206             : 
     207         334 :     osCanonicalRequest += osSignedHeaders + "\n";
     208             : 
     209         334 :     osCanonicalRequest += osXAMZContentSHA256;
     210             : 
     211             : #ifdef DEBUG_VERBOSE
     212             :     CPLDebug(AWS_DEBUG_KEY, "osCanonicalRequest='%s'",
     213             :              osCanonicalRequest.c_str());
     214             : #endif
     215             : 
     216             :     /* -------------------------------------------------------------------- */
     217             :     /*      Compute StringToSign .                                          */
     218             :     /* -------------------------------------------------------------------- */
     219         668 :     std::string osStringToSign = "AWS4-HMAC-SHA256\n";
     220         334 :     osStringToSign += osTimestamp + "\n";
     221             : 
     222         668 :     std::string osYYMMDD(osTimestamp);
     223         334 :     osYYMMDD.resize(8);
     224             : 
     225         668 :     std::string osScope = osYYMMDD + "/";
     226         334 :     osScope += osRegion;
     227         334 :     osScope += "/";
     228         334 :     osScope += osService;
     229         334 :     osScope += "/aws4_request";
     230         334 :     osStringToSign += osScope + "\n";
     231         334 :     osStringToSign += CPLGetLowerCaseHexSHA256(osCanonicalRequest);
     232             : 
     233             : #ifdef DEBUG_VERBOSE
     234             :     CPLDebug(AWS_DEBUG_KEY, "osStringToSign='%s'", osStringToSign.c_str());
     235             : #endif
     236             : 
     237             :     /* -------------------------------------------------------------------- */
     238             :     /*      Compute signing key.                                            */
     239             :     /* -------------------------------------------------------------------- */
     240         334 :     GByte abySigningKeyIn[CPL_SHA256_HASH_SIZE] = {};
     241         334 :     GByte abySigningKeyOut[CPL_SHA256_HASH_SIZE] = {};
     242             : 
     243        1002 :     std::string osFirstKey(std::string("AWS4") + osSecretAccessKey);
     244         334 :     CPL_HMAC_SHA256(osFirstKey.c_str(), osFirstKey.size(), osYYMMDD.c_str(),
     245             :                     osYYMMDD.size(), abySigningKeyOut);
     246         334 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     247             : 
     248         334 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osRegion.c_str(),
     249             :                     osRegion.size(), abySigningKeyOut);
     250         334 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     251             : 
     252         334 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, osService.c_str(),
     253             :                     osService.size(), abySigningKeyOut);
     254         334 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     255             : 
     256         334 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE, "aws4_request",
     257             :                     strlen("aws4_request"), abySigningKeyOut);
     258         334 :     memcpy(abySigningKeyIn, abySigningKeyOut, CPL_SHA256_HASH_SIZE);
     259             : 
     260             : #ifdef DEBUG_VERBOSE
     261             :     std::string osSigningKey(
     262             :         CPLGetLowerCaseHex(abySigningKeyIn, CPL_SHA256_HASH_SIZE));
     263             :     CPLDebug(AWS_DEBUG_KEY, "osSigningKey='%s'", osSigningKey.c_str());
     264             : #endif
     265             : 
     266             :     /* -------------------------------------------------------------------- */
     267             :     /*      Compute signature.                                              */
     268             :     /* -------------------------------------------------------------------- */
     269         334 :     GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
     270         668 :     CPL_HMAC_SHA256(abySigningKeyIn, CPL_SHA256_HASH_SIZE,
     271         334 :                     osStringToSign.c_str(), osStringToSign.size(),
     272             :                     abySignature);
     273             :     std::string osSignature(
     274         334 :         CPLGetLowerCaseHex(abySignature, CPL_SHA256_HASH_SIZE));
     275             : 
     276             : #ifdef DEBUG_VERBOSE
     277             :     CPLDebug(AWS_DEBUG_KEY, "osSignature='%s'", osSignature.c_str());
     278             : #endif
     279             : 
     280         334 :     psHeaders = curl_slist_append(
     281             :         psHeaders, CPLSPrintf("x-amz-date: %s", osTimestamp.c_str()));
     282         334 :     psHeaders =
     283         334 :         curl_slist_append(psHeaders, CPLSPrintf("x-amz-content-sha256: %s",
     284             :                                                 osXAMZContentSHA256.c_str()));
     285         668 :     return osSignature;
     286             : }
     287             : 
     288             : /************************************************************************/
     289             : /*                CPLGetAWS_SIGN4_Authorization()                       */
     290             : /************************************************************************/
     291             : 
     292         329 : static std::string CPLGetAWS_SIGN4_Authorization(
     293             :     const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
     294             :     const std::string &osRegion, const std::string &osService,
     295             :     const std::string &osVerb, struct curl_slist *&psHeaders,
     296             :     const std::string &osHost, const std::string &osCanonicalURI,
     297             :     const std::string &osCanonicalQueryString,
     298             :     const std::string &osXAMZContentSHA256, bool bAddHeaderAMZContentSHA256,
     299             :     const std::string &osTimestamp)
     300             : {
     301         658 :     std::string osSignedHeaders;
     302             :     std::string osSignature(CPLGetAWS_SIGN4_Signature(
     303             :         osSecretAccessKey, osRegion, osService, osVerb, psHeaders, osHost,
     304             :         osCanonicalURI, osCanonicalQueryString, osXAMZContentSHA256,
     305         658 :         bAddHeaderAMZContentSHA256, osTimestamp, osSignedHeaders));
     306             : 
     307         658 :     std::string osYYMMDD(osTimestamp);
     308         329 :     osYYMMDD.resize(8);
     309             : 
     310             :     /* -------------------------------------------------------------------- */
     311             :     /*      Build authorization header.                                     */
     312             :     /* -------------------------------------------------------------------- */
     313         329 :     std::string osAuthorization;
     314         329 :     osAuthorization = "AWS4-HMAC-SHA256 Credential=";
     315         329 :     osAuthorization += osAccessKeyId;
     316         329 :     osAuthorization += "/";
     317         329 :     osAuthorization += osYYMMDD;
     318         329 :     osAuthorization += "/";
     319         329 :     osAuthorization += osRegion;
     320         329 :     osAuthorization += "/";
     321         329 :     osAuthorization += osService;
     322         329 :     osAuthorization += "/";
     323         329 :     osAuthorization += "aws4_request";
     324         329 :     osAuthorization += ",";
     325         329 :     osAuthorization += "SignedHeaders=";
     326         329 :     osAuthorization += osSignedHeaders;
     327         329 :     osAuthorization += ",";
     328         329 :     osAuthorization += "Signature=";
     329         329 :     osAuthorization += osSignature;
     330             : 
     331             : #ifdef DEBUG_VERBOSE
     332             :     CPLDebug(AWS_DEBUG_KEY, "osAuthorization='%s'", osAuthorization.c_str());
     333             : #endif
     334             : 
     335         658 :     return osAuthorization;
     336             : }
     337             : 
     338             : /************************************************************************/
     339             : /*                        CPLGetAWS_SIGN4_Timestamp()                   */
     340             : /************************************************************************/
     341             : 
     342           4 : std::string CPLGetAWS_SIGN4_Timestamp(GIntBig timestamp)
     343             : {
     344             :     struct tm brokenDown;
     345           4 :     CPLUnixTimeToYMDHMS(timestamp, &brokenDown);
     346             : 
     347           4 :     char szTimeStamp[80] = {};
     348           4 :     snprintf(szTimeStamp, sizeof(szTimeStamp), "%04d%02d%02dT%02d%02d%02dZ",
     349           4 :              brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
     350             :              brokenDown.tm_mday, brokenDown.tm_hour, brokenDown.tm_min,
     351             :              brokenDown.tm_sec);
     352           4 :     return szTimeStamp;
     353             : }
     354             : 
     355             : /************************************************************************/
     356             : /*                         VSIS3HandleHelper()                          */
     357             : /************************************************************************/
     358         556 : VSIS3HandleHelper::VSIS3HandleHelper(
     359             :     const std::string &osService, const std::string &osSecretAccessKey,
     360             :     const std::string &osAccessKeyId, const std::string &osSessionToken,
     361             :     const std::string &osS3SessionToken, const std::string &osEndpoint,
     362             :     const std::string &osRegion, const std::string &osRequestPayer,
     363             :     const std::string &osBucket, const std::string &osObjectKey, bool bUseHTTPS,
     364             :     bool bUseVirtualHosting, AWSCredentialsSource eCredentialsSource,
     365         556 :     bool bIsDirectoryBucket)
     366             :     : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS,
     367             :                        bUseVirtualHosting)),
     368             :       m_osService(osService), m_osSecretAccessKey(osSecretAccessKey),
     369             :       m_osAccessKeyId(osAccessKeyId), m_osSessionToken(osSessionToken),
     370             :       m_osS3SessionToken(osS3SessionToken), m_osEndpoint(osEndpoint),
     371             :       m_osRegion(osRegion), m_osRequestPayer(osRequestPayer),
     372             :       m_osBucket(osBucket), m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS),
     373             :       m_bUseVirtualHosting(bUseVirtualHosting),
     374             :       m_bIsDirectoryBucket(bIsDirectoryBucket),
     375         556 :       m_eCredentialsSource(eCredentialsSource)
     376             : {
     377         556 :     VSIS3UpdateParams::UpdateHandleFromMap(this);
     378         556 : }
     379             : 
     380             : /************************************************************************/
     381             : /*                        ~VSIS3HandleHelper()                          */
     382             : /************************************************************************/
     383             : 
     384        1112 : VSIS3HandleHelper::~VSIS3HandleHelper()
     385             : {
     386       11591 :     for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
     387       11035 :         m_osSecretAccessKey[i] = 0;
     388        1112 : }
     389             : 
     390             : /************************************************************************/
     391             : /*                           BuildURL()                                 */
     392             : /************************************************************************/
     393             : 
     394        1149 : std::string VSIS3HandleHelper::BuildURL(const std::string &osEndpoint,
     395             :                                         const std::string &osBucket,
     396             :                                         const std::string &osObjectKey,
     397             :                                         bool bUseHTTPS, bool bUseVirtualHosting)
     398             : {
     399        1149 :     const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
     400        1149 :     if (osBucket.empty())
     401          20 :         return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
     402        1129 :     else if (bUseVirtualHosting)
     403             :         return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
     404             :                           osEndpoint.c_str(),
     405          36 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     406             :     else
     407             :         return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
     408             :                           osBucket.c_str(),
     409        2222 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     410             : }
     411             : 
     412             : /************************************************************************/
     413             : /*                           RebuildURL()                               */
     414             : /************************************************************************/
     415             : 
     416         593 : void VSIS3HandleHelper::RebuildURL()
     417             : {
     418         593 :     m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
     419         593 :                        m_bUseVirtualHosting);
     420         593 :     m_osURL += GetQueryString(false);
     421         593 : }
     422             : 
     423             : IVSIS3LikeHandleHelper::IVSIS3LikeHandleHelper() = default;
     424             : 
     425             : IVSIS3LikeHandleHelper::~IVSIS3LikeHandleHelper() = default;
     426             : 
     427             : /************************************************************************/
     428             : /*                        GetBucketAndObjectKey()                       */
     429             : /************************************************************************/
     430             : 
     431         633 : bool IVSIS3LikeHandleHelper::GetBucketAndObjectKey(const char *pszURI,
     432             :                                                    const char *pszFSPrefix,
     433             :                                                    bool bAllowNoObject,
     434             :                                                    std::string &osBucket,
     435             :                                                    std::string &osObjectKey)
     436             : {
     437         633 :     osBucket = pszURI;
     438         633 :     if (osBucket.empty())
     439             :     {
     440           0 :         return false;
     441             :     }
     442         633 :     size_t nPos = osBucket.find('/');
     443         633 :     if (nPos == std::string::npos)
     444             :     {
     445         119 :         if (bAllowNoObject)
     446             :         {
     447         117 :             osObjectKey = "";
     448         117 :             return true;
     449             :         }
     450           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     451             :                  "Filename should be of the form %sbucket/key", pszFSPrefix);
     452           2 :         return false;
     453             :     }
     454         514 :     osBucket.resize(nPos);
     455         514 :     osObjectKey = pszURI + nPos + 1;
     456         514 :     return true;
     457             : }
     458             : 
     459             : /************************************************************************/
     460             : /*                      BuildCanonicalizedHeaders()                    */
     461             : /************************************************************************/
     462             : 
     463         652 : std::string IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
     464             :     std::map<std::string, std::string> &oSortedMapHeaders,
     465             :     const struct curl_slist *psExistingHeaders, const char *pszHeaderPrefix)
     466             : {
     467         652 :     const struct curl_slist *psIter = psExistingHeaders;
     468        1042 :     for (; psIter != nullptr; psIter = psIter->next)
     469             :     {
     470         390 :         if (STARTS_WITH_CI(psIter->data, pszHeaderPrefix) ||
     471         328 :             STARTS_WITH_CI(psIter->data, "Content-MD5"))
     472             :         {
     473          68 :             const char *pszColumn = strstr(psIter->data, ":");
     474          68 :             if (pszColumn)
     475             :             {
     476          68 :                 CPLString osKey(psIter->data);
     477          68 :                 osKey.resize(pszColumn - psIter->data);
     478          68 :                 oSortedMapHeaders[osKey.tolower()] =
     479         136 :                     CPLString(pszColumn + strlen(":")).Trim();
     480             :             }
     481             :         }
     482             :     }
     483             : 
     484         652 :     std::string osCanonicalizedHeaders;
     485             :     std::map<std::string, std::string>::const_iterator oIter =
     486         652 :         oSortedMapHeaders.begin();
     487        2123 :     for (; oIter != oSortedMapHeaders.end(); ++oIter)
     488             :     {
     489        1471 :         osCanonicalizedHeaders += oIter->first + ":" + oIter->second + "\n";
     490             :     }
     491        1304 :     return osCanonicalizedHeaders;
     492             : }
     493             : 
     494             : /************************************************************************/
     495             : /*                         GetRFC822DateTime()                          */
     496             : /************************************************************************/
     497             : 
     498          33 : std::string IVSIS3LikeHandleHelper::GetRFC822DateTime()
     499             : {
     500             :     char szDate[64];
     501          33 :     time_t nNow = time(nullptr);
     502             :     struct tm tm;
     503          33 :     CPLUnixTimeToYMDHMS(nNow, &tm);
     504          33 :     int nRet = CPLPrintTime(szDate, sizeof(szDate) - 1,
     505             :                             "%a, %d %b %Y %H:%M:%S GMT", &tm, "C");
     506          33 :     szDate[nRet] = 0;
     507          33 :     return szDate;
     508             : }
     509             : 
     510             : /************************************************************************/
     511             : /*                        Iso8601ToUnixTime()                           */
     512             : /************************************************************************/
     513             : 
     514          17 : static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime)
     515             : {
     516             :     int nYear;
     517             :     int nMonth;
     518             :     int nDay;
     519             :     int nHour;
     520             :     int nMinute;
     521             :     int nSecond;
     522          17 :     if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay,
     523          17 :                &nHour, &nMinute, &nSecond) == 6)
     524             :     {
     525             :         struct tm brokendowntime;
     526          17 :         brokendowntime.tm_year = nYear - 1900;
     527          17 :         brokendowntime.tm_mon = nMonth - 1;
     528          17 :         brokendowntime.tm_mday = nDay;
     529          17 :         brokendowntime.tm_hour = nHour;
     530          17 :         brokendowntime.tm_min = nMinute;
     531          17 :         brokendowntime.tm_sec = nSecond;
     532          17 :         *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
     533          17 :         return true;
     534             :     }
     535           0 :     return false;
     536             : }
     537             : 
     538             : /************************************************************************/
     539             : /*                  IsMachinePotentiallyEC2Instance()                   */
     540             : /************************************************************************/
     541             : 
     542             : enum class EC2InstanceCertainty
     543             : {
     544             :     YES,
     545             :     NO,
     546             :     MAYBE
     547             : };
     548             : 
     549          14 : static EC2InstanceCertainty IsMachinePotentiallyEC2Instance()
     550             : {
     551             : #if defined(__linux) || defined(_WIN32)
     552           8 :     const auto IsMachinePotentiallyEC2InstanceFromLinuxHost = []()
     553             :     {
     554             :         // On the newer Nitro Hypervisor (C5, M5, H1, T3), use
     555             :         // /sys/devices/virtual/dmi/id/sys_vendor = 'Amazon EC2' instead.
     556             : 
     557             :         // On older Xen hypervisor EC2 instances, a /sys/hypervisor/uuid file
     558             :         // will exist with a string beginning with 'ec2'.
     559             : 
     560             :         // If the files exist but don't contain the correct content, then we're
     561             :         // not EC2 and do not attempt any network access
     562             : 
     563             :         // Check for Xen Hypervisor instances
     564             :         // This file doesn't exist on Nitro instances
     565           8 :         VSILFILE *fp = VSIFOpenL("/sys/hypervisor/uuid", "rb");
     566           8 :         if (fp != nullptr)
     567             :         {
     568           0 :             char uuid[36 + 1] = {0};
     569           0 :             VSIFReadL(uuid, 1, sizeof(uuid) - 1, fp);
     570           0 :             VSIFCloseL(fp);
     571           0 :             return EQUALN(uuid, "ec2", 3) ? EC2InstanceCertainty::YES
     572           0 :                                           : EC2InstanceCertainty::NO;
     573             :         }
     574             : 
     575             :         // Check for Nitro Hypervisor instances
     576             :         // This file may exist on Xen instances with a value of 'Xen'
     577             :         // (but that doesn't mean we're on EC2)
     578           8 :         fp = VSIFOpenL("/sys/devices/virtual/dmi/id/sys_vendor", "rb");
     579           8 :         if (fp != nullptr)
     580             :         {
     581           8 :             char buf[10 + 1] = {0};
     582           8 :             VSIFReadL(buf, 1, sizeof(buf) - 1, fp);
     583           8 :             VSIFCloseL(fp);
     584           8 :             return EQUALN(buf, "Amazon EC2", 10) ? EC2InstanceCertainty::YES
     585           8 :                                                  : EC2InstanceCertainty::NO;
     586             :         }
     587             : 
     588             :         // Fallback: Check via the network
     589           0 :         return EC2InstanceCertainty::MAYBE;
     590             :     };
     591             : #endif
     592             : 
     593             : #ifdef __linux
     594             :     // Optimization on Linux to avoid the network request
     595             :     // See
     596             :     // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html
     597             :     // Skip if either:
     598             :     // - CPL_AWS_AUTODETECT_EC2=NO
     599             :     // - CPL_AWS_CHECK_HYPERVISOR_UUID=NO (deprecated)
     600             : 
     601          14 :     if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
     602             :     {
     603           6 :         return EC2InstanceCertainty::MAYBE;
     604             :     }
     605             :     else
     606             :     {
     607             :         const char *opt =
     608           8 :             CPLGetConfigOption("CPL_AWS_CHECK_HYPERVISOR_UUID", "");
     609           8 :         if (opt[0])
     610             :         {
     611           0 :             CPLDebug(AWS_DEBUG_KEY,
     612             :                      "CPL_AWS_CHECK_HYPERVISOR_UUID is deprecated. Use "
     613             :                      "CPL_AWS_AUTODETECT_EC2 instead");
     614           0 :             if (!CPLTestBool(opt))
     615             :             {
     616           0 :                 return EC2InstanceCertainty::MAYBE;
     617             :             }
     618             :         }
     619             :     }
     620             : 
     621           8 :     return IsMachinePotentiallyEC2InstanceFromLinuxHost();
     622             : 
     623             : #elif defined(_WIN32)
     624             :     if (!CPLTestBool(CPLGetConfigOption("CPL_AWS_AUTODETECT_EC2", "YES")))
     625             :     {
     626             :         return EC2InstanceCertainty::MAYBE;
     627             :     }
     628             : 
     629             :     // Regular UUID is not valid for WINE, fetch from sysfs instead.
     630             :     if (CPLGetWineVersion() != nullptr)
     631             :     {
     632             :         return IsMachinePotentiallyEC2InstanceFromLinuxHost();
     633             :     }
     634             :     else
     635             :     {
     636             : #if defined(HAVE_ATLBASE_H)
     637             :         std::string osMachineUUID;
     638             :         if (CPLFetchWindowsProductUUID(osMachineUUID))
     639             :         {
     640             :             if (osMachineUUID.length() >= 3 &&
     641             :                 EQUALN(osMachineUUID.c_str(), "EC2", 3))
     642             :             {
     643             :                 return EC2InstanceCertainty::YES;
     644             :             }
     645             :             else if (osMachineUUID.length() >= 8 && osMachineUUID[4] == '2' &&
     646             :                      osMachineUUID[6] == 'E' && osMachineUUID[7] == 'C')
     647             :             {
     648             :                 return EC2InstanceCertainty::YES;
     649             :             }
     650             :             else
     651             :             {
     652             :                 return EC2InstanceCertainty::NO;
     653             :             }
     654             :         }
     655             : #endif
     656             :     }
     657             : 
     658             :     // Fallback: Check via the network
     659             :     return EC2InstanceCertainty::MAYBE;
     660             : #else
     661             :     // At time of writing EC2 instances can be only Linux or Windows
     662             :     return EC2InstanceCertainty::NO;
     663             : #endif
     664             : }
     665             : 
     666             : /************************************************************************/
     667             : /*                   ReadAWSTokenFile()                                 */
     668             : /************************************************************************/
     669             : 
     670           5 : static bool ReadAWSTokenFile(const std::string &osAWSTokenFile,
     671             :                              std::string &awsToken)
     672             : {
     673           5 :     GByte *pabyOut = nullptr;
     674           5 :     if (!VSIIngestFile(nullptr, osAWSTokenFile.c_str(), &pabyOut, nullptr, -1))
     675           0 :         return false;
     676             : 
     677           5 :     awsToken = reinterpret_cast<char *>(pabyOut);
     678           5 :     VSIFree(pabyOut);
     679             :     // Remove trailing end-of-line character
     680           5 :     if (!awsToken.empty() && awsToken.back() == '\n')
     681           4 :         awsToken.pop_back();
     682           5 :     return !awsToken.empty();
     683             : }
     684             : 
     685             : /************************************************************************/
     686             : /*          GetConfigurationFromAssumeRoleWithWebIdentity()             */
     687             : /************************************************************************/
     688             : 
     689          17 : bool VSIS3HandleHelper::GetConfigurationFromAssumeRoleWithWebIdentity(
     690             :     bool bForceRefresh, const std::string &osPathForOption,
     691             :     const std::string &osRoleArnIn, const std::string &osWebIdentityTokenFileIn,
     692             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
     693             :     std::string &osSessionToken)
     694             : {
     695          34 :     CPLMutexHolder oHolder(&ghMutex);
     696          17 :     if (!bForceRefresh)
     697             :     {
     698             :         time_t nCurTime;
     699          17 :         time(&nCurTime);
     700             :         // Try to reuse credentials if they are still valid, but
     701             :         // keep one minute of margin...
     702          17 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
     703             :         {
     704           1 :             osAccessKeyId = gosGlobalAccessKeyId;
     705           1 :             osSecretAccessKey = gosGlobalSecretAccessKey;
     706           1 :             osSessionToken = gosGlobalSessionToken;
     707           1 :             return true;
     708             :         }
     709             :     }
     710             : 
     711             :     const std::string roleArn =
     712          16 :         !osRoleArnIn.empty() ? osRoleArnIn
     713             :                              : VSIGetPathSpecificOption(osPathForOption.c_str(),
     714          32 :                                                         "AWS_ROLE_ARN", "");
     715          16 :     if (roleArn.empty())
     716             :     {
     717          12 :         CPLDebug(AWS_DEBUG_KEY,
     718             :                  "AWS_ROLE_ARN configuration option not defined");
     719          12 :         return false;
     720             :     }
     721             : 
     722             :     const std::string webIdentityTokenFile =
     723           4 :         !osWebIdentityTokenFileIn.empty()
     724             :             ? osWebIdentityTokenFileIn
     725             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     726           8 :                                        "AWS_WEB_IDENTITY_TOKEN_FILE", "");
     727           4 :     if (webIdentityTokenFile.empty())
     728             :     {
     729           0 :         CPLDebug(
     730             :             AWS_DEBUG_KEY,
     731             :             "AWS_WEB_IDENTITY_TOKEN_FILE configuration option not defined");
     732           0 :         return false;
     733             :     }
     734             : 
     735             :     const std::string stsRegionalEndpoints = VSIGetPathSpecificOption(
     736           8 :         osPathForOption.c_str(), "AWS_STS_REGIONAL_ENDPOINTS", "regional");
     737             : 
     738           8 :     std::string osStsDefaultUrl;
     739           4 :     if (stsRegionalEndpoints == "regional")
     740             :     {
     741             :         const std::string osRegion = VSIGetPathSpecificOption(
     742           4 :             osPathForOption.c_str(), "AWS_REGION", "us-east-1");
     743           4 :         osStsDefaultUrl = "https://sts." + osRegion + ".amazonaws.com";
     744             :     }
     745             :     else
     746             :     {
     747           0 :         osStsDefaultUrl = "https://sts.amazonaws.com";
     748             :     }
     749             :     const std::string osStsRootUrl(VSIGetPathSpecificOption(
     750             :         osPathForOption.c_str(), "CPL_AWS_STS_ROOT_URL",
     751           8 :         osStsDefaultUrl.c_str()));
     752             : 
     753             :     // Get token from web identity token file
     754           8 :     std::string webIdentityToken;
     755           4 :     if (!ReadAWSTokenFile(webIdentityTokenFile, webIdentityToken))
     756             :     {
     757           0 :         CPLDebug(AWS_DEBUG_KEY, "%s is empty", webIdentityTokenFile.c_str());
     758           0 :         return false;
     759             :     }
     760             : 
     761             :     // Get credentials from sts AssumeRoleWithWebIdentity
     762           4 :     std::string osExpiration;
     763             :     {
     764             :         const std::string osSTS_asuume_role_with_web_identity_URL =
     765           8 :             osStsRootUrl +
     766             :             "/?Action=AssumeRoleWithWebIdentity&RoleSessionName=gdal"
     767           8 :             "&Version=2011-06-15&RoleArn=" +
     768          16 :             CPLAWSURLEncode(roleArn) +
     769          12 :             "&WebIdentityToken=" + CPLAWSURLEncode(webIdentityToken);
     770             : 
     771           4 :         CPLPushErrorHandler(CPLQuietErrorHandler);
     772             : 
     773           4 :         CPLHTTPResult *psResult = CPLHTTPFetch(
     774             :             osSTS_asuume_role_with_web_identity_URL.c_str(), nullptr);
     775           4 :         CPLPopErrorHandler();
     776           4 :         if (psResult)
     777             :         {
     778           4 :             if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     779             :             {
     780             :                 CPLXMLTreeCloser oTree(CPLParseXMLString(
     781           8 :                     reinterpret_cast<char *>(psResult->pabyData)));
     782           4 :                 if (oTree)
     783             :                 {
     784           4 :                     const auto psCredentials = CPLGetXMLNode(
     785             :                         oTree.get(),
     786             :                         "=AssumeRoleWithWebIdentityResponse."
     787             :                         "AssumeRoleWithWebIdentityResult.Credentials");
     788           4 :                     if (psCredentials)
     789             :                     {
     790             :                         osAccessKeyId =
     791           3 :                             CPLGetXMLValue(psCredentials, "AccessKeyId", "");
     792             :                         osSecretAccessKey = CPLGetXMLValue(
     793           3 :                             psCredentials, "SecretAccessKey", "");
     794             :                         osSessionToken =
     795           3 :                             CPLGetXMLValue(psCredentials, "SessionToken", "");
     796             :                         osExpiration =
     797           3 :                             CPLGetXMLValue(psCredentials, "Expiration", "");
     798             :                     }
     799             :                 }
     800             :             }
     801           4 :             CPLHTTPDestroyResult(psResult);
     802             :         }
     803             :     }
     804             : 
     805           4 :     GIntBig nExpirationUnix = 0;
     806           7 :     if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
     807          10 :         !osSessionToken.empty() &&
     808           3 :         Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
     809             :     {
     810           3 :         gosGlobalAccessKeyId = osAccessKeyId;
     811           3 :         gosGlobalSecretAccessKey = osSecretAccessKey;
     812           3 :         gosGlobalSessionToken = osSessionToken;
     813           3 :         gnGlobalExpiration = nExpirationUnix;
     814           3 :         CPLDebug(AWS_DEBUG_KEY, "Storing AIM credentials until %s",
     815             :                  osExpiration.c_str());
     816             :     }
     817           7 :     return !osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
     818           7 :            !osSessionToken.empty();
     819             : }
     820             : 
     821             : /************************************************************************/
     822             : /*                      GetConfigurationFromEC2()                       */
     823             : /************************************************************************/
     824             : 
     825          26 : bool VSIS3HandleHelper::GetConfigurationFromEC2(
     826             :     bool bForceRefresh, const std::string &osPathForOption,
     827             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
     828             :     std::string &osSessionToken)
     829             : {
     830          52 :     CPLMutexHolder oHolder(&ghMutex);
     831          26 :     if (!bForceRefresh)
     832             :     {
     833             :         time_t nCurTime;
     834          25 :         time(&nCurTime);
     835             :         // Try to reuse credentials if they are still valid, but
     836             :         // keep one minute of margin...
     837          25 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
     838             :         {
     839           9 :             osAccessKeyId = gosGlobalAccessKeyId;
     840           9 :             osSecretAccessKey = gosGlobalSecretAccessKey;
     841           9 :             osSessionToken = gosGlobalSessionToken;
     842           9 :             return true;
     843             :         }
     844             :     }
     845             : 
     846          34 :     std::string osURLRefreshCredentials;
     847          34 :     const std::string osEC2DefaultURL("http://169.254.169.254");
     848             :     // coverity[tainted_data]
     849             :     const std::string osEC2RootURL(VSIGetPathSpecificOption(
     850             :         osPathForOption.c_str(), "CPL_AWS_EC2_API_ROOT_URL",
     851          34 :         osEC2DefaultURL.c_str()));
     852             :     // coverity[tainted_data]
     853             :     std::string osECSFullURI(VSIGetPathSpecificOption(
     854          34 :         osPathForOption.c_str(), "AWS_CONTAINER_CREDENTIALS_FULL_URI", ""));
     855             :     // coverity[tainted_data]
     856             :     const std::string osECSRelativeURI(
     857          17 :         osECSFullURI.empty() ? VSIGetPathSpecificOption(
     858             :                                    osPathForOption.c_str(),
     859             :                                    "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "")
     860          34 :                              : std::string());
     861             :     // coverity[tainted_data]
     862             :     const std::string osECSTokenFile(
     863          14 :         (osECSFullURI.empty() && osECSRelativeURI.empty())
     864          17 :             ? std::string()
     865             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     866             :                                        "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
     867          51 :                                        ""));
     868             : 
     869             :     // coverity[tainted_data]
     870             :     std::string osECSTokenValue(
     871          14 :         (osECSFullURI.empty() && osECSRelativeURI.empty() &&
     872          14 :          !osECSTokenFile.empty())
     873          17 :             ? std::string()
     874             :             : VSIGetPathSpecificOption(osPathForOption.c_str(),
     875             :                                        "AWS_CONTAINER_AUTHORIZATION_TOKEN",
     876          51 :                                        ""));
     877             : 
     878          34 :     std::string osECSToken;
     879          17 :     if (!osECSTokenFile.empty())
     880             :     {
     881           1 :         if (!ReadAWSTokenFile(osECSTokenFile, osECSToken))
     882             :         {
     883           0 :             CPLDebug(AWS_DEBUG_KEY, "%s is empty", osECSTokenFile.c_str());
     884             :         }
     885             :     }
     886          16 :     else if (!osECSTokenValue.empty())
     887             :     {
     888           1 :         osECSToken = std::move(osECSTokenValue);
     889             :     }
     890             : 
     891          34 :     std::string osToken;
     892          17 :     if (!osECSFullURI.empty())
     893             :     {
     894             :         // Cf https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
     895           3 :         osURLRefreshCredentials = std::move(osECSFullURI);
     896             :     }
     897          14 :     else if (osEC2RootURL == osEC2DefaultURL && !osECSRelativeURI.empty())
     898             :     {
     899             :         // See
     900             :         // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
     901           0 :         osURLRefreshCredentials = "http://169.254.170.2" + osECSRelativeURI;
     902             :     }
     903             :     else
     904             :     {
     905          14 :         const auto eIsEC2 = IsMachinePotentiallyEC2Instance();
     906          14 :         if (eIsEC2 == EC2InstanceCertainty::NO)
     907           8 :             return false;
     908             : 
     909             :         // Use IMDSv2 protocol:
     910             :         // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
     911             : 
     912             :         // Retrieve IMDSv2 token
     913             :         {
     914             :             const std::string osEC2_IMDSv2_api_token_URL =
     915          12 :                 osEC2RootURL + "/latest/api/token";
     916          12 :             CPLStringList aosOptions;
     917           6 :             aosOptions.SetNameValue("TIMEOUT", "1");
     918           6 :             aosOptions.SetNameValue("CUSTOMREQUEST", "PUT");
     919             :             aosOptions.SetNameValue("HEADERS",
     920           6 :                                     "X-aws-ec2-metadata-token-ttl-seconds: 10");
     921           6 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     922           6 :             CPLHTTPResult *psResult = CPLHTTPFetch(
     923           6 :                 osEC2_IMDSv2_api_token_URL.c_str(), aosOptions.List());
     924           6 :             CPLPopErrorHandler();
     925           6 :             if (psResult)
     926             :             {
     927           6 :                 if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     928             :                 {
     929           4 :                     osToken = reinterpret_cast<char *>(psResult->pabyData);
     930             :                 }
     931             :                 else
     932             :                 {
     933             :                     // Failure: either we are not running on EC2 (or something
     934             :                     // emulating it) or this doesn't implement yet IMDSv2.
     935             :                     // Fallback to IMDSv1
     936             : 
     937             :                     // /latest/api/token doesn't work inside a Docker container
     938             :                     // that has no host networking. Cf
     939             :                     // https://community.grafana.com/t/imdsv2-is-not-working-from-docker/65944
     940           2 :                     if (psResult->pszErrBuf != nullptr &&
     941           2 :                         strstr(psResult->pszErrBuf,
     942             :                                "Operation timed out after") != nullptr)
     943             :                     {
     944           0 :                         aosOptions.Clear();
     945           0 :                         aosOptions.SetNameValue("TIMEOUT", "1");
     946           0 :                         CPLPushErrorHandler(CPLQuietErrorHandler);
     947           0 :                         CPLHTTPResult *psResult2 = CPLHTTPFetch(
     948           0 :                             (osEC2RootURL + "/latest/meta-data").c_str(),
     949           0 :                             aosOptions.List());
     950           0 :                         CPLPopErrorHandler();
     951           0 :                         if (psResult2)
     952             :                         {
     953           0 :                             if (psResult2->nStatus == 0 &&
     954           0 :                                 psResult2->pabyData != nullptr)
     955             :                             {
     956           0 :                                 CPLDebug(AWS_DEBUG_KEY,
     957             :                                          "/latest/api/token EC2 IMDSv2 request "
     958             :                                          "timed out, but /latest/metadata "
     959             :                                          "succeeded. "
     960             :                                          "Trying with IMDSv1. "
     961             :                                          "Consult "
     962             :                                          "https://gdal.org/user/"
     963             :                                          "virtual_file_systems.html#vsis3_imds "
     964             :                                          "for IMDS related issues.");
     965             :                             }
     966           0 :                             CPLHTTPDestroyResult(psResult2);
     967             :                         }
     968             :                     }
     969             :                 }
     970           6 :                 CPLHTTPDestroyResult(psResult);
     971             :             }
     972           6 :             CPLErrorReset();
     973             :         }
     974             : 
     975             :         // If we don't know yet the IAM role, fetch it
     976             :         const std::string osEC2CredentialsURL =
     977           6 :             osEC2RootURL + "/latest/meta-data/iam/security-credentials/";
     978           6 :         if (gosIAMRole.empty())
     979             :         {
     980           3 :             CPLStringList aosOptions;
     981           3 :             aosOptions.SetNameValue("TIMEOUT", "1");
     982           3 :             if (!osToken.empty())
     983             :             {
     984             :                 aosOptions.SetNameValue(
     985             :                     "HEADERS",
     986           2 :                     ("X-aws-ec2-metadata-token: " + osToken).c_str());
     987             :             }
     988           3 :             CPLPushErrorHandler(CPLQuietErrorHandler);
     989             :             CPLHTTPResult *psResult =
     990           3 :                 CPLHTTPFetch(osEC2CredentialsURL.c_str(), aosOptions.List());
     991           3 :             CPLPopErrorHandler();
     992           3 :             if (psResult)
     993             :             {
     994           3 :                 if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
     995             :                 {
     996           3 :                     gosIAMRole = reinterpret_cast<char *>(psResult->pabyData);
     997             :                 }
     998           3 :                 CPLHTTPDestroyResult(psResult);
     999             :             }
    1000           3 :             CPLErrorReset();
    1001           3 :             if (gosIAMRole.empty())
    1002             :             {
    1003             :                 // We didn't get the IAM role. We are definitely not running
    1004             :                 // on (a correctly configured) EC2 or an emulation of it.
    1005             : 
    1006           0 :                 if (eIsEC2 == EC2InstanceCertainty::YES)
    1007             :                 {
    1008           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1009             :                              "EC2 IMDSv2 and IMDSv1 requests failed. Consult "
    1010             :                              "https://gdal.org/user/"
    1011             :                              "virtual_file_systems.html#vsis3_imds "
    1012             :                              "for IMDS related issues.");
    1013             :                 }
    1014             : 
    1015           0 :                 return false;
    1016             :             }
    1017             :         }
    1018           6 :         osURLRefreshCredentials = osEC2CredentialsURL + gosIAMRole;
    1019             :     }
    1020             : 
    1021             :     // Now fetch the refreshed credentials
    1022          18 :     CPLStringList oResponse;
    1023          18 :     CPLStringList aosOptions;
    1024           9 :     if (!osToken.empty())
    1025             :     {
    1026             :         aosOptions.SetNameValue(
    1027           4 :             "HEADERS", ("X-aws-ec2-metadata-token: " + osToken).c_str());
    1028             :     }
    1029           5 :     else if (!osECSToken.empty())
    1030             :     {
    1031             :         aosOptions.SetNameValue("HEADERS",
    1032           2 :                                 ("Authorization: " + osECSToken).c_str());
    1033             :     }
    1034             :     CPLHTTPResult *psResult =
    1035           9 :         CPLHTTPFetch(osURLRefreshCredentials.c_str(), aosOptions.List());
    1036           9 :     if (psResult)
    1037             :     {
    1038           9 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
    1039             :         {
    1040             :             const std::string osJSon =
    1041           8 :                 reinterpret_cast<char *>(psResult->pabyData);
    1042           8 :             oResponse = CPLParseKeyValueJson(osJSon.c_str());
    1043             :         }
    1044           9 :         CPLHTTPDestroyResult(psResult);
    1045             :     }
    1046           9 :     CPLErrorReset();
    1047           9 :     osAccessKeyId = oResponse.FetchNameValueDef("AccessKeyId", "");
    1048           9 :     osSecretAccessKey = oResponse.FetchNameValueDef("SecretAccessKey", "");
    1049           9 :     osSessionToken = oResponse.FetchNameValueDef("Token", "");
    1050             :     const std::string osExpiration =
    1051           9 :         oResponse.FetchNameValueDef("Expiration", "");
    1052           9 :     GIntBig nExpirationUnix = 0;
    1053          17 :     if (!osAccessKeyId.empty() && !osSecretAccessKey.empty() &&
    1054           8 :         Iso8601ToUnixTime(osExpiration.c_str(), &nExpirationUnix))
    1055             :     {
    1056           8 :         gosGlobalAccessKeyId = osAccessKeyId;
    1057           8 :         gosGlobalSecretAccessKey = osSecretAccessKey;
    1058           8 :         gosGlobalSessionToken = osSessionToken;
    1059           8 :         gnGlobalExpiration = nExpirationUnix;
    1060           8 :         CPLDebug(AWS_DEBUG_KEY, "Storing AIM credentials until %s",
    1061             :                  osExpiration.c_str());
    1062             :     }
    1063           9 :     return !osAccessKeyId.empty() && !osSecretAccessKey.empty();
    1064             : }
    1065             : 
    1066             : /************************************************************************/
    1067             : /*                      UpdateAndWarnIfInconsistent()                   */
    1068             : /************************************************************************/
    1069             : 
    1070           6 : static void UpdateAndWarnIfInconsistent(const char *pszKeyword,
    1071             :                                         std::string &osVal,
    1072             :                                         const std::string &osNewVal,
    1073             :                                         const std::string &osCredentials,
    1074             :                                         const std::string &osConfig)
    1075             : {
    1076             :     // nominally defined in ~/.aws/credentials but can
    1077             :     // be set here too. If both values exist, credentials
    1078             :     // has the priority
    1079           6 :     if (osVal.empty())
    1080             :     {
    1081           2 :         osVal = osNewVal;
    1082             :     }
    1083           4 :     else if (osVal != osNewVal)
    1084             :     {
    1085           2 :         CPLError(CE_Warning, CPLE_AppDefined,
    1086             :                  "%s defined in both %s "
    1087             :                  "and %s. The one of %s will be used",
    1088             :                  pszKeyword, osCredentials.c_str(), osConfig.c_str(),
    1089             :                  osCredentials.c_str());
    1090             :     }
    1091           6 : }
    1092             : 
    1093             : /************************************************************************/
    1094             : /*                         ReadAWSCredentials()                         */
    1095             : /************************************************************************/
    1096             : 
    1097          30 : static bool ReadAWSCredentials(const std::string &osProfile,
    1098             :                                const std::string &osCredentials,
    1099             :                                std::string &osSecretAccessKey,
    1100             :                                std::string &osAccessKeyId,
    1101             :                                std::string &osSessionToken)
    1102             : {
    1103          30 :     osSecretAccessKey.clear();
    1104          30 :     osAccessKeyId.clear();
    1105          30 :     osSessionToken.clear();
    1106             : 
    1107          30 :     VSILFILE *fp = VSIFOpenL(osCredentials.c_str(), "rb");
    1108          30 :     if (fp != nullptr)
    1109             :     {
    1110             :         const char *pszLine;
    1111           9 :         bool bInProfile = false;
    1112          27 :         const std::string osBracketedProfile("[" + osProfile + "]");
    1113          49 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
    1114             :         {
    1115          44 :             if (pszLine[0] == '[')
    1116             :             {
    1117          15 :                 if (bInProfile)
    1118           4 :                     break;
    1119          11 :                 if (std::string(pszLine) == osBracketedProfile)
    1120           6 :                     bInProfile = true;
    1121             :             }
    1122          29 :             else if (bInProfile)
    1123             :             {
    1124          12 :                 char *pszKey = nullptr;
    1125          12 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
    1126          12 :                 if (pszKey && pszValue)
    1127             :                 {
    1128          12 :                     if (EQUAL(pszKey, "aws_access_key_id"))
    1129           6 :                         osAccessKeyId = pszValue;
    1130           6 :                     else if (EQUAL(pszKey, "aws_secret_access_key"))
    1131           6 :                         osSecretAccessKey = pszValue;
    1132           0 :                     else if (EQUAL(pszKey, "aws_session_token"))
    1133           0 :                         osSessionToken = pszValue;
    1134             :                 }
    1135          12 :                 CPLFree(pszKey);
    1136             :             }
    1137             :         }
    1138           9 :         VSIFCloseL(fp);
    1139             :     }
    1140             : 
    1141          30 :     return !osSecretAccessKey.empty() && !osAccessKeyId.empty();
    1142             : }
    1143             : 
    1144             : /************************************************************************/
    1145             : /*                         GetDirSeparator()                            */
    1146             : /************************************************************************/
    1147             : 
    1148          59 : static const char *GetDirSeparator()
    1149             : {
    1150             : #ifdef _WIN32
    1151             :     static const char SEP_STRING[] = "\\";
    1152             : #else
    1153             :     static const char SEP_STRING[] = "/";
    1154             : #endif
    1155          59 :     return SEP_STRING;
    1156             : }
    1157             : 
    1158             : /************************************************************************/
    1159             : /*                          GetAWSRootDirectory()                       */
    1160             : /************************************************************************/
    1161             : 
    1162          30 : static std::string GetAWSRootDirectory()
    1163             : {
    1164          30 :     const char *pszAWSRootDir = CPLGetConfigOption("CPL_AWS_ROOT_DIR", nullptr);
    1165          30 :     if (pszAWSRootDir)
    1166           2 :         return pszAWSRootDir;
    1167             : #ifdef _WIN32
    1168             :     const char *pszHome = CPLGetConfigOption("USERPROFILE", nullptr);
    1169             : #else
    1170          28 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
    1171             : #endif
    1172             : 
    1173          56 :     return std::string(pszHome ? pszHome : "")
    1174          28 :         .append(GetDirSeparator())
    1175          28 :         .append(".aws");
    1176             : }
    1177             : 
    1178             : /************************************************************************/
    1179             : /*                GetConfigurationFromAWSConfigFiles()                  */
    1180             : /************************************************************************/
    1181             : 
    1182          29 : bool VSIS3HandleHelper::GetConfigurationFromAWSConfigFiles(
    1183             :     const std::string &osPathForOption, const char *pszProfile,
    1184             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
    1185             :     std::string &osSessionToken, std::string &osRegion,
    1186             :     std::string &osCredentials, std::string &osRoleArn,
    1187             :     std::string &osSourceProfile, std::string &osExternalId,
    1188             :     std::string &osMFASerial, std::string &osRoleSessionName,
    1189             :     std::string &osWebIdentityTokenFile, std::string &osSSOStartURL,
    1190             :     std::string &osSSOAccountID, std::string &osSSORoleName,
    1191             :     std::string &osSSOSession)
    1192             : {
    1193             :     // See http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
    1194             :     // If AWS_DEFAULT_PROFILE is set (obsolete, no longer documented), use it in
    1195             :     // priority Otherwise use AWS_PROFILE Otherwise fallback to "default"
    1196          29 :     const char *pszProfileOri = pszProfile;
    1197          29 :     if (pszProfile == nullptr)
    1198             :     {
    1199          27 :         pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
    1200             :                                               "AWS_DEFAULT_PROFILE", "");
    1201          27 :         if (pszProfile[0] == '\0')
    1202          27 :             pszProfile = VSIGetPathSpecificOption(osPathForOption.c_str(),
    1203             :                                                   "AWS_PROFILE", "");
    1204             :     }
    1205          58 :     const std::string osProfile(pszProfile[0] != '\0' ? pszProfile : "default");
    1206             : 
    1207          58 :     std::string osDotAws(GetAWSRootDirectory());
    1208             : 
    1209             :     // Read first ~/.aws/credential file
    1210             : 
    1211             :     // GDAL specific config option (mostly for testing purpose, but also
    1212             :     // used in production in some cases)
    1213          29 :     const char *pszCredentials = VSIGetPathSpecificOption(
    1214             :         osPathForOption.c_str(), "CPL_AWS_CREDENTIALS_FILE", nullptr);
    1215          29 :     if (pszCredentials)
    1216             :     {
    1217          23 :         osCredentials = pszCredentials;
    1218             :     }
    1219             :     else
    1220             :     {
    1221           6 :         osCredentials = osDotAws;
    1222           6 :         osCredentials += GetDirSeparator();
    1223           6 :         osCredentials += "credentials";
    1224             :     }
    1225             : 
    1226          29 :     ReadAWSCredentials(osProfile, osCredentials, osSecretAccessKey,
    1227             :                        osAccessKeyId, osSessionToken);
    1228             : 
    1229             :     // And then ~/.aws/config file (unless AWS_CONFIG_FILE is defined)
    1230          29 :     const char *pszAWSConfigFileEnv = VSIGetPathSpecificOption(
    1231             :         osPathForOption.c_str(), "AWS_CONFIG_FILE", nullptr);
    1232          29 :     std::string osConfig;
    1233          29 :     if (pszAWSConfigFileEnv && pszAWSConfigFileEnv[0])
    1234             :     {
    1235           7 :         osConfig = pszAWSConfigFileEnv;
    1236             :     }
    1237             :     else
    1238             :     {
    1239          22 :         osConfig = std::move(osDotAws);
    1240          22 :         osConfig += GetDirSeparator();
    1241          22 :         osConfig += "config";
    1242             :     }
    1243             : 
    1244          29 :     VSILFILE *fp = VSIFOpenL(osConfig.c_str(), "rb");
    1245          29 :     if (fp != nullptr)
    1246             :     {
    1247             :         // Start by reading sso-session's
    1248             :         const char *pszLine;
    1249             :         std::map<std::string, std::map<std::string, std::string>>
    1250          18 :             oMapSSOSessions;
    1251          92 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
    1252             :         {
    1253          83 :             if (STARTS_WITH(pszLine, "[sso-session ") &&
    1254           1 :                 pszLine[strlen(pszLine) - 1] == ']')
    1255             :             {
    1256           1 :                 osSSOSession = pszLine + strlen("[sso-session ");
    1257           1 :                 osSSOSession.pop_back();
    1258             :             }
    1259          82 :             else if (pszLine[0] == '[')
    1260             :             {
    1261          19 :                 osSSOSession.clear();
    1262             :             }
    1263          63 :             else if (!osSSOSession.empty())
    1264             :             {
    1265           4 :                 char *pszKey = nullptr;
    1266           4 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
    1267           4 :                 if (pszKey && pszValue)
    1268             :                 {
    1269             :                     // CPLDebugOnly(AWS_DEBUG_KEY, "oMapSSOSessions[%s][%s] = %s",
    1270             :                     //              osSSOSession.c_str(), pszKey, pszValue);
    1271           3 :                     oMapSSOSessions[osSSOSession][pszKey] = pszValue;
    1272             :                 }
    1273           4 :                 CPLFree(pszKey);
    1274             :             }
    1275             :         }
    1276           9 :         osSSOSession.clear();
    1277             : 
    1278           9 :         bool bInProfile = false;
    1279          18 :         const std::string osBracketedProfile("[" + osProfile + "]");
    1280           9 :         const std::string osBracketedProfileProfile("[profile " + osProfile +
    1281          18 :                                                     "]");
    1282             : 
    1283           9 :         VSIFSeekL(fp, 0, SEEK_SET);
    1284             : 
    1285          77 :         while ((pszLine = CPLReadLineL(fp)) != nullptr)
    1286             :         {
    1287          73 :             if (pszLine[0] == '[')
    1288             :             {
    1289          20 :                 if (bInProfile)
    1290           5 :                     break;
    1291             :                 // In config file, the section name is nominally [profile foo]
    1292             :                 // for the non default profile.
    1293          42 :                 if (std::string(pszLine) == osBracketedProfile ||
    1294          27 :                     std::string(pszLine) == osBracketedProfileProfile)
    1295             :                 {
    1296           8 :                     bInProfile = true;
    1297             :                 }
    1298             :             }
    1299          53 :             else if (bInProfile)
    1300             :             {
    1301          24 :                 char *pszKey = nullptr;
    1302          24 :                 const char *pszValue = CPLParseNameValue(pszLine, &pszKey);
    1303          24 :                 if (pszKey && pszValue)
    1304             :                 {
    1305          23 :                     if (EQUAL(pszKey, "aws_access_key_id"))
    1306             :                     {
    1307           3 :                         UpdateAndWarnIfInconsistent(pszKey, osAccessKeyId,
    1308             :                                                     pszValue, osCredentials,
    1309             :                                                     osConfig);
    1310             :                     }
    1311          20 :                     else if (EQUAL(pszKey, "aws_secret_access_key"))
    1312             :                     {
    1313           3 :                         UpdateAndWarnIfInconsistent(pszKey, osSecretAccessKey,
    1314             :                                                     pszValue, osCredentials,
    1315             :                                                     osConfig);
    1316             :                     }
    1317          17 :                     else if (EQUAL(pszKey, "aws_session_token"))
    1318             :                     {
    1319           0 :                         UpdateAndWarnIfInconsistent(pszKey, osSessionToken,
    1320             :                                                     pszValue, osCredentials,
    1321             :                                                     osConfig);
    1322             :                     }
    1323          17 :                     else if (EQUAL(pszKey, "region"))
    1324             :                     {
    1325           5 :                         osRegion = pszValue;
    1326             :                     }
    1327          12 :                     else if (strcmp(pszKey, "role_arn") == 0)
    1328             :                     {
    1329           3 :                         osRoleArn = pszValue;
    1330             :                     }
    1331           9 :                     else if (strcmp(pszKey, "source_profile") == 0)
    1332             :                     {
    1333           2 :                         osSourceProfile = pszValue;
    1334             :                     }
    1335           7 :                     else if (strcmp(pszKey, "external_id") == 0)
    1336             :                     {
    1337           1 :                         osExternalId = pszValue;
    1338             :                     }
    1339           6 :                     else if (strcmp(pszKey, "mfa_serial") == 0)
    1340             :                     {
    1341           1 :                         osMFASerial = pszValue;
    1342             :                     }
    1343           5 :                     else if (strcmp(pszKey, "role_session_name") == 0)
    1344             :                     {
    1345           1 :                         osRoleSessionName = pszValue;
    1346             :                     }
    1347           4 :                     else if (strcmp(pszKey, "web_identity_token_file") == 0)
    1348             :                     {
    1349           1 :                         osWebIdentityTokenFile = pszValue;
    1350             :                     }
    1351           3 :                     else if (strcmp(pszKey, "sso_session") == 0)
    1352             :                     {
    1353           1 :                         osSSOSession = pszValue;
    1354             :                     }
    1355           2 :                     else if (strcmp(pszKey, "sso_start_url") == 0)
    1356             :                     {
    1357           0 :                         osSSOStartURL = pszValue;
    1358             :                     }
    1359           2 :                     else if (strcmp(pszKey, "sso_account_id") == 0)
    1360             :                     {
    1361           1 :                         osSSOAccountID = pszValue;
    1362             :                     }
    1363           1 :                     else if (strcmp(pszKey, "sso_role_name") == 0)
    1364             :                     {
    1365           1 :                         osSSORoleName = pszValue;
    1366             :                     }
    1367             :                 }
    1368          24 :                 CPLFree(pszKey);
    1369             :             }
    1370             :         }
    1371           9 :         VSIFCloseL(fp);
    1372             : 
    1373           9 :         if (!osSSOSession.empty())
    1374             :         {
    1375           1 :             if (osSSOStartURL.empty())
    1376           1 :                 osSSOStartURL = oMapSSOSessions[osSSOSession]["sso_start_url"];
    1377             :         }
    1378             :     }
    1379          20 :     else if (pszAWSConfigFileEnv != nullptr)
    1380             :     {
    1381          15 :         if (pszAWSConfigFileEnv[0] != '\0')
    1382             :         {
    1383           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1384             :                      "%s does not exist or cannot be open",
    1385             :                      pszAWSConfigFileEnv);
    1386             :         }
    1387             :     }
    1388             : 
    1389          35 :     return (!osAccessKeyId.empty() && !osSecretAccessKey.empty()) ||
    1390          23 :            (!osRoleArn.empty() && !osSourceProfile.empty()) ||
    1391           1 :            (pszProfileOri != nullptr && !osRoleArn.empty() &&
    1392          58 :             !osWebIdentityTokenFile.empty()) ||
    1393          20 :            (!osSSOStartURL.empty() && !osSSOAccountID.empty() &&
    1394          59 :             !osSSORoleName.empty());
    1395             : }
    1396             : 
    1397             : /************************************************************************/
    1398             : /*                     GetTemporaryCredentialsForRole()                 */
    1399             : /************************************************************************/
    1400             : 
    1401             : // Issue a STS AssumedRole operation to get temporary credentials for an assumed
    1402             : // role.
    1403           5 : static bool GetTemporaryCredentialsForRole(
    1404             :     const std::string &osRoleArn, const std::string &osExternalId,
    1405             :     const std::string &osMFASerial, const std::string &osRoleSessionName,
    1406             :     const std::string &osSecretAccessKey, const std::string &osAccessKeyId,
    1407             :     const std::string &osSessionToken, std::string &osTempSecretAccessKey,
    1408             :     std::string &osTempAccessKeyId, std::string &osTempSessionToken,
    1409             :     std::string &osExpiration)
    1410             : {
    1411          10 :     std::string osXAMZDate = CPLGetConfigOption("AWS_TIMESTAMP", "");
    1412           5 :     if (osXAMZDate.empty())
    1413           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    1414          10 :     std::string osDate(osXAMZDate);
    1415           5 :     osDate.resize(8);
    1416             : 
    1417          10 :     const std::string osVerb("GET");
    1418          10 :     const std::string osService("sts");
    1419             :     const std::string osRegion(
    1420          10 :         CPLGetConfigOption("AWS_STS_REGION", "us-east-1"));
    1421             :     const std::string osHost(
    1422          10 :         CPLGetConfigOption("AWS_STS_ENDPOINT", "sts.amazonaws.com"));
    1423             : 
    1424          10 :     std::map<std::string, std::string> oMap;
    1425           5 :     oMap["Version"] = "2011-06-15";
    1426           5 :     oMap["Action"] = "AssumeRole";
    1427           5 :     oMap["RoleArn"] = osRoleArn;
    1428          10 :     oMap["RoleSessionName"] =
    1429           5 :         !osRoleSessionName.empty()
    1430           3 :             ? osRoleSessionName.c_str()
    1431          10 :             : CPLGetConfigOption("AWS_ROLE_SESSION_NAME", "GDAL-session");
    1432           5 :     if (!osExternalId.empty())
    1433           3 :         oMap["ExternalId"] = osExternalId;
    1434           5 :     if (!osMFASerial.empty())
    1435           3 :         oMap["SerialNumber"] = osMFASerial;
    1436             : 
    1437          10 :     std::string osQueryString;
    1438          31 :     for (const auto &kv : oMap)
    1439             :     {
    1440          26 :         if (osQueryString.empty())
    1441           5 :             osQueryString += "?";
    1442             :         else
    1443          21 :             osQueryString += "&";
    1444          26 :         osQueryString += kv.first;
    1445          26 :         osQueryString += "=";
    1446          26 :         osQueryString += CPLAWSURLEncode(kv.second);
    1447             :     }
    1448          10 :     std::string osCanonicalQueryString(osQueryString.substr(1));
    1449             : 
    1450           5 :     struct curl_slist *psHeaders = nullptr;
    1451           5 :     if (!osSessionToken.empty())
    1452           2 :         psHeaders =
    1453           2 :             curl_slist_append(psHeaders, CPLSPrintf("X-Amz-Security-Token: %s",
    1454             :                                                     osSessionToken.c_str()));
    1455             : 
    1456             :     const std::string osAuthorization = CPLGetAWS_SIGN4_Authorization(
    1457             :         osSecretAccessKey, osAccessKeyId, osRegion, osService, osVerb,
    1458             :         psHeaders, osHost, "/", osCanonicalQueryString,
    1459          10 :         CPLGetLowerCaseHexSHA256(std::string()),
    1460             :         false,  // bAddHeaderAMZContentSHA256
    1461          15 :         osXAMZDate);
    1462             : 
    1463           5 :     curl_slist_free_all(psHeaders);
    1464             : 
    1465           5 :     bool bRet = false;
    1466           5 :     const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES"));
    1467             : 
    1468          10 :     CPLStringList aosOptions;
    1469          10 :     std::string headers;
    1470           5 :     if (!osSessionToken.empty())
    1471           2 :         headers += "X-Amz-Security-Token: " + osSessionToken + "\r\n";
    1472           5 :     headers += "X-Amz-Date: " + osXAMZDate + "\r\n";
    1473           5 :     headers += "Authorization: " + osAuthorization;
    1474           5 :     aosOptions.AddNameValue("HEADERS", headers.c_str());
    1475             : 
    1476             :     const std::string osURL =
    1477          10 :         (bUseHTTPS ? "https://" : "http://") + osHost + "/" + osQueryString;
    1478           5 :     CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
    1479           5 :     if (psResult)
    1480             :     {
    1481           5 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr)
    1482             :         {
    1483             :             CPLXMLTreeCloser oTree(CPLParseXMLString(
    1484          10 :                 reinterpret_cast<char *>(psResult->pabyData)));
    1485           5 :             if (oTree)
    1486             :             {
    1487           5 :                 const auto psCredentials = CPLGetXMLNode(
    1488             :                     oTree.get(),
    1489             :                     "=AssumeRoleResponse.AssumeRoleResult.Credentials");
    1490           5 :                 if (psCredentials)
    1491             :                 {
    1492             :                     osTempAccessKeyId =
    1493           5 :                         CPLGetXMLValue(psCredentials, "AccessKeyId", "");
    1494             :                     osTempSecretAccessKey =
    1495           5 :                         CPLGetXMLValue(psCredentials, "SecretAccessKey", "");
    1496             :                     osTempSessionToken =
    1497           5 :                         CPLGetXMLValue(psCredentials, "SessionToken", "");
    1498             :                     osExpiration =
    1499           5 :                         CPLGetXMLValue(psCredentials, "Expiration", "");
    1500           5 :                     bRet = true;
    1501             :                 }
    1502             :                 else
    1503             :                 {
    1504           0 :                     CPLDebug(AWS_DEBUG_KEY, "%s",
    1505           0 :                              reinterpret_cast<char *>(psResult->pabyData));
    1506             :                 }
    1507             :             }
    1508             :         }
    1509           5 :         CPLHTTPDestroyResult(psResult);
    1510             :     }
    1511          10 :     return bRet;
    1512             : }
    1513             : 
    1514             : /************************************************************************/
    1515             : /*                     GetTemporaryCredentialsForSSO()                  */
    1516             : /************************************************************************/
    1517             : 
    1518             : // Issue a GetRoleCredentials request
    1519           1 : static bool GetTemporaryCredentialsForSSO(
    1520             :     const std::string &osSSOStartURL, const std::string &osSSOSession,
    1521             :     const std::string &osSSOAccountID, const std::string &osSSORoleName,
    1522             :     std::string &osTempSecretAccessKey, std::string &osTempAccessKeyId,
    1523             :     std::string &osTempSessionToken, std::string &osExpirationEpochInMS)
    1524             : {
    1525           2 :     std::string osSSOFilename = GetAWSRootDirectory();
    1526           1 :     osSSOFilename += GetDirSeparator();
    1527           1 :     osSSOFilename += "sso";
    1528           1 :     osSSOFilename += GetDirSeparator();
    1529           1 :     osSSOFilename += "cache";
    1530           1 :     osSSOFilename += GetDirSeparator();
    1531             : 
    1532           2 :     std::string hashValue = osSSOStartURL;
    1533           1 :     if (!osSSOSession.empty())
    1534             :     {
    1535           1 :         hashValue = osSSOSession;
    1536             :     }
    1537             : 
    1538             :     GByte hash[CPL_SHA1_HASH_SIZE];
    1539           1 :     CPL_SHA1(hashValue.data(), hashValue.size(), hash);
    1540           1 :     osSSOFilename += CPLGetLowerCaseHex(hash, sizeof(hash));
    1541           1 :     osSSOFilename += ".json";
    1542             : 
    1543           2 :     CPLJSONDocument oDoc;
    1544           1 :     if (!oDoc.Load(osSSOFilename))
    1545             :     {
    1546           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find file %s",
    1547             :                  osSSOFilename.c_str());
    1548           0 :         return false;
    1549             :     }
    1550             : 
    1551           2 :     const auto oRoot = oDoc.GetRoot();
    1552           3 :     const auto osGotStartURL = oRoot.GetString("startUrl");
    1553           1 :     if (osGotStartURL != osSSOStartURL)
    1554             :     {
    1555           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1556             :                  "startUrl in %s = '%s', but expected '%s'.",
    1557             :                  osSSOFilename.c_str(), osGotStartURL.c_str(),
    1558             :                  osSSOStartURL.c_str());
    1559           0 :         return false;
    1560             :     }
    1561           3 :     const std::string osAccessToken = oRoot.GetString("accessToken");
    1562           1 :     if (osAccessToken.empty())
    1563             :     {
    1564           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing accessToken in %s",
    1565             :                  osSSOFilename.c_str());
    1566           0 :         return false;
    1567             :     }
    1568             : 
    1569           3 :     const std::string osExpiresAt = oRoot.GetString("expiresAt");
    1570           1 :     if (!osExpiresAt.empty())
    1571             :     {
    1572           1 :         GIntBig nExpirationUnix = 0;
    1573           2 :         if (Iso8601ToUnixTime(osExpiresAt.c_str(), &nExpirationUnix) &&
    1574           1 :             time(nullptr) > nExpirationUnix)
    1575             :         {
    1576           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1577             :                      "accessToken in %s is no longer valid since %s. You may "
    1578             :                      "need to sign again using aws cli",
    1579             :                      osSSOFilename.c_str(), osExpiresAt.c_str());
    1580           0 :             return false;
    1581             :         }
    1582             :     }
    1583             : 
    1584           2 :     std::string osResourceAndQueryString = "/federation/credentials?role_name=";
    1585           1 :     osResourceAndQueryString += osSSORoleName;
    1586           1 :     osResourceAndQueryString += "&account_id=";
    1587           1 :     osResourceAndQueryString += osSSOAccountID;
    1588             : 
    1589           2 :     CPLStringList aosOptions;
    1590           2 :     std::string headers;
    1591           1 :     headers += "x-amz-sso_bearer_token: " + osAccessToken;
    1592           1 :     aosOptions.AddNameValue("HEADERS", headers.c_str());
    1593             : 
    1594           3 :     const std::string osRegion = oRoot.GetString("region", "us-east-1");
    1595           1 :     const std::string osDefaultHost("portal.sso." + osRegion +
    1596           2 :                                     ".amazonaws.com");
    1597             : 
    1598           1 :     const bool bUseHTTPS = CPLTestBool(CPLGetConfigOption("AWS_HTTPS", "YES"));
    1599             :     const std::string osHost(
    1600           2 :         CPLGetConfigOption("CPL_AWS_SSO_ENDPOINT", osDefaultHost.c_str()));
    1601             : 
    1602           1 :     const std::string osURL = (bUseHTTPS ? "https://" : "http://") + osHost +
    1603           1 :                               osResourceAndQueryString;
    1604           1 :     CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), aosOptions.List());
    1605           1 :     bool bRet = false;
    1606           1 :     if (psResult)
    1607             :     {
    1608           2 :         if (psResult->nStatus == 0 && psResult->pabyData != nullptr &&
    1609           2 :             oDoc.LoadMemory(reinterpret_cast<char *>(psResult->pabyData)))
    1610             :         {
    1611           2 :             auto oRoleCredentials = oDoc.GetRoot().GetObj("roleCredentials");
    1612           1 :             osTempAccessKeyId = oRoleCredentials.GetString("accessKeyId");
    1613             :             osTempSecretAccessKey =
    1614           1 :                 oRoleCredentials.GetString("secretAccessKey");
    1615           1 :             osTempSessionToken = oRoleCredentials.GetString("sessionToken");
    1616           1 :             osExpirationEpochInMS = oRoleCredentials.GetString("expiration");
    1617           1 :             bRet =
    1618           2 :                 !osTempAccessKeyId.empty() && !osTempSecretAccessKey.empty() &&
    1619           2 :                 !osTempSessionToken.empty() && !osExpirationEpochInMS.empty();
    1620             :         }
    1621           1 :         CPLHTTPDestroyResult(psResult);
    1622             :     }
    1623           1 :     if (!bRet)
    1624             :     {
    1625           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1626             :                  "Did not manage to get temporary credentials for SSO "
    1627             :                  "authentication");
    1628             :     }
    1629           1 :     return bRet;
    1630             : }
    1631             : 
    1632             : /************************************************************************/
    1633             : /*               GetOrRefreshTemporaryCredentialsForRole()              */
    1634             : /************************************************************************/
    1635             : 
    1636          10 : bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForRole(
    1637             :     bool bForceRefresh, std::string &osSecretAccessKey,
    1638             :     std::string &osAccessKeyId, std::string &osSessionToken,
    1639             :     std::string &osRegion)
    1640             : {
    1641          20 :     CPLMutexHolder oHolder(&ghMutex);
    1642          10 :     if (!bForceRefresh)
    1643             :     {
    1644             :         time_t nCurTime;
    1645          10 :         time(&nCurTime);
    1646             :         // Try to reuse credentials if they are still valid, but
    1647             :         // keep one minute of margin...
    1648          10 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
    1649             :         {
    1650           6 :             osAccessKeyId = gosGlobalAccessKeyId;
    1651           6 :             osSecretAccessKey = gosGlobalSecretAccessKey;
    1652           6 :             osSessionToken = gosGlobalSessionToken;
    1653           6 :             osRegion = gosRegion;
    1654           6 :             return true;
    1655             :         }
    1656             :     }
    1657             : 
    1658           4 :     if (!gosRoleArnWebIdentity.empty())
    1659             :     {
    1660           2 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1661           4 :                 bForceRefresh, std::string(), gosRoleArnWebIdentity,
    1662             :                 gosWebIdentityTokenFile, osSecretAccessKey, osAccessKeyId,
    1663             :                 osSessionToken))
    1664             :         {
    1665           1 :             gosSourceProfileSecretAccessKey = osSecretAccessKey;
    1666           1 :             gosSourceProfileAccessKeyId = osAccessKeyId;
    1667           1 :             gosSourceProfileSessionToken = osSessionToken;
    1668             :         }
    1669             :         else
    1670             :         {
    1671           1 :             return false;
    1672             :         }
    1673             :     }
    1674             : 
    1675           3 :     if (!gosRoleArn.empty())
    1676             :     {
    1677           3 :         std::string osExpiration;
    1678           3 :         gosGlobalSecretAccessKey.clear();
    1679           3 :         gosGlobalAccessKeyId.clear();
    1680           3 :         gosGlobalSessionToken.clear();
    1681           3 :         if (GetTemporaryCredentialsForRole(
    1682             :                 gosRoleArn, gosExternalId, gosMFASerial, gosRoleSessionName,
    1683             :                 gosSourceProfileSecretAccessKey, gosSourceProfileAccessKeyId,
    1684             :                 gosSourceProfileSessionToken, gosGlobalSecretAccessKey,
    1685             :                 gosGlobalAccessKeyId, gosGlobalSessionToken, osExpiration))
    1686             :         {
    1687           3 :             Iso8601ToUnixTime(osExpiration.c_str(), &gnGlobalExpiration);
    1688           3 :             osAccessKeyId = gosGlobalAccessKeyId;
    1689           3 :             osSecretAccessKey = gosGlobalSecretAccessKey;
    1690           3 :             osSessionToken = gosGlobalSessionToken;
    1691           3 :             osRegion = gosRegion;
    1692           3 :             return true;
    1693             :         }
    1694             :     }
    1695             : 
    1696           0 :     return false;
    1697             : }
    1698             : 
    1699             : /************************************************************************/
    1700             : /*               GetOrRefreshTemporaryCredentialsForSSO()               */
    1701             : /************************************************************************/
    1702             : 
    1703           3 : bool VSIS3HandleHelper::GetOrRefreshTemporaryCredentialsForSSO(
    1704             :     bool bForceRefresh, std::string &osSecretAccessKey,
    1705             :     std::string &osAccessKeyId, std::string &osSessionToken,
    1706             :     std::string &osRegion)
    1707             : {
    1708           6 :     CPLMutexHolder oHolder(&ghMutex);
    1709           3 :     if (!bForceRefresh)
    1710             :     {
    1711             :         time_t nCurTime;
    1712           3 :         time(&nCurTime);
    1713             :         // Try to reuse credentials if they are still valid, but
    1714             :         // keep one minute of margin...
    1715           3 :         if (!gosGlobalAccessKeyId.empty() && nCurTime < gnGlobalExpiration - 60)
    1716             :         {
    1717           3 :             osAccessKeyId = gosGlobalAccessKeyId;
    1718           3 :             osSecretAccessKey = gosGlobalSecretAccessKey;
    1719           3 :             osSessionToken = gosGlobalSessionToken;
    1720           3 :             osRegion = gosRegion;
    1721           3 :             return true;
    1722             :         }
    1723             :     }
    1724             : 
    1725           0 :     if (!gosSSOStartURL.empty())
    1726             :     {
    1727           0 :         std::string osExpirationEpochInMS;
    1728           0 :         gosGlobalSecretAccessKey.clear();
    1729           0 :         gosGlobalAccessKeyId.clear();
    1730           0 :         gosGlobalSessionToken.clear();
    1731           0 :         if (GetTemporaryCredentialsForSSO(
    1732             :                 gosSSOStartURL, "", gosSSOAccountID, gosSSORoleName,
    1733             :                 gosGlobalSecretAccessKey, gosGlobalAccessKeyId,
    1734             :                 gosGlobalSessionToken, osExpirationEpochInMS))
    1735             :         {
    1736           0 :             gnGlobalExpiration =
    1737           0 :                 CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000;
    1738           0 :             osAccessKeyId = gosGlobalAccessKeyId;
    1739           0 :             osSecretAccessKey = gosGlobalSecretAccessKey;
    1740           0 :             osSessionToken = gosGlobalSessionToken;
    1741           0 :             osRegion = gosRegion;
    1742           0 :             return true;
    1743             :         }
    1744             :     }
    1745             : 
    1746           0 :     return false;
    1747             : }
    1748             : 
    1749             : /************************************************************************/
    1750             : /*                        GetConfiguration()                            */
    1751             : /************************************************************************/
    1752             : 
    1753         567 : bool VSIS3HandleHelper::GetConfiguration(
    1754             :     const std::string &osPathForOption, CSLConstList papszOptions,
    1755             :     std::string &osSecretAccessKey, std::string &osAccessKeyId,
    1756             :     std::string &osSessionToken, std::string &osRegion,
    1757             :     AWSCredentialsSource &eCredentialsSource)
    1758             : {
    1759         567 :     eCredentialsSource = AWSCredentialsSource::REGULAR;
    1760             : 
    1761             :     // AWS_REGION is GDAL specific. Later overloaded by standard
    1762             :     // AWS_DEFAULT_REGION
    1763             :     osRegion = CSLFetchNameValueDef(
    1764             :         papszOptions, "AWS_REGION",
    1765             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_REGION",
    1766         567 :                                  "us-east-1"));
    1767             : 
    1768         567 :     if (CPLTestBool(VSIGetPathSpecificOption(osPathForOption.c_str(),
    1769             :                                              "AWS_NO_SIGN_REQUEST", "NO")))
    1770             :     {
    1771          31 :         osSecretAccessKey.clear();
    1772          31 :         osAccessKeyId.clear();
    1773          31 :         osSessionToken.clear();
    1774          31 :         return true;
    1775             :     }
    1776             : 
    1777             :     osSecretAccessKey = CSLFetchNameValueDef(
    1778             :         papszOptions, "AWS_SECRET_ACCESS_KEY",
    1779             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
    1780         536 :                                  "AWS_SECRET_ACCESS_KEY", ""));
    1781         536 :     if (!osSecretAccessKey.empty())
    1782             :     {
    1783             :         osAccessKeyId = CSLFetchNameValueDef(
    1784             :             papszOptions, "AWS_ACCESS_KEY_ID",
    1785             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
    1786         504 :                                      "AWS_ACCESS_KEY_ID", ""));
    1787         504 :         if (osAccessKeyId.empty())
    1788             :         {
    1789           1 :             VSIError(VSIE_InvalidCredentials,
    1790             :                      "AWS_ACCESS_KEY_ID configuration option not defined");
    1791           1 :             return false;
    1792             :         }
    1793             : 
    1794             :         osSessionToken = CSLFetchNameValueDef(
    1795             :             papszOptions, "AWS_SESSION_TOKEN",
    1796             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
    1797         503 :                                      "AWS_SESSION_TOKEN", ""));
    1798         503 :         return true;
    1799             :     }
    1800             : 
    1801             :     // Next try to see if we have a current assumed role
    1802          32 :     bool bAssumedRole = false;
    1803          32 :     bool bSSO = false;
    1804             :     {
    1805          32 :         CPLMutexHolder oHolder(&ghMutex);
    1806          32 :         bAssumedRole = !gosRoleArn.empty();
    1807          32 :         bSSO = !gosSSOStartURL.empty();
    1808             :     }
    1809          32 :     if (bAssumedRole && GetOrRefreshTemporaryCredentialsForRole(
    1810             :                             /* bForceRefresh = */ false, osSecretAccessKey,
    1811             :                             osAccessKeyId, osSessionToken, osRegion))
    1812             :     {
    1813           4 :         eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
    1814           4 :         return true;
    1815             :     }
    1816          28 :     else if (bSSO && GetOrRefreshTemporaryCredentialsForSSO(
    1817             :                          /* bForceRefresh = */ false, osSecretAccessKey,
    1818             :                          osAccessKeyId, osSessionToken, osRegion))
    1819             :     {
    1820           1 :         eCredentialsSource = AWSCredentialsSource::SSO;
    1821           1 :         return true;
    1822             :     }
    1823             : 
    1824             :     // Next try reading from ~/.aws/credentials and ~/.aws/config
    1825          54 :     std::string osCredentials;
    1826          54 :     std::string osRoleArn;
    1827          54 :     std::string osSourceProfile;
    1828          54 :     std::string osExternalId;
    1829          54 :     std::string osMFASerial;
    1830          54 :     std::string osRoleSessionName;
    1831          54 :     std::string osWebIdentityTokenFile;
    1832          54 :     std::string osSSOStartURL;
    1833          54 :     std::string osSSOAccountID;
    1834          54 :     std::string osSSORoleName;
    1835          54 :     std::string osSSOSession;
    1836             :     // coverity[tainted_data]
    1837          27 :     if (GetConfigurationFromAWSConfigFiles(
    1838             :             osPathForOption,
    1839             :             /* pszProfile = */ nullptr, osSecretAccessKey, osAccessKeyId,
    1840             :             osSessionToken, osRegion, osCredentials, osRoleArn, osSourceProfile,
    1841             :             osExternalId, osMFASerial, osRoleSessionName,
    1842             :             osWebIdentityTokenFile, osSSOStartURL, osSSOAccountID,
    1843             :             osSSORoleName, osSSOSession))
    1844             :     {
    1845           8 :         if (osSecretAccessKey.empty() && !osRoleArn.empty())
    1846             :         {
    1847             :             // Check if the default profile is pointing to another profile
    1848             :             // that has a role_arn and web_identity_token_file settings.
    1849           2 :             if (!osSourceProfile.empty())
    1850             :             {
    1851           4 :                 std::string osSecretAccessKeySP;
    1852           4 :                 std::string osAccessKeyIdSP;
    1853           4 :                 std::string osSessionTokenSP;
    1854           4 :                 std::string osRegionSP;
    1855           4 :                 std::string osCredentialsSP;
    1856           4 :                 std::string osRoleArnSP;
    1857           4 :                 std::string osSourceProfileSP;
    1858           4 :                 std::string osExternalIdSP;
    1859           4 :                 std::string osMFASerialSP;
    1860           4 :                 std::string osRoleSessionNameSP;
    1861           4 :                 std::string osSSOStartURLSP;
    1862           4 :                 std::string osSSOAccountIDSP;
    1863           4 :                 std::string osSSORoleNameSP;
    1864           2 :                 if (GetConfigurationFromAWSConfigFiles(
    1865             :                         osPathForOption, osSourceProfile.c_str(),
    1866             :                         osSecretAccessKeySP, osAccessKeyIdSP, osSessionTokenSP,
    1867             :                         osRegionSP, osCredentialsSP, osRoleArnSP,
    1868             :                         osSourceProfileSP, osExternalIdSP, osMFASerialSP,
    1869             :                         osRoleSessionNameSP, osWebIdentityTokenFile,
    1870             :                         osSSOStartURLSP, osSSOAccountIDSP, osSSORoleNameSP,
    1871             :                         osSSOSession))
    1872             :                 {
    1873           2 :                     if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1874             :                             /* bForceRefresh = */ false, osPathForOption,
    1875             :                             osRoleArnSP, osWebIdentityTokenFile,
    1876             :                             osSecretAccessKey, osAccessKeyId, osSessionToken))
    1877             :                     {
    1878           2 :                         CPLMutexHolder oHolder(&ghMutex);
    1879           1 :                         gosRoleArnWebIdentity = std::move(osRoleArnSP);
    1880             :                         gosWebIdentityTokenFile =
    1881           1 :                             std::move(osWebIdentityTokenFile);
    1882             :                     }
    1883             :                 }
    1884             :             }
    1885             : 
    1886           2 :             if (gosRoleArnWebIdentity.empty())
    1887             :             {
    1888             :                 // Get the credentials for the source profile, that will be
    1889             :                 // used to sign the STS AssumedRole request.
    1890           1 :                 if (!ReadAWSCredentials(osSourceProfile, osCredentials,
    1891             :                                         osSecretAccessKey, osAccessKeyId,
    1892             :                                         osSessionToken))
    1893             :                 {
    1894           0 :                     VSIError(
    1895             :                         VSIE_InvalidCredentials,
    1896             :                         "Cannot retrieve credentials for source profile %s",
    1897             :                         osSourceProfile.c_str());
    1898           0 :                     return false;
    1899             :                 }
    1900             :             }
    1901             : 
    1902           4 :             std::string osTempSecretAccessKey;
    1903           4 :             std::string osTempAccessKeyId;
    1904           4 :             std::string osTempSessionToken;
    1905           4 :             std::string osExpiration;
    1906           2 :             if (GetTemporaryCredentialsForRole(
    1907             :                     osRoleArn, osExternalId, osMFASerial, osRoleSessionName,
    1908             :                     osSecretAccessKey, osAccessKeyId, osSessionToken,
    1909             :                     osTempSecretAccessKey, osTempAccessKeyId,
    1910             :                     osTempSessionToken, osExpiration))
    1911             :             {
    1912           2 :                 CPLDebug(AWS_DEBUG_KEY, "Using assumed role %s",
    1913             :                          osRoleArn.c_str());
    1914             :                 {
    1915             :                     // Store global variables to be able to reuse the
    1916             :                     // temporary credentials
    1917           4 :                     CPLMutexHolder oHolder(&ghMutex);
    1918           2 :                     Iso8601ToUnixTime(osExpiration.c_str(),
    1919             :                                       &gnGlobalExpiration);
    1920           2 :                     gosRoleArn = std::move(osRoleArn);
    1921           2 :                     gosExternalId = std::move(osExternalId);
    1922           2 :                     gosMFASerial = std::move(osMFASerial);
    1923           2 :                     gosRoleSessionName = std::move(osRoleSessionName);
    1924             :                     gosSourceProfileSecretAccessKey =
    1925           2 :                         std::move(osSecretAccessKey);
    1926           2 :                     gosSourceProfileAccessKeyId = std::move(osAccessKeyId);
    1927           2 :                     gosSourceProfileSessionToken = std::move(osSessionToken);
    1928           2 :                     gosGlobalAccessKeyId = osTempAccessKeyId;
    1929           2 :                     gosGlobalSecretAccessKey = osTempSecretAccessKey;
    1930           2 :                     gosGlobalSessionToken = osTempSessionToken;
    1931           2 :                     gosRegion = osRegion;
    1932             :                 }
    1933           2 :                 osSecretAccessKey = std::move(osTempSecretAccessKey);
    1934           2 :                 osAccessKeyId = std::move(osTempAccessKeyId);
    1935           2 :                 osSessionToken = std::move(osTempSessionToken);
    1936           2 :                 eCredentialsSource = AWSCredentialsSource::ASSUMED_ROLE;
    1937           2 :                 return true;
    1938             :             }
    1939           0 :             return false;
    1940             :         }
    1941             : 
    1942           6 :         if (!osSSOStartURL.empty() || !osSSOSession.empty())
    1943             :         {
    1944           2 :             std::string osTempSecretAccessKey;
    1945           2 :             std::string osTempAccessKeyId;
    1946           2 :             std::string osTempSessionToken;
    1947           2 :             std::string osExpirationEpochInMS;
    1948           1 :             if (GetTemporaryCredentialsForSSO(
    1949             :                     osSSOStartURL, osSSOSession, osSSOAccountID, osSSORoleName,
    1950             :                     osTempSecretAccessKey, osTempAccessKeyId,
    1951             :                     osTempSessionToken, osExpirationEpochInMS))
    1952             :             {
    1953           1 :                 CPLDebug(AWS_DEBUG_KEY, "Using SSO %s", osSSOStartURL.c_str());
    1954             :                 {
    1955             :                     // Store global variables to be able to reuse the
    1956             :                     // temporary credentials
    1957           2 :                     CPLMutexHolder oHolder(&ghMutex);
    1958           1 :                     gnGlobalExpiration =
    1959           1 :                         CPLAtoGIntBig(osExpirationEpochInMS.c_str()) / 1000;
    1960           1 :                     gosSSOStartURL = std::move(osSSOStartURL);
    1961           1 :                     gosSSOAccountID = std::move(osSSOAccountID);
    1962           1 :                     gosSSORoleName = std::move(osSSORoleName);
    1963           1 :                     gosGlobalAccessKeyId = osTempAccessKeyId;
    1964           1 :                     gosGlobalSecretAccessKey = osTempSecretAccessKey;
    1965           1 :                     gosGlobalSessionToken = osTempSessionToken;
    1966           1 :                     gosRegion = osRegion;
    1967             :                 }
    1968           1 :                 osSecretAccessKey = std::move(osTempSecretAccessKey);
    1969           1 :                 osAccessKeyId = std::move(osTempAccessKeyId);
    1970           1 :                 osSessionToken = std::move(osTempSessionToken);
    1971           1 :                 eCredentialsSource = AWSCredentialsSource::SSO;
    1972           1 :                 return true;
    1973             :             }
    1974           0 :             return false;
    1975             :         }
    1976             : 
    1977           5 :         return true;
    1978             :     }
    1979             : 
    1980          19 :     if (CPLTestBool(CPLGetConfigOption("CPL_AWS_WEB_IDENTITY_ENABLE", "YES")))
    1981             :     {
    1982             :         // WebIdentity method: use Web Identity Token
    1983          12 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    1984             :                 /* bForceRefresh = */ false, osPathForOption,
    1985          24 :                 /* osRoleArnIn = */ std::string(),
    1986          24 :                 /* osWebIdentityTokenFileIn = */ std::string(),
    1987             :                 osSecretAccessKey, osAccessKeyId, osSessionToken))
    1988             :         {
    1989           1 :             eCredentialsSource = AWSCredentialsSource::WEB_IDENTITY;
    1990           1 :             return true;
    1991             :         }
    1992             :     }
    1993             : 
    1994             :     // Last method: use IAM role security credentials on EC2 instances
    1995          18 :     if (GetConfigurationFromEC2(/* bForceRefresh = */ false, osPathForOption,
    1996             :                                 osSecretAccessKey, osAccessKeyId,
    1997             :                                 osSessionToken))
    1998             :     {
    1999           9 :         eCredentialsSource = AWSCredentialsSource::EC2;
    2000           9 :         return true;
    2001             :     }
    2002             : 
    2003           9 :     CPLString osMsg;
    2004             :     osMsg.Printf(
    2005             :         "No valid AWS credentials found. "
    2006             :         "For authenticated requests, you need to set "
    2007             :         "AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID or other configuration "
    2008             :         "options, or create a %s file. Consult "
    2009             :         "https://gdal.org/en/stable/user/"
    2010             :         "virtual_file_systems.html#vsis3-aws-s3-files for more details. "
    2011             :         "For unauthenticated requests on public resources, set the "
    2012             :         "AWS_NO_SIGN_REQUEST configuration option to YES.",
    2013           9 :         osCredentials.c_str());
    2014           9 :     CPLDebug(AWS_DEBUG_KEY, "%s", osMsg.c_str());
    2015           9 :     VSIError(VSIE_InvalidCredentials, "%s", osMsg.c_str());
    2016             : 
    2017           9 :     return false;
    2018             : }
    2019             : 
    2020             : /************************************************************************/
    2021             : /*                          CleanMutex()                                */
    2022             : /************************************************************************/
    2023             : 
    2024        1122 : void VSIS3HandleHelper::CleanMutex()
    2025             : {
    2026        1122 :     if (ghMutex != nullptr)
    2027        1122 :         CPLDestroyMutex(ghMutex);
    2028        1122 :     ghMutex = nullptr;
    2029        1122 : }
    2030             : 
    2031             : /************************************************************************/
    2032             : /*                          ClearCache()                                */
    2033             : /************************************************************************/
    2034             : 
    2035        1457 : void VSIS3HandleHelper::ClearCache()
    2036             : {
    2037        2914 :     CPLMutexHolder oHolder(&ghMutex);
    2038             : 
    2039        1457 :     gosIAMRole.clear();
    2040        1457 :     gosGlobalAccessKeyId.clear();
    2041        1457 :     gosGlobalSecretAccessKey.clear();
    2042        1457 :     gosGlobalSessionToken.clear();
    2043        1457 :     gnGlobalExpiration = 0;
    2044        1457 :     gosRoleArn.clear();
    2045        1457 :     gosExternalId.clear();
    2046        1457 :     gosMFASerial.clear();
    2047        1457 :     gosRoleSessionName.clear();
    2048        1457 :     gosSourceProfileAccessKeyId.clear();
    2049        1457 :     gosSourceProfileSecretAccessKey.clear();
    2050        1457 :     gosSourceProfileSessionToken.clear();
    2051        1457 :     gosRegion.clear();
    2052        1457 :     gosRoleArnWebIdentity.clear();
    2053        1457 :     gosWebIdentityTokenFile.clear();
    2054        1457 :     gosSSOStartURL.clear();
    2055        1457 :     gosSSOAccountID.clear();
    2056        1457 :     gosSSORoleName.clear();
    2057        1457 : }
    2058             : 
    2059             : /************************************************************************/
    2060             : /*                          BuildFromURI()                              */
    2061             : /************************************************************************/
    2062             : 
    2063         567 : VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI,
    2064             :                                                    const char *pszFSPrefix,
    2065             :                                                    bool bAllowNoObject,
    2066             :                                                    CSLConstList papszOptions)
    2067             : {
    2068        1134 :     std::string osPathForOption("/vsis3/");
    2069         567 :     if (pszURI)
    2070         564 :         osPathForOption += pszURI;
    2071             : 
    2072        1134 :     std::string osSecretAccessKey;
    2073        1134 :     std::string osAccessKeyId;
    2074        1134 :     std::string osSessionToken;
    2075        1134 :     std::string osRegion;
    2076         567 :     AWSCredentialsSource eCredentialsSource = AWSCredentialsSource::REGULAR;
    2077         567 :     if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
    2078             :                           osAccessKeyId, osSessionToken, osRegion,
    2079             :                           eCredentialsSource))
    2080             :     {
    2081          10 :         return nullptr;
    2082             :     }
    2083             : 
    2084             :     // According to
    2085             :     // http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html "
    2086             :     // This variable overrides the default region of the in-use profile, if
    2087             :     // set."
    2088             :     std::string osDefaultRegion = CSLFetchNameValueDef(
    2089             :         papszOptions, "AWS_DEFAULT_REGION",
    2090             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_DEFAULT_REGION",
    2091        1114 :                                  ""));
    2092         557 :     if (!osDefaultRegion.empty())
    2093             :     {
    2094         557 :         osRegion = std::move(osDefaultRegion);
    2095             :     }
    2096             : 
    2097             :     std::string osEndpoint = VSIGetPathSpecificOption(
    2098        1114 :         osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com");
    2099         557 :     bool bForceHTTP = false;
    2100         557 :     bool bForceHTTPS = false;
    2101         557 :     if (STARTS_WITH(osEndpoint.c_str(), "http://"))
    2102             :     {
    2103          14 :         bForceHTTP = true;
    2104          14 :         osEndpoint = osEndpoint.substr(strlen("http://"));
    2105             :     }
    2106         543 :     else if (STARTS_WITH(osEndpoint.c_str(), "https://"))
    2107             :     {
    2108          10 :         bForceHTTPS = true;
    2109          10 :         osEndpoint = osEndpoint.substr(strlen("https://"));
    2110             :     }
    2111         557 :     if (!osEndpoint.empty() && osEndpoint.back() == '/')
    2112           0 :         osEndpoint.pop_back();
    2113             : 
    2114             :     const std::string osRequestPayer = VSIGetPathSpecificOption(
    2115        1114 :         osPathForOption.c_str(), "AWS_REQUEST_PAYER", "");
    2116        1114 :     std::string osBucket;
    2117        1114 :     std::string osObjectKey;
    2118        1106 :     if (pszURI != nullptr && pszURI[0] != '\0' &&
    2119         549 :         !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
    2120             :                                osObjectKey))
    2121             :     {
    2122           1 :         return nullptr;
    2123             :     }
    2124             : 
    2125             :     // Detect if this is a directory bucket
    2126             :     // Cf https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-bucket-naming-rules.html
    2127        1112 :     std::string osZoneId;
    2128         556 :     constexpr const char *DIR_BUCKET_SUFFIX = "--x-s3";
    2129         997 :     if (osBucket.size() > strlen(DIR_BUCKET_SUFFIX) &&
    2130         441 :         cpl::ends_with(osBucket, DIR_BUCKET_SUFFIX))
    2131             :     {
    2132           4 :         const auto posEndZoneId = osBucket.size() - strlen(DIR_BUCKET_SUFFIX);
    2133           4 :         auto posZoneId = osBucket.rfind("--", posEndZoneId - 1);
    2134           4 :         if (posZoneId != std::string::npos)
    2135             :         {
    2136           4 :             posZoneId += strlen("--");
    2137           4 :             osZoneId = osBucket.substr(posZoneId, posEndZoneId - posZoneId);
    2138             :         }
    2139             :     }
    2140             : 
    2141        1112 :     std::string osService = "s3";
    2142             : 
    2143         556 :     if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com")
    2144             :     {
    2145          43 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions,
    2146             :                                              "LIST_DIRECTORY_BUCKETS", "NO")))
    2147             :         {
    2148           0 :             osService = "s3express";
    2149           0 :             osEndpoint = "s3express-control." + osRegion + ".amazonaws.com";
    2150             :         }
    2151          43 :         else if (!osZoneId.empty())
    2152             :         {
    2153             :             osEndpoint =
    2154           0 :                 "s3express-" + osZoneId + "." + osRegion + ".amazonaws.com";
    2155             :         }
    2156             :         else
    2157             :         {
    2158          43 :             osEndpoint = "s3." + osRegion + ".amazonaws.com";
    2159             :         }
    2160             :     }
    2161             : 
    2162             :     const bool bUseHTTPS =
    2163        1102 :         bForceHTTPS ||
    2164         546 :         (!bForceHTTP && CPLTestBool(VSIGetPathSpecificOption(
    2165         556 :                             osPathForOption.c_str(), "AWS_HTTPS", "YES")));
    2166             :     const bool bIsValidNameForVirtualHosting =
    2167         556 :         osBucket.find('.') == std::string::npos;
    2168         556 :     const bool bUseVirtualHosting = CPLTestBool(CSLFetchNameValueDef(
    2169             :         papszOptions, "AWS_VIRTUAL_HOSTING",
    2170             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_VIRTUAL_HOSTING",
    2171             :                                  bIsValidNameForVirtualHosting ? "TRUE"
    2172             :                                                                : "FALSE")));
    2173             :     const std::string osS3SessionToken = VSIGetPathSpecificOption(
    2174         556 :         osPathForOption.c_str(), "AWS_S3SESSION_TOKEN", "");
    2175             : 
    2176             :     return new VSIS3HandleHelper(osService, osSecretAccessKey, osAccessKeyId,
    2177             :                                  osSessionToken, osS3SessionToken, osEndpoint,
    2178             :                                  osRegion, osRequestPayer, osBucket,
    2179             :                                  osObjectKey, bUseHTTPS, bUseVirtualHosting,
    2180         556 :                                  eCredentialsSource, !osZoneId.empty());
    2181             : }
    2182             : 
    2183             : /************************************************************************/
    2184             : /*                          GetQueryString()                            */
    2185             : /************************************************************************/
    2186             : 
    2187             : std::string
    2188        1843 : IVSIS3LikeHandleHelper::GetQueryString(bool bAddEmptyValueAfterEqual) const
    2189             : {
    2190        1843 :     std::string osQueryString;
    2191             :     std::map<std::string, std::string>::const_iterator oIter =
    2192        1843 :         m_oMapQueryParameters.begin();
    2193        3547 :     for (; oIter != m_oMapQueryParameters.end(); ++oIter)
    2194             :     {
    2195        1704 :         if (oIter == m_oMapQueryParameters.begin())
    2196         824 :             osQueryString += "?";
    2197             :         else
    2198         880 :             osQueryString += "&";
    2199        1704 :         osQueryString += oIter->first;
    2200        1704 :         if (!oIter->second.empty() || bAddEmptyValueAfterEqual)
    2201             :         {
    2202        1671 :             osQueryString += "=";
    2203        1671 :             osQueryString += CPLAWSURLEncode(oIter->second);
    2204             :         }
    2205             :     }
    2206        3686 :     return osQueryString;
    2207             : }
    2208             : 
    2209             : /************************************************************************/
    2210             : /*                       ResetQueryParameters()                         */
    2211             : /************************************************************************/
    2212             : 
    2213         596 : void IVSIS3LikeHandleHelper::ResetQueryParameters()
    2214             : {
    2215         596 :     m_oMapQueryParameters.clear();
    2216         596 :     RebuildURL();
    2217         596 : }
    2218             : 
    2219             : /************************************************************************/
    2220             : /*                         AddQueryParameter()                          */
    2221             : /************************************************************************/
    2222             : 
    2223         696 : void IVSIS3LikeHandleHelper::AddQueryParameter(const std::string &osKey,
    2224             :                                                const std::string &osValue)
    2225             : {
    2226         696 :     m_oMapQueryParameters[osKey] = osValue;
    2227         696 :     RebuildURL();
    2228         696 : }
    2229             : 
    2230             : /************************************************************************/
    2231             : /*                           GetURLNoKVP()                              */
    2232             : /************************************************************************/
    2233             : 
    2234         415 : std::string IVSIS3LikeHandleHelper::GetURLNoKVP() const
    2235             : {
    2236         415 :     std::string osURL(GetURL());
    2237         415 :     const auto nPos = osURL.find('?');
    2238         415 :     if (nPos != std::string::npos)
    2239           8 :         osURL.resize(nPos);
    2240         415 :     return osURL;
    2241             : }
    2242             : 
    2243             : /************************************************************************/
    2244             : /*                          RefreshCredentials()                        */
    2245             : /************************************************************************/
    2246             : 
    2247         339 : void VSIS3HandleHelper::RefreshCredentials(const std::string &osPathForOption,
    2248             :                                            bool bForceRefresh) const
    2249             : {
    2250         339 :     if (m_eCredentialsSource == AWSCredentialsSource::EC2)
    2251             :     {
    2252          16 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    2253           8 :         if (GetConfigurationFromEC2(bForceRefresh, osPathForOption.c_str(),
    2254             :                                     osSecretAccessKey, osAccessKeyId,
    2255             :                                     osSessionToken))
    2256             :         {
    2257           8 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    2258           8 :             m_osAccessKeyId = std::move(osAccessKeyId);
    2259           8 :             m_osSessionToken = std::move(osSessionToken);
    2260             :         }
    2261             :     }
    2262         331 :     else if (m_eCredentialsSource == AWSCredentialsSource::ASSUMED_ROLE)
    2263             :     {
    2264          12 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    2265          12 :         std::string osRegion;
    2266           6 :         if (GetOrRefreshTemporaryCredentialsForRole(
    2267             :                 bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
    2268             :                 osRegion))
    2269             :         {
    2270           5 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    2271           5 :             m_osAccessKeyId = std::move(osAccessKeyId);
    2272           5 :             m_osSessionToken = std::move(osSessionToken);
    2273             :         }
    2274             :     }
    2275         325 :     else if (m_eCredentialsSource == AWSCredentialsSource::WEB_IDENTITY)
    2276             :     {
    2277           2 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    2278           1 :         if (GetConfigurationFromAssumeRoleWithWebIdentity(
    2279           2 :                 bForceRefresh, osPathForOption.c_str(), std::string(),
    2280           2 :                 std::string(), osSecretAccessKey, osAccessKeyId,
    2281             :                 osSessionToken))
    2282             :         {
    2283           1 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    2284           1 :             m_osAccessKeyId = std::move(osAccessKeyId);
    2285           1 :             m_osSessionToken = std::move(osSessionToken);
    2286             :         }
    2287             :     }
    2288         324 :     else if (m_eCredentialsSource == AWSCredentialsSource::SSO)
    2289             :     {
    2290           4 :         std::string osSecretAccessKey, osAccessKeyId, osSessionToken;
    2291           4 :         std::string osRegion;
    2292           2 :         if (GetOrRefreshTemporaryCredentialsForSSO(
    2293             :                 bForceRefresh, osSecretAccessKey, osAccessKeyId, osSessionToken,
    2294             :                 osRegion))
    2295             :         {
    2296           2 :             m_osSecretAccessKey = std::move(osSecretAccessKey);
    2297           2 :             m_osAccessKeyId = std::move(osAccessKeyId);
    2298           2 :             m_osSessionToken = std::move(osSessionToken);
    2299             :         }
    2300             :     }
    2301         339 : }
    2302             : 
    2303             : /************************************************************************/
    2304             : /*                           GetCurlHeaders()                           */
    2305             : /************************************************************************/
    2306             : 
    2307         338 : struct curl_slist *VSIS3HandleHelper::GetCurlHeaders(
    2308             :     const std::string &osVerb, struct curl_slist *psHeaders,
    2309             :     const void *pabyDataContent, size_t nBytesContent) const
    2310             : {
    2311         676 :     std::string osPathForOption("/vsis3/");
    2312         338 :     osPathForOption += m_osBucket;
    2313         338 :     osPathForOption += '/';
    2314         338 :     osPathForOption += m_osObjectKey;
    2315             : 
    2316         338 :     RefreshCredentials(osPathForOption, /* bForceRefresh = */ false);
    2317             : 
    2318             :     std::string osXAMZDate =
    2319         676 :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", "");
    2320         338 :     if (osXAMZDate.empty())
    2321           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    2322             : 
    2323             :     const std::string osXAMZContentSHA256 =
    2324         676 :         CPLGetLowerCaseHexSHA256(pabyDataContent, nBytesContent);
    2325             : 
    2326         676 :     std::string osCanonicalQueryString(GetQueryString(true));
    2327         338 :     if (!osCanonicalQueryString.empty())
    2328         119 :         osCanonicalQueryString = osCanonicalQueryString.substr(1);
    2329             : 
    2330           2 :     const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
    2331         340 :                                  ? std::string(m_osBucket + "." + m_osEndpoint)
    2332         678 :                                  : m_osEndpoint);
    2333             : 
    2334         338 :     if (!m_osSessionToken.empty())
    2335          11 :         psHeaders =
    2336          11 :             curl_slist_append(psHeaders, CPLSPrintf("X-Amz-Security-Token: %s",
    2337             :                                                     m_osSessionToken.c_str()));
    2338             : 
    2339         338 :     if (!m_osS3SessionToken.empty())
    2340           1 :         psHeaders = curl_slist_append(psHeaders,
    2341             :                                       CPLSPrintf("x-amz-s3session-token: %s",
    2342             :                                                  m_osS3SessionToken.c_str()));
    2343             : 
    2344         338 :     if (!m_osRequestPayer.empty())
    2345           2 :         psHeaders =
    2346           2 :             curl_slist_append(psHeaders, CPLSPrintf("x-amz-request-payer: %s",
    2347             :                                                     m_osRequestPayer.c_str()));
    2348             :     const std::string osAuthorization =
    2349         338 :         m_osSecretAccessKey.empty()
    2350             :             ? std::string()
    2351             :             : CPLGetAWS_SIGN4_Authorization(
    2352         324 :                   m_osSecretAccessKey, m_osAccessKeyId, m_osRegion, m_osService,
    2353             :                   osVerb, psHeaders, osHost,
    2354         324 :                   m_bUseVirtualHosting
    2355         338 :                       ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
    2356        1634 :                       : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey,
    2357             :                                         false)
    2358         324 :                             .c_str(),
    2359             :                   osCanonicalQueryString, osXAMZContentSHA256,
    2360             :                   true,  // bAddHeaderAMZContentSHA256
    2361        1324 :                   osXAMZDate);
    2362             : 
    2363         338 :     if (!osAuthorization.empty())
    2364             :     {
    2365         324 :         psHeaders =
    2366         324 :             curl_slist_append(psHeaders, CPLSPrintf("Authorization: %s",
    2367             :                                                     osAuthorization.c_str()));
    2368             :     }
    2369         676 :     return psHeaders;
    2370             : }
    2371             : 
    2372             : /************************************************************************/
    2373             : /*                          CanRestartOnError()                         */
    2374             : /************************************************************************/
    2375             : 
    2376          46 : bool VSIS3HandleHelper::CanRestartOnError(const char *pszErrorMsg,
    2377             :                                           const char *pszHeaders,
    2378             :                                           bool bSetError)
    2379             : {
    2380             : #ifdef DEBUG_VERBOSE
    2381             :     CPLDebug(AWS_DEBUG_KEY, "%s", pszErrorMsg);
    2382             :     CPLDebug(AWS_DEBUG_KEY, "%s", pszHeaders ? pszHeaders : "");
    2383             : #endif
    2384             : 
    2385          46 :     if (!STARTS_WITH(pszErrorMsg, "<?xml") &&
    2386          11 :         !STARTS_WITH(pszErrorMsg, "<Error>"))
    2387             :     {
    2388          11 :         if (bSetError)
    2389             :         {
    2390           7 :             VSIError(VSIE_ObjectStorageGenericError, "Invalid AWS response: %s",
    2391             :                      pszErrorMsg);
    2392             :         }
    2393          11 :         return false;
    2394             :     }
    2395             : 
    2396          35 :     CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
    2397          35 :     if (psTree == nullptr)
    2398             :     {
    2399           2 :         if (bSetError)
    2400             :         {
    2401           2 :             VSIError(VSIE_ObjectStorageGenericError,
    2402             :                      "Malformed AWS XML response: %s", pszErrorMsg);
    2403             :         }
    2404           2 :         return false;
    2405             :     }
    2406             : 
    2407          33 :     const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
    2408          33 :     if (pszCode == nullptr)
    2409             :     {
    2410           2 :         CPLDestroyXMLNode(psTree);
    2411           2 :         if (bSetError)
    2412             :         {
    2413           2 :             VSIError(VSIE_ObjectStorageGenericError,
    2414             :                      "Malformed AWS XML response: %s", pszErrorMsg);
    2415             :         }
    2416           2 :         return false;
    2417             :     }
    2418             : 
    2419          31 :     if (EQUAL(pszCode, "AuthorizationHeaderMalformed"))
    2420             :     {
    2421             :         const char *pszRegion =
    2422           9 :             CPLGetXMLValue(psTree, "=Error.Region", nullptr);
    2423           9 :         if (pszRegion == nullptr)
    2424             :         {
    2425           2 :             CPLDestroyXMLNode(psTree);
    2426           2 :             if (bSetError)
    2427             :             {
    2428           2 :                 VSIError(VSIE_ObjectStorageGenericError,
    2429             :                          "Malformed AWS XML response: %s", pszErrorMsg);
    2430             :             }
    2431           2 :             return false;
    2432             :         }
    2433           7 :         SetRegion(pszRegion);
    2434           7 :         CPLDebug(AWS_DEBUG_KEY, "Switching to region %s", m_osRegion.c_str());
    2435           7 :         CPLDestroyXMLNode(psTree);
    2436             : 
    2437           7 :         VSIS3UpdateParams::UpdateMapFromHandle(this);
    2438             : 
    2439           7 :         return true;
    2440             :     }
    2441             : 
    2442          22 :     if (EQUAL(pszCode, "PermanentRedirect") ||
    2443          14 :         EQUAL(pszCode, "TemporaryRedirect"))
    2444             :     {
    2445          14 :         const bool bIsTemporaryRedirect = EQUAL(pszCode, "TemporaryRedirect");
    2446             :         const char *pszEndpoint =
    2447          14 :             CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
    2448          26 :         if (pszEndpoint == nullptr ||
    2449          12 :             (m_bUseVirtualHosting && (strncmp(pszEndpoint, m_osBucket.c_str(),
    2450           0 :                                               m_osBucket.size()) != 0 ||
    2451           0 :                                       pszEndpoint[m_osBucket.size()] != '.')))
    2452             :         {
    2453           2 :             CPLDestroyXMLNode(psTree);
    2454           2 :             if (bSetError)
    2455             :             {
    2456           2 :                 VSIError(VSIE_ObjectStorageGenericError,
    2457             :                          "Malformed AWS XML response: %s", pszErrorMsg);
    2458             :             }
    2459           2 :             return false;
    2460             :         }
    2461          36 :         if (!m_bUseVirtualHosting &&
    2462          13 :             strncmp(pszEndpoint, m_osBucket.c_str(), m_osBucket.size()) == 0 &&
    2463           1 :             pszEndpoint[m_osBucket.size()] == '.')
    2464             :         {
    2465             :             /* If we have a body with
    2466             :             <Error><Code>PermanentRedirect</Code><Message>The bucket you are
    2467             :             attempting to access must be addressed using the specified endpoint.
    2468             :             Please send all future requests to this
    2469             :             endpoint.</Message><Bucket>bucket.with.dot</Bucket><Endpoint>bucket.with.dot.s3.amazonaws.com</Endpoint></Error>
    2470             :             and headers like
    2471             :             x-amz-bucket-region: eu-west-1
    2472             :             and the bucket name has dot in it,
    2473             :             then we must use s3.$(x-amz-bucket-region).amazon.com as endpoint.
    2474             :             See #7154 */
    2475           1 :             const char *pszRegionPtr =
    2476             :                 (pszHeaders != nullptr)
    2477           1 :                     ? strstr(pszHeaders, "x-amz-bucket-region: ")
    2478             :                     : nullptr;
    2479           1 :             if (strchr(m_osBucket.c_str(), '.') != nullptr &&
    2480             :                 pszRegionPtr != nullptr)
    2481             :             {
    2482             :                 std::string osRegion(pszRegionPtr +
    2483           1 :                                      strlen("x-amz-bucket-region: "));
    2484           1 :                 size_t nPos = osRegion.find('\r');
    2485           1 :                 if (nPos != std::string::npos)
    2486           1 :                     osRegion.resize(nPos);
    2487           1 :                 SetEndpoint(
    2488             :                     CPLSPrintf("s3.%s.amazonaws.com", osRegion.c_str()));
    2489           1 :                 SetRegion(osRegion.c_str());
    2490           1 :                 CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s",
    2491             :                          m_osEndpoint.c_str());
    2492           1 :                 CPLDebug(AWS_DEBUG_KEY, "Switching to region %s",
    2493             :                          m_osRegion.c_str());
    2494           1 :                 CPLDestroyXMLNode(psTree);
    2495           1 :                 if (!bIsTemporaryRedirect)
    2496           1 :                     VSIS3UpdateParams::UpdateMapFromHandle(this);
    2497           1 :                 return true;
    2498             :             }
    2499             : 
    2500           0 :             m_bUseVirtualHosting = true;
    2501           0 :             CPLDebug(AWS_DEBUG_KEY, "Switching to virtual hosting");
    2502             :         }
    2503          11 :         SetEndpoint(m_bUseVirtualHosting ? pszEndpoint + m_osBucket.size() + 1
    2504             :                                          : pszEndpoint);
    2505          11 :         CPLDebug(AWS_DEBUG_KEY, "Switching to endpoint %s",
    2506             :                  m_osEndpoint.c_str());
    2507          11 :         CPLDestroyXMLNode(psTree);
    2508             : 
    2509          11 :         if (!bIsTemporaryRedirect)
    2510           5 :             VSIS3UpdateParams::UpdateMapFromHandle(this);
    2511             : 
    2512          11 :         return true;
    2513             :     }
    2514             : 
    2515           8 :     if (bSetError)
    2516             :     {
    2517             :         // Translate AWS errors into VSI errors.
    2518             :         const char *pszMessage =
    2519           8 :             CPLGetXMLValue(psTree, "=Error.Message", nullptr);
    2520             : 
    2521           8 :         if (pszMessage == nullptr)
    2522             :         {
    2523           2 :             VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
    2524             :         }
    2525           6 :         else if (EQUAL(pszCode, "AccessDenied"))
    2526             :         {
    2527           0 :             VSIError(VSIE_AccessDenied, "%s", pszMessage);
    2528             :         }
    2529           6 :         else if (EQUAL(pszCode, "NoSuchBucket"))
    2530             :         {
    2531           0 :             VSIError(VSIE_BucketNotFound, "%s", pszMessage);
    2532             :         }
    2533           6 :         else if (EQUAL(pszCode, "NoSuchKey"))
    2534             :         {
    2535           2 :             VSIError(VSIE_ObjectNotFound, "%s", pszMessage);
    2536             :         }
    2537           4 :         else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
    2538             :         {
    2539           0 :             VSIError(VSIE_SignatureDoesNotMatch, "%s", pszMessage);
    2540             :         }
    2541             :         else
    2542             :         {
    2543           4 :             VSIError(VSIE_ObjectStorageGenericError, "%s", pszMessage);
    2544             :         }
    2545             :     }
    2546             : 
    2547           8 :     CPLDestroyXMLNode(psTree);
    2548             : 
    2549           8 :     return false;
    2550             : }
    2551             : 
    2552             : /************************************************************************/
    2553             : /*                          SetEndpoint()                          */
    2554             : /************************************************************************/
    2555             : 
    2556          62 : void VSIS3HandleHelper::SetEndpoint(const std::string &osStr)
    2557             : {
    2558          62 :     m_osEndpoint = osStr;
    2559          62 :     RebuildURL();
    2560          62 : }
    2561             : 
    2562             : /************************************************************************/
    2563             : /*                           SetRegion()                             */
    2564             : /************************************************************************/
    2565             : 
    2566          58 : void VSIS3HandleHelper::SetRegion(const std::string &osStr)
    2567             : {
    2568          58 :     m_osRegion = osStr;
    2569          58 : }
    2570             : 
    2571             : /************************************************************************/
    2572             : /*                           SetRequestPayer()                          */
    2573             : /************************************************************************/
    2574             : 
    2575          50 : void VSIS3HandleHelper::SetRequestPayer(const std::string &osStr)
    2576             : {
    2577          50 :     m_osRequestPayer = osStr;
    2578          50 : }
    2579             : 
    2580             : /************************************************************************/
    2581             : /*                         SetVirtualHosting()                          */
    2582             : /************************************************************************/
    2583             : 
    2584          50 : void VSIS3HandleHelper::SetVirtualHosting(bool b)
    2585             : {
    2586          50 :     m_bUseVirtualHosting = b;
    2587          50 :     RebuildURL();
    2588          50 : }
    2589             : 
    2590             : /************************************************************************/
    2591             : /*                           GetSignedURL()                             */
    2592             : /************************************************************************/
    2593             : 
    2594           5 : std::string VSIS3HandleHelper::GetSignedURL(CSLConstList papszOptions)
    2595             : {
    2596          10 :     std::string osPathForOption("/vsis3/");
    2597           5 :     osPathForOption += m_osBucket;
    2598           5 :     osPathForOption += '/';
    2599           5 :     osPathForOption += m_osObjectKey;
    2600             : 
    2601             :     std::string osXAMZDate = CSLFetchNameValueDef(
    2602             :         papszOptions, "START_DATE",
    2603          10 :         VSIGetPathSpecificOption(osPathForOption.c_str(), "AWS_TIMESTAMP", ""));
    2604           5 :     if (osXAMZDate.empty())
    2605           0 :         osXAMZDate = CPLGetAWS_SIGN4_Timestamp(time(nullptr));
    2606          10 :     std::string osDate(osXAMZDate);
    2607           5 :     osDate.resize(8);
    2608             : 
    2609             :     std::string osXAMZExpires =
    2610          10 :         CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600");
    2611             : 
    2612           5 :     if (m_eCredentialsSource != AWSCredentialsSource::REGULAR)
    2613             :     {
    2614             :         // For credentials that have an expiration, we must check their
    2615             :         // expiration compared to the expiration of the signed URL, since
    2616             :         // if the effective expiration is min(desired_expiration,
    2617             :         // credential_expiration) Cf
    2618             :         // https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration
    2619           2 :         int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
    2620           2 :         if (sscanf(osXAMZDate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear,
    2621           2 :                    &nMonth, &nDay, &nHour, &nMin, &nSec) < 3)
    2622             :         {
    2623           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Bad format for START_DATE");
    2624           0 :             return std::string();
    2625             :         }
    2626             :         struct tm brokendowntime;
    2627           2 :         brokendowntime.tm_year = nYear - 1900;
    2628           2 :         brokendowntime.tm_mon = nMonth - 1;
    2629           2 :         brokendowntime.tm_mday = nDay;
    2630           2 :         brokendowntime.tm_hour = nHour;
    2631           2 :         brokendowntime.tm_min = nMin;
    2632           2 :         brokendowntime.tm_sec = nSec;
    2633           2 :         const GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
    2634             : 
    2635             :         {
    2636           4 :             CPLMutexHolder oHolder(&ghMutex);
    2637             : 
    2638             :             // Try to reuse credentials if they will still be valid after the
    2639             :             // desired end of the validity of the signed URL,
    2640             :             // with one minute of margin
    2641           2 :             if (nStartDate + CPLAtoGIntBig(osXAMZExpires.c_str()) >=
    2642           2 :                 gnGlobalExpiration - 60)
    2643             :             {
    2644           1 :                 RefreshCredentials(osPathForOption, /* bForceRefresh = */ true);
    2645             :             }
    2646             :         }
    2647             :     }
    2648             : 
    2649          10 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
    2650             : 
    2651           5 :     ResetQueryParameters();
    2652           5 :     AddQueryParameter("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
    2653          15 :     AddQueryParameter("X-Amz-Credential", m_osAccessKeyId + "/" + osDate + "/" +
    2654          15 :                                               m_osRegion + "/s3/aws4_request");
    2655           5 :     AddQueryParameter("X-Amz-Date", osXAMZDate);
    2656           5 :     AddQueryParameter("X-Amz-Expires", osXAMZExpires);
    2657           5 :     if (!m_osSessionToken.empty())
    2658           1 :         AddQueryParameter("X-Amz-Security-Token", m_osSessionToken);
    2659           5 :     AddQueryParameter("X-Amz-SignedHeaders", "host");
    2660             : 
    2661          10 :     std::string osCanonicalQueryString(GetQueryString(true).substr(1));
    2662             : 
    2663           0 :     const std::string osHost(m_bUseVirtualHosting && !m_osBucket.empty()
    2664           5 :                                  ? std::string(m_osBucket + "." + m_osEndpoint)
    2665          10 :                                  : m_osEndpoint);
    2666          10 :     std::string osSignedHeaders;
    2667             : 
    2668           5 :     struct curl_slist *psHeaders = nullptr;
    2669           5 :     if (!m_osRequestPayer.empty())
    2670           0 :         psHeaders =
    2671           0 :             curl_slist_append(psHeaders, CPLSPrintf("x-amz-request-payer: %s",
    2672             :                                                     m_osRequestPayer.c_str()));
    2673             :     const std::string osSignature = CPLGetAWS_SIGN4_Signature(
    2674           5 :         m_osSecretAccessKey, m_osRegion, "s3", osVerb, psHeaders, osHost,
    2675           5 :         m_bUseVirtualHosting
    2676           5 :             ? CPLAWSURLEncode("/" + m_osObjectKey, false).c_str()
    2677          25 :             : CPLAWSURLEncode("/" + m_osBucket + "/" + m_osObjectKey, false)
    2678           5 :                   .c_str(),
    2679             :         osCanonicalQueryString, "UNSIGNED-PAYLOAD",
    2680             :         false,  // bAddHeaderAMZContentSHA256
    2681          25 :         osXAMZDate, osSignedHeaders);
    2682             : 
    2683           5 :     curl_slist_free_all(psHeaders);
    2684             : 
    2685           5 :     AddQueryParameter("X-Amz-Signature", osSignature);
    2686           5 :     return m_osURL;
    2687             : }
    2688             : 
    2689             : /************************************************************************/
    2690             : /*                        UpdateMapFromHandle()                         */
    2691             : /************************************************************************/
    2692             : 
    2693             : std::mutex VSIS3UpdateParams::gsMutex{};
    2694             : 
    2695             : std::map<std::string, VSIS3UpdateParams>
    2696             :     VSIS3UpdateParams::goMapBucketsToS3Params{};
    2697             : 
    2698          13 : void VSIS3UpdateParams::UpdateMapFromHandle(VSIS3HandleHelper *poS3HandleHelper)
    2699             : {
    2700          13 :     std::lock_guard<std::mutex> guard(gsMutex);
    2701             : 
    2702          13 :     goMapBucketsToS3Params[poS3HandleHelper->GetBucket()] =
    2703          26 :         VSIS3UpdateParams(poS3HandleHelper);
    2704          13 : }
    2705             : 
    2706             : /************************************************************************/
    2707             : /*                         UpdateHandleFromMap()                        */
    2708             : /************************************************************************/
    2709             : 
    2710         556 : void VSIS3UpdateParams::UpdateHandleFromMap(VSIS3HandleHelper *poS3HandleHelper)
    2711             : {
    2712        1112 :     std::lock_guard<std::mutex> guard(gsMutex);
    2713             : 
    2714             :     std::map<std::string, VSIS3UpdateParams>::iterator oIter =
    2715         556 :         goMapBucketsToS3Params.find(poS3HandleHelper->GetBucket());
    2716         556 :     if (oIter != goMapBucketsToS3Params.end())
    2717             :     {
    2718          50 :         oIter->second.UpdateHandlerHelper(poS3HandleHelper);
    2719             :     }
    2720         556 : }
    2721             : 
    2722             : /************************************************************************/
    2723             : /*                            ClearCache()                              */
    2724             : /************************************************************************/
    2725             : 
    2726        1792 : void VSIS3UpdateParams::ClearCache()
    2727             : {
    2728        3584 :     std::lock_guard<std::mutex> guard(gsMutex);
    2729             : 
    2730        1792 :     goMapBucketsToS3Params.clear();
    2731        1792 : }
    2732             : 
    2733             : #endif
    2734             : 
    2735             : //! @endcond

Generated by: LCOV version 1.14