libmicrohttpd

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

test_chunked_ext.c (29204B)


      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  * @file test_chunked_ext.c
     22  * @brief Regression test for the handling of the chunk-extension part of
     23  *        the chunk size line of a chunked request body.
     24  *
     25  * When a chunk size line carries a chunk-extension (RFC 9112, Section 7.1.1)
     26  * the length of that line used to be computed as the position of the CR (or
     27  * of the bare LF) instead of the position just after the line delimiter.
     28  * As a result the line delimiter was not consumed and became part of the
     29  * chunk data, shifting all the following body parsing by two (or one) bytes.
     30  * That both corrupts the data given to the application and de-synchronises
     31  * the request/response stream, which can be abused for HTTP request
     32  * smuggling.
     33  *
     34  * Every sub-case sends, in a single write, a chunked request using a
     35  * chunk-extension immediately followed by a pipelined second request on the
     36  * same connection.  The test verifies the exact bytes received by the
     37  * request handler, that exactly one chunked request was processed and that
     38  * the pipelined request was parsed correctly.
     39  *
     40  * The test honours the daemon option matrix of mhd_opt_matrix.h: when one of
     41  * the MHD_TEST_* environment variables selects a profile, its connection
     42  * memory limit, threading mode and polling backend are applied to every
     43  * daemon this test starts.  The *client discipline level* is deliberately
     44  * NOT taken from the profile: it is the very thing each sub-case varies (a
     45  * bare LF is accepted as the chunk size line delimiter only at level -3), so
     46  * the per-sub-case value always wins.  Without those environment variables
     47  * nothing changes.  As the sub-cases need a pool that can still hold the
     48  * request and the reply, a profile asking for less than #MIN_POOL_FOR_TEST
     49  * bytes is raised to that value and the adjustment is reported.
     50  *
     51  * @author Christian Grothoff
     52  */
     53 #include "MHD_config.h"
     54 #include "platform.h"
     55 #include <microhttpd.h>
     56 #include <stdlib.h>
     57 #include <string.h>
     58 #include <stdio.h>
     59 #include <errno.h>
     60 
     61 #ifdef HAVE_STRINGS_H
     62 #include <strings.h>
     63 #endif /* HAVE_STRINGS_H */
     64 
     65 #ifndef WINDOWS
     66 #include <unistd.h>
     67 #endif /* ! WINDOWS */
     68 
     69 #ifdef _WIN32
     70 #ifndef WIN32_LEAN_AND_MEAN
     71 #define WIN32_LEAN_AND_MEAN 1
     72 #endif /* !WIN32_LEAN_AND_MEAN */
     73 #include <windows.h>
     74 #endif /* _WIN32 */
     75 
     76 #ifdef HAVE_SIGNAL_H
     77 #include <signal.h>
     78 #endif /* HAVE_SIGNAL_H */
     79 
     80 #include "mhd_sockets.h" /* only macros used */
     81 #include "mhd_opt_matrix.h"
     82 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this
     83    test into a marked, classifiable test error (TESTING.md, P5). */
     84 #include "mhd_panic_tripwire.h"
     85 
     86 /**
     87  * The smallest connection memory pool this test can work with: the sub-cases
     88  * send a chunked request plus a pipelined second request and expect two
     89  * complete replies, which does not fit into a smaller pool.
     90  */
     91 #define MIN_POOL_FOR_TEST 1024
     92 
     93 #ifndef MHD_STATICSTR_LEN_
     94 /**
     95  * Determine length of static string / macro strings at compile time.
     96  */
     97 #define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1)
     98 #endif /* ! MHD_STATICSTR_LEN_ */
     99 
    100 #if MHD_VERSION >= 0x00097701
    101 #define TEST_MK_RESPONSE(len,str) \
    102   MHD_create_response_from_buffer_static ((len), (str))
    103 #else  /* MHD_VERSION < 0x00097701 */
    104 #define TEST_MK_RESPONSE(len,str) \
    105   MHD_create_response_from_buffer ((len), (void *) (str), \
    106                                    MHD_RESPMEM_PERSISTENT)
    107 #endif /* MHD_VERSION < 0x00097701 */
    108 
    109 /**
    110  * The exit code for a failed test.
    111  */
    112 #define EXIT_FAILED_TEST 1
    113 
    114 /**
    115  * The exit code for a failure of the test framework itself.
    116  */
    117 #define EXIT_HARD_ERROR 99
    118 
    119 /**
    120  * The maximum time (in seconds) to wait for any single socket operation.
    121  */
    122 #define TEST_SOCKET_TIMEOUT_SEC 5
    123 
    124 /**
    125  * The time (in milliseconds) to wait between the two parts of a request
    126  * which is intentionally sent in two writes.
    127  */
    128 #define TEST_SPLIT_DELAY_MS 50
    129 
    130 /**
    131  * The body of the response sent by the test daemon.
    132  */
    133 #define RESP_BODY "OK"
    134 
    135 /**
    136  * The URL of the chunked request.
    137  */
    138 #define URL_FIRST "/first"
    139 
    140 /**
    141  * The URL of the pipelined request.
    142  */
    143 #define URL_SECOND "/second"
    144 
    145 /**
    146  * The head of the chunked request, the chunked body is appended to it.
    147  */
    148 #define REQ_FIRST_HEAD \
    149   "POST " URL_FIRST " HTTP/1.1\r\nHost: h\r\n" \
    150   "Transfer-Encoding: chunked\r\n\r\n"
    151 
    152 /**
    153  * The complete pipelined request, appended after the chunked body.
    154  */
    155 #define REQ_SECOND \
    156   "GET " URL_SECOND " HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n"
    157 
    158 /**
    159  * The number of the requests that could be tracked.
    160  */
    161 #define MAX_TRACKED_REQS 8
    162 
    163 /**
    164  * The maximum size of the tracked request body.
    165  */
    166 #define MAX_TRACKED_BODY 256
    167 
    168 /**
    169  * The data tracked for a single request.
    170  */
    171 struct ReqTrack
    172 {
    173   /**
    174    * The URL of the request.
    175    */
    176   char url[64];
    177 
    178   /**
    179    * The body of the request as given to the request handler.
    180    */
    181   char body[MAX_TRACKED_BODY];
    182 
    183   /**
    184    * The number of bytes in @a body.
    185    */
    186   size_t body_size;
    187 
    188   /**
    189    * Non-zero if the body of the request did not fit in @a body.
    190    */
    191   int body_overflow;
    192 
    193   /**
    194    * Non-zero if the request has been fully received.
    195    */
    196   int completed;
    197 };
    198 
    199 /**
    200  * The tracked requests.
    201  * Written by the daemon thread only, read by the main thread only after
    202  * MHD_stop_daemon() (which joins the daemon thread) has returned.
    203  */
    204 static struct ReqTrack tracked[MAX_TRACKED_REQS];
    205 
    206 /**
    207  * The number of the used elements in #tracked.
    208  */
    209 static unsigned int num_tracked;
    210 
    211 /**
    212  * Be verbose.
    213  */
    214 static int verbose;
    215 
    216 /**
    217  * The profile selected by the environment, NULL if the built-in daemon
    218  * configuration is used (which is the case for a stock "make check").
    219  */
    220 static const struct MHD_OptMatrixProfile *test_prof;
    221 
    222 /**
    223  * The (possibly adjusted) copy of the profile @a test_prof points to.
    224  */
    225 static struct MHD_OptMatrixProfile test_prof_copy;
    226 
    227 
    228 _MHD_NORETURN static void
    229 hard_error (const char *desc,
    230             int line_num)
    231 {
    232   fprintf (stderr,
    233            "HARD ERROR: %s at line %d. Last errno: %d (%s).\n",
    234            desc,
    235            line_num,
    236            (int) errno,
    237            strerror (errno));
    238   fflush (stderr);
    239   exit (EXIT_HARD_ERROR);
    240 }
    241 
    242 
    243 #define hardErrorExit(desc) hard_error ((desc), __LINE__)
    244 
    245 
    246 /**
    247  * Pause the execution for the specified number of milliseconds.
    248  *
    249  * @param ms the number of the milliseconds to sleep
    250  */
    251 static void
    252 test_sleep_ms (unsigned int ms)
    253 {
    254 #if defined(_WIN32) && ! defined(__CYGWIN__)
    255   Sleep ((DWORD) ms);
    256 #elif defined(HAVE_NANOSLEEP)
    257   struct timespec slp;
    258   struct timespec rmn;
    259   int num_retries = 0;
    260 
    261   slp.tv_sec = (time_t) (ms / 1000);
    262   slp.tv_nsec = (long) ((ms % 1000) * 1000000);
    263   while (0 != nanosleep (&slp, &rmn))
    264   {
    265     if (EINTR != errno)
    266       break;
    267     if (8 < num_retries++)
    268       break;
    269     slp = rmn;
    270   }
    271 #elif defined(HAVE_USLEEP)
    272   usleep (((unsigned long) ms) * 1000);
    273 #else  /* ! HAVE_NANOSLEEP && ! HAVE_USLEEP */
    274   (void) ms; /* Mute compiler warning, the test works without the delay */
    275 #endif /* ! HAVE_NANOSLEEP && ! HAVE_USLEEP */
    276 }
    277 
    278 
    279 /**
    280  * The handler of the requests, tracks the URL and the exact body bytes.
    281  */
    282 static enum MHD_Result
    283 ahc_track (void *cls,
    284            struct MHD_Connection *connection,
    285            const char *url,
    286            const char *method,
    287            const char *version,
    288            const char *upload_data,
    289            size_t *upload_data_size,
    290            void **req_cls)
    291 {
    292   struct ReqTrack *trk = (struct ReqTrack *) *req_cls;
    293   struct MHD_Response *resp;
    294   enum MHD_Result ret;
    295 
    296   (void) cls; (void) method; (void) version;
    297 
    298   if (NULL == trk)
    299   {
    300     if (MAX_TRACKED_REQS <= num_tracked)
    301       return MHD_NO; /* Too many requests, the check in the main thread fails */
    302     trk = tracked + num_tracked++;
    303     memset (trk,
    304             0,
    305             sizeof (*trk));
    306     if (sizeof (trk->url) <= strlen (url))
    307       return MHD_NO;
    308     memcpy (trk->url,
    309             url,
    310             strlen (url) + 1);
    311     *req_cls = trk;
    312     return MHD_YES;
    313   }
    314   if (0 != *upload_data_size)
    315   {
    316     if (sizeof (trk->body) < trk->body_size + *upload_data_size)
    317       trk->body_overflow = 1;
    318     else
    319     {
    320       memcpy (trk->body + trk->body_size,
    321               upload_data,
    322               *upload_data_size);
    323       trk->body_size += *upload_data_size;
    324     }
    325     *upload_data_size = 0;
    326     return MHD_YES;
    327   }
    328   trk->completed = 1;
    329   resp = TEST_MK_RESPONSE (MHD_STATICSTR_LEN_ (RESP_BODY),
    330                            RESP_BODY);
    331   if (NULL == resp)
    332     return MHD_NO;
    333   ret = MHD_queue_response (connection,
    334                             MHD_HTTP_OK,
    335                             resp);
    336   MHD_destroy_response (resp);
    337   return ret;
    338 }
    339 
    340 
    341 /**
    342  * Set send and receive timeouts on the socket so that the test cannot
    343  * block indefinitely.
    344  *
    345  * @param sk the socket to set the timeouts on
    346  * @return non-zero if succeed, zero if failed
    347  */
    348 static int
    349 set_socket_timeouts (MHD_socket sk)
    350 {
    351 #if defined(MHD_WINSOCK_SOCKETS)
    352   DWORD tv = (DWORD) (TEST_SOCKET_TIMEOUT_SEC * 1000);
    353 #else  /* ! MHD_WINSOCK_SOCKETS */
    354   struct timeval tv;
    355 
    356   tv.tv_sec = (time_t) TEST_SOCKET_TIMEOUT_SEC;
    357   tv.tv_usec = 0;
    358 #endif /* ! MHD_WINSOCK_SOCKETS */
    359   if (0 != setsockopt (sk,
    360                        SOL_SOCKET,
    361                        SO_RCVTIMEO,
    362                        (const void *) &tv,
    363                        (socklen_t) sizeof (tv)))
    364     return 0;
    365   if (0 != setsockopt (sk,
    366                        SOL_SOCKET,
    367                        SO_SNDTIMEO,
    368                        (const void *) &tv,
    369                        (socklen_t) sizeof (tv)))
    370     return 0;
    371   return ! 0;
    372 }
    373 
    374 
    375 /**
    376  * Open a TCP connection to the given port on the loopback interface.
    377  *
    378  * @param port the port to connect to
    379  * @return the connected socket, MHD_INVALID_SOCKET on failure
    380  */
    381 static MHD_socket
    382 connect_to_port (uint16_t port)
    383 {
    384   MHD_socket sk;
    385   struct sockaddr_in sa;
    386 
    387   sk = socket (PF_INET,
    388                SOCK_STREAM,
    389                0);
    390   if (MHD_INVALID_SOCKET == sk)
    391     return MHD_INVALID_SOCKET;
    392   if (! set_socket_timeouts (sk))
    393   {
    394     (void) MHD_socket_close_ (sk);
    395     return MHD_INVALID_SOCKET;
    396   }
    397   memset (&sa,
    398           0,
    399           sizeof (sa));
    400   sa.sin_family = AF_INET;
    401   sa.sin_port = htons (port);
    402   sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    403   if (0 != connect (sk,
    404                     (const struct sockaddr *) &sa,
    405                     (socklen_t) sizeof (sa)))
    406   {
    407     (void) MHD_socket_close_ (sk);
    408     return MHD_INVALID_SOCKET;
    409   }
    410   return sk;
    411 }
    412 
    413 
    414 /**
    415  * Send all the given data over the socket.
    416  *
    417  * @param sk the socket to use
    418  * @param buf the data to send
    419  * @param size the size of @a buf
    420  * @return non-zero if succeed, zero if failed
    421  */
    422 static int
    423 send_all (MHD_socket sk,
    424           const char *buf,
    425           size_t size)
    426 {
    427   size_t off = 0;
    428 
    429   while (off < size)
    430   {
    431     ssize_t res;
    432 
    433     res = MHD_send_ (sk,
    434                      buf + off,
    435                      size - off);
    436     if (0 > res)
    437     {
    438       if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ()))
    439         continue;
    440       return 0;
    441     }
    442     if (0 == res)
    443       return 0;
    444     off += (size_t) res;
    445   }
    446   return ! 0;
    447 }
    448 
    449 
    450 /**
    451  * Receive data from the socket until the remote side closes the connection,
    452  * the buffer is full or the receive timeout expires.
    453  *
    454  * @param sk the socket to use
    455  * @param[out] buf the buffer to fill
    456  * @param buf_size the size of @a buf, the result is always zero-terminated,
    457  *                 therefore at most @a buf_size minus one bytes are received
    458  * @return the number of the received bytes
    459  */
    460 static size_t
    461 recv_all (MHD_socket sk,
    462           char *buf,
    463           size_t buf_size)
    464 {
    465   size_t off = 0;
    466 
    467   while (off + 1 < buf_size)
    468   {
    469     ssize_t res;
    470 
    471     res = MHD_recv_ (sk,
    472                      buf + off,
    473                      buf_size - 1 - off);
    474     if (0 > res)
    475     {
    476       if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ()))
    477         continue;
    478       break; /* Timeout or error */
    479     }
    480     if (0 == res)
    481       break;   /* The remote side closed the connection */
    482     off += (size_t) res;
    483   }
    484   buf[off] = 0;
    485   return off;
    486 }
    487 
    488 
    489 /**
    490  * Count the number of the non-overlapping occurrences of @a needle
    491  * in @a haystack.
    492  *
    493  * @param haystack the zero-terminated string to search in
    494  * @param needle the zero-terminated string to search for
    495  * @return the number of the found occurrences
    496  */
    497 static unsigned int
    498 count_substr (const char *haystack,
    499               const char *needle)
    500 {
    501   const size_t needle_len = strlen (needle);
    502   unsigned int num = 0;
    503   const char *pos = haystack;
    504 
    505   if (0 == needle_len)
    506     return 0;
    507   while (NULL != (pos = strstr (pos, needle)))
    508   {
    509     ++num;
    510     pos += needle_len;
    511   }
    512   return num;
    513 }
    514 
    515 
    516 /**
    517  * Print the data to the standard error stream, replacing the non-printable
    518  * characters with their escaped hexadecimal form.
    519  *
    520  * @param data the data to print
    521  * @param size the size of @a data
    522  */
    523 static void
    524 print_escaped (const char *data,
    525                size_t size)
    526 {
    527   size_t i;
    528 
    529   for (i = 0; i < size; ++i)
    530   {
    531     const unsigned char chr = (unsigned char) data[i];
    532 
    533     if ((0x20 <= chr) && (0x7e >= chr) && ('\\' != chr))
    534       fputc ((int) chr,
    535              stderr);
    536     else if ('\\' == chr)
    537       fputs ("\\\\",
    538              stderr);
    539     else if ('\r' == chr)
    540       fputs ("\\r",
    541              stderr);
    542     else if ('\n' == chr)
    543       fputs ("\\n",
    544              stderr);
    545     else if ('\t' == chr)
    546       fputs ("\\t",
    547              stderr);
    548     else
    549       fprintf (stderr,
    550                "\\x%02X",
    551                (unsigned int) chr);
    552   }
    553 }
    554 
    555 
    556 /**
    557  * The description of a single sub-case.
    558  */
    559 struct ChunkCase
    560 {
    561   /**
    562    * The human readable name of the sub-case.
    563    */
    564   const char *name;
    565 
    566   /**
    567    * The chunked body of the request, including the terminating chunk and
    568    * the (empty) trailer section.
    569    */
    570   const char *chunked;
    571 
    572   /**
    573    * The exact bytes that the request handler must receive.
    574    */
    575   const char *expected;
    576 
    577   /**
    578    * The number of the bytes in @a expected.
    579    */
    580   size_t expected_size;
    581 
    582   /**
    583    * If non-zero, the request is sent in two writes and this is the number of
    584    * the bytes of @a chunked included in the first write.
    585    */
    586   size_t split_at;
    587 
    588   /**
    589    * The value for MHD_OPTION_CLIENT_DISCIPLINE_LVL.
    590    */
    591   int discipline;
    592 
    593   /**
    594    * Non-zero if the sub-case requires the support of the bare LF as the
    595    * line delimiter of the chunk size line.
    596    */
    597   int need_bare_lf;
    598 };
    599 
    600 #define CHUNK_CASE(nm,ch,exp,spl,dsc,blf) \
    601   { (nm), (ch), (exp), MHD_STATICSTR_LEN_ (exp), (spl), (dsc), (blf) }
    602 
    603 /**
    604  * The sub-cases.
    605  * All of them must be processed identically: the handler must receive
    606  * exactly the expected bytes, and the pipelined request must be processed
    607  * as the second (and the last) request on the connection.
    608  */
    609 static const struct ChunkCase chunkcases[] = {
    610   CHUNK_CASE ("no chunk-extension (control)",
    611               "5\r\nHELLO\r\n0\r\n\r\n",
    612               "HELLO", 0, 0, 0),
    613   CHUNK_CASE ("chunk-extension with a value",
    614               "5;ext=val\r\nHELLO\r\n0\r\n\r\n",
    615               "HELLO", 0, 0, 0),
    616   CHUNK_CASE ("chunk-extension without a value",
    617               "5;ext\r\nHELLO\r\n0\r\n\r\n",
    618               "HELLO", 0, 0, 0),
    619   CHUNK_CASE ("chunk-extension after a bad whitespace (space)",
    620               "5 ;ext=val\r\nHELLO\r\n0\r\n\r\n",
    621               "HELLO", 0, 0, 0),
    622   CHUNK_CASE ("chunk-extension after a bad whitespace (tab)",
    623               "5\t;ext\r\nHELLO\r\n0\r\n\r\n",
    624               "HELLO", 0, 0, 0),
    625   CHUNK_CASE ("chunk-extension with a quoted-string value",
    626               "5;ext=\"quoted; value\"\r\nHELLO\r\n0\r\n\r\n",
    627               "HELLO", 0, 0, 0),
    628   CHUNK_CASE ("several chunk-extensions",
    629               "5;e1=v1;e2=\"v 2\";e3\r\nHELLO\r\n0\r\n\r\n",
    630               "HELLO", 0, 0, 0),
    631   CHUNK_CASE ("two chunks, both with a chunk-extension",
    632               "3;a=b\r\nabc\r\n4;c=d\r\ndefg\r\n0\r\n\r\n",
    633               "abcdefg", 0, 0, 0),
    634   CHUNK_CASE ("two chunks, only the first with a chunk-extension",
    635               "3;a=b\r\nabc\r\n4\r\ndefg\r\n0\r\n\r\n",
    636               "abcdefg", 0, 0, 0),
    637   CHUNK_CASE ("two chunks, only the second with a chunk-extension",
    638               "3\r\nabc\r\n4;c=d\r\ndefg\r\n0\r\n\r\n",
    639               "abcdefg", 0, 0, 0),
    640   CHUNK_CASE ("chunk-extension on the terminating chunk as well",
    641               "3;a=b\r\nabc\r\n4;c=d\r\ndefg\r\n0;last=1\r\n\r\n",
    642               "abcdefg", 0, 0, 0),
    643   CHUNK_CASE ("chunk data with an embedded CRLF",
    644               "7;ext=val\r\nAB\r\nCDE\r\n0\r\n\r\n",
    645               "AB\r\nCDE", 0, 0, 0),
    646   CHUNK_CASE ("chunk-extension line split between two writes",
    647               "5;ext=val\r\nHELLO\r\n0\r\n\r\n",
    648               "HELLO", 4, 0, 0),
    649   CHUNK_CASE ("chunk-extension line delimiter split between two writes",
    650               "5;ext=val\r\nHELLO\r\n0\r\n\r\n",
    651               "HELLO", 10, 0, 0),
    652   CHUNK_CASE ("chunk-extension with a bare LF line delimiter",
    653               "5;ext=val\nHELLO\r\n0\r\n\r\n",
    654               "HELLO", 0, -3, 1),
    655   CHUNK_CASE ("two chunks with a bare LF line delimiter",
    656               "3;a=b\nabc\r\n4;c=d\ndefg\r\n0\r\n\r\n",
    657               "abcdefg", 0, -3, 1)
    658 };
    659 
    660 /**
    661  * The sub-case used to detect whether this build accepts the bare LF as the
    662  * line delimiter of the chunk size line.  It does not use any
    663  * chunk-extension, therefore it is not affected by the tested bug.
    664  */
    665 static const struct ChunkCase bare_lf_probe =
    666   CHUNK_CASE ("bare LF support probe",
    667               "5\nHELLO\r\n0\r\n\r\n",
    668               "HELLO", 0, -3, 0);
    669 
    670 
    671 /**
    672  * Run a single sub-case.
    673  *
    674  * @param cc the sub-case to run
    675  * @param quiet if non-zero, do not report the failures
    676  * @return non-zero if the sub-case succeeded, zero if it failed
    677  */
    678 static int
    679 run_subcase (const struct ChunkCase *cc,
    680              int quiet)
    681 {
    682   struct MHD_Daemon *d;
    683   const union MHD_DaemonInfo *dinfo;
    684   uint16_t port;
    685   MHD_socket sk;
    686   char req[1024];
    687   char resp[4096];
    688   size_t req_size;
    689   size_t chunked_size;
    690   size_t resp_size;
    691   int failed = 0;
    692   int sent_ok;
    693   struct MHD_OptMatrixProfile prof;
    694   struct MHD_OptionItem ops[8];
    695   unsigned int flags;
    696 
    697   chunked_size = strlen (cc->chunked);
    698   req_size = MHD_STATICSTR_LEN_ (REQ_FIRST_HEAD) + chunked_size
    699              + MHD_STATICSTR_LEN_ (REQ_SECOND);
    700   if (sizeof (req) <= req_size)
    701     hardErrorExit ("The request does not fit in the buffer");
    702   memcpy (req,
    703           REQ_FIRST_HEAD,
    704           MHD_STATICSTR_LEN_ (REQ_FIRST_HEAD));
    705   memcpy (req + MHD_STATICSTR_LEN_ (REQ_FIRST_HEAD),
    706           cc->chunked,
    707           chunked_size);
    708   memcpy (req + MHD_STATICSTR_LEN_ (REQ_FIRST_HEAD) + chunked_size,
    709           REQ_SECOND,
    710           MHD_STATICSTR_LEN_ (REQ_SECOND) + 1);
    711 
    712   num_tracked = 0;
    713   memset (tracked,
    714           0,
    715           sizeof (tracked));
    716 
    717   /* The profile (if any) supplies the memory limit, the threading mode and
    718      the polling backend.  The client discipline level always comes from the
    719      sub-case, see the file comment. */
    720   if (NULL != test_prof)
    721     prof = *test_prof;
    722   else
    723   {
    724     memset (&prof, 0, sizeof (prof));
    725     prof.name = "built-in";
    726     prof.threading = MHD_OPT_MATRIX_THR_INTERNAL;
    727     prof.poll_backend = MHD_OPT_MATRIX_POLL_SELECT;
    728   }
    729   prof.discipline_lvl = 0;
    730   prof.use_legacy_strict = 0;
    731   if (0 == mhd_opt_matrix_fill_options (&prof, ops,
    732                                         (unsigned int) (sizeof (ops)
    733                                                         / sizeof (ops[0]))))
    734     hardErrorExit ("The daemon option array is too small");
    735   /* The client of this test is a plain blocking socket client, so external
    736      polling is served with an internal polling thread instead. */
    737   flags = mhd_opt_matrix_daemon_flags (&prof, MHD_USE_ERROR_LOG, 0);
    738 
    739   d = MHD_start_daemon (flags,
    740                         0,
    741                         NULL, NULL,
    742                         &ahc_track, NULL,
    743                         MHD_OPTION_ARRAY, ops,
    744                         MHD_OPTION_CLIENT_DISCIPLINE_LVL, cc->discipline,
    745                         MHD_OPTION_CONNECTION_TIMEOUT,
    746                         (unsigned int) TEST_SOCKET_TIMEOUT_SEC,
    747                         MHD_OPTION_END);
    748   if (NULL == d)
    749     hardErrorExit ("Cannot start the test daemon");
    750   dinfo = MHD_get_daemon_info (d,
    751                                MHD_DAEMON_INFO_BIND_PORT);
    752   if ((NULL == dinfo) || (0 == dinfo->port))
    753   {
    754     MHD_stop_daemon (d);
    755     hardErrorExit ("Cannot detect the port used by the test daemon");
    756   }
    757   port = (uint16_t) dinfo->port;
    758 
    759   sk = connect_to_port (port);
    760   if (MHD_INVALID_SOCKET == sk)
    761   {
    762     MHD_stop_daemon (d);
    763     hardErrorExit ("Cannot connect to the test daemon");
    764   }
    765 
    766   if (0 == cc->split_at)
    767     sent_ok = send_all (sk,
    768                         req,
    769                         req_size);
    770   else
    771   {
    772     const size_t first_part =
    773       MHD_STATICSTR_LEN_ (REQ_FIRST_HEAD) + cc->split_at;
    774 
    775     if (first_part >= req_size)
    776       hardErrorExit ("Wrong 'split_at' value in the sub-case");
    777     sent_ok = send_all (sk,
    778                         req,
    779                         first_part);
    780     if (sent_ok)
    781     {
    782       test_sleep_ms (TEST_SPLIT_DELAY_MS);
    783       sent_ok = send_all (sk,
    784                           req + first_part,
    785                           req_size - first_part);
    786     }
    787   }
    788   if (! sent_ok)
    789   {
    790     if (! quiet)
    791       fprintf (stderr,
    792                "FAILED: sub-case '%s': cannot send the request.\n",
    793                cc->name);
    794     (void) MHD_socket_close_ (sk);
    795     MHD_stop_daemon (d);
    796     return 0;
    797   }
    798   resp_size = recv_all (sk,
    799                         resp,
    800                         sizeof (resp));
    801   (void) MHD_socket_close_ (sk);
    802   /* MHD_stop_daemon() joins the daemon thread, therefore all the data
    803      tracked by the request handler is safely visible afterwards. */
    804   MHD_stop_daemon (d);
    805 
    806   if (2 != num_tracked)
    807   {
    808     if (! quiet)
    809       fprintf (stderr,
    810                "FAILED: sub-case '%s': the number of the processed requests "
    811                "is %u, while exactly 2 requests are expected.\n",
    812                cc->name,
    813                num_tracked);
    814     failed = 1;
    815   }
    816   if (1 <= num_tracked)
    817   {
    818     const struct ReqTrack *const trk = tracked + 0;
    819 
    820     if (0 != strcmp (trk->url,
    821                      URL_FIRST))
    822     {
    823       if (! quiet)
    824         fprintf (stderr,
    825                  "FAILED: sub-case '%s': the URL of the first request is "
    826                  "'%s', while '%s' is expected.\n",
    827                  cc->name,
    828                  trk->url,
    829                  URL_FIRST);
    830       failed = 1;
    831     }
    832     if (0 != trk->body_overflow)
    833     {
    834       if (! quiet)
    835         fprintf (stderr,
    836                  "FAILED: sub-case '%s': the body of the first request is "
    837                  "too large.\n",
    838                  cc->name);
    839       failed = 1;
    840     }
    841     else if ((cc->expected_size != trk->body_size) ||
    842              (0 != memcmp (cc->expected,
    843                            trk->body,
    844                            cc->expected_size)))
    845     {
    846       if (! quiet)
    847       {
    848         fprintf (stderr,
    849                  "FAILED: sub-case '%s': the body of the first request is '",
    850                  cc->name);
    851         print_escaped (trk->body,
    852                        trk->body_size);
    853         fprintf (stderr,
    854                  "' (%u bytes), while '",
    855                  (unsigned) trk->body_size);
    856         print_escaped (cc->expected,
    857                        cc->expected_size);
    858         fprintf (stderr,
    859                  "' (%u bytes) is expected.\n",
    860                  (unsigned) cc->expected_size);
    861       }
    862       failed = 1;
    863     }
    864     if (0 == trk->completed)
    865     {
    866       if (! quiet)
    867         fprintf (stderr,
    868                  "FAILED: sub-case '%s': the first request has not been "
    869                  "completed.\n",
    870                  cc->name);
    871       failed = 1;
    872     }
    873   }
    874   if (2 <= num_tracked)
    875   {
    876     const struct ReqTrack *const trk = tracked + 1;
    877 
    878     if (0 != strcmp (trk->url,
    879                      URL_SECOND))
    880     {
    881       if (! quiet)
    882         fprintf (stderr,
    883                  "FAILED: sub-case '%s': the URL of the pipelined request is "
    884                  "'%s', while '%s' is expected. The request stream has been "
    885                  "de-synchronised.\n",
    886                  cc->name,
    887                  trk->url,
    888                  URL_SECOND);
    889       failed = 1;
    890     }
    891     if ((0 != trk->body_size) || (0 != trk->body_overflow))
    892     {
    893       if (! quiet)
    894         fprintf (stderr,
    895                  "FAILED: sub-case '%s': the pipelined request has a body of "
    896                  "%u bytes, while no body is expected.\n",
    897                  cc->name,
    898                  (unsigned) trk->body_size);
    899       failed = 1;
    900     }
    901     if (0 == trk->completed)
    902     {
    903       if (! quiet)
    904         fprintf (stderr,
    905                  "FAILED: sub-case '%s': the pipelined request has not been "
    906                  "completed.\n",
    907                  cc->name);
    908       failed = 1;
    909     }
    910   }
    911   if (2 != count_substr (resp,
    912                          "HTTP/1.1 "))
    913   {
    914     if (! quiet)
    915       fprintf (stderr,
    916                "FAILED: sub-case '%s': the number of the replies is %u, "
    917                "while exactly 2 replies are expected. The reply stream is:\n"
    918                "%.*s\n",
    919                cc->name,
    920                count_substr (resp, "HTTP/1.1 "),
    921                (int) resp_size,
    922                resp);
    923     failed = 1;
    924   }
    925   else if (2 != count_substr (resp,
    926                               "HTTP/1.1 200 "))
    927   {
    928     if (! quiet)
    929       fprintf (stderr,
    930                "FAILED: sub-case '%s': not all replies have the '200' status "
    931                "code. The reply stream is:\n%.*s\n",
    932                cc->name,
    933                (int) resp_size,
    934                resp);
    935     failed = 1;
    936   }
    937 
    938   if ((0 == failed) && verbose)
    939     printf ("PASSED: %s\n",
    940             cc->name);
    941   return ! failed;
    942 }
    943 
    944 
    945 int
    946 main (int argc, char *const *argv)
    947 {
    948   unsigned int num_failed = 0;
    949   unsigned int num_skipped = 0;
    950   int bare_lf_supported;
    951   size_t i;
    952 
    953   verbose = 0;
    954   for (i = 1; i < (size_t) argc; ++i)
    955   {
    956     if ((0 == strcmp (argv[i], "-v")) ||
    957         (0 == strcmp (argv[i], "--verbose")))
    958       verbose = 1;
    959   }
    960 
    961   if (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_AUTOSUPPRESS_SIGPIPE))
    962   {
    963 #if defined(HAVE_SIGNAL_H) && defined(SIGPIPE)
    964     if (SIG_ERR == signal (SIGPIPE, SIG_IGN))
    965       hardErrorExit ("Cannot suppress the SIGPIPE signal");
    966 #else  /* ! HAVE_SIGNAL_H || ! SIGPIPE */
    967     fprintf (stderr,
    968              "Cannot suppress the SIGPIPE signal.\n");
    969 #endif /* ! HAVE_SIGNAL_H || ! SIGPIPE */
    970   }
    971 
    972   mhd_opt_matrix_print_notice ("test_chunked_ext");
    973   test_prof = mhd_opt_matrix_from_env ();
    974   if (NULL != test_prof)
    975   {
    976     char desc[256];
    977 
    978     test_prof_copy = *test_prof;
    979     test_prof = &test_prof_copy;
    980     if (! mhd_opt_matrix_profile_supported (test_prof))
    981     {
    982       printf ("test_chunked_ext: the selected profile %s is not supported by "
    983               "this build, the test is skipped.\n",
    984               mhd_opt_matrix_describe (test_prof, desc, sizeof (desc)));
    985       return 77;
    986     }
    987     if (mhd_opt_matrix_raise_mem_limit (&test_prof_copy, MIN_POOL_FOR_TEST))
    988       printf ("test_chunked_ext: the connection memory limit of the profile "
    989               "was raised to %u, the smallest pool this test can work "
    990               "with.\n", (unsigned) MIN_POOL_FOR_TEST);
    991   }
    992 
    993   /* Detect whether the bare LF is accepted as the line delimiter of the
    994      chunk size line with the lowest client discipline level. */
    995   bare_lf_supported = run_subcase (&bare_lf_probe,
    996                                    1);
    997   if ((! bare_lf_supported) && verbose)
    998     printf ("The bare LF as the chunk size line delimiter is not supported "
    999             "by this build, the related sub-cases are skipped.\n");
   1000 
   1001   for (i = 0; i < sizeof (chunkcases) / sizeof (chunkcases[0]); ++i)
   1002   {
   1003     const struct ChunkCase *const cc = chunkcases + i;
   1004 
   1005     if ((0 != cc->need_bare_lf) && (! bare_lf_supported))
   1006     {
   1007       ++num_skipped;
   1008       continue;
   1009     }
   1010     /* Report the sub-case before running it, so that the failing sub-case
   1011        can be identified even if the daemon aborts the process. */
   1012     fprintf (stderr,
   1013              "Running sub-case: %s\n",
   1014              cc->name);
   1015     fflush (stderr);
   1016     if (! run_subcase (cc,
   1017                        0))
   1018       ++num_failed;
   1019   }
   1020 
   1021   if (0 != num_failed)
   1022   {
   1023     fprintf (stderr,
   1024              "FAILED: %u sub-case(s) failed.\n",
   1025              num_failed);
   1026     return EXIT_FAILED_TEST;
   1027   }
   1028   if (verbose)
   1029     printf ("All sub-cases passed (%u sub-case(s) skipped).\n",
   1030             num_skipped);
   1031   return 0;
   1032 }