LCOV - code coverage report
Current view: top level - port - cpl_aws.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 893 950 94.0 %
Date: 2024-04-28 18:08:58 Functions: 46 46 100.0 %

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

Generated by: LCOV version 1.14