LCOV - code coverage report
Current view: top level - port - cpl_vsil_gs.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 272 321 84.7 %
Date: 2025-08-01 10:10:57 Functions: 22 26 84.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement VSI large file api for Google Cloud Storage
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2018, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "cpl_http.h"
      15             : #include "cpl_minixml.h"
      16             : #include "cpl_json.h"
      17             : #include "cpl_vsil_curl_priv.h"
      18             : #include "cpl_vsil_curl_class.h"
      19             : 
      20             : #include <errno.h>
      21             : 
      22             : #include <algorithm>
      23             : #include <set>
      24             : #include <map>
      25             : #include <memory>
      26             : 
      27             : #include "cpl_google_cloud.h"
      28             : 
      29             : // To avoid aliasing to GetDiskFreeSpace to GetDiskFreeSpaceA on Windows
      30             : #ifdef GetDiskFreeSpace
      31             : #undef GetDiskFreeSpace
      32             : #endif
      33             : 
      34             : #ifndef HAVE_CURL
      35             : 
      36             : void VSIInstallGSFileHandler(void)
      37             : {
      38             :     // Not supported.
      39             : }
      40             : 
      41             : #else
      42             : 
      43             : //! @cond Doxygen_Suppress
      44             : #ifndef DOXYGEN_SKIP
      45             : 
      46             : #define ENABLE_DEBUG 0
      47             : 
      48             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      49             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      50             : 
      51             : namespace cpl
      52             : {
      53             : 
      54             : /************************************************************************/
      55             : /*                         VSIGSFSHandler                               */
      56             : /************************************************************************/
      57             : 
      58             : class VSIGSFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload
      59             : {
      60             :     CPL_DISALLOW_COPY_ASSIGN(VSIGSFSHandler)
      61             :     const std::string m_osPrefix;
      62             : 
      63             :   protected:
      64             :     VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
      65             : 
      66          49 :     const char *GetDebugKey() const override
      67             :     {
      68          49 :         return "GS";
      69             :     }
      70             : 
      71         578 :     std::string GetFSPrefix() const override
      72             :     {
      73         578 :         return m_osPrefix;
      74             :     }
      75             : 
      76             :     std::string
      77             :     GetURLFromFilename(const std::string &osFilename) const override;
      78             : 
      79             :     IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
      80             :                                                bool bAllowNoObject) override;
      81             : 
      82             :     void ClearCache() override;
      83             : 
      84           0 :     bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
      85             :     {
      86           0 :         return STARTS_WITH(pszHeaderName, "x-goog-");
      87             :     }
      88             : 
      89             :     VSIVirtualHandleUniquePtr
      90             :     CreateWriteHandle(const char *pszFilename,
      91             :                       CSLConstList papszOptions) override;
      92             : 
      93           0 :     GIntBig GetDiskFreeSpace(const char * /* pszDirname */) override
      94             :     {
      95             :         // There is no limit per bucket, but a 5 TiB limit per object.
      96           0 :         return static_cast<GIntBig>(5) * 1024 * 1024 * 1024 * 1024;
      97             :     }
      98             : 
      99             :   public:
     100        1691 :     explicit VSIGSFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
     101             :     {
     102        1691 :     }
     103             : 
     104             :     ~VSIGSFSHandler() override;
     105             : 
     106             :     const char *GetOptions() override;
     107             : 
     108             :     char *GetSignedURL(const char *pszFilename,
     109             :                        CSLConstList papszOptions) override;
     110             : 
     111             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
     112             :                            CSLConstList papszOptions) override;
     113             : 
     114             :     bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
     115             :                          const char *pszDomain,
     116             :                          CSLConstList papszOptions) override;
     117             : 
     118             :     int *UnlinkBatch(CSLConstList papszFiles) override;
     119             :     int RmdirRecursive(const char *pszDirname) override;
     120             : 
     121             :     std::string
     122             :     GetStreamingFilename(const std::string &osFilename) const override;
     123             : 
     124           0 :     VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
     125             :     {
     126           0 :         return new VSIGSFSHandler(pszPrefix);
     127             :     }
     128             : 
     129           1 :     bool SupportsMultipartAbort() const override
     130             :     {
     131           1 :         return true;
     132             :     }
     133             : };
     134             : 
     135             : /************************************************************************/
     136             : /*                            VSIGSHandle                               */
     137             : /************************************************************************/
     138             : 
     139             : class VSIGSHandle final : public IVSIS3LikeHandle
     140             : {
     141             :     CPL_DISALLOW_COPY_ASSIGN(VSIGSHandle)
     142             : 
     143             :     VSIGSHandleHelper *m_poHandleHelper = nullptr;
     144             : 
     145             :   protected:
     146             :     struct curl_slist *GetCurlHeaders(const std::string &osVerb,
     147             :                                       struct curl_slist *psHeaders) override;
     148             : 
     149             :   public:
     150             :     VSIGSHandle(VSIGSFSHandler *poFS, const char *pszFilename,
     151             :                 VSIGSHandleHelper *poHandleHelper);
     152             :     ~VSIGSHandle() override;
     153             : };
     154             : 
     155             : /************************************************************************/
     156             : /*                          ~VSIGSFSHandler()                           */
     157             : /************************************************************************/
     158             : 
     159        2244 : VSIGSFSHandler::~VSIGSFSHandler()
     160             : {
     161        1122 :     VSICurlFilesystemHandlerBase::ClearCache();
     162        2244 : }
     163             : 
     164             : /************************************************************************/
     165             : /*                            ClearCache()                              */
     166             : /************************************************************************/
     167             : 
     168         331 : void VSIGSFSHandler::ClearCache()
     169             : {
     170         331 :     VSICurlFilesystemHandlerBase::ClearCache();
     171             : 
     172         331 :     VSIGSHandleHelper::ClearCache();
     173         331 : }
     174             : 
     175             : /************************************************************************/
     176             : /*                          CreateFileHandle()                          */
     177             : /************************************************************************/
     178             : 
     179          35 : VSICurlHandle *VSIGSFSHandler::CreateFileHandle(const char *pszFilename)
     180             : {
     181          70 :     VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
     182         105 :         pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
     183          35 :     if (poHandleHelper == nullptr)
     184           5 :         return nullptr;
     185          30 :     return new VSIGSHandle(this, pszFilename, poHandleHelper);
     186             : }
     187             : 
     188             : /************************************************************************/
     189             : /*                           GetOptions()                               */
     190             : /************************************************************************/
     191             : 
     192           2 : const char *VSIGSFSHandler::GetOptions()
     193             : {
     194             :     static std::string osOptions(
     195           2 :         std::string("<Options>")
     196             :             .append(
     197             :                 "  <Option name='GS_SECRET_ACCESS_KEY' type='string' "
     198             :                 "description='Secret access key. To use with "
     199             :                 "GS_ACCESS_KEY_ID'/>"
     200             :                 "  <Option name='GS_ACCESS_KEY_ID' type='string' "
     201             :                 "description='Access key id'/>"
     202             :                 "  <Option name='GS_NO_SIGN_REQUEST' type='boolean' "
     203             :                 "description='Whether to disable signing of requests' "
     204             :                 "default='NO'/>"
     205             :                 "  <Option name='GS_OAUTH2_REFRESH_TOKEN' type='string' "
     206             :                 "description='OAuth2 refresh token. For OAuth2 client "
     207             :                 "authentication. "
     208             :                 "To use with GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET'/>"
     209             :                 "  <Option name='GS_OAUTH2_CLIENT_ID' type='string' "
     210             :                 "description='OAuth2 client id for OAuth2 client "
     211             :                 "authentication'/>"
     212             :                 "  <Option name='GS_OAUTH2_CLIENT_SECRET' type='string' "
     213             :                 "description='OAuth2 client secret for OAuth2 client "
     214             :                 "authentication'/>"
     215             :                 "  <Option name='GS_OAUTH2_PRIVATE_KEY' type='string' "
     216             :                 "description='Private key for OAuth2 service account "
     217             :                 "authentication. "
     218             :                 "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
     219             :                 "  <Option name='GS_OAUTH2_PRIVATE_KEY_FILE' type='string' "
     220             :                 "description='Filename that contains private key for OAuth2 "
     221             :                 "service "
     222             :                 "account authentication. "
     223             :                 "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
     224             :                 "  <Option name='GS_OAUTH2_CLIENT_EMAIL' type='string' "
     225             :                 "description='Client email to use with OAuth2 service account "
     226             :                 "authentication'/>"
     227             :                 "  <Option name='GS_OAUTH2_SCOPE' type='string' "
     228             :                 "description='OAuth2 authorization scope' "
     229             :                 "default='https://www.googleapis.com/auth/"
     230             :                 "devstorage.read_write'/>"
     231             :                 "  <Option name='CPL_MACHINE_IS_GCE' type='boolean' "
     232             :                 "description='Whether the current machine is a Google Compute "
     233             :                 "Engine "
     234             :                 "instance' default='NO'/>"
     235             :                 "  <Option name='CPL_GCE_CHECK_LOCAL_FILES' type='boolean' "
     236             :                 "description='Whether to check system logs to determine "
     237             :                 "if current machine is a GCE instance' default='YES'/>"
     238             :                 "description='Filename that contains AWS configuration' "
     239             :                 "default='~/.aws/config'/>"
     240             :                 "  <Option name='CPL_GS_CREDENTIALS_FILE' type='string' "
     241             :                 "description='Filename that contains Google Storage "
     242             :                 "credentials' "
     243             :                 "default='~/.boto'/>"
     244             :                 "  <Option name='VSIGS_CHUNK_SIZE' type='int' "
     245             :                 "description='Size in MiB for chunks of files that are "
     246             :                 "uploaded. The"
     247           1 :                 "default value allows for files up to ")
     248           1 :             .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB() *
     249           1 :                                          GetMaximumPartCount() / 1024))
     250           1 :             .append("GiB each' default='")
     251           1 :             .append(CPLSPrintf("%d", GetDefaultPartSizeInMiB()))
     252           1 :             .append("' min='")
     253           1 :             .append(CPLSPrintf("%d", GetMinimumPartSizeInMiB()))
     254           1 :             .append("' max='")
     255           1 :             .append(CPLSPrintf("%d", GetMaximumPartSizeInMiB()))
     256           1 :             .append("'/>")
     257           1 :             .append(VSICurlFilesystemHandlerBase::GetOptionsStatic())
     258           3 :             .append("</Options>"));
     259           2 :     return osOptions.c_str();
     260             : }
     261             : 
     262             : /************************************************************************/
     263             : /*                           GetSignedURL()                             */
     264             : /************************************************************************/
     265             : 
     266           6 : char *VSIGSFSHandler::GetSignedURL(const char *pszFilename,
     267             :                                    CSLConstList papszOptions)
     268             : {
     269           6 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     270           0 :         return nullptr;
     271             : 
     272          12 :     VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
     273          18 :         pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(), nullptr,
     274             :         papszOptions);
     275           6 :     if (poHandleHelper == nullptr)
     276             :     {
     277           1 :         return nullptr;
     278             :     }
     279             : 
     280           5 :     std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
     281             : 
     282           5 :     delete poHandleHelper;
     283           5 :     return osRet.empty() ? nullptr : CPLStrdup(osRet.c_str());
     284             : }
     285             : 
     286             : /************************************************************************/
     287             : /*                          GetURLFromFilename()                         */
     288             : /************************************************************************/
     289             : 
     290             : std::string
     291          11 : VSIGSFSHandler::GetURLFromFilename(const std::string &osFilename) const
     292             : {
     293             :     const std::string osFilenameWithoutPrefix =
     294          22 :         osFilename.substr(GetFSPrefix().size());
     295             :     auto poHandleHelper =
     296             :         std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     297          22 :             osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str()));
     298          11 :     if (poHandleHelper == nullptr)
     299           1 :         return std::string();
     300          10 :     return poHandleHelper->GetURL();
     301             : }
     302             : 
     303             : /************************************************************************/
     304             : /*                          CreateHandleHelper()                        */
     305             : /************************************************************************/
     306             : 
     307          14 : IVSIS3LikeHandleHelper *VSIGSFSHandler::CreateHandleHelper(const char *pszURI,
     308             :                                                            bool)
     309             : {
     310          14 :     return VSIGSHandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str());
     311             : }
     312             : 
     313             : /************************************************************************/
     314             : /*                          CreateWriteHandle()                         */
     315             : /************************************************************************/
     316             : 
     317             : VSIVirtualHandleUniquePtr
     318           1 : VSIGSFSHandler::CreateWriteHandle(const char *pszFilename,
     319             :                                   CSLConstList papszOptions)
     320             : {
     321             :     auto poHandleHelper =
     322           1 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false);
     323           1 :     if (poHandleHelper == nullptr)
     324           0 :         return nullptr;
     325             :     auto poHandle = std::make_unique<VSIMultipartWriteHandle>(
     326           2 :         this, pszFilename, poHandleHelper, papszOptions);
     327           1 :     if (!poHandle->IsOK())
     328             :     {
     329           0 :         return nullptr;
     330             :     }
     331           1 :     return VSIVirtualHandleUniquePtr(poHandle.release());
     332             : }
     333             : 
     334             : /************************************************************************/
     335             : /*                          GetFileMetadata()                           */
     336             : /************************************************************************/
     337             : 
     338           7 : char **VSIGSFSHandler::GetFileMetadata(const char *pszFilename,
     339             :                                        const char *pszDomain,
     340             :                                        CSLConstList papszOptions)
     341             : {
     342           7 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     343           0 :         return nullptr;
     344             : 
     345           7 :     if (pszDomain == nullptr)
     346             :     {
     347             :         // Handle case of requesting GetFileMetadata() on the bucket root
     348           3 :         std::string osFilename(pszFilename);
     349           3 :         if (osFilename.back() == '/')
     350           3 :             osFilename.pop_back();
     351           3 :         if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
     352             :         {
     353             :             const std::string osBucket =
     354           6 :                 osFilename.substr(GetFSPrefix().size());
     355             :             const std::string osResource =
     356           9 :                 std::string("storage/v1/b/").append(osBucket);
     357             : 
     358             :             auto poHandleHelper = std::unique_ptr<VSIGSHandleHelper>(
     359             :                 VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
     360           3 :                                                 GetFSPrefix().c_str(),
     361           9 :                                                 osBucket.c_str()));
     362           3 :             if (!poHandleHelper)
     363           0 :                 return nullptr;
     364             : 
     365             :             // Check if OAuth2 is used externally and a bearer token is passed
     366             :             // as a header in path-specific options
     367             :             const CPLStringList aosHTTPOptions(
     368           6 :                 CPLHTTPGetOptionsFromEnv(pszFilename));
     369           3 :             bool bUsingBearerToken = false;
     370           3 :             const char *pszHeaders = aosHTTPOptions.FetchNameValue("HEADERS");
     371           3 :             if (pszHeaders && strstr(pszHeaders, "Authorization: Bearer "))
     372           1 :                 bUsingBearerToken = true;
     373             : 
     374             :             // The JSON API cannot be used with HMAC keys
     375           3 :             if (poHandleHelper->UsesHMACKey() && !bUsingBearerToken)
     376             :             {
     377           1 :                 CPLDebug(GetDebugKey(),
     378             :                          "GetFileMetadata() on bucket "
     379             :                          "only available for OAuth2 authentication");
     380           1 :                 return VSICurlFilesystemHandlerBase::GetFileMetadata(
     381           1 :                     pszFilename, pszDomain, papszOptions);
     382             :             }
     383             : 
     384           4 :             NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     385           4 :             NetworkStatisticsAction oContextAction("GetFileMetadata");
     386             : 
     387           4 :             const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     388           4 :             CPLHTTPRetryContext oRetryContext(oRetryParameters);
     389             : 
     390             :             bool bRetry;
     391           4 :             CPLStringList aosResult;
     392           2 :             do
     393             :             {
     394           2 :                 bRetry = false;
     395           2 :                 CURL *hCurlHandle = curl_easy_init();
     396             : 
     397             :                 struct curl_slist *headers =
     398           4 :                     static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     399           2 :                         hCurlHandle, poHandleHelper->GetURL().c_str(),
     400             :                         aosHTTPOptions.List()));
     401           2 :                 headers = poHandleHelper->GetCurlHeaders("GET", headers);
     402             : 
     403           4 :                 CurlRequestHelper requestHelper;
     404           2 :                 const long response_code = requestHelper.perform(
     405           2 :                     hCurlHandle, headers, this, poHandleHelper.get());
     406             : 
     407           2 :                 NetworkStatisticsLogger::LogGET(
     408             :                     requestHelper.sWriteFuncData.nSize);
     409             : 
     410           2 :                 if (response_code != 200 ||
     411           2 :                     requestHelper.sWriteFuncData.pBuffer == nullptr)
     412             :                 {
     413             :                     // Look if we should attempt a retry
     414           0 :                     if (oRetryContext.CanRetry(
     415             :                             static_cast<int>(response_code),
     416           0 :                             requestHelper.sWriteFuncHeaderData.pBuffer,
     417             :                             requestHelper.szCurlErrBuf))
     418             :                     {
     419           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     420             :                                  "HTTP error code: %d - %s. "
     421             :                                  "Retrying again in %.1f secs",
     422             :                                  static_cast<int>(response_code),
     423           0 :                                  poHandleHelper->GetURL().c_str(),
     424             :                                  oRetryContext.GetCurrentDelay());
     425           0 :                         CPLSleep(oRetryContext.GetCurrentDelay());
     426           0 :                         bRetry = true;
     427             :                     }
     428             :                     else
     429             :                     {
     430           0 :                         CPLDebug(GetDebugKey(), "%s",
     431           0 :                                  requestHelper.sWriteFuncData.pBuffer
     432             :                                      ? requestHelper.sWriteFuncData.pBuffer
     433             :                                      : "(null)");
     434           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     435             :                                  "GetFileMetadata failed");
     436             :                     }
     437             :                 }
     438             :                 else
     439             :                 {
     440           4 :                     CPLJSONDocument oDoc;
     441           6 :                     if (oDoc.LoadMemory(
     442             :                             reinterpret_cast<const GByte *>(
     443           2 :                                 requestHelper.sWriteFuncData.pBuffer),
     444             :                             static_cast<int>(
     445           4 :                                 requestHelper.sWriteFuncData.nSize)) &&
     446           4 :                         oDoc.GetRoot().GetType() == CPLJSONObject::Type::Object)
     447             :                     {
     448           4 :                         for (const auto &oObj : oDoc.GetRoot().GetChildren())
     449             :                         {
     450           4 :                             aosResult.SetNameValue(oObj.GetName().c_str(),
     451           6 :                                                    oObj.ToString().c_str());
     452             :                         }
     453             :                     }
     454             :                     else
     455             :                     {
     456             :                         // Shouldn't happen normally
     457             :                         aosResult.SetNameValue(
     458           0 :                             "DATA", requestHelper.sWriteFuncData.pBuffer);
     459             :                     }
     460             :                 }
     461             : 
     462           2 :                 curl_easy_cleanup(hCurlHandle);
     463             :             } while (bRetry);
     464             : 
     465           2 :             return aosResult.StealList();
     466             :         }
     467             :     }
     468             : 
     469           4 :     if (pszDomain == nullptr || !EQUAL(pszDomain, "ACL"))
     470             :     {
     471           2 :         return VSICurlFilesystemHandlerBase::GetFileMetadata(
     472           2 :             pszFilename, pszDomain, papszOptions);
     473             :     }
     474             : 
     475             :     auto poHandleHelper =
     476           4 :         std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     477          10 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
     478           2 :     if (!poHandleHelper)
     479           0 :         return nullptr;
     480             : 
     481           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     482           4 :     NetworkStatisticsAction oContextAction("GetFileMetadata");
     483             : 
     484           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     485           4 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     486           4 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
     487             : 
     488             :     bool bRetry;
     489           4 :     CPLStringList aosResult;
     490           2 :     do
     491             :     {
     492           2 :         bRetry = false;
     493           2 :         CURL *hCurlHandle = curl_easy_init();
     494           2 :         poHandleHelper->AddQueryParameter("acl", "");
     495             : 
     496             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     497           2 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
     498             :                               aosHTTPOptions.List()));
     499           2 :         headers = poHandleHelper->GetCurlHeaders("GET", headers);
     500             : 
     501           4 :         CurlRequestHelper requestHelper;
     502           2 :         const long response_code = requestHelper.perform(
     503             :             hCurlHandle, headers, this, poHandleHelper.get());
     504             : 
     505           2 :         NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
     506             : 
     507           2 :         if (response_code != 200 ||
     508           1 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
     509             :         {
     510             :             // Look if we should attempt a retry
     511           1 :             if (oRetryContext.CanRetry(
     512             :                     static_cast<int>(response_code),
     513           1 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     514             :                     requestHelper.szCurlErrBuf))
     515             :             {
     516           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     517             :                          "HTTP error code: %d - %s. "
     518             :                          "Retrying again in %.1f secs",
     519             :                          static_cast<int>(response_code),
     520           0 :                          poHandleHelper->GetURL().c_str(),
     521             :                          oRetryContext.GetCurrentDelay());
     522           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     523           0 :                 bRetry = true;
     524             :             }
     525             :             else
     526             :             {
     527           1 :                 CPLDebug(GetDebugKey(), "%s",
     528           1 :                          requestHelper.sWriteFuncData.pBuffer
     529             :                              ? requestHelper.sWriteFuncData.pBuffer
     530             :                              : "(null)");
     531           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "GetFileMetadata failed");
     532             :             }
     533             :         }
     534             :         else
     535             :         {
     536           1 :             aosResult.SetNameValue("XML", requestHelper.sWriteFuncData.pBuffer);
     537             :         }
     538             : 
     539           2 :         curl_easy_cleanup(hCurlHandle);
     540             :     } while (bRetry);
     541           2 :     return aosResult.StealList();
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                          SetFileMetadata()                           */
     546             : /************************************************************************/
     547             : 
     548           5 : bool VSIGSFSHandler::SetFileMetadata(const char *pszFilename,
     549             :                                      CSLConstList papszMetadata,
     550             :                                      const char *pszDomain,
     551             :                                      CSLConstList /* papszOptions */)
     552             : {
     553           5 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     554           0 :         return false;
     555             : 
     556           5 :     if (pszDomain == nullptr ||
     557           5 :         !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "ACL")))
     558             :     {
     559           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     560             :                  "Only HEADERS and ACL domain are supported");
     561           1 :         return false;
     562             :     }
     563             : 
     564           4 :     if (EQUAL(pszDomain, "HEADERS"))
     565             :     {
     566           1 :         return CopyObject(pszFilename, pszFilename, papszMetadata) == 0;
     567             :     }
     568             : 
     569           3 :     const char *pszXML = CSLFetchNameValue(papszMetadata, "XML");
     570           3 :     if (pszXML == nullptr)
     571             :     {
     572           1 :         CPLError(CE_Failure, CPLE_AppDefined, "XML key is missing in metadata");
     573           1 :         return false;
     574             :     }
     575             : 
     576             :     auto poHandleHelper =
     577           4 :         std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     578          10 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
     579           2 :     if (!poHandleHelper)
     580           0 :         return false;
     581             : 
     582           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     583           4 :     NetworkStatisticsAction oContextAction("SetFileMetadata");
     584             : 
     585             :     bool bRetry;
     586           2 :     bool bRet = false;
     587             : 
     588           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     589           4 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     590           2 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
     591             : 
     592           2 :     do
     593             :     {
     594           2 :         bRetry = false;
     595           2 :         CURL *hCurlHandle = curl_easy_init();
     596           2 :         poHandleHelper->AddQueryParameter("acl", "");
     597           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
     598           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
     599             : 
     600             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     601           2 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
     602             :                               aosHTTPOptions.List()));
     603           2 :         headers = curl_slist_append(headers, "Content-Type: application/xml");
     604           4 :         headers = poHandleHelper->GetCurlHeaders("PUT", headers, pszXML,
     605           2 :                                                  strlen(pszXML));
     606           2 :         NetworkStatisticsLogger::LogPUT(strlen(pszXML));
     607             : 
     608           4 :         CurlRequestHelper requestHelper;
     609           2 :         const long response_code = requestHelper.perform(
     610             :             hCurlHandle, headers, this, poHandleHelper.get());
     611             : 
     612           2 :         if (response_code != 200)
     613             :         {
     614             :             // Look if we should attempt a retry
     615           1 :             if (oRetryContext.CanRetry(
     616             :                     static_cast<int>(response_code),
     617           1 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     618             :                     requestHelper.szCurlErrBuf))
     619             :             {
     620           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     621             :                          "HTTP error code: %d - %s. "
     622             :                          "Retrying again in %.1f secs",
     623             :                          static_cast<int>(response_code),
     624           0 :                          poHandleHelper->GetURL().c_str(),
     625             :                          oRetryContext.GetCurrentDelay());
     626           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     627           0 :                 bRetry = true;
     628             :             }
     629             :             else
     630             :             {
     631           1 :                 CPLDebug(GetDebugKey(), "%s",
     632           1 :                          requestHelper.sWriteFuncData.pBuffer
     633             :                              ? requestHelper.sWriteFuncData.pBuffer
     634             :                              : "(null)");
     635           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "SetFileMetadata failed");
     636             :             }
     637             :         }
     638             :         else
     639             :         {
     640           1 :             bRet = true;
     641             :         }
     642             : 
     643           2 :         curl_easy_cleanup(hCurlHandle);
     644             :     } while (bRetry);
     645           2 :     return bRet;
     646             : }
     647             : 
     648             : /************************************************************************/
     649             : /*                           UnlinkBatch()                              */
     650             : /************************************************************************/
     651             : 
     652           2 : int *VSIGSFSHandler::UnlinkBatch(CSLConstList papszFiles)
     653             : {
     654             :     // Implemented using
     655             :     // https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
     656             : 
     657           2 :     const char *pszFirstFilename =
     658           2 :         papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
     659             : 
     660           2 :     bool bUsingBearerToken = false;
     661           2 :     if (pszFirstFilename)
     662             :     {
     663             :         const CPLStringList aosHTTPOptions(
     664           4 :             CPLHTTPGetOptionsFromEnv(pszFirstFilename));
     665           2 :         const char *pszHeaders = aosHTTPOptions.FetchNameValue("HEADERS");
     666           2 :         if (pszHeaders && strstr(pszHeaders, "Authorization: Bearer "))
     667           1 :             bUsingBearerToken = true;
     668             :     }
     669             : 
     670             :     auto poHandleHelper =
     671             :         std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     672           2 :             "batch/storage/v1", GetFSPrefix().c_str(),
     673           6 :             pszFirstFilename &&
     674           4 :                     STARTS_WITH(pszFirstFilename, GetFSPrefix().c_str())
     675           6 :                 ? pszFirstFilename + GetFSPrefix().size()
     676           8 :                 : nullptr));
     677             : 
     678             :     // The JSON API cannot be used with HMAC keys
     679           2 :     if ((poHandleHelper && poHandleHelper->UsesHMACKey()) && !bUsingBearerToken)
     680             :     {
     681           0 :         CPLDebug(GetDebugKey(), "UnlinkBatch() has an efficient implementation "
     682             :                                 "only for OAuth2 authentication");
     683           0 :         return VSICurlFilesystemHandlerBase::UnlinkBatch(papszFiles);
     684             :     }
     685             : 
     686             :     int *panRet =
     687           2 :         static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
     688             : 
     689           2 :     if (!poHandleHelper || pszFirstFilename == nullptr)
     690           0 :         return panRet;
     691             : 
     692           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     693           4 :     NetworkStatisticsAction oContextAction("UnlinkBatch");
     694             : 
     695             :     // For debug / testing only
     696             :     const int nBatchSize =
     697           2 :         std::max(1, std::min(100, atoi(CPLGetConfigOption(
     698           2 :                                       "CPL_VSIGS_UNLINK_BATCH_SIZE", "100"))));
     699           4 :     std::string osPOSTContent;
     700             : 
     701             :     const CPLStringList aosHTTPOptions(
     702           4 :         CPLHTTPGetOptionsFromEnv(pszFirstFilename));
     703           4 :     const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
     704           4 :     CPLHTTPRetryContext oRetryContext(oRetryParameters);
     705             : 
     706           6 :     for (int i = 0; papszFiles && papszFiles[i]; i++)
     707             :     {
     708           4 :         CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
     709             :         const char *pszFilenameWithoutPrefix =
     710           4 :             papszFiles[i] + GetFSPrefix().size();
     711           4 :         const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
     712           4 :         if (!pszSlash)
     713           0 :             return panRet;
     714           8 :         std::string osBucket;
     715             :         osBucket.assign(pszFilenameWithoutPrefix,
     716           4 :                         pszSlash - pszFilenameWithoutPrefix);
     717             : 
     718           8 :         std::string osResource = "storage/v1/b/";
     719           4 :         osResource += osBucket;
     720           4 :         osResource += "/o/";
     721           4 :         osResource += CPLAWSURLEncode(pszSlash + 1, true);
     722             : 
     723             : #ifdef ADD_AUTH_TO_NESTED_REQUEST
     724             :         std::string osAuthorization;
     725             :         std::string osDate;
     726             :         {
     727             :             auto poTmpHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
     728             :                 VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
     729             :                                                 GetFSPrefix().c_str()));
     730             :             CURL *hCurlHandle = curl_easy_init();
     731             :             struct curl_slist *subrequest_headers =
     732             :                 static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     733             :                     hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
     734             :                     aosHTTPOptions.List()));
     735             :             subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
     736             :                 "DELETE", subrequest_headers, nullptr, 0);
     737             :             for (struct curl_slist *iter = subrequest_headers; iter;
     738             :                  iter = iter->next)
     739             :             {
     740             :                 if (STARTS_WITH_CI(iter->data, "Authorization: "))
     741             :                 {
     742             :                     osAuthorization = iter->data;
     743             :                 }
     744             :                 else if (STARTS_WITH_CI(iter->data, "Date: "))
     745             :                 {
     746             :                     osDate = iter->data;
     747             :                 }
     748             :             }
     749             :             curl_slist_free_all(subrequest_headers);
     750             :             curl_easy_cleanup(hCurlHandle);
     751             :         }
     752             : #endif
     753             : 
     754           4 :         osPOSTContent += "--===============7330845974216740156==\r\n";
     755           4 :         osPOSTContent += "Content-Type: application/http\r\n";
     756           4 :         osPOSTContent += CPLSPrintf("Content-ID: <%d>\r\n", i + 1);
     757           4 :         osPOSTContent += "\r\n\r\n";
     758           4 :         osPOSTContent += "DELETE /";
     759           4 :         osPOSTContent += osResource;
     760           4 :         osPOSTContent += " HTTP/1.1\r\n";
     761             : #ifdef ADD_AUTH_TO_NESTED_REQUEST
     762             :         if (!osAuthorization.empty())
     763             :         {
     764             :             osPOSTContent += osAuthorization;
     765             :             osPOSTContent += "\r\n";
     766             :         }
     767             :         if (!osDate.empty())
     768             :         {
     769             :             osPOSTContent += osDate;
     770             :             osPOSTContent += "\r\n";
     771             :         }
     772             : #endif
     773           4 :         osPOSTContent += "\r\n\r\n";
     774             : 
     775           4 :         if (((i + 1) % nBatchSize) == 0 || papszFiles[i + 1] == nullptr)
     776             :         {
     777           3 :             osPOSTContent += "--===============7330845974216740156==--\r\n";
     778             : 
     779             : #ifdef DEBUG_VERBOSE
     780             :             CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
     781             : #endif
     782             : 
     783             :             // Run request
     784             :             bool bRetry;
     785           6 :             std::string osResponse;
     786           3 :             do
     787             :             {
     788           3 :                 bRetry = false;
     789           3 :                 CURL *hCurlHandle = curl_easy_init();
     790             : 
     791           3 :                 unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
     792             :                                            "POST");
     793           3 :                 unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
     794             :                                            osPOSTContent.c_str());
     795             : 
     796             :                 struct curl_slist *headers =
     797           6 :                     static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     798           3 :                         hCurlHandle, poHandleHelper->GetURL().c_str(),
     799             :                         aosHTTPOptions.List()));
     800           3 :                 headers = curl_slist_append(
     801             :                     headers,
     802             :                     "Content-Type: multipart/mixed; "
     803             :                     "boundary=\"===============7330845974216740156==\"");
     804           6 :                 headers = poHandleHelper->GetCurlHeaders("POST", headers,
     805           3 :                                                          osPOSTContent.c_str(),
     806             :                                                          osPOSTContent.size());
     807             : 
     808           6 :                 CurlRequestHelper requestHelper;
     809           3 :                 const long response_code = requestHelper.perform(
     810           3 :                     hCurlHandle, headers, this, poHandleHelper.get());
     811             : 
     812           3 :                 NetworkStatisticsLogger::LogPOST(
     813             :                     osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
     814             : 
     815           3 :                 if (response_code != 200 ||
     816           3 :                     requestHelper.sWriteFuncData.pBuffer == nullptr)
     817             :                 {
     818             :                     // Look if we should attempt a retry
     819           0 :                     if (oRetryContext.CanRetry(
     820             :                             static_cast<int>(response_code),
     821           0 :                             requestHelper.sWriteFuncHeaderData.pBuffer,
     822             :                             requestHelper.szCurlErrBuf))
     823             :                     {
     824           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     825             :                                  "HTTP error code: %d - %s. "
     826             :                                  "Retrying again in %.1f secs",
     827             :                                  static_cast<int>(response_code),
     828           0 :                                  poHandleHelper->GetURL().c_str(),
     829             :                                  oRetryContext.GetCurrentDelay());
     830           0 :                         CPLSleep(oRetryContext.GetCurrentDelay());
     831           0 :                         bRetry = true;
     832             :                     }
     833             :                     else
     834             :                     {
     835           0 :                         CPLDebug(GetDebugKey(), "%s",
     836           0 :                                  requestHelper.sWriteFuncData.pBuffer
     837             :                                      ? requestHelper.sWriteFuncData.pBuffer
     838             :                                      : "(null)");
     839           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     840             :                                  "DeleteObjects failed");
     841             :                     }
     842             :                 }
     843             :                 else
     844             :                 {
     845             : #ifdef DEBUG_VERBOSE
     846             :                     CPLDebug(GetDebugKey(), "%s",
     847             :                              requestHelper.sWriteFuncData.pBuffer);
     848             : #endif
     849           3 :                     osResponse = requestHelper.sWriteFuncData.pBuffer;
     850             :                 }
     851             : 
     852           3 :                 curl_easy_cleanup(hCurlHandle);
     853             :             } while (bRetry);
     854             : 
     855             :             // Mark deleted files
     856         107 :             for (int j = i + 1 - nBatchSize; j <= i; j++)
     857             :             {
     858         104 :                 auto nPos = osResponse.find(
     859             :                     CPLSPrintf("Content-ID: <response-%d>", j + 1));
     860         104 :                 if (nPos != std::string::npos)
     861             :                 {
     862           4 :                     nPos = osResponse.find("HTTP/1.1 ", nPos);
     863           4 :                     if (nPos != std::string::npos)
     864             :                     {
     865             :                         const char *pszHTTPCode =
     866           4 :                             osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
     867           4 :                         panRet[j] = (atoi(pszHTTPCode) == 204) ? 1 : 0;
     868             :                     }
     869             :                 }
     870             :             }
     871             : 
     872           3 :             osPOSTContent.clear();
     873             :         }
     874             :     }
     875           2 :     return panRet;
     876             : }
     877             : 
     878             : /************************************************************************/
     879             : /*                           RmdirRecursive()                           */
     880             : /************************************************************************/
     881             : 
     882           1 : int VSIGSFSHandler::RmdirRecursive(const char *pszDirname)
     883             : {
     884             :     // For debug / testing only
     885             :     const int nBatchSize = std::min(
     886           1 :         100, atoi(CPLGetConfigOption("CPL_VSIGS_UNLINK_BATCH_SIZE", "100")));
     887             : 
     888           1 :     return RmdirRecursiveInternal(pszDirname, nBatchSize);
     889             : }
     890             : 
     891             : /************************************************************************/
     892             : /*                      GetStreamingFilename()                          */
     893             : /************************************************************************/
     894             : 
     895             : std::string
     896           0 : VSIGSFSHandler::GetStreamingFilename(const std::string &osFilename) const
     897             : {
     898           0 :     if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
     899           0 :         return "/vsigs_streaming/" + osFilename.substr(GetFSPrefix().size());
     900           0 :     return osFilename;
     901             : }
     902             : 
     903             : /************************************************************************/
     904             : /*                             VSIGSHandle()                            */
     905             : /************************************************************************/
     906             : 
     907          30 : VSIGSHandle::VSIGSHandle(VSIGSFSHandler *poFSIn, const char *pszFilename,
     908          30 :                          VSIGSHandleHelper *poHandleHelper)
     909          30 :     : IVSIS3LikeHandle(poFSIn, pszFilename, poHandleHelper->GetURL().c_str()),
     910          30 :       m_poHandleHelper(poHandleHelper)
     911             : {
     912          30 : }
     913             : 
     914             : /************************************************************************/
     915             : /*                            ~VSIGSHandle()                            */
     916             : /************************************************************************/
     917             : 
     918          60 : VSIGSHandle::~VSIGSHandle()
     919             : {
     920          30 :     delete m_poHandleHelper;
     921          60 : }
     922             : 
     923             : /************************************************************************/
     924             : /*                          GetCurlHeaders()                            */
     925             : /************************************************************************/
     926             : 
     927          24 : struct curl_slist *VSIGSHandle::GetCurlHeaders(const std::string &osVerb,
     928             :                                                struct curl_slist *psHeaders)
     929             : {
     930          24 :     return m_poHandleHelper->GetCurlHeaders(osVerb, psHeaders);
     931             : }
     932             : 
     933             : } /* end of namespace cpl */
     934             : 
     935             : #endif  // DOXYGEN_SKIP
     936             : //! @endcond
     937             : 
     938             : /************************************************************************/
     939             : /*                      VSIInstallGSFileHandler()                       */
     940             : /************************************************************************/
     941             : 
     942             : /*!
     943             :  \brief Install /vsigs/ Google Cloud Storage file system handler
     944             :  (requires libcurl)
     945             : 
     946             :  \verbatim embed:rst
     947             :  See :ref:`/vsigs/ documentation <vsigs>`
     948             :  \endverbatim
     949             : 
     950             :  @since GDAL 2.2
     951             :  */
     952             : 
     953        1691 : void VSIInstallGSFileHandler(void)
     954             : {
     955        1691 :     VSIFileManager::InstallHandler("/vsigs/",
     956        1691 :                                    new cpl::VSIGSFSHandler("/vsigs/"));
     957        1691 : }
     958             : 
     959             : #endif /* HAVE_CURL */

Generated by: LCOV version 1.14