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

Generated by: LCOV version 1.14