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: 2025-06-28 21:28:23 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         119 : 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         119 :                                  "OSS_SECRET_ACCESS_KEY", ""));
     185             : 
     186         119 :     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_InvalidCredentials,
     195             :                      "OSS_ACCESS_KEY_ID configuration option not defined");
     196           1 :             return false;
     197             :         }
     198             : 
     199          88 :         return true;
     200             :     }
     201             : 
     202          30 :     VSIError(VSIE_InvalidCredentials,
     203             :              "OSS_SECRET_ACCESS_KEY configuration option not defined");
     204          30 :     return false;
     205             : }
     206             : 
     207             : /************************************************************************/
     208             : /*                          BuildFromURI()                              */
     209             : /************************************************************************/
     210             : 
     211         119 : VSIOSSHandleHelper *VSIOSSHandleHelper::BuildFromURI(const char *pszURI,
     212             :                                                      const char *pszFSPrefix,
     213             :                                                      bool bAllowNoObject,
     214             :                                                      CSLConstList papszOptions)
     215             : {
     216         238 :     std::string osPathForOption("/vsioss/");
     217         119 :     if (pszURI)
     218         119 :         osPathForOption += pszURI;
     219             : 
     220         238 :     std::string osSecretAccessKey;
     221         238 :     std::string osAccessKeyId;
     222         119 :     if (!GetConfiguration(osPathForOption, papszOptions, osSecretAccessKey,
     223             :                           osAccessKeyId))
     224             :     {
     225          31 :         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          10 : 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          10 :     if (!STARTS_WITH(pszErrorMsg, "<?xml"))
     287             :     {
     288           4 :         if (bSetError)
     289             :         {
     290           4 :             VSIError(VSIE_ObjectStorageGenericError, "Invalid OSS response: %s",
     291             :                      pszErrorMsg);
     292             :         }
     293           4 :         return false;
     294             :     }
     295             : 
     296           6 :     CPLXMLNode *psTree = CPLParseXMLString(pszErrorMsg);
     297           6 :     if (psTree == nullptr)
     298             :     {
     299           1 :         if (bSetError)
     300             :         {
     301           1 :             VSIError(VSIE_ObjectStorageGenericError,
     302             :                      "Malformed OSS XML response: %s", pszErrorMsg);
     303             :         }
     304           1 :         return false;
     305             :     }
     306             : 
     307           5 :     const char *pszCode = CPLGetXMLValue(psTree, "=Error.Code", nullptr);
     308           5 :     if (pszCode == nullptr)
     309             :     {
     310           1 :         CPLDestroyXMLNode(psTree);
     311           1 :         if (bSetError)
     312             :         {
     313           1 :             VSIError(VSIE_ObjectStorageGenericError,
     314             :                      "Malformed OSS XML response: %s", pszErrorMsg);
     315             :         }
     316           1 :         return false;
     317             :     }
     318             : 
     319           4 :     if (EQUAL(pszCode, "AccessDenied"))
     320             :     {
     321             :         const char *pszEndpoint =
     322           1 :             CPLGetXMLValue(psTree, "=Error.Endpoint", nullptr);
     323           1 :         if (pszEndpoint && pszEndpoint != m_osEndpoint)
     324             :         {
     325           1 :             SetEndpoint(pszEndpoint);
     326           1 :             CPLDebug("OSS", "Switching to endpoint %s", m_osEndpoint.c_str());
     327           1 :             CPLDestroyXMLNode(psTree);
     328             : 
     329           1 :             VSIOSSUpdateParams::UpdateMapFromHandle(this);
     330             : 
     331           1 :             return true;
     332             :         }
     333             :     }
     334             : 
     335           3 :     if (bSetError)
     336             :     {
     337             :         // Translate AWS errors into VSI errors.
     338             :         const char *pszMessage =
     339           3 :             CPLGetXMLValue(psTree, "=Error.Message", nullptr);
     340             : 
     341           3 :         if (pszMessage == nullptr)
     342             :         {
     343           3 :             VSIError(VSIE_ObjectStorageGenericError, "%s", pszErrorMsg);
     344             :         }
     345           0 :         else if (EQUAL(pszCode, "AccessDenied"))
     346             :         {
     347           0 :             VSIError(VSIE_AccessDenied, "%s", pszMessage);
     348             :         }
     349           0 :         else if (EQUAL(pszCode, "NoSuchBucket"))
     350             :         {
     351           0 :             VSIError(VSIE_BucketNotFound, "%s", pszMessage);
     352             :         }
     353           0 :         else if (EQUAL(pszCode, "NoSuchKey"))
     354             :         {
     355           0 :             VSIError(VSIE_ObjectNotFound, "%s", pszMessage);
     356             :         }
     357           0 :         else if (EQUAL(pszCode, "SignatureDoesNotMatch"))
     358             :         {
     359           0 :             VSIError(VSIE_SignatureDoesNotMatch, "%s", pszMessage);
     360             :         }
     361             :         else
     362             :         {
     363           0 :             VSIError(VSIE_ObjectStorageGenericError, "%s", pszMessage);
     364             :         }
     365             :     }
     366             : 
     367           3 :     CPLDestroyXMLNode(psTree);
     368             : 
     369           3 :     return false;
     370             : }
     371             : 
     372             : /************************************************************************/
     373             : /*                            SetEndpoint()                             */
     374             : /************************************************************************/
     375             : 
     376           8 : void VSIOSSHandleHelper::SetEndpoint(const std::string &osStr)
     377             : {
     378           8 :     m_osEndpoint = osStr;
     379           8 :     RebuildURL();
     380           8 : }
     381             : 
     382             : /************************************************************************/
     383             : /*                           GetSignedURL()                             */
     384             : /************************************************************************/
     385             : 
     386           1 : std::string VSIOSSHandleHelper::GetSignedURL(CSLConstList papszOptions)
     387             : {
     388           1 :     GIntBig nStartDate = static_cast<GIntBig>(time(nullptr));
     389           1 :     const char *pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
     390           1 :     if (pszStartDate)
     391             :     {
     392             :         int nYear, nMonth, nDay, nHour, nMin, nSec;
     393           1 :         if (sscanf(pszStartDate, "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
     394           1 :                    &nDay, &nHour, &nMin, &nSec) == 6)
     395             :         {
     396             :             struct tm brokendowntime;
     397           1 :             brokendowntime.tm_year = nYear - 1900;
     398           1 :             brokendowntime.tm_mon = nMonth - 1;
     399           1 :             brokendowntime.tm_mday = nDay;
     400           1 :             brokendowntime.tm_hour = nHour;
     401           1 :             brokendowntime.tm_min = nMin;
     402           1 :             brokendowntime.tm_sec = nSec;
     403           1 :             nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
     404             :         }
     405             :     }
     406             :     GIntBig nExpiresIn =
     407             :         nStartDate +
     408           1 :         atoi(CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
     409             :     std::string osExpires(CSLFetchNameValueDef(
     410           2 :         papszOptions, "EXPIRES", CPLSPrintf(CPL_FRMT_GIB, nExpiresIn)));
     411             : 
     412           2 :     std::string osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
     413             : 
     414             :     std::string osCanonicalizedResource(
     415           1 :         m_osBucket.empty() ? std::string("/")
     416           4 :                            : "/" + m_osBucket + "/" + m_osObjectKey);
     417             : 
     418           2 :     std::string osStringToSign;
     419           1 :     osStringToSign += osVerb + "\n";
     420           1 :     osStringToSign += "\n";
     421           1 :     osStringToSign += "\n";
     422           1 :     osStringToSign += osExpires + "\n";
     423             :     // osStringToSign += ; // osCanonicalizedHeaders;
     424           1 :     osStringToSign += osCanonicalizedResource;
     425             : #ifdef DEBUG_VERBOSE
     426             :     CPLDebug("OSS", "osStringToSign = %s", osStringToSign.c_str());
     427             : #endif
     428             : 
     429           2 :     std::string osSignature(GetSignature(osStringToSign, m_osSecretAccessKey));
     430             : 
     431           1 :     ResetQueryParameters();
     432             :     //  Note:
     433             :     //  https://www.alibabacloud.com/help/doc-detail/31952.htm?spm=a3c0i.o32002en.b99.294.6d70a0fc7cRJfJ
     434             :     //  is wrong on the name of the OSSAccessKeyId parameter !
     435           1 :     AddQueryParameter("OSSAccessKeyId", m_osAccessKeyId);
     436           1 :     AddQueryParameter("Expires", osExpires);
     437           1 :     AddQueryParameter("Signature", osSignature);
     438           2 :     return m_osURL;
     439             : }
     440             : 
     441             : /************************************************************************/
     442             : /*                         UpdateMapFromHandle()                        */
     443             : /************************************************************************/
     444             : 
     445             : std::mutex VSIOSSUpdateParams::gsMutex{};
     446             : 
     447             : std::map<std::string, VSIOSSUpdateParams>
     448             :     VSIOSSUpdateParams::goMapBucketsToOSSParams{};
     449             : 
     450           1 : void VSIOSSUpdateParams::UpdateMapFromHandle(
     451             :     VSIOSSHandleHelper *poOSSHandleHelper)
     452             : {
     453           1 :     std::lock_guard<std::mutex> guard(gsMutex);
     454             : 
     455           1 :     goMapBucketsToOSSParams[poOSSHandleHelper->GetBucket()] =
     456           2 :         VSIOSSUpdateParams(poOSSHandleHelper);
     457           1 : }
     458             : 
     459             : /************************************************************************/
     460             : /*                         UpdateHandleFromMap()                        */
     461             : /************************************************************************/
     462             : 
     463          87 : void VSIOSSUpdateParams::UpdateHandleFromMap(
     464             :     VSIOSSHandleHelper *poOSSHandleHelper)
     465             : {
     466         174 :     std::lock_guard<std::mutex> guard(gsMutex);
     467             : 
     468             :     std::map<std::string, VSIOSSUpdateParams>::iterator oIter =
     469          87 :         goMapBucketsToOSSParams.find(poOSSHandleHelper->GetBucket());
     470          87 :     if (oIter != goMapBucketsToOSSParams.end())
     471             :     {
     472           7 :         oIter->second.UpdateHandlerHelper(poOSSHandleHelper);
     473             :     }
     474          87 : }
     475             : 
     476             : /************************************************************************/
     477             : /*                            ClearCache()                              */
     478             : /************************************************************************/
     479             : 
     480        1767 : void VSIOSSUpdateParams::ClearCache()
     481             : {
     482        3534 :     std::lock_guard<std::mutex> guard(gsMutex);
     483             : 
     484        1767 :     goMapBucketsToOSSParams.clear();
     485        1767 : }
     486             : 
     487             : #endif  // HAVE_CURL
     488             : 
     489             : //! @endcond

Generated by: LCOV version 1.14