libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

test_ram.c (17223B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2025 Christian Grothoff
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file test_ram.c
     41  * @brief tests handling of memory pool exhaustion
     42  * @author Christian Grothoff
     43  */
     44 #include <stdio.h>
     45 #include <stdbool.h>
     46 #include <errno.h>
     47 #include <string.h>
     48 #include <stdlib.h>
     49 #include <unistd.h>
     50 #include <arpa/inet.h>
     51 #include <netinet/ip.h>
     52 #include "microhttpd2.h"
     53 
     54 
     55 /**
     56  * How big do we make the MHD buffer? Use a small value so we
     57  * can trigger OOM in a reasonable amount of time.
     58  */
     59 #define BUFFER_SIZE 2048
     60 
     61 /**
     62  * What is the step size. Should eventually use 1, but
     63  * as long as we get tons of failures, a larger step size
     64  * is probably nicer.
     65  */
     66 #define STEP 71
     67 
     68 /**
     69  * Our port.
     70  */
     71 static uint16_t port;
     72 
     73 /**
     74  * Set to true once we hit the out-of-memory condition.
     75  */
     76 static bool out_of_memory;
     77 
     78 /**
     79  * Callback used by libmicrohttpd in order to obtain content.  The
     80  * callback is to copy at most @a max bytes of content into @a buf or
     81  * provide zero-copy data for #MHD_DCC_action_continue_zc().
     82  *
     83  * @param dyn_cont_cls closure argument to the callback
     84  * @param ctx the context to produce the action to return,
     85  *            the pointer is only valid until the callback returns
     86  * @param pos position in the datastream to access;
     87  *        note that if a `struct MHD_Response` object is re-used,
     88  *        it is possible for the same content reader to
     89  *        be queried multiple times for the same data;
     90  *        however, if a `struct MHD_Response` is not re-used,
     91  *        libmicrohttpd guarantees that "pos" will be
     92  *        the sum of all data sizes provided by this callback
     93  * @param[out] buf where to copy the data
     94  * @param max maximum number of bytes to copy to @a buf (size of @a buf),
     95               if the size of the content of the response is known then size
     96               of the buffer is never larger than amount of the content left
     97  * @return action to use,
     98  *         NULL in case of any error (the response will be aborted)
     99  */
    100 static const struct MHD_DynamicContentCreatorAction *
    101 dyn_cc (void *dyn_cont_cls,
    102         struct MHD_DynamicContentCreatorContext *ctx,
    103         uint_fast64_t pos,
    104         void *buf,
    105         size_t max)
    106 {
    107   int *flag = dyn_cont_cls;
    108   struct MHD_NameValueCStr footer = {
    109     .name = "Footer",
    110     .value = "Value"
    111   };
    112 
    113   if (0 == *flag)
    114     return MHD_DCC_action_finish_with_footer (ctx,
    115                                               1,
    116                                               &footer);
    117   (*flag) = 0;
    118   memset (buf,
    119           'a',
    120           max);
    121   return MHD_DCC_action_continue (ctx,
    122                                   max);
    123 }
    124 
    125 
    126 /**
    127  * This method is called by libmicrohttpd when response with dynamic content
    128  * is being destroyed.  It should be used to free resources associated
    129  * with the dynamic content.
    130  *
    131  * @param[in] free_cls closure
    132  * @ingroup response
    133  */
    134 static void
    135 dyn_cc_free (void *free_cls)
    136 {
    137   free (free_cls);
    138 }
    139 
    140 
    141 /**
    142  * Function to process data uploaded by a client.
    143  *
    144  * @param upload_cls the argument given together with the function
    145  *                   pointer when the handler was registered with MHD
    146  * @param request the request is being processed
    147  * @param content_data_size the size of the @a content_data,
    148  *                          zero when all data have been processed
    149  * @param[in] content_data the uploaded content data,
    150  *                         may be modified in the callback,
    151  *                         valid only until return from the callback,
    152  *                         NULL when all data have been processed
    153  * @return action specifying how to proceed:
    154  *         #MHD_upload_action_continue() to continue upload (for incremental
    155  *         upload processing only),
    156  *         #MHD_upload_action_suspend() to stop reading the upload until
    157  *         the request is resumed,
    158  *         #MHD_upload_action_abort_request() to close the socket,
    159  *         or a response to discard the rest of the upload and transmit
    160  *         the response
    161  * @ingroup action
    162  */
    163 static const struct MHD_UploadAction *
    164 upload_cb (void *upload_cls,
    165            struct MHD_Request *request,
    166            size_t content_data_size,
    167            void *content_data)
    168 {
    169   int *flag;
    170 
    171   (void) upload_cls;
    172   (void) content_data_size;
    173   (void) content_data;
    174   flag = malloc (sizeof (int));
    175   *flag = 1;
    176 
    177   return MHD_upload_action_from_response (
    178     request,
    179     MHD_response_from_callback (MHD_HTTP_STATUS_OK,
    180                                 MHD_SIZE_UNKNOWN,
    181                                 &dyn_cc,
    182                                 flag,
    183                                 &dyn_cc_free));
    184 }
    185 
    186 
    187 /**
    188  * A client has requested the given url using the given method
    189  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    190  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
    191  * If @a upload_size is not zero and response action is provided by this
    192  * callback, then upload will be discarded and the stream (the connection for
    193  * HTTP/1.1) will be closed after sending the response.
    194  *
    195  * @param cls argument given together with the function
    196  *        pointer when the handler was registered with MHD
    197  * @param request the request object
    198  * @param path the requested uri (without arguments after "?")
    199  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    200  *        #MHD_HTTP_METHOD_PUT, etc.)
    201  * @param upload_size the size of the message upload content payload,
    202  *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
    203  *                    final chunk has not been processed yet)
    204  * @return action how to proceed, NULL
    205  *         if the request must be aborted due to a serious
    206  *         error while handling the request (implies closure
    207  *         of underling data stream, for HTTP/1.1 it means
    208  *         socket closure).
    209  */
    210 static const struct MHD_Action *
    211 server_req_cb (void *cls,
    212                struct MHD_Request *MHD_RESTRICT request,
    213                const struct MHD_String *MHD_RESTRICT path,
    214                enum MHD_HTTP_Method method,
    215                uint_fast64_t upload_size)
    216 {
    217   (void) path;
    218   (void) method;
    219   (void) upload_size;
    220   return MHD_action_process_upload_full (request,
    221                                          upload_size,
    222                                          &upload_cb,
    223                                          NULL);
    224 }
    225 
    226 
    227 /**
    228  * Helper function to deal with partial writes.
    229  * Fails hard (calls exit() on failures)!
    230  *
    231  * @param fd where to write to
    232  * @param buf what to write
    233  * @param buf_size number of bytes in @a buf
    234  */
    235 static void
    236 write_all (int fd,
    237            const void *buf,
    238            size_t buf_size)
    239 {
    240   const char *cbuf = buf;
    241   size_t off;
    242 
    243   off = 0;
    244   while (off < buf_size)
    245   {
    246     ssize_t ret;
    247 
    248     ret = write (fd,
    249                  &cbuf[off],
    250                  buf_size - off);
    251     if (ret <= 0)
    252     {
    253       fprintf (stderr,
    254                "Writing %u bytes to %d failed: %s\n",
    255                (unsigned int) (buf_size - off),
    256                fd,
    257                strerror (errno));
    258       exit (1);
    259     }
    260     off += ret;
    261   }
    262 }
    263 
    264 
    265 static int
    266 run_test (unsigned int url_len,
    267           unsigned int query_len,
    268           unsigned int header_len,
    269           unsigned int cookie_len,
    270           unsigned int body_len)
    271 {
    272   char filler[BUFFER_SIZE + 1];
    273   int s;
    274 
    275   out_of_memory = false;
    276   memset (filler,
    277           'a',
    278           BUFFER_SIZE);
    279   filler[BUFFER_SIZE] = '\0'; /* just to be conservative */
    280   s = socket (AF_INET,
    281               SOCK_STREAM,
    282               0);
    283   if (-1 == s)
    284   {
    285     fprintf (stderr,
    286              "socket() failed: %s\n",
    287              strerror (errno));
    288     return -1;
    289   }
    290 
    291   {
    292     struct sockaddr_in sa = {
    293       .sin_family = AF_INET,
    294       .sin_port = htons (port),
    295     };
    296     inet_pton (AF_INET,
    297                "127.0.0.1",
    298                &sa.sin_addr);
    299     if (0 != connect (s,
    300                       (struct sockaddr *) &sa,
    301                       sizeof (sa)))
    302     {
    303       fprintf (stderr,
    304                "bind() failed: %s\n",
    305                strerror (errno));
    306       close (s);
    307       return -1;
    308     }
    309   }
    310 
    311   {
    312     char upload[BUFFER_SIZE * 2];
    313     int iret;
    314 
    315     iret = snprintf (upload,
    316                      sizeof (upload),
    317                      "PUT /%.*s?q=%.*s HTTP/1.0\r\n"
    318                      "Content-Length: %u\r\n"
    319                      "Key: %.*s\r\n"
    320                      "Cookie: a=%.*s\r\n\r\n"
    321                      "%.*s",
    322                      (int) url_len,
    323                      filler,
    324                      (int) query_len,
    325                      filler,
    326                      body_len,
    327                      (int) header_len,
    328                      filler,
    329                      (int) cookie_len,
    330                      filler,
    331                      (int) body_len,
    332                      filler);
    333     if ( (-1 == iret) ||
    334          (iret > sizeof (upload)) )
    335     {
    336       fprintf (stderr,
    337                "failed to build request buffer: %d\n",
    338                iret);
    339       close (s);
    340       return -1;
    341     }
    342     write_all (s,
    343                upload,
    344                strlen (upload));
    345   }
    346   /* read and discard response */
    347   {
    348     bool got_data = false;
    349     bool nice = false;
    350     char dummy[16 * 1024];
    351     int flags = 0;
    352 
    353     while (1)
    354     {
    355       ssize_t res;
    356 
    357       res = recv (s,
    358                   &dummy,
    359                   sizeof (dummy),
    360                   flags);
    361       flags = MSG_DONTWAIT;
    362       if (res > 0)
    363       {
    364         got_data = true;
    365         dummy[res] = '\0';
    366         /* FIXME: allow other "too large" responses to also count as
    367            'nice' here */
    368         if (NULL !=
    369             strstr (dummy,
    370                     "431 Request Header Fields Too Large"))
    371           nice = true;
    372       }
    373       if (res <= 0)
    374         break;
    375     }
    376     if (nice)
    377       out_of_memory = true;
    378     if (! got_data)
    379     {
    380       out_of_memory = true;
    381       fprintf (stderr,
    382                "Response was not nice (%u/%u/%u/%u/%u)\n",
    383                url_len,
    384                query_len,
    385                header_len,
    386                cookie_len,
    387                body_len);
    388     }
    389   }
    390   close (s);
    391   return out_of_memory ? 1 : 0;
    392 }
    393 
    394 
    395 static int
    396 test_url (void)
    397 {
    398   bool oom_hit;
    399 
    400   oom_hit = false;
    401   for (unsigned int i = 0;
    402        i < BUFFER_SIZE;
    403        i += STEP)
    404   {
    405     int ret;
    406 
    407     ret = run_test (i, 0, 0, 0, 0);
    408     if (-1 == ret)
    409     {
    410       return 1;
    411     }
    412     if (1 == ret)
    413     {
    414       oom_hit = true;
    415     }
    416     if ( (oom_hit) && (1 != ret) )
    417     {
    418       fprintf (stderr,
    419                "Strange: OOM stopped at %u after being hit earlier (url)?\n",
    420                i);
    421     }
    422   }
    423   if (! oom_hit)
    424   {
    425     fprintf (stderr,
    426              "Failed to trigger OOM condition via URL\n");
    427     return 1;
    428   }
    429   return 0;
    430 }
    431 
    432 
    433 static int
    434 test_query (void)
    435 {
    436   bool oom_hit;
    437 
    438   oom_hit = false;
    439   for (unsigned int i = 0;
    440        i < BUFFER_SIZE;
    441        i += STEP)
    442   {
    443     int ret;
    444 
    445     ret = run_test (0, i, 0, 0, 0);
    446     if (-1 == ret)
    447     {
    448       return 1;
    449     }
    450     if (1 == ret)
    451     {
    452       oom_hit = true;
    453     }
    454     if ( (oom_hit) && (1 != ret) )
    455     {
    456       fprintf (stderr,
    457                "Strange: OOM stopped at %u after being hit earlier (query)?\n",
    458                i);
    459     }
    460   }
    461   if (! oom_hit)
    462   {
    463     fprintf (stderr,
    464              "Failed to trigger OOM condition via query\n");
    465     return 1;
    466   }
    467   return 0;
    468 }
    469 
    470 
    471 static int
    472 test_header (void)
    473 {
    474   bool oom_hit;
    475 
    476   oom_hit = false;
    477   for (unsigned int i = 0;
    478        i < BUFFER_SIZE;
    479        i += STEP)
    480   {
    481     int ret;
    482 
    483     ret = run_test (0, 0, i, 0, 0);
    484     if (-1 == ret)
    485     {
    486       return 1;
    487     }
    488     if (1 == ret)
    489     {
    490       oom_hit = true;
    491     }
    492     if ( (oom_hit) && (1 != ret) )
    493     {
    494       fprintf (stderr,
    495                "Strange: OOM stopped at %u after being hit earlier (header)?\n",
    496                i);
    497     }
    498   }
    499   if (! oom_hit)
    500   {
    501     fprintf (stderr,
    502              "Failed to trigger OOM condition via header\n");
    503     return 1;
    504   }
    505   return 0;
    506 }
    507 
    508 
    509 static int
    510 test_cookie (void)
    511 {
    512   bool oom_hit;
    513 
    514   oom_hit = false;
    515   for (unsigned int i = 0;
    516        i < BUFFER_SIZE;
    517        i += STEP)
    518   {
    519     int ret;
    520 
    521     ret = run_test (0, 0, 0, i, 0);
    522     if (-1 == ret)
    523     {
    524       return 1;
    525     }
    526     if (1 == ret)
    527     {
    528       oom_hit = true;
    529     }
    530     if ( (oom_hit) && (1 != ret) )
    531     {
    532       fprintf (stderr,
    533                "Strange: OOM stopped at %u after being hit earlier (cookie)?\n",
    534                i);
    535     }
    536   }
    537   if (! oom_hit)
    538   {
    539     fprintf (stderr,
    540              "Failed to trigger OOM condition via cookie\n");
    541     return 1;
    542   }
    543   return 0;
    544 }
    545 
    546 
    547 static int
    548 test_body (void)
    549 {
    550   bool oom_hit;
    551 
    552   oom_hit = false;
    553   for (unsigned int i = 0;
    554        i < BUFFER_SIZE;
    555        i += STEP)
    556   {
    557     int ret;
    558 
    559     ret = run_test (0, 0, 0, 0, i);
    560     if (-1 == ret)
    561     {
    562       return 1;
    563     }
    564     if (1 == ret)
    565     {
    566       oom_hit = true;
    567     }
    568     if ( (oom_hit) && (1 != ret) )
    569     {
    570       fprintf (stderr,
    571                "Strange: OOM stopped at %u after being hit earlier (body)?\n",
    572                i);
    573     }
    574   }
    575   if (! oom_hit)
    576   {
    577     fprintf (stderr,
    578              "Failed to trigger OOM condition via body\n");
    579     return 1;
    580   }
    581   return 0;
    582 }
    583 
    584 
    585 static int
    586 test_mix (void)
    587 {
    588   bool oom_hit;
    589 
    590   /* mix and match path */
    591   for (unsigned int i = 0;
    592        i < BUFFER_SIZE;
    593        i += STEP)
    594   {
    595     int ret;
    596 
    597     ret = run_test (i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1);
    598     if (-1 == ret)
    599     {
    600       return 1;
    601     }
    602     if (1 == ret)
    603     {
    604       oom_hit = true;
    605     }
    606     if ( (oom_hit) && (1 != ret) )
    607     {
    608       fprintf (stderr,
    609                "Strange: OOM stopped at %u after being hit earlier (mix)?\n",
    610                i);
    611     }
    612   }
    613   if (! oom_hit)
    614   {
    615     fprintf (stderr,
    616              "Failed to trigger OOM condition in mix-and-match\n");
    617     return 1;
    618   }
    619 
    620 
    621   return 0;
    622 }
    623 
    624 
    625 static int
    626 run_tests (void)
    627 {
    628   int ret = 0;
    629 
    630 #if 1
    631   ret |= test_url ();
    632   ret |= test_query ();
    633   ret |= test_header ();
    634   ret |= test_cookie ();
    635   ret |= test_body ();
    636   ret |= test_mix ();
    637 #endif
    638   return ret;
    639 }
    640 
    641 
    642 static void
    643 no_log (void *cls,
    644         enum MHD_StatusCode sc,
    645         const char *fm,
    646         va_list ap)
    647 {
    648   /* intentionally empty */
    649 }
    650 
    651 
    652 int
    653 main ()
    654 {
    655   struct MHD_Daemon *d;
    656 
    657   d = MHD_daemon_create (&server_req_cb,
    658                          NULL);
    659   if (MHD_SC_OK !=
    660       MHD_DAEMON_SET_OPTIONS (
    661         d,
    662         MHD_D_OPTION_WM_WORKER_THREADS (2),
    663         MHD_D_OPTION_LOG_CALLBACK (&no_log, NULL),
    664         MHD_D_OPTION_CONN_MEMORY_LIMIT (BUFFER_SIZE),
    665         MHD_D_OPTION_DEFAULT_TIMEOUT (1),
    666         MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
    667                                 0)))
    668   {
    669     fprintf (stderr,
    670              "Failed to configure daemon!");
    671     return 1;
    672   }
    673 
    674   {
    675     enum MHD_StatusCode sc;
    676 
    677     sc = MHD_daemon_start (d);
    678     if (MHD_SC_OK != sc)
    679     {
    680 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED
    681       fprintf (stderr,
    682                "Failed to start server: %s\n",
    683                MHD_status_code_to_string_lazy (sc));
    684 #else
    685       fprintf (stderr,
    686                "Failed to start server: %u\n",
    687                (unsigned int) sc);
    688 #endif
    689       MHD_daemon_destroy (d);
    690       return 1;
    691     }
    692   }
    693 
    694   {
    695     union MHD_DaemonInfoFixedData info;
    696     enum MHD_StatusCode sc;
    697 
    698     sc = MHD_daemon_get_info_fixed (
    699       d,
    700       MHD_DAEMON_INFO_FIXED_BIND_PORT,
    701       &info);
    702     if (MHD_SC_OK != sc)
    703     {
    704       fprintf (stderr,
    705                "Failed to determine our port: %u\n",
    706                (unsigned int) sc);
    707       MHD_daemon_destroy (d);
    708       return 1;
    709     }
    710     port = info.v_bind_port_uint16;
    711   }
    712 
    713   {
    714     int result;
    715 
    716     result = run_tests ();
    717     MHD_daemon_destroy (d);
    718     return result;
    719   }
    720 }