LCOV - code coverage report
Current view: top level - port - cpl_alibaba_oss.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 164 175 93.7 %
Date: 2024-11-21 22:18:42 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Name:     cpl_alibaba_oss.h
       4             :  * Project:  CPL - Common Portability Library
       5             :  * Purpose:  Alibaba Cloud Object Storage Service
       6             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  **********************************************************************
       9             :  * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : //! @cond Doxygen_Suppress
      15             : 
      16             : #include "cpl_alibaba_oss.h"
      17             : #include "cpl_vsi_error.h"
      18             : #include "cpl_time.h"
      19             : #include "cpl_minixml.h"
      20             : #include "cpl_multiproc.h"
      21             : #include "cpl_http.h"
      22             : #include "cpl_sha1.h"
      23             : #include <algorithm>
      24             : 
      25             : // #define DEBUG_VERBOSE 1
      26             : 
      27             : #ifdef HAVE_CURL
      28             : 
      29             : /************************************************************************/
      30             : /*                            GetSignature()                            */
      31             : /************************************************************************/
      32             : 
      33          69 : static std::string GetSignature(const std::string &osStringToSign,
      34             :                                 const std::string &osSecretAccessKey)
      35             : {
      36             : 
      37             :     /* -------------------------------------------------------------------- */
      38             :     /*      Compute signature.                                              */
      39             :     /* -------------------------------------------------------------------- */
      40          69 :     GByte abySignature[CPL_SHA1_HASH_SIZE] = {};
      41         138 :     CPL_HMAC_SHA1(osSecretAccessKey.c_str(), osSecretAccessKey.size(),
      42          69 :                   osStringToSign.c_str(), osStringToSign.size(), abySignature);
      43          69 :     char *pszBase64 = CPLBase64Encode(sizeof(abySignature), abySignature);
      44          69 :     std::string osSignature(pszBase64);
      45          69 :     CPLFree(pszBase64);
      46             : 
      47         138 :     return osSignature;
      48             : }
      49             : 
      50             : /************************************************************************/
      51             : /*                         CPLGetOSSHeaders()                           */
      52             : /************************************************************************/
      53             : 
      54             : // See:
      55             : // https://www.alibabacloud.com/help/doc-detail/31951.htm?spm=a3c0i.o31982en.b99.178.5HUTqV
      56             : static struct curl_slist *
      57          68 : CPLGetOSSHeaders(const std::string &osSecretAccessKey,
      58             :                  const std::string &osAccessKeyId, const std::string &osVerb,
      59             :                  const struct curl_slist *psExistingHeaders,
      60             :                  const std::string &osCanonicalizedResource)
      61             : {
      62         136 :     std::string osDate = CPLGetConfigOption("CPL_OSS_TIMESTAMP", "");
      63          68 :     if (osDate.empty())
      64             :     {
      65           0 :         osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
      66             :     }
      67             : 
      68         136 :     std::map<std::string, std::string> oSortedMapHeaders;
      69             :     std::string osCanonicalizedHeaders(
      70             :         IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
      71         136 :             oSortedMapHeaders, psExistingHeaders, "x-oss-"));
      72             : 
      73         136 :     std::string osStringToSign;
      74          68 :     osStringToSign += osVerb + "\n";
      75             :     osStringToSign +=
      76          68 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-MD5") + "\n";
      77             :     osStringToSign +=
      78          68 :         CPLAWSGetHeaderVal(psExistingHeaders, "Content-Type") + "\n";
      79          68 :     osStringToSign += osDate + "\n";
      80          68 :     osStringToSign += osCanonicalizedHeaders;
      81          68 :     osStringToSign += osCanonicalizedResource;
      82             : #ifdef DEBUG_VERBOSE
      83             :     CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
      84             : #endif
      85             : 
      86             :     /* -------------------------------------------------------------------- */
      87             :     /*      Build authorization header.                                     */
      88             :     /* -------------------------------------------------------------------- */
      89             : 
      90          68 :     std::string osAuthorization("OSS ");
      91          68 :     osAuthorization += osAccessKeyId;
      92          68 :     osAuthorization += ":";
      93          68 :     osAuthorization += GetSignature(osStringToSign, osSecretAccessKey);
      94             : 
      95             : #ifdef DEBUG_VERBOSE
      96             :     CPLDebug("OSS", "osAuthorization='%s'", osAuthorization.c_str());
      97             : #endif
      98             : 
      99          68 :     struct curl_slist *headers = nullptr;
     100             :     headers =
     101          68 :         curl_slist_append(headers, CPLSPrintf("Date: %s", osDate.c_str()));
     102          68 :     headers = curl_slist_append(
     103             :         headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
     104         136 :     return headers;
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                         VSIOSSHandleHelper()                         */
     109             : /************************************************************************/
     110          87 : VSIOSSHandleHelper::VSIOSSHandleHelper(const std::string &osSecretAccessKey,
     111             :                                        const std::string &osAccessKeyId,
     112             :                                        const std::string &osEndpoint,
     113             :                                        const std::string &osBucket,
     114             :                                        const std::string &osObjectKey,
     115          87 :                                        bool bUseHTTPS, bool bUseVirtualHosting)
     116             :     : m_osURL(BuildURL(osEndpoint, osBucket, osObjectKey, bUseHTTPS,
     117             :                        bUseVirtualHosting)),
     118             :       m_osSecretAccessKey(osSecretAccessKey), m_osAccessKeyId(osAccessKeyId),
     119             :       m_osEndpoint(osEndpoint), m_osBucket(osBucket),
     120             :       m_osObjectKey(osObjectKey), m_bUseHTTPS(bUseHTTPS),
     121          87 :       m_bUseVirtualHosting(bUseVirtualHosting)
     122             : {
     123          87 :     VSIOSSUpdateParams::UpdateHandleFromMap(this);
     124          87 : }
     125             : 
     126             : /************************************************************************/
     127             : /*                        ~VSIOSSHandleHelper()                         */
     128             : /************************************************************************/
     129             : 
     130         174 : VSIOSSHandleHelper::~VSIOSSHandleHelper()
     131             : {
     132        1914 :     for (size_t i = 0; i < m_osSecretAccessKey.size(); i++)
     133        1827 :         m_osSecretAccessKey[i] = 0;
     134         174 : }
     135             : 
     136             : /************************************************************************/
     137             : /*                           BuildURL()                                 */
     138             : /************************************************************************/
     139             : 
     140         145 : std::string VSIOSSHandleHelper::BuildURL(const std::string &osEndpoint,
     141             :                                          const std::string &osBucket,
     142             :                                          const std::string &osObjectKey,
     143             :                                          bool bUseHTTPS,
     144             :                                          bool bUseVirtualHosting)
     145             : {
     146         145 :     const char *pszProtocol = (bUseHTTPS) ? "https" : "http";
     147         145 :     if (osBucket.empty())
     148             :     {
     149           8 :         return CPLSPrintf("%s://%s", pszProtocol, osEndpoint.c_str());
     150             :     }
     151         137 :     else if (bUseVirtualHosting)
     152             :         return CPLSPrintf("%s://%s.%s/%s", pszProtocol, osBucket.c_str(),
     153             :                           osEndpoint.c_str(),
     154           0 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     155             :     else
     156             :         return CPLSPrintf("%s://%s/%s/%s", pszProtocol, osEndpoint.c_str(),
     157             :                           osBucket.c_str(),
     158         274 :                           CPLAWSURLEncode(osObjectKey, false).c_str());
     159             : }
     160             : 
     161             : /************************************************************************/
     162             : /*                           RebuildURL()                               */
     163             : /************************************************************************/
     164             : 
     165          58 : void VSIOSSHandleHelper::RebuildURL()
     166             : {
     167          58 :     m_osURL = BuildURL(m_osEndpoint, m_osBucket, m_osObjectKey, m_bUseHTTPS,
     168          58 :                        m_bUseVirtualHosting);
     169          58 :     m_osURL += GetQueryString(false);
     170          58 : }
     171             : 
     172             : /************************************************************************/
     173             : /*                        GetConfiguration()                            */
     174             : /************************************************************************/
     175             : 
     176          92 : bool VSIOSSHandleHelper::GetConfiguration(const std::string &osPathForOption,
     177             :                                           CSLConstList papszOptions,
     178             :                                           std::string &osSecretAccessKey,
     179             :                                           std::string &osAccessKeyId)
     180             : {
     181             :     osSecretAccessKey = CSLFetchNameValueDef(
     182             :         papszOptions, "OSS_SECRET_ACCESS_KEY",
     183             :         VSIGetPathSpecificOption(osPathForOption.c_str(),
     184          92 :                                  "OSS_SECRET_ACCESS_KEY", ""));
     185             : 
     186          92 :     if (!osSecretAccessKey.empty())
     187             :     {
     188             :         osAccessKeyId = CSLFetchNameValueDef(
     189             :             papszOptions, "OSS_ACCESS_KEY_ID",
     190             :             VSIGetPathSpecificOption(osPathForOption.c_str(),
     191          89 :                                      "OSS_ACCESS_KEY_ID", ""));
     192          89 :         if (osAccessKeyId.empty())
     193             :         {
     194           1 :             VSIError(VSIE_AWSInvalidCredentials,
     195             :                      "OSS_ACCESS_KEY_ID configuration option not defined");
     196           1 :             return false;
     197             :         }
     198             : 
     199          88 :         return true;
     200             :     }
     201             : 
     202           3 :     VSIError(VSIE_AWSInvalidCredentials,
     203             :              "OSS_SECRET_ACCESS_KEY configuration option not defined");
     204           3 :     return false;
     205             : }
     206             : 
     207             : /************************************************************************/
     208             : /*                          BuildFromURI()                              */
     209             : /************************************************************************/
     210             : 
     211          92 : VSIOSSHandleHelper *VSIOSSHandleHelper::BuildFromURI(const char *pszURI,
     212             :                                                      const char *pszFSPrefix,
     213             :                                                      bool bAllowNoObject,
     214             :                                                      CSLConstList papszOptions)
     215             : {
     216         184 :     std::string osPathForOption("/vsioss/");
     217          92 :     if (pszURI)
     218          92 :         osPathForOption += pszURI;
     219             : 
     220         184 :     std::string osSecretAccessKey;
     221         184 :     std::string osAccessKeyId;
     222          92 :     if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
     223             :                           osAccessKeyId))
     224             :     {
     225           4 :         return nullptr;
     226             :     }
     227             : 
     228             :     const std::string osEndpoint = CSLFetchNameValueDef(
     229             :         papszOptions, "OSS_ENDPOINT",
     230             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "OSS_ENDPOINT",
     231         176 :                                  "oss-us-east-1.aliyuncs.com"));
     232         176 :     std::string osBucket;
     233         176 :     std::string osObjectKey;
     234         172 :     if (pszURI != nullptr && pszURI[0] != '\0' &&
     235          84 :         !GetBucketAndObjectKey(pszURI, pszFSPrefix, bAllowNoObject, osBucket,
     236             :                                osObjectKey))
     237             :     {
     238           1 :         return nullptr;
     239             :     }
     240          87 :     const bool bUseHTTPS = CPLTestBool(
     241             :         VSIGetPathSpecificOption(osPathForOption.c_str(), "OSS_HTTPS", "YES"));
     242             :     const bool bIsValidNameForVirtualHosting =
     243          87 :         osBucket.find('.') == std::string::npos;
     244          87 :     const bool bUseVirtualHosting = CPLTestBool(VSIGetPathSpecificOption(
     245             :         osPathForOption.c_str(), "OSS_VIRTUAL_HOSTING",
     246             :         bIsValidNameForVirtualHosting ? "TRUE" : "FALSE"));
     247             :     return new VSIOSSHandleHelper(osSecretAccessKey, osAccessKeyId, osEndpoint,
     248             :                                   osBucket, osObjectKey, bUseHTTPS,
     249          87 :                                   bUseVirtualHosting);
     250             : }
     251             : 
     252             : /************************************************************************/
     253             : /*                           GetCurlHeaders()                           */
     254             : /************************************************************************/
     255             : 
     256          68 : struct curl_slist *VSIOSSHandleHelper::GetCurlHeaders(
     257             :     const std::string &osVerb, const struct curl_slist *psExistingHeaders,
     258             :     const void * /*pabyDataContent*/, size_t /*nBytesContent*/) const
     259             : {
     260         136 :     std::string osCanonicalQueryString;
     261          68 :     if (!m_osObjectKey.empty())
     262             :     {
     263          58 :         osCanonicalQueryString = GetQueryString(false);
     264             :     }
     265             : 
     266             :     std::string osCanonicalizedResource(
     267          68 :         m_osBucket.empty() ? std::string("/")
     268         268 :                            : "/" + m_osBucket + "/" + m_osObjectKey);
     269          68 :     osCanonicalizedResource += osCanonicalQueryString;
     270             : 
     271          68 :     return CPLGetOSSHeaders(m_osSecretAccessKey, m_osAccessKeyId, osVerb,
     272         136 :                             psExistingHeaders, osCanonicalizedResource);
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*                          CanRestartOnError()                         */
     277             : /************************************************************************/
     278             : 
     279           9 : bool VSIOSSHandleHelper::CanRestartOnError(const char *pszErrorMsg,
     280             :                                            const char *, bool bSetError)
     281             : {
     282             : #ifdef DEBUG_VERBOSE
     283             :     CPLDebug("OSS", "%s", pszErrorMsg);
     284             : #endif
     285             : 
     286           9 :     if (!STARTS_WITH(pszErrorMsg, "<?xml"))
     287             :     {
     288           3 :         if (bSetError)
     289             :         {
     290           1 :             VSIError(VSIE_AWSError, "Invalid AWS response: %s", pszErrorMsg);
     291             :         }
     292           3 :         return false;
     293             :     }
     294             : 
     295           6 :     CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
     296           6 :     if (psTree == nullptr)
     297             :     {
     298           1 :         if (bSetError)
     299             :         {
     300           1 :             VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
     301             :                      pszErrorMsg);
     302             :         }
     303           1 :         return false;
     304             :     }
     305             : 
     306           5 :     const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
     307           5 :     if (pszCode == nullptr)
     308             :     {
     309           1 :         CPLDestroyXMLNode(psTree);
     310           1 :         if (bSetError)
     311             :         {
     312           1 :             VSIError(VSIE_AWSError, "Malformed AWS XML response: %s",
     313             :                      pszErrorMsg);
     314             :         }
     315           1 :         return false;
     316             :     }
     317             : 
     318           4 :     if (EQUAL(pszCode, "AccessDenied"))
     319             :     {
     320             :         const char *pszEndpoint =
     321           1 :             CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
     322           1 :         if (pszEndpoint && pszEndpoint != m_osEndpoint)
     323             :         {
     324           1 :             SetEndpoint(pszEndpoint);
     325           1 :             CPLDebug("OSS", "Switching to endpoint %s", m_osEndpoint.c_str());
     326           1 :             CPLDestroyXMLNode(psTree);
     327             : 
     328           1 :             VSIOSSUpdateParams::UpdateMapFromHandle(this);
     329             : 
     330           1 :             return true;
     331             :         }
     332             :     }
     333             : 
     334           3 :     if (bSetError)
     335             :     {
     336             :         // Translate AWS errors into VSI errors.
     337             :         const char *pszMessage =
     338           3 :             CPLGetXMLValue(psTree, "=Error.Message", nullptr);
     339             : 
     340           3 :         if (pszMessage == nullptr)
     341             :         {
     342           3 :             VSIError(VSIE_AWSError, "%s", pszErrorMsg);
     343             :         }
     344           0 :         else if (EQUAL(pszCode, "AccessDenied"))
     345             :         {
     346           0 :             VSIError(VSIE_AWSAccessDenied, "%s", pszMessage);
     347             :         }
     348           0 :         else if (EQUAL(pszCode, "NoSuchBucket"))
     349             :         {
     350           0 :             VSIError(VSIE_AWSBucketNotFound, "%s", pszMessage);
     351             :         }
     352           0 :         else if (EQUAL(pszCode, "NoSuchKey"))
     353             :         {
     354           0 :             VSIError(VSIE_AWSObjectNotFound, "%s", pszMessage);
     355             :         }
     356           0 :         else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
     357             :         {
     358           0 :             VSIError(VSIE_AWSSignatureDoesNotMatch, "%s", pszMessage);
     359             :         }
     360             :         else
     361             :         {
     362           0 :             VSIError(VSIE_AWSError, "%s", pszMessage);
     363             :         }
     364             :     }
     365             : 
     366           3 :     CPLDestroyXMLNode(psTree);
     367             : 
     368           3 :     return false;
     369             : }
     370             : 
     371             : /************************************************************************/
     372             : /*                            SetEndpoint()                             */
     373             : /************************************************************************/
     374             : 
     375           8 : void VSIOSSHandleHelper::SetEndpoint(const std::string &osStr)
     376             : {
     377           8 :     m_osEndpoint = osStr;
     378           8 :     RebuildURL();
     379           8 : }
     380             : 
     381             : /************************************************************************/
     382             : /*                           GetSignedURL()                             */
     383             : /************************************************************************/
     384             : 
     385           1 : std::string VSIOSSHandleHelper::GetSignedURL(CSLConstList papszOptions)
     386             : {
     387           1 :     GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
     388           1 :     const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
     389           1 :     if (pszStartDate)
     390             :     {
     391             :         int nYear, nMonth, nDay, nHour, nMin, nSec;
     392           1 :         if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
     393           1 :                    &nDay, &nHour, &nMin, &nSec) == 6)
     394             :         {
     395             :             struct tm brokendowntime;
     396           1 :             brokendowntime.tm_year = nYear - 1900;
     397           1 :             brokendowntime.tm_mon = nMonth - 1;
     398           1 :             brokendowntime.tm_mday = nDay;
     399           1 :             brokendowntime.tm_hour = nHour;
     400           1 :             brokendowntime.tm_min = nMin;
     401           1 :             brokendowntime.tm_sec = nSec;
     402           1 :             nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
     403             :         }
     404             :     }
     405             :     GIntBig nExpiresIn =
     406             :         nStartDate +
     407           1 :         atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
     408             :     std::string osExpires(CSLFetchNameValueDef(
     409           2 :         papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
     410             : 
     411           2 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
     412             : 
     413             :     std::string osCanonicalizedResource(
     414           1 :         m_osBucket.empty() ? std::string("/")
     415           4 :                            : "/" + m_osBucket + "/" + m_osObjectKey);
     416             : 
     417           2 :     std::string osStringToSign;
     418           1 :     osStringToSign += osVerb + "\n";
     419           1 :     osStringToSign += "\n";
     420           1 :     osStringToSign += "\n";
     421           1 :     osStringToSign += osExpires + "\n";
     422             :     // osStringToSign += ; // osCanonicalizedHeaders;
     423           1 :     osStringToSign += osCanonicalizedResource;
     424             : #ifdef DEBUG_VERBOSE
     425             :     CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
     426             : #endif
     427             : 
     428           2 :     std::string osSignature(GetSignature(osStringToSign, m_osSecretAccessKey));
     429             : 
     430           1 :     ResetQueryParameters();
     431             :     //  Note:
     432             :     //  https://www.alibabacloud.com/help/doc-detail/31952.htm?spm=a3c0i.o32002en.b99.294.6d70a0fc7cRJfJ
     433             :     //  is wrong on the name of the OSSAccessKeyId parameter !
     434           1 :     AddQueryParameter("OSSAccessKeyId", m_osAccessKeyId);
     435           1 :     AddQueryParameter("Expires", osExpires);
     436           1 :     AddQueryParameter("Signature", osSignature);
     437           2 :     return m_osURL;
     438             : }
     439             : 
     440             : /************************************************************************/
     441             : /*                         UpdateMapFromHandle()                        */
     442             : /************************************************************************/
     443             : 
     444             : std::mutex VSIOSSUpdateParams::gsMutex{};
     445             : 
     446             : std::map<std::string, VSIOSSUpdateParams>
     447             :     VSIOSSUpdateParams::goMapBucketsToOSSParams{};
     448             : 
     449           1 : void VSIOSSUpdateParams::UpdateMapFromHandle(
     450             :     VSIOSSHandleHelper *poOSSHandleHelper)
     451             : {
     452           1 :     std::lock_guard<std::mutex> guard(gsMutex);
     453             : 
     454           1 :     goMapBucketsToOSSParams[poOSSHandleHelper->GetBucket()] =
     455           2 :         VSIOSSUpdateParams(poOSSHandleHelper);
     456           1 : }
     457             : 
     458             : /************************************************************************/
     459             : /*                         UpdateHandleFromMap()                        */
     460             : /************************************************************************/
     461             : 
     462          87 : void VSIOSSUpdateParams::UpdateHandleFromMap(
     463             :     VSIOSSHandleHelper *poOSSHandleHelper)
     464             : {
     465         174 :     std::lock_guard<std::mutex> guard(gsMutex);
     466             : 
     467             :     std::map<std::string, VSIOSSUpdateParams>::iterator oIter =
     468          87 :         goMapBucketsToOSSParams.find(poOSSHandleHelper->GetBucket());
     469          87 :     if (oIter != goMapBucketsToOSSParams.end())
     470             :     {
     471           7 :         oIter->second.UpdateHandlerHelper(poOSSHandleHelper);
     472             :     }
     473          87 : }
     474             : 
     475             : /************************************************************************/
     476             : /*                            ClearCache()                              */
     477             : /************************************************************************/
     478             : 
     479        1531 : void VSIOSSUpdateParams::ClearCache()
     480             : {
     481        3062 :     std::lock_guard<std::mutex> guard(gsMutex);
     482             : 
     483        1531 :     goMapBucketsToOSSParams.clear();
     484        1531 : }
     485             : 
     486             : #endif  // HAVE_CURL
     487             : 
     488             : //! @endcond

Generated by: LCOV version 1.14