LCOV - code coverage report
Current view: top level - port - cpl_vsil_gs.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 263 311 84.6 %
Date: 2024-11-21 22:18:42 Functions: 22 25 88.0 %

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

Generated by: LCOV version 1.14