paivana

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

paivana-httpd_reverse.c (45530B)


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