libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

test_pipelined.c (8621B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2026 Christian Grothoff
      4 
      5      libmicrohttpd is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 2, or (at your
      8      option) any later version.
      9 
     10      libmicrohttpd is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libmicrohttpd; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file testzzuf/test_pipelined.c
     23  * @brief  Fuzzing testcase for pipelined requests
     24  * @author Christian Grothoff
     25  *
     26  * Several complete requests are written with a single send() call, so that
     27  * MHD has to find the request boundaries inside one read buffer.  Corrupting
     28  * such a stream (or combining it with a connection memory pool that is too
     29  * small to hold all pipelined requests at once) is the most direct way to
     30  * produce a request boundary de-synchronisation.
     31  *
     32  * libcurl does not pipeline any more (HTTP/1.1 pipelining support was
     33  * removed in libcurl 7.62), therefore the raw socket client is used.  The
     34  * traffic still goes to the same port and through the same zzuf/socat
     35  * corruption layer as the libcurl based tests of this directory.
     36  */
     37 
     38 #include "platform.h"
     39 #include <curl/curl.h>
     40 #include <microhttpd.h>
     41 #include <stdlib.h>
     42 #include <string.h>
     43 
     44 #include "mhd_zzuf_common.h"
     45 
     46 #define TEST_MAGIC_MARKER 0xFEE1C0DE
     47 
     48 #define EMPTY_PAGE "Empty page."
     49 
     50 /**
     51  * The port offset used if the port cannot be auto-detected.
     52  */
     53 #define TEST_PORT_OFFSET 150
     54 
     55 /**
     56  * The maximum size of a single pipelined batch.
     57  */
     58 #define MAX_BATCH_SIZE 4096
     59 
     60 #define HOST_HDR "Host: " ZZUF_MHD_LISTEN_IP "\r\n"
     61 
     62 
     63 /**
     64  * The closure of the access handler callback.
     65  */
     66 struct ahc_param
     67 {
     68   /**
     69    * Must have #TEST_MAGIC_MARKER value.
     70    */
     71   unsigned int magic;
     72 
     73   /**
     74    * Non-zero if any error has been encountered.
     75    */
     76   unsigned int err_flag;
     77 
     78   /**
     79    * The number of requests seen so far.
     80    */
     81   unsigned int num_requests;
     82 };
     83 
     84 
     85 /**
     86  * The request templates that are concatenated into one pipelined batch.
     87  * The last request of every batch is added by build_batch().
     88  */
     89 static const char *const req_templates[] = {
     90   "GET /pipe/a HTTP/1.1\r\n" HOST_HDR "\r\n",
     91   "GET /pipe/b?x=1&y HTTP/1.1\r\n" HOST_HDR "X-Pad: 0123456789\r\n\r\n",
     92   "HEAD /pipe/c HTTP/1.1\r\n" HOST_HDR "\r\n",
     93   "POST /pipe/d HTTP/1.1\r\n" HOST_HDR
     94   "Content-Length: 5\r\n\r\nHELLO",
     95   "PUT /pipe/e HTTP/1.1\r\n" HOST_HDR
     96   "Transfer-Encoding: chunked\r\n\r\n5\r\nHELLO\r\n0\r\n\r\n",
     97   /* An empty line before the request line: allowed to be skipped by MHD */
     98   "\r\nGET /pipe/f HTTP/1.1\r\n" HOST_HDR "\r\n",
     99   /* A request with a body that is *not* announced: the following bytes
    100      must not be swallowed as a body */
    101   "GET /pipe/g HTTP/1.1\r\n" HOST_HDR "\r\n"
    102 };
    103 
    104 #define NUM_REQ_TEMPLATES \
    105         (sizeof(req_templates) / sizeof(req_templates[0]))
    106 
    107 
    108 static enum MHD_Result
    109 ahc_pipelined (void *cls,
    110                struct MHD_Connection *connection,
    111                const char *url,
    112                const char *method,
    113                const char *version,
    114                const char *upload_data,
    115                size_t *upload_data_size,
    116                void **req_cls)
    117 {
    118   static int marker;
    119   struct ahc_param *param = (struct ahc_param *) cls;
    120   struct MHD_Response *response;
    121   enum MHD_Result ret;
    122 
    123   (void) url; (void) method; (void) version; (void) upload_data;
    124 
    125   if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic))
    126   {
    127     fprintf (stderr, "The 'cls' parameter is invalid "
    128              "at line %d.\n", (int) __LINE__);
    129     fflush (stderr);
    130     abort ();
    131   }
    132   if ((NULL == url) || (NULL == method) || (NULL == version))
    133   {
    134     fprintf (stderr, "One of the mandatory string parameters is NULL "
    135              "at line %d.\n", (int) __LINE__);
    136     param->err_flag = 1;
    137     return MHD_NO;
    138   }
    139   if (NULL == *req_cls)
    140   {
    141     *req_cls = &marker;
    142     return MHD_YES;
    143   }
    144   if ((NULL != upload_data_size) && (0 != *upload_data_size))
    145   {
    146     *upload_data_size = 0; /* Discard the body */
    147     return MHD_YES;
    148   }
    149   param->num_requests++;
    150 
    151   response =
    152     MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (EMPTY_PAGE),
    153                                             EMPTY_PAGE);
    154   if (NULL == response)
    155   {
    156     fprintf (stderr, "MHD_create_response_from_buffer_static() failed "
    157              "at line %d.\n", (int) __LINE__);
    158     return MHD_NO; /* External error, do not raise the error flag */
    159   }
    160   ret = MHD_YES;
    161   if (zzuf_use_close)
    162     ret = MHD_add_response_header (response,
    163                                    MHD_HTTP_HEADER_CONNECTION,
    164                                    "close");
    165   if (MHD_YES == ret)
    166     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    167   MHD_destroy_response (response);
    168   return ret;
    169 }
    170 
    171 
    172 /**
    173  * Build one pipelined batch of requests.
    174  *
    175  * @param iteration the number of the current iteration
    176  * @param[out] buf the buffer to fill
    177  * @param buf_size the size of @a buf
    178  * @return the number of bytes written to @a buf
    179  */
    180 static size_t
    181 build_batch (unsigned int iteration, char *buf, size_t buf_size)
    182 {
    183   static const char last_req[] =
    184     "GET /pipe/last HTTP/1.1\r\n" HOST_HDR "Connection: close\r\n\r\n";
    185   size_t pos = 0;
    186   unsigned int i;
    187   unsigned int num;
    188 
    189   num = 2 + (iteration % 4); /* 2 to 5 requests per batch */
    190   for (i = 0; i < num; ++i)
    191   {
    192     const char *req =
    193       req_templates[(iteration + i) % NUM_REQ_TEMPLATES];
    194     const size_t len = strlen (req);
    195 
    196     if (pos + len + sizeof(last_req) > buf_size)
    197       break;
    198     memcpy (buf + pos, req, len);
    199     pos += len;
    200   }
    201   if (pos + MHD_STATICSTR_LEN_ (last_req) <= buf_size)
    202   {
    203     memcpy (buf + pos, last_req, MHD_STATICSTR_LEN_ (last_req));
    204     pos += MHD_STATICSTR_LEN_ (last_req);
    205   }
    206   return pos;
    207 }
    208 
    209 
    210 static unsigned int
    211 client_run (struct MHD_Daemon *d_extern,
    212             uint16_t port,
    213             void *cls)
    214 {
    215   struct ahc_param *param = (struct ahc_param *) cls;
    216   unsigned int i;
    217   unsigned int loops;
    218   char batch[MAX_BATCH_SIZE];
    219 
    220   loops = zzuf_loop_count ();
    221   for (i = 0; i < loops; ++i)
    222   {
    223     struct zzuf_raw_part parts[2];
    224     size_t batch_len;
    225     size_t num_parts;
    226 
    227     fprintf (stderr, ".");
    228     batch_len = build_batch (i, batch, sizeof(batch));
    229     parts[0].data = batch;
    230     parts[0].size = batch_len;
    231     num_parts = 1;
    232     if (0 != i % 2)
    233     {
    234       /* Every other iteration: split the batch in the middle of a request,
    235          so that MHD has to keep a partial request in the read buffer while
    236          the previous ones are still being answered. */
    237       parts[0].size = batch_len / 2;
    238       parts[1].data = batch + parts[0].size;
    239       parts[1].size = batch_len - parts[0].size;
    240       num_parts = 2;
    241     }
    242     if (ZZUF_RAW_SETUP_FAILED ==
    243         zzuf_raw_exchange (d_extern, port, parts, num_parts,
    244                            (unsigned int) ZZUF_CLIENT_TIMEOUT))
    245     {
    246       fprintf (stderr, "The raw client could not be set up "
    247                "at line %d.\n", (int) __LINE__);
    248       return 99; /* Not an MHD error */
    249     }
    250     fflush (stderr);
    251   }
    252 
    253   if (0 != param->err_flag)
    254   {
    255     fprintf (stderr, "One or more errors have been detected by the access "
    256              "handler callback function. At line %d.\n", (int) __LINE__);
    257     return 1;
    258   }
    259   return 0;
    260 }
    261 
    262 
    263 int
    264 main (int argc, char *const *argv)
    265 {
    266   struct ahc_param param;
    267   struct zzuf_run_params rp;
    268   unsigned int res;
    269   int use_magic_exit_codes;
    270 
    271   zzuf_parse_common_args (argc, argv);
    272   use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run;
    273 
    274   res = zzuf_check_runnable ();
    275   if (0 != res)
    276     return use_magic_exit_codes ? (int) res : 0;
    277 
    278   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    279   param.err_flag = 0;
    280   param.num_requests = 0;
    281 
    282   memset (&rp, 0, sizeof(rp));
    283   rp.port = zzuf_pick_port (TEST_PORT_OFFSET);
    284   rp.ahc = &ahc_pipelined;
    285   rp.ahc_cls = &param;
    286   rp.rcc = NULL;
    287   rp.rcc_cls = NULL;
    288   rp.mem_limit_override = 0;
    289   rp.sweep_profiles = 1;
    290   rp.profile_start = 0;
    291   rp.client = &client_run;
    292   rp.client_cls = &param;
    293 
    294   res = zzuf_run_polling_modes (&rp);
    295 
    296   if (99 == res)
    297     return use_magic_exit_codes ? 99 : 0;
    298   if (77 == res)
    299     return use_magic_exit_codes ? 77 : 0;
    300   return (0 == res) ? 0 : 1; /* 0 == pass */
    301 }