libmicrohttpd

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

commit d50159bf9987c018e9ca7ed4cd72390494678da1
parent 7bbbcd63f987b5451a29d3e084e045fc2ba1d14a
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
Date:   Mon, 18 Jan 2021 17:16:14 +0300

Implemented new function MHD_create_response_from_iovec()

Implemented the new function, related framework, and tests for iovec-based
responses.
The implementation is based on the patch provided by Lawrence Sebald and
Damon N. Earp from NASA.

Diffstat:
Mdoc/libmicrohttpd.texi | 27+++++++++++++++++++++++++++
Msrc/include/microhttpd.h | 38++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/connection.c | 39++++++++++++++++++++++++++++++++++-----
Msrc/microhttpd/internal.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/mhd_send.c | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/mhd_send.h | 22++++++++++++++++++++++
Msrc/microhttpd/response.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testcurl/.gitignore | 2++
Msrc/testcurl/Makefile.am | 16+++++++++++++++-
Msrc/testcurl/https/.gitignore | 1+
Msrc/testcurl/https/Makefile.am | 16+++++++++++++++-
Asrc/testcurl/https/test_https_get_iovec.c | 421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/testcurl/test_get_iovec.c | 757+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 1797 insertions(+), 7 deletions(-)

diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi @@ -1270,6 +1270,11 @@ Handle for a response. @end deftp +@deftp {C Struct} MHD_IoVec +An element of an array of memory buffers. +@end deftp + + @deftp {C Struct} MHD_PostProcessor @cindex POST method Handle for @code{POST} processing. @@ -2170,6 +2175,28 @@ MHD_destroy_response(response); @end example +@deftypefun {struct MHD_Response *} MHD_create_response_from_iovec (const struct MHD_IoVec *iov, int iovcnt, MHD_ContentReaderFreeCallback crfc, void *cls) +Create a response object from an array of memory buffers. +The response object can be extended with header information and then be used +any number of times. +@table @var +@item iov +the array for response data buffers, an internal copy of this will be made; + +@item iovcnt +the number of elements in @var{iov}; + +@item crfc +the callback to call to free resources associated with @var{iov}; + +@item cls +the argument to @var{crfc}; +@end table + +Return @code{NULL} on error (i.e. invalid arguments, out of memory). +@end deftypefun + + @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -1974,6 +1974,23 @@ union MHD_ConnectionInfo /** + * I/O vector type. Provided for use with MHD_create_response_from_iovec. + */ +struct MHD_IoVec +{ + /** + * The pointer to the memory region for I/O. + */ + void *iov_base; + + /** + * The size in bytes of the memory region for I/O. + */ + size_t iov_len; +}; + + +/** * Values of this enum are used to specify what * information about a connection is desired. * @ingroup request @@ -3230,6 +3247,27 @@ MHD_create_response_from_fd_at_offset64 (uint64_t size, /** + * Create a response object from an array of memory buffers. + * The response object can be extended with header information and then be used + * any number of times. + * + * @param iov the array for response data buffers, an internal copy of this + * will be made + * @param iovcnt the number of elements in @a iov + * @param free_cb the callback to clean up any data associated with @a iov when + * the response is destroyed. + * @param cls the argument passed to @a free_cb + * @return NULL on error (i.e. invalid arguments, out of memory) + * @ingroup response + */ +_MHD_EXTERN struct MHD_Response * +MHD_create_response_from_iovec (const struct MHD_IoVec *iov, + int iovcnt, + MHD_ContentReaderFreeCallback free_cb, + void *cls); + + +/** * Enumeration for actions MHD should perform on the underlying socket * of the upgrade. This API is not finalized, and in particular * the final set of actions is yet to be decided. This is just an diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -794,11 +794,33 @@ try_ready_normal_body (struct MHD_Connection *connection) struct MHD_Response *response; response = connection->response; - if (NULL == response->crc) - return MHD_YES; if ( (0 == response->total_size) || (connection->response_write_position == response->total_size) ) return MHD_YES; /* 0-byte response is always ready */ + if (NULL != response->data_iov) + { + size_t copy_size; + if (NULL != connection->resp_iov.iov) + return MHD_YES; + + copy_size = response->data_iovcnt * sizeof(MHD_iovec_); + connection->resp_iov.iov = MHD_pool_allocate (connection->pool, copy_size, + true); + if (NULL == connection->resp_iov.iov) + { + MHD_mutex_unlock_chk_ (&response->mutex); + /* not enough memory */ + CONNECTION_CLOSE_ERROR (connection, + _ ("Closing connection (out of memory).\n")); + return MHD_NO; + } + memcpy (connection->resp_iov.iov, response->data_iov, copy_size); + connection->resp_iov.cnt = response->data_iovcnt; + connection->resp_iov.sent = 0; + return MHD_YES; + } + if (NULL == response->crc) + return MHD_YES; if ( (response->data_start <= connection->response_write_position) && (response->data_size + response->data_start > @@ -2935,6 +2957,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) connection->response_write_position) ); if ( (NULL == resp->crc) && + (NULL == resp->data_iov) && (0 == connection->response_write_position) ) { mhd_assert (resp->total_size >= resp->data_size); @@ -3017,12 +3040,16 @@ MHD_connection_handle_write (struct MHD_Connection *connection) #if defined(_MHD_HAVE_SENDFILE) if (MHD_resp_sender_sendfile == connection->resp_sender) { + mhd_assert (NULL == response->data_iov); ret = MHD_send_sendfile_ (connection); } + else /* combined with the next 'if' */ +#endif /* _MHD_HAVE_SENDFILE */ + if (NULL != response->data_iov) + { + ret = MHD_send_iovec_ (connection, &connection->resp_iov, true); + } else -#else /* ! _MHD_HAVE_SENDFILE */ - if (1) -#endif /* ! _MHD_HAVE_SENDFILE */ { data_write_offset = connection->response_write_position - response->data_start; @@ -3674,6 +3701,8 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) connection->write_buffer_size = 0; connection->write_buffer_send_offset = 0; connection->write_buffer_append_offset = 0; + /* iov (if any) was deallocated by MHD_pool_reset */ + memset (&connection->resp_iov, 0, sizeof(connection->resp_iov)); continue; case MHD_CONNECTION_CLOSED: cleanup_connection (connection); diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -343,6 +343,57 @@ struct MHD_HTTP_Header }; +#if defined(MHD_WINSOCK_SOCKETS) +/** + * Internally used I/O vector type for use with winsock. + * Binary matches system "WSABUF". + */ +typedef struct _MHD_W32_iovec +{ + unsigned long iov_len; + char *iov_base; +} MHD_iovec_; +#define MHD_IOV_ELMN_MAX_SIZE ULONG_MAX +#elif defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) +/** + * Internally used I/O vector type for use when writev or sendmsg + * is available. Matches system "struct iovec". + */ +typedef struct iovec MHD_iovec_; +#define MHD_IOV_ELMN_MAX_SIZE SIZE_MAX +#else +/** + * Internally used I/O vector type for use when writev or sendmsg + * is not available. + */ +typedef struct MHD_IoVec MHD_iovec_; +#define MHD_IOV_ELMN_MAX_SIZE SIZE_MAX +#endif + + +struct MHD_iovec_track_ +{ + /** + * The copy of array of iovec elements. + * The copy of elements are updated during sending. + * The number of elements is not changed during lifetime. + */ + MHD_iovec_ *iov; + + /** + * The number of elements in @iov. + * This value is not changed during lifetime. + */ + size_t cnt; + + /** + * The number of sent elements. + * At the same time, it is the index of the next (or current) element + * to send. + */ + size_t sent; +}; + /** * Representation of a response. */ @@ -450,6 +501,15 @@ struct MHD_Response */ bool is_pipe; + /** + * I/O vector used with MHD_create_response_from_iovec. + */ + MHD_iovec_ *data_iov; + + /** + * Number of elements in data_iov. + */ + size_t data_iovcnt; }; @@ -873,6 +933,15 @@ struct MHD_Connection */ uint64_t response_write_position; + /** + * The copy of iov response. + * Valid if iovec response is used. + * Updated during send. + * Members are allocated in the pool. + */ + struct MHD_iovec_track_ resp_iov; + + #if defined(_MHD_HAVE_SENDFILE) enum MHD_resp_sender_ { diff --git a/src/microhttpd/mhd_send.c b/src/microhttpd/mhd_send.c @@ -51,6 +51,15 @@ #include "mhd_limits.h" +#ifdef MHD_VECT_SEND +#if (! defined (HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL)) && \ + defined (MHD_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ + defined (MHD_SEND_SPIPE_SUPPRESS_NEEDED) +#define _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED 1 +#endif /* (!HAVE_SENDMSG || !MSG_NOSIGNAL) && + MHD_SEND_SPIPE_SUPPRESS_POSSIBLE && MHD_SEND_SPIPE_SUPPRESS_NEEDED */ +#endif /* MHD_VECT_SEND */ + /** * sendfile() chuck size */ @@ -1243,3 +1252,237 @@ MHD_send_sendfile_ (struct MHD_Connection *connection) #endif /* _MHD_HAVE_SENDFILE */ + +#if defined(MHD_VECT_SEND) + + +/** + * Function sends iov data by system sendmsg or writev function. + * + * Connection must be in non-TLS (non-HTTPS) mode. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov data structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @return actual number of bytes sent + */ +static ssize_t +send_iov_nontls (struct MHD_Connection *connection, + struct MHD_iovec_track_ *const r_iov, + bool push_data) +{ + ssize_t res; + ssize_t total_sent; + size_t items_to_send; +#ifdef HAVE_SENDMSG + struct msghdr msg; +#elif defined(MHD_WINSOCK_SOCKETS) + DWORD bytes_sent; + DWORD cnt_w; +#endif /* MHD_WINSOCK_SOCKETS */ + + mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); + + if ( (MHD_INVALID_SOCKET == connection->socket_fd) || + (MHD_CONNECTION_CLOSED == connection->state) ) + { + return MHD_ERR_NOTCONN_; + } + + pre_send_setopt (connection, false, push_data); + + items_to_send = r_iov->cnt - r_iov->sent; +#ifdef HAVE_SENDMSG + memset (&msg, 0, sizeof(struct msghdr)); + msg.msg_iov = r_iov->iov + r_iov->sent; + msg.msg_iovlen = items_to_send; + + res = sendmsg (connection->socket_fd, &msg, MSG_NOSIGNAL_OR_ZERO); +#elif defined(HAVE_WRITEV) + res = writev (connection->socket_fd, r_iov->iov + r_iov->sent, + items_to_send); +#elif defined(MHD_WINSOCK_SOCKETS) +#ifdef _WIN64 + cnt_w = (items_to_send > UINT32_MAX) ? UINT32_MAX : (DWORD) items_to_send; +#else /* ! _WIN64 */ + cnt_w = (DWORD) items_to_send; +#endif /* ! _WIN64 */ + if (0 == WSASend (connection->socket_fd, + (LPWSABUF) (r_iov->iov + r_iov->sent), + cnt_w, + &bytes_sent, 0, NULL, NULL)) + res = (ssize_t) bytes_sent; + else + res = -1; +#else /* !HAVE_SENDMSG && !HAVE_WRITEV && !MHD_WINSOCK_SOCKETS */ +#error No vector-send function available +#endif + + if (0 > res) + { + const int err = MHD_socket_get_error_ (); + + if (MHD_SCKT_ERR_IS_EAGAIN_ (err)) + { +#ifdef EPOLL_SUPPORT + /* EAGAIN --- no longer write-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; +#endif /* EPOLL_SUPPORT */ + return MHD_ERR_AGAIN_; + } + if (MHD_SCKT_ERR_IS_EINTR_ (err)) + return MHD_ERR_AGAIN_; + if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ECONNRESET_)) + return MHD_ERR_CONNRESET_; + /* Treat any other error as hard error. */ + return MHD_ERR_NOTCONN_; + } + + /* Some data has been sent */ + total_sent = res; + /* Adjust the internal tracking information for the iovec to + * take this last send into account. */ + while ((0 != res) && (r_iov->iov[r_iov->sent].iov_len <= (size_t) res)) + { + res -= r_iov->iov[r_iov->sent].iov_len; + r_iov->sent++; /* The iov element has been completely sent */ + mhd_assert ((r_iov->cnt > r_iov->sent) || (0 == res)); + } + + if (r_iov->cnt == r_iov->sent) + post_send_setopt (connection, false, push_data); + else + { +#ifdef EPOLL_SUPPORT + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; +#endif /* EPOLL_SUPPORT */ + if (0 != res) + { + mhd_assert (r_iov->cnt > r_iov->sent); + /* The last iov element has been partially sent */ + r_iov->iov[r_iov->sent].iov_base = + (void*) ((uint8_t*) r_iov->iov[r_iov->sent].iov_base + (size_t) res); + r_iov->iov[r_iov->sent].iov_len -= res; + } + } + + return total_sent; +} + + +#endif /* MHD_VECT_SEND */ + +#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + + +/** + * Function sends iov data by sending buffers one-by-one by standard + * data send function. + * + * Connection could be in HTTPS or non-HTTPS mode. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov data structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @return actual number of bytes sent + */ +static ssize_t +send_iov_emu (struct MHD_Connection *connection, + struct MHD_iovec_track_ *const r_iov, + bool push_data) +{ + const bool non_blk = connection->sk_nonblck; + size_t total_sent; + ssize_t res; + + mhd_assert (NULL != r_iov->iov); + total_sent = 0; + do + { + if ((size_t) SSIZE_MAX - total_sent < r_iov->iov[r_iov->sent].iov_len) + return total_sent; /* return value would overflow */ + + res = MHD_send_data_ (connection, + r_iov->iov[r_iov->sent].iov_base, + r_iov->iov[r_iov->sent].iov_len, + push_data && (r_iov->cnt == r_iov->sent + 1)); + if (0 > res) + { + /* Result is an error */ + if (0 == total_sent) + return res; /* Nothing was sent, return result as is */ + + if (MHD_ERR_AGAIN_ == res) + return total_sent; /* Some data has been sent, return the amount */ + + return res; /* Any kind of a hard error */ + } + + total_sent += (size_t) res; + + if (r_iov->iov[r_iov->sent].iov_len != (size_t) res) + { + /* Incomplete buffer has been sent. + * Adjust buffer of the last element. */ + r_iov->iov[r_iov->sent].iov_base = + (void*) ((uint8_t*) r_iov->iov[r_iov->sent].iov_base + (size_t) res); + r_iov->iov[r_iov->sent].iov_len -= res; + + return total_sent; + } + /* The iov element has been completely sent */ + r_iov->sent++; + } while ((r_iov->cnt > r_iov->sent) && (non_blk)); + + return (ssize_t) total_sent; +} + + +#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT + || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + + +ssize_t +MHD_send_iovec_ (struct MHD_Connection *connection, + struct MHD_iovec_track_ *const r_iov, + bool push_data) +{ +#ifdef MHD_VECT_SEND +#if defined(HTTPS_SUPPORT) || \ + defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + bool use_iov_send = true; +#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* MHD_VECT_SEND */ + + mhd_assert (NULL != connection->resp_iov.iov); + mhd_assert (NULL != connection->response->data_iov); + mhd_assert (connection->resp_iov.cnt > connection->resp_iov.sent); +#ifdef MHD_VECT_SEND +#if defined(HTTPS_SUPPORT) || \ + defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) +#ifdef HTTPS_SUPPORT + use_iov_send = use_iov_send && + (0 == (connection->daemon->options & MHD_USE_TLS)); +#endif /* HTTPS_SUPPORT */ +#ifdef _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED + use_iov_send = use_iov_send && (connection->daemon->sigpipe_blocked || + connection->sk_spipe_suppress); +#endif /* _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + if (use_iov_send) +#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ + return send_iov_nontls (connection, r_iov, push_data); +#endif /* MHD_VECT_SEND */ + +#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \ + defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) + return send_iov_emu (connection, r_iov, push_data); +#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT + || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +} diff --git a/src/microhttpd/mhd_send.h b/src/microhttpd/mhd_send.h @@ -41,6 +41,11 @@ #include "connection_https.h" #endif +#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) || \ + defined(MHD_WINSOCK_SOCKETS) +#define MHD_VECT_SEND 1 +#endif /* HAVE_SENDMSG || HAVE_WRITEV || MHD_WINSOCK_SOCKETS */ + #ifdef HAVE_FREEBSD_SENDFILE /** * Initialises static variables @@ -125,4 +130,21 @@ MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection, bool nodelay_state); +/** + * Function for sending responses backed by a an array of memory buffers. + * + * @param connection the MHD connection structure + * @param r_iov the pointer to iov response structure with tracking + * @param push_data set to true to force push the data to the network from + * system buffers (usually set for the last piece of data), + * set to false to prefer holding incomplete network packets + * (more data will be send for the same reply). + * @return actual number of bytes sent + */ +ssize_t +MHD_send_iovec_ (struct MHD_Connection *connection, + struct MHD_iovec_track_ *r_iov, + bool push_data); + + #endif /* MHD_SEND_H */ diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c @@ -846,6 +846,153 @@ MHD_create_response_from_buffer_with_free_callback (size_t size, } +/** + * Create a response object from an array of memory buffers. + * The response object can be extended with header information and then be used + * any number of times. + * + * @param iov the array for response data buffers, an internal copy of this + * will be made + * @param iovcnt the number of elements in @a iov + * @param free_cb the callback to clean up any data associated with @a iov when + * the response is destroyed. + * @param cls the argument passed to @a free_cb + * @return NULL on error (i.e. invalid arguments, out of memory) + */ +_MHD_EXTERN struct MHD_Response * +MHD_create_response_from_iovec (const struct MHD_IoVec *iov, + int iovcnt, + MHD_ContentReaderFreeCallback free_cb, + void *cls) +{ + struct MHD_Response *response; + + if ((NULL == iov) && (0 < iovcnt)) + return NULL; + + response = MHD_calloc_ (1, sizeof (struct MHD_Response)); + if (NULL != response) + { + if (MHD_mutex_init_ (&response->mutex)) + { + int i; + int i_cp; /**< Index in the copy of iov */ + uint64_t total_size; + void *last_valid_buffer; + + i_cp = 0; + total_size = 0; + last_valid_buffer = NULL; + /* Calculate final size, number of valid elements, and check 'iov' */ + for (i = 0; iovcnt > i; ++i) + { +#if defined(MHD_WINSOCK_SOCKETS) && defined(_WIN64) + int64_t i_add; +#endif /* ! MHD_WINSOCK_SOCKETS && _WIN64 */ + if (0 == iov[i].iov_len) + continue; /* skip zero-sized elements */ + + if (NULL == iov[i].iov_base) + { + i_cp = -1; /* error */ + break; + } + if ( (total_size > (total_size + iov[i].iov_len)) || + (INT_MAX == i_cp) || + (SSIZE_MAX < iov[i].iov_len) ) + { + i_cp = -1; /* overflow */ + break; + } + last_valid_buffer = iov[i].iov_base; + total_size += iov[i].iov_len; +#if defined(MHD_POSIX_SOCKETS) || ! defined(_WIN64) + i_cp++; +#else /* ! MHD_POSIX_SOCKETS && _WIN64 */ + i_add = iov[i].iov_len / ULONG_MAX; + if (0 != iov[i].iov_len % ULONG_MAX) + i_add++; + if (INT_MAX < (i_add + i_cp)) + { + i_cp = -1; /* overflow */ + break; + } + i_cp += (int) i_add; +#endif /* ! MHD_POSIX_SOCKETS && _WIN64 */ + } + if (0 <= i_cp) + { + response->fd = -1; + response->reference_count = 1; + response->total_size = total_size; + response->crc_cls = cls; + response->crfc = free_cb; + if (1 < i_cp) + { + MHD_iovec_ *iov_copy; + int num_copy_elements = i_cp; + + iov_copy = MHD_calloc_ (num_copy_elements, sizeof(MHD_iovec_)); + if (NULL != iov_copy) + { + i_cp = 0; + for (i = 0; iovcnt > i; ++i) + { + size_t element_size; + uint8_t *buf; + + if (0 == iov[i].iov_len) + continue; /* skip zero-sized elements */ + + buf = (uint8_t*) iov[i].iov_base; + element_size = iov[i].iov_len; +#if defined(MHD_WINSOCK_SOCKETS) && defined(_WIN64) + while (ULONG_MAX < element_size) + { + iov_copy[i_cp].iov_base = (void*) buf; + iov_copy[i_cp].iov_len = ULONG_MAX; + buf += ULONG_MAX; + element_size -= ULONG_MAX; + i_cp++; + } +#endif /* MHD_WINSOCK_SOCKETS && _WIN64 */ + iov_copy[i_cp].iov_base = (void*) buf; + iov_copy[i_cp].iov_len = element_size; + i_cp++; + } + + mhd_assert (num_copy_elements == i_cp); + response->data_iov = iov_copy; + response->data_iovcnt = i_cp; + + return response; + } + + } + else if (1 == i_cp) + { + mhd_assert (NULL != last_valid_buffer); + response->data = last_valid_buffer; + response->data_size = total_size; + + return response; + } + else /* if (0 == i_nz) */ + { + mhd_assert (0 == total_size); + + return response; + } + } + /* Some error condition */ + MHD_mutex_destroy_chk_ (&response->mutex); + } + free (response); + } + return NULL; +} + + #ifdef UPGRADE_SUPPORT /** * This connection-specific callback is provided by MHD to @@ -1287,6 +1434,12 @@ MHD_destroy_response (struct MHD_Response *response) #endif if (NULL != response->crfc) response->crfc (response->crc_cls); + + if (NULL != response->data_iov) + { + free (response->data_iov); + } + while (NULL != response->first_header) { pos = response->first_header; diff --git a/src/testcurl/.gitignore b/src/testcurl/.gitignore @@ -116,3 +116,5 @@ test_patch11 /test_add_conn_cleanup /test_add_conn_cleanup_nolisten core +/test_get_iovec +/test_get_iovec11 diff --git a/src/testcurl/Makefile.am b/src/testcurl/Makefile.am @@ -78,6 +78,7 @@ endif if HAVE_CURL check_PROGRAMS = \ test_get \ + test_get_iovec \ test_get_sendfile \ test_delete \ test_patch \ @@ -89,6 +90,7 @@ check_PROGRAMS = \ test_parse_cookies \ test_large_put \ test_get11 \ + test_get_iovec11 \ test_get_sendfile11 \ test_patch11 \ test_put11 \ @@ -218,6 +220,12 @@ test_digestauth_with_arguments_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ @LIBGCRYPT_LIBS@ @LIBCURL@ +test_get_iovec_SOURCES = \ + test_get_iovec.c mhd_has_in_name.h +test_get_iovec_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + @LIBCURL@ + test_get_sendfile_SOURCES = \ test_get_sendfile.c mhd_has_in_name.h test_get_sendfile_LDADD = \ @@ -306,7 +314,7 @@ test_put_chunked_SOURCES = \ test_put_chunked_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ @LIBCURL@ - + test_add_conn_SOURCES = \ test_add_conn.c $(top_srcdir)/src/microhttpd/test_helpers.h test_add_conn_CFLAGS = \ @@ -345,6 +353,12 @@ test_get11_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ @LIBCURL@ +test_get_iovec11_SOURCES = \ + test_get_iovec.c mhd_has_in_name.h +test_get_iovec11_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + @LIBCURL@ + test_get_sendfile11_SOURCES = \ test_get_sendfile.c mhd_has_in_name.h test_get_sendfile11_LDADD = \ diff --git a/src/testcurl/https/.gitignore b/src/testcurl/https/.gitignore @@ -53,4 +53,5 @@ /mhds_multi_daemon_test /tls_authentication_test /tmp_ca_cert.pem +/test_https_get_iovec *.exe diff --git a/src/testcurl/https/Makefile.am b/src/testcurl/https/Makefile.am @@ -1,4 +1,6 @@ # This Makefile.am is in the public domain +EMPTY_ITEM = + SUBDIRS = . if USE_COVERAGE @@ -34,7 +36,9 @@ THREAD_ONLY_TESTS = \ test_https_time_out \ test_https_multi_daemon \ test_https_get \ - test_empty_response + test_empty_response \ + test_https_get_iovec \ + $(EMPTY_ITEM) check_PROGRAMS = \ test_https_get_select @@ -155,6 +159,16 @@ test_https_get_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ $(MHD_TLS_LIB_LDFLAGS) $(MHD_TLS_LIBDEPS) @LIBGCRYPT_LIBS@ @LIBCURL@ +test_https_get_iovec_SOURCES = \ + test_https_get_iovec.c \ + tls_test_keys.h \ + tls_test_common.h \ + tls_test_common.c +test_https_get_iovec_LDADD = \ + $(top_builddir)/src/testcurl/libcurl_version_check.a \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + $(MHD_TLS_LIB_LDFLAGS) $(MHD_TLS_LIBDEPS) @LIBGCRYPT_LIBS@ @LIBCURL@ + if HAVE_GNUTLS_SNI test_https_sni_SOURCES = \ test_https_sni.c \ diff --git a/src/testcurl/https/test_https_get_iovec.c b/src/testcurl/https/test_https_get_iovec.c @@ -0,0 +1,421 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007-2021 Christian Grothoff + Copyright (C) 2016-2021 Evgeny Grin + + 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 3, 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_https_get_iovec.c + * @brief Testcase for libmicrohttpd HTTPS GET operations using an iovec + * @author Sagie Amir + * @author Karlson2k (Evgeny Grin) + * @author Lawrence Sebald + */ + +/* + * This testcase is derived from the test_https_get.c testcase. This version + * adds the usage of a scatter/gather array for storing the response data. + */ + +#include "platform.h" +#include "microhttpd.h" +#include <limits.h> +#include <sys/stat.h> +#include <curl/curl.h> +#ifdef MHD_HTTPS_REQUIRE_GRYPT +#include <gcrypt.h> +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ +#include "tls_test_common.h" + +extern const char srv_signed_cert_pem[]; +extern const char srv_signed_key_pem[]; + + +static int global_port; + +/* Use large enough pieces (>16KB) to test partially consumed + * data as TLS doesn't take more than 16KB by a single call. */ +#define TESTSTR_IOVLEN 20480 +#define TESTSTR_IOVCNT 30 +#define TESTSTR_SIZE (TESTSTR_IOVCNT * TESTSTR_IOVLEN) + + +static void +iov_free_callback (void *cls) +{ + free (cls); +} + + +static int +check_read_data (const void *ptr, size_t len) +{ + const int *buf; + size_t i; + + if (len % sizeof(int)) + return -1; + + buf = (const int *) ptr; + + for (i = 0; i < len / sizeof(int); ++i) + { + if (buf[i] != (int) i) + return -1; + } + + return 0; +} + + +static enum MHD_Result +iovec_ahc (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 **ptr) +{ + static int aptr; + struct MHD_Response *response; + enum MHD_Result ret; + int *data; + struct MHD_IoVec iov[TESTSTR_IOVCNT]; + int i; + int j; + (void) cls; (void) url; (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, MHD_HTTP_METHOD_GET)) + return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + *ptr = NULL; /* reset when done */ + + /* Create some test data. */ + if (NULL == (data = malloc (TESTSTR_SIZE))) + return MHD_NO; + + for (j = 0; j < TESTSTR_IOVCNT; ++j) + { + /* Assign chunks of memory area in the reverse order + * to make non-continous set of data therefore + * possible buffer overruns could be detected */ + iov[j].iov_base = data + (((TESTSTR_IOVCNT - 1) - j) + * (TESTSTR_SIZE / TESTSTR_IOVCNT + / sizeof(int))); + iov[j].iov_len = TESTSTR_SIZE / TESTSTR_IOVCNT; + + for (i = 0; i < (int) (TESTSTR_IOVLEN / sizeof(int)); ++i) + ((int*) iov[j].iov_base)[i] = i + (j * TESTSTR_IOVLEN / sizeof(int)); + } + + response = MHD_create_response_from_iovec (iov, + TESTSTR_IOVCNT, + &iov_free_callback, + data); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + + +static int +test_iovec_transfer (void *cls, + int port, + const char *cipher_suite, + int proto_version) +{ + int len; + int ret = 0; + struct CBC cbc; + char url[255]; + (void) cls; /* Unused. Silent compiler warning. */ + + len = TESTSTR_SIZE; + if (NULL == (cbc.buf = malloc (sizeof (char) * len))) + { + fprintf (stderr, MHD_E_MEM); + return -1; + } + cbc.size = len; + cbc.pos = 0; + + if (gen_test_file_url (url, + sizeof (url), + port)) + { + ret = -1; + goto cleanup; + } + + if (CURLE_OK != + send_curl_req (url, &cbc, cipher_suite, proto_version)) + { + ret = -1; + goto cleanup; + } + + /* compare test file & daemon response */ + if ((cbc.pos != TESTSTR_SIZE) || + (0 != check_read_data (cbc.buf, cbc.pos))) + { + fprintf (stderr, "Error: local file & received file differ.\n"); + ret = -1; + } +cleanup: + free (cbc.buf); + return ret; +} + + +/* perform a HTTP GET request via SSL/TLS */ +static int +test_secure_get (FILE *test_fd, + const char *cipher_suite, + int proto_version) +{ + int ret; + struct MHD_Daemon *d; + int port; + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + port = 3041; + + d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION + | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS + | MHD_USE_ERROR_LOG, port, + NULL, NULL, + &iovec_ahc, NULL, + MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem, + MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem, + MHD_OPTION_END); + + if (d == NULL) + { + fprintf (stderr, MHD_E_SERVER_INIT); + return -1; + } + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return -1; + } + port = (int) dinfo->port; + } + + ret = test_iovec_transfer (test_fd, + port, + cipher_suite, + proto_version); + + MHD_stop_daemon (d); + return ret; +} + + +static enum MHD_Result +ahc_empty (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 **unused) +{ + static int ptr; + struct MHD_Response *response; + enum MHD_Result ret; + struct MHD_IoVec iov; + (void) cls; + (void) url; + (void) url; + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcasecmp ("GET", + method)) + return MHD_NO; /* unexpected method */ + if (&ptr != *unused) + { + *unused = &ptr; + return MHD_YES; + } + *unused = NULL; + + iov.iov_base = NULL; + iov.iov_len = 0; + + response = MHD_create_response_from_iovec (&iov, + 1, + NULL, + NULL); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + if (ret == MHD_NO) + { + fprintf (stderr, "Failed to queue response.\n"); + _exit (20); + } + return ret; +} + + +static int +curlExcessFound (CURL *c, + curl_infotype type, + char *data, + size_t size, + void *cls) +{ + static const char *excess_found = "Excess found"; + const size_t str_size = strlen (excess_found); + (void) c; /* Unused. Silence compiler warning. */ + + if ((CURLINFO_TEXT == type) + && (size >= str_size) + && (0 == strncmp (excess_found, data, str_size))) + *(int *) cls = 1; + return 0; +} + + +static int +testEmptyGet (int poll_flag) +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + CURLcode errornum; + int excess_found = 0; + + + if ( (0 == global_port) && + (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) ) + { + global_port = 1225; + + } + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG + | poll_flag | MHD_USE_TLS, + global_port, NULL, NULL, + &ahc_empty, NULL, + MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem, + MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem, + MHD_OPTION_END); + if (d == NULL) + return 4194304; + if (0 == global_port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + global_port = (int) dinfo->port; + } + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "https://127.0.0.1/"); + curl_easy_setopt (c, CURLOPT_PORT, (long) global_port); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_DEBUGFUNCTION, &curlExcessFound); + curl_easy_setopt (c, CURLOPT_DEBUGDATA, &excess_found); + curl_easy_setopt (c, CURLOPT_VERBOSE, 1L); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system!*/ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 8388608; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != 0) + return 16777216; + if (excess_found) + return 33554432; + return 0; +} + + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + const char *aes256_sha_tlsv1 = "AES256-SHA"; + (void) argc; (void) argv; /* Unused. Silent compiler warning. */ + +#ifdef MHD_HTTPS_REQUIRE_GRYPT + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); +#ifdef GCRYCTL_INITIALIZATION_FINISHED + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ + if (! testsuite_curl_global_init ()) + return 99; + if (NULL == curl_version_info (CURLVERSION_NOW)->ssl_version) + { + fprintf (stderr, "Curl does not support SSL. Cannot run the test.\n"); + curl_global_cleanup (); + return 77; + } + + if (curl_uses_nss_ssl () == 0) + { + aes256_sha_tlsv1 = "rsa_aes_256_sha"; + } + errorCount += + test_secure_get (NULL, aes256_sha_tlsv1, CURL_SSLVERSION_TLSv1); + errorCount += testEmptyGet (0); + curl_global_cleanup (); + + return errorCount != 0 ? 1 : 0; +} diff --git a/src/testcurl/test_get_iovec.c b/src/testcurl/test_get_iovec.c @@ -0,0 +1,757 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007-2021 Christian Grothoff + Copyright (C) 2014-2021 Evgeny Grin + + 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_get_iovec.c + * @brief Testcase for libmicrohttpd response from scatter/gather array + * @author Christian Grothoff + * @author Karlson2k (Evgeny Grin) + * @author Lawrence Sebald + */ + +/* + * This test is largely derived from the test_get_sendfile.c file, with the + * daemon using MHD_create_response_from_iovec instead of working from an fd. + */ + +#include "MHD_config.h" +#include "platform.h" +#include <curl/curl.h> +#include <microhttpd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <fcntl.h> +#include "mhd_sockets.h" +#include "mhd_has_in_name.h" + +#ifndef WINDOWS +#include <sys/socket.h> +#include <unistd.h> +#endif + +#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 + +#define TESTSTR_IOVLEN 20480 +#define TESTSTR_IOVCNT 20 +#define TESTSTR_SIZE (TESTSTR_IOVCNT * TESTSTR_IOVLEN) + +static int oneone; + +static int readbuf[TESTSTR_SIZE * 2 / sizeof(int)]; + +struct CBC +{ + char *buf; + size_t pos; + size_t size; +}; + + +static size_t +copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + struct CBC *cbc = ctx; + + if (cbc->pos + size * nmemb > cbc->size) + _exit (7); /* overflow */ + memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); + cbc->pos += size * nmemb; + return size * nmemb; +} + + +static void +iov_free_callback (void *cls) +{ + free (cls); +} + + +static void +iovncont_free_callback (void *cls) +{ + struct MHD_IoVec *iov = (struct MHD_IoVec *) cls; + int i; + + for (i = 0; i < TESTSTR_IOVCNT; ++i) + { + free (iov[i].iov_base); + } + + free (iov); +} + + +static int +check_read_data (const void *ptr, size_t len) +{ + const int *buf; + size_t i; + + if (len % sizeof(int)) + return -1; + + buf = (const int *) ptr; + + for (i = 0; i < len / sizeof(int); ++i) + { + if (buf[i] != (int) i) + return -1; + } + + return 0; +} + + +static enum MHD_Result +ahc_echo (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 **unused) +{ + static int ptr; + const char *me = cls; + struct MHD_Response *response; + enum MHD_Result ret; + int *data; + struct MHD_IoVec iov[TESTSTR_IOVCNT]; + int i; + (void) url; (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (me, method)) + return MHD_NO; /* unexpected method */ + if (&ptr != *unused) + { + *unused = &ptr; + return MHD_YES; + } + *unused = NULL; + + /* Create some test data. */ + if (NULL == (data = malloc (TESTSTR_SIZE))) + return MHD_NO; + + for (i = 0; i < (int) (TESTSTR_SIZE / sizeof(int)); ++i) + { + data[i] = i; + } + + for (i = 0; i < TESTSTR_IOVCNT; ++i) + { + iov[i].iov_base = data + (i * (TESTSTR_SIZE / TESTSTR_IOVCNT + / sizeof(int))); + iov[i].iov_len = TESTSTR_SIZE / TESTSTR_IOVCNT; + } + + response = MHD_create_response_from_iovec (iov, + TESTSTR_IOVCNT, + &iov_free_callback, + data); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + if (ret == MHD_NO) + abort (); + return ret; +} + + +static enum MHD_Result +ncont_echo (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 **unused) +{ + static int ptr; + const char *me = cls; + struct MHD_Response *response; + enum MHD_Result ret; + int *data; + struct MHD_IoVec *iov; + int i, j; + (void) url; (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (me, method)) + return MHD_NO; /* unexpected method */ + if (&ptr != *unused) + { + *unused = &ptr; + return MHD_YES; + } + *unused = NULL; + + if (NULL == (iov = malloc (sizeof(struct MHD_IoVec) * TESTSTR_IOVCNT))) + return MHD_NO; + + memset (iov, 0, sizeof(struct MHD_IoVec) * TESTSTR_IOVCNT); + + /* Create some test data. */ + for (j = TESTSTR_IOVCNT - 1; j >= 0; --j) + { + if (NULL == (data = malloc (TESTSTR_IOVLEN))) + goto err_out; + + iov[j].iov_base = data; + iov[j].iov_len = TESTSTR_IOVLEN; + + for (i = 0; i < (int) (TESTSTR_IOVLEN / sizeof(int)); ++i) + { + data[i] = i + (j * TESTSTR_IOVLEN / sizeof(int)); + } + } + + response = MHD_create_response_from_iovec (iov, + TESTSTR_IOVCNT, + &iovncont_free_callback, + iov); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + if (ret == MHD_NO) + abort (); + return ret; + +err_out: + for (j = 0; j < TESTSTR_IOVCNT; ++j) + { + if (NULL != iov[j].iov_base) + free (iov[j].iov_base); + } + + return MHD_NO; +} + + +static int +testInternalGet (bool contiguous) +{ + struct MHD_Daemon *d; + CURL *c; + struct CBC cbc; + CURLcode errornum; + int port; + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + { + port = 1200; + if (oneone) + port += 10; + } + + cbc.buf = (char*) readbuf; + cbc.size = sizeof(readbuf); + cbc.pos = 0; + + if (contiguous) + { + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, + port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + } + else + { + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, + port, NULL, NULL, &ncont_echo, "GET", MHD_OPTION_END); + } + + if (d == NULL) + return 1; + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + port = (int) dinfo->port; + } + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/"); + curl_easy_setopt (c, CURLOPT_PORT, (long) port); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + if (oneone) + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + else + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system!*/ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != TESTSTR_SIZE) + return 4; + if (0 != check_read_data (cbc.buf, cbc.pos)) + return 8; + return 0; +} + + +static int +testMultithreadedGet () +{ + struct MHD_Daemon *d; + CURL *c; + struct CBC cbc; + CURLcode errornum; + int port; + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + { + port = 1201; + if (oneone) + port += 10; + } + + cbc.buf = (char*) readbuf; + cbc.size = sizeof(readbuf); + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION + | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG + | MHD_USE_AUTO, + port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + if (d == NULL) + return 16; + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + port = (int) dinfo->port; + } + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/"); + curl_easy_setopt (c, CURLOPT_PORT, (long) port); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + if (oneone) + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + else + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 32; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != TESTSTR_SIZE) + return 64; + if (0 != check_read_data (cbc.buf, cbc.pos)) + return 128; + return 0; +} + + +static int +testMultithreadedPoolGet () +{ + struct MHD_Daemon *d; + CURL *c; + struct CBC cbc; + CURLcode errornum; + int port; + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + { + port = 1202; + if (oneone) + port += 10; + } + + cbc.buf = (char*) readbuf; + cbc.size = sizeof(readbuf); + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG + | MHD_USE_AUTO, + port, NULL, NULL, &ahc_echo, "GET", + MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, + MHD_OPTION_END); + if (d == NULL) + return 16; + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + port = (int) dinfo->port; + } + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/"); + curl_easy_setopt (c, CURLOPT_PORT, (long) port); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + if (oneone) + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + else + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system!*/ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 32; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != TESTSTR_SIZE) + return 64; + if (0 != check_read_data (cbc.buf, cbc.pos)) + return 128; + return 0; +} + + +static int +testExternalGet () +{ + struct MHD_Daemon *d; + CURL *c; + struct CBC cbc; + CURLM *multi; + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + MHD_socket maxsock; +#ifdef MHD_WINSOCK_SOCKETS + int maxposixs; /* Max socket number unused on W32 */ +#else /* MHD_POSIX_SOCKETS */ +#define maxposixs maxsock +#endif /* MHD_POSIX_SOCKETS */ + int running; + struct CURLMsg *msg; + time_t start; + struct timeval tv; + int port; + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + { + port = 1203; + if (oneone) + port += 10; + } + + multi = NULL; + cbc.buf = (char*) readbuf; + cbc.size = sizeof(readbuf); + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_ERROR_LOG, + port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + if (d == NULL) + return 256; + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + port = (int) dinfo->port; + } + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/"); + curl_easy_setopt (c, CURLOPT_PORT, (long) port); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + if (oneone) + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + else + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + + + multi = curl_multi_init (); + if (multi == NULL) + { + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 512; + } + mret = curl_multi_add_handle (multi, c); + if (mret != CURLM_OK) + { + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 1024; + } + start = time (NULL); + while ((time (NULL) - start < 5) && (multi != NULL)) + { + maxsock = MHD_INVALID_SOCKET; + maxposixs = -1; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + curl_multi_perform (multi, &running); + mret = curl_multi_fdset (multi, &rs, &ws, &es, &maxposixs); + if (mret != CURLM_OK) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2048; + } + if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxsock)) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 4096; + } + tv.tv_sec = 0; + tv.tv_usec = 1000; + if (-1 == select (maxposixs + 1, &rs, &ws, &es, &tv)) + { +#ifdef MHD_POSIX_SOCKETS + if (EINTR != errno) + abort (); +#else + if ((WSAEINVAL != WSAGetLastError ()) || (0 != rs.fd_count) || (0 != + ws. + fd_count) + || (0 != es.fd_count) ) + abort (); + Sleep (1000); +#endif + } + curl_multi_perform (multi, &running); + if (running == 0) + { + msg = curl_multi_info_read (multi, &running); + if (msg == NULL) + break; + if (msg->msg == CURLMSG_DONE) + { + if (msg->data.result != CURLE_OK) + printf ("%s failed at %s:%d: `%s'\n", + "curl_multi_perform", + __FILE__, + __LINE__, curl_easy_strerror (msg->data.result)); + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + c = NULL; + multi = NULL; + } + } + MHD_run (d); + } + if (multi != NULL) + { + curl_multi_remove_handle (multi, c); + curl_easy_cleanup (c); + curl_multi_cleanup (multi); + } + MHD_stop_daemon (d); + if (cbc.pos != TESTSTR_SIZE) + return 8192; + if (0 != check_read_data (cbc.buf, cbc.pos)) + return 16384; + return 0; +} + + +static int +testUnknownPortGet () +{ + struct MHD_Daemon *d; + const union MHD_DaemonInfo *di; + CURL *c; + struct CBC cbc; + CURLcode errornum; + int port; + char buf[2048]; + + struct sockaddr_in addr; + socklen_t addr_len = sizeof(addr); + memset (&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = INADDR_ANY; + + cbc.buf = (char*) readbuf; + cbc.size = sizeof(readbuf); + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, + 0, NULL, NULL, &ahc_echo, "GET", + MHD_OPTION_SOCK_ADDR, &addr, + MHD_OPTION_END); + if (d == NULL) + return 32768; + + if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + { + di = MHD_get_daemon_info (d, MHD_DAEMON_INFO_LISTEN_FD); + if (di == NULL) + return 65536; + + if (0 != getsockname (di->listen_fd, (struct sockaddr *) &addr, &addr_len)) + return 131072; + + if (addr.sin_family != AF_INET) + return 26214; + port = (int) ntohs (addr.sin_port); + } + else + { + const union MHD_DaemonInfo *dinfo; + dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); + if ((NULL == dinfo) || (0 == dinfo->port) ) + { + MHD_stop_daemon (d); return 32; + } + port = (int) dinfo->port; + } + + snprintf (buf, sizeof(buf), "http://127.0.0.1:%d/", + port); + + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, buf); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + if (oneone) + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + else + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 524288; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != TESTSTR_SIZE) + return 1048576; + if (0 != check_read_data (cbc.buf, cbc.pos)) + return 2097152; + return 0; +} + + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + (void) argc; /* Unused. Silent compiler warning. */ + + if ((NULL == argv) || (0 == argv[0])) + return 99; + oneone = has_in_name (argv[0], "11"); + + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + return 2; + if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS)) + { + errorCount += testInternalGet (true); + errorCount += testInternalGet (false); + errorCount += testMultithreadedGet (); + errorCount += testMultithreadedPoolGet (); + errorCount += testUnknownPortGet (); + } + errorCount += testExternalGet (); + if (errorCount != 0) + fprintf (stderr, "Error (code: %u)\n", errorCount); + curl_global_cleanup (); + return errorCount != 0; /* 0 == pass */ +}