LCOV - code coverage report
Current view: top level - port - cpl_aws.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1218 1362 89.4 %
Date: 2026-06-20 20:44:25 Functions: 52 55 94.5 %

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

Generated by: LCOV version 1.14