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

Generated by: LCOV version 1.14