paivana

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

upstream_mhd.c (14781B)


      1 /*
      2   This file is part of paivana tests.
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   Paivana is free software; you can redistribute it and/or
      6   modify it under the terms of the GNU Affero General Public License
      7   as published by the Free Software Foundation; either version
      8   3, or (at your option) any later version.
      9 
     10   Paivana is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty
     12   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
     13   the GNU Affero General Public License for more details.
     14 
     15   You should have received a copy of the GNU Affero General Public
     16   License along with Paivana; see the file COPYING.  If not,
     17   write to the Free Software Foundation, Inc., 51 Franklin
     18   Street, Fifth Floor, Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file upstream_mhd.c
     23  * @brief libmicrohttpd-based upstream test server for the
     24  *        paivana reverse-proxy test suite.  Implements a small
     25  *        set of canned endpoints (see README).
     26  */
     27 #include <errno.h>
     28 #include <netinet/in.h>
     29 #include <signal.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <time.h>
     34 #include <unistd.h>
     35 #include <sys/socket.h>
     36 #include <sys/time.h>
     37 #include <microhttpd.h>
     38 
     39 #define SERVER_NAME "mhd"
     40 
     41 struct Ctx
     42 {
     43   char *body;
     44   size_t body_len;
     45   size_t body_cap;
     46 };
     47 
     48 
     49 static void
     50 ctx_free (struct Ctx *c)
     51 {
     52   free (c->body);
     53   free (c);
     54 }
     55 
     56 
     57 struct HdrBuf
     58 {
     59   char *data;
     60   size_t len;
     61   size_t cap;
     62 };
     63 
     64 
     65 static enum MHD_Result
     66 append_hdr (void *cls,
     67             enum MHD_ValueKind kind,
     68             const char *key,
     69             const char *value)
     70 {
     71   struct HdrBuf *hb = cls;
     72   size_t need = strlen (key) + strlen (value) + 4;
     73 
     74   (void) kind;
     75   if (hb->len + need > hb->cap)
     76   {
     77     size_t nc = hb->cap ? hb->cap * 2 : 1024;
     78     char *nb;
     79 
     80     while (nc < hb->len + need)
     81       nc *= 2;
     82     nb = realloc (hb->data, nc);
     83     if (NULL == nb)
     84       return MHD_NO;
     85     hb->data = nb;
     86     hb->cap = nc;
     87   }
     88   hb->len += (size_t) snprintf (hb->data + hb->len,
     89                                 hb->cap - hb->len,
     90                                 "%s: %s\n",
     91                                 key,
     92                                 value);
     93   return MHD_YES;
     94 }
     95 
     96 
     97 static struct MHD_Response *
     98 make_text_response (const char *s)
     99 {
    100   struct MHD_Response *r;
    101 
    102   r = MHD_create_response_from_buffer (strlen (s),
    103                                        (void *) s,
    104                                        MHD_RESPMEM_MUST_COPY);
    105   MHD_add_response_header (r,
    106                            MHD_HTTP_HEADER_CONTENT_TYPE,
    107                            "text/plain");
    108   MHD_add_response_header (r,
    109                            "X-Upstream",
    110                            SERVER_NAME);
    111   return r;
    112 }
    113 
    114 
    115 static enum MHD_Result
    116 reply_text (struct MHD_Connection *con,
    117             unsigned int status,
    118             const char *s)
    119 {
    120   struct MHD_Response *r = make_text_response (s);
    121   enum MHD_Result ret;
    122 
    123   ret = MHD_queue_response (con,
    124                             status,
    125                             r);
    126   MHD_destroy_response (r);
    127   return ret;
    128 }
    129 
    130 
    131 static enum MHD_Result
    132 reply_bytes (struct MHD_Connection *con,
    133              unsigned int status,
    134              const char *buf,
    135              size_t len)
    136 {
    137   struct MHD_Response *r;
    138   enum MHD_Result ret;
    139 
    140   r = MHD_create_response_from_buffer (len,
    141                                        (void *) buf,
    142                                        MHD_RESPMEM_MUST_COPY);
    143   MHD_add_response_header (r,
    144                            "X-Upstream",
    145                            SERVER_NAME);
    146   MHD_add_response_header (r,
    147                            MHD_HTTP_HEADER_CONTENT_TYPE,
    148                            "application/octet-stream");
    149   ret = MHD_queue_response (con,
    150                             status,
    151                             r);
    152   MHD_destroy_response (r);
    153   return ret;
    154 }
    155 
    156 
    157 static enum MHD_Result
    158 handler (void *cls,
    159          struct MHD_Connection *con,
    160          const char *url,
    161          const char *method,
    162          const char *version,
    163          const char *upload_data,
    164          size_t *upload_data_size,
    165          void **con_cls)
    166 {
    167   struct Ctx *ctx = *con_cls;
    168 
    169   (void) cls;
    170   (void) version;
    171   if (NULL == ctx)
    172   {
    173     ctx = calloc (1,
    174                   sizeof (*ctx));
    175     if (NULL == ctx)
    176       return MHD_NO;
    177     *con_cls = ctx;
    178     return MHD_YES;
    179   }
    180   if (0 != *upload_data_size)
    181   {
    182     if (ctx->body_len + *upload_data_size > ctx->body_cap)
    183     {
    184       size_t n = ctx->body_cap ? ctx->body_cap * 2 : 4096;
    185       char *nb;
    186 
    187       while (n < ctx->body_len + *upload_data_size)
    188         n *= 2;
    189       nb = realloc (ctx->body,
    190                     n);
    191       if (NULL == nb)
    192         return MHD_NO;
    193       ctx->body = nb;
    194       ctx->body_cap = n;
    195     }
    196     memcpy (ctx->body + ctx->body_len,
    197             upload_data,
    198             *upload_data_size);
    199     ctx->body_len += *upload_data_size;
    200     *upload_data_size = 0;
    201     return MHD_YES;
    202   }
    203 
    204   /* OPTIONS on anything */
    205   if (0 == strcmp (method,
    206                    MHD_HTTP_METHOD_OPTIONS))
    207   {
    208     struct MHD_Response *r = make_text_response ("");
    209     enum MHD_Result ret;
    210 
    211     MHD_add_response_header (r,
    212                              MHD_HTTP_HEADER_ALLOW,
    213                              "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS");
    214     ret = MHD_queue_response (con,
    215                               MHD_HTTP_NO_CONTENT,
    216                               r);
    217     MHD_destroy_response (r);
    218     return ret;
    219   }
    220 
    221   /* GET /hello */
    222   if ( (0 == strcmp (method,
    223                      MHD_HTTP_METHOD_GET) ||
    224         0 == strcmp (method,
    225                      MHD_HTTP_METHOD_HEAD)) &&
    226        (0 == strcmp (url,
    227                      "/hello")) )
    228     return reply_text (con,
    229                        MHD_HTTP_OK,
    230                        "Hello from " SERVER_NAME "\n");
    231 
    232   /* GET /echo-headers — return all request headers in the body */
    233   if ( (0 == strcmp (method,
    234                      MHD_HTTP_METHOD_GET)) &&
    235        (0 == strcmp (url,
    236                      "/echo-headers")) )
    237   {
    238     struct HdrBuf hb = { 0 };
    239     enum MHD_Result ret;
    240 
    241     MHD_get_connection_values (con,
    242                                MHD_HEADER_KIND,
    243                                &append_hdr,
    244                                &hb);
    245     ret = reply_bytes (con,
    246                        MHD_HTTP_OK,
    247                        hb.data ? hb.data : "",
    248                        hb.len);
    249     free (hb.data);
    250     return ret;
    251   }
    252 
    253   /* GET /conn-response — reply naming two of our own response
    254      headers in Connection; a conforming proxy must strip both
    255      (RFC 9110 section 7.6.1) but keep X-Keep-Resp. */
    256   if ( (0 == strcmp (method,
    257                      MHD_HTTP_METHOD_GET)) &&
    258        (0 == strcmp (url,
    259                      "/conn-response")) )
    260   {
    261     struct MHD_Response *r = make_text_response ("conn\n");
    262     enum MHD_Result ret;
    263 
    264     MHD_add_response_header (r,
    265                              "X-Hop-Before",
    266                              "must-not-leak");
    267     MHD_add_response_header (r,
    268                              MHD_HTTP_HEADER_CONNECTION,
    269                              "X-Hop-Before, X-Hop-After");
    270     MHD_add_response_header (r,
    271                              "X-Hop-After",
    272                              "must-not-leak");
    273     MHD_add_response_header (r,
    274                              "X-Keep-Resp",
    275                              "survivor");
    276     ret = MHD_queue_response (con,
    277                               MHD_HTTP_OK,
    278                               r);
    279     MHD_destroy_response (r);
    280     return ret;
    281   }
    282 
    283   /* GET /status/NNN */
    284   if ( (0 == strcmp (method,
    285                      MHD_HTTP_METHOD_GET)) &&
    286        (0 == strncmp (url,
    287                       "/status/",
    288                       strlen ("/status/"))) )
    289   {
    290     char msg[64];
    291     int s = atoi (url + 8);
    292 
    293     if (s < 100 || s > 599)
    294       s = 500;
    295     snprintf (msg,
    296               sizeof (msg),
    297               "status %d\n",
    298               s);
    299     return reply_text (con,
    300                        (unsigned int) s,
    301                        msg);
    302   }
    303 
    304   /* GET /large/NNN */
    305   if ( (0 == strcmp (method,
    306                      MHD_HTTP_METHOD_GET) ||
    307         0 == strcmp (method,
    308                      MHD_HTTP_METHOD_HEAD)) &&
    309        (0 == strncmp (url,
    310                       "/large/",
    311                       strlen ("/large/"))) )
    312   {
    313     long n = atol (url + 7);
    314     char *b;
    315     enum MHD_Result ret;
    316 
    317     if (n < 0)
    318       n = 0;
    319     if (n > 10 * 1024 * 1024)
    320       n = 10 * 1024 * 1024;
    321     b = malloc ((size_t) n);
    322     if (NULL == b && n > 0)
    323       return reply_text (con, 500, "oom\n");
    324     for (long i = 0; i < n; i++)
    325       b[i] = 'A' + (char) (i % 26);
    326     ret = reply_bytes (con,
    327                        MHD_HTTP_OK,
    328                        b,
    329                        (size_t) n);
    330     free (b);
    331     return ret;
    332   }
    333 
    334   /* GET /slow/NNN — sleep NNN ms then respond */
    335   if ( (0 == strcmp (method,
    336                      MHD_HTTP_METHOD_GET)) &&
    337        (0 == strncmp (url,
    338                       "/slow/",
    339                       strlen ("/slow/"))) )
    340   {
    341     int ms = atoi (url + 6);
    342     struct timespec ts;
    343 
    344     if (ms < 0)
    345       ms = 0;
    346     if (ms > 30000)
    347       ms = 30000;
    348     ts.tv_sec = ms / 1000;
    349     ts.tv_nsec = (ms % 1000) * 1000000L;
    350     nanosleep (&ts, NULL);
    351     return reply_text (con,
    352                        MHD_HTTP_OK,
    353                        "slept\n");
    354   }
    355 
    356   /* POST /echo — echoes body */
    357   if ( (0 == strcmp (method,
    358                      MHD_HTTP_METHOD_POST)) &&
    359        (0 == strcmp (url, "/echo")) )
    360     return reply_bytes (con,
    361                         MHD_HTTP_OK,
    362                         ctx->body ? ctx->body : "",
    363                         ctx->body_len);
    364 
    365   /* POST /upload — returns count */
    366   if ( (0 == strcmp (method,
    367                      MHD_HTTP_METHOD_POST)) &&
    368        (0 == strcmp (url,
    369                      "/upload")) )
    370   {
    371     char msg[64];
    372 
    373     snprintf (msg,
    374               sizeof (msg),
    375               "Received %zu bytes\n",
    376               ctx->body_len);
    377     return reply_text (con,
    378                        MHD_HTTP_OK,
    379                        msg);
    380   }
    381 
    382   /* PUT /put */
    383   if ( (0 == strcmp (method,
    384                      MHD_HTTP_METHOD_PUT)) &&
    385        (0 == strcmp (url,
    386                      "/put")) )
    387   {
    388     char msg[64];
    389 
    390     snprintf (msg,
    391               sizeof (msg),
    392               "PUT received %zu\n",
    393               ctx->body_len);
    394     return reply_text (con,
    395                        MHD_HTTP_OK,
    396                        msg);
    397   }
    398 
    399   /* PATCH /patch */
    400   if ( (0 == strcmp (method,
    401                      MHD_HTTP_METHOD_PATCH)) &&
    402        (0 == strcmp (url,
    403                      "/patch")) )
    404   {
    405     char msg[64];
    406 
    407     snprintf (msg,
    408               sizeof (msg),
    409               "PATCH received %zu\n",
    410               ctx->body_len);
    411     return reply_text (con,
    412                        MHD_HTTP_OK,
    413                        msg);
    414   }
    415 
    416   /* DELETE /item */
    417   if ( (0 == strcmp (method,
    418                      "DELETE")) &&
    419        (0 == strncmp (url,
    420                       "/item",
    421                       strlen ("/item"))) )
    422   {
    423     struct MHD_Response *r = make_text_response ("");
    424     enum MHD_Result ret = MHD_queue_response (con,
    425                                               MHD_HTTP_NO_CONTENT,
    426                                               r);
    427     MHD_destroy_response (r);
    428     return ret;
    429   }
    430 
    431   return reply_text (con,
    432                      MHD_HTTP_NOT_FOUND,
    433                      "not found\n");
    434 }
    435 
    436 
    437 static void
    438 completed_cb (void *cls,
    439               struct MHD_Connection *con,
    440               void **con_cls,
    441               enum MHD_RequestTerminationCode toe)
    442 {
    443   struct Ctx *ctx = *con_cls;
    444 
    445   (void) cls;
    446   (void) con;
    447   (void) toe;
    448   if (NULL != ctx)
    449     ctx_free (ctx);
    450   *con_cls = NULL;
    451 }
    452 
    453 
    454 /**
    455  * Parse @a arg as a TCP port number.
    456  *
    457  * atoi(3) would map "garbage" to 0, MHD would then bind an ephemeral
    458  * port, and the driver's readiness probe would time out five seconds
    459  * later blaming the port it asked for -- a failure pointing at
    460  * something other than the mistake.
    461  *
    462  * @param arg argument to parse
    463  * @param[out] port set to the parsed port on success
    464  * @return 0 on success, -1 if @a arg is not a port number
    465  */
    466 static int
    467 parse_port (const char *arg,
    468             unsigned int *port)
    469 {
    470   char *end;
    471   long long v;
    472 
    473   errno = 0;
    474   v = strtoll (arg,
    475                &end,
    476                10);
    477   if ( (0 != errno) ||
    478        (end == arg) ||
    479        ('\0' != *end) ||
    480        (v < 1) ||
    481        (v > 65535) )
    482     return -1;
    483   *port = (unsigned int) v;
    484   return 0;
    485 }
    486 
    487 
    488 int
    489 main (int argc, char **argv)
    490 {
    491   unsigned int port = 8401;
    492   struct MHD_Daemon *d;
    493   struct sockaddr_in addr;
    494   sigset_t quit;
    495   int sig;
    496 
    497   if ( (argc > 1) &&
    498        (0 != parse_port (argv[1],
    499                          &port)) )
    500   {
    501     fprintf (stderr,
    502              "invalid port `%s'\n",
    503              argv[1]);
    504     return 1;
    505   }
    506   signal (SIGPIPE,
    507           SIG_IGN);
    508   /* Block the two shutdown signals here, while we are still
    509      single-threaded, so that MHD's internal polling thread inherits
    510      the block and the signal can only be taken by the sigwait below.
    511      A handler plus `while (run_flag) sleep (1);' cost up to a second
    512      of shutdown latency per upstream, and a handler plus pause(2)
    513      would deadlock outright whenever the process-directed signal
    514      happened to be delivered to the MHD thread. */
    515   sigemptyset (&quit);
    516   sigaddset (&quit,
    517              SIGINT);
    518   sigaddset (&quit,
    519              SIGTERM);
    520   if (0 != sigprocmask (SIG_BLOCK,
    521                         &quit,
    522                         NULL))
    523   {
    524     perror ("sigprocmask");
    525     return 1;
    526   }
    527   /* Bind the loopback interface explicitly.  This is a test server
    528      that echoes an arbitrary POST body back and hands out 10 MiB on
    529      request; it has no business being reachable from the network for
    530      the duration of `make check'. */
    531   memset (&addr,
    532           0,
    533           sizeof (addr));
    534   addr.sin_family = AF_INET;
    535   addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    536   addr.sin_port = htons ((uint16_t) port);
    537   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD,
    538                         port,
    539                         NULL, NULL,
    540                         &handler, NULL,
    541                         MHD_OPTION_SOCK_ADDR, &addr,
    542                         MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
    543                         MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 30,
    544                         MHD_OPTION_END);
    545   if (NULL == d)
    546   {
    547     fprintf (stderr,
    548              "MHD_start_daemon failed on port %u\n",
    549              port);
    550     return 1;
    551   }
    552   fprintf (stderr,
    553            "upstream_mhd listening on port %u\n",
    554            port);
    555   fflush (stderr);
    556   while (0 != sigwait (&quit,
    557                        &sig))
    558     /* EINTR is the only failure sigwait can report here; retry. */;
    559   MHD_stop_daemon (d);
    560   return 0;
    561 }