LCOV - code coverage report
Current view: top level - curl - curl.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 48.6 % 72 35
Test Date: 2026-10-01 12:53:39 Functions: 57.1 % 7 4

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2019-2024 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it
       6              :   under the terms of the GNU General Public License as published
       7              :   by the Free Software Foundation; either version 3, or (at your
       8              :   option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful, but
      11              :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13              :   GNU General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not, see
      17              :   <http://www.gnu.org/licenses/>
      18              : */
      19              : /**
      20              :  * @file curl/curl.c
      21              :  * @brief Helper routines for interactions with libcurl
      22              :  * @author Christian Grothoff
      23              :  */
      24              : #include "taler/taler_curl_lib.h"
      25              : #include <curl/curl.h>
      26              : 
      27              : 
      28              : #if TALER_CURL_COMPRESS_BODIES
      29              : #include <zlib.h>
      30              : #endif
      31              : 
      32              : 
      33              : /* libcurl versions before 7.87.0 lack 'feature_names' in the version
      34              :    info, and versions before 7.66.0 lack 'quic_version' and HTTP/3
      35              :    altogether.  With those we simply never consider HTTP/3 usable. */
      36              : #if LIBCURL_VERSION_NUM >= 0x075700
      37              : 
      38              : /**
      39              :  * Check if feature @a name is in the @a vi
      40              :  *
      41              :  * @param vi version data to test
      42              :  * @param name feature to test for
      43              :  * @return true if feature is supported by curl
      44              :  */
      45              : static bool
      46            0 : has_feature (const curl_version_info_data *vi,
      47              :              const char *name)
      48              : {
      49            0 :   if (NULL == vi->feature_names)
      50            0 :     return false;
      51            0 :   for (const char *const *p = vi->feature_names; NULL != *p; p++)
      52            0 :     if (0 ==
      53            0 :         strcmp (*p,
      54              :                 name))
      55            0 :       return true;
      56            0 :   return false;
      57              : }
      58              : 
      59              : 
      60              : /**
      61              :  * Check if @a s is non-NULL and starts with @a prefix
      62              :  *
      63              :  * @param s string to check, can be NULL
      64              :  * @param prefix prefix to check for
      65              :  * @return true if @a s starts with @a prefix
      66              :  */
      67              : static bool
      68            0 : starts_with (const char *s,
      69              :              const char *prefix)
      70              : {
      71            0 :   return ( (NULL != s) &&
      72            0 :            (0 == strncmp (s,
      73              :                           prefix,
      74              :                           strlen (prefix)) ) );
      75              : }
      76              : 
      77              : 
      78              : #endif
      79              : 
      80              : 
      81              : /**
      82              :  * Check if using HTTP/3 is likely OK with our version of libcurl.
      83              :  *
      84              :  * @return true if HTTP/3 should be well-supported
      85              :  */
      86              : static bool
      87            0 : curl_http3_is_conservative_ok (void)
      88              : {
      89              : #if LIBCURL_VERSION_NUM >= 0x075700
      90              :   const curl_version_info_data *vi
      91            0 :     = curl_version_info (CURLVERSION_NOW);
      92              : 
      93            0 :   if (! has_feature (vi,
      94              :                      "HTTP3"))
      95            0 :     return false;
      96              : 
      97              :   /*
      98              :    * Require a QUIC backend we regard as production-ready.
      99              :    *
     100              :    * curl currently considers ngtcp2 non-experimental.
     101              :    * quiche is still experimental.
     102              :    */
     103            0 :   if (! starts_with (vi->quic_version,
     104              :                      "ngtcp2/"))
     105            0 :     return false;
     106              : 
     107              :   /*
     108              :    * Conservative policy: don't use HTTP/3 with GnuTLS.
     109              :    * (too many bugs in recent releases still)
     110              :    */
     111            0 :   if (starts_with (vi->ssl_version,
     112              :                    "GnuTLS/"))
     113            0 :     return false;
     114              : 
     115              :   /*
     116              :    * At this point the remaining ngtcp2 TLS configurations
     117              :    * are the OpenSSL family and wolfSSL.
     118              :    *
     119              :    * We deliberately whitelist them rather than assuming
     120              :    * every possible future TLS backend is safe.
     121              :    */
     122            0 :   if (starts_with (vi->ssl_version,
     123              :                    "OpenSSL/"))
     124            0 :     return true;
     125            0 :   if (starts_with (vi->ssl_version,
     126              :                    "wolfSSL/"))
     127            0 :     return true;
     128              : #endif
     129            0 :   return false;
     130              : }
     131              : 
     132              : 
     133              : void
     134          869 : TALER_curl_set_http_version (CURL *eh,
     135              :                              bool enable_http3)
     136              : {
     137              :   static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR;
     138          869 :   long version = CURL_HTTP_VERSION_1_1;
     139              : 
     140          869 :   if (enable_http3)
     141              :   {
     142            0 :     if (GNUNET_SYSERR == http3)
     143            0 :       http3 = curl_http3_is_conservative_ok ()
     144              :         ? GNUNET_YES
     145            0 :         : GNUNET_NO;
     146              : #ifdef CURL_HTTP_VERSION_3
     147            0 :     if (GNUNET_YES == http3)
     148              :     {
     149              :       /* Falls back to HTTP/2 or HTTP/1.1 if the server does
     150              :          not speak HTTP/3. */
     151            0 :       version = CURL_HTTP_VERSION_3;
     152              :     }
     153              :     else
     154              : #endif
     155              :     {
     156              : #ifdef CURL_HTTP_VERSION_2TLS
     157              :       /* HTTP/3 support of this libcurl build is spotty, at
     158              :          most use HTTP/2 (which falls back to HTTP/1.1). */
     159            0 :       version = CURL_HTTP_VERSION_2TLS;
     160              : #endif
     161              :     }
     162              :   }
     163          869 :   GNUNET_assert (CURLE_OK ==
     164              :                  curl_easy_setopt (eh,
     165              :                                    CURLOPT_HTTP_VERSION,
     166              :                                    version));
     167          869 : }
     168              : 
     169              : 
     170              : void
     171          869 : TALER_curl_set_secure_redirect_policy (CURL *eh,
     172              :                                        const char *url)
     173              : {
     174          869 :   GNUNET_assert (CURLE_OK ==
     175              :                  curl_easy_setopt (eh,
     176              :                                    CURLOPT_FOLLOWLOCATION,
     177              :                                    1L));
     178          869 :   GNUNET_assert ( (0 == strncasecmp (url,
     179              :                                      "https://",
     180              :                                      strlen ("https://"))) ||
     181              :                   (0 == strncasecmp (url,
     182              :                                      "http://",
     183              :                                      strlen ("http://"))) );
     184              : #ifdef CURLOPT_REDIR_PROTOCOLS_STR
     185              :   if (0 == strncasecmp (url,
     186              :                         "https://",
     187              :                         strlen ("https://")))
     188              :     GNUNET_assert (CURLE_OK ==
     189              :                    curl_easy_setopt (eh,
     190              :                                      CURLOPT_REDIR_PROTOCOLS_STR,
     191              :                                      "https"));
     192              :   else
     193              :     GNUNET_assert (CURLE_OK ==
     194              :                    curl_easy_setopt (eh,
     195              :                                      CURLOPT_REDIR_PROTOCOLS_STR,
     196              :                                      "http,https"));
     197              : #else
     198              : #ifdef CURLOPT_REDIR_PROTOCOLS
     199              :   if (0 == strncasecmp (url,
     200              :                         "https://",
     201              :                         strlen ("https://")))
     202              :     GNUNET_assert (CURLE_OK ==
     203              :                    curl_easy_setopt (eh,
     204              :                                      CURLOPT_REDIR_PROTOCOLS,
     205              :                                      CURLPROTO_HTTPS));
     206              :   else
     207              :     GNUNET_assert (CURLE_OK ==
     208              :                    curl_easy_setopt (eh,
     209              :                                      CURLOPT_REDIR_PROTOCOLS,
     210              :                                      CURLPROTO_HTTP | CURLPROTO_HTTPS));
     211              : #endif
     212              : #endif
     213              :   /* limit MAXREDIRS to 5 as a simple security measure against
     214              :      a potential infinite loop caused by a malicious target */
     215          869 :   GNUNET_assert (CURLE_OK ==
     216              :                  curl_easy_setopt (eh,
     217              :                                    CURLOPT_MAXREDIRS,
     218              :                                    5L));
     219          869 : }
     220              : 
     221              : 
     222              : enum GNUNET_GenericReturnValue
     223          818 : TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
     224              :                       CURL *eh,
     225              :                       const json_t *body)
     226              : {
     227              :   char *str;
     228              :   size_t slen;
     229              : 
     230          818 :   str = json_dumps (body,
     231              :                     JSON_COMPACT);
     232          818 :   if (NULL == str)
     233              :   {
     234            0 :     GNUNET_break (0);
     235            0 :     return GNUNET_SYSERR;
     236              :   }
     237          818 :   slen = strlen (str);
     238          818 :   if (TALER_CURL_COMPRESS_BODIES &&
     239          818 :       (! ctx->disable_compression) )
     240              :   {
     241              :     Bytef *cbuf;
     242              :     uLongf cbuf_size;
     243              :     int ret;
     244              : 
     245          818 :     cbuf_size = compressBound (slen);
     246          818 :     cbuf = GNUNET_malloc (cbuf_size);
     247          818 :     ret = compress (cbuf,
     248              :                     &cbuf_size,
     249              :                     (const Bytef *) str,
     250              :                     slen);
     251          818 :     if (Z_OK != ret)
     252              :     {
     253              :       /* compression failed!? */
     254            0 :       GNUNET_break (0);
     255            0 :       GNUNET_free (cbuf);
     256            0 :       free (str);
     257            0 :       return GNUNET_SYSERR;
     258              :     }
     259          818 :     free (str);
     260          818 :     slen = (size_t) cbuf_size;
     261          818 :     ctx->json_enc = (char *) cbuf;
     262          818 :     GNUNET_assert (
     263              :       NULL !=
     264              :       (ctx->headers = curl_slist_append (
     265              :          ctx->headers,
     266              :          "Content-Encoding: deflate")));
     267              :   }
     268              :   else
     269              :   {
     270            0 :     ctx->json_enc = str;
     271              :   }
     272          818 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     273              :               "Uploading JSON of %d bytes (%s)\n",
     274              :               (int) slen,
     275              :               (TALER_CURL_COMPRESS_BODIES &&
     276              :                (! ctx->disable_compression) )
     277              :               ? "compressed"
     278              :               : "uncompressed");
     279          818 :   GNUNET_assert (
     280              :     NULL !=
     281              :     (ctx->headers = curl_slist_append (
     282              :        ctx->headers,
     283              :        "Content-Type: application/json")));
     284              : 
     285          818 :   GNUNET_assert (CURLE_OK ==
     286              :                  curl_easy_setopt (eh,
     287              :                                    CURLOPT_POSTFIELDS,
     288              :                                    ctx->json_enc));
     289          818 :   GNUNET_assert (CURLE_OK ==
     290              :                  curl_easy_setopt (eh,
     291              :                                    CURLOPT_POSTFIELDSIZE,
     292              :                                    (long) slen));
     293          818 :   return GNUNET_OK;
     294              : }
     295              : 
     296              : 
     297              : void
     298          828 : TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx)
     299              : {
     300          828 :   curl_slist_free_all (ctx->headers);
     301          828 :   ctx->headers = NULL;
     302          828 :   GNUNET_free (ctx->json_enc);
     303          828 :   ctx->json_enc = NULL;
     304          828 : }
        

Generated by: LCOV version 2.0-1