LCOV - code coverage report
Current view: top level - port - cpl_vsil_gs.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 212 262 80.9 %
Date: 2024-04-28 18:08:58 Functions: 20 25 80.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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_port.h"
      30             : #include "cpl_http.h"
      31             : #include "cpl_minixml.h"
      32             : #include "cpl_vsil_curl_priv.h"
      33             : #include "cpl_vsil_curl_class.h"
      34             : 
      35             : #include <errno.h>
      36             : 
      37             : #include <algorithm>
      38             : #include <set>
      39             : #include <map>
      40             : #include <memory>
      41             : 
      42             : #include "cpl_google_cloud.h"
      43             : 
      44             : #ifndef HAVE_CURL
      45             : 
      46             : void VSIInstallGSFileHandler(void)
      47             : {
      48             :     // Not supported.
      49             : }
      50             : 
      51             : #else
      52             : 
      53             : //! @cond Doxygen_Suppress
      54             : #ifndef DOXYGEN_SKIP
      55             : 
      56             : #define ENABLE_DEBUG 0
      57             : 
      58             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      59             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      60             : 
      61             : namespace cpl
      62             : {
      63             : 
      64             : /************************************************************************/
      65             : /*                         VSIGSFSHandler                               */
      66             : /************************************************************************/
      67             : 
      68             : class VSIGSFSHandler final : public IVSIS3LikeFSHandler
      69             : {
      70             :     CPL_DISALLOW_COPY_ASSIGN(VSIGSFSHandler)
      71             :     const std::string m_osPrefix;
      72             : 
      73             :   protected:
      74             :     VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
      75             : 
      76          44 :     const char *GetDebugKey() const override
      77             :     {
      78          44 :         return "GS";
      79             :     }
      80             : 
      81         459 :     std::string GetFSPrefix() const override
      82             :     {
      83         459 :         return m_osPrefix;
      84             :     }
      85             : 
      86             :     std::string GetURLFromFilename(const std::string &osFilename) override;
      87             : 
      88             :     IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
      89             :                                                bool bAllowNoObject) override;
      90             : 
      91             :     void ClearCache() override;
      92             : 
      93           0 :     bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
      94             :     {
      95           0 :         return STARTS_WITH(pszHeaderName, "x-goog-");
      96             :     }
      97             : 
      98             :     VSIVirtualHandleUniquePtr
      99             :     CreateWriteHandle(const char *pszFilename,
     100             :                       CSLConstList papszOptions) override;
     101             : 
     102             :   public:
     103        1225 :     explicit VSIGSFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
     104             :     {
     105        1225 :     }
     106             : 
     107             :     ~VSIGSFSHandler() override;
     108             : 
     109             :     const char *GetOptions() override;
     110             : 
     111             :     char *GetSignedURL(const char *pszFilename,
     112             :                        CSLConstList papszOptions) override;
     113             : 
     114             :     // Multipart upload
     115           0 :     bool SupportsParallelMultipartUpload() const override
     116             :     {
     117           0 :         return true;
     118             :     }
     119             : 
     120             :     char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
     121             :                            CSLConstList papszOptions) override;
     122             : 
     123             :     bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
     124             :                          const char *pszDomain,
     125             :                          CSLConstList papszOptions) override;
     126             : 
     127             :     int *UnlinkBatch(CSLConstList papszFiles) override;
     128             :     int RmdirRecursive(const char *pszDirname) override;
     129             : 
     130             :     std::string
     131             :     GetStreamingFilename(const std::string &osFilename) const override;
     132             : 
     133           0 :     VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
     134             :     {
     135           0 :         return new VSIGSFSHandler(pszPrefix);
     136             :     }
     137             : };
     138             : 
     139             : /************************************************************************/
     140             : /*                            VSIGSHandle                               */
     141             : /************************************************************************/
     142             : 
     143             : class VSIGSHandle final : public IVSIS3LikeHandle
     144             : {
     145             :     CPL_DISALLOW_COPY_ASSIGN(VSIGSHandle)
     146             : 
     147             :     VSIGSHandleHelper *m_poHandleHelper = nullptr;
     148             : 
     149             :   protected:
     150             :     struct curl_slist *
     151             :     GetCurlHeaders(const std::string &osVerb,
     152             :                    const struct curl_slist *psExistingHeaders) override;
     153             : 
     154             :   public:
     155             :     VSIGSHandle(VSIGSFSHandler *poFS, const char *pszFilename,
     156             :                 VSIGSHandleHelper *poHandleHelper);
     157             :     ~VSIGSHandle() override;
     158             : };
     159             : 
     160             : /************************************************************************/
     161             : /*                          ~VSIGSFSHandler()                           */
     162             : /************************************************************************/
     163             : 
     164        1698 : VSIGSFSHandler::~VSIGSFSHandler()
     165             : {
     166         849 :     VSICurlFilesystemHandlerBase::ClearCache();
     167         849 :     VSIGSHandleHelper::CleanMutex();
     168        1698 : }
     169             : 
     170             : /************************************************************************/
     171             : /*                            ClearCache()                              */
     172             : /************************************************************************/
     173             : 
     174         246 : void VSIGSFSHandler::ClearCache()
     175             : {
     176         246 :     VSICurlFilesystemHandlerBase::ClearCache();
     177             : 
     178         246 :     VSIGSHandleHelper::ClearCache();
     179         246 : }
     180             : 
     181             : /************************************************************************/
     182             : /*                          CreateFileHandle()                          */
     183             : /************************************************************************/
     184             : 
     185          31 : VSICurlHandle *VSIGSFSHandler::CreateFileHandle(const char *pszFilename)
     186             : {
     187          62 :     VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
     188          93 :         pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
     189          31 :     if (poHandleHelper == nullptr)
     190           4 :         return nullptr;
     191          27 :     return new VSIGSHandle(this, pszFilename, poHandleHelper);
     192             : }
     193             : 
     194             : /************************************************************************/
     195             : /*                           GetOptions()                               */
     196             : /************************************************************************/
     197             : 
     198           2 : const char *VSIGSFSHandler::GetOptions()
     199             : {
     200             :     static std::string osOptions(
     201           2 :         std::string("<Options>") +
     202             :         "  <Option name='GS_SECRET_ACCESS_KEY' type='string' "
     203             :         "description='Secret access key. To use with GS_ACCESS_KEY_ID'/>"
     204             :         "  <Option name='GS_ACCESS_KEY_ID' type='string' "
     205             :         "description='Access key id'/>"
     206             :         "  <Option name='GS_NO_SIGN_REQUEST' type='boolean' "
     207             :         "description='Whether to disable signing of requests' default='NO'/>"
     208             :         "  <Option name='GS_OAUTH2_REFRESH_TOKEN' type='string' "
     209             :         "description='OAuth2 refresh token. For OAuth2 client authentication. "
     210             :         "To use with GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET'/>"
     211             :         "  <Option name='GS_OAUTH2_CLIENT_ID' type='string' "
     212             :         "description='OAuth2 client id for OAuth2 client authentication'/>"
     213             :         "  <Option name='GS_OAUTH2_CLIENT_SECRET' type='string' "
     214             :         "description='OAuth2 client secret for OAuth2 client authentication'/>"
     215             :         "  <Option name='GS_OAUTH2_PRIVATE_KEY' type='string' "
     216             :         "description='Private key for OAuth2 service account authentication. "
     217             :         "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
     218             :         "  <Option name='GS_OAUTH2_PRIVATE_KEY_FILE' type='string' "
     219             :         "description='Filename that contains private key for OAuth2 service "
     220             :         "account authentication. "
     221             :         "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
     222             :         "  <Option name='GS_OAUTH2_CLIENT_EMAIL' type='string' "
     223             :         "description='Client email to use with OAuth2 service account "
     224             :         "authentication'/>"
     225             :         "  <Option name='GS_OAUTH2_SCOPE' type='string' "
     226             :         "description='OAuth2 authorization scope' "
     227             :         "default='https://www.googleapis.com/auth/devstorage.read_write'/>"
     228             :         "  <Option name='CPL_MACHINE_IS_GCE' type='boolean' "
     229             :         "description='Whether the current machine is a Google Compute Engine "
     230             :         "instance' default='NO'/>"
     231             :         "  <Option name='CPL_GCE_CHECK_LOCAL_FILES' type='boolean' "
     232             :         "description='Whether to check system logs to determine "
     233             :         "if current machine is a GCE instance' default='YES'/>"
     234             :         "description='Filename that contains AWS configuration' "
     235             :         "default='~/.aws/config'/>"
     236             :         "  <Option name='CPL_GS_CREDENTIALS_FILE' type='string' "
     237             :         "description='Filename that contains Google Storage credentials' "
     238           3 :         "default='~/.boto'/>" +
     239           3 :         VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
     240           2 :     return osOptions.c_str();
     241             : }
     242             : 
     243             : /************************************************************************/
     244             : /*                           GetSignedURL()                             */
     245             : /************************************************************************/
     246             : 
     247           6 : char *VSIGSFSHandler::GetSignedURL(const char *pszFilename,
     248             :                                    CSLConstList papszOptions)
     249             : {
     250           6 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     251           0 :         return nullptr;
     252             : 
     253             :     VSIGSHandleHelper *poHandleHelper =
     254           6 :         VSIGSHandleHelper::BuildFromURI(pszFilename + GetFSPrefix().size(),
     255          12 :                                         GetFSPrefix().c_str(), papszOptions);
     256           6 :     if (poHandleHelper == nullptr)
     257             :     {
     258           1 :         return nullptr;
     259             :     }
     260             : 
     261           5 :     std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
     262             : 
     263           5 :     delete poHandleHelper;
     264           5 :     return osRet.empty() ? nullptr : CPLStrdup(osRet.c_str());
     265             : }
     266             : 
     267             : /************************************************************************/
     268             : /*                          GetURLFromFilename()                         */
     269             : /************************************************************************/
     270             : 
     271           7 : std::string VSIGSFSHandler::GetURLFromFilename(const std::string &osFilename)
     272             : {
     273             :     std::string osFilenameWithoutPrefix =
     274          14 :         osFilename.substr(GetFSPrefix().size());
     275           7 :     VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
     276          14 :         osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str());
     277           7 :     if (poHandleHelper == nullptr)
     278           0 :         return std::string();
     279          14 :     std::string osURL(poHandleHelper->GetURL());
     280           7 :     delete poHandleHelper;
     281           7 :     return osURL;
     282             : }
     283             : 
     284             : /************************************************************************/
     285             : /*                          CreateHandleHelper()                        */
     286             : /************************************************************************/
     287             : 
     288           9 : IVSIS3LikeHandleHelper *VSIGSFSHandler::CreateHandleHelper(const char *pszURI,
     289             :                                                            bool)
     290             : {
     291           9 :     return VSIGSHandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str());
     292             : }
     293             : 
     294             : /************************************************************************/
     295             : /*                          CreateWriteHandle()                         */
     296             : /************************************************************************/
     297             : 
     298             : VSIVirtualHandleUniquePtr
     299           1 : VSIGSFSHandler::CreateWriteHandle(const char *pszFilename,
     300             :                                   CSLConstList papszOptions)
     301             : {
     302             :     auto poHandleHelper =
     303           1 :         CreateHandleHelper(pszFilename + GetFSPrefix().size(), false);
     304           1 :     if (poHandleHelper == nullptr)
     305           0 :         return nullptr;
     306             :     auto poHandle = std::make_unique<VSIS3WriteHandle>(
     307           2 :         this, pszFilename, poHandleHelper, false, papszOptions);
     308           1 :     if (!poHandle->IsOK())
     309             :     {
     310           0 :         return nullptr;
     311             :     }
     312           1 :     return VSIVirtualHandleUniquePtr(poHandle.release());
     313             : }
     314             : 
     315             : /************************************************************************/
     316             : /*                          GetFileMetadata()                           */
     317             : /************************************************************************/
     318             : 
     319           4 : char **VSIGSFSHandler::GetFileMetadata(const char *pszFilename,
     320             :                                        const char *pszDomain,
     321             :                                        CSLConstList papszOptions)
     322             : {
     323           4 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     324           0 :         return nullptr;
     325             : 
     326           4 :     if (pszDomain == nullptr || !EQUAL(pszDomain, "ACL"))
     327             :     {
     328           2 :         return VSICurlFilesystemHandlerBase::GetFileMetadata(
     329           2 :             pszFilename, pszDomain, papszOptions);
     330             :     }
     331             : 
     332             :     auto poHandleHelper =
     333           4 :         std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     334          10 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
     335           2 :     if (!poHandleHelper)
     336           0 :         return nullptr;
     337             : 
     338           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     339           4 :     NetworkStatisticsAction oContextAction("GetFileMetadata");
     340             : 
     341           4 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     342             : 
     343             :     bool bRetry;
     344             :     // coverity[tainted_data]
     345           2 :     double dfRetryDelay = CPLAtof(
     346             :         VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
     347             :                                  CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
     348             :     const int nMaxRetry =
     349           2 :         atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
     350             :                                       CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
     351           2 :     int nRetryCount = 0;
     352             : 
     353           4 :     CPLStringList aosResult;
     354           2 :     do
     355             :     {
     356           2 :         bRetry = false;
     357           2 :         CURL *hCurlHandle = curl_easy_init();
     358           2 :         poHandleHelper->AddQueryParameter("acl", "");
     359             : 
     360             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     361           2 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
     362             :                               aosHTTPOptions.List()));
     363           2 :         headers = VSICurlMergeHeaders(
     364           2 :             headers, poHandleHelper->GetCurlHeaders("GET", headers));
     365             : 
     366           4 :         CurlRequestHelper requestHelper;
     367           2 :         const long response_code = requestHelper.perform(
     368             :             hCurlHandle, headers, this, poHandleHelper.get());
     369             : 
     370           2 :         NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
     371             : 
     372           2 :         if (response_code != 200 ||
     373           1 :             requestHelper.sWriteFuncData.pBuffer == nullptr)
     374             :         {
     375             :             // Look if we should attempt a retry
     376           2 :             const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
     377             :                 static_cast<int>(response_code), dfRetryDelay,
     378           1 :                 requestHelper.sWriteFuncHeaderData.pBuffer,
     379             :                 requestHelper.szCurlErrBuf);
     380           1 :             if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
     381             :             {
     382           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     383             :                          "HTTP error code: %d - %s. "
     384             :                          "Retrying again in %.1f secs",
     385             :                          static_cast<int>(response_code),
     386           0 :                          poHandleHelper->GetURL().c_str(), dfRetryDelay);
     387           0 :                 CPLSleep(dfRetryDelay);
     388           0 :                 dfRetryDelay = dfNewRetryDelay;
     389           0 :                 nRetryCount++;
     390           0 :                 bRetry = true;
     391             :             }
     392             :             else
     393             :             {
     394           1 :                 CPLDebug(GetDebugKey(), "%s",
     395           1 :                          requestHelper.sWriteFuncData.pBuffer
     396             :                              ? requestHelper.sWriteFuncData.pBuffer
     397             :                              : "(null)");
     398           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "GetFileMetadata failed");
     399           1 :             }
     400             :         }
     401             :         else
     402             :         {
     403           1 :             aosResult.SetNameValue("XML", requestHelper.sWriteFuncData.pBuffer);
     404             :         }
     405             : 
     406           2 :         curl_easy_cleanup(hCurlHandle);
     407             :     } while (bRetry);
     408           2 :     return CSLDuplicate(aosResult.List());
     409             : }
     410             : 
     411             : /************************************************************************/
     412             : /*                          SetFileMetadata()                           */
     413             : /************************************************************************/
     414             : 
     415           5 : bool VSIGSFSHandler::SetFileMetadata(const char *pszFilename,
     416             :                                      CSLConstList papszMetadata,
     417             :                                      const char *pszDomain,
     418             :                                      CSLConstList /* papszOptions */)
     419             : {
     420           5 :     if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
     421           0 :         return false;
     422             : 
     423           5 :     if (pszDomain == nullptr ||
     424           5 :         !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "ACL")))
     425             :     {
     426           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     427             :                  "Only HEADERS and ACL domain are supported");
     428           1 :         return false;
     429             :     }
     430             : 
     431           4 :     if (EQUAL(pszDomain, "HEADERS"))
     432             :     {
     433           1 :         return CopyObject(pszFilename, pszFilename, papszMetadata) == 0;
     434             :     }
     435             : 
     436           3 :     const char *pszXML = CSLFetchNameValue(papszMetadata, "XML");
     437           3 :     if (pszXML == nullptr)
     438             :     {
     439           1 :         CPLError(CE_Failure, CPLE_AppDefined, "XML key is missing in metadata");
     440           1 :         return false;
     441             :     }
     442             : 
     443             :     auto poHandleHelper =
     444           4 :         std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     445          10 :             pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
     446           2 :     if (!poHandleHelper)
     447           0 :         return false;
     448             : 
     449           4 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     450           4 :     NetworkStatisticsAction oContextAction("SetFileMetadata");
     451             : 
     452             :     bool bRetry;
     453             :     // coverity[tainted_data]
     454           2 :     double dfRetryDelay = CPLAtof(
     455             :         VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
     456             :                                  CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
     457             :     const int nMaxRetry =
     458           2 :         atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
     459             :                                       CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
     460           2 :     int nRetryCount = 0;
     461             : 
     462           2 :     bool bRet = false;
     463             : 
     464           2 :     const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
     465             : 
     466           2 :     do
     467             :     {
     468           2 :         bRetry = false;
     469           2 :         CURL *hCurlHandle = curl_easy_init();
     470           2 :         poHandleHelper->AddQueryParameter("acl", "");
     471           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
     472           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
     473             : 
     474             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     475           2 :             CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
     476             :                               aosHTTPOptions.List()));
     477           2 :         headers = curl_slist_append(headers, "Content-Type: application/xml");
     478           2 :         headers = VSICurlMergeHeaders(
     479           2 :             headers, poHandleHelper->GetCurlHeaders("PUT", headers, pszXML,
     480           2 :                                                     strlen(pszXML)));
     481           2 :         NetworkStatisticsLogger::LogPUT(strlen(pszXML));
     482             : 
     483           4 :         CurlRequestHelper requestHelper;
     484           2 :         const long response_code = requestHelper.perform(
     485             :             hCurlHandle, headers, this, poHandleHelper.get());
     486             : 
     487           2 :         if (response_code != 200)
     488             :         {
     489             :             // Look if we should attempt a retry
     490           2 :             const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
     491             :                 static_cast<int>(response_code), dfRetryDelay,
     492           1 :                 requestHelper.sWriteFuncHeaderData.pBuffer,
     493             :                 requestHelper.szCurlErrBuf);
     494           1 :             if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
     495             :             {
     496           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     497             :                          "HTTP error code: %d - %s. "
     498             :                          "Retrying again in %.1f secs",
     499             :                          static_cast<int>(response_code),
     500           0 :                          poHandleHelper->GetURL().c_str(), dfRetryDelay);
     501           0 :                 CPLSleep(dfRetryDelay);
     502           0 :                 dfRetryDelay = dfNewRetryDelay;
     503           0 :                 nRetryCount++;
     504           0 :                 bRetry = true;
     505             :             }
     506             :             else
     507             :             {
     508           1 :                 CPLDebug(GetDebugKey(), "%s",
     509           1 :                          requestHelper.sWriteFuncData.pBuffer
     510             :                              ? requestHelper.sWriteFuncData.pBuffer
     511             :                              : "(null)");
     512           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "SetFileMetadata failed");
     513             :             }
     514             :         }
     515             :         else
     516             :         {
     517           1 :             bRet = true;
     518             :         }
     519             : 
     520           2 :         curl_easy_cleanup(hCurlHandle);
     521             :     } while (bRetry);
     522           2 :     return bRet;
     523             : }
     524             : 
     525             : /************************************************************************/
     526             : /*                           UnlinkBatch()                              */
     527             : /************************************************************************/
     528             : 
     529           1 : int *VSIGSFSHandler::UnlinkBatch(CSLConstList papszFiles)
     530             : {
     531             :     // Implemented using
     532             :     // https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
     533             : 
     534             :     auto poHandleHelper =
     535             :         std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
     536           2 :             "batch/storage/v1", GetFSPrefix().c_str()));
     537             : 
     538             :     // The JSON API cannot be used with HMAC keys
     539           1 :     if (poHandleHelper && poHandleHelper->UsesHMACKey())
     540             :     {
     541           0 :         CPLDebug(GetDebugKey(), "UnlinkBatch() has an efficient implementation "
     542             :                                 "only for OAuth2 authentication");
     543           0 :         return VSICurlFilesystemHandlerBase::UnlinkBatch(papszFiles);
     544             :     }
     545             : 
     546             :     int *panRet =
     547           1 :         static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
     548             : 
     549           1 :     const char *pszFirstFilename =
     550           1 :         papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
     551           1 :     if (!poHandleHelper || pszFirstFilename == nullptr)
     552           0 :         return panRet;
     553             : 
     554           2 :     NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
     555           2 :     NetworkStatisticsAction oContextAction("UnlinkBatch");
     556             : 
     557             :     // coverity[tainted_data]
     558           1 :     double dfRetryDelay = CPLAtof(
     559             :         VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_RETRY_DELAY",
     560             :                                  CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
     561             :     const int nMaxRetry =
     562           1 :         atoi(VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_MAX_RETRY",
     563             :                                       CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
     564             : 
     565             :     // For debug / testing only
     566             :     const int nBatchSize =
     567           1 :         std::max(1, std::min(100, atoi(CPLGetConfigOption(
     568           1 :                                       "CPL_VSIGS_UNLINK_BATCH_SIZE", "100"))));
     569           2 :     std::string osPOSTContent;
     570             : 
     571             :     const CPLStringList aosHTTPOptions(
     572           2 :         CPLHTTPGetOptionsFromEnv(pszFirstFilename));
     573             : 
     574           4 :     for (int i = 0; papszFiles && papszFiles[i]; i++)
     575             :     {
     576           3 :         CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
     577             :         const char *pszFilenameWithoutPrefix =
     578           3 :             papszFiles[i] + GetFSPrefix().size();
     579           3 :         const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
     580           3 :         if (!pszSlash)
     581           0 :             return panRet;
     582           6 :         std::string osBucket;
     583             :         osBucket.assign(pszFilenameWithoutPrefix,
     584           3 :                         pszSlash - pszFilenameWithoutPrefix);
     585             : 
     586           6 :         std::string osResource = "storage/v1/b/";
     587           3 :         osResource += osBucket;
     588           3 :         osResource += "/o/";
     589           3 :         osResource += CPLAWSURLEncode(pszSlash + 1, true);
     590             : 
     591             : #ifdef ADD_AUTH_TO_NESTED_REQUEST
     592             :         std::string osAuthorization;
     593             :         std::string osDate;
     594             :         {
     595             :             auto poTmpHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
     596             :                 VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
     597             :                                                 GetFSPrefix().c_str()));
     598             :             CURL *hCurlHandle = curl_easy_init();
     599             :             struct curl_slist *subrequest_headers =
     600             :                 static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     601             :                     hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
     602             :                     aosHTTPOptions.List()));
     603             :             subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
     604             :                 "DELETE", subrequest_headers, nullptr, 0);
     605             :             for (struct curl_slist *iter = subrequest_headers; iter;
     606             :                  iter = iter->next)
     607             :             {
     608             :                 if (STARTS_WITH_CI(iter->data, "Authorization: "))
     609             :                 {
     610             :                     osAuthorization = iter->data;
     611             :                 }
     612             :                 else if (STARTS_WITH_CI(iter->data, "Date: "))
     613             :                 {
     614             :                     osDate = iter->data;
     615             :                 }
     616             :             }
     617             :             curl_slist_free_all(subrequest_headers);
     618             :             curl_easy_cleanup(hCurlHandle);
     619             :         }
     620             : #endif
     621             : 
     622           3 :         osPOSTContent += "--===============7330845974216740156==\r\n";
     623           3 :         osPOSTContent += "Content-Type: application/http\r\n";
     624           3 :         osPOSTContent += CPLSPrintf("Content-ID: <%d>\r\n", i + 1);
     625           3 :         osPOSTContent += "\r\n\r\n";
     626           3 :         osPOSTContent += "DELETE /";
     627           3 :         osPOSTContent += osResource;
     628           3 :         osPOSTContent += " HTTP/1.1\r\n";
     629             : #ifdef ADD_AUTH_TO_NESTED_REQUEST
     630             :         if (!osAuthorization.empty())
     631             :         {
     632             :             osPOSTContent += osAuthorization;
     633             :             osPOSTContent += "\r\n";
     634             :         }
     635             :         if (!osDate.empty())
     636             :         {
     637             :             osPOSTContent += osDate;
     638             :             osPOSTContent += "\r\n";
     639             :         }
     640             : #endif
     641           3 :         osPOSTContent += "\r\n\r\n";
     642             : 
     643           3 :         if (((i + 1) % nBatchSize) == 0 || papszFiles[i + 1] == nullptr)
     644             :         {
     645           2 :             osPOSTContent += "--===============7330845974216740156==--\r\n";
     646             : 
     647             : #ifdef DEBUG_VERBOSE
     648             :             CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
     649             : #endif
     650             : 
     651             :             // Run request
     652           2 :             int nRetryCount = 0;
     653             :             bool bRetry;
     654           4 :             std::string osResponse;
     655           2 :             do
     656             :             {
     657           2 :                 bRetry = false;
     658           2 :                 CURL *hCurlHandle = curl_easy_init();
     659             : 
     660           2 :                 unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
     661             :                                            "POST");
     662           2 :                 unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
     663             :                                            osPOSTContent.c_str());
     664             : 
     665             :                 struct curl_slist *headers =
     666           4 :                     static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     667           2 :                         hCurlHandle, poHandleHelper->GetURL().c_str(),
     668             :                         aosHTTPOptions.List()));
     669           2 :                 headers = curl_slist_append(
     670             :                     headers,
     671             :                     "Content-Type: multipart/mixed; "
     672             :                     "boundary=\"===============7330845974216740156==\"");
     673           4 :                 headers = VSICurlMergeHeaders(
     674             :                     headers, poHandleHelper->GetCurlHeaders(
     675           2 :                                  "POST", headers, osPOSTContent.c_str(),
     676             :                                  osPOSTContent.size()));
     677             : 
     678           4 :                 CurlRequestHelper requestHelper;
     679           2 :                 const long response_code = requestHelper.perform(
     680           2 :                     hCurlHandle, headers, this, poHandleHelper.get());
     681             : 
     682           2 :                 NetworkStatisticsLogger::LogPOST(
     683             :                     osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
     684             : 
     685           2 :                 if (response_code != 200 ||
     686           2 :                     requestHelper.sWriteFuncData.pBuffer == nullptr)
     687             :                 {
     688             :                     // Look if we should attempt a retry
     689           0 :                     const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
     690             :                         static_cast<int>(response_code), dfRetryDelay,
     691           0 :                         requestHelper.sWriteFuncHeaderData.pBuffer,
     692             :                         requestHelper.szCurlErrBuf);
     693           0 :                     if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
     694             :                     {
     695           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     696             :                                  "HTTP error code: %d - %s. "
     697             :                                  "Retrying again in %.1f secs",
     698             :                                  static_cast<int>(response_code),
     699           0 :                                  poHandleHelper->GetURL().c_str(),
     700             :                                  dfRetryDelay);
     701           0 :                         CPLSleep(dfRetryDelay);
     702           0 :                         dfRetryDelay = dfNewRetryDelay;
     703           0 :                         nRetryCount++;
     704           0 :                         bRetry = true;
     705             :                     }
     706             :                     else
     707             :                     {
     708           0 :                         CPLDebug(GetDebugKey(), "%s",
     709           0 :                                  requestHelper.sWriteFuncData.pBuffer
     710             :                                      ? requestHelper.sWriteFuncData.pBuffer
     711             :                                      : "(null)");
     712           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     713             :                                  "DeleteObjects failed");
     714           0 :                     }
     715             :                 }
     716             :                 else
     717             :                 {
     718             : #ifdef DEBUG_VERBOSE
     719             :                     CPLDebug(GetDebugKey(), "%s",
     720             :                              requestHelper.sWriteFuncData.pBuffer);
     721             : #endif
     722           2 :                     osResponse = requestHelper.sWriteFuncData.pBuffer;
     723             :                 }
     724             : 
     725           2 :                 curl_easy_cleanup(hCurlHandle);
     726             :             } while (bRetry);
     727             : 
     728             :             // Mark deleted files
     729           6 :             for (int j = i + 1 - nBatchSize; j <= i; j++)
     730             :             {
     731           4 :                 auto nPos = osResponse.find(
     732             :                     CPLSPrintf("Content-ID: <response-%d>", j + 1));
     733           4 :                 if (nPos != std::string::npos)
     734             :                 {
     735           3 :                     nPos = osResponse.find("HTTP/1.1 ", nPos);
     736           3 :                     if (nPos != std::string::npos)
     737             :                     {
     738             :                         const char *pszHTTPCode =
     739           3 :                             osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
     740           3 :                         panRet[j] = (atoi(pszHTTPCode) == 204) ? 1 : 0;
     741             :                     }
     742             :                 }
     743             :             }
     744             : 
     745           2 :             osPOSTContent.clear();
     746             :         }
     747             :     }
     748           1 :     return panRet;
     749             : }
     750             : 
     751             : /************************************************************************/
     752             : /*                           RmdirRecursive()                           */
     753             : /************************************************************************/
     754             : 
     755           0 : int VSIGSFSHandler::RmdirRecursive(const char *pszDirname)
     756             : {
     757             :     // For debug / testing only
     758             :     const int nBatchSize = std::min(
     759           0 :         100, atoi(CPLGetConfigOption("CPL_VSIGS_UNLINK_BATCH_SIZE", "100")));
     760             : 
     761           0 :     return RmdirRecursiveInternal(pszDirname, nBatchSize);
     762             : }
     763             : 
     764             : /************************************************************************/
     765             : /*                      GetStreamingFilename()                          */
     766             : /************************************************************************/
     767             : 
     768             : std::string
     769           0 : VSIGSFSHandler::GetStreamingFilename(const std::string &osFilename) const
     770             : {
     771           0 :     if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
     772           0 :         return "/vsigs_streaming/" + osFilename.substr(GetFSPrefix().size());
     773           0 :     return osFilename;
     774             : }
     775             : 
     776             : /************************************************************************/
     777             : /*                             VSIGSHandle()                            */
     778             : /************************************************************************/
     779             : 
     780          27 : VSIGSHandle::VSIGSHandle(VSIGSFSHandler *poFSIn, const char *pszFilename,
     781          27 :                          VSIGSHandleHelper *poHandleHelper)
     782          27 :     : IVSIS3LikeHandle(poFSIn, pszFilename, poHandleHelper->GetURL().c_str()),
     783          27 :       m_poHandleHelper(poHandleHelper)
     784             : {
     785          27 : }
     786             : 
     787             : /************************************************************************/
     788             : /*                            ~VSIGSHandle()                            */
     789             : /************************************************************************/
     790             : 
     791          54 : VSIGSHandle::~VSIGSHandle()
     792             : {
     793          27 :     delete m_poHandleHelper;
     794          54 : }
     795             : 
     796             : /************************************************************************/
     797             : /*                          GetCurlHeaders()                            */
     798             : /************************************************************************/
     799             : 
     800             : struct curl_slist *
     801          22 : VSIGSHandle::GetCurlHeaders(const std::string &osVerb,
     802             :                             const struct curl_slist *psExistingHeaders)
     803             : {
     804          22 :     return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
     805             : }
     806             : 
     807             : } /* end of namespace cpl */
     808             : 
     809             : #endif  // DOXYGEN_SKIP
     810             : //! @endcond
     811             : 
     812             : /************************************************************************/
     813             : /*                      VSIInstallGSFileHandler()                       */
     814             : /************************************************************************/
     815             : 
     816             : /*!
     817             :  \brief Install /vsigs/ Google Cloud Storage file system handler
     818             :  (requires libcurl)
     819             : 
     820             :  \verbatim embed:rst
     821             :  See :ref:`/vsigs/ documentation <vsigs>`
     822             :  \endverbatim
     823             : 
     824             :  @since GDAL 2.2
     825             :  */
     826             : 
     827        1225 : void VSIInstallGSFileHandler(void)
     828             : {
     829        1225 :     VSIFileManager::InstallHandler("/vsigs/",
     830        1225 :                                    new cpl::VSIGSFSHandler("/vsigs/"));
     831        1225 : }
     832             : 
     833             : #endif /* HAVE_CURL */

Generated by: LCOV version 1.14