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

Generated by: LCOV version 1.14