libmicrohttpd2

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

commit a8d2956096874354248f1dcdb86112dd2e7a6606
parent c545756c6a22f620f6c75d2ba6f54e6df5b7dc04
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Wed, 31 Dec 2025 16:22:34 +0100

Renamed test_ram -> test_oom

Diffstat:
Msrc/tests/raw/Makefile.am | 11+----------
Asrc/tests/raw/test_oom.c | 726+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/tests/raw/test_ram.c | 726-------------------------------------------------------------------------------
3 files changed, 727 insertions(+), 736 deletions(-)

diff --git a/src/tests/raw/Makefile.am b/src/tests/raw/Makefile.am @@ -27,16 +27,7 @@ $(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile check_PROGRAMS = \ test_incompatible \ - test_ram \ + test_oom \ test_raw TESTS = $(check_PROGRAMS) - -test_incompatible_SOURCES = \ - test_incompatible.c - -test_ram_SOURCES = \ - test_ram.c - -test_raw_SOURCES = \ - test_raw.c diff --git a/src/tests/raw/test_oom.c b/src/tests/raw/test_oom.c @@ -0,0 +1,726 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2025 Christian Grothoff + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file test_oom.c + * @brief tests handling of memory pool exhaustion + * @author Christian Grothoff + */ +#include <stdio.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include "microhttpd2.h" + + +/** + * How big do we make the MHD buffer? Use a small value so we + * can trigger OOM in a reasonable amount of time. + */ +#define BUFFER_SIZE 2048 + +/** + * What is the step size. Should eventually use 1, but + * as long as we get tons of failures, a larger step size + * is probably nicer. + */ +#define STEP 71 + +/** + * Our port. + */ +static uint16_t port; + +/** + * Set to true once we hit the out-of-memory condition. + */ +static bool out_of_memory; + +/** + * Callback used by libmicrohttpd in order to obtain content. The + * callback is to copy at most @a max bytes of content into @a buf or + * provide zero-copy data for #MHD_DCC_action_continue_zc(). + * + * @param dyn_cont_cls closure argument to the callback + * @param ctx the context to produce the action to return, + * the pointer is only valid until the callback returns + * @param pos position in the datastream to access; + * note that if a `struct MHD_Response` object is re-used, + * it is possible for the same content reader to + * be queried multiple times for the same data; + * however, if a `struct MHD_Response` is not re-used, + * libmicrohttpd guarantees that "pos" will be + * the sum of all data sizes provided by this callback + * @param[out] buf where to copy the data + * @param max maximum number of bytes to copy to @a buf (size of @a buf), + if the size of the content of the response is known then size + of the buffer is never larger than amount of the content left + * @return action to use, + * NULL in case of any error (the response will be aborted) + */ +static const struct MHD_DynamicContentCreatorAction * +dyn_cc (void *dyn_cont_cls, + struct MHD_DynamicContentCreatorContext *ctx, + uint_fast64_t pos, + void *buf, + size_t max) +{ + int *flag = dyn_cont_cls; + struct MHD_NameValueCStr footer = { + .name = "Footer", + .value = "Value" + }; + + if (0 == *flag) + return MHD_DCC_action_finish_with_footer (ctx, + 0, + &footer); + (*flag) = 0; + memset (buf, + 'a', + max); + return MHD_DCC_action_continue (ctx, + max); +} + + +/** + * This method is called by libmicrohttpd when response with dynamic content + * is being destroyed. It should be used to free resources associated + * with the dynamic content. + * + * @param[in] free_cls closure + * @ingroup response + */ +static void +dyn_cc_free (void *free_cls) +{ + free (free_cls); +} + + +/** + * Function to process data uploaded by a client. + * + * @param upload_cls the argument given together with the function + * pointer when the handler was registered with MHD + * @param request the request is being processed + * @param content_data_size the size of the @a content_data, + * zero when all data have been processed + * @param[in] content_data the uploaded content data, + * may be modified in the callback, + * valid only until return from the callback, + * NULL when all data have been processed + * @return action specifying how to proceed: + * #MHD_upload_action_continue() to continue upload (for incremental + * upload processing only), + * #MHD_upload_action_suspend() to stop reading the upload until + * the request is resumed, + * #MHD_upload_action_abort_request() to close the socket, + * or a response to discard the rest of the upload and transmit + * the response + * @ingroup action + */ +static const struct MHD_UploadAction * +upload_cb (void *upload_cls, + struct MHD_Request *request, + size_t content_data_size, + void *content_data) +{ + int *flag; + + (void) upload_cls; + (void) content_data_size; + (void) content_data; + flag = malloc (sizeof (int)); + *flag = 1; + + return MHD_upload_action_from_response ( + request, + MHD_response_from_callback (MHD_HTTP_STATUS_OK, + MHD_SIZE_UNKNOWN, + &dyn_cc, + flag, + &dyn_cc_free)); +} + + +/** + * A client has requested the given url using the given method + * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, + * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). + * If @a upload_size is not zero and response action is provided by this + * callback, then upload will be discarded and the stream (the connection for + * HTTP/1.1) will be closed after sending the response. + * + * @param cls argument given together with the function + * pointer when the handler was registered with MHD + * @param request the request object + * @param path the requested uri (without arguments after "?") + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param upload_size the size of the message upload content payload, + * #MHD_SIZE_UNKNOWN for chunked uploads (if the + * final chunk has not been processed yet) + * @return action how to proceed, NULL + * if the request must be aborted due to a serious + * error while handling the request (implies closure + * of underling data stream, for HTTP/1.1 it means + * socket closure). + */ +static const struct MHD_Action * +server_req_cb (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; + (void) path; + (void) method; + (void) upload_size; + return MHD_action_process_upload_full (request, + upload_size, + &upload_cb, + NULL); +} + + +/** + * Helper function to deal with partial writes. + * Fails hard (calls exit() on failures)! + * + * @param fd where to write to + * @param buf what to write + * @param buf_size number of bytes in @a buf + */ +static void +write_all (int fd, + const void *buf, + size_t buf_size) +{ + const char *cbuf = (const char *) buf; + size_t off; + + off = 0; + while (off < buf_size) + { + ssize_t ret; + + ret = write (fd, + &cbuf[off], + buf_size - off); + if (ret <= 0) + { + fprintf (stderr, + "Writing %u bytes to %d failed: %s\n", + (unsigned int) (buf_size - off), + fd, + strerror (errno)); + exit (1); + } + off += (size_t) ret; + } +} + + +static int +run_test (unsigned int url_len, + unsigned int query_len, + unsigned int header_len, + unsigned int cookie_len, + unsigned int body_len) +{ + char filler[BUFFER_SIZE + 1]; + int s; + + out_of_memory = false; + memset (filler, + 'a', + BUFFER_SIZE); + filler[BUFFER_SIZE] = '\0'; /* just to be conservative */ + s = socket (AF_INET, + SOCK_STREAM, + 0); + if (-1 == s) + { + fprintf (stderr, + "socket() failed: %s\n", + strerror (errno)); + return -1; + } + + { + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons (port), + }; + inet_pton (AF_INET, + "127.0.0.1", + &sa.sin_addr); + if (0 != connect (s, + (struct sockaddr *) &sa, + sizeof (sa))) + { + fprintf (stderr, + "bind() failed: %s\n", + strerror (errno)); + close (s); + return -1; + } + } + + { + char upload[BUFFER_SIZE * 2]; + int iret; + + iret = snprintf (upload, + sizeof (upload), + "PUT /%.*s?q=%.*s HTTP/1.0\r\n" + "Content-Length: %u\r\n" + "Key: %.*s\r\n" + "Cookie: a=%.*s\r\n\r\n" + "%.*s", + (int) url_len, + filler, + (int) query_len, + filler, + body_len, + (int) header_len, + filler, + (int) cookie_len, + filler, + (int) body_len, + filler); + if ( (-1 == iret) || + ( ((size_t) iret) > sizeof (upload)) ) + { + fprintf (stderr, + "failed to build request buffer: %d\n", + iret); + close (s); + return -1; + } + write_all (s, + upload, + strlen (upload)); + } + /* read and discard response */ + { + bool got_data = false; + bool nice = false; + char dummy[16 * 1024]; + int flags = 0; + + while (1) + { + ssize_t res; + + res = recv (s, + &dummy, + sizeof (dummy), + flags); + flags = MSG_DONTWAIT; + if (res > 0) + { + got_data = true; + dummy[res] = '\0'; + /* FIXME: allow other "too large" responses to also count as + 'nice' here */ + if (NULL != + strstr (dummy, + "431 Request Header Fields Too Large")) + nice = true; + } + if (res <= 0) + break; + } + if (nice) + out_of_memory = true; + if (! got_data) + { + out_of_memory = true; + fprintf (stderr, + "Response was not nice (%u/%u/%u/%u/%u)\n", + url_len, + query_len, + header_len, + cookie_len, + body_len); + } + } + close (s); + return out_of_memory ? 1 : 0; +} + + +static int +test_url (void) +{ + bool oom_hit; + + oom_hit = false; + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (i, 0, 0, 0, 0); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (url)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition via URL\n"); + return 1; + } + return 0; +} + + +static int +test_query (void) +{ + bool oom_hit; + + oom_hit = false; + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (0, i, 0, 0, 0); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (query)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition via query\n"); + return 1; + } + return 0; +} + + +static int +test_header (void) +{ + bool oom_hit; + + oom_hit = false; + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (0, 0, i, 0, 0); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (header)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition via header\n"); + return 1; + } + return 0; +} + + +static int +test_cookie (void) +{ + bool oom_hit; + + oom_hit = false; + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (0, 0, 0, i, 0); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (cookie)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition via cookie\n"); + return 1; + } + return 0; +} + + +static int +test_body (void) +{ + bool oom_hit; + + oom_hit = false; + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (0, 0, 0, 0, i); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (body)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition via body\n"); + return 1; + } + return 0; +} + + +static int +test_mix (void) +{ + bool oom_hit; + + /* mix and match path */ + for (unsigned int i = 0; + i < BUFFER_SIZE; + i += STEP) + { + int ret; + + ret = run_test (i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1); + if (-1 == ret) + { + return 1; + } + if (1 == ret) + { + oom_hit = true; + } + if ( (oom_hit) && (1 != ret) ) + { + fprintf (stderr, + "Strange: OOM stopped at %u after being hit earlier (mix)?\n", + i); + } + } + if (! oom_hit) + { + fprintf (stderr, + "Failed to trigger OOM condition in mix-and-match\n"); + return 1; + } + + + return 0; +} + + +static int +run_tests (void) +{ + int ret = 0; + +#if 1 + ret |= test_url (); + ret |= test_query (); + ret |= test_header (); + ret |= test_cookie (); + ret |= test_body (); + ret |= test_mix (); +#endif + return ret; +} + + +static void +no_log (void *cls, + enum MHD_StatusCode sc, + const char *fm, + va_list ap) +{ + (void) cls; + (void) sc; + (void) fm; + (void) ap; + + /* intentionally empty */ +} + + +int +main (void) +{ + struct MHD_Daemon *d; + + d = MHD_daemon_create (&server_req_cb, + NULL); + if (MHD_SC_OK != + MHD_DAEMON_SET_OPTIONS ( + d, + MHD_D_OPTION_WM_WORKER_THREADS (2), + MHD_D_OPTION_LOG_CALLBACK (&no_log, NULL), + MHD_D_OPTION_CONN_MEMORY_LIMIT (BUFFER_SIZE), + MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1500), + MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, + 0))) + { + fprintf (stderr, + "Failed to configure daemon!"); + return 1; + } + + { + enum MHD_StatusCode sc; + + sc = MHD_daemon_start (d); + if (MHD_SC_OK != sc) + { +#ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED + fprintf (stderr, + "Failed to start server: %s\n", + MHD_status_code_to_string_lazy (sc)); +#else + fprintf (stderr, + "Failed to start server: %u\n", + (unsigned int) sc); +#endif + MHD_daemon_destroy (d); + return 1; + } + } + + { + union MHD_DaemonInfoFixedData info; + enum MHD_StatusCode sc; + + sc = MHD_daemon_get_info_fixed ( + d, + MHD_DAEMON_INFO_FIXED_BIND_PORT, + &info); + if (MHD_SC_OK != sc) + { + fprintf (stderr, + "Failed to determine our port: %u\n", + (unsigned int) sc); + MHD_daemon_destroy (d); + return 1; + } + port = info.v_bind_port_uint16; + } + + { + int result; + + result = run_tests (); + MHD_daemon_destroy (d); + return result; + } +} diff --git a/src/tests/raw/test_ram.c b/src/tests/raw/test_ram.c @@ -1,726 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ -/* - This file is part of GNU libmicrohttpd. - Copyright (C) 2025 Christian Grothoff - - GNU libmicrohttpd is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - GNU libmicrohttpd is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - Alternatively, you can redistribute GNU libmicrohttpd and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version, together - with the eCos exception, as follows: - - As a special exception, if other files instantiate templates or - use macros or inline functions from this file, or you compile this - file and link it with other works to produce a work based on this - file, this file does not by itself cause the resulting work to be - covered by the GNU General Public License. However the source code - for this file must still be made available in accordance with - section (3) of the GNU General Public License v2. - - This exception does not invalidate any other reasons why a work - based on this file might be covered by the GNU General Public - License. - - You should have received copies of the GNU Lesser General Public - License and the GNU General Public License along with this library; - if not, see <https://www.gnu.org/licenses/>. -*/ - -/** - * @file test_ram.c - * @brief tests handling of memory pool exhaustion - * @author Christian Grothoff - */ -#include <stdio.h> -#include <stdbool.h> -#include <errno.h> -#include <string.h> -#include <stdlib.h> -#include <unistd.h> -#include <arpa/inet.h> -#include <netinet/ip.h> -#include "microhttpd2.h" - - -/** - * How big do we make the MHD buffer? Use a small value so we - * can trigger OOM in a reasonable amount of time. - */ -#define BUFFER_SIZE 2048 - -/** - * What is the step size. Should eventually use 1, but - * as long as we get tons of failures, a larger step size - * is probably nicer. - */ -#define STEP 71 - -/** - * Our port. - */ -static uint16_t port; - -/** - * Set to true once we hit the out-of-memory condition. - */ -static bool out_of_memory; - -/** - * Callback used by libmicrohttpd in order to obtain content. The - * callback is to copy at most @a max bytes of content into @a buf or - * provide zero-copy data for #MHD_DCC_action_continue_zc(). - * - * @param dyn_cont_cls closure argument to the callback - * @param ctx the context to produce the action to return, - * the pointer is only valid until the callback returns - * @param pos position in the datastream to access; - * note that if a `struct MHD_Response` object is re-used, - * it is possible for the same content reader to - * be queried multiple times for the same data; - * however, if a `struct MHD_Response` is not re-used, - * libmicrohttpd guarantees that "pos" will be - * the sum of all data sizes provided by this callback - * @param[out] buf where to copy the data - * @param max maximum number of bytes to copy to @a buf (size of @a buf), - if the size of the content of the response is known then size - of the buffer is never larger than amount of the content left - * @return action to use, - * NULL in case of any error (the response will be aborted) - */ -static const struct MHD_DynamicContentCreatorAction * -dyn_cc (void *dyn_cont_cls, - struct MHD_DynamicContentCreatorContext *ctx, - uint_fast64_t pos, - void *buf, - size_t max) -{ - int *flag = dyn_cont_cls; - struct MHD_NameValueCStr footer = { - .name = "Footer", - .value = "Value" - }; - - if (0 == *flag) - return MHD_DCC_action_finish_with_footer (ctx, - 0, - &footer); - (*flag) = 0; - memset (buf, - 'a', - max); - return MHD_DCC_action_continue (ctx, - max); -} - - -/** - * This method is called by libmicrohttpd when response with dynamic content - * is being destroyed. It should be used to free resources associated - * with the dynamic content. - * - * @param[in] free_cls closure - * @ingroup response - */ -static void -dyn_cc_free (void *free_cls) -{ - free (free_cls); -} - - -/** - * Function to process data uploaded by a client. - * - * @param upload_cls the argument given together with the function - * pointer when the handler was registered with MHD - * @param request the request is being processed - * @param content_data_size the size of the @a content_data, - * zero when all data have been processed - * @param[in] content_data the uploaded content data, - * may be modified in the callback, - * valid only until return from the callback, - * NULL when all data have been processed - * @return action specifying how to proceed: - * #MHD_upload_action_continue() to continue upload (for incremental - * upload processing only), - * #MHD_upload_action_suspend() to stop reading the upload until - * the request is resumed, - * #MHD_upload_action_abort_request() to close the socket, - * or a response to discard the rest of the upload and transmit - * the response - * @ingroup action - */ -static const struct MHD_UploadAction * -upload_cb (void *upload_cls, - struct MHD_Request *request, - size_t content_data_size, - void *content_data) -{ - int *flag; - - (void) upload_cls; - (void) content_data_size; - (void) content_data; - flag = malloc (sizeof (int)); - *flag = 1; - - return MHD_upload_action_from_response ( - request, - MHD_response_from_callback (MHD_HTTP_STATUS_OK, - MHD_SIZE_UNKNOWN, - &dyn_cc, - flag, - &dyn_cc_free)); -} - - -/** - * A client has requested the given url using the given method - * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, - * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). - * If @a upload_size is not zero and response action is provided by this - * callback, then upload will be discarded and the stream (the connection for - * HTTP/1.1) will be closed after sending the response. - * - * @param cls argument given together with the function - * pointer when the handler was registered with MHD - * @param request the request object - * @param path the requested uri (without arguments after "?") - * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, - * #MHD_HTTP_METHOD_PUT, etc.) - * @param upload_size the size of the message upload content payload, - * #MHD_SIZE_UNKNOWN for chunked uploads (if the - * final chunk has not been processed yet) - * @return action how to proceed, NULL - * if the request must be aborted due to a serious - * error while handling the request (implies closure - * of underling data stream, for HTTP/1.1 it means - * socket closure). - */ -static const struct MHD_Action * -server_req_cb (void *cls, - struct MHD_Request *MHD_RESTRICT request, - const struct MHD_String *MHD_RESTRICT path, - enum MHD_HTTP_Method method, - uint_fast64_t upload_size) -{ - (void) cls; - (void) path; - (void) method; - (void) upload_size; - return MHD_action_process_upload_full (request, - upload_size, - &upload_cb, - NULL); -} - - -/** - * Helper function to deal with partial writes. - * Fails hard (calls exit() on failures)! - * - * @param fd where to write to - * @param buf what to write - * @param buf_size number of bytes in @a buf - */ -static void -write_all (int fd, - const void *buf, - size_t buf_size) -{ - const char *cbuf = (const char *) buf; - size_t off; - - off = 0; - while (off < buf_size) - { - ssize_t ret; - - ret = write (fd, - &cbuf[off], - buf_size - off); - if (ret <= 0) - { - fprintf (stderr, - "Writing %u bytes to %d failed: %s\n", - (unsigned int) (buf_size - off), - fd, - strerror (errno)); - exit (1); - } - off += (size_t) ret; - } -} - - -static int -run_test (unsigned int url_len, - unsigned int query_len, - unsigned int header_len, - unsigned int cookie_len, - unsigned int body_len) -{ - char filler[BUFFER_SIZE + 1]; - int s; - - out_of_memory = false; - memset (filler, - 'a', - BUFFER_SIZE); - filler[BUFFER_SIZE] = '\0'; /* just to be conservative */ - s = socket (AF_INET, - SOCK_STREAM, - 0); - if (-1 == s) - { - fprintf (stderr, - "socket() failed: %s\n", - strerror (errno)); - return -1; - } - - { - struct sockaddr_in sa = { - .sin_family = AF_INET, - .sin_port = htons (port), - }; - inet_pton (AF_INET, - "127.0.0.1", - &sa.sin_addr); - if (0 != connect (s, - (struct sockaddr *) &sa, - sizeof (sa))) - { - fprintf (stderr, - "bind() failed: %s\n", - strerror (errno)); - close (s); - return -1; - } - } - - { - char upload[BUFFER_SIZE * 2]; - int iret; - - iret = snprintf (upload, - sizeof (upload), - "PUT /%.*s?q=%.*s HTTP/1.0\r\n" - "Content-Length: %u\r\n" - "Key: %.*s\r\n" - "Cookie: a=%.*s\r\n\r\n" - "%.*s", - (int) url_len, - filler, - (int) query_len, - filler, - body_len, - (int) header_len, - filler, - (int) cookie_len, - filler, - (int) body_len, - filler); - if ( (-1 == iret) || - ( ((size_t) iret) > sizeof (upload)) ) - { - fprintf (stderr, - "failed to build request buffer: %d\n", - iret); - close (s); - return -1; - } - write_all (s, - upload, - strlen (upload)); - } - /* read and discard response */ - { - bool got_data = false; - bool nice = false; - char dummy[16 * 1024]; - int flags = 0; - - while (1) - { - ssize_t res; - - res = recv (s, - &dummy, - sizeof (dummy), - flags); - flags = MSG_DONTWAIT; - if (res > 0) - { - got_data = true; - dummy[res] = '\0'; - /* FIXME: allow other "too large" responses to also count as - 'nice' here */ - if (NULL != - strstr (dummy, - "431 Request Header Fields Too Large")) - nice = true; - } - if (res <= 0) - break; - } - if (nice) - out_of_memory = true; - if (! got_data) - { - out_of_memory = true; - fprintf (stderr, - "Response was not nice (%u/%u/%u/%u/%u)\n", - url_len, - query_len, - header_len, - cookie_len, - body_len); - } - } - close (s); - return out_of_memory ? 1 : 0; -} - - -static int -test_url (void) -{ - bool oom_hit; - - oom_hit = false; - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (i, 0, 0, 0, 0); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (url)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition via URL\n"); - return 1; - } - return 0; -} - - -static int -test_query (void) -{ - bool oom_hit; - - oom_hit = false; - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (0, i, 0, 0, 0); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (query)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition via query\n"); - return 1; - } - return 0; -} - - -static int -test_header (void) -{ - bool oom_hit; - - oom_hit = false; - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (0, 0, i, 0, 0); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (header)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition via header\n"); - return 1; - } - return 0; -} - - -static int -test_cookie (void) -{ - bool oom_hit; - - oom_hit = false; - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (0, 0, 0, i, 0); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (cookie)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition via cookie\n"); - return 1; - } - return 0; -} - - -static int -test_body (void) -{ - bool oom_hit; - - oom_hit = false; - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (0, 0, 0, 0, i); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (body)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition via body\n"); - return 1; - } - return 0; -} - - -static int -test_mix (void) -{ - bool oom_hit; - - /* mix and match path */ - for (unsigned int i = 0; - i < BUFFER_SIZE; - i += STEP) - { - int ret; - - ret = run_test (i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1); - if (-1 == ret) - { - return 1; - } - if (1 == ret) - { - oom_hit = true; - } - if ( (oom_hit) && (1 != ret) ) - { - fprintf (stderr, - "Strange: OOM stopped at %u after being hit earlier (mix)?\n", - i); - } - } - if (! oom_hit) - { - fprintf (stderr, - "Failed to trigger OOM condition in mix-and-match\n"); - return 1; - } - - - return 0; -} - - -static int -run_tests (void) -{ - int ret = 0; - -#if 1 - ret |= test_url (); - ret |= test_query (); - ret |= test_header (); - ret |= test_cookie (); - ret |= test_body (); - ret |= test_mix (); -#endif - return ret; -} - - -static void -no_log (void *cls, - enum MHD_StatusCode sc, - const char *fm, - va_list ap) -{ - (void) cls; - (void) sc; - (void) fm; - (void) ap; - - /* intentionally empty */ -} - - -int -main (void) -{ - struct MHD_Daemon *d; - - d = MHD_daemon_create (&server_req_cb, - NULL); - if (MHD_SC_OK != - MHD_DAEMON_SET_OPTIONS ( - d, - MHD_D_OPTION_WM_WORKER_THREADS (2), - MHD_D_OPTION_LOG_CALLBACK (&no_log, NULL), - MHD_D_OPTION_CONN_MEMORY_LIMIT (BUFFER_SIZE), - MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1500), - MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, - 0))) - { - fprintf (stderr, - "Failed to configure daemon!"); - return 1; - } - - { - enum MHD_StatusCode sc; - - sc = MHD_daemon_start (d); - if (MHD_SC_OK != sc) - { -#ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED - fprintf (stderr, - "Failed to start server: %s\n", - MHD_status_code_to_string_lazy (sc)); -#else - fprintf (stderr, - "Failed to start server: %u\n", - (unsigned int) sc); -#endif - MHD_daemon_destroy (d); - return 1; - } - } - - { - union MHD_DaemonInfoFixedData info; - enum MHD_StatusCode sc; - - sc = MHD_daemon_get_info_fixed ( - d, - MHD_DAEMON_INFO_FIXED_BIND_PORT, - &info); - if (MHD_SC_OK != sc) - { - fprintf (stderr, - "Failed to determine our port: %u\n", - (unsigned int) sc); - MHD_daemon_destroy (d); - return 1; - } - port = info.v_bind_port_uint16; - } - - { - int result; - - result = run_tests (); - MHD_daemon_destroy (d); - return result; - } -}