/* 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 #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif /* HAVE_STRINGS_H */ #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif /* !WIN32_LEAN_AND_MEAN */ #include #endif #ifndef WINDOWS #include #include #endif #ifdef HAVE_LIMITS_H #include #endif /* HAVE_LIMITS_H */ #ifdef HAVE_SIGNAL_H #include #endif /* HAVE_SIGNAL_H */ #ifdef HAVE_SYSCTL #ifdef HAVE_SYS_TYPES_H #include #endif /* HAVE_SYS_TYPES_H */ #ifdef HAVE_SYS_SYSCTL_H #include #endif /* HAVE_SYS_SYSCTL_H */ #ifdef HAVE_SYS_SOCKET_H #include #endif /* HAVE_SYS_SOCKET_H */ #ifdef HAVE_NETINET_IN_SYSTM_H #include #endif /* HAVE_NETINET_IN_SYSTM_H */ #ifdef HAVE_NETINET_IN_H #include #endif /* HAVE_NETINET_IN_H */ #ifdef HAVE_NETINET_IP_H #include #endif /* HAVE_NETINET_IP_H */ #ifdef HAVE_NETINET_IP_ICMP_H #include #endif /* HAVE_NETINET_IP_ICMP_H */ #ifdef HAVE_NETINET_ICMP_VAR_H #include #endif /* HAVE_NETINET_ICMP_VAR_H */ #endif /* HAVE_SYSCTL */ #include #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 /* Time in ms to wait for final packets to be delivered */ #define FINAL_PACKETS_MS 20 #define EXPECTED_URI_BASE_PATH "/a" #define REQ_HOST "localhost" #define REQ_METHOD "PUT" #define REQ_BODY "Some content 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 generic functions */ void _MHD_sleep (uint32_t ms); /** * Pause execution for specified number of milliseconds. * @param ms the number of milliseconds to sleep */ void _MHD_sleep (uint32_t ms) { #if defined(_WIN32) Sleep (ms); #elif defined(HAVE_NANOSLEEP) struct timespec slp = {ms / 1000, (ms % 1000) * 1000000}; struct timespec rmn; int num_retries = 0; while (0 != nanosleep (&slp, &rmn)) { if (EINTR != errno) externalErrorExit (); if (num_retries++ > 8) break; slp = rmn; } #elif defined(HAVE_USLEEP) uint64_t us = ms * 1000; do { uint64_t this_sleep; if (999999 < us) this_sleep = 999999; else this_sleep = us; /* Ignore return value as it could be void */ usleep (this_sleep); us -= this_sleep; } while (us > 0); #else externalErrorExitDesc ("No sleep function available on this system"); #endif } /* Global parameters */ static int verbose; /**< Be verbose */ static int oneone; /**< If false use HTTP/1.0 for requests*/ static uint16_t 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 use_stress_os; /**< Stress OS by RST before getting ACKs for sent packets */ static int by_step; /**< Send request byte-by-byte */ static int upl_chunked; /**< Use chunked encoding for request body */ static unsigned int rate_limiter; /**< Maximum number of checks per second */ static void test_global_init (void) { rate_limiter = 0; if (use_hard_close) { #ifdef HAVE_SYSCTLBYNAME if (1) { int blck_hl; size_t blck_hl_size = sizeof (blck_hl); if (0 == sysctlbyname ("net.inet.tcp.blackhole", &blck_hl, &blck_hl_size, NULL, 0)) { if (2 <= blck_hl) { fprintf (stderr, "'sysctl net.inet.tcp.blackhole = %d', test is " "unreliable with this system setting, skipping.\n", blck_hl); exit (77); } } else { if (ENOENT != errno) externalErrorExitDesc ("Cannot get 'net.inet.tcp.blackhole' value"); } } #endif #if defined(HAVE_SYSCTL) && defined(CTL_NET) && defined(PF_INET) && \ defined(IPPROTO_ICMP) && defined(ICMPCTL_ICMPLIM) if (1) { int mib[4]; int limit; size_t limit_size = sizeof(limit); mib[0] = CTL_NET; mib[1] = PF_INET; mib[2] = IPPROTO_ICMP; mib[3] = ICMPCTL_ICMPLIM; if (0 != sysctl (mib, 4, &limit, &limit_size, NULL, 0)) { if (ENOENT == errno) limit = 0; /* No such parameter (new Darwin versions) */ else externalErrorExitDesc ("Cannot get RST rate limit value"); } else if (sizeof(limit) != limit_size) externalErrorExitDesc ("Cannot get RST rate limit value"); if (limit > 0) { #ifndef _MHD_HEAVY_TESTS fprintf (stderr, "This system has limits on number of RST packet" " per second (%d).\nThis test will be used only if configured " "with '--enable-heavy-test'.\n", limit); exit (77); #else /* _MHD_HEAVY_TESTS */ int test_limit; /**< Maximum number of checks per second */ if (use_stress_os) { fprintf (stderr, "This system has limits on number of RST packet" " per second (%d).\n'_stress_os' is not possible.\n", limit); exit (77); } test_limit = limit - limit / 10; /* Add some space to not hit the limiter */ test_limit /= 4; /* Assume that all four tests with 'hard_close' run in parallel */ test_limit -= 5; /* Add some more space to not hit the limiter */ test_limit /= 3; /* Use only one third of available limit */ if (test_limit <= 0) { fprintf (stderr, "System limit for 'net.inet.icmp.icmplim' is " "too strict for this test (value: %d).\n", limit); exit (77); } if (verbose) { printf ("Limiting number of checks to %d checks/second.\n", test_limit); fflush (stdout); } rate_limiter = (unsigned int) test_limit; #if ! defined(HAVE_USLEEP) && ! defined(HAVE_NANOSLEEP) && ! defined(_WIN32) fprintf (stderr, "Sleep function is required for this test, " "but not available on this system.\n"); exit (77); #endif #endif /* _MHD_HEAVY_TESTS */ } } #endif /* HAVE_SYSCTL && CTL_NET && PF_INET && IPPROTO_ICMP && ICMPCTL_ICMPLIM */ } if (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_AUTOSUPPRESS_SIGPIPE)) { #if defined(HAVE_SIGNAL_H) && defined(SIGPIPE) if (SIG_ERR == signal (SIGPIPE, SIG_IGN)) externalErrorExitDesc ("Error suppressing SIGPIPE signal"); #else /* ! HAVE_SIGNAL_H || ! SIGPIPE */ fprintf (stderr, "Cannot suppress SIGPIPE signal.\n"); /* exit (77); */ #endif } } 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 */ } /* DumbClient API */ struct _MHD_dumbClient * _MHD_dumbClient_create (uint16_t port, const char *method, const char *url, const char *add_headers, const uint8_t *req_body, size_t req_body_size, int chunked); void _MHD_dumbClient_set_send_limits (struct _MHD_dumbClient *clnt, size_t step_size, size_t max_total_send); void _MHD_dumbClient_start_connect (struct _MHD_dumbClient *clnt); int _MHD_dumbClient_is_req_sent (struct _MHD_dumbClient *clnt); /** * 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); void _MHD_dumbClient_get_fdsets (struct _MHD_dumbClient *clnt, MHD_socket *maxsckt, fd_set *rs, fd_set *ws, fd_set *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); /** * 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); /** * Close the client and free internally allocated resources. * @param clnt the client to close */ void _MHD_dumbClient_close (struct _MHD_dumbClient *clnt); /* DumbClient implementation */ 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 */ uint16_t port; /**< the port to connect to */ const char *send_buf; /**< the buffer for the request, malloced */ void *buf; /**< the buffer location */ 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 (uint16_t 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_size); mhd_assert (0 == req_body_size || 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"); #ifdef MHD_socket_nosignal_ if (! MHD_socket_nosignal_ (clnt->sckt)) externalErrorExitDesc ("Cannot suppress SIGPIPE on the client socket"); #endif /* MHD_socket_nosignal_ */ clnt->sckt_nonblock = 0; if (clnt->sckt_nonblock) make_nonblocking (clnt->sckt); else make_blocking (clnt->sckt); if (1) { /* Always set TCP NODELAY */ const MHD_SCKT_OPT_BOOL_ on_val = 1; if (0 != setsockopt (clnt->sckt, IPPROTO_TCP, TCP_NODELAY, (const void *) &on_val, sizeof (on_val))) externalErrorExitDesc ("Cannot set TCP_NODELAY option"); } 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->buf = send_buf; 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 = MHD_send_ (clnt->sckt, clnt->send_buf + clnt->send_off, send_size); 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"); if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_)) mhdErrorExitDesc ("The connection was shut down on MHD side"); 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 */ _MHD_NORETURN /* Declared as 'noreturn' until it is implemented */ 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: case DUMB_CLIENT_FINISHED: return ! 0; case DUMB_CLIENT_CONNECTING: case DUMB_CLIENT_CONNECTED: case DUMB_CLIENT_REQ_SENDING: case DUMB_CLIENT_HEADER_RECVEIVING: case DUMB_CLIENT_BODY_RECVEIVING: case DUMB_CLIENT_FINISHING: default: break; } return 0; } /** * 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; case DUMB_CLIENT_FINISHED: return ! 0; 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_INVALID_SOCKET != clnt->sckt) { 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); #if ! defined(_WIN32) || defined(__CYGWIN__) tv.tv_sec = (time_t) (TIMEOUTS_VAL * 2 - (now - start) + 1); #else /* Native W32 */ tv.tv_sec = (long) (TIMEOUTS_VAL * 2 - (now - start) + 1); #endif /* Native W32 */ tv.tv_usec = 250 * 1000; if (-1 == select ((int) 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"); Sleep ((DWORD) (tv.tv_sec * 1000 + tv.tv_usec / 1000)); #endif /* ! MHD_POSIX_SOCKETS */ continue; } if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) return 0; } /* Use double timeout value here as MHD must catch timeout situations * in this test. Timeout in client as a last resort. */ } while ((now = time (NULL)) - 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) { mhd_assert (clnt->send_buf == clnt->buf); free (clnt->buf); clnt->buf = NULL; clnt->send_buf = NULL; } free (clnt); } struct sckt_notif_cb_param { volatile unsigned int num_started; volatile unsigned int num_finished; }; static 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 unsigned int num_called; }; static void term_cb (void *cls, struct MHD_Connection *c, void **req_cls, enum MHD_RequestTerminationCode term_code) { struct term_notif_cb_param *param = (struct term_notif_cb_param *) cls; if (NULL == req_cls) mhdErrorExitDesc ("'req_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++; } static 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: break; } return "(unknown code)"; } 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 */ }; static 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 **req_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 != *req_cls) { /* The first call of the callback for this connection */ mhd_assert (NULL == upload_data); param->req_body_uploaded = 0; *req_cls = ▮ 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 */ uint16_t queryPort; /* Additional request headers, static */ const char *headers; /* NULL for request without body */ const 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; struct timeval tv; int ret; const union MHD_DaemonInfo *di; MHD_socket lstn_sk; int client_accepted; int full_req_recieved; int full_req_sent; int some_data_recieved; 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); full_req_recieved = 0; some_data_recieved = 0; start = time (NULL); do { fd_set rs; fd_set ws; fd_set es; MHD_socket maxMhdSk; int num_ready; int do_client; /**< Process data in client */ maxMhdSk = MHD_INVALID_SOCKET; FD_ZERO (&rs); FD_ZERO (&ws); FD_ZERO (&es); if (NULL == clnt) { /* client has finished, check whether MHD is still * processing any connections */ full_req_sent = 1; do_client = 0; if (client_accepted && (0 > MHD_get_timeout64s (d))) { ret = 0; break; /* MHD finished as well */ } } else { full_req_sent = _MHD_dumbClient_is_req_sent (clnt); if (! full_req_sent) do_client = 1; /* Request hasn't been sent yet, send the data */ else { /* All request data has been sent. * Client will close the socket as the next step. */ if (full_req_recieved) { /* All data has been received by the MHD */ do_client = 1; /* Close the client socket */ } else if (some_data_recieved && (! use_hard_close || ((0 == rate_limiter) && use_stress_os))) { /* No RST rate limiter or no "hard close", no need to avoid extra RST * and at least something was received by the MHD */ /* In case of 'hard close' this can stress the OS, especially * if 'by_step' is enabled as several ACKs (for delivered packets * containing the request) from the server may arrive to the client * when the client has closed port and may be reflected by several * RSTs from the client side to the server side (when ACK received * without active connection then RST packet should be sent). * When listening socket receives RST packets, it may block * the sender preventing the next connection. */ do_client = 1; /* Proceed with the closure of the client socket */ } else { /* When rate limiter is enabled, all sent packets must be received * before client closes connection to avoid RST for every ACK. * When rate limiter is not enabled, the MHD must receive at * least something before closing the connection. */ do_client = 0; /* Do not close the client socket yet */ } } if (do_client) _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"); if (do_client) { tv.tv_sec = 1; tv.tv_usec = 250 * 1000; } else { /* Request completely sent but not yet fully received */ tv.tv_sec = 0; tv.tv_usec = FINAL_PACKETS_MS * 1000; } num_ready = select ((int) maxMhdSk + 1, &rs, &ws, &es, &tv); if (-1 == num_ready) { #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 ((DWORD) (tv.tv_sec * 1000 + tv.tv_usec / 1000)); #endif continue; } if (0 == num_ready) { /* select() finished by timeout, looks like no more packets are pending */ if (do_client) externalErrorExitDesc ("Timeout waiting for sockets"); if (full_req_sent && (! full_req_recieved)) full_req_recieved = 1; } 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); else { /* Client connection was already accepted by MHD */ if (! some_data_recieved) { if (! do_client) { if (0 != num_ready) { /* Connection was accepted before, "ready" socket means data */ some_data_recieved = 1; } } else { if (2 == num_ready) some_data_recieved = 1; else if ((1 == num_ready) && ((MHD_INVALID_SOCKET == clnt->sckt) || ! FD_ISSET (clnt->sckt, &ws))) some_data_recieved = 1; } } } if (do_client) { if (_MHD_dumbClient_process_from_fdsets (clnt, &rs, &ws, &es)) clnt = NULL; } /* Use double timeout value here so MHD would be able to catch timeout * internally */ } while (time (NULL) - 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) externalErrorExit (); 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; } static 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 ((enum MHD_RequestTerminationCode) term_result->term_reason)); fprintf (stream, "\n"); fflush (stream); } /* Perform test queries, shut down MHD daemon, and free parameters */ static unsigned int performTestQueries (struct MHD_Daemon *d, uint16_t 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; time_t start; unsigned int ret = 0; /* Return value */ size_t req_total_size; size_t limit_send_size; size_t inc_size; int expected_reason; int found_right_reason; /* Common parameters, to be individually overridden by specific test cases * if needed */ qParam.queryPort = d_port; qParam.method = MHD_HTTP_METHOD_PUT; qParam.queryPath = EXPECTED_URI_BASE_PATH; qParam.headers = REQ_HEADER_CT; qParam.req_body = (const 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); expected_reason = use_hard_close ? MHD_REQUEST_TERMINATED_READ_ERROR : MHD_REQUEST_TERMINATED_CLIENT_ABORT; found_right_reason = 0; if (0 != rate_limiter) { if (verbose) { printf ("Pausing for rate limiter..."); fflush (stdout); } _MHD_sleep (1150); /* Just a bit more than one second */ if (verbose) { printf (" OK\n"); fflush (stdout); } inc_size = ((req_total_size - 1) + (rate_limiter - 1)) / rate_limiter; if (0 == inc_size) inc_size = 1; } else inc_size = 1; start = time (NULL); for (limit_send_size = 1; limit_send_size < req_total_size; limit_send_size += inc_size) { int test_succeed; test_succeed = 0; /* Make sure that maximum size is tested */ if (req_total_size - inc_size < limit_send_size) limit_send_size = req_total_size - 1; 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 (expected_reason == term_result->term_reason) found_right_reason = 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); } if (time (NULL) - start > (time_t) ((TIMEOUTS_VAL * 25) + (rate_limiter * FINAL_PACKETS_MS) / 1000 + 1)) { ret |= 1 << 2; fprintf (stderr, "FAILED: Test total time exceeded.\n"); break; } } MHD_stop_daemon (d); free (uri_cb_param); free (ahc_param); free (term_result); free (sckt_result); if (! found_right_reason) { fprintf (stderr, "FAILED: termination callback was not called with " "expected (%s) reason.\n", term_reason_str ((enum MHD_RequestTerminationCode) expected_reason)); fflush (stderr); ret |= 1 << 1; } 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) { unsigned 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, uint16_t *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)) externalErrorExit (); *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 (((unsigned int) thrType) | ((unsigned 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 | ((unsigned 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) mhdErrorExitDesc ("Failed to start MHD daemon"); if (0 == *pport) { dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); if ((NULL == dinfo) || (0 == dinfo->port)) mhdErrorExitDesc ("MHD_get_daemon_info() failed"); *pport = dinfo->port; if (0 == global_port) global_port = *pport; /* Reuse the same port for all tests */ } return d; } /* Test runners */ static unsigned int testExternalGet (void) { struct MHD_Daemon *d; uint16_t 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; uint16_t 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; uint16_t 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; uint16_t 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; use_stress_os = has_in_name (argv[0], "_stress_os") ? 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") || has_param (argc, argv, "-s") || has_param (argc, argv, "--silent")); if (use_stress_os && ! use_hard_close) return 99; 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 (). Result: %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). " "Result: %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). " "Result: %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). " "Result: %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). " "Result: %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). " "Result: %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). " "Result: %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 */ }