LCOV - code coverage report
Current view: top level - frmts/wms - gdalhttp.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 133 157 84.7 %
Date: 2025-05-31 00:00:17 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WMS Client Driver
       4             :  * Purpose:  Implementation of Dataset and RasterBand classes for WMS
       5             :  *           and other similar services.
       6             :  * Author:   Adam Nowacki, nowak@xpam.de
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2007, Adam Nowacki
      10             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      11             :  * Copyright (c) 2016, Lucian Plesea
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "wmsdriver.h"
      17             : #include <algorithm>
      18             : 
      19          78 : static size_t WriteFunc(void *buffer, size_t count, size_t nmemb, void *req)
      20             : {
      21          78 :     WMSHTTPRequest *psRequest = reinterpret_cast<WMSHTTPRequest *>(req);
      22          78 :     size_t size = count * nmemb;
      23             : 
      24          78 :     if (size == 0)
      25           0 :         return 0;
      26             : 
      27          78 :     const size_t required_size = psRequest->nDataLen + size + 1;
      28          78 :     if (required_size > psRequest->nDataAlloc)
      29             :     {
      30          48 :         size_t new_size = required_size * 2;
      31          48 :         if (new_size < 512)
      32           0 :             new_size = 512;
      33          48 :         psRequest->nDataAlloc = new_size;
      34             :         GByte *pabyNewData = reinterpret_cast<GByte *>(
      35          48 :             VSIRealloc(psRequest->pabyData, new_size));
      36          48 :         if (pabyNewData == nullptr)
      37             :         {
      38           0 :             VSIFree(psRequest->pabyData);
      39           0 :             psRequest->pabyData = nullptr;
      40             :             psRequest->Error.Printf(
      41             :                 "Out of memory allocating %u bytes for HTTP data buffer.",
      42           0 :                 static_cast<unsigned int>(new_size));
      43           0 :             psRequest->nDataAlloc = 0;
      44           0 :             psRequest->nDataLen = 0;
      45           0 :             return 0;
      46             :         }
      47          48 :         psRequest->pabyData = pabyNewData;
      48             :     }
      49          78 :     memcpy(psRequest->pabyData + psRequest->nDataLen, buffer, size);
      50          78 :     psRequest->nDataLen += size;
      51          78 :     psRequest->pabyData[psRequest->nDataLen] = 0;
      52          78 :     return nmemb;
      53             : }
      54             : 
      55             : // Process curl errors
      56          37 : static void ProcessCurlErrors(CURLMsg *msg, WMSHTTPRequest *pasRequest,
      57             :                               int nRequestCount)
      58             : {
      59          37 :     CPLAssert(msg != nullptr);
      60          37 :     CPLAssert(msg->msg == CURLMSG_DONE);
      61             : 
      62             :     // in case of local file error: update status code
      63          37 :     if (msg->data.result == CURLE_FILE_COULDNT_READ_FILE)
      64             :     {
      65             :         // identify current request
      66           1 :         for (int current_req_i = 0; current_req_i < nRequestCount;
      67             :              ++current_req_i)
      68             :         {
      69           1 :             WMSHTTPRequest *const psRequest = &pasRequest[current_req_i];
      70           1 :             if (psRequest->m_curl_handle != msg->easy_handle)
      71           0 :                 continue;
      72             : 
      73             :             // sanity check for local files
      74           1 :             if (STARTS_WITH(psRequest->URL.c_str(), "file://"))
      75             :             {
      76           1 :                 psRequest->nStatus = 404;
      77           1 :                 break;
      78             :             }
      79             :         }
      80             :     }
      81          37 : }
      82             : 
      83             : // Builds a curl request
      84          47 : void WMSHTTPInitializeRequest(WMSHTTPRequest *psRequest)
      85             : {
      86          47 :     psRequest->nStatus = 0;
      87          47 :     psRequest->pabyData = nullptr;
      88          47 :     psRequest->nDataLen = 0;
      89          47 :     psRequest->nDataAlloc = 0;
      90             : 
      91          47 :     psRequest->m_curl_handle = curl_easy_init();
      92          47 :     if (psRequest->m_curl_handle == nullptr)
      93             :     {
      94           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      95             :                  "CPLHTTPInitializeRequest(): Unable to create CURL handle.");
      96           0 :         return;
      97             :     }
      98             : 
      99          47 :     if (!psRequest->Range.empty())
     100             :     {
     101           0 :         CPL_IGNORE_RET_VAL(curl_easy_setopt(
     102             :             psRequest->m_curl_handle, CURLOPT_RANGE, psRequest->Range.c_str()));
     103             :     }
     104             : 
     105          47 :     CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
     106             :                                         CURLOPT_WRITEDATA, psRequest));
     107          47 :     CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
     108             :                                         CURLOPT_WRITEFUNCTION, WriteFunc));
     109             : 
     110          47 :     psRequest->m_curl_error.resize(CURL_ERROR_SIZE + 1);
     111          47 :     CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
     112             :                                         CURLOPT_ERRORBUFFER,
     113             :                                         &psRequest->m_curl_error[0]));
     114             : 
     115          47 :     psRequest->m_headers = static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     116             :         psRequest->m_curl_handle, psRequest->URL.c_str(), psRequest->options));
     117          47 :     if (psRequest->m_headers != nullptr)
     118             :     {
     119           2 :         CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
     120             :                                             CURLOPT_HTTPHEADER,
     121             :                                             psRequest->m_headers));
     122             :     }
     123             : }
     124             : 
     125          59 : WMSHTTPRequest::~WMSHTTPRequest()
     126             : {
     127          59 :     if (m_curl_handle != nullptr)
     128          47 :         curl_easy_cleanup(m_curl_handle);
     129          59 :     if (m_headers != nullptr)
     130           2 :         curl_slist_free_all(m_headers);
     131          59 :     if (pabyData != nullptr)
     132          41 :         CPLFree(pabyData);
     133          59 : }
     134             : 
     135             : //
     136             : // Like CPLHTTPFetch, but multiple requests in parallel
     137             : // By default it uses 5 connections
     138             : //
     139          39 : CPLErr WMSHTTPFetchMulti(WMSHTTPRequest *pasRequest, int nRequestCount)
     140             : {
     141          39 :     CPLErr ret = CE_None;
     142          39 :     CURLM *curl_multi = nullptr;
     143             :     int max_conn;
     144             :     int i, conn_i;
     145             : 
     146          39 :     CPLAssert(nRequestCount >= 0);
     147          39 :     if (nRequestCount == 0)
     148           5 :         return CE_None;
     149             : 
     150             :     const char *max_conn_opt =
     151          34 :         CSLFetchNameValue(const_cast<char **>(pasRequest->options), "MAXCONN");
     152          34 :     max_conn =
     153          34 :         (max_conn_opt == nullptr) ? 5 : MAX(1, MIN(atoi(max_conn_opt), 1000));
     154             : 
     155             :     // If the first url starts with vsimem, assume all do and defer to
     156             :     // CPLHTTPFetch
     157          44 :     if (STARTS_WITH(pasRequest[0].URL.c_str(), "/vsimem/") &&
     158             :         /* Disabled by default for potential security issues */
     159          10 :         CPLTestBool(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")))
     160             :     {
     161          20 :         for (i = 0; i < nRequestCount; i++)
     162             :         {
     163             :             CPLHTTPResult *psResult =
     164          10 :                 CPLHTTPFetch(pasRequest[i].URL.c_str(),
     165          10 :                              const_cast<char **>(pasRequest[i].options));
     166          10 :             pasRequest[i].pabyData = psResult->pabyData;
     167          10 :             pasRequest[i].nDataLen = psResult->nDataLen;
     168          10 :             pasRequest[i].Error =
     169          10 :                 psResult->pszErrBuf ? psResult->pszErrBuf : "";
     170             :             // Conventions are different between this module and cpl_http...
     171          10 :             if (psResult->pszErrBuf != nullptr &&
     172           1 :                 strcmp(psResult->pszErrBuf, "HTTP error code : 404") == 0)
     173           1 :                 pasRequest[i].nStatus = 404;
     174             :             else
     175           9 :                 pasRequest[i].nStatus = 200;
     176          10 :             pasRequest[i].ContentType =
     177          10 :                 psResult->pszContentType ? psResult->pszContentType : "";
     178             :             // took ownership of content, we're done with the rest
     179          10 :             psResult->pabyData = nullptr;
     180          10 :             psResult->nDataLen = 0;
     181          10 :             CPLHTTPDestroyResult(psResult);
     182             :         }
     183          10 :         return CE_None;
     184             :     }
     185             : 
     186          24 :     curl_multi = curl_multi_init();
     187          24 :     if (curl_multi == nullptr)
     188             :     {
     189           0 :         CPLError(CE_Fatal, CPLE_AppDefined,
     190             :                  "CPLHTTPFetchMulti(): Unable to create CURL multi-handle.");
     191             :     }
     192             : 
     193             :     // add at most max_conn requests
     194          24 :     int torun = std::min(nRequestCount, max_conn);
     195          57 :     for (conn_i = 0; conn_i < torun; ++conn_i)
     196             :     {
     197          33 :         WMSHTTPRequest *const psRequest = &pasRequest[conn_i];
     198          33 :         CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1, nRequestCount,
     199          33 :                  pasRequest[conn_i].URL.c_str());
     200          33 :         curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
     201             :     }
     202             : 
     203          24 :     void *old_handler = CPLHTTPIgnoreSigPipe();
     204             :     int still_running;
     205         638 :     do
     206             :     {
     207             :         CURLMcode mc;
     208           0 :         do
     209             :         {
     210         662 :             mc = curl_multi_perform(curl_multi, &still_running);
     211         662 :         } while (CURLM_CALL_MULTI_PERFORM == mc);
     212             : 
     213             :         // Pick up messages, clean up the completed ones, add more
     214         662 :         int msgs_in_queue = 0;
     215           2 :         do
     216             :         {
     217         664 :             CURLMsg *m = curl_multi_info_read(curl_multi, &msgs_in_queue);
     218         664 :             if (m && (m->msg == CURLMSG_DONE))
     219             :             {
     220          37 :                 ProcessCurlErrors(m, pasRequest, nRequestCount);
     221             : 
     222          37 :                 curl_multi_remove_handle(curl_multi, m->easy_handle);
     223          37 :                 if (conn_i < nRequestCount)
     224             :                 {
     225           4 :                     auto psRequest = &pasRequest[conn_i];
     226           4 :                     CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1,
     227           4 :                              nRequestCount, pasRequest[conn_i].URL.c_str());
     228           4 :                     curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
     229           4 :                     ++conn_i;
     230           4 :                     still_running = 1;  // Still have request pending
     231             :                 }
     232             :             }
     233         664 :         } while (msgs_in_queue);
     234             : 
     235         662 :         if (CURLM_OK == mc)
     236             :         {
     237             :             int numfds;
     238         662 :             curl_multi_wait(curl_multi, nullptr, 0, 100, &numfds);
     239             :         }
     240         662 :     } while (still_running || conn_i != nRequestCount);
     241             : 
     242             :     // process any message still in queue
     243             :     CURLMsg *msg;
     244             :     int msgs_in_queue;
     245           0 :     do
     246             :     {
     247          24 :         msg = curl_multi_info_read(curl_multi, &msgs_in_queue);
     248          24 :         if (msg != nullptr)
     249             :         {
     250           0 :             if (msg->msg == CURLMSG_DONE)
     251             :             {
     252           0 :                 ProcessCurlErrors(msg, pasRequest, nRequestCount);
     253             :             }
     254             :         }
     255          24 :     } while (msg != nullptr);
     256             : 
     257          24 :     CPLHTTPRestoreSigPipeHandler(old_handler);
     258             : 
     259          24 :     if (conn_i != nRequestCount)
     260             :     {  // something gone really really wrong
     261             :         // oddly built libcurl or perhaps absence of network interface
     262           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     263             :                  "WMSHTTPFetchMulti(): conn_i != nRequestCount, this should "
     264             :                  "never happen ...");
     265           0 :         nRequestCount = conn_i;
     266           0 :         ret = CE_Failure;
     267             :     }
     268             : 
     269          61 :     for (i = 0; i < nRequestCount; ++i)
     270             :     {
     271          37 :         WMSHTTPRequest *const psRequest = &pasRequest[i];
     272             : 
     273             :         long response_code;
     274          37 :         curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_RESPONSE_CODE,
     275             :                           &response_code);
     276             :         // for local files, don't update the status code if one is already set
     277          38 :         if (!(psRequest->nStatus != 0 &&
     278           1 :               STARTS_WITH(psRequest->URL.c_str(), "file://")))
     279          36 :             psRequest->nStatus = static_cast<int>(response_code);
     280             : 
     281          37 :         char *content_type = nullptr;
     282          37 :         curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_CONTENT_TYPE,
     283             :                           &content_type);
     284          37 :         psRequest->ContentType = content_type ? content_type : "";
     285             : 
     286          37 :         if (psRequest->Error.empty())
     287          37 :             psRequest->Error = &psRequest->m_curl_error[0];
     288             : 
     289             :         /* In the case of a file:// URL, curl will return a status == 0, so if
     290             :          * there's no */
     291             :         /* error returned, patch the status code to be 200, as it would be for
     292             :          * http:// */
     293          37 :         if (psRequest->nStatus == 0 && psRequest->Error.empty() &&
     294           0 :             STARTS_WITH(psRequest->URL.c_str(), "file://"))
     295           0 :             psRequest->nStatus = 200;
     296             : 
     297             :         // If there is an error with no error message, use the content if it is
     298             :         // text
     299          72 :         if (psRequest->Error.empty() && psRequest->nStatus != 0 &&
     300          35 :             psRequest->nStatus != 200 &&
     301          72 :             strstr(psRequest->ContentType, "text") &&
     302           0 :             psRequest->pabyData != nullptr)
     303             :             psRequest->Error =
     304           0 :                 reinterpret_cast<const char *>(psRequest->pabyData);
     305             : 
     306          72 :         CPLDebug(
     307             :             "HTTP", "Request [%d] %s : status = %d, type = %s, error = %s", i,
     308             :             psRequest->URL.c_str(), psRequest->nStatus,
     309          37 :             !psRequest->ContentType.empty() ? psRequest->ContentType.c_str()
     310             :                                             : "(null)",
     311          37 :             !psRequest->Error.empty() ? psRequest->Error.c_str() : "(null)");
     312             : 
     313          37 :         curl_multi_remove_handle(curl_multi, pasRequest->m_curl_handle);
     314             :     }
     315             : 
     316          24 :     curl_multi_cleanup(curl_multi);
     317             : 
     318          24 :     return ret;
     319             : }

Generated by: LCOV version 1.14