paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

paivana-httpd_reverse.c (46256B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2012-2014 GNUnet e.V.
      4   Copyright (C) 2018, 2025, 2026 Taler Systems SA
      5 
      6   GNU Taler is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU General Public License
      8   as published by the Free Software Foundation; either version
      9   3, or (at your option) any later version.
     10 
     11   GNU Taler is distributed in the hope that it will be useful, but
     12   WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU General Public License for more details.
     15 
     16   You should have received a copy of the GNU General Public
     17   License along with GNU Taler; see the file COPYING.  If not,
     18   write to the Free Software Foundation, Inc., 51 Franklin
     19   Street, Fifth Floor, Boston, MA 02110-1301, USA.
     20 */
     21 
     22 /**
     23  * @author Martin Schanzenbach
     24  * @author Christian Grothoff
     25  * @author Marcello Stanisci
     26  * @file src/backend/paivana-httpd_reverse.c
     27  * @brief Reverse proxy logic that just forwards the request
     28  */
     29 #include "platform.h"
     30 #include <curl/curl.h>
     31 #include <gnunet/gnunet_util_lib.h>
     32 #include <gnunet/gnunet_curl_lib.h>
     33 #include <taler/taler_mhd_lib.h>
     34 #include "paivana-httpd.h"
     35 #include "paivana-httpd_reverse.h"
     36 
     37 
     38 /**
     39  * Log curl error.
     40  *
     41  * @param level log level
     42  * @param fun name of curl_easy-function that gave the error
     43  * @param rc return code from curl
     44  */
     45 #define LOG_CURL_EASY(level,fun,rc) \
     46         GNUNET_log (level, _ ("%s failed at %s:%d: `%s'\n"), fun, __FILE__, \
     47                     __LINE__, \
     48                     curl_easy_strerror (rc))
     49 
     50 
     51 /**
     52  * State machine for HTTP requests (per request).  MHD invokes the
     53  * access handler multiple times per request — we use this enum to
     54  * know what each invocation is expected to do, and cascade between
     55  * states in a single invocation when no new data from MHD is
     56  * required.
     57  */
     58 enum RequestState
     59 {
     60   /**
     61    * Initial state.  MHD's first access-handler call (immediately
     62    * after parsing the request headers, `upload_data_size == 0`)
     63    * has not yet been observed.  In this state we can still queue
     64    * a final response before MHD auto-generates a 100 Continue and
     65    * the client invariably starts with the upload (if any).
     66    */
     67   REQUEST_STATE_HEADERS_PENDING,
     68 
     69   /**
     70    * We have accepted the request and are receiving the
     71    * body from the client.  Each body chunk is buffered into
     72    * `io_buf`; when MHD signals end-of-body (size == 0) we
     73    * advance to `CLIENT_UPLOAD_DONE`.
     74    */
     75   REQUEST_STATE_CLIENT_UPLOAD_STARTED,
     76 
     77   /**
     78    * We have decided to reject the upload (Content-Length exceeded
     79    * the buffer cap, or the actual body did) but MHD is still
     80    * delivering body chunks and refuses to let us queue a response
     81    * during `BODY_RECEIVING`.  Silently drop the bytes here until
     82    * MHD gets back to a state that allows `MHD_queue_response`.
     83    */
     84   REQUEST_STATE_REJECT_UPLOAD_DRAIN,
     85 
     86   /**
     87    * Ready to queue the 413 Content Too Large response.
     88    */
     89   REQUEST_STATE_REJECT_UPLOAD,
     90 
     91   /**
     92    * Client body fully received; next step is to initialize the
     93    * curl handle and start forwarding to the upstream.
     94    */
     95   REQUEST_STATE_CLIENT_UPLOAD_DONE,
     96 
     97   /**
     98    * We have started uploading data to the proxied service.
     99    * MHD handling will be suspended.
    100    */
    101   REQUEST_STATE_PROXY_UPLOAD_STARTED,
    102 
    103   /**
    104    * We're done uploading data to the proxied service.
    105    * MHD handling should remain suspended.
    106    */
    107   REQUEST_STATE_PROXY_UPLOAD_DONE,
    108 
    109   /**
    110    * We've finished uploading data via CURL and can now download.
    111    * MHD handling should remain suspended.
    112    */
    113   REQUEST_STATE_PROXY_DOWNLOAD_STARTED,
    114 
    115   /**
    116    * We've finished receiving download data from cURL; ready to
    117    * build and queue the final response to the client.
    118    */
    119   REQUEST_STATE_PROXY_DOWNLOAD_DONE,
    120 
    121   /**
    122    * We've failed to download data from cURL; ready to
    123    * build and queue the final response to the client.
    124    */
    125   REQUEST_STATE_PROXY_DOWNLOAD_FAILED
    126 };
    127 
    128 
    129 /**
    130  * A header list
    131  */
    132 struct HttpResponseHeader
    133 {
    134   /**
    135    * DLL
    136    */
    137   struct HttpResponseHeader *next;
    138 
    139   /**
    140    * DLL
    141    */
    142   struct HttpResponseHeader *prev;
    143 
    144   /**
    145    * Header type
    146    */
    147   char *type;
    148 
    149   /**
    150    * Header value
    151    */
    152   char *value;
    153 };
    154 
    155 
    156 /**
    157  * A structure for socks requests
    158  */
    159 struct HttpRequest
    160 {
    161 
    162   /**
    163    * Kept in DLL.
    164    */
    165   struct HttpRequest *prev;
    166 
    167   /**
    168    * Kept in DLL.
    169    */
    170   struct HttpRequest *next;
    171 
    172   /**
    173    * MHD request that triggered us.
    174    */
    175   struct MHD_Connection *con;
    176 
    177   /**
    178    * Client socket read task
    179    */
    180   struct GNUNET_SCHEDULER_Task *rtask;
    181 
    182   /**
    183    * Client socket write task
    184    */
    185   struct GNUNET_SCHEDULER_Task *wtask;
    186 
    187   /**
    188    * MHD response object for this request.
    189    */
    190   struct MHD_Response *response;
    191 
    192   /**
    193    * The URL to fetch
    194    */
    195   char *url;
    196 
    197   /**
    198    * Handle to cURL
    199    */
    200   CURL *curl;
    201 
    202   /**
    203    * Job handle for the CURL request in our event loop.
    204    */
    205   struct GNUNET_CURL_Job *job;
    206 
    207   /**
    208    * HTTP request headers for the curl request.
    209    */
    210   struct curl_slist *headers;
    211 
    212   /**
    213    * Headers from response
    214    */
    215   struct HttpResponseHeader *header_head;
    216 
    217   /**
    218    * Headers from response
    219    */
    220   struct HttpResponseHeader *header_tail;
    221 
    222   /**
    223    * Buffer we use for moving data between MHD and
    224    * curl (in both directions).
    225    */
    226   char *io_buf;
    227 
    228   /**
    229    * Number of bytes already in the IO buffer.
    230    */
    231   size_t io_len;
    232 
    233   /**
    234    * Number of bytes allocated for the IO buffer.
    235    */
    236   unsigned int io_size;
    237 
    238   /**
    239    * HTTP response code to give to MHD for the response.
    240    */
    241   unsigned int response_code;
    242 
    243   /**
    244    * Request processing state machine.
    245    */
    246   enum RequestState state;
    247 
    248   /**
    249    * Did we suspend MHD processing?
    250    */
    251   enum GNUNET_GenericReturnValue suspended;
    252 
    253   /**
    254    * Concatenated value of the client's Via header(s), if any.  Per
    255    * RFC 9110 §7.6.3 a proxy must *append* its own entry to this
    256    * list, not replace it; we capture the inbound value here before
    257    * iterating over the other headers and emit our appended Via when
    258    * building the curl request.
    259    */
    260   char *client_via;
    261 
    262   /**
    263    * Concatenated value of the client's Connection header(s), if
    264    * any.  Per RFC 9110 §7.6.1 this lists additional header names
    265    * that are connection-specific and must not be forwarded; we
    266    * consult this list in `con_val_iter` to filter such headers out
    267    * of the upstream request.
    268    */
    269   char *client_connection;
    270 };
    271 
    272 
    273 /**
    274  * DLL of active HTTP requests.
    275  */
    276 static struct HttpRequest *hr_head;
    277 
    278 /**
    279  * DLL of active HTTP requests.
    280  */
    281 static struct HttpRequest *hr_tail;
    282 
    283 /**
    284  * Response we return on cURL failures.
    285  */
    286 static struct MHD_Response *curl_failure_response;
    287 
    288 /**
    289  * Response we return if the HTTP method is not allowed.
    290  */
    291 static struct MHD_Response *method_failure_response;
    292 
    293 /**
    294  * Response we return if the upload is too big.
    295  */
    296 static struct MHD_Response *upload_failure_response;
    297 
    298 /**
    299  * Response we return if we encountered an internal failure.
    300  */
    301 static struct MHD_Response *internal_failure_response;
    302 
    303 
    304 /**
    305  * Create HTML response using @a body
    306  *
    307  * @param body UTF-8 encoded 0-terminated body to use
    308  * @return NULL on error
    309  */
    310 static struct MHD_Response *
    311 make_html_response (const char *body)
    312 {
    313   struct MHD_Response *ret;
    314 
    315   ret = MHD_create_response_from_buffer_static (strlen (body),
    316                                                 body);
    317   if (NULL == ret)
    318   {
    319     GNUNET_break (0);
    320     return NULL;
    321   }
    322   GNUNET_break (MHD_YES ==
    323                 MHD_add_response_header (ret,
    324                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    325                                          "text/html; charset=utf-8"));
    326   return ret;
    327 }
    328 
    329 
    330 bool
    331 PAIVANA_HTTPD_reverse_init (void)
    332 {
    333   static const char *curl_failure_body =
    334     "<!DOCTYPE html>\n"
    335     "<html><head><title>Bad Gateway</title></head>"
    336     "<body><h1>502 Bad Gateway</h1>"
    337     "<p>The upstream server could not be reached.</p>"
    338     "</body></html>\n";
    339   static const char *internal_failure_body =
    340     "<!DOCTYPE html>\n"
    341     "<html><head><title>Internal server failure</title></head>"
    342     "<body><h1>500 Internal Server Failure</h1>"
    343     "<p>The server experienced an internal failure.</p>"
    344     "</body></html>\n";
    345   static const char *upload_failure_body =
    346     "<!DOCTYPE html>\n"
    347     "<html><head><title>Content too large</title></head>"
    348     "<body><h1>413 Content too large</h1>"
    349     "<p>The size of the body exceeds the limit.</p>"
    350     "</body></html>\n";
    351   static const char *method_failure_body =
    352     "<!DOCTYPE html>\n"
    353     "<html><head><title>Method not allowed</title></head>"
    354     "<body><h1>405 Method not allowed</h1>"
    355     "<p>The HTTP method specified is not allowed.</p>"
    356     "</body></html>\n";
    357 
    358   curl_failure_response
    359     = make_html_response (curl_failure_body);
    360   if (NULL == curl_failure_response)
    361   {
    362     GNUNET_break (0);
    363     return false;
    364   }
    365   upload_failure_response
    366     = make_html_response (upload_failure_body);
    367   if (NULL == upload_failure_response)
    368   {
    369     GNUNET_break (0);
    370     return false;
    371   }
    372   method_failure_response
    373     = make_html_response (method_failure_body);
    374   if (NULL == method_failure_response)
    375   {
    376     GNUNET_break (0);
    377     return false;
    378   }
    379   internal_failure_response
    380     = make_html_response (internal_failure_body);
    381   if (NULL == internal_failure_response)
    382   {
    383     GNUNET_break (0);
    384     return false;
    385   }
    386   return true;
    387 }
    388 
    389 
    390 void
    391 PAIVANA_HTTPD_reverse_shutdown (void)
    392 {
    393   for (struct HttpRequest *hr = hr_head;
    394        NULL != hr;
    395        hr = hr->next)
    396   {
    397     if (GNUNET_YES == hr->suspended)
    398     {
    399       hr->suspended = GNUNET_NO;
    400       MHD_resume_connection (hr->con);
    401     }
    402   }
    403   if (NULL != curl_failure_response)
    404   {
    405     MHD_destroy_response (curl_failure_response);
    406     curl_failure_response = NULL;
    407   }
    408   if (NULL != upload_failure_response)
    409   {
    410     MHD_destroy_response (upload_failure_response);
    411     upload_failure_response = NULL;
    412   }
    413   if (NULL != method_failure_response)
    414   {
    415     MHD_destroy_response (method_failure_response);
    416     method_failure_response = NULL;
    417   }
    418   if (NULL != internal_failure_response)
    419   {
    420     MHD_destroy_response (internal_failure_response);
    421     internal_failure_response = NULL;
    422   }
    423 }
    424 
    425 
    426 /**
    427  * Is @a name a hop-by-hop HTTP header name that a proxy must not
    428  * forward (RFC 9110 section 7.6.1 and RFC 7230, section 6.1).
    429  * The client may name *additional* hop-by-hop headers in its
    430  * Connection header; those are handled separately in
    431  * `connection_lists_header()`.
    432  *
    433  * @param name header field name
    434  * @return true if @a name is a hop-by-hop header
    435  */
    436 static bool
    437 is_hop_by_hop_header (const char *name)
    438 {
    439   static const char *const hop_headers[] = {
    440     MHD_HTTP_HEADER_CONNECTION,
    441     MHD_HTTP_HEADER_KEEP_ALIVE,
    442     MHD_HTTP_HEADER_PROXY_AUTHENTICATE,
    443     MHD_HTTP_HEADER_PROXY_AUTHORIZATION,
    444     MHD_HTTP_HEADER_TE,
    445     MHD_HTTP_HEADER_TRAILER,
    446     MHD_HTTP_HEADER_TRANSFER_ENCODING,
    447     MHD_HTTP_HEADER_UPGRADE,
    448     NULL
    449   };
    450 
    451   for (unsigned int i = 0; NULL != hop_headers[i]; i++)
    452     if (0 == strcasecmp (name,
    453                          hop_headers[i]))
    454       return true;
    455   return false;
    456 }
    457 
    458 
    459 /**
    460  * Return true if @a name appears (case-insensitively) as a
    461  * comma-separated element of @a list.  Used to honor RFC 9110
    462  * §7.6.1: the client's Connection header lists additional
    463  * hop-by-hop header names that must not be forwarded.
    464  *
    465  * @param list comma-separated list of header names, may be NULL
    466  * @param name header name to look for
    467  * @return true if @a list names @a name
    468  */
    469 static bool
    470 connection_lists_header (const char *list,
    471                          const char *name)
    472 {
    473   size_t namelen;
    474   const char *p;
    475 
    476   if (NULL == list)
    477     return false;
    478   namelen = strlen (name);
    479   p = list;
    480   while ('\0' != *p)
    481   {
    482     const char *comma;
    483     size_t len;
    484 
    485     while ( (' ' == *p) ||
    486             ('\t' == *p) ||
    487             (',' == *p) )
    488       p++;
    489     if ('\0' == *p)
    490       break;
    491     comma = strchr (p, ',');
    492     len = (NULL == comma) ? strlen (p) : (size_t) (comma - p);
    493     while ( (len > 0) &&
    494             ( (' ' == p[len - 1]) ||
    495               ('\t' == p[len - 1]) ) )
    496       len--;
    497     if ( (len == namelen) &&
    498          (0 == strncasecmp (p, name, namelen)) )
    499       return true;
    500     if (NULL == comma)
    501       break;
    502     p = comma + 1;
    503   }
    504   return false;
    505 }
    506 
    507 
    508 /**
    509  * Pre-iteration collector: records the client's Via and Connection
    510  * headers on @a hr so the subsequent forwarding pass can append to
    511  * Via (RFC 9110 §7.6.3) and honor the hop-by-hop names listed in
    512  * Connection (RFC 9110 §7.6.1).  Multiple values for the same
    513  * header are joined with ", ", matching the list-header combining
    514  * rule.
    515  *
    516  * @param cls our `struct HttpRequest *`
    517  * @param kind value kind (unused)
    518  * @param key header field name
    519  * @param value header field value
    520  * @return #MHD_YES to continue iteration
    521  */
    522 static enum MHD_Result
    523 collect_proxy_state (void *cls,
    524                      enum MHD_ValueKind kind,
    525                      const char *key,
    526                      const char *value)
    527 {
    528   struct HttpRequest *hr = cls;
    529   char **target;
    530 
    531   (void) kind;
    532   if (NULL == value)
    533     return MHD_YES;
    534   if (0 == strcasecmp (MHD_HTTP_HEADER_VIA,
    535                        key))
    536     target = &hr->client_via;
    537   else if (0 == strcasecmp (MHD_HTTP_HEADER_CONNECTION,
    538                             key))
    539     target = &hr->client_connection;
    540   else
    541     return MHD_YES;
    542   if (NULL == *target)
    543   {
    544     *target = GNUNET_strdup (value);
    545   }
    546   else
    547   {
    548     char *combined;
    549 
    550     GNUNET_asprintf (&combined,
    551                      "%s, %s",
    552                      *target,
    553                      value);
    554     GNUNET_free (*target);
    555     *target = combined;
    556   }
    557   return MHD_YES;
    558 }
    559 
    560 
    561 /* *************** HTTP handling with cURL ***************** */
    562 
    563 
    564 /**
    565  * Transform _one_ CURL header (gotten from the request) into
    566  * MHD format and put it into the response headers list; mostly
    567  * copies the headers, but makes special adjustments based on
    568  * control requests.
    569  *
    570  * @param buffer curl buffer with a single
    571  *        line of header data; not 0-terminated!
    572  * @param size curl blocksize
    573  * @param nmemb curl blocknumber
    574  * @param cls our `struct HttpRequest *`
    575  * @return size of processed bytes
    576  */
    577 static size_t
    578 curl_check_hdr (void *buffer,
    579                 size_t size,
    580                 size_t nmemb,
    581                 void *cls)
    582 {
    583   struct HttpRequest *hr = cls;
    584   struct HttpResponseHeader *header;
    585   size_t bytes = size * nmemb;
    586   char *ndup;
    587   const char *hdr_type;
    588   char *hdr_val;
    589   char *tok;
    590 
    591   /* Raw line is not guaranteed to be null-terminated.  */
    592   ndup = GNUNET_malloc (bytes + 1);
    593   memcpy (ndup,
    594           buffer,
    595           bytes);
    596   ndup[bytes] = '\0';
    597   hdr_type = strtok (ndup, ":");
    598   if (NULL == hdr_type)
    599   {
    600     GNUNET_free (ndup);
    601     return bytes;
    602   }
    603   hdr_val = strtok (NULL, "\n");
    604   if (NULL == hdr_val)
    605   {
    606     GNUNET_free (ndup);
    607     return bytes;
    608   }
    609   while (' ' == *hdr_val)
    610     hdr_val++;
    611 
    612   /* MHD does not allow certain characters in values,
    613    * remove those, plus those could alter strings matching.  */
    614   if (NULL != (tok = strchr (hdr_val, '\n')))
    615     *tok = '\0';
    616   if (NULL != (tok = strchr (hdr_val, '\r')))
    617     *tok = '\0';
    618   if (NULL != (tok = strchr (hdr_val, '\t')))
    619     *tok = '\0';
    620   PAIVANA_LOG_DEBUG ("Parsed line: '%s: %s'\n",
    621                      hdr_type,
    622                      hdr_val);
    623   /* Skip "Content-length:" header as it will be wrong, given
    624      that we are man-in-the-middling the connection */
    625   if (0 == strcasecmp (hdr_type,
    626                        MHD_HTTP_HEADER_CONTENT_LENGTH))
    627   {
    628     GNUNET_free (ndup);
    629     return bytes;
    630   }
    631   /* Skip hop-by-hop headers. In particular Transfer-Encoding
    632      must not leak through: libcurl has already dechunked the
    633      body for us and MHD will decide whether to re-chunk. */
    634   if (is_hop_by_hop_header (hdr_type))
    635   {
    636     GNUNET_free (ndup);
    637     return bytes;
    638   }
    639   if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
    640   {
    641     header = GNUNET_new (struct HttpResponseHeader);
    642     header->type = GNUNET_strdup (hdr_type);
    643     header->value = GNUNET_strdup (hdr_val);
    644     GNUNET_CONTAINER_DLL_insert (hr->header_head,
    645                                  hr->header_tail,
    646                                  header);
    647   }
    648   GNUNET_free (ndup);
    649   return bytes;
    650 }
    651 
    652 
    653 /**
    654  * Handle response payload data from cURL.
    655  *
    656  * @param cls our `struct HttpRequest *`
    657  * @param response_code HTTP status returned by the server
    658  * @param body response body
    659  * @param body_size number of blocks of data
    660  */
    661 static void
    662 curl_download_cb (void *cls,
    663                   long response_code,
    664                   const void *body,
    665                   size_t body_size)
    666 {
    667   struct HttpRequest *hr = cls;
    668 
    669   hr->job = NULL;
    670   if (GNUNET_YES == hr->suspended)
    671   {
    672     hr->suspended = GNUNET_NO;
    673     MHD_resume_connection (hr->con);
    674     TALER_MHD_daemon_trigger ();
    675   }
    676   if (0 == response_code)
    677   {
    678     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    679                 "Failed to receive response from HTTP server\n");
    680     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_FAILED;
    681     return;
    682   }
    683   hr->response = MHD_create_response_from_buffer_copy (body_size,
    684                                                        body);
    685   if (NULL == hr->response)
    686   {
    687     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    688                 "Could not allocate memory for response of %llu bytes\n",
    689                 (unsigned long long) body_size);
    690     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_FAILED;
    691     return;
    692   }
    693   hr->response_code = response_code;
    694   for (struct HttpResponseHeader *header = hr->header_head;
    695        NULL != header;
    696        header = header->next)
    697   {
    698     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    699                 "Adding MHD response header %s->%s\n",
    700                 header->type,
    701                 header->value);
    702     GNUNET_break (MHD_YES ==
    703                   MHD_add_response_header (hr->response,
    704                                            header->type,
    705                                            header->value));
    706   }
    707   if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ||
    708        ( (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) &&
    709          (0 == hr->io_len) ) )
    710   {
    711     /* Either the request body was empty (CURLOPT_POSTFIELDSIZE = 0,
    712        so libcurl never called our read callback) or the upload
    713        already drained but we have not yet entered upload_cb to
    714        flip the state — either way, the upload is complete and we
    715        can move straight on to consuming the response. */
    716     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_DONE;
    717   }
    718 }
    719 
    720 
    721 /**
    722  * cURL callback for uploaded (PUT/POST) data.
    723  * Copies from our `io_buf` to make it available to cURL.
    724  *
    725  * @param buf where to write the data
    726  * @param size number of bytes per member
    727  * @param nmemb number of members available in @a buf
    728  * @param cls our `struct HttpRequest` that generated the data
    729  * @return number of bytes copied to @a buf
    730  */
    731 static size_t
    732 curl_upload_cb (void *buf,
    733                 size_t size,
    734                 size_t nmemb,
    735                 void *cls)
    736 {
    737   struct HttpRequest *hr = cls;
    738   size_t len = size * nmemb;
    739   size_t to_copy;
    740 
    741   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    742               "Upload cb is working...\n");
    743 
    744   if (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state)
    745   {
    746     /* Should not happen: the curl task only declares the transfer
    747        done after consuming POSTFIELDSIZE bytes from us. */
    748     GNUNET_break (0);
    749     return CURL_READFUNC_ABORT;
    750   }
    751 
    752   if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state)
    753   {
    754     /* We have already drained io_buf and flipped the state in a
    755        previous call.  With CURLOPT_POSTFIELDSIZE set, libcurl
    756        should not ask for more bytes — but if it does, signal a
    757        clean end-of-upload rather than aborting the transfer. */
    758     GNUNET_assert (0 == hr->io_len);
    759     return 0;
    760   }
    761 
    762   GNUNET_assert (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state);
    763   if (0 == hr->io_len)
    764   {
    765     /* Should not happen: start_curl_request runs only after the
    766        client body is fully buffered, so io_len begins > 0 (or 0
    767        for an empty body, which CURLOPT_POSTFIELDSIZE = 0 keeps
    768        libcurl from asking about). */
    769     GNUNET_break (0);
    770     return CURL_READFUNC_PAUSE;
    771   }
    772 
    773   to_copy = GNUNET_MIN (hr->io_len,
    774                         len);
    775   GNUNET_memcpy (buf,
    776                  hr->io_buf,
    777                  to_copy);
    778   /* shift remaining data back to the beginning of the buffer.  */
    779   memmove (hr->io_buf,
    780            &hr->io_buf[to_copy],
    781            hr->io_len - to_copy);
    782   hr->io_len -= to_copy;
    783   if (0 == hr->io_len)
    784   {
    785     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
    786     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    787                 "Completed CURL UPLOAD\n");
    788   }
    789   return to_copy;
    790 }
    791 
    792 
    793 /* ************** helper functions ************* */
    794 
    795 /**
    796  * Extract the hostname from a complete URL.
    797  *
    798  * @param url full fledged URL
    799  * @return pointer to the 0-terminated hostname, to be freed
    800  *         by the caller.
    801  */
    802 static char *
    803 build_host_header (const char *url)
    804 {
    805   #define MARKER "://"
    806 
    807   char *header;
    808   char *end;
    809   char *hostname;
    810   char *dup = GNUNET_strdup (url);
    811 
    812   hostname = strstr (dup,
    813                      MARKER);
    814   if (NULL == hostname)
    815   {
    816     GNUNET_free (dup);
    817     return NULL;
    818   }
    819   hostname += 3;
    820   end = strchrnul (hostname, '/');
    821   *end = '\0';
    822   GNUNET_asprintf (&header,
    823                    "Host: %s",
    824                    hostname);
    825   GNUNET_free (dup);
    826   return header;
    827 }
    828 
    829 
    830 /* ************** main loop of cURL interaction ************* */
    831 
    832 
    833 /**
    834  * "Filter" function that translates MHD request headers to
    835  * cURL's.
    836  *
    837  * @param cls our `struct HttpRequest`
    838  * @param kind value kind
    839  * @param key field key
    840  * @param value field value
    841  * @return #MHD_YES to continue to iterate
    842  */
    843 static enum MHD_Result
    844 con_val_iter (void *cls,
    845               enum MHD_ValueKind kind,
    846               const char *key,
    847               const char *value)
    848 {
    849   struct HttpRequest *hr = cls;
    850   char *hdr;
    851 
    852   (void) kind;
    853   if (NULL == value)
    854     return MHD_YES;
    855   if (0 == strcasecmp (MHD_HTTP_HEADER_HOST,
    856                        key))
    857   {
    858     /* We don't take the host header as given in the request.
    859      * We'll instead put the proxied service's hostname in it*/
    860     return MHD_YES;
    861   }
    862   if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
    863                        key))
    864   {
    865     /* libcurl sets Content-Length itself from CURLOPT_POSTFIELDSIZE
    866        / CURLOPT_INFILESIZE. */
    867     return MHD_YES;
    868   }
    869   if (0 == strcasecmp (MHD_HTTP_HEADER_EXPECT,
    870                        key))
    871   {
    872     /* libcurl manages Expect: 100-continue on its own. */
    873     return MHD_YES;
    874   }
    875   if (is_hop_by_hop_header (key))
    876     return MHD_YES;
    877   /* RFC 9110 §7.6.1: suppress any header named by the client's
    878      Connection list (additional hop-by-hop headers). */
    879   if (connection_lists_header (hr->client_connection,
    880                                key))
    881     return MHD_YES;
    882   if ( (0 == strncasecmp ("X-Forwarded-",
    883                           key,
    884                           strlen ("X-Forwarded-"))) ||
    885        (0 == strcasecmp (MHD_HTTP_HEADER_FORWARDED,
    886                          key)) )
    887   {
    888     /* We will replace these with our own below. */
    889     return MHD_YES;
    890   }
    891   if (0 == strcasecmp (MHD_HTTP_HEADER_VIA,
    892                        key))
    893   {
    894     /* The client's Via was captured in `collect_proxy_state` and
    895        will be emitted below with our own entry appended (RFC 9110
    896        §7.6.3).  Drop the raw header here so we don't forward it
    897        twice. */
    898     return MHD_YES;
    899   }
    900   GNUNET_asprintf (&hdr,
    901                    "%s: %s",
    902                    key,
    903                    value);
    904   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    905               "Adding header `%s' to HTTP request\n",
    906               hdr);
    907   hr->headers = curl_slist_append (hr->headers,
    908                                    hdr);
    909   GNUNET_free (hdr);
    910   return MHD_YES;
    911 }
    912 
    913 
    914 struct HttpRequest *
    915 PAIVANA_HTTPD_reverse_create (struct MHD_Connection *connection,
    916                               const char *url)
    917 {
    918   struct HttpRequest *hr;
    919 
    920   hr = GNUNET_new (struct HttpRequest);
    921   hr->state = REQUEST_STATE_HEADERS_PENDING;
    922   hr->con = connection;
    923   hr->url = GNUNET_strdup (url);
    924   GNUNET_CONTAINER_DLL_insert (hr_head,
    925                                hr_tail,
    926                                hr);
    927   return hr;
    928 }
    929 
    930 
    931 void
    932 PAIVANA_HTTPD_reverse_cleanup (struct HttpRequest *hr)
    933 {
    934   struct HttpResponseHeader *header;
    935 
    936   if (NULL != hr->curl)
    937   {
    938     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    939                 "Resetting cURL handle\n");
    940     curl_easy_cleanup (hr->curl);
    941     hr->curl = NULL;
    942     hr->io_len = 0;
    943   }
    944   if (NULL != hr->job)
    945   {
    946     GNUNET_CURL_job_cancel (hr->job);
    947     hr->job = NULL;
    948   }
    949   if (NULL != hr->headers)
    950   {
    951     curl_slist_free_all (hr->headers);
    952     hr->headers = NULL;
    953   }
    954   if ( (NULL != hr->response) &&
    955        (curl_failure_response != hr->response) &&
    956        (method_failure_response != hr->response) &&
    957        (upload_failure_response != hr->response) &&
    958        (internal_failure_response != hr->response) )
    959     /* Destroy non-error responses... (?) */
    960     MHD_destroy_response (hr->response);
    961 
    962   for (header = hr->header_head;
    963        header != NULL;
    964        header = hr->header_head)
    965   {
    966     GNUNET_CONTAINER_DLL_remove (hr->header_head,
    967                                  hr->header_tail,
    968                                  header);
    969     GNUNET_free (header->type);
    970     GNUNET_free (header->value);
    971     GNUNET_free (header);
    972   }
    973   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    974               "Proxying of '%s' completely done\n",
    975               hr->url);
    976 
    977   GNUNET_free (hr->url);
    978   GNUNET_free (hr->io_buf);
    979   GNUNET_free (hr->client_via);
    980   GNUNET_free (hr->client_connection);
    981   GNUNET_CONTAINER_DLL_remove (hr_head,
    982                                hr_tail,
    983                                hr);
    984   GNUNET_free (hr);
    985 }
    986 
    987 
    988 /**
    989  * Parse the client's Content-Length header (if any) and decide
    990  * whether the declared body size fits within our per-request
    991  * buffer cap.  Returns false if the header is present, well-formed,
    992  * and exceeds `PH_request_buffer_max`; the caller must then
    993  * transition to the reject path to suppress the implicit 100
    994  * Continue and avoid buffering a body we would only throw away.
    995  *
    996  * @param con MHD connection to look up the header on
    997  * @return true if OK to continue accepting the body
    998  */
    999 static bool
   1000 content_length_ok (struct MHD_Connection *con)
   1001 {
   1002   const char *cl_str;
   1003   char *endptr;
   1004   unsigned long long cl;
   1005 
   1006   cl_str = MHD_lookup_connection_value (con,
   1007                                         MHD_HEADER_KIND,
   1008                                         MHD_HTTP_HEADER_CONTENT_LENGTH);
   1009   if (NULL == cl_str)
   1010     return true;
   1011   errno = 0;
   1012   cl = strtoull (cl_str,
   1013                  &endptr,
   1014                  10);
   1015   if ( (0 != errno) ||
   1016        ('\0' != *endptr) )
   1017     return true; /* unparseable — defer judgment to the drain path */
   1018   if (cl <= PH_request_buffer_max)
   1019     return true;
   1020   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1021               "Rejecting upload: Content-Length %llu exceeds %llu byte limit\n",
   1022               cl,
   1023               PH_request_buffer_max);
   1024   return false;
   1025 }
   1026 
   1027 
   1028 /**
   1029  * Append an upload chunk from the client into `hr->io_buf`, growing
   1030  * the buffer as needed.  Returns false when the chunk would push
   1031  * the total past `PH_request_buffer_max`; the caller must then
   1032  * transition to the drain path (MHD disallows queuing a response
   1033  * while `BODY_RECEIVING`, so we silently discard the rest and
   1034  * queue the 413 once MHD calls us back at `FULL_REQ_RECEIVED`).
   1035  *
   1036  * @param[in,out] hr request we are handling
   1037  * @param upload_data_size number of bytes in @a upload_data
   1038  * @param upload_data data being uploaded
   1039  * @return true on success, false if the upload is too big
   1040  */
   1041 static bool
   1042 buffer_upload_chunk (struct HttpRequest *hr,
   1043                      size_t upload_data_size,
   1044                      const char upload_data[static upload_data_size])
   1045 {
   1046   uint64_t min_size;
   1047 
   1048   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1049               "Processing %u bytes UPLOAD\n",
   1050               (unsigned int) upload_data_size);
   1051   min_size = ((uint64_t) hr->io_len) + ((uint64_t) upload_data_size);
   1052   if ( (min_size < hr->io_len /* integer overflow */) ||
   1053        (min_size > PH_request_buffer_max /* overflows config limit */) ||
   1054        (min_size > GNUNET_MAX_MALLOC_CHECKED) ||
   1055        (min_size > UINT_MAX /* would overflow later */) )
   1056   {
   1057     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1058                 "Upload exceeds limit, rejecting\n");
   1059     return false;
   1060   }
   1061   if (hr->io_size - hr->io_len < upload_data_size)
   1062   {
   1063     unsigned int new_size;
   1064 
   1065     new_size = GNUNET_MIN (
   1066       GNUNET_MAX_MALLOC_CHECKED,
   1067       GNUNET_MAX (
   1068         GNUNET_MIN (hr->io_size * 2 + 1024,
   1069                     UINT_MAX),
   1070         min_size));
   1071     GNUNET_assert (new_size > hr->io_size);
   1072     GNUNET_array_grow (hr->io_buf,
   1073                        hr->io_size,
   1074                        new_size);
   1075   }
   1076   GNUNET_memcpy (&hr->io_buf[hr->io_len],
   1077                  upload_data,
   1078                  upload_data_size);
   1079   hr->io_len += upload_data_size;
   1080   return true;
   1081 }
   1082 
   1083 
   1084 /**
   1085  * Choose the curl options for the HTTP method we're proxying and
   1086  * set the next proxy state accordingly.  Queues an error response
   1087  * and returns the corresponding MHD_Result for unsupported methods;
   1088  * otherwise returns #MHD_YES and leaves @a hr->state advanced to
   1089  * either #PROXY_UPLOAD_STARTED (method has a body to forward) or
   1090  * #PROXY_DOWNLOAD_STARTED (bodyless request).
   1091  *
   1092  * On error, the "curl" handle is set to NULL (!).
   1093  *
   1094  * @param[in,out] hr the request
   1095  * @param con client connection handle from MHD
   1096  * @param meth HTTP method specified by the client
   1097  * @return MHD status to return
   1098  */
   1099 static enum MHD_Result
   1100 configure_curl_method (struct HttpRequest *hr,
   1101                        struct MHD_Connection *con,
   1102                        const char *meth)
   1103 {
   1104   if (0 == strcasecmp (meth,
   1105                        MHD_HTTP_METHOD_GET))
   1106   {
   1107     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1108     curl_easy_setopt (hr->curl,
   1109                       CURLOPT_HTTPGET,
   1110                       1L);
   1111     return MHD_YES;
   1112   }
   1113   if (0 == strcasecmp (meth,
   1114                        MHD_HTTP_METHOD_POST))
   1115   {
   1116     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1117                 "Crafting a CURL POST request\n");
   1118     curl_easy_setopt (hr->curl,
   1119                       CURLOPT_POST,
   1120                       1L);
   1121     hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1122     return MHD_YES;
   1123   }
   1124   if (0 == strcasecmp (meth,
   1125                        MHD_HTTP_METHOD_HEAD))
   1126   {
   1127     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1128     curl_easy_setopt (hr->curl,
   1129                       CURLOPT_NOBODY,
   1130                       1L);
   1131     return MHD_YES;
   1132   }
   1133   if (0 == strcasecmp (meth,
   1134                        MHD_HTTP_METHOD_PUT))
   1135   {
   1136     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1137                 "Crafting a CURL PUT request\n");
   1138     curl_easy_setopt (hr->curl,
   1139                       CURLOPT_UPLOAD,
   1140                       1L);
   1141     hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1142     return MHD_YES;
   1143   }
   1144   if (0 == strcasecmp (meth,
   1145                        MHD_HTTP_METHOD_DELETE))
   1146   {
   1147     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1148                 "Crafting a CURL DELETE request\n");
   1149     curl_easy_setopt (hr->curl,
   1150                       CURLOPT_CUSTOMREQUEST,
   1151                       "DELETE");
   1152     if (0 != hr->io_len)
   1153     {
   1154       /* DELETE with a request body is unusual but legal. */
   1155       curl_easy_setopt (hr->curl,
   1156                         CURLOPT_POST,
   1157                         1L);
   1158       hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1159     }
   1160     else
   1161     {
   1162       hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1163     }
   1164     return MHD_YES;
   1165   }
   1166   if (0 == strcasecmp (meth,
   1167                        MHD_HTTP_METHOD_PATCH))
   1168   {
   1169     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1170                 "Crafting a CURL PATCH request\n");
   1171     /* CURLOPT_POST=1 turns on body upload via the read callback;
   1172        CURLOPT_CUSTOMREQUEST then overrides the verb on the wire. */
   1173     curl_easy_setopt (hr->curl,
   1174                       CURLOPT_POST,
   1175                       1L);
   1176     curl_easy_setopt (hr->curl,
   1177                       CURLOPT_CUSTOMREQUEST,
   1178                       "PATCH");
   1179     hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED;
   1180     return MHD_YES;
   1181   }
   1182   if (0 == strcasecmp (meth,
   1183                        MHD_HTTP_METHOD_OPTIONS))
   1184   {
   1185     hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED;
   1186     curl_easy_setopt (hr->curl,
   1187                       CURLOPT_CUSTOMREQUEST,
   1188                       "OPTIONS");
   1189     return MHD_YES;
   1190   }
   1191   /* TRACE leaks headers back to the client; CONNECT is for TLS
   1192      tunnelling and doesn't fit the reverse-proxy model.  Reject
   1193      anything else with a proper 405. */
   1194   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1195               "Unsupported HTTP method `%s'\n",
   1196               meth);
   1197   curl_easy_cleanup (hr->curl);
   1198   hr->curl = NULL;
   1199   return MHD_queue_response (con,
   1200                              MHD_HTTP_METHOD_NOT_ALLOWED,
   1201                              method_failure_response);
   1202 }
   1203 
   1204 
   1205 /**
   1206  * Attach the reverse-proxy forwarding headers (X-Forwarded-For /
   1207  * -Proto / -Host and Via) to `hr->headers`.  Our X-Forwarded-*
   1208  * replace any client-supplied values (filtered in `con_val_iter`);
   1209  * Via is appended to whatever chain the client already carried,
   1210  * per RFC 9110 §7.6.3.
   1211  *
   1212  * @param[in,out] hr the request
   1213  * @param con MHD connection we are processing
   1214  * @param ver HTTP version of the client, as given by MHD
   1215  */
   1216 static void
   1217 append_forwarded_headers (struct HttpRequest *hr,
   1218                           struct MHD_Connection *con,
   1219                           const char *ver)
   1220 {
   1221   const union MHD_ConnectionInfo *ci;
   1222   char *hdr;
   1223   const char *proto;
   1224   const char *fhost;
   1225   const char *via_ver = "1.1";
   1226 
   1227   ci = MHD_get_connection_info (con,
   1228                                 MHD_CONNECTION_INFO_CLIENT_ADDRESS);
   1229   if ( (NULL != ci) &&
   1230        (NULL != ci->client_addr) )
   1231   {
   1232     char ipbuf[INET6_ADDRSTRLEN];
   1233     const char *ip = NULL;
   1234 
   1235     switch (ci->client_addr->sa_family)
   1236     {
   1237     case AF_INET:
   1238       ip = inet_ntop (
   1239         AF_INET,
   1240         &((const struct sockaddr_in *) ci->client_addr)->sin_addr,
   1241         ipbuf, sizeof (ipbuf));
   1242       break;
   1243     case AF_INET6:
   1244       ip = inet_ntop (
   1245         AF_INET6,
   1246         &((const struct sockaddr_in6 *) ci->client_addr)->sin6_addr,
   1247         ipbuf, sizeof (ipbuf));
   1248       break;
   1249     default:
   1250       break;
   1251     }
   1252     if (NULL != ip)
   1253     {
   1254       GNUNET_asprintf (&hdr,
   1255                        "X-Forwarded-For: %s",
   1256                        ip);
   1257       hr->headers = curl_slist_append (hr->headers,
   1258                                        hdr);
   1259       GNUNET_free (hdr);
   1260     }
   1261   }
   1262   proto = (GNUNET_YES == TALER_mhd_is_https (con))
   1263           ? "https" : "http";
   1264   GNUNET_asprintf (&hdr,
   1265                    "X-Forwarded-Proto: %s",
   1266                    proto);
   1267   hr->headers = curl_slist_append (hr->headers,
   1268                                    hdr);
   1269   GNUNET_free (hdr);
   1270   fhost = MHD_lookup_connection_value (con,
   1271                                        MHD_HEADER_KIND,
   1272                                        MHD_HTTP_HEADER_HOST);
   1273   if (NULL != fhost)
   1274   {
   1275     GNUNET_asprintf (&hdr,
   1276                      "X-Forwarded-Host: %s",
   1277                      fhost);
   1278     hr->headers = curl_slist_append (hr->headers,
   1279                                      hdr);
   1280     GNUNET_free (hdr);
   1281   }
   1282   /* MHD hands us e.g. "HTTP/1.1" but Via wants just "1.1". */
   1283   if ( (NULL != ver) &&
   1284        (0 == strncasecmp (ver,
   1285                           "HTTP/",
   1286                           strlen ("HTTP/"))) )
   1287     via_ver = ver + 5;
   1288   if (NULL != hr->client_via)
   1289     GNUNET_asprintf (&hdr,
   1290                      "%s: %s, %s paivana",
   1291                      MHD_HTTP_HEADER_VIA,
   1292                      hr->client_via,
   1293                      via_ver);
   1294   else
   1295     GNUNET_asprintf (&hdr,
   1296                      "%s: %s paivana",
   1297                      MHD_HTTP_HEADER_VIA,
   1298                      via_ver);
   1299   hr->headers = curl_slist_append (hr->headers,
   1300                                    hdr);
   1301   GNUNET_free (hdr);
   1302 }
   1303 
   1304 
   1305 /**
   1306  * Initialize the curl handle, attach the forwarding headers, and
   1307  * hand the request off to the curl multi loop.  On success
   1308  * advances @a hr->state to one of the PROXY_*_STARTED states and
   1309  * returns #MHD_YES.  On any failure queues an appropriate error
   1310  * response and returns its MHD_Result.
   1311  *
   1312  * On error, the "curl" handle is set to NULL (!).
   1313  *
   1314  * @param[in,out] hr request we are handling
   1315  * @param con MHD connection handle
   1316  * @param meth HTTP method of the request
   1317  * @param ver HTTP version to use
   1318  * @return MHD status code to return
   1319  */
   1320 static enum MHD_Result
   1321 start_curl_request (struct HttpRequest *hr,
   1322                     struct MHD_Connection *con,
   1323                     const char *meth,
   1324                     const char *ver)
   1325 {
   1326   enum MHD_Result r;
   1327 
   1328   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1329               "Generating curl request\n");
   1330   hr->curl = curl_easy_init ();
   1331   if (NULL == hr->curl)
   1332   {
   1333     PAIVANA_LOG_ERROR ("Could not init the curl handle\n");
   1334     hr->suspended = GNUNET_NO;
   1335     MHD_resume_connection (hr->con);
   1336     return MHD_queue_response (con,
   1337                                MHD_HTTP_INTERNAL_SERVER_ERROR,
   1338                                internal_failure_response);
   1339   }
   1340 
   1341   /* No need to check whether we're POSTing or PUTting.
   1342    * If not needed, one of the following values will be
   1343    * ignored.*/
   1344   curl_easy_setopt (hr->curl,
   1345                     CURLOPT_POSTFIELDSIZE_LARGE,
   1346                     hr->io_len);
   1347   curl_easy_setopt (hr->curl,
   1348                     CURLOPT_INFILESIZE_LARGE,
   1349                     hr->io_len);
   1350   curl_easy_setopt (hr->curl,
   1351                     CURLOPT_HEADERFUNCTION,
   1352                     &curl_check_hdr);
   1353   curl_easy_setopt (hr->curl,
   1354                     CURLOPT_HEADERDATA,
   1355                     hr);
   1356   curl_easy_setopt (hr->curl,
   1357                     CURLOPT_FOLLOWLOCATION,
   1358                     0);
   1359   curl_easy_setopt (hr->curl,
   1360                     CURLOPT_CONNECTTIMEOUT,
   1361                     60L);
   1362   curl_easy_setopt (hr->curl,
   1363                     CURLOPT_TIMEOUT,
   1364                     60L);
   1365   curl_easy_setopt (hr->curl,
   1366                     CURLOPT_NOSIGNAL,
   1367                     1L);
   1368   curl_easy_setopt (hr->curl,
   1369                     CURLOPT_PRIVATE,
   1370                     hr);
   1371   curl_easy_setopt (hr->curl,
   1372                     CURLOPT_VERBOSE,
   1373                     0);
   1374   curl_easy_setopt (hr->curl,
   1375                     CURLOPT_READFUNCTION,
   1376                     &curl_upload_cb);
   1377   curl_easy_setopt (hr->curl,
   1378                     CURLOPT_READDATA,
   1379                     hr);
   1380   {
   1381     char *curlurl;
   1382 
   1383     GNUNET_asprintf (&curlurl,
   1384                      "%s%s",
   1385                      PH_target_server_base_url,
   1386                      hr->url);
   1387     curl_easy_setopt (hr->curl,
   1388                       CURLOPT_URL,
   1389                       curlurl);
   1390     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1391                 "Forwarding request to: %s\n",
   1392                 curlurl);
   1393     GNUNET_free (curlurl);
   1394   }
   1395 
   1396   if (NULL != PH_target_server_unixpath)
   1397   {
   1398     curl_easy_setopt (hr->curl,
   1399                       CURLOPT_UNIX_SOCKET_PATH,
   1400                       PH_target_server_unixpath);
   1401 
   1402     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1403                 "Forwarding using unixpath: %s\n",
   1404                 PH_target_server_unixpath);
   1405   }
   1406 
   1407   {
   1408     char *host_hdr;
   1409 
   1410     host_hdr = build_host_header (PH_target_server_base_url);
   1411     if (NULL != host_hdr)
   1412     {
   1413       PAIVANA_LOG_DEBUG ("Faking the host header, %s\n",
   1414                          host_hdr);
   1415       hr->headers = curl_slist_append (hr->headers,
   1416                                        host_hdr);
   1417       GNUNET_free (host_hdr);
   1418     }
   1419   }
   1420 
   1421   r = configure_curl_method (hr,
   1422                              con,
   1423                              meth);
   1424   if (NULL == hr->curl)
   1425   {
   1426     hr->suspended = GNUNET_NO;
   1427     MHD_resume_connection (hr->con);
   1428     return r; /* unsupported method: response already queued */
   1429   }
   1430   /* First pass: collect Via / Connection so `con_val_iter` can
   1431      honor them (append to Via; drop headers named by Connection). */
   1432   MHD_get_connection_values (con,
   1433                              MHD_HEADER_KIND,
   1434                              &collect_proxy_state,
   1435                              hr);
   1436   MHD_get_connection_values (con,
   1437                              MHD_HEADER_KIND,
   1438                              &con_val_iter,
   1439                              hr);
   1440   append_forwarded_headers (hr,
   1441                             con,
   1442                             ver);
   1443   hr->job = GNUNET_CURL_job_add_raw (PH_ctx,
   1444                                      hr->curl,
   1445                                      hr->headers,
   1446                                      &curl_download_cb,
   1447                                      hr);
   1448   hr->curl = NULL;
   1449   if (NULL == hr->job)
   1450   {
   1451     GNUNET_break (0);
   1452     hr->suspended = GNUNET_NO;
   1453     MHD_resume_connection (hr->con);
   1454     return MHD_queue_response (con,
   1455                                MHD_HTTP_BAD_GATEWAY,
   1456                                curl_failure_response);
   1457   }
   1458   return MHD_YES;
   1459 }
   1460 
   1461 
   1462 /**
   1463  * Build the final MHD response from the accumulated upstream body
   1464  * (or the pre-built failure page, on curl error) and queue it.
   1465  *
   1466  * @param[in,out] request handle
   1467  * @param con MHD client connection to send response on
   1468  */
   1469 static enum MHD_Result
   1470 finalize_response (struct HttpRequest *hr,
   1471                    struct MHD_Connection *con)
   1472 {
   1473   /* `hr->response` may already be set to `curl_failure_response` by
   1474      the curl task on upstream failure; in that case, don't build a
   1475      buffer response and don't attach per-request headers to the
   1476      shared failure response. */
   1477   if (NULL == hr->response)
   1478   {
   1479     GNUNET_break (0);
   1480     hr->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1481     hr->response = internal_failure_response;
   1482   }
   1483   return MHD_queue_response (con,
   1484                              hr->response_code,
   1485                              hr->response);
   1486 }
   1487 
   1488 
   1489 /**
   1490  * Main MHD callback for reverse proxy.
   1491  *
   1492  * Pure state-machine dispatch: each invocation picks up @a hr->state,
   1493  * runs the transitions it can without new input from MHD, and
   1494  * either returns to wait for more MHD data / curl progress, or
   1495  * cascades through the `while` loop to the next applicable state.
   1496  *
   1497  * @param hr the HTTP request context
   1498  * @param con MHD connection handle
   1499  * @param url the url in the request (unused; kept for ABI symmetry
   1500  *        with the MHD access handler signature)
   1501  * @param meth the HTTP method used ("GET", "PUT", etc.)
   1502  * @param ver the HTTP version string (i.e. "HTTP/1.1")
   1503  * @param upload_data the data being uploaded (excluding HEADERS)
   1504  * @param upload_data_size set initially to the size of the
   1505  *        @a upload_data provided; the method must update this
   1506  *        value to the number of bytes NOT processed;
   1507  * @return #MHD_YES if the connection was handled successfully,
   1508  *         #MHD_NO if the socket must be closed due to a serious
   1509  *         error while handling the request
   1510  */
   1511 enum MHD_Result
   1512 PAIVANA_HTTPD_reverse (struct HttpRequest *hr,
   1513                        struct MHD_Connection *con,
   1514                        const char *url,
   1515                        const char *meth,
   1516                        const char *ver,
   1517                        const char *upload_data,
   1518                        size_t *upload_data_size)
   1519 {
   1520   (void) url;
   1521 
   1522   while (true)
   1523   {
   1524     switch (hr->state)
   1525     {
   1526     case REQUEST_STATE_HEADERS_PENDING:
   1527       /* MHD's HEADERS_PROCESSED callback: we can still queue a
   1528          response here before the client body is consumed, so this
   1529          is our only chance to short-circuit an oversized upload
   1530          based on Content-Length and suppress the implicit 100
   1531          Continue (RFC 7231 §5.1.1). */
   1532       if (! content_length_ok (con))
   1533       {
   1534         hr->state = REQUEST_STATE_REJECT_UPLOAD;
   1535         continue;
   1536       }
   1537       hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED;
   1538       return MHD_YES;
   1539 
   1540     case REQUEST_STATE_CLIENT_UPLOAD_STARTED:
   1541       if (0 == *upload_data_size)
   1542       {
   1543         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1544                     "Finished processing UPLOAD\n");
   1545         hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE;
   1546         continue;
   1547       }
   1548       if (buffer_upload_chunk (hr,
   1549                                *upload_data_size,
   1550                                upload_data))
   1551       {
   1552         *upload_data_size = 0;
   1553         return MHD_YES;
   1554       }
   1555       hr->state = REQUEST_STATE_REJECT_UPLOAD_DRAIN;
   1556       continue;
   1557 
   1558     case REQUEST_STATE_REJECT_UPLOAD_DRAIN:
   1559       /* MHD disallows queuing a response while BODY_RECEIVING, so
   1560          silently discard the remaining body bytes.  Once MHD calls
   1561          us with `upload_data_size == 0` (FULL_REQ_RECEIVED) we can
   1562          transition to REJECT_UPLOAD and queue the deferred 413. */
   1563       if (0 != *upload_data_size)
   1564       {
   1565         *upload_data_size = 0;
   1566         return MHD_YES;
   1567       }
   1568       hr->state = REQUEST_STATE_REJECT_UPLOAD;
   1569       continue;
   1570 
   1571     case REQUEST_STATE_REJECT_UPLOAD:
   1572       return MHD_queue_response (con,
   1573                                  MHD_HTTP_CONTENT_TOO_LARGE,
   1574                                  upload_failure_response);
   1575 
   1576     case REQUEST_STATE_CLIENT_UPLOAD_DONE:
   1577       /* start_curl_request advances state to PROXY_UPLOAD_STARTED
   1578          or PROXY_DOWNLOAD_STARTED on success and returns MHD_YES;
   1579          on error it queues a response and returns its MHD_Result. */
   1580       GNUNET_assert (GNUNET_NO == hr->suspended);
   1581       MHD_suspend_connection (con);
   1582       hr->suspended = GNUNET_YES;
   1583       return start_curl_request (hr,
   1584                                  con,
   1585                                  meth,
   1586                                  ver);
   1587 
   1588     case REQUEST_STATE_PROXY_UPLOAD_STARTED:
   1589     case REQUEST_STATE_PROXY_UPLOAD_DONE:
   1590     case REQUEST_STATE_PROXY_DOWNLOAD_STARTED:
   1591       /* we should not have been resumed in this state,
   1592          how did we get here? */
   1593       GNUNET_break (0);
   1594       GNUNET_assert (GNUNET_NO == hr->suspended);
   1595       MHD_suspend_connection (con);
   1596       hr->suspended = GNUNET_YES;
   1597       return MHD_YES;
   1598 
   1599     case REQUEST_STATE_PROXY_DOWNLOAD_DONE:
   1600       return finalize_response (hr,
   1601                                 con);
   1602     case REQUEST_STATE_PROXY_DOWNLOAD_FAILED:
   1603       return MHD_queue_response (con,
   1604                                  MHD_HTTP_BAD_GATEWAY,
   1605                                  curl_failure_response);
   1606     }
   1607     GNUNET_assert (0); /* unreachable */
   1608   }
   1609 }