LCOV - code coverage report
Current view: top level - port - cpl_vsil_chunked_write_handle.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 152 251 60.6 %
Date: 2025-01-18 12:42:00 Functions: 9 12 75.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement a write-only file handle using PUT chunked writing
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_vsil_curl_class.h"
      14             : 
      15             : #ifdef HAVE_CURL
      16             : 
      17             : //! @cond Doxygen_Suppress
      18             : 
      19             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      20             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      21             : 
      22             : namespace cpl
      23             : {
      24             : 
      25             : /************************************************************************/
      26             : /*                        VSIChunkedWriteHandle()                       */
      27             : /************************************************************************/
      28             : 
      29           3 : VSIChunkedWriteHandle::VSIChunkedWriteHandle(
      30             :     IVSIS3LikeFSHandler *poFS, const char *pszFilename,
      31           3 :     IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions)
      32             :     : m_poFS(poFS), m_osFilename(pszFilename),
      33             :       m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions),
      34             :       m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
      35           3 :       m_oRetryParameters(m_aosHTTPOptions)
      36             : {
      37           3 : }
      38             : 
      39             : /************************************************************************/
      40             : /*                      ~VSIChunkedWriteHandle()                        */
      41             : /************************************************************************/
      42             : 
      43           6 : VSIChunkedWriteHandle::~VSIChunkedWriteHandle()
      44             : {
      45           3 :     VSIChunkedWriteHandle::Close();
      46           3 :     delete m_poS3HandleHelper;
      47             : 
      48           3 :     if (m_hCurlMulti)
      49             :     {
      50           1 :         if (m_hCurl)
      51             :         {
      52           1 :             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
      53           1 :             curl_easy_cleanup(m_hCurl);
      54             :         }
      55           1 :         VSICURLMultiCleanup(m_hCurlMulti);
      56             :     }
      57           3 :     CPLFree(m_sWriteFuncHeaderData.pBuffer);
      58           6 : }
      59             : 
      60             : /************************************************************************/
      61             : /*                                 Close()                              */
      62             : /************************************************************************/
      63             : 
      64           6 : int VSIChunkedWriteHandle::Close()
      65             : {
      66           6 :     int nRet = 0;
      67           6 :     if (!m_bClosed)
      68             :     {
      69           3 :         m_bClosed = true;
      70           3 :         if (m_hCurlMulti != nullptr)
      71             :         {
      72           1 :             nRet = FinishChunkedTransfer();
      73             :         }
      74             :         else
      75             :         {
      76           2 :             if (!m_bError && !DoEmptyPUT())
      77           0 :                 nRet = -1;
      78             :         }
      79             :     }
      80           6 :     return nRet;
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                    InvalidateParentDirectory()                       */
      85             : /************************************************************************/
      86             : 
      87           3 : void VSIChunkedWriteHandle::InvalidateParentDirectory()
      88             : {
      89           3 :     m_poFS->InvalidateCachedData(m_poS3HandleHelper->GetURL().c_str());
      90             : 
      91           3 :     std::string osFilenameWithoutSlash(m_osFilename);
      92           3 :     if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
      93           1 :         osFilenameWithoutSlash.pop_back();
      94           3 :     m_poFS->InvalidateDirContent(
      95           6 :         CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
      96           3 : }
      97             : 
      98             : /************************************************************************/
      99             : /*                               Seek()                                 */
     100             : /************************************************************************/
     101             : 
     102           0 : int VSIChunkedWriteHandle::Seek(vsi_l_offset nOffset, int nWhence)
     103             : {
     104           0 :     if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) ||
     105           0 :           (nWhence == SEEK_CUR && nOffset == 0) ||
     106           0 :           (nWhence == SEEK_END && nOffset == 0)))
     107             :     {
     108           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     109             :                  "Seek not supported on writable %s files",
     110           0 :                  m_poFS->GetFSPrefix().c_str());
     111           0 :         m_bError = true;
     112           0 :         return -1;
     113             :     }
     114           0 :     return 0;
     115             : }
     116             : 
     117             : /************************************************************************/
     118             : /*                               Tell()                                 */
     119             : /************************************************************************/
     120             : 
     121           0 : vsi_l_offset VSIChunkedWriteHandle::Tell()
     122             : {
     123           0 :     return m_nCurOffset;
     124             : }
     125             : 
     126             : /************************************************************************/
     127             : /*                               Read()                                 */
     128             : /************************************************************************/
     129             : 
     130           0 : size_t VSIChunkedWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */,
     131             :                                    size_t /* nMemb */)
     132             : {
     133           0 :     CPLError(CE_Failure, CPLE_NotSupported,
     134             :              "Read not supported on writable %s files",
     135           0 :              m_poFS->GetFSPrefix().c_str());
     136           0 :     m_bError = true;
     137           0 :     return 0;
     138             : }
     139             : 
     140             : /************************************************************************/
     141             : /*                      ReadCallBackBufferChunked()                     */
     142             : /************************************************************************/
     143             : 
     144           3 : size_t VSIChunkedWriteHandle::ReadCallBackBufferChunked(char *buffer,
     145             :                                                         size_t size,
     146             :                                                         size_t nitems,
     147             :                                                         void *instream)
     148             : {
     149           3 :     VSIChunkedWriteHandle *poThis =
     150             :         static_cast<VSIChunkedWriteHandle *>(instream);
     151           3 :     if (poThis->m_nChunkedBufferSize == 0)
     152             :     {
     153             :         // CPLDebug("VSIChunkedWriteHandle", "Writing 0 byte (finish)");
     154           1 :         return 0;
     155             :     }
     156           2 :     const size_t nSizeMax = size * nitems;
     157           2 :     size_t nSizeToWrite = nSizeMax;
     158           2 :     size_t nChunkedBufferRemainingSize =
     159           2 :         poThis->m_nChunkedBufferSize - poThis->m_nChunkedBufferOff;
     160           2 :     if (nChunkedBufferRemainingSize < nSizeToWrite)
     161           2 :         nSizeToWrite = nChunkedBufferRemainingSize;
     162           2 :     memcpy(buffer,
     163           2 :            static_cast<const GByte *>(poThis->m_pBuffer) +
     164           2 :                poThis->m_nChunkedBufferOff,
     165             :            nSizeToWrite);
     166           2 :     poThis->m_nChunkedBufferOff += nSizeToWrite;
     167             :     // CPLDebug("VSIChunkedWriteHandle", "Writing %d bytes", nSizeToWrite);
     168           2 :     return nSizeToWrite;
     169             : }
     170             : 
     171             : /************************************************************************/
     172             : /*                               Write()                                */
     173             : /************************************************************************/
     174             : 
     175           2 : size_t VSIChunkedWriteHandle::Write(const void *pBuffer, size_t nSize,
     176             :                                     size_t nMemb)
     177             : {
     178           2 :     if (m_bError)
     179           0 :         return 0;
     180             : 
     181           2 :     const size_t nBytesToWrite = nSize * nMemb;
     182           2 :     if (nBytesToWrite == 0)
     183           0 :         return 0;
     184             : 
     185           2 :     if (m_hCurlMulti == nullptr)
     186             :     {
     187           1 :         m_hCurlMulti = curl_multi_init();
     188             :     }
     189             : 
     190           2 :     WriteFuncStruct sWriteFuncData;
     191           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     192             :     // We can only easily retry at the first chunk of a transfer
     193           2 :     bool bCanRetry = (m_hCurl == nullptr);
     194             :     bool bRetry;
     195           2 :     do
     196             :     {
     197           2 :         bRetry = false;
     198           2 :         struct curl_slist *headers = nullptr;
     199           2 :         if (m_hCurl == nullptr)
     200             :         {
     201           1 :             CURL *hCurlHandle = curl_easy_init();
     202           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     203           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     204             :                                        ReadCallBackBufferChunked);
     205           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, this);
     206             : 
     207           1 :             VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr,
     208             :                                        nullptr);
     209           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
     210             :                                        &sWriteFuncData);
     211           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
     212             :                                        VSICurlHandleWriteFunc);
     213             : 
     214           1 :             VSICURLInitWriteFuncStruct(&m_sWriteFuncHeaderData, nullptr,
     215             :                                        nullptr, nullptr);
     216           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
     217             :                                        &m_sWriteFuncHeaderData);
     218           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
     219             :                                        VSICurlHandleWriteFunc);
     220             : 
     221           1 :             headers = static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     222           1 :                 hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     223           1 :                 m_aosHTTPOptions.List()));
     224           2 :             headers = VSICurlSetCreationHeadersFromOptions(
     225           1 :                 headers, m_aosOptions.List(), m_osFilename.c_str());
     226           1 :             headers = VSICurlMergeHeaders(
     227           1 :                 headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers));
     228           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
     229             :                                        headers);
     230             : 
     231           1 :             m_osCurlErrBuf.resize(CURL_ERROR_SIZE + 1);
     232           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
     233             :                                        &m_osCurlErrBuf[0]);
     234             : 
     235           1 :             curl_multi_add_handle(m_hCurlMulti, hCurlHandle);
     236           1 :             m_hCurl = hCurlHandle;
     237             :         }
     238             : 
     239           2 :         m_pBuffer = pBuffer;
     240           2 :         m_nChunkedBufferOff = 0;
     241           2 :         m_nChunkedBufferSize = nBytesToWrite;
     242             : 
     243           2 :         int repeats = 0;
     244             :         // cppcheck-suppress knownConditionTrueFalse
     245           3 :         while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry)
     246             :         {
     247             :             int still_running;
     248             : 
     249           3 :             memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size());
     250             : 
     251           3 :             while (curl_multi_perform(m_hCurlMulti, &still_running) ==
     252           3 :                        CURLM_CALL_MULTI_PERFORM &&
     253             :                    // cppcheck-suppress knownConditionTrueFalse
     254           0 :                    m_nChunkedBufferOff < m_nChunkedBufferSize)
     255             :             {
     256             :                 // loop
     257             :             }
     258             :             // cppcheck-suppress knownConditionTrueFalse
     259           3 :             if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize)
     260             :                 break;
     261             : 
     262             :             CURLMsg *msg;
     263           0 :             do
     264             :             {
     265           1 :                 int msgq = 0;
     266           1 :                 msg = curl_multi_info_read(m_hCurlMulti, &msgq);
     267           1 :                 if (msg && (msg->msg == CURLMSG_DONE))
     268             :                 {
     269           0 :                     CURL *e = msg->easy_handle;
     270           0 :                     if (e == m_hCurl)
     271             :                     {
     272             :                         long response_code;
     273           0 :                         curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE,
     274             :                                           &response_code);
     275           0 :                         if (response_code != 200 && response_code != 201)
     276             :                         {
     277             :                             // Look if we should attempt a retry
     278           0 :                             if (bCanRetry &&
     279           0 :                                 oRetryContext.CanRetry(
     280             :                                     static_cast<int>(response_code),
     281           0 :                                     m_sWriteFuncHeaderData.pBuffer,
     282             :                                     m_osCurlErrBuf.c_str()))
     283             :                             {
     284           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     285             :                                          "HTTP error code: %d - %s. "
     286             :                                          "Retrying again in %.1f secs",
     287             :                                          static_cast<int>(response_code),
     288           0 :                                          m_poS3HandleHelper->GetURL().c_str(),
     289             :                                          oRetryContext.GetCurrentDelay());
     290           0 :                                 CPLSleep(oRetryContext.GetCurrentDelay());
     291           0 :                                 bRetry = true;
     292             :                             }
     293           0 :                             else if (sWriteFuncData.pBuffer != nullptr &&
     294           0 :                                      m_poS3HandleHelper->CanRestartOnError(
     295           0 :                                          sWriteFuncData.pBuffer,
     296           0 :                                          m_sWriteFuncHeaderData.pBuffer, false))
     297             :                             {
     298           0 :                                 bRetry = true;
     299             :                             }
     300             :                             else
     301             :                             {
     302           0 :                                 CPLError(CE_Failure, CPLE_AppDefined,
     303             :                                          "Error %d: %s",
     304             :                                          static_cast<int>(response_code),
     305             :                                          m_osCurlErrBuf.c_str());
     306             : 
     307           0 :                                 curl_slist_free_all(headers);
     308           0 :                                 bRetry = false;
     309             :                             }
     310             : 
     311           0 :                             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     312           0 :                             curl_easy_cleanup(m_hCurl);
     313             : 
     314           0 :                             CPLFree(sWriteFuncData.pBuffer);
     315           0 :                             CPLFree(m_sWriteFuncHeaderData.pBuffer);
     316             : 
     317           0 :                             m_hCurl = nullptr;
     318           0 :                             sWriteFuncData.pBuffer = nullptr;
     319           0 :                             m_sWriteFuncHeaderData.pBuffer = nullptr;
     320           0 :                             if (!bRetry)
     321           0 :                                 return 0;
     322             :                         }
     323             :                     }
     324             :                 }
     325           1 :             } while (msg);
     326             : 
     327           1 :             CPLMultiPerformWait(m_hCurlMulti, repeats);
     328             :         }
     329             : 
     330           2 :         m_nWrittenInPUT += nBytesToWrite;
     331             : 
     332           2 :         curl_slist_free_all(headers);
     333             : 
     334           2 :         m_pBuffer = nullptr;
     335             : 
     336           2 :         if (!bRetry)
     337             :         {
     338             :             long response_code;
     339           2 :             curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     340           2 :             if (response_code != 100)
     341             :             {
     342             :                 // Look if we should attempt a retry
     343           0 :                 if (bCanRetry &&
     344           0 :                     oRetryContext.CanRetry(static_cast<int>(response_code),
     345           0 :                                            m_sWriteFuncHeaderData.pBuffer,
     346             :                                            m_osCurlErrBuf.c_str()))
     347             :                 {
     348           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     349             :                              "HTTP error code: %d - %s. "
     350             :                              "Retrying again in %.1f secs",
     351             :                              static_cast<int>(response_code),
     352           0 :                              m_poS3HandleHelper->GetURL().c_str(),
     353             :                              oRetryContext.GetCurrentDelay());
     354           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
     355           0 :                     bRetry = true;
     356             :                 }
     357           0 :                 else if (sWriteFuncData.pBuffer != nullptr &&
     358           0 :                          m_poS3HandleHelper->CanRestartOnError(
     359           0 :                              sWriteFuncData.pBuffer,
     360           0 :                              m_sWriteFuncHeaderData.pBuffer, false))
     361             :                 {
     362           0 :                     bRetry = true;
     363             :                 }
     364             :                 else
     365             :                 {
     366           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     367             :                              static_cast<int>(response_code),
     368             :                              m_osCurlErrBuf.c_str());
     369           0 :                     bRetry = false;
     370           0 :                     nMemb = 0;
     371             :                 }
     372             : 
     373           0 :                 curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     374           0 :                 curl_easy_cleanup(m_hCurl);
     375             : 
     376           0 :                 CPLFree(sWriteFuncData.pBuffer);
     377           0 :                 CPLFree(m_sWriteFuncHeaderData.pBuffer);
     378             : 
     379           0 :                 m_hCurl = nullptr;
     380           0 :                 sWriteFuncData.pBuffer = nullptr;
     381           0 :                 m_sWriteFuncHeaderData.pBuffer = nullptr;
     382             :             }
     383             :         }
     384             :     } while (bRetry);
     385             : 
     386           2 :     m_nCurOffset += nBytesToWrite;
     387             : 
     388           2 :     return nMemb;
     389             : }
     390             : 
     391             : /************************************************************************/
     392             : /*                        FinishChunkedTransfer()                       */
     393             : /************************************************************************/
     394             : 
     395           1 : int VSIChunkedWriteHandle::FinishChunkedTransfer()
     396             : {
     397           1 :     if (m_hCurl == nullptr)
     398           0 :         return -1;
     399             : 
     400           2 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     401           2 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     402           2 :     NetworkStatisticsAction oContextAction("Write");
     403             : 
     404           1 :     NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT);
     405           1 :     m_nWrittenInPUT = 0;
     406             : 
     407           1 :     m_pBuffer = nullptr;
     408           1 :     m_nChunkedBufferOff = 0;
     409           1 :     m_nChunkedBufferSize = 0;
     410             : 
     411           1 :     VSICURLMultiPerform(m_hCurlMulti);
     412             : 
     413             :     long response_code;
     414           1 :     curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     415           1 :     if (response_code == 200 || response_code == 201)
     416             :     {
     417           1 :         InvalidateParentDirectory();
     418             :     }
     419             :     else
     420             :     {
     421           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     422             :                  static_cast<int>(response_code), m_osCurlErrBuf.c_str());
     423           0 :         return -1;
     424             :     }
     425           1 :     return 0;
     426             : }
     427             : 
     428             : /************************************************************************/
     429             : /*                            DoEmptyPUT()                              */
     430             : /************************************************************************/
     431             : 
     432           2 : bool VSIChunkedWriteHandle::DoEmptyPUT()
     433             : {
     434           2 :     bool bSuccess = true;
     435             :     bool bRetry;
     436           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     437             : 
     438           4 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     439           4 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     440           2 :     NetworkStatisticsAction oContextAction("Write");
     441             : 
     442           2 :     do
     443             :     {
     444           2 :         bRetry = false;
     445             : 
     446           2 :         PutData putData;
     447           2 :         putData.pabyData = nullptr;
     448           2 :         putData.nOff = 0;
     449           2 :         putData.nTotalSize = 0;
     450             : 
     451           2 :         CURL *hCurlHandle = curl_easy_init();
     452           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     453           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     454             :                                    PutData::ReadCallBackBuffer);
     455           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
     456           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
     457             : 
     458             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     459           2 :             CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     460           2 :                               m_aosHTTPOptions.List()));
     461           4 :         headers = VSICurlSetCreationHeadersFromOptions(
     462           2 :             headers, m_aosOptions.List(), m_osFilename.c_str());
     463           2 :         headers = VSICurlMergeHeaders(
     464           2 :             headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers, "", 0));
     465           2 :         headers = curl_slist_append(headers, "Expect: 100-continue");
     466             : 
     467           4 :         CurlRequestHelper requestHelper;
     468           4 :         const long response_code = requestHelper.perform(
     469           2 :             hCurlHandle, headers, m_poFS, m_poS3HandleHelper);
     470             : 
     471           2 :         NetworkStatisticsLogger::LogPUT(0);
     472             : 
     473           2 :         if (response_code != 200 && response_code != 201)
     474             :         {
     475             :             // Look if we should attempt a retry
     476           0 :             if (oRetryContext.CanRetry(
     477             :                     static_cast<int>(response_code),
     478           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     479             :                     requestHelper.szCurlErrBuf))
     480             :             {
     481           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     482             :                          "HTTP error code: %d - %s. "
     483             :                          "Retrying again in %.1f secs",
     484             :                          static_cast<int>(response_code),
     485           0 :                          m_poS3HandleHelper->GetURL().c_str(),
     486             :                          oRetryContext.GetCurrentDelay());
     487           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     488           0 :                 bRetry = true;
     489             :             }
     490           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
     491           0 :                      m_poS3HandleHelper->CanRestartOnError(
     492           0 :                          requestHelper.sWriteFuncData.pBuffer,
     493           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, false))
     494             :             {
     495           0 :                 bRetry = true;
     496             :             }
     497             :             else
     498             :             {
     499           0 :                 CPLDebug("S3", "%s",
     500           0 :                          requestHelper.sWriteFuncData.pBuffer
     501             :                              ? requestHelper.sWriteFuncData.pBuffer
     502             :                              : "(null)");
     503           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     504             :                          "DoSinglePartPUT of %s failed", m_osFilename.c_str());
     505           0 :                 bSuccess = false;
     506             :             }
     507             :         }
     508             :         else
     509             :         {
     510           2 :             InvalidateParentDirectory();
     511             :         }
     512             : 
     513           2 :         if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr)
     514             :         {
     515             :             const char *pzETag =
     516           2 :                 strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \"");
     517           2 :             if (pzETag)
     518             :             {
     519           0 :                 pzETag += strlen("ETag: \"");
     520           0 :                 const char *pszEndOfETag = strchr(pzETag, '"');
     521           0 :                 if (pszEndOfETag)
     522             :                 {
     523           0 :                     FileProp oFileProp;
     524           0 :                     oFileProp.eExists = EXIST_YES;
     525           0 :                     oFileProp.fileSize = m_nBufferOff;
     526           0 :                     oFileProp.bHasComputedFileSize = true;
     527           0 :                     oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag);
     528           0 :                     m_poFS->SetCachedFileProp(
     529           0 :                         m_poFS->GetURLFromFilename(m_osFilename.c_str())
     530             :                             .c_str(),
     531             :                         oFileProp);
     532             :                 }
     533             :             }
     534             :         }
     535             : 
     536           2 :         curl_easy_cleanup(hCurlHandle);
     537             :     } while (bRetry);
     538           4 :     return bSuccess;
     539             : }
     540             : 
     541             : }  // namespace cpl
     542             : 
     543             : //! @endcond
     544             : 
     545             : #endif  // HAVE_CURL

Generated by: LCOV version 1.14