LCOV - code coverage report
Current view: top level - port - cpl_vsil_gs.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 282 328 86.0 %
Date: 2026-03-25 02:32:38 Functions: 23 27 85.2 %

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

Generated by: LCOV version 1.14