exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

curl.c (8308B)


      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 has_feature (const curl_version_info_data *vi,
     47              const char *name)
     48 {
     49   if (NULL == vi->feature_names)
     50     return false;
     51   for (const char *const *p = vi->feature_names; NULL != *p; p++)
     52     if (0 ==
     53         strcmp (*p,
     54                 name))
     55       return true;
     56   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 starts_with (const char *s,
     69              const char *prefix)
     70 {
     71   return ( (NULL != s) &&
     72            (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 curl_http3_is_conservative_ok (void)
     88 {
     89 #if LIBCURL_VERSION_NUM >= 0x075700
     90   const curl_version_info_data *vi
     91     = curl_version_info (CURLVERSION_NOW);
     92 
     93   if (! has_feature (vi,
     94                      "HTTP3"))
     95     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   if (! starts_with (vi->quic_version,
    104                      "ngtcp2/"))
    105     return false;
    106 
    107   /*
    108    * Conservative policy: don't use HTTP/3 with GnuTLS.
    109    * (too many bugs in recent releases still)
    110    */
    111   if (starts_with (vi->ssl_version,
    112                    "GnuTLS/"))
    113     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   if (starts_with (vi->ssl_version,
    123                    "OpenSSL/"))
    124     return true;
    125   if (starts_with (vi->ssl_version,
    126                    "wolfSSL/"))
    127     return true;
    128 #endif
    129   return false;
    130 }
    131 
    132 
    133 void
    134 TALER_curl_set_http_version (CURL *eh,
    135                              bool enable_http3)
    136 {
    137   static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR;
    138   long version = CURL_HTTP_VERSION_1_1;
    139 
    140   if (enable_http3)
    141   {
    142     if (GNUNET_SYSERR == http3)
    143       http3 = curl_http3_is_conservative_ok ()
    144         ? GNUNET_YES
    145         : GNUNET_NO;
    146 #ifdef CURL_HTTP_VERSION_3
    147     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       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       version = CURL_HTTP_VERSION_2TLS;
    160 #endif
    161     }
    162   }
    163   GNUNET_assert (CURLE_OK ==
    164                  curl_easy_setopt (eh,
    165                                    CURLOPT_HTTP_VERSION,
    166                                    version));
    167 }
    168 
    169 
    170 void
    171 TALER_curl_set_secure_redirect_policy (CURL *eh,
    172                                        const char *url)
    173 {
    174   GNUNET_assert (CURLE_OK ==
    175                  curl_easy_setopt (eh,
    176                                    CURLOPT_FOLLOWLOCATION,
    177                                    1L));
    178   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   GNUNET_assert (CURLE_OK ==
    216                  curl_easy_setopt (eh,
    217                                    CURLOPT_MAXREDIRS,
    218                                    5L));
    219 }
    220 
    221 
    222 enum GNUNET_GenericReturnValue
    223 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   str = json_dumps (body,
    231                     JSON_COMPACT);
    232   if (NULL == str)
    233   {
    234     GNUNET_break (0);
    235     return GNUNET_SYSERR;
    236   }
    237   slen = strlen (str);
    238   if (TALER_CURL_COMPRESS_BODIES &&
    239       (! ctx->disable_compression) )
    240   {
    241     Bytef *cbuf;
    242     uLongf cbuf_size;
    243     int ret;
    244 
    245     cbuf_size = compressBound (slen);
    246     cbuf = GNUNET_malloc (cbuf_size);
    247     ret = compress (cbuf,
    248                     &cbuf_size,
    249                     (const Bytef *) str,
    250                     slen);
    251     if (Z_OK != ret)
    252     {
    253       /* compression failed!? */
    254       GNUNET_break (0);
    255       GNUNET_free (cbuf);
    256       free (str);
    257       return GNUNET_SYSERR;
    258     }
    259     free (str);
    260     slen = (size_t) cbuf_size;
    261     ctx->json_enc = (char *) cbuf;
    262     GNUNET_assert (
    263       NULL !=
    264       (ctx->headers = curl_slist_append (
    265          ctx->headers,
    266          "Content-Encoding: deflate")));
    267   }
    268   else
    269   {
    270     ctx->json_enc = str;
    271   }
    272   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   GNUNET_assert (
    280     NULL !=
    281     (ctx->headers = curl_slist_append (
    282        ctx->headers,
    283        "Content-Type: application/json")));
    284 
    285   GNUNET_assert (CURLE_OK ==
    286                  curl_easy_setopt (eh,
    287                                    CURLOPT_POSTFIELDS,
    288                                    ctx->json_enc));
    289   GNUNET_assert (CURLE_OK ==
    290                  curl_easy_setopt (eh,
    291                                    CURLOPT_POSTFIELDSIZE,
    292                                    (long) slen));
    293   return GNUNET_OK;
    294 }
    295 
    296 
    297 void
    298 TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx)
    299 {
    300   curl_slist_free_all (ctx->headers);
    301   ctx->headers = NULL;
    302   GNUNET_free (ctx->json_enc);
    303   ctx->json_enc = NULL;
    304 }