libmicrohttpd

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

commit 025d2ab1b3f2a3ecffc029924a5e36a53b6bab00
parent 2a412f8887e0efb126e1d07b1896a8287a0af502
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Sat, 13 Nov 2021 13:21:10 +0300

test_client_put_*: new tests

Implemented simple emulation of HTTP client to test various MHD
aspects and thorough checking of all MHD callbacks.

Diffstat:
Msrc/microhttpd/.gitignore | 12++++++++++++
Msrc/microhttpd/Makefile.am | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/microhttpd/test_client_put_stop.c | 1788+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1872 insertions(+), 0 deletions(-)

diff --git a/src/microhttpd/.gitignore b/src/microhttpd/.gitignore @@ -65,3 +65,15 @@ test_sha256 test_upgrade_large test_upgrade_large_tls test_postprocessor_md +/test_client_put_shutdown +/test_client_put_close +/test_client_put_hard_close +/test_client_put_steps_shutdown +/test_client_put_steps_close +/test_client_put_steps_hard_close +/test_client_put_chunked_shutdown +/test_client_put_chunked_close +/test_client_put_chunked_hard_close +/test_client_put_chunked_steps_shutdown +/test_client_put_chunked_steps_close +/test_client_put_chunked_steps_hard_close diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am @@ -167,6 +167,18 @@ check_PROGRAMS = \ test_daemon \ test_response_entries \ test_postprocessor_md \ + test_client_put_shutdown \ + test_client_put_close \ + test_client_put_hard_close \ + test_client_put_steps_shutdown \ + test_client_put_steps_close \ + test_client_put_steps_hard_close \ + test_client_put_chunked_shutdown \ + test_client_put_chunked_close \ + test_client_put_chunked_hard_close \ + test_client_put_chunked_steps_shutdown \ + test_client_put_chunked_steps_close \ + test_client_put_chunked_steps_hard_close \ test_options if HAVE_POSIX_THREADS @@ -389,3 +401,63 @@ test_options_SOURCES = \ test_options.c test_options_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la + +test_client_put_shutdown_SOURCES = \ + test_client_put_stop.c +test_client_put_shutdown_LDADD = \ + libmicrohttpd.la + +test_client_put_close_SOURCES = \ + test_client_put_stop.c +test_client_put_close_LDADD = \ + libmicrohttpd.la + +test_client_put_hard_close_SOURCES = \ + test_client_put_stop.c +test_client_put_hard_close_LDADD = \ + libmicrohttpd.la + +test_client_put_steps_shutdown_SOURCES = \ + test_client_put_stop.c +test_client_put_steps_shutdown_LDADD = \ + libmicrohttpd.la + +test_client_put_steps_close_SOURCES = \ + test_client_put_stop.c +test_client_put_steps_close_LDADD = \ + libmicrohttpd.la + +test_client_put_steps_hard_close_SOURCES = \ + test_client_put_stop.c +test_client_put_steps_hard_close_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_shutdown_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_shutdown_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_close_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_close_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_hard_close_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_hard_close_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_steps_shutdown_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_steps_shutdown_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_steps_close_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_steps_close_LDADD = \ + libmicrohttpd.la + +test_client_put_chunked_steps_hard_close_SOURCES = \ + test_client_put_stop.c +test_client_put_chunked_steps_hard_close_LDADD = \ + libmicrohttpd.la diff --git a/src/microhttpd/test_client_put_stop.c b/src/microhttpd/test_client_put_stop.c @@ -0,0 +1,1788 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2021 Evgeny Grin (Karlson2k) + + libmicrohttpd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libmicrohttpd; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + * @file test_client_put_stop.c + * @brief Testcase for handling of clients aborts + * @author Karlson2k (Evgeny Grin) + * @author Christian Grothoff + */ +#include "MHD_config.h" +#include "platform.h" +#include <microhttpd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif /* HAVE_STRINGS_H */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif /* !WIN32_LEAN_AND_MEAN */ +#include <windows.h> +#endif + +#ifndef WINDOWS +#include <unistd.h> +#include <sys/socket.h> +#endif + +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif /* HAVE_LIMITS_H */ + +#include "mhd_sockets.h" /* only macros used */ +#include "test_helpers.h" +#include "mhd_assert.h" + +#if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 +#undef MHD_CPU_COUNT +#endif +#if ! defined(MHD_CPU_COUNT) +#define MHD_CPU_COUNT 2 +#endif +#if MHD_CPU_COUNT > 32 +#undef MHD_CPU_COUNT +/* Limit to reasonable value */ +#define MHD_CPU_COUNT 32 +#endif /* MHD_CPU_COUNT > 32 */ + +#ifndef MHD_STATICSTR_LEN_ +/** + * Determine length of static string / macro strings at compile time. + */ +#define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1) +#endif /* ! MHD_STATICSTR_LEN_ */ + +#ifndef _MHD_INSTRMACRO +/* Quoted macro parameter */ +#define _MHD_INSTRMACRO(a) #a +#endif /* ! _MHD_INSTRMACRO */ +#ifndef _MHD_STRMACRO +/* Quoted expanded macro parameter */ +#define _MHD_STRMACRO(a) _MHD_INSTRMACRO (a) +#endif /* ! _MHD_STRMACRO */ + + +/* Could be increased to facilitate debugging */ +#define TIMEOUTS_VAL 5 + +#define EXPECTED_URI_BASE_PATH "/a" + +#define REQ_HOST "localhost" + +#define REQ_METHOD "PUT" + +#define REQ_BODY "Some data." + +#define REQ_LINE_END "\r\n" + +/* Mandatory request headers */ +#define REQ_HEADER_HOST_NAME "Host" +#define REQ_HEADER_HOST_VALUE REQ_HOST +#define REQ_HEADER_HOST \ + REQ_HEADER_HOST_NAME ": " REQ_HEADER_HOST_VALUE REQ_LINE_END +#define REQ_HEADER_UA_NAME "User-Agent" +#define REQ_HEADER_UA_VALUE "dummyclient/0.9" +#define REQ_HEADER_UA REQ_HEADER_UA_NAME ": " REQ_HEADER_UA_VALUE REQ_LINE_END + +/* Optional request headers */ +#define REQ_HEADER_CT_NAME "Content-Type" +#define REQ_HEADER_CT_VALUE "text/plain" +#define REQ_HEADER_CT REQ_HEADER_CT_NAME ": " REQ_HEADER_CT_VALUE REQ_LINE_END + + +#if defined(HAVE___FUNC__) +#define externalErrorExit(ignore) \ + _externalErrorExit_func(NULL, __func__, __LINE__) +#define externalErrorExitDesc(errDesc) \ + _externalErrorExit_func(errDesc, __func__, __LINE__) +#define mhdErrorExit(ignore) \ + _mhdErrorExit_func(NULL, __func__, __LINE__) +#define mhdErrorExitDesc(errDesc) \ + _mhdErrorExit_func(errDesc, __func__, __LINE__) +#elif defined(HAVE___FUNCTION__) +#define externalErrorExit(ignore) \ + _externalErrorExit_func(NULL, __FUNCTION__, __LINE__) +#define externalErrorExitDesc(errDesc) \ + _externalErrorExit_func(errDesc, __FUNCTION__, __LINE__) +#define mhdErrorExit(ignore) \ + _mhdErrorExit_func(NULL, __FUNCTION__, __LINE__) +#define mhdErrorExitDesc(errDesc) \ + _mhdErrorExit_func(errDesc, __FUNCTION__, __LINE__) +#else +#define externalErrorExit(ignore) _externalErrorExit_func(NULL, NULL, __LINE__) +#define externalErrorExitDesc(errDesc) \ + _externalErrorExit_func(errDesc, NULL, __LINE__) +#define mhdErrorExit(ignore) _mhdErrorExit_func(NULL, NULL, __LINE__) +#define mhdErrorExitDesc(errDesc) _mhdErrorExit_func(errDesc, NULL, __LINE__) +#endif + + +_MHD_NORETURN static void +_externalErrorExit_func (const char *errDesc, const char *funcName, int lineNum) +{ + if ((NULL != errDesc) && (0 != errDesc[0])) + fprintf (stderr, "%s", errDesc); + else + fprintf (stderr, "System or external library call failed"); + if ((NULL != funcName) && (0 != funcName[0])) + fprintf (stderr, " in %s", funcName); + if (0 < lineNum) + fprintf (stderr, " at line %d", lineNum); + + fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, + strerror (errno)); +#ifdef MHD_WINSOCK_SOCKETS + fprintf (stderr, "WSAGetLastError() value: %d\n", (int) WSAGetLastError ()); +#endif /* MHD_WINSOCK_SOCKETS */ + fflush (stderr); + exit (99); +} + + +_MHD_NORETURN static void +_mhdErrorExit_func (const char *errDesc, const char *funcName, int lineNum) +{ + if ((NULL != errDesc) && (0 != errDesc[0])) + fprintf (stderr, "%s", errDesc); + else + fprintf (stderr, "MHD unexpected error"); + if ((NULL != funcName) && (0 != funcName[0])) + fprintf (stderr, " in %s", funcName); + if (0 < lineNum) + fprintf (stderr, " at line %d", lineNum); + + fprintf (stderr, ".\nLast errno value: %d (%s)\n", (int) errno, + strerror (errno)); + + fflush (stderr); + exit (8); +} + + +/* Global parameters */ +static int verbose; /**< Be verbose */ +static int oneone; /**< If false use HTTP/1.0 for requests*/ +static int global_port; /**< MHD daemons listen port number */ + +static int use_shutdown; /**< Use shutdown at client side */ +static int use_close; /**< Use socket close at client side */ +static int use_hard_close; /**< Use socket close with RST at client side */ +static int by_step; /**< Send request byte-by-byte */ +static int upl_chunked; /**< Use chunked encoding for request body */ + +static void +test_global_init (void) +{ +} + + +static void +test_global_cleanup (void) +{ +} + + +/** + * Change socket to blocking. + * + * @param fd the socket to manipulate + */ +static void +make_blocking (MHD_socket fd) +{ +#if defined(MHD_POSIX_SOCKETS) + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + externalErrorExitDesc ("Cannot make socket non-blocking"); + if ((flags & ~O_NONBLOCK) != flags) + { + if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) + externalErrorExitDesc ("Cannot make socket non-blocking"); + } +#elif defined(MHD_WINSOCK_SOCKETS) + unsigned long flags = 0; + + if (0 != ioctlsocket (fd, (int) FIONBIO, &flags)) + externalErrorExitDesc ("Cannot make socket non-blocking"); +#endif /* MHD_WINSOCK_SOCKETS */ +} + + +/** + * Change socket to non-blocking. + * + * @param fd the socket to manipulate + */ +static void +make_nonblocking (MHD_socket fd) +{ +#if defined(MHD_POSIX_SOCKETS) + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + externalErrorExitDesc ("Cannot make socket non-blocking"); + if ((flags | O_NONBLOCK) != flags) + { + if (-1 == fcntl (fd, F_SETFL, flags | O_NONBLOCK)) + externalErrorExitDesc ("Cannot make socket non-blocking"); + } +#elif defined(MHD_WINSOCK_SOCKETS) + unsigned long flags = 1; + + if (0 != ioctlsocket (fd, (int) FIONBIO, &flags)) + externalErrorExitDesc ("Cannot make socket non-blocking"); +#endif /* MHD_WINSOCK_SOCKETS */ +} + + +enum _MHD_clientStage +{ + DUMB_CLIENT_INIT = 0, + DUMB_CLIENT_CONNECTING, + DUMB_CLIENT_CONNECTED, + DUMB_CLIENT_REQ_SENDING, + DUMB_CLIENT_REQ_SENT, + DUMB_CLIENT_HEADER_RECVEIVING, + DUMB_CLIENT_HEADER_RECVEIVED, + DUMB_CLIENT_BODY_RECVEIVING, + DUMB_CLIENT_BODY_RECVEIVED, + DUMB_CLIENT_FINISHING, + DUMB_CLIENT_FINISHED +}; + +struct _MHD_dumbClient +{ + MHD_socket sckt; /**< the socket to communicate */ + + int sckt_nonblock; /**< non-zero if socket is non-blocking */ + + unsigned int port; /**< the port to connect to */ + + const char *send_buf; /**< the buffer for the request, malloced */ + + size_t req_size; /**< the size of the request, including header */ + + size_t send_off; /**< the number of bytes already sent */ + + enum _MHD_clientStage stage; + + /* the test-specific variables */ + size_t single_send_size; /**< the maximum number of bytes to be sent by + single send() */ + size_t send_size_limit; /**< the total number of send bytes limit */ +}; + +struct _MHD_dumbClient * +_MHD_dumbClient_create (unsigned int port, const char *method, const char *url, + const char *add_headers, + const uint8_t *req_body, size_t req_body_size, + int chunked) +{ + struct _MHD_dumbClient *clnt; + size_t method_size; + size_t url_size; + size_t add_hdrs_size; + size_t buf_alloc_size; + char *send_buf; + mhd_assert (0 != port); + mhd_assert (NULL != req_body || 0 == req_body); + mhd_assert (0 == req_body || NULL != req_body); + + clnt = (struct _MHD_dumbClient *) malloc (sizeof(struct _MHD_dumbClient)); + if (NULL == clnt) + externalErrorExit (); + memset (clnt, 0, sizeof(struct _MHD_dumbClient)); + clnt->sckt = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (MHD_INVALID_SOCKET == clnt->sckt) + externalErrorExitDesc ("Cannot create the client socket"); + clnt->sckt_nonblock = 0; + + if (clnt->sckt_nonblock) + make_nonblocking (clnt->sckt); + else + make_blocking (clnt->sckt); + + clnt->port = port; + + if (NULL != method) + method_size = strlen (method); + else + { + method = MHD_HTTP_METHOD_GET; + method_size = MHD_STATICSTR_LEN_ (MHD_HTTP_METHOD_GET); + } + mhd_assert (0 != method_size); + if (NULL != url) + url_size = strlen (url); + else + { + url = "/"; + url_size = 1; + } + mhd_assert (0 != url_size); + add_hdrs_size = (NULL == add_headers) ? 0 : strlen (add_headers); + buf_alloc_size = 1024 + method_size + url_size + + add_hdrs_size + req_body_size; + send_buf = (char *) malloc (buf_alloc_size); + if (NULL == send_buf) + externalErrorExit (); + + clnt->req_size = 0; + /* Form the request line */ + memcpy (send_buf + clnt->req_size, method, method_size); + clnt->req_size += method_size; + send_buf[clnt->req_size++] = ' '; + memcpy (send_buf + clnt->req_size, url, url_size); + clnt->req_size += url_size; + send_buf[clnt->req_size++] = ' '; + memcpy (send_buf + clnt->req_size, MHD_HTTP_VERSION_1_1, + MHD_STATICSTR_LEN_ (MHD_HTTP_VERSION_1_1)); + clnt->req_size += MHD_STATICSTR_LEN_ (MHD_HTTP_VERSION_1_1); + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + /* Form the header */ + memcpy (send_buf + clnt->req_size, REQ_HEADER_HOST, + MHD_STATICSTR_LEN_ (REQ_HEADER_HOST)); + clnt->req_size += MHD_STATICSTR_LEN_ (REQ_HEADER_HOST); + memcpy (send_buf + clnt->req_size, REQ_HEADER_UA, + MHD_STATICSTR_LEN_ (REQ_HEADER_UA)); + clnt->req_size += MHD_STATICSTR_LEN_ (REQ_HEADER_UA); + if ((NULL != req_body) || chunked) + { + if (! chunked) + { + int prn_size; + memcpy (send_buf + clnt->req_size, MHD_HTTP_HEADER_CONTENT_LENGTH ": ", + MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_CONTENT_LENGTH ": ")); + clnt->req_size += MHD_STATICSTR_LEN_ ( + MHD_HTTP_HEADER_CONTENT_LENGTH ": "); + prn_size = snprintf (send_buf + clnt->req_size, + (buf_alloc_size - clnt->req_size), + "%u", (unsigned int) req_body_size); + if (0 >= prn_size) + externalErrorExit (); + if ((unsigned int) prn_size >= buf_alloc_size - clnt->req_size) + externalErrorExit (); + clnt->req_size += (unsigned int) prn_size; + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + } + else + { + memcpy (send_buf + clnt->req_size, + MHD_HTTP_HEADER_TRANSFER_ENCODING ": chunked\r\n", + MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_TRANSFER_ENCODING \ + ": chunked\r\n")); + clnt->req_size += MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_TRANSFER_ENCODING \ + ": chunked\r\n"); + } + } + if (0 != add_hdrs_size) + { + memcpy (send_buf + clnt->req_size, add_headers, add_hdrs_size); + clnt->req_size += add_hdrs_size; + } + /* Terminate header */ + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + + /* Add body (if any) */ + if (! chunked) + { + if (0 != req_body_size) + { + memcpy (send_buf + clnt->req_size, req_body, req_body_size); + clnt->req_size += req_body_size; + } + } + else + { + if (0 != req_body_size) + { + int prn_size; + prn_size = snprintf (send_buf + clnt->req_size, + (buf_alloc_size - clnt->req_size), + "%x", (unsigned int) req_body_size); + if (0 >= prn_size) + externalErrorExit (); + if ((unsigned int) prn_size >= buf_alloc_size - clnt->req_size) + externalErrorExit (); + clnt->req_size += (unsigned int) prn_size; + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + memcpy (send_buf + clnt->req_size, req_body, req_body_size); + clnt->req_size += req_body_size; + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + } + send_buf[clnt->req_size++] = '0'; + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + send_buf[clnt->req_size++] = '\r'; + send_buf[clnt->req_size++] = '\n'; + } + mhd_assert (clnt->req_size < buf_alloc_size); + clnt->send_buf = send_buf; + + return clnt; +} + + +void +_MHD_dumbClient_set_send_limits (struct _MHD_dumbClient *clnt, + size_t step_size, size_t max_total_send) +{ + clnt->single_send_size = step_size; + clnt->send_size_limit = max_total_send; +} + + +/* internal */ +static void +_MHD_dumbClient_connect_init (struct _MHD_dumbClient *clnt) +{ + struct sockaddr_in sa; + mhd_assert (DUMB_CLIENT_INIT == clnt->stage); + + sa.sin_family = AF_INET; + sa.sin_port = htons ((uint16_t) clnt->port); + sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + if (0 != connect (clnt->sckt, (struct sockaddr *) &sa, sizeof(sa))) + { + const int err = MHD_socket_get_error_ (); + if ( (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINPROGRESS_)) || + (MHD_SCKT_ERR_IS_EAGAIN_ (err))) + clnt->stage = DUMB_CLIENT_CONNECTING; + else + externalErrorExitDesc ("Cannot 'connect()' the client socket"); + } + else + clnt->stage = DUMB_CLIENT_CONNECTED; +} + + +void +_MHD_dumbClient_start_connect (struct _MHD_dumbClient *clnt) +{ + mhd_assert (DUMB_CLIENT_INIT == clnt->stage); + _MHD_dumbClient_connect_init (clnt); +} + + +/* internal */ +static void +_MHD_dumbClient_connect_finish (struct _MHD_dumbClient *clnt) +{ + int err = 0; + socklen_t err_size = sizeof(err); + mhd_assert (DUMB_CLIENT_CONNECTING == clnt->stage); + if (0 != getsockopt (clnt->sckt, SOL_SOCKET, SO_ERROR, + (void *) &err, &err_size)) + externalErrorExitDesc ("'getsockopt()' call failed"); + if (0 != err) + externalErrorExitDesc ("Socket connect() failed"); + clnt->stage = DUMB_CLIENT_CONNECTED; +} + + +/* internal */ +static void +_MHD_dumbClient_send_req (struct _MHD_dumbClient *clnt) +{ + size_t send_size; + ssize_t res; + mhd_assert (DUMB_CLIENT_CONNECTED <= clnt->stage); + mhd_assert (DUMB_CLIENT_REQ_SENT > clnt->stage); + mhd_assert (clnt->req_size > clnt->send_off); + + send_size = (((0 != clnt->send_size_limit) && + (clnt->req_size > clnt->send_size_limit)) ? + clnt->send_size_limit : clnt->req_size) - clnt->send_off; + mhd_assert (0 != send_size); + if ((0 != clnt->single_send_size) && + (clnt->single_send_size < send_size)) + send_size = clnt->single_send_size; + + res = send (clnt->sckt, (const void *) (clnt->send_buf + clnt->send_off), + send_size, 0); + + if (res < 0) + { + const int err = MHD_socket_get_error_ (); + if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) + return; + if (MHD_SCKT_ERR_IS_EINTR_ (err)) + return; + if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err)) + mhdErrorExitDesc ("The connection was aborted by MHD"); + externalErrorExitDesc ("Unexpected network error"); + } + clnt->send_off += (size_t) res; + mhd_assert (clnt->send_off <= clnt->req_size); + mhd_assert (clnt->send_off <= clnt->send_size_limit || \ + 0 == clnt->send_size_limit); + if (clnt->req_size == clnt->send_off) + clnt->stage = DUMB_CLIENT_REQ_SENT; + if ((0 != clnt->send_size_limit) && + (clnt->send_size_limit == clnt->send_off)) + clnt->stage = DUMB_CLIENT_FINISHING; +} + + +/* internal */ +static void +_MHD_dumbClient_recv_reply (struct _MHD_dumbClient *clnt) +{ + (void) clnt; + externalErrorExitDesc ("Not implemented for this test"); +} + + +int +_MHD_dumbClient_is_req_sent (struct _MHD_dumbClient *clnt) +{ + return DUMB_CLIENT_REQ_SENT <= clnt->stage; +} + + +/* internal */ +static void +_MHD_dumbClient_socket_close (struct _MHD_dumbClient *clnt) +{ + if (MHD_INVALID_SOCKET != clnt->sckt) + { + if (use_hard_close) + { +#ifdef SO_LINGER + static const struct linger hard_close = {1, 0}; + mhd_assert (0 == hard_close.l_linger); + if (0 != setsockopt (clnt->sckt, SOL_SOCKET, SO_LINGER, + (const void *) &hard_close, sizeof (hard_close))) +#endif /* SO_LINGER */ + externalErrorExitDesc ("Failed to set SO_LINGER option"); + } + if (! MHD_socket_close_ (clnt->sckt)) + externalErrorExitDesc ("Unexpected error while closing " \ + "the client socket"); + clnt->sckt = MHD_INVALID_SOCKET; + } +} + + +/* internal */ +static void +_MHD_dumbClient_finalize (struct _MHD_dumbClient *clnt) +{ + if (MHD_INVALID_SOCKET != clnt->sckt) + { + if (use_shutdown) + { + if (0 != shutdown (clnt->sckt, SHUT_WR)) + { + const int err = MHD_socket_get_error_ (); + if (! MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_) && + ! MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err)) + mhdErrorExitDesc ("Unexpected error when shutting down " \ + "the client socket"); + } + } + else if (use_close) + { + _MHD_dumbClient_socket_close (clnt); + } + else + mhd_assert (0); + } + clnt->stage = DUMB_CLIENT_FINISHED; +} + + +/* internal */ +static int +_MHD_dumbClient_needs_send (const struct _MHD_dumbClient *clnt) +{ + return ((DUMB_CLIENT_CONNECTING <= clnt->stage) && + (DUMB_CLIENT_REQ_SENT > clnt->stage)) || + (DUMB_CLIENT_FINISHING == clnt->stage); +} + + +/* internal */ +static int +_MHD_dumbClient_needs_recv (const struct _MHD_dumbClient *clnt) +{ + return (DUMB_CLIENT_HEADER_RECVEIVING <= clnt->stage) && + (DUMB_CLIENT_BODY_RECVEIVED > clnt->stage); +} + + +/* internal */ +/** + * Check whether the client needs unconditionally process the data. + * @param clnt the client to check + * @return non-zero if client needs unconditionally process the data, + * zero otherwise. + */ +static int +_MHD_dumbClient_needs_process (const struct _MHD_dumbClient *clnt) +{ + switch (clnt->stage) + { + case DUMB_CLIENT_INIT: + case DUMB_CLIENT_REQ_SENT: + case DUMB_CLIENT_HEADER_RECVEIVED: + case DUMB_CLIENT_BODY_RECVEIVED: + return ! 0; + default: + return 0; + } + return 0; /* Should be unreachable */ +} + + +/** + * Process the client data with send()/recv() as needed. + * @param clnt the client to process + * @return non-zero if client finished processing the request, + * zero otherwise. + */ +int +_MHD_dumbClient_process (struct _MHD_dumbClient *clnt) +{ + do + { + switch (clnt->stage) + { + case DUMB_CLIENT_INIT: + _MHD_dumbClient_connect_init (clnt); + break; + case DUMB_CLIENT_CONNECTING: + _MHD_dumbClient_connect_finish (clnt); + break; + case DUMB_CLIENT_CONNECTED: + case DUMB_CLIENT_REQ_SENDING: + _MHD_dumbClient_send_req (clnt); + break; + case DUMB_CLIENT_REQ_SENT: + mhd_assert (0); + clnt->stage = DUMB_CLIENT_HEADER_RECVEIVING; + break; + case DUMB_CLIENT_HEADER_RECVEIVING: + _MHD_dumbClient_recv_reply (clnt); + break; + case DUMB_CLIENT_HEADER_RECVEIVED: + clnt->stage = DUMB_CLIENT_BODY_RECVEIVING; + break; + case DUMB_CLIENT_BODY_RECVEIVING: + _MHD_dumbClient_recv_reply (clnt); + break; + case DUMB_CLIENT_BODY_RECVEIVED: + clnt->stage = DUMB_CLIENT_FINISHING; + break; + case DUMB_CLIENT_FINISHING: + _MHD_dumbClient_finalize (clnt); + break; + default: + mhd_assert (0); + mhdErrorExit (); + } + } while (_MHD_dumbClient_needs_process (clnt)); + return DUMB_CLIENT_FINISHED == clnt->stage; +} + + +void +_MHD_dumbClient_get_fdsets (struct _MHD_dumbClient *clnt, + MHD_socket *maxsckt, + fd_set *rs, fd_set *ws, fd_set *es) +{ + mhd_assert (NULL != rs); + mhd_assert (NULL != ws); + mhd_assert (NULL != es); + if (DUMB_CLIENT_FINISHED > clnt->stage) + { + if (MHD_INVALID_SOCKET != clnt->sckt) + { + if ( (MHD_INVALID_SOCKET == *maxsckt) || + (clnt->sckt > *maxsckt) ) + *maxsckt = clnt->sckt; + if (_MHD_dumbClient_needs_recv (clnt)) + FD_SET (clnt->sckt, rs); + if (_MHD_dumbClient_needs_send (clnt)) + FD_SET (clnt->sckt, ws); + FD_SET (clnt->sckt, es); + } + } +} + + +/** + * Process the client data with send()/recv() as needed based on + * information in fd_sets. + * @param clnt the client to process + * @return non-zero if client finished processing the request, + * zero otherwise. + */ +int +_MHD_dumbClient_process_from_fdsets (struct _MHD_dumbClient *clnt, + fd_set *rs, fd_set *ws, fd_set *es) +{ + if (_MHD_dumbClient_needs_process (clnt)) + return _MHD_dumbClient_process (clnt); + else if (_MHD_dumbClient_needs_recv (clnt) && FD_ISSET (clnt->sckt, rs)) + return _MHD_dumbClient_process (clnt); + else if (_MHD_dumbClient_needs_send (clnt) && FD_ISSET (clnt->sckt, ws)) + return _MHD_dumbClient_process (clnt); + else if (FD_ISSET (clnt->sckt, es)) + return _MHD_dumbClient_process (clnt); + return DUMB_CLIENT_FINISHED == clnt->stage; +} + + +/** + * Perform full request. + * @param clnt the client to run + * @return zero if client finished processing the request, + * non-zero if timeout is reached. + */ +int +_MHD_dumbClient_perform (struct _MHD_dumbClient *clnt) +{ + time_t start; + time_t now; + start = time (NULL); + now = start; + do + { + fd_set rs; + fd_set ws; + fd_set es; + MHD_socket maxMhdSk; + struct timeval tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + + if (! _MHD_dumbClient_needs_process (clnt)) + { + maxMhdSk = MHD_INVALID_SOCKET; + _MHD_dumbClient_get_fdsets (clnt, &maxMhdSk, &rs, &ws, &es); + mhd_assert (now >= start); + tv.tv_sec = TIMEOUTS_VAL - (now - start) + 1; + tv.tv_usec = 1000; + if (-1 == select (maxMhdSk + 1, &rs, &ws, &es, &tv)) + { +#ifdef MHD_POSIX_SOCKETS + if (EINTR != errno) + externalErrorExitDesc ("Unexpected select() error"); +#else /* ! MHD_POSIX_SOCKETS */ + mhd_assert ((0 != rs.fd_count) || (0 != ws.fd_count) || \ + (0 != es.fd_count)); + externalErrorExitDesc ("Unexpected select() error"); +#endif /* ! MHD_POSIX_SOCKETS */ + } + if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) + return 0; + } + now = time (NULL); + /* Use double timeout value here as MHD must catch timeout situations + * in this test. Timeout in client as a last resort. */ + } while (now - start <= (TIMEOUTS_VAL * 2)); + return 1; +} + + +/** + * Close the client and free internally allocated resources. + * @param clnt the client to close + */ +void +_MHD_dumbClient_close (struct _MHD_dumbClient *clnt) +{ + if (DUMB_CLIENT_FINISHED != clnt->stage) + _MHD_dumbClient_finalize (clnt); + _MHD_dumbClient_socket_close (clnt); + if (NULL != clnt->send_buf) + { + free ((void *) clnt->send_buf); + clnt->send_buf = NULL; + } + free (clnt); +} + + +struct sckt_notif_cb_param +{ + volatile unsigned int num_started; + volatile unsigned int num_finished; +}; + +void +socket_cb (void *cls, + struct MHD_Connection *c, + void **socket_context, + enum MHD_ConnectionNotificationCode toe) +{ + struct sckt_notif_cb_param *param = (struct sckt_notif_cb_param *) cls; + if (NULL == socket_context) + mhdErrorExitDesc ("'socket_context' pointer is NULL"); + if (NULL == c) + mhdErrorExitDesc ("'connection' pointer is NULL"); + if (NULL == param) + mhdErrorExitDesc ("'cls' pointer is NULL"); + + if (MHD_CONNECTION_NOTIFY_STARTED == toe) + param->num_started++; + else if (MHD_CONNECTION_NOTIFY_CLOSED == toe) + param->num_finished++; + else + mhdErrorExitDesc ("Unknown 'toe' value"); +} + + +struct term_notif_cb_param +{ + volatile int term_reason; + volatile int num_called; +}; + + +static void +term_cb (void *cls, + struct MHD_Connection *c, + void **con_cls, + enum MHD_RequestTerminationCode term_code) +{ + struct term_notif_cb_param *param = (struct term_notif_cb_param *) cls; + if (NULL == con_cls) + mhdErrorExitDesc ("'con_cls' pointer is NULL"); + if (NULL == c) + mhdErrorExitDesc ("'connection' pointer is NULL"); + if (NULL == param) + mhdErrorExitDesc ("'cls' pointer is NULL"); + param->term_reason = (int) term_code; + param->num_called++; +} + + +const char * +term_reason_str (enum MHD_RequestTerminationCode term_code) +{ + switch ((int) term_code) + { + case MHD_REQUEST_TERMINATED_COMPLETED_OK: + return "COMPLETED_OK"; + case MHD_REQUEST_TERMINATED_WITH_ERROR: + return "TERMINATED_WITH_ERROR"; + case MHD_REQUEST_TERMINATED_TIMEOUT_REACHED: + return "TIMEOUT_REACHED"; + case MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN: + return "DAEMON_SHUTDOWN"; + case MHD_REQUEST_TERMINATED_READ_ERROR: + return "READ_ERROR"; + case MHD_REQUEST_TERMINATED_CLIENT_ABORT: + return "CLIENT_ABORT"; + case -1: + return "(not called)"; + default: + return "(unknown code)"; + } + return "(problem)"; /* unreachable */ +} + + +struct check_uri_cls +{ + const char *volatile uri; + volatile unsigned int cb_called; +}; + +static void * +check_uri_cb (void *cls, + const char *uri, + struct MHD_Connection *con) +{ + struct check_uri_cls *param = (struct check_uri_cls *) cls; + + if (NULL == con) + mhdErrorExitDesc ("The 'con' pointer is NULL"); + + param->cb_called++; + + if (0 != strcmp (param->uri, + uri)) + { + fprintf (stderr, "Wrong URI: '%s'\n", uri); + mhdErrorExit (); + } + return NULL; +} + + +struct mhd_header_checker_param +{ + int found_header_host; /**< the number of 'Host' headers */ + int found_header_ua; /**< the number of 'User-Agent' headers */ + int found_header_ct; /**< the number of 'Content-Type' headers */ + int found_header_cl; /**< the number of 'Content-Length' headers */ + int found_header_te; /**< the number of 'Transfer-Encoding' headers */ +}; + +enum MHD_Result +headerCheckerInterator (void *cls, + enum MHD_ValueKind kind, + const char *key, + size_t key_size, + const char *value, + size_t value_size) +{ + struct mhd_header_checker_param *const param = + (struct mhd_header_checker_param *) cls; + + if (NULL == param) + mhdErrorExitDesc ("cls parameter is NULL"); + + if (MHD_HEADER_KIND != kind) + return MHD_YES; /* Continue iteration */ + + if (0 == key_size) + mhdErrorExitDesc ("Zero key length"); + + if ((strlen (REQ_HEADER_HOST_NAME) == key_size) && + (0 == memcmp (key, REQ_HEADER_HOST_NAME, key_size))) + { + if ((strlen (REQ_HEADER_HOST_VALUE) == value_size) && + (0 == memcmp (value, REQ_HEADER_HOST_VALUE, value_size))) + param->found_header_host++; + else + fprintf (stderr, "Unexpected header value: '%.*s', expected: '%s'\n", + (int) value_size, value, REQ_HEADER_HOST_VALUE); + } + else if ((strlen (REQ_HEADER_UA_NAME) == key_size) && + (0 == memcmp (key, REQ_HEADER_UA_NAME, key_size))) + { + if ((strlen (REQ_HEADER_UA_VALUE) == value_size) && + (0 == memcmp (value, REQ_HEADER_UA_VALUE, value_size))) + param->found_header_ua++; + else + fprintf (stderr, "Unexpected header value: '%.*s', expected: '%s'\n", + (int) value_size, value, REQ_HEADER_UA_VALUE); + } + else if ((strlen (REQ_HEADER_CT_NAME) == key_size) && + (0 == memcmp (key, REQ_HEADER_CT_NAME, key_size))) + { + if ((strlen (REQ_HEADER_CT_VALUE) == value_size) && + (0 == memcmp (value, REQ_HEADER_CT_VALUE, value_size))) + param->found_header_ct++; + else + fprintf (stderr, "Unexpected header value: '%.*s', expected: '%s'\n", + (int) value_size, value, REQ_HEADER_CT_VALUE); + } + else if ((strlen (MHD_HTTP_HEADER_CONTENT_LENGTH) == key_size) && + (0 == memcmp (key, MHD_HTTP_HEADER_CONTENT_LENGTH, key_size))) + { + /* do not check value of the header here for simplicity */ + param->found_header_cl++; + } + else if ((strlen (MHD_HTTP_HEADER_TRANSFER_ENCODING) == key_size) && + (0 == memcmp (key, MHD_HTTP_HEADER_TRANSFER_ENCODING, key_size))) + { + if ((strlen ("chunked") == value_size) && + (0 == memcmp (value, "chunked", value_size))) + param->found_header_te++; + else + fprintf (stderr, "Unexpected header value: '%.*s', expected: '%s'\n", + (int) value_size, value, "chunked"); + } + return MHD_YES; +} + + +struct ahc_cls_type +{ + const char *volatile rp_data; + volatile size_t rp_data_size; + const char *volatile rq_method; + const char *volatile rq_url; + const char *volatile req_body; + volatile unsigned int cb_called; /* Non-zero indicates that callback was called at least one time */ + size_t req_body_size; /**< The number of bytes in @a req_body */ + size_t req_body_uploaded; /* Updated by callback */ +}; + + +static enum MHD_Result +ahcCheck (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, size_t *upload_data_size, + void **con_cls) +{ + static int marker; + enum MHD_Result ret; + struct mhd_header_checker_param header_check_param; + struct ahc_cls_type *const param = (struct ahc_cls_type *) cls; + + if (NULL == param) + mhdErrorExitDesc ("cls parameter is NULL"); + param->cb_called++; + + if (0 != strcmp (version, MHD_HTTP_VERSION_1_1)) + mhdErrorExitDesc ("Unexpected HTTP version"); + + if (0 != strcmp (url, param->rq_url)) + mhdErrorExitDesc ("Unexpected URI"); + + if (0 != strcmp (param->rq_method, method)) + mhdErrorExitDesc ("Unexpected request method"); + + if (NULL == upload_data_size) + mhdErrorExitDesc ("'upload_data_size' pointer is NULL"); + + if (0 != *upload_data_size) + { + const char *const upload_body = param->req_body; + if (NULL == upload_data) + mhdErrorExitDesc ("'upload_data' is NULL while " \ + "'*upload_data_size' value is not zero"); + if (NULL == upload_body) + mhdErrorExitDesc ("'*upload_data_size' value is not zero " \ + "while no request body is expected"); + if (param->req_body_uploaded + *upload_data_size > param->req_body_size) + { + fprintf (stderr, "Too large upload body received. Got %u, expected %u", + (unsigned int) (param->req_body_uploaded + *upload_data_size), + (unsigned int) param->req_body_size); + mhdErrorExit (); + } + if (0 != memcmp (upload_data, upload_body + param->req_body_uploaded, + *upload_data_size)) + { + fprintf (stderr, "Unexpected request body at offset %u: " \ + "'%.*s', expected: '%.*s'\n", + (unsigned int) param->req_body_uploaded, + (int) *upload_data_size, upload_data, + (int) *upload_data_size, upload_body + param->req_body_uploaded); + mhdErrorExit (); + } + param->req_body_uploaded += *upload_data_size; + *upload_data_size = 0; + } + + if (&marker != *con_cls) + { + /* The first call of the callback for this connection */ + mhd_assert (NULL == upload_data); + param->req_body_uploaded = 0; + + *con_cls = &marker; + return MHD_YES; + } + + memset (&header_check_param, 0, sizeof(header_check_param)); + if (1 > MHD_get_connection_values_n (connection, MHD_HEADER_KIND, + &headerCheckerInterator, + &header_check_param)) + mhdErrorExitDesc ("Wrong number of headers in the request"); + if (1 != header_check_param.found_header_host) + mhdErrorExitDesc ("'Host' header has not been detected in request"); + if (1 != header_check_param.found_header_ua) + mhdErrorExitDesc ("'User-Agent' header has not been detected in request"); + if (1 != header_check_param.found_header_ct) + mhdErrorExitDesc ("'Content-Type' header has not been detected in request"); + if (! upl_chunked && (1 != header_check_param.found_header_cl)) + mhdErrorExitDesc ("'Content-Length' header has not been detected " + "in request"); + if (upl_chunked && (1 != header_check_param.found_header_te)) + mhdErrorExitDesc ("'Transfer-Encoding' header has not been detected " + "in request"); + + if (NULL != upload_data) + return MHD_YES; /* Full request has not been received so far */ + +#if 0 /* Code unused in this test */ + struct MHD_Response *response; + response = MHD_create_response_from_buffer (param->rp_data_size, + (void *) param->rp_data, + MHD_RESPMEM_MUST_COPY); + if (NULL == response) + mhdErrorExitDesc ("Failed to create response"); + + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + if (MHD_YES != ret) + mhdErrorExitDesc ("Failed to queue response"); +#else + if (NULL == upload_data) + mhdErrorExitDesc ("Full request received, " \ + "while incomplete request expected"); + ret = MHD_NO; +#endif + + return ret; +} + + +struct simpleQueryParams +{ + /* Destination path for HTTP query */ + const char *queryPath; + + /* Custom query method, NULL for default */ + const char *method; + + /* Destination port for HTTP query */ + int queryPort; + + /* Additional request headers, static */ + const char *headers; + + /* NULL for request without body */ + uint8_t *req_body; + size_t req_body_size; + + /* Non-zero to use chunked encoding for request body */ + int chunked; + + /* Max size of data for single 'send()' call */ + size_t step_size; + + /* Limit for total amount of sent data */ + size_t total_send_max; + + /* HTTP query result error flag */ + volatile int queryError; + + /* Response HTTP code, zero if no response */ + volatile int responseCode; +}; + + +/* returns non-zero if timed-out */ +static int +performQueryExternal (struct MHD_Daemon *d, struct _MHD_dumbClient *clnt) +{ + time_t start; + time_t now; + struct timeval tv; + int ret; + const union MHD_DaemonInfo *di; + MHD_socket lstn_sk; + int client_accepted; + + di = MHD_get_daemon_info (d, MHD_DAEMON_INFO_LISTEN_FD); + if (NULL == di) + mhdErrorExitDesc ("Cannot get lister socket"); + lstn_sk = di->listen_fd; + + ret = 1; /* will be replaced with real result */ + client_accepted = 0; + + _MHD_dumbClient_start_connect (clnt); + + start = time (NULL); + now = start; + do + { + fd_set rs; + fd_set ws; + fd_set es; + MHD_socket maxMhdSk; + int maxCurlSk; + + maxMhdSk = MHD_INVALID_SOCKET; + maxCurlSk = -1; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + if (NULL == clnt) + { + /* client has finished, check whether MHD is still + * processing any connections */ + unsigned long long to; + if (client_accepted && (MHD_YES != MHD_get_timeout (d, &to))) + { + ret = 0; + break; /* MHD finished as well */ + } + } + else + _MHD_dumbClient_get_fdsets (clnt, &maxMhdSk, &rs, &ws, &es); + if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxMhdSk)) + mhdErrorExitDesc ("MHD_get_fdset() failed"); + tv.tv_sec = 1; + tv.tv_usec = 100000; +#ifdef MHD_POSIX_SOCKETS + if (maxMhdSk > maxCurlSk) + maxCurlSk = maxMhdSk; +#endif /* MHD_POSIX_SOCKETS */ + if (-1 == select (maxCurlSk + 1, &rs, &ws, &es, &tv)) + { +#ifdef MHD_POSIX_SOCKETS + if (EINTR != errno) + externalErrorExitDesc ("Unexpected select() error"); +#else + if ((WSAEINVAL != WSAGetLastError ()) || + (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) ) + externalErrorExitDesc ("Unexpected select() error"); + Sleep (1); +#endif + } + if (MHD_YES != MHD_run_from_select (d, &rs, &ws, &es)) + mhdErrorExitDesc ("MHD_run_from_select() failed"); + if (! client_accepted) + client_accepted = FD_ISSET (lstn_sk, &rs); + if (NULL != clnt) + { + /* Do not close the socket on client side until + * MHD is accepted and processed the socket. */ + if (! _MHD_dumbClient_is_req_sent (clnt) || + (client_accepted && ! FD_ISSET (lstn_sk, &rs))) + { + if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) + clnt = NULL; + } + } + now = time (NULL); + /* Use double timeout value here so MHD would be able to catch timeout + * internally */ + } while (now - start <= (TIMEOUTS_VAL * 2)); + + return ret; +} + + +/* Returns zero for successful response and non-zero for failed response */ +static int +doClientQueryInThread (struct MHD_Daemon *d, + struct simpleQueryParams *p) +{ + const union MHD_DaemonInfo *dinfo; + struct _MHD_dumbClient *c; + int errornum; + int use_external_poll; + + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_FLAGS); + if (NULL == dinfo) + mhdErrorExitDesc ("MHD_get_daemon_info() failed"); + use_external_poll = (0 == (dinfo->flags + & MHD_USE_INTERNAL_POLLING_THREAD)); + + if (0 == p->queryPort) + abort (); + + c = _MHD_dumbClient_create (p->queryPort, p->method, p->queryPath, + p->headers, p->req_body, p->req_body_size, + p->chunked); + _MHD_dumbClient_set_send_limits (c, p->step_size, p->total_send_max); + + /* 'internal' polling should not be used in this test */ + mhd_assert (use_external_poll); + if (! use_external_poll) + errornum = _MHD_dumbClient_perform (c); + else + errornum = performQueryExternal (d, c); + + if (errornum) + fprintf (stderr, "Request timeout out.\n"); + + _MHD_dumbClient_close (c); + + return errornum; +} + + +void +printTestResults (FILE *stream, + struct simpleQueryParams *qParam, + struct ahc_cls_type *ahc_param, + struct check_uri_cls *uri_cb_param, + struct term_notif_cb_param *term_result, + struct sckt_notif_cb_param *sckt_result) +{ + if (stderr != stream) + fflush (stderr); + fprintf (stream, " Request aborted at %u byte%s.", + (unsigned int) qParam->total_send_max, + 1 == qParam->total_send_max ? "" : "s"); + if ((1 == sckt_result->num_started) && (1 == sckt_result->num_finished)) + fprintf (stream, " One socket has been accepted and then closed."); + else + fprintf (stream, " Sockets have been accepted %u time%s" + " and closed %u time%s.", sckt_result->num_started, + (1 == sckt_result->num_started) ? "" : "s", + sckt_result->num_finished, + (1 == sckt_result->num_finished) ? "" : "s"); + if (0 == uri_cb_param->cb_called) + fprintf (stream, " URI callback has NOT been called."); + else + fprintf (stream, " URI callback has been called %u time%s.", + uri_cb_param->cb_called, + 1 == uri_cb_param->cb_called ? "" : "s"); + if (0 == ahc_param->cb_called) + fprintf (stream, " Access handler callback has NOT been called."); + else + fprintf (stream, " Access handler callback has been called %u time%s.", + ahc_param->cb_called, + 1 == ahc_param->cb_called ? "" : "s"); + if (0 == term_result->num_called) + fprintf (stream, " Final notification callback has NOT been called."); + else + fprintf (stream, " Final notification callback has been called %u time%s " + "with %s code.", term_result->num_called, + (1 == term_result->num_called) ? "" : "s", + term_reason_str (term_result->term_reason)); + fprintf (stream, "\n"); + fflush (stream); +} + + +/* Perform test queries, shut down MHD daemon, and free parameters */ +static int +performTestQueries (struct MHD_Daemon *d, int d_port, + struct ahc_cls_type *ahc_param, + struct check_uri_cls *uri_cb_param, + struct term_notif_cb_param *term_result, + struct sckt_notif_cb_param *sckt_result) +{ + struct simpleQueryParams qParam; + int ret = 0; /* Return value */ + size_t req_total_size; + size_t limit_send_size; + + /* Common parameters, to be individually overridden by specific test cases */ + qParam.queryPort = d_port; + qParam.method = MHD_HTTP_METHOD_PUT; + qParam.queryPath = EXPECTED_URI_BASE_PATH; + qParam.headers = REQ_HEADER_CT; + qParam.req_body = (uint8_t *) REQ_BODY; + qParam.req_body_size = MHD_STATICSTR_LEN_ (REQ_BODY); + qParam.chunked = upl_chunked; + qParam.step_size = by_step ? 1 : 0; + + uri_cb_param->uri = EXPECTED_URI_BASE_PATH; + + ahc_param->rq_url = EXPECTED_URI_BASE_PATH; + ahc_param->rq_method = MHD_HTTP_METHOD_PUT; + ahc_param->rp_data = "~"; + ahc_param->rp_data_size = 1; + ahc_param->req_body = (const char *) qParam.req_body; + ahc_param->req_body_size = qParam.req_body_size; + + do + { + struct _MHD_dumbClient *test_c; + struct simpleQueryParams *p = &qParam; + test_c = _MHD_dumbClient_create (p->queryPort, p->method, p->queryPath, + p->headers, p->req_body, p->req_body_size, + p->chunked); + req_total_size = test_c->req_size; + _MHD_dumbClient_close (test_c); + } while (0); + + for (limit_send_size = 1; limit_send_size < req_total_size; limit_send_size++) + { + int test_succeed; + test_succeed = 0; + qParam.total_send_max = limit_send_size; + /* To be updated by callbacks */ + ahc_param->cb_called = 0; + uri_cb_param->cb_called = 0; + term_result->num_called = 0; + term_result->term_reason = -1; + sckt_result->num_started = 0; + sckt_result->num_finished = 0; + + if (0 != doClientQueryInThread (d, &qParam)) + fprintf (stderr, "FAILED: connection has NOT been closed by MHD."); + else + { + if ((-1 != term_result->term_reason) && + (MHD_REQUEST_TERMINATED_READ_ERROR != term_result->term_reason) && + (MHD_REQUEST_TERMINATED_CLIENT_ABORT != term_result->term_reason) ) + fprintf (stderr, "FAILED: Wrong termination code."); + else if ((0 == term_result->num_called) && + ((0 != uri_cb_param->cb_called) || (0 != ahc_param->cb_called))) + fprintf (stderr, "FAILED: Missing required call of final notification " + "callback."); + else if (1 < uri_cb_param->cb_called) + fprintf (stderr, "FAILED: Too many URI callbacks."); + else if ((0 != ahc_param->cb_called) && (0 == uri_cb_param->cb_called)) + fprintf (stderr, "FAILED: URI callback has NOT been called " + "while Access Handler callback has been called."); + else if (1 < term_result->num_called) + fprintf (stderr, "FAILED: Too many final callbacks."); + else if (1 != sckt_result->num_started) + fprintf (stderr, "FAILED: Wrong number of sockets accepted."); + else if (1 != sckt_result->num_finished) + fprintf (stderr, "FAILED: Wrong number of sockets closed."); + else + test_succeed = 1; + } + + if (! test_succeed) + { + ret = 1; + printTestResults (stderr, + &qParam, ahc_param, uri_cb_param, + term_result, sckt_result); + } + else if (verbose) + { + printf ("SUCCEED:"); + printTestResults (stdout, + &qParam, ahc_param, uri_cb_param, + term_result, sckt_result); + } + } + + MHD_stop_daemon (d); + free (uri_cb_param); + free (ahc_param); + free (term_result); + free (sckt_result); + + return ret; +} + + +enum testMhdThreadsType +{ + testMhdThreadExternal = 0, + testMhdThreadInternal = MHD_USE_INTERNAL_POLLING_THREAD, + testMhdThreadInternalPerConnection = MHD_USE_THREAD_PER_CONNECTION + | MHD_USE_INTERNAL_POLLING_THREAD, + testMhdThreadInternalPool +}; + +enum testMhdPollType +{ + testMhdPollBySelect = 0, + testMhdPollByPoll = MHD_USE_POLL, + testMhdPollByEpoll = MHD_USE_EPOLL, + testMhdPollAuto = MHD_USE_AUTO +}; + +/* Get number of threads for thread pool depending + * on used poll function and test type. */ +static unsigned int +testNumThreadsForPool (enum testMhdPollType pollType) +{ + int numThreads = MHD_CPU_COUNT; + (void) pollType; /* Don't care about pollType for this test */ + return numThreads; /* No practical limit for non-cleanup test */ +} + + +static struct MHD_Daemon * +startTestMhdDaemon (enum testMhdThreadsType thrType, + enum testMhdPollType pollType, int *pport, + struct ahc_cls_type **ahc_param, + struct check_uri_cls **uri_cb_param, + struct term_notif_cb_param **term_result, + struct sckt_notif_cb_param **sckt_result) +{ + struct MHD_Daemon *d; + const union MHD_DaemonInfo *dinfo; + + if ((NULL == ahc_param) || (NULL == uri_cb_param) || (NULL == term_result)) + abort (); + + *ahc_param = (struct ahc_cls_type *) malloc (sizeof(struct ahc_cls_type)); + if (NULL == *ahc_param) + externalErrorExit (); + *uri_cb_param = + (struct check_uri_cls *) malloc (sizeof(struct check_uri_cls)); + if (NULL == *uri_cb_param) + externalErrorExit (); + *term_result = + (struct term_notif_cb_param *) malloc (sizeof(struct term_notif_cb_param)); + if (NULL == *term_result) + externalErrorExit (); + *sckt_result = + (struct sckt_notif_cb_param *) malloc (sizeof(struct sckt_notif_cb_param)); + if (NULL == *sckt_result) + externalErrorExit (); + + if ( (0 == *pport) && + (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) ) + { + *pport = 4170; + if (use_shutdown) + *pport += 0; + if (use_close) + *pport += 1; + if (use_hard_close) + *pport += 1; + if (by_step) + *pport += 1 << 2; + if (upl_chunked) + *pport += 1 << 3; + if (! oneone) + *pport += 1 << 4; + } + + if (testMhdThreadInternalPool != thrType) + d = MHD_start_daemon (((int) thrType) | ((int) pollType) + | (verbose ? MHD_USE_ERROR_LOG : 0), + *pport, NULL, NULL, + &ahcCheck, *ahc_param, + MHD_OPTION_URI_LOG_CALLBACK, &check_uri_cb, + *uri_cb_param, + MHD_OPTION_NOTIFY_COMPLETED, &term_cb, *term_result, + MHD_OPTION_NOTIFY_CONNECTION, &socket_cb, + *sckt_result, + MHD_OPTION_CONNECTION_TIMEOUT, + (unsigned) TIMEOUTS_VAL, + MHD_OPTION_END); + else + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | ((int) pollType) + | (verbose ? MHD_USE_ERROR_LOG : 0), + *pport, NULL, NULL, + &ahcCheck, *ahc_param, + MHD_OPTION_THREAD_POOL_SIZE, + testNumThreadsForPool (pollType), + MHD_OPTION_URI_LOG_CALLBACK, &check_uri_cb, + *uri_cb_param, + MHD_OPTION_NOTIFY_COMPLETED, &term_cb, *term_result, + MHD_OPTION_NOTIFY_CONNECTION, &socket_cb, + *sckt_result, + MHD_OPTION_CONNECTION_TIMEOUT, + (unsigned) TIMEOUTS_VAL, + MHD_OPTION_END); + + if (NULL == d) + { + fprintf (stderr, "Failed to start MHD daemon, errno=%d.\n", errno); + abort (); + } + + if (0 == *pport) + { + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + fprintf (stderr, "MHD_get_daemon_info() failed.\n"); + abort (); + } + *pport = (int) dinfo->port; + if (0 == global_port) + global_port = *pport; /* Reuse the same port for all tests */ + } + + return d; +} + + +/* Test runners */ + + +static int +testExternalGet (void) +{ + struct MHD_Daemon *d; + int d_port = global_port; /* Daemon's port */ + struct ahc_cls_type *ahc_param; + struct check_uri_cls *uri_cb_param; + struct term_notif_cb_param *term_result; + struct sckt_notif_cb_param *sckt_result; + + d = startTestMhdDaemon (testMhdThreadExternal, testMhdPollBySelect, &d_port, + &ahc_param, &uri_cb_param, &term_result, + &sckt_result); + + return performTestQueries (d, d_port, ahc_param, uri_cb_param, term_result, + sckt_result); +} + + +#if 0 /* disabled runners, not suitable for this test */ +static int +testInternalGet (enum testMhdPollType pollType) +{ + struct MHD_Daemon *d; + int d_port = global_port; /* Daemon's port */ + struct ahc_cls_type *ahc_param; + struct check_uri_cls *uri_cb_param; + struct term_notif_cb_param *term_result; + + d = startTestMhdDaemon (testMhdThreadInternal, pollType, &d_port, + &ahc_param, &uri_cb_param, &term_result); + + return performTestQueries (d, d_port, ahc_param, uri_cb_param, term_result); +} + + +static int +testMultithreadedGet (enum testMhdPollType pollType) +{ + struct MHD_Daemon *d; + int d_port = global_port; /* Daemon's port */ + struct ahc_cls_type *ahc_param; + struct check_uri_cls *uri_cb_param; + struct term_notif_cb_param *term_result; + + d = startTestMhdDaemon (testMhdThreadInternalPerConnection, pollType, &d_port, + &ahc_param, &uri_cb_param); + return performTestQueries (d, d_port, ahc_param, uri_cb_param, term_result); +} + + +static int +testMultithreadedPoolGet (enum testMhdPollType pollType) +{ + struct MHD_Daemon *d; + int d_port = global_port; /* Daemon's port */ + struct ahc_cls_type *ahc_param; + struct check_uri_cls *uri_cb_param; + struct term_notif_cb_param *term_result; + + d = startTestMhdDaemon (testMhdThreadInternalPool, pollType, &d_port, + &ahc_param, &uri_cb_param); + return performTestQueries (d, d_port, ahc_param, uri_cb_param, term_result); +} + + +#endif /* disabled runners, not suitable for this test */ + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + unsigned int test_result = 0; + verbose = 0; + + if ((NULL == argv) || (0 == argv[0])) + return 99; + oneone = ! has_in_name (argv[0], "10"); + use_shutdown = has_in_name (argv[0], "_shutdown") ? 1 : 0; + use_close = has_in_name (argv[0], "_close") ? 1 : 0; + use_hard_close = has_in_name (argv[0], "_hard_close") ? 1 : 0; + by_step = has_in_name (argv[0], "_steps") ? 1 : 0; + upl_chunked = has_in_name (argv[0], "_chunked") ? 1 : 0; +#ifndef SO_LINGER + if (use_hard_close) + { + fprintf (stderr, "This test requires SO_LINGER socket option support.\n"); + return 77; + } +#endif /* ! SO_LINGER */ + if (1 != use_shutdown + use_close) + return 99; + verbose = ! has_param (argc, argv, "-q") || has_param (argc, argv, "--quiet"); + + test_global_init (); + + /* Could be set to non-zero value to enforce using specific port + * in the test */ + global_port = 0; + test_result = testExternalGet (); + if (test_result) + fprintf (stderr, "FAILED: testExternalGet () - %u.\n", test_result); + else if (verbose) + printf ("PASSED: testExternalGet ().\n"); + errorCount += test_result; +#if 0 /* disabled runners, not suitable for this test */ + if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS)) + { + test_result = testInternalGet (testMhdPollAuto); + if (test_result) + fprintf (stderr, "FAILED: testInternalGet (testMhdPollAuto) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testInternalGet (testMhdPollBySelect).\n"); + errorCount += test_result; +#ifdef _MHD_HEAVY_TESTS + /* Actually tests are not heavy, but took too long to complete while + * not really provide any additional results. */ + test_result = testInternalGet (testMhdPollBySelect); + if (test_result) + fprintf (stderr, "FAILED: testInternalGet (testMhdPollBySelect) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testInternalGet (testMhdPollBySelect).\n"); + errorCount += test_result; + test_result = testMultithreadedPoolGet (testMhdPollBySelect); + if (test_result) + fprintf (stderr, + "FAILED: testMultithreadedPoolGet (testMhdPollBySelect) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testMultithreadedPoolGet (testMhdPollBySelect).\n"); + errorCount += test_result; + test_result = testMultithreadedGet (testMhdPollBySelect); + if (test_result) + fprintf (stderr, + "FAILED: testMultithreadedGet (testMhdPollBySelect) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testMultithreadedGet (testMhdPollBySelect).\n"); + errorCount += test_result; + if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_POLL)) + { + test_result = testInternalGet (testMhdPollByPoll); + if (test_result) + fprintf (stderr, "FAILED: testInternalGet (testMhdPollByPoll) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testInternalGet (testMhdPollByPoll).\n"); + errorCount += test_result; + } + if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_EPOLL)) + { + test_result = testInternalGet (testMhdPollByEpoll); + if (test_result) + fprintf (stderr, "FAILED: testInternalGet (testMhdPollByEpoll) - %u.\n", + test_result); + else if (verbose) + printf ("PASSED: testInternalGet (testMhdPollByEpoll).\n"); + errorCount += test_result; + } +#else + /* Mute compiler warnings */ + (void) testMultithreadedGet; + (void) testMultithreadedPoolGet; +#endif /* _MHD_HEAVY_TESTS */ + } +#endif /* disabled runners, not suitable for this test */ + if (0 != errorCount) + fprintf (stderr, + "Error (code: %u)\n", + errorCount); + else if (verbose) + printf ("All tests passed.\n"); + + test_global_cleanup (); + + return (errorCount == 0) ? 0 : 1; /* 0 == pass */ +}