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 249 60.6 %
Date: 2026-09-09 22:24:20 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             :         // cppcheck-suppress knownConditionTrueFalse
     242           4 :         while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry)
     243             :         {
     244           4 :             int still_running = FALSE;
     245             : 
     246           4 :             memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size());
     247             : 
     248           4 :             CPL_IGNORE_RET_VAL(
     249           4 :                 curl_multi_perform(m_hCurlMulti, &still_running));
     250             :             // cppcheck-suppress knownConditionTrueFalse
     251           4 :             if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize)
     252             :                 break;
     253             : 
     254             :             CURLMsg *msg;
     255           0 :             do
     256             :             {
     257           2 :                 int msgq = 0;
     258           2 :                 msg = curl_multi_info_read(m_hCurlMulti, &msgq);
     259           2 :                 if (msg && (msg->msg == CURLMSG_DONE))
     260             :                 {
     261           0 :                     CURL *e = msg->easy_handle;
     262           0 :                     if (e == m_hCurl)
     263             :                     {
     264             :                         long response_code;
     265           0 :                         curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE,
     266             :                                           &response_code);
     267           0 :                         if (response_code != 200 && response_code != 201)
     268             :                         {
     269             :                             // Look if we should attempt a retry
     270           0 :                             if (bCanRetry &&
     271           0 :                                 oRetryContext.CanRetry(
     272             :                                     static_cast<int>(response_code),
     273           0 :                                     m_sWriteFuncHeaderData.pBuffer,
     274             :                                     m_osCurlErrBuf.c_str()))
     275             :                             {
     276           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     277             :                                          "HTTP error code: %d - %s. "
     278             :                                          "Retrying again in %.1f secs",
     279             :                                          static_cast<int>(response_code),
     280           0 :                                          m_poS3HandleHelper->GetURL().c_str(),
     281             :                                          oRetryContext.GetCurrentDelay());
     282           0 :                                 CPLSleep(oRetryContext.GetCurrentDelay());
     283           0 :                                 bRetry = true;
     284             :                             }
     285           0 :                             else if (sWriteFuncData.pBuffer != nullptr &&
     286           0 :                                      m_poS3HandleHelper->CanRestartOnError(
     287           0 :                                          sWriteFuncData.pBuffer,
     288           0 :                                          m_sWriteFuncHeaderData.pBuffer, false))
     289             :                             {
     290           0 :                                 bRetry = true;
     291             :                             }
     292             :                             else
     293             :                             {
     294           0 :                                 CPLError(CE_Failure, CPLE_AppDefined,
     295             :                                          "Error %d: %s",
     296             :                                          static_cast<int>(response_code),
     297             :                                          m_osCurlErrBuf.c_str());
     298             : 
     299           0 :                                 curl_slist_free_all(headers);
     300           0 :                                 bRetry = false;
     301             :                             }
     302             : 
     303           0 :                             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     304           0 :                             curl_easy_cleanup(m_hCurl);
     305             : 
     306           0 :                             CPLFree(sWriteFuncData.pBuffer);
     307           0 :                             CPLFree(m_sWriteFuncHeaderData.pBuffer);
     308             : 
     309           0 :                             m_hCurl = nullptr;
     310           0 :                             sWriteFuncData.pBuffer = nullptr;
     311           0 :                             m_sWriteFuncHeaderData.pBuffer = nullptr;
     312           0 :                             if (!bRetry)
     313           0 :                                 return 0;
     314             :                         }
     315             :                     }
     316             :                 }
     317           2 :             } while (msg);
     318             : 
     319           2 :             CPLMultiPerformWait(m_hCurlMulti);
     320             :         }
     321             : 
     322           2 :         m_nWrittenInPUT += nBytesToWrite;
     323             : 
     324           2 :         curl_slist_free_all(headers);
     325             : 
     326           2 :         m_pBuffer = nullptr;
     327             : 
     328           2 :         if (!bRetry)
     329             :         {
     330             :             long response_code;
     331           2 :             curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     332           2 :             if (response_code != 100)
     333             :             {
     334             :                 // Look if we should attempt a retry
     335           0 :                 if (bCanRetry &&
     336           0 :                     oRetryContext.CanRetry(static_cast<int>(response_code),
     337           0 :                                            m_sWriteFuncHeaderData.pBuffer,
     338             :                                            m_osCurlErrBuf.c_str()))
     339             :                 {
     340           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     341             :                              "HTTP error code: %d - %s. "
     342             :                              "Retrying again in %.1f secs",
     343             :                              static_cast<int>(response_code),
     344           0 :                              m_poS3HandleHelper->GetURL().c_str(),
     345             :                              oRetryContext.GetCurrentDelay());
     346           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
     347           0 :                     bRetry = true;
     348             :                 }
     349           0 :                 else if (sWriteFuncData.pBuffer != nullptr &&
     350           0 :                          m_poS3HandleHelper->CanRestartOnError(
     351           0 :                              sWriteFuncData.pBuffer,
     352           0 :                              m_sWriteFuncHeaderData.pBuffer, false))
     353             :                 {
     354           0 :                     bRetry = true;
     355             :                 }
     356             :                 else
     357             :                 {
     358           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     359             :                              static_cast<int>(response_code),
     360             :                              m_osCurlErrBuf.c_str());
     361           0 :                     bRetry = false;
     362           0 :                     nRet = 0;
     363             :                 }
     364             : 
     365           0 :                 curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     366           0 :                 curl_easy_cleanup(m_hCurl);
     367             : 
     368           0 :                 CPLFree(sWriteFuncData.pBuffer);
     369           0 :                 CPLFree(m_sWriteFuncHeaderData.pBuffer);
     370             : 
     371           0 :                 m_hCurl = nullptr;
     372           0 :                 sWriteFuncData.pBuffer = nullptr;
     373           0 :                 m_sWriteFuncHeaderData.pBuffer = nullptr;
     374             :             }
     375             :         }
     376             :     } while (bRetry);
     377             : 
     378           2 :     m_nCurOffset += nBytesToWrite;
     379             : 
     380           2 :     return nRet;
     381             : }
     382             : 
     383             : /************************************************************************/
     384             : /*                       FinishChunkedTransfer()                        */
     385             : /************************************************************************/
     386             : 
     387           1 : int VSIChunkedWriteHandle::FinishChunkedTransfer()
     388             : {
     389           1 :     if (m_hCurl == nullptr)
     390           0 :         return -1;
     391             : 
     392           2 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     393           2 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     394           2 :     NetworkStatisticsAction oContextAction("Write");
     395             : 
     396           1 :     NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT);
     397           1 :     m_nWrittenInPUT = 0;
     398             : 
     399           1 :     m_pBuffer = nullptr;
     400           1 :     m_nChunkedBufferOff = 0;
     401           1 :     m_nChunkedBufferSize = 0;
     402             : 
     403           1 :     VSICURLMultiPerform(m_hCurlMulti);
     404             : 
     405             :     long response_code;
     406           1 :     curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     407           1 :     if (response_code == 200 || response_code == 201)
     408             :     {
     409           1 :         InvalidateParentDirectory();
     410             :     }
     411             :     else
     412             :     {
     413           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     414             :                  static_cast<int>(response_code), m_osCurlErrBuf.c_str());
     415           0 :         return -1;
     416             :     }
     417           1 :     return 0;
     418             : }
     419             : 
     420             : /************************************************************************/
     421             : /*                             DoEmptyPUT()                             */
     422             : /************************************************************************/
     423             : 
     424           2 : bool VSIChunkedWriteHandle::DoEmptyPUT()
     425             : {
     426           2 :     bool bSuccess = true;
     427             :     bool bRetry;
     428           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     429             : 
     430           4 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     431           4 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     432           2 :     NetworkStatisticsAction oContextAction("Write");
     433             : 
     434           2 :     do
     435             :     {
     436           2 :         bRetry = false;
     437             : 
     438           2 :         PutData putData;
     439           2 :         putData.pabyData = nullptr;
     440           2 :         putData.nOff = 0;
     441           2 :         putData.nTotalSize = 0;
     442             : 
     443           2 :         CURL *hCurlHandle = curl_easy_init();
     444           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     445           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     446             :                                    PutData::ReadCallBackBuffer);
     447           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
     448           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
     449             : 
     450             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     451           2 :             CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     452           2 :                               m_aosHTTPOptions.List()));
     453           4 :         headers = VSICurlSetCreationHeadersFromOptions(
     454           2 :             headers, m_aosOptions.List(), m_osFilename.c_str());
     455           2 :         headers = m_poS3HandleHelper->GetCurlHeaders("PUT", headers, "", 0);
     456           2 :         headers = curl_slist_append(headers, "Expect: 100-continue");
     457             : 
     458           4 :         CurlRequestHelper requestHelper;
     459           4 :         const long response_code = requestHelper.perform(
     460           2 :             hCurlHandle, headers, m_poFS, m_poS3HandleHelper);
     461             : 
     462           2 :         NetworkStatisticsLogger::LogPUT(0);
     463             : 
     464           2 :         if (response_code != 200 && response_code != 201)
     465             :         {
     466             :             // Look if we should attempt a retry
     467           0 :             if (oRetryContext.CanRetry(
     468             :                     static_cast<int>(response_code),
     469           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     470             :                     requestHelper.szCurlErrBuf))
     471             :             {
     472           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     473             :                          "HTTP error code: %d - %s. "
     474             :                          "Retrying again in %.1f secs",
     475             :                          static_cast<int>(response_code),
     476           0 :                          m_poS3HandleHelper->GetURL().c_str(),
     477             :                          oRetryContext.GetCurrentDelay());
     478           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     479           0 :                 bRetry = true;
     480             :             }
     481           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
     482           0 :                      m_poS3HandleHelper->CanRestartOnError(
     483           0 :                          requestHelper.sWriteFuncData.pBuffer,
     484           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, false))
     485             :             {
     486           0 :                 bRetry = true;
     487             :             }
     488             :             else
     489             :             {
     490           0 :                 CPLDebug("S3", "%s",
     491           0 :                          requestHelper.sWriteFuncData.pBuffer
     492             :                              ? requestHelper.sWriteFuncData.pBuffer
     493             :                              : "(null)");
     494           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     495             :                          "DoSinglePartPUT of %s failed", m_osFilename.c_str());
     496           0 :                 bSuccess = false;
     497             :             }
     498             :         }
     499             :         else
     500             :         {
     501           2 :             InvalidateParentDirectory();
     502             :         }
     503             : 
     504           2 :         if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr)
     505             :         {
     506             :             const char *pzETag =
     507           2 :                 strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \"");
     508           2 :             if (pzETag)
     509             :             {
     510           0 :                 pzETag += strlen("ETag: \"");
     511           0 :                 const char *pszEndOfETag = strchr(pzETag, '"');
     512           0 :                 if (pszEndOfETag)
     513             :                 {
     514           0 :                     FileProp oFileProp;
     515           0 :                     oFileProp.eExists = EXIST_YES;
     516           0 :                     oFileProp.fileSize = m_nBufferOff;
     517           0 :                     oFileProp.bHasComputedFileSize = true;
     518           0 :                     oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag);
     519           0 :                     m_poFS->SetCachedFileProp(
     520           0 :                         m_poFS->GetURLFromFilename(m_osFilename.c_str())
     521             :                             .c_str(),
     522             :                         oFileProp);
     523             :                 }
     524             :             }
     525             :         }
     526             : 
     527           2 :         curl_easy_cleanup(hCurlHandle);
     528             :     } while (bRetry);
     529           4 :     return bSuccess;
     530             : }
     531             : 
     532             : }  // namespace cpl
     533             : 
     534             : //! @endcond
     535             : 
     536             : #endif  // HAVE_CURL

Generated by: LCOV version 1.14