libmicrohttpd

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

test_get_chunked_ext.c (20486B)


      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_get_chunked_ext.c
     23  * @brief  Fuzzing testcase for chunked transfers with chunk extensions
     24  * @author Christian Grothoff
     25  *
     26  * This single source builds two programs:
     27  * - test_get_chunked_ext: the request is a GET with a chunked body and
     28  *   MHD answers with a chunked reply that carries a footer, so that both
     29  *   the request-side chunk parser and the reply-side chunk writer are
     30  *   exercised;
     31  * - test_put_chunked_ext: the request is a PUT with a chunked body and
     32  *   MHD answers from a static buffer.
     33  *
     34  * libcurl cannot generate chunk extensions, so the requests are written
     35  * by the raw socket client of mhd_zzuf_common.c.  The client still talks
     36  * to the very same port (and therefore through the very same zzuf/socat
     37  * corruption layer) as the libcurl based tests of this directory.
     38  *
     39  * In addition to the fuzzed traffic, the test runs a small deterministic
     40  * self-check of the chunk-extension parser.  The self-check talks to MHD
     41  * *directly*, bypassing socat, and is therefore not fuzzed at all; it can
     42  * only be run in the socat mode (which is the mode that is mandatory when
     43  * MHD is built with sanitizers).  The self-check verifies that a chunked
     44  * body with chunk extensions is decoded byte-exactly; a parser that fails
     45  * to consume the CRLF that terminates a chunk-extension line cannot pass
     46  * it.
     47  */
     48 
     49 #include "platform.h"
     50 #include <curl/curl.h>
     51 #include <microhttpd.h>
     52 #include <stdlib.h>
     53 #include <string.h>
     54 
     55 #include "mhd_zzuf_common.h"
     56 
     57 #define TEST_MAGIC_MARKER 0xFEE1C0DE
     58 
     59 #define EMPTY_PAGE "Empty page."
     60 
     61 /**
     62  * The port offset used if the port cannot be auto-detected.
     63  */
     64 #define TEST_PORT_OFFSET 145
     65 
     66 /**
     67  * The port offset of the (unfuzzed) self-check daemon.
     68  */
     69 #define TEST_SELFCHECK_PORT_OFFSET 160
     70 
     71 /**
     72  * The maximum size of a request body accepted by this test.
     73  */
     74 #define MAX_BODY_SIZE 256
     75 
     76 
     77 /**
     78  * Non-zero for the "put" flavour of this test.
     79  */
     80 static int use_put;
     81 
     82 /**
     83  * Non-zero if MHD should answer with a chunked reply.
     84  */
     85 static int use_chunked_reply;
     86 
     87 
     88 /**
     89  * One chunked request body with (possibly hostile) chunk extensions.
     90  */
     91 struct chunk_case
     92 {
     93   /**
     94    * The name of the case; also used as the last path segment of the URI.
     95    */
     96   const char *name;
     97 
     98   /**
     99    * The chunked body, exactly as it goes on the wire.
    100    */
    101   const char *wire;
    102 
    103   /**
    104    * The body that MHD is expected to decode, or NULL if the request is
    105    * not required to be accepted at all (deliberately malformed input).
    106    */
    107   const char *expected;
    108 };
    109 
    110 
    111 static const struct chunk_case chunk_cases[] = {
    112   { "simple",
    113     "5;name=value\r\nHELLO\r\n0\r\n\r\n",
    114     "HELLO" },
    115   { "flag",
    116     "5;flag\r\nHELLO\r\n0\r\n\r\n",
    117     "HELLO" },
    118   { "quoted",
    119     "5;n=\"a;b=c\"\r\nHELLO\r\n0\r\n\r\n",
    120     "HELLO" },
    121   { "multi",
    122     "3;a=b;c=d;e\r\nabc\r\n2;x\r\nde\r\n0\r\n\r\n",
    123     "abcde" },
    124   { "lastext",
    125     "5;a=b\r\nHELLO\r\n0;final=1\r\n\r\n",
    126     "HELLO" },
    127   { "trailers",
    128     "5;a=b\r\nHELLO\r\n0\r\nX-Trailer: yes\r\nX-Other: 1\r\n\r\n",
    129     "HELLO" },
    130   { "extonly",
    131     "5;a\r\nHELLO\r\n0;b\r\n\r\n",
    132     "HELLO" },
    133   /* The following cases are deliberately malformed: MHD is free to reject
    134      them, it just must not misbehave. */
    135   { "wsbefore",
    136     "5 ;n=v\r\nHELLO\r\n0\r\n\r\n",
    137     NULL },
    138   { "wsaround",
    139     "5; n = v \r\nHELLO\r\n0\r\n\r\n",
    140     NULL },
    141   { "barelf",
    142     "5;a=b\nHELLO\r\n0\r\n\r\n",
    143     NULL },
    144   { "extcr",
    145     "5;a=b\rHELLO\r\n0\r\n\r\n",
    146     NULL },
    147   { "noext",
    148     "5;\r\nHELLO\r\n0\r\n\r\n",
    149     NULL }
    150 };
    151 
    152 #define NUM_CHUNK_CASES \
    153         (sizeof(chunk_cases) / sizeof(chunk_cases[0]))
    154 
    155 /**
    156  * The number of leading entries of #chunk_cases that are well-formed and
    157  * therefore usable for the deterministic self-check.
    158  */
    159 #define NUM_VALID_CHUNK_CASES 7
    160 
    161 /**
    162  * A chunk-size line with a very long extension, built at run time.
    163  */
    164 static char *long_ext_body;
    165 
    166 
    167 /**
    168  * The closure of the access handler callback.
    169  */
    170 struct ahc_param
    171 {
    172   /**
    173    * Must have #TEST_MAGIC_MARKER value.
    174    */
    175   unsigned int magic;
    176 
    177   /**
    178    * Non-zero if any error has been encountered.
    179    */
    180   unsigned int err_flag;
    181 
    182   /**
    183    * Non-zero while the deterministic self-check is running.
    184    */
    185   int self_check;
    186 
    187   /**
    188    * The body that is expected during the self-check, may be NULL.
    189    */
    190   const char *expect_body;
    191 
    192   /**
    193    * Set to non-zero by the handler when the expected body has been seen.
    194    */
    195   int body_matched;
    196 };
    197 
    198 
    199 /**
    200  * The per-request state.
    201  */
    202 struct req_state
    203 {
    204   /**
    205    * The number of bytes stored in @e body.
    206    */
    207   size_t body_len;
    208 
    209   /**
    210    * Set to non-zero if more data has been received than fits into @e body.
    211    */
    212   int overflow;
    213 
    214   /**
    215    * The received body.
    216    */
    217   char body[MAX_BODY_SIZE];
    218 };
    219 
    220 
    221 /**
    222  * The closure of the content reader callback.
    223  */
    224 struct content_cb_param
    225 {
    226   /**
    227    * The response the callback belongs to, needed to add the footer.
    228    */
    229   struct MHD_Response *response;
    230 };
    231 
    232 
    233 /**
    234  * MHD content reader callback for the chunked reply.
    235  *
    236  * @param cls the closure
    237  * @param pos the position in the reply body
    238  * @param[out] buf the buffer to fill
    239  * @param max the size of @a buf
    240  * @return the number of bytes written, or the end-of-stream marker
    241  */
    242 static ssize_t
    243 content_cb (void *cls, uint64_t pos, char *buf, size_t max)
    244 {
    245   struct content_cb_param *ccp = (struct content_cb_param *) cls;
    246 
    247   if (pos >= 512)
    248   {
    249     if (MHD_YES != MHD_add_response_footer (ccp->response,
    250                                             "Footer", "working"))
    251     {
    252       fprintf (stderr, "MHD_add_response_footer() failed "
    253                "at line %d.\n", (int) __LINE__);
    254       fflush (stderr);
    255       abort ();
    256     }
    257     return MHD_CONTENT_READER_END_OF_STREAM;
    258   }
    259   if (max > 128)
    260     max = 128;
    261   memset (buf, 'A' + (char) (unsigned int) (pos / 128), max);
    262   return (ssize_t) max;
    263 }
    264 
    265 
    266 /**
    267  * Free the closure of the content reader callback.
    268  *
    269  * @param ptr the closure to free
    270  */
    271 static void
    272 content_cb_free (void *ptr)
    273 {
    274   free (ptr);
    275 }
    276 
    277 
    278 /**
    279  * Iterator over the request footers (trailers).
    280  *
    281  * @param cls the closure
    282  * @param kind the kind of the value
    283  * @param key the key
    284  * @param value the value
    285  * @return #MHD_YES to continue the iteration
    286  */
    287 static enum MHD_Result
    288 footer_iterator (void *cls,
    289                  enum MHD_ValueKind kind,
    290                  const char *key,
    291                  const char *value)
    292 {
    293   unsigned int *sum = (unsigned int *) cls;
    294 
    295   (void) kind; /* Unused. Mute compiler warning. */
    296   if (NULL != key)
    297     *sum += (unsigned int) strlen (key);
    298   if (NULL != value)
    299     *sum += (unsigned int) strlen (value);
    300   return MHD_YES;
    301 }
    302 
    303 
    304 static enum MHD_Result
    305 ahc_chunked_ext (void *cls,
    306                  struct MHD_Connection *connection,
    307                  const char *url,
    308                  const char *method,
    309                  const char *version,
    310                  const char *upload_data,
    311                  size_t *upload_data_size,
    312                  void **req_cls)
    313 {
    314   struct ahc_param *param = (struct ahc_param *) cls;
    315   struct req_state *rs;
    316   struct MHD_Response *response;
    317   enum MHD_Result ret;
    318   unsigned int footer_sum = 0;
    319 
    320   (void) url; (void) method; (void) version;
    321 
    322   if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic))
    323   {
    324     fprintf (stderr, "The 'cls' parameter is invalid "
    325              "at line %d.\n", (int) __LINE__);
    326     fflush (stderr);
    327     abort ();
    328   }
    329   if (NULL == *req_cls)
    330   {
    331     rs = malloc (sizeof(struct req_state));
    332     if (NULL == rs)
    333       return MHD_NO; /* External error */
    334     rs->body_len = 0;
    335     rs->overflow = 0;
    336     *req_cls = rs;
    337     return MHD_YES;
    338   }
    339   rs = (struct req_state *) *req_cls;
    340 
    341   if ((NULL != upload_data_size) && (0 != *upload_data_size))
    342   {
    343     size_t to_copy = *upload_data_size;
    344 
    345     if (to_copy > sizeof(rs->body) - rs->body_len)
    346     {
    347       to_copy = sizeof(rs->body) - rs->body_len;
    348       rs->overflow = 1;
    349     }
    350     if ((0 != to_copy) && (NULL != upload_data))
    351     {
    352       memcpy (rs->body + rs->body_len, upload_data, to_copy);
    353       rs->body_len += to_copy;
    354     }
    355     *upload_data_size = 0; /* All data have been processed */
    356     return MHD_YES;
    357   }
    358 
    359   /* The request is complete. */
    360   (void) MHD_get_connection_values (connection, MHD_FOOTER_KIND,
    361                                     &footer_iterator, &footer_sum);
    362   if (param->self_check && (NULL != param->expect_body))
    363   {
    364     const size_t elen = strlen (param->expect_body);
    365 
    366     if ((! rs->overflow) &&
    367         (elen == rs->body_len) &&
    368         (0 == memcmp (rs->body, param->expect_body, elen)))
    369       param->body_matched = 1;
    370     else
    371     {
    372       fprintf (stderr,
    373                "Self-check: the decoded request body does not match the "
    374                "expected body. Expected %u bytes ('%s'), got %u bytes. "
    375                "At line %d.\n",
    376                (unsigned int) elen, param->expect_body,
    377                (unsigned int) rs->body_len, (int) __LINE__);
    378       param->err_flag = 1;
    379     }
    380   }
    381   free (rs);
    382   *req_cls = NULL;
    383 
    384   if (use_chunked_reply)
    385   {
    386     struct content_cb_param *ccp;
    387 
    388     ccp = malloc (sizeof(struct content_cb_param));
    389     if (NULL == ccp)
    390       return MHD_NO; /* External error */
    391     ccp->response = NULL;
    392     response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 1024,
    393                                                   &content_cb, ccp,
    394                                                   &content_cb_free);
    395     if (NULL == response)
    396       free (ccp);
    397     else
    398       ccp->response = response;
    399   }
    400   else
    401     response =
    402       MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (EMPTY_PAGE),
    403                                               EMPTY_PAGE);
    404   if (NULL == response)
    405   {
    406     fprintf (stderr, "Failed to create the response "
    407              "at line %d.\n", (int) __LINE__);
    408     return MHD_NO; /* External error, do not raise the error flag */
    409   }
    410   ret = MHD_YES;
    411   if (zzuf_use_close || ! zzuf_oneone)
    412     ret = MHD_add_response_header (response,
    413                                    MHD_HTTP_HEADER_CONNECTION,
    414                                    "close");
    415   if (MHD_YES == ret)
    416     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    417   MHD_destroy_response (response);
    418   return ret;
    419 }
    420 
    421 
    422 static void
    423 req_completed (void *cls,
    424                struct MHD_Connection *connection,
    425                void **req_cls,
    426                enum MHD_RequestTerminationCode toe)
    427 {
    428   (void) cls; (void) connection; (void) toe;
    429 
    430   if (NULL == *req_cls)
    431     return;
    432   free (*req_cls);
    433   *req_cls = NULL;
    434 }
    435 
    436 
    437 /**
    438  * Build the request head for a given case.
    439  *
    440  * @param name the case name (used in the URI)
    441  * @param close_conn if non-zero, "Connection: close" is added, so that MHD
    442  *                   closes the connection right after the reply instead of
    443  *                   waiting for the connection timeout
    444  * @param[out] buf the buffer to fill
    445  * @param buf_size the size of @a buf
    446  */
    447 static void
    448 build_head (const char *name, int close_conn, char *buf, size_t buf_size)
    449 {
    450   int len;
    451 
    452   len = snprintf (buf, buf_size,
    453                   "%s /chunked_ext/%s HTTP/1.1\r\n"
    454                   "Host: " ZZUF_MHD_LISTEN_IP "\r\n"
    455                   "Transfer-Encoding: chunked\r\n"
    456                   "%s"
    457                   "\r\n",
    458                   use_put ? "PUT" : "GET",
    459                   name,
    460                   close_conn ? "Connection: close\r\n" : "");
    461   if ((0 > len) || (buf_size <= (size_t) len))
    462   {
    463     fprintf (stderr, "snprintf() failed at line %d.\n", (int) __LINE__);
    464     fflush (stderr);
    465     abort ();
    466   }
    467 }
    468 
    469 
    470 static unsigned int
    471 client_run (struct MHD_Daemon *d_extern,
    472             uint16_t port,
    473             void *cls)
    474 {
    475   struct ahc_param *param = (struct ahc_param *) cls;
    476   unsigned int i;
    477   unsigned int loops;
    478 
    479   loops = zzuf_loop_count ();
    480   for (i = 0; i < loops; ++i)
    481   {
    482     const struct chunk_case *cc = chunk_cases + (i % NUM_CHUNK_CASES);
    483     struct zzuf_raw_part parts[4];
    484     char head[256];
    485     size_t num_parts;
    486     const char *wire;
    487     size_t wire_len;
    488     size_t split;
    489 
    490     fprintf (stderr, ".");
    491     build_head (cc->name, 0, head, sizeof(head));
    492     if ((NULL != long_ext_body) && (0 == i % 5))
    493       wire = long_ext_body; /* Occasionally use the over-long extension */
    494     else
    495       wire = cc->wire;
    496     wire_len = strlen (wire);
    497     /* Split the body so that the chunk-size (and chunk-extension) lines
    498        are torn apart between two TCP segments. */
    499     split = wire_len / 2;
    500     parts[0].data = head;
    501     parts[0].size = strlen (head);
    502     parts[1].data = wire;
    503     parts[1].size = split;
    504     parts[2].data = wire + split;
    505     parts[2].size = wire_len - split;
    506     /* Always append a pipelined follow-up request: if the chunk-extension
    507        line is not consumed correctly, the request boundary is lost and this
    508        second request is interpreted as chunk data (or the other way round).
    509        Its "Connection: close" also makes MHD close the connection right
    510        after the reply instead of waiting for the connection timeout. */
    511     parts[3].data = "GET /chunked_ext/after HTTP/1.1\r\n"
    512                     "Host: " ZZUF_MHD_LISTEN_IP "\r\n"
    513                     "Connection: close\r\n\r\n";
    514     parts[3].size = strlen (parts[3].data);
    515     num_parts = 4;
    516     if (ZZUF_RAW_SETUP_FAILED ==
    517         zzuf_raw_exchange (d_extern, port, parts, num_parts,
    518                            (unsigned int) ZZUF_CLIENT_TIMEOUT))
    519     {
    520       fprintf (stderr, "The raw client could not be set up "
    521                "at line %d.\n", (int) __LINE__);
    522       return 99; /* Not an MHD error */
    523     }
    524     fflush (stderr);
    525   }
    526 
    527   if (0 != param->err_flag)
    528   {
    529     fprintf (stderr, "One or more errors have been detected by the access "
    530              "handler callback function. At line %d.\n", (int) __LINE__);
    531     return 1;
    532   }
    533   return 0;
    534 }
    535 
    536 
    537 /**
    538  * Run the deterministic (unfuzzed) self-check of the chunk-extension
    539  * parser.  Only possible in the socat mode, where MHD's own port is not
    540  * touched by zzuf.
    541  *
    542  * @return zero on success, non-zero on failure
    543  */
    544 static unsigned int
    545 run_self_check (void)
    546 {
    547   struct MHD_Daemon *d;
    548   struct MHD_Daemon *d_extern;
    549   struct ahc_param param;
    550   uint16_t port;
    551   unsigned int flags;
    552   unsigned int i;
    553   unsigned int ret = 0;
    554 
    555   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    556   param.err_flag = 0;
    557   param.self_check = 1;
    558   param.expect_body = NULL;
    559   param.body_matched = 0;
    560 
    561   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    562     port = 0;
    563   else
    564     port = (uint16_t) (ZZUF_BASE_PORT + TEST_SELFCHECK_PORT_OFFSET);
    565   if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS))
    566     flags = MHD_USE_SELECT_INTERNALLY;
    567   else
    568     flags = MHD_USE_NO_THREAD_SAFETY;
    569   d = zzuf_start_daemon (flags, &port, zzuf_opt_profile (0), 0,
    570                          &ahc_chunked_ext, &param,
    571                          &req_completed, &param, NULL);
    572   if (NULL == d)
    573     return 99; /* Not an MHD error */
    574   d_extern = (0 == (MHD_USE_INTERNAL_POLLING_THREAD & flags)) ? d : NULL;
    575 
    576   printf ("Running the deterministic (unfuzzed) chunk-extension "
    577           "self-check...\n");
    578   fflush (stdout);
    579   for (i = 0; i < NUM_VALID_CHUNK_CASES; ++i)
    580   {
    581     const struct chunk_case *cc = chunk_cases + i;
    582     struct zzuf_raw_part parts[3];
    583     char head[256];
    584     char resp[512];
    585     size_t resp_len = 0;
    586     size_t wire_len;
    587     size_t split;
    588 
    589     build_head (cc->name, 1, head, sizeof(head));
    590     wire_len = strlen (cc->wire);
    591     split = wire_len / 2;
    592     parts[0].data = head;
    593     parts[0].size = strlen (head);
    594     parts[1].data = cc->wire;
    595     parts[1].size = split;
    596     parts[2].data = cc->wire + split;
    597     parts[2].size = wire_len - split;
    598 
    599     param.expect_body = cc->expected;
    600     param.body_matched = 0;
    601     if (ZZUF_RAW_SETUP_FAILED ==
    602         zzuf_raw_exchange2 (d_extern, port, 1, parts, 3,
    603                             (unsigned int) ZZUF_CLIENT_TIMEOUT,
    604                             resp, sizeof(resp), &resp_len))
    605     {
    606       fprintf (stderr, "The raw client could not be set up "
    607                "at line %d.\n", (int) __LINE__);
    608       MHD_stop_daemon (d);
    609       return 99; /* Not an MHD error */
    610     }
    611     if (! param.body_matched)
    612     {
    613       fprintf (stderr,
    614                "Self-check FAILED for the chunk-extension case '%s': "
    615                "MHD did not decode the body as expected. "
    616                "At line %d.\n", cc->name, (int) __LINE__);
    617       ret = 1;
    618     }
    619     else if ((12 > resp_len) || (0 != memcmp (resp, "HTTP/1.1 200", 12)))
    620     {
    621       fprintf (stderr,
    622                "Self-check FAILED for the chunk-extension case '%s': "
    623                "MHD did not reply with '200'. "
    624                "At line %d.\n", cc->name, (int) __LINE__);
    625       ret = 1;
    626     }
    627   }
    628   MHD_stop_daemon (d);
    629   if (0 != param.err_flag)
    630     ret = 1;
    631   if (0 == ret)
    632     printf ("The chunk-extension self-check succeeded.\n");
    633   fflush (stdout);
    634   return ret;
    635 }
    636 
    637 
    638 /**
    639  * Build the chunked body with a very long chunk extension.
    640  *
    641  * With a connection memory pool of 256 or 512 bytes the chunk-size line
    642  * does not fit into the read buffer, so MHD emits the 413 reply from
    643  * handle_req_chunk_size_line_no_space().  Before commit e04eb218 it then
    644  * re-entered the error path and discarded that reply, so this is also the
    645  * regression test for it.
    646  *
    647  * @return non-zero on success, zero on failure
    648  */
    649 static int
    650 init_long_ext_body (void)
    651 {
    652   static const char head[] = "5;longext=";
    653   static const char tail[] = "\r\nHELLO\r\n0\r\n\r\n";
    654   const size_t ext_len = 600;
    655   char *buf;
    656 
    657   buf = malloc (sizeof(head) + ext_len + sizeof(tail));
    658   if (NULL == buf)
    659     return 0;
    660   memcpy (buf, head, MHD_STATICSTR_LEN_ (head));
    661   memset (buf + MHD_STATICSTR_LEN_ (head), 'E', ext_len);
    662   memcpy (buf + MHD_STATICSTR_LEN_ (head) + ext_len, tail, sizeof(tail));
    663   long_ext_body = buf;
    664   return ! 0;
    665 }
    666 
    667 
    668 int
    669 main (int argc, char *const *argv)
    670 {
    671   struct ahc_param param;
    672   struct zzuf_run_params rp;
    673   unsigned int res;
    674   int use_magic_exit_codes;
    675 
    676   zzuf_parse_common_args (argc, argv);
    677   use_put = zzuf_name_has ("_put");
    678   use_chunked_reply = zzuf_name_has ("_get");
    679   if (1 != ((use_put ? 1 : 0) + (use_chunked_reply ? 1 : 0)))
    680   {
    681     fprintf (stderr, "Wrong test name '%s': no or multiple indications "
    682              "for the test type.\n", (NULL != argv[0]) ? argv[0] : "(NULL)");
    683     return 99;
    684   }
    685   use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run;
    686 
    687   res = zzuf_check_runnable ();
    688   if (0 != res)
    689     return use_magic_exit_codes ? (int) res : 0;
    690 
    691   long_ext_body = NULL;
    692   if (! init_long_ext_body ())
    693   {
    694     fprintf (stderr, "malloc() failed at line %d.\n", (int) __LINE__);
    695     return use_magic_exit_codes ? 99 : 0;
    696   }
    697 
    698   /* The self-check needs an unfuzzed channel to MHD, which only exists
    699      when zzuf fuzzes socat instead of this process. */
    700   if (zzuf_run_with_socat && ! zzuf_dry_run)
    701   {
    702     res = run_self_check ();
    703     if (99 == res)
    704     {
    705       free (long_ext_body);
    706       return 99;
    707     }
    708     if (0 != res)
    709     {
    710       free (long_ext_body);
    711       return 1;
    712     }
    713   }
    714 
    715   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    716   param.err_flag = 0;
    717   param.self_check = 0;
    718   param.expect_body = NULL;
    719   param.body_matched = 0;
    720 
    721   memset (&rp, 0, sizeof(rp));
    722   rp.port = zzuf_pick_port (TEST_PORT_OFFSET);
    723   rp.ahc = &ahc_chunked_ext;
    724   rp.ahc_cls = &param;
    725   rp.rcc = &req_completed;
    726   rp.rcc_cls = &param;
    727   rp.mem_limit_override = 0;
    728   rp.sweep_profiles = 1;
    729   rp.profile_start = 0;
    730   rp.client = &client_run;
    731   rp.client_cls = &param;
    732 
    733   res = zzuf_run_polling_modes (&rp);
    734   free (long_ext_body);
    735   long_ext_body = NULL;
    736 
    737   if (99 == res)
    738     return use_magic_exit_codes ? 99 : 0;
    739   if (77 == res)
    740     return use_magic_exit_codes ? 77 : 0;
    741   return (0 == res) ? 0 : 1; /* 0 == pass */
    742 }