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: 151 250 60.4 %
Date: 2026-02-12 06:20:29 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 /* nBytes */)
     131             : {
     132           0 :     CPLError(CE_Failure, CPLE_NotSupported,
     133             :              "Read not supported on writable %s files",
     134           0 :              m_poFS->GetFSPrefix().c_str());
     135           0 :     m_bError = true;
     136           0 :     return 0;
     137             : }
     138             : 
     139             : /************************************************************************/
     140             : /*                     ReadCallBackBufferChunked()                      */
     141             : /************************************************************************/
     142             : 
     143           3 : size_t VSIChunkedWriteHandle::ReadCallBackBufferChunked(char *buffer,
     144             :                                                         size_t size,
     145             :                                                         size_t nitems,
     146             :                                                         void *instream)
     147             : {
     148           3 :     VSIChunkedWriteHandle *poThis =
     149             :         static_cast<VSIChunkedWriteHandle *>(instream);
     150           3 :     if (poThis->m_nChunkedBufferSize == 0)
     151             :     {
     152             :         // CPLDebug("VSIChunkedWriteHandle", "Writing 0 byte (finish)");
     153           1 :         return 0;
     154             :     }
     155           2 :     const size_t nSizeMax = size * nitems;
     156           2 :     size_t nSizeToWrite = nSizeMax;
     157           2 :     size_t nChunkedBufferRemainingSize =
     158           2 :         poThis->m_nChunkedBufferSize - poThis->m_nChunkedBufferOff;
     159           2 :     if (nChunkedBufferRemainingSize < nSizeToWrite)
     160           2 :         nSizeToWrite = nChunkedBufferRemainingSize;
     161           2 :     memcpy(buffer,
     162           2 :            static_cast<const GByte *>(poThis->m_pBuffer) +
     163           2 :                poThis->m_nChunkedBufferOff,
     164             :            nSizeToWrite);
     165           2 :     poThis->m_nChunkedBufferOff += nSizeToWrite;
     166             :     // CPLDebug("VSIChunkedWriteHandle", "Writing %d bytes", nSizeToWrite);
     167           2 :     return nSizeToWrite;
     168             : }
     169             : 
     170             : /************************************************************************/
     171             : /*                               Write()                                */
     172             : /************************************************************************/
     173             : 
     174           2 : size_t VSIChunkedWriteHandle::Write(const void *pBuffer, size_t nBytes)
     175             : {
     176           2 :     if (m_bError)
     177           0 :         return 0;
     178             : 
     179           2 :     const size_t nBytesToWrite = nBytes;
     180           2 :     if (nBytesToWrite == 0)
     181           0 :         return 0;
     182           2 :     size_t nRet = nBytes;
     183             : 
     184           2 :     if (m_hCurlMulti == nullptr)
     185             :     {
     186           1 :         m_hCurlMulti = curl_multi_init();
     187             :     }
     188             : 
     189           2 :     WriteFuncStruct sWriteFuncData;
     190           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     191             :     // We can only easily retry at the first chunk of a transfer
     192           2 :     bool bCanRetry = (m_hCurl == nullptr);
     193             :     bool bRetry;
     194           2 :     do
     195             :     {
     196           2 :         bRetry = false;
     197           2 :         struct curl_slist *headers = nullptr;
     198           2 :         if (m_hCurl == nullptr)
     199             :         {
     200           1 :             CURL *hCurlHandle = curl_easy_init();
     201           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     202           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     203             :                                        ReadCallBackBufferChunked);
     204           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, this);
     205             : 
     206           1 :             VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr,
     207             :                                        nullptr);
     208           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
     209             :                                        &sWriteFuncData);
     210           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
     211             :                                        VSICurlHandleWriteFunc);
     212             : 
     213           1 :             VSICURLInitWriteFuncStruct(&m_sWriteFuncHeaderData, nullptr,
     214             :                                        nullptr, nullptr);
     215           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
     216             :                                        &m_sWriteFuncHeaderData);
     217           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
     218             :                                        VSICurlHandleWriteFunc);
     219             : 
     220           1 :             headers = static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     221           1 :                 hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     222           1 :                 m_aosHTTPOptions.List()));
     223           2 :             headers = VSICurlSetCreationHeadersFromOptions(
     224           1 :                 headers, m_aosOptions.List(), m_osFilename.c_str());
     225           1 :             headers = m_poS3HandleHelper->GetCurlHeaders("PUT", headers);
     226           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
     227             :                                        headers);
     228             : 
     229           1 :             m_osCurlErrBuf.resize(CURL_ERROR_SIZE + 1);
     230           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
     231             :                                        &m_osCurlErrBuf[0]);
     232             : 
     233           1 :             curl_multi_add_handle(m_hCurlMulti, hCurlHandle);
     234           1 :             m_hCurl = hCurlHandle;
     235             :         }
     236             : 
     237           2 :         m_pBuffer = pBuffer;
     238           2 :         m_nChunkedBufferOff = 0;
     239           2 :         m_nChunkedBufferSize = nBytesToWrite;
     240             : 
     241           2 :         int repeats = 0;
     242             :         // cppcheck-suppress knownConditionTrueFalse
     243           4 :         while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry)
     244             :         {
     245             :             int still_running;
     246             : 
     247           4 :             memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size());
     248             : 
     249           4 :             while (curl_multi_perform(m_hCurlMulti, &still_running) ==
     250           4 :                        CURLM_CALL_MULTI_PERFORM &&
     251             :                    // cppcheck-suppress knownConditionTrueFalse
     252           0 :                    m_nChunkedBufferOff < m_nChunkedBufferSize)
     253             :             {
     254             :                 // loop
     255             :             }
     256             :             // cppcheck-suppress knownConditionTrueFalse
     257           4 :             if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize)
     258             :                 break;
     259             : 
     260             :             CURLMsg *msg;
     261           0 :             do
     262             :             {
     263           2 :                 int msgq = 0;
     264           2 :                 msg = curl_multi_info_read(m_hCurlMulti, &msgq);
     265           2 :                 if (msg && (msg->msg == CURLMSG_DONE))
     266             :                 {
     267           0 :                     CURL *e = msg->easy_handle;
     268           0 :                     if (e == m_hCurl)
     269             :                     {
     270             :                         long response_code;
     271           0 :                         curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE,
     272             :                                           &response_code);
     273           0 :                         if (response_code != 200 && response_code != 201)
     274             :                         {
     275             :                             // Look if we should attempt a retry
     276           0 :                             if (bCanRetry &&
     277           0 :                                 oRetryContext.CanRetry(
     278             :                                     static_cast<int>(response_code),
     279           0 :                                     m_sWriteFuncHeaderData.pBuffer,
     280             :                                     m_osCurlErrBuf.c_str()))
     281             :                             {
     282           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     283             :                                          "HTTP error code: %d - %s. "
     284             :                                          "Retrying again in %.1f secs",
     285             :                                          static_cast<int>(response_code),
     286           0 :                                          m_poS3HandleHelper->GetURL().c_str(),
     287             :                                          oRetryContext.GetCurrentDelay());
     288           0 :                                 CPLSleep(oRetryContext.GetCurrentDelay());
     289           0 :                                 bRetry = true;
     290             :                             }
     291           0 :                             else if (sWriteFuncData.pBuffer != nullptr &&
     292           0 :                                      m_poS3HandleHelper->CanRestartOnError(
     293           0 :                                          sWriteFuncData.pBuffer,
     294           0 :                                          m_sWriteFuncHeaderData.pBuffer, false))
     295             :                             {
     296           0 :                                 bRetry = true;
     297             :                             }
     298             :                             else
     299             :                             {
     300           0 :                                 CPLError(CE_Failure, CPLE_AppDefined,
     301             :                                          "Error %d: %s",
     302             :                                          static_cast<int>(response_code),
     303             :                                          m_osCurlErrBuf.c_str());
     304             : 
     305           0 :                                 curl_slist_free_all(headers);
     306           0 :                                 bRetry = false;
     307             :                             }
     308             : 
     309           0 :                             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     310           0 :                             curl_easy_cleanup(m_hCurl);
     311             : 
     312           0 :                             CPLFree(sWriteFuncData.pBuffer);
     313           0 :                             CPLFree(m_sWriteFuncHeaderData.pBuffer);
     314             : 
     315           0 :                             m_hCurl = nullptr;
     316           0 :                             sWriteFuncData.pBuffer = nullptr;
     317           0 :                             m_sWriteFuncHeaderData.pBuffer = nullptr;
     318           0 :                             if (!bRetry)
     319           0 :                                 return 0;
     320             :                         }
     321             :                     }
     322             :                 }
     323           2 :             } while (msg);
     324             : 
     325           2 :             CPLMultiPerformWait(m_hCurlMulti, repeats);
     326             :         }
     327             : 
     328           2 :         m_nWrittenInPUT += nBytesToWrite;
     329             : 
     330           2 :         curl_slist_free_all(headers);
     331             : 
     332           2 :         m_pBuffer = nullptr;
     333             : 
     334           2 :         if (!bRetry)
     335             :         {
     336             :             long response_code;
     337           2 :             curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     338           2 :             if (response_code != 100)
     339             :             {
     340             :                 // Look if we should attempt a retry
     341           0 :                 if (bCanRetry &&
     342           0 :                     oRetryContext.CanRetry(static_cast<int>(response_code),
     343           0 :                                            m_sWriteFuncHeaderData.pBuffer,
     344             :                                            m_osCurlErrBuf.c_str()))
     345             :                 {
     346           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     347             :                              "HTTP error code: %d - %s. "
     348             :                              "Retrying again in %.1f secs",
     349             :                              static_cast<int>(response_code),
     350           0 :                              m_poS3HandleHelper->GetURL().c_str(),
     351             :                              oRetryContext.GetCurrentDelay());
     352           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
     353           0 :                     bRetry = true;
     354             :                 }
     355           0 :                 else if (sWriteFuncData.pBuffer != nullptr &&
     356           0 :                          m_poS3HandleHelper->CanRestartOnError(
     357           0 :                              sWriteFuncData.pBuffer,
     358           0 :                              m_sWriteFuncHeaderData.pBuffer, false))
     359             :                 {
     360           0 :                     bRetry = true;
     361             :                 }
     362             :                 else
     363             :                 {
     364           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     365             :                              static_cast<int>(response_code),
     366             :                              m_osCurlErrBuf.c_str());
     367           0 :                     bRetry = false;
     368           0 :                     nRet = 0;
     369             :                 }
     370             : 
     371           0 :                 curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     372           0 :                 curl_easy_cleanup(m_hCurl);
     373             : 
     374           0 :                 CPLFree(sWriteFuncData.pBuffer);
     375           0 :                 CPLFree(m_sWriteFuncHeaderData.pBuffer);
     376             : 
     377           0 :                 m_hCurl = nullptr;
     378           0 :                 sWriteFuncData.pBuffer = nullptr;
     379           0 :                 m_sWriteFuncHeaderData.pBuffer = nullptr;
     380             :             }
     381             :         }
     382             :     } while (bRetry);
     383             : 
     384           2 :     m_nCurOffset += nBytesToWrite;
     385             : 
     386           2 :     return nRet;
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                       FinishChunkedTransfer()                        */
     391             : /************************************************************************/
     392             : 
     393           1 : int VSIChunkedWriteHandle::FinishChunkedTransfer()
     394             : {
     395           1 :     if (m_hCurl == nullptr)
     396           0 :         return -1;
     397             : 
     398           2 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     399           2 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     400           2 :     NetworkStatisticsAction oContextAction("Write");
     401             : 
     402           1 :     NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT);
     403           1 :     m_nWrittenInPUT = 0;
     404             : 
     405           1 :     m_pBuffer = nullptr;
     406           1 :     m_nChunkedBufferOff = 0;
     407           1 :     m_nChunkedBufferSize = 0;
     408             : 
     409           1 :     VSICURLMultiPerform(m_hCurlMulti);
     410             : 
     411             :     long response_code;
     412           1 :     curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     413           1 :     if (response_code == 200 || response_code == 201)
     414             :     {
     415           1 :         InvalidateParentDirectory();
     416             :     }
     417             :     else
     418             :     {
     419           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     420             :                  static_cast<int>(response_code), m_osCurlErrBuf.c_str());
     421           0 :         return -1;
     422             :     }
     423           1 :     return 0;
     424             : }
     425             : 
     426             : /************************************************************************/
     427             : /*                             DoEmptyPUT()                             */
     428             : /************************************************************************/
     429             : 
     430           2 : bool VSIChunkedWriteHandle::DoEmptyPUT()
     431             : {
     432           2 :     bool bSuccess = true;
     433             :     bool bRetry;
     434           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     435             : 
     436           4 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     437           4 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     438           2 :     NetworkStatisticsAction oContextAction("Write");
     439             : 
     440           2 :     do
     441             :     {
     442           2 :         bRetry = false;
     443             : 
     444           2 :         PutData putData;
     445           2 :         putData.pabyData = nullptr;
     446           2 :         putData.nOff = 0;
     447           2 :         putData.nTotalSize = 0;
     448             : 
     449           2 :         CURL *hCurlHandle = curl_easy_init();
     450           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     451           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     452             :                                    PutData::ReadCallBackBuffer);
     453           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
     454           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
     455             : 
     456             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     457           2 :             CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     458           2 :                               m_aosHTTPOptions.List()));
     459           4 :         headers = VSICurlSetCreationHeadersFromOptions(
     460           2 :             headers, m_aosOptions.List(), m_osFilename.c_str());
     461           2 :         headers = m_poS3HandleHelper->GetCurlHeaders("PUT", headers, "", 0);
     462           2 :         headers = curl_slist_append(headers, "Expect: 100-continue");
     463             : 
     464           4 :         CurlRequestHelper requestHelper;
     465           4 :         const long response_code = requestHelper.perform(
     466           2 :             hCurlHandle, headers, m_poFS, m_poS3HandleHelper);
     467             : 
     468           2 :         NetworkStatisticsLogger::LogPUT(0);
     469             : 
     470           2 :         if (response_code != 200 && response_code != 201)
     471             :         {
     472             :             // Look if we should attempt a retry
     473           0 :             if (oRetryContext.CanRetry(
     474             :                     static_cast<int>(response_code),
     475           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     476             :                     requestHelper.szCurlErrBuf))
     477             :             {
     478           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     479             :                          "HTTP error code: %d - %s. "
     480             :                          "Retrying again in %.1f secs",
     481             :                          static_cast<int>(response_code),
     482           0 :                          m_poS3HandleHelper->GetURL().c_str(),
     483             :                          oRetryContext.GetCurrentDelay());
     484           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     485           0 :                 bRetry = true;
     486             :             }
     487           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
     488           0 :                      m_poS3HandleHelper->CanRestartOnError(
     489           0 :                          requestHelper.sWriteFuncData.pBuffer,
     490           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, false))
     491             :             {
     492           0 :                 bRetry = true;
     493             :             }
     494             :             else
     495             :             {
     496           0 :                 CPLDebug("S3", "%s",
     497           0 :                          requestHelper.sWriteFuncData.pBuffer
     498             :                              ? requestHelper.sWriteFuncData.pBuffer
     499             :                              : "(null)");
     500           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     501             :                          "DoSinglePartPUT of %s failed", m_osFilename.c_str());
     502           0 :                 bSuccess = false;
     503             :             }
     504             :         }
     505             :         else
     506             :         {
     507           2 :             InvalidateParentDirectory();
     508             :         }
     509             : 
     510           2 :         if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr)
     511             :         {
     512             :             const char *pzETag =
     513           2 :                 strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \"");
     514           2 :             if (pzETag)
     515             :             {
     516           0 :                 pzETag += strlen("ETag: \"");
     517           0 :                 const char *pszEndOfETag = strchr(pzETag, '"');
     518           0 :                 if (pszEndOfETag)
     519             :                 {
     520           0 :                     FileProp oFileProp;
     521           0 :                     oFileProp.eExists = EXIST_YES;
     522           0 :                     oFileProp.fileSize = m_nBufferOff;
     523           0 :                     oFileProp.bHasComputedFileSize = true;
     524           0 :                     oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag);
     525           0 :                     m_poFS->SetCachedFileProp(
     526           0 :                         m_poFS->GetURLFromFilename(m_osFilename.c_str())
     527             :                             .c_str(),
     528             :                         oFileProp);
     529             :                 }
     530             :             }
     531             :         }
     532             : 
     533           2 :         curl_easy_cleanup(hCurlHandle);
     534             :     } while (bRetry);
     535           4 :     return bSuccess;
     536             : }
     537             : 
     538             : }  // namespace cpl
     539             : 
     540             : //! @endcond
     541             : 
     542             : #endif  // HAVE_CURL

Generated by: LCOV version 1.14