summaryrefslogtreecommitdiffstats
path: root/src/lib-http/test-http-server-errors.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-http/test-http-server-errors.c1042
1 files changed, 1042 insertions, 0 deletions
diff --git a/src/lib-http/test-http-server-errors.c b/src/lib-http/test-http-server-errors.c
new file mode 100644
index 0000000..4a95bde
--- /dev/null
+++ b/src/lib-http/test-http-server-errors.c
@@ -0,0 +1,1042 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "sleep.h"
+#include "connection.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "http-url.h"
+#include "http-request.h"
+#include "http-server.h"
+
+#include <unistd.h>
+
+#define SERVER_MAX_TIMEOUT_MSECS 10*1000
+#define CLIENT_KILL_TIMEOUT_SECS 20
+
+static void main_deinit(void);
+
+/*
+ * Types
+ */
+
+struct client_connection {
+ struct connection conn;
+
+ pool_t pool;
+};
+
+typedef void
+(*test_server_init_t)(const struct http_server_settings *server_set);
+typedef void (*test_client_init_t)(unsigned int index);
+
+/*
+ * State
+ */
+
+/* common */
+static struct ip_addr bind_ip;
+static in_port_t bind_port = 0;
+static struct ioloop *ioloop;
+static bool debug = FALSE;
+
+/* server */
+static struct http_server *http_server = NULL;
+static struct io *io_listen;
+static int fd_listen = -1;
+static void (*test_server_request)(struct http_server_request *req);
+
+/* client */
+static struct connection_list *client_conn_list;
+static unsigned int client_index;
+static void (*test_client_connected)(struct client_connection *conn);
+static void (*test_client_input)(struct client_connection *conn);
+
+/*
+ * Forward declarations
+ */
+
+/* server */
+static void test_server_defaults(struct http_server_settings *http_set);
+static void test_server_run(const struct http_server_settings *http_set);
+
+/* client */
+static void client_connection_deinit(struct client_connection **_conn);
+static void test_client_run(unsigned int index);
+
+/* test*/
+static void
+test_run_client_server(const struct http_server_settings *server_set,
+ test_server_init_t server_test,
+ test_client_init_t client_test,
+ unsigned int client_tests_count) ATTR_NULL(3);
+
+/*
+ * Slow request
+ */
+
+/* client */
+
+static void
+test_slow_request_input(struct client_connection *conn ATTR_UNUSED)
+{
+ /* do nothing */
+}
+
+static void test_slow_request_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "\r\n");
+}
+
+static void test_client_slow_request(unsigned int index)
+{
+ test_client_input = test_slow_request_input;
+ test_client_connected = test_slow_request_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _slow_request {
+ struct http_server_request *req;
+ struct timeout *to_delay;
+ bool serviced:1;
+};
+
+static void test_server_slow_request_destroyed(struct _slow_request *ctx)
+{
+ test_assert(ctx->serviced);
+ timeout_remove(&ctx->to_delay);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void test_server_slow_request_delayed(struct _slow_request *ctx)
+{
+ struct http_server_response *resp;
+ struct http_server_request *req = ctx->req;
+
+ resp = http_server_response_create(req, 200, "OK");
+ http_server_response_submit(resp);
+ ctx->serviced = TRUE;
+
+ http_server_request_unref(&req);
+}
+
+static void test_server_slow_request_request(struct http_server_request *req)
+{
+ const struct http_request *hreq = http_server_request_get(req);
+ struct _slow_request *ctx;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _slow_request, 1);
+ ctx->req = req;
+
+ http_server_request_set_destroy_callback(
+ req, test_server_slow_request_destroyed, ctx);
+
+ http_server_request_ref(req);
+ ctx->to_delay =
+ timeout_add(4000, test_server_slow_request_delayed, ctx);
+}
+
+static void
+test_server_slow_request(const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_slow_request_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_slow_request(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("slow request");
+ test_run_client_server(&http_server_set, test_server_slow_request,
+ test_client_slow_request, 1);
+ test_end();
+}
+
+/*
+ * Hanging request payload
+ */
+
+/* client */
+
+static void
+test_hanging_request_payload_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Content-Length: 1000\r\n"
+ "\r\n"
+ "To be continued... or not");
+}
+
+static void test_client_hanging_request_payload(unsigned int index)
+{
+ test_client_connected = test_hanging_request_payload_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _hanging_request_payload {
+ struct http_server_request *req;
+ struct istream *payload_input;
+ struct io *io;
+ bool serviced:1;
+};
+
+static void
+test_server_hanging_request_payload_destroyed(
+ struct _hanging_request_payload *ctx)
+{
+ test_assert(!ctx->serviced);
+ io_remove(&ctx->io);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_hanging_request_payload_input(struct _hanging_request_payload *ctx)
+{
+ struct http_server_response *resp;
+ struct http_server_request *req = ctx->req;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ if (debug)
+ i_debug("test server: got more payload");
+
+ while ((ret = i_stream_read_data(ctx->payload_input,
+ &data, &size, 0)) > 0)
+ i_stream_skip(ctx->payload_input, size);
+
+ if (ret == 0)
+ return;
+ if (ctx->payload_input->stream_errno != 0) {
+ if (debug) {
+ i_debug("test server: failed to read payload: %s",
+ i_stream_get_error(ctx->payload_input));
+ }
+ i_stream_unref(&ctx->payload_input);
+ io_remove(&ctx->io);
+ http_server_request_fail_close(req, 400, "Bad request");
+ http_server_request_unref(&req);
+ return;
+ }
+
+ i_assert(ctx->payload_input->eof);
+
+ resp = http_server_response_create(req, 200, "OK");
+ http_server_response_submit(resp);
+ ctx->serviced = TRUE;
+
+ i_stream_unref(&ctx->payload_input);
+ http_server_request_unref(&req);
+}
+
+static void
+test_server_hanging_request_payload_request(struct http_server_request *req)
+{
+ const struct http_request *hreq = http_server_request_get(req);
+ struct _hanging_request_payload *ctx;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _hanging_request_payload, 1);
+ ctx->req = req;
+
+ http_server_request_set_destroy_callback(
+ req, test_server_hanging_request_payload_destroyed, ctx);
+
+ ctx->payload_input = http_server_request_get_payload_input(req, FALSE);
+
+ http_server_request_ref(req);
+ ctx->io = io_add_istream(ctx->payload_input,
+ test_server_hanging_request_payload_input,
+ ctx);
+ test_server_hanging_request_payload_input(ctx);
+}
+
+static void
+test_server_hanging_request_payload(
+ const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_hanging_request_payload_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_hanging_request_payload(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("hanging request payload");
+ test_run_client_server(&http_server_set,
+ test_server_hanging_request_payload,
+ test_client_hanging_request_payload, 1);
+ test_end();
+}
+
+/*
+ * Hanging response payload
+ */
+
+/* client */
+
+static void
+test_hanging_response_payload_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Content-Length: 18\r\n"
+ "\r\n"
+ "Complete payload\r\n");
+}
+
+static void test_client_hanging_response_payload(unsigned int index)
+{
+ test_client_connected = test_hanging_response_payload_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _hanging_response_payload {
+ struct http_server_request *req;
+ struct istream *payload_input;
+ struct io *io;
+ bool serviced:1;
+};
+
+static void
+test_server_hanging_response_payload_destroyed(
+ struct _hanging_response_payload *ctx)
+{
+ test_assert(!ctx->serviced);
+ io_remove(&ctx->io);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_hanging_response_payload_request(struct http_server_request *req)
+{
+ const struct http_request *hreq =
+ http_server_request_get(req);
+ struct http_server_response *resp;
+ struct _hanging_response_payload *ctx;
+ string_t *payload;
+ unsigned int i;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _hanging_response_payload, 1);
+ ctx->req = req;
+
+ http_server_request_set_destroy_callback(
+ req, test_server_hanging_response_payload_destroyed, ctx);
+
+ resp = http_server_response_create(req, 200, "OK");
+ T_BEGIN {
+ payload = t_str_new(204800);
+ for (i = 0; i < 3200; i++) {
+ str_append(payload,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
+ }
+
+ http_server_response_set_payload_data(resp, str_data(payload),
+ str_len(payload));
+ } T_END;
+ http_server_response_submit(resp);
+}
+
+static void
+test_server_hanging_response_payload(
+ const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_hanging_response_payload_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_hanging_response_payload(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.socket_send_buffer_size = 4096;
+ http_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("hanging response payload");
+ test_run_client_server(&http_server_set,
+ test_server_hanging_response_payload,
+ test_client_hanging_response_payload, 1);
+ test_end();
+}
+
+/*
+ * Excessive payload length
+ */
+
+/* client */
+
+static void
+test_excessive_payload_length_connected1(struct client_connection *conn)
+{
+ o_stream_nsend_str(
+ conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Content-Length: 150\r\n"
+ "\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n");
+}
+
+static void test_client_excessive_payload_length1(unsigned int index)
+{
+ test_client_connected = test_excessive_payload_length_connected1;
+ test_client_run(index);
+}
+
+static void
+test_excessive_payload_length_connected2(struct client_connection *conn)
+{
+ o_stream_nsend_str(
+ conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "0\r\n"
+ "\r\n");
+}
+
+static void test_client_excessive_payload_length2(unsigned int index)
+{
+ test_client_connected = test_excessive_payload_length_connected2;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _excessive_payload_length {
+ struct http_server_request *req;
+ buffer_t *buffer;
+ bool serviced:1;
+};
+
+static void
+test_server_excessive_payload_length_destroyed(
+ struct _excessive_payload_length *ctx)
+{
+ struct http_server_response *resp;
+ const char *reason;
+ int status;
+
+ resp = http_server_request_get_response(ctx->req);
+ test_assert(resp != NULL);
+ if (resp != NULL) {
+ http_server_response_get_status(resp, &status, &reason);
+ test_assert(status == 413);
+ }
+
+ test_assert(!ctx->serviced);
+ buffer_free(&ctx->buffer);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_excessive_payload_length_finished(
+ struct _excessive_payload_length *ctx)
+{
+ struct http_server_response *resp;
+
+ resp = http_server_response_create(ctx->req, 200, "OK");
+ http_server_response_submit(resp);
+ ctx->serviced = TRUE;
+}
+
+static void
+test_server_excessive_payload_length_request(struct http_server_request *req)
+{
+ const struct http_request *hreq = http_server_request_get(req);
+ struct _excessive_payload_length *ctx;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _excessive_payload_length, 1);
+ ctx->req = req;
+ ctx->buffer = buffer_create_dynamic(default_pool, 128);
+
+ http_server_request_set_destroy_callback(
+ req, test_server_excessive_payload_length_destroyed, ctx);
+ http_server_request_buffer_payload(
+ req, ctx->buffer, 128,
+ test_server_excessive_payload_length_finished, ctx);
+}
+
+static void
+test_server_excessive_payload_length(
+ const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_excessive_payload_length_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_excessive_payload_length(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("excessive payload length (length)");
+ test_run_client_server(&http_server_set,
+ test_server_excessive_payload_length,
+ test_client_excessive_payload_length1, 1);
+ test_end();
+
+ test_begin("excessive payload length (chunked)");
+ test_run_client_server(&http_server_set,
+ test_server_excessive_payload_length,
+ test_client_excessive_payload_length2, 1);
+ test_end();
+}
+
+/*
+ * Response ostream disconnect
+ */
+
+/* client */
+
+static void
+test_response_ostream_disconnect_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Content-Length: 18\r\n"
+ "\r\n"
+ "Complete payload\r\n");
+ i_sleep_intr_msecs(10);
+ client_connection_deinit(&conn);
+ io_loop_stop(ioloop);
+}
+
+static void test_client_response_ostream_disconnect(unsigned int index)
+{
+ test_client_connected = test_response_ostream_disconnect_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _response_ostream_disconnect {
+ struct http_server_request *req;
+ struct istream *payload_input;
+ struct ostream *payload_output;
+ struct io *io;
+ bool finished:1;
+ bool seen_stream_error:1;
+};
+
+static void
+test_server_response_ostream_disconnect_destroyed(
+ struct _response_ostream_disconnect *ctx)
+{
+ test_assert(ctx->seen_stream_error);
+ io_remove(&ctx->io);
+ i_stream_unref(&ctx->payload_input);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_response_ostream_disconnect_output(
+ struct _response_ostream_disconnect *ctx)
+{
+ struct ostream *output = ctx->payload_output;
+ enum ostream_send_istream_result res;
+ int ret;
+
+ if (ctx->finished) {
+ ret = o_stream_finish(output);
+ if (ret == 0)
+ return ret;
+ if (ret < 0) {
+ if (debug) {
+ i_debug("OUTPUT ERROR: %s",
+ o_stream_get_error(output));
+ }
+ test_assert(output->stream_errno == ECONNRESET ||
+ output->stream_errno == EPIPE);
+
+ ctx->seen_stream_error = TRUE;
+ o_stream_destroy(&ctx->payload_output);
+ return -1;
+ }
+ return 1;
+ }
+
+ o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
+ res = o_stream_send_istream(output, ctx->payload_input);
+ o_stream_set_max_buffer_size(output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ ctx->finished = TRUE;
+ return test_server_response_ostream_disconnect_output(ctx);
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ if (debug)
+ i_debug("WAIT OUTPUT");
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ if (debug) {
+ i_debug("OUTPUT ERROR: %s",
+ o_stream_get_error(output));
+ }
+ test_assert(output->stream_errno == ECONNRESET ||
+ output->stream_errno == EPIPE);
+
+ ctx->seen_stream_error = TRUE;
+ o_stream_destroy(&ctx->payload_output);
+ return -1;
+ }
+ i_unreached();
+}
+
+static void
+test_server_response_ostream_disconnect_request(struct http_server_request *req)
+{
+ const struct http_request *hreq = http_server_request_get(req);
+ struct http_server_response *resp;
+ struct _response_ostream_disconnect *ctx;
+ string_t *data;
+ unsigned int i;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _response_ostream_disconnect, 1);
+ ctx->req = req;
+
+ data = str_new(default_pool, 2048000);
+ for (i = 0; i < 32000; i++) {
+ str_append(data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
+ }
+ ctx->payload_input = i_stream_create_copy_from_data(
+ str_data(data), str_len(data));
+ str_free(&data);
+
+ resp = http_server_response_create(req, 200, "OK");
+ ctx->payload_output = http_server_response_get_payload_output(
+ resp, IO_BLOCK_SIZE, FALSE);
+
+ o_stream_add_destroy_callback(
+ ctx->payload_output,
+ test_server_response_ostream_disconnect_destroyed, ctx);
+
+ o_stream_set_flush_callback(
+ ctx->payload_output,
+ test_server_response_ostream_disconnect_output, ctx);
+ o_stream_set_flush_pending(ctx->payload_output, TRUE);
+}
+
+static void
+test_server_response_ostream_disconnect(
+ const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_response_ostream_disconnect_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_response_ostream_disconnect(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.socket_send_buffer_size = 4096;
+ http_server_set.max_client_idle_time_msecs = 10000;
+
+ test_begin("response ostream disconnect");
+ test_run_client_server(&http_server_set,
+ test_server_response_ostream_disconnect,
+ test_client_response_ostream_disconnect, 1);
+ test_end();
+}
+
+/*
+ * All tests
+ */
+
+static void (*const test_functions[])(void) = {
+ test_slow_request,
+ test_hanging_request_payload,
+ test_hanging_response_payload,
+ test_excessive_payload_length,
+ test_response_ostream_disconnect,
+ NULL
+};
+
+/*
+ * Test client
+ */
+
+/* client connection */
+
+static void client_connection_input(struct connection *_conn)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ if (test_client_input != NULL)
+ test_client_input(conn);
+}
+
+static void client_connection_connected(struct connection *_conn, bool success)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ if (success && test_client_connected != NULL)
+ test_client_connected(conn);
+}
+
+static void client_connection_init(const struct ip_addr *ip, in_port_t port)
+{
+ struct client_connection *conn;
+ pool_t pool;
+
+ pool = pool_alloconly_create("client connection", 512);
+ conn = p_new(pool, struct client_connection, 1);
+ conn->pool = pool;
+
+ connection_init_client_ip(client_conn_list, &conn->conn, NULL,
+ ip, port);
+ (void)connection_client_connect(&conn->conn);
+}
+
+static void client_connection_deinit(struct client_connection **_conn)
+{
+ struct client_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ connection_deinit(&conn->conn);
+ pool_unref(&conn->pool);
+}
+
+static void client_connection_destroy(struct connection *_conn)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ client_connection_deinit(&conn);
+}
+
+/* */
+
+static struct connection_settings client_connection_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = TRUE
+};
+
+static const struct connection_vfuncs client_connection_vfuncs = {
+ .destroy = client_connection_destroy,
+ .client_connected = client_connection_connected,
+ .input = client_connection_input
+};
+
+static void test_client_run(unsigned int index)
+{
+ client_index = index;
+
+ if (debug)
+ i_debug("client connecting to %u", bind_port);
+
+ client_conn_list = connection_list_init(&client_connection_set,
+ &client_connection_vfuncs);
+
+ client_connection_init(&bind_ip, bind_port);
+
+ io_loop_run(ioloop);
+
+ /* close server socket */
+ io_remove(&io_listen);
+
+ connection_list_deinit(&client_conn_list);
+}
+
+/*
+ * Test server
+ */
+
+static void test_server_defaults(struct http_server_settings *http_set)
+{
+ /* server settings */
+ i_zero(http_set);
+ http_set->max_client_idle_time_msecs = 5*1000;
+ http_set->max_pipelined_requests = 1;
+ http_set->debug = debug;
+}
+
+/* client connection */
+
+static void
+server_handle_request(void *context ATTR_UNUSED,
+ struct http_server_request *req)
+{
+ test_server_request(req);
+}
+
+struct http_server_callbacks http_server_callbacks = {
+ .handle_request = server_handle_request
+};
+
+static void server_connection_accept(void *context ATTR_UNUSED)
+{
+ int fd;
+
+ /* accept new client */
+ fd = net_accept(fd_listen, NULL, NULL);
+ if (fd == -1)
+ return;
+ if (fd == -2) {
+ i_fatal("test server: accept() failed: %m");
+ }
+
+ (void)http_server_connection_create(http_server, fd, fd, FALSE,
+ &http_server_callbacks, NULL);
+}
+
+/* */
+
+static void test_server_timeout(void *context ATTR_UNUSED)
+{
+ i_fatal("Server timed out");
+}
+
+static void test_server_run(const struct http_server_settings *http_set)
+{
+ struct timeout *to;
+
+ to = timeout_add(SERVER_MAX_TIMEOUT_MSECS, test_server_timeout, NULL);
+
+ /* open server socket */
+ io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL);
+
+ http_server = http_server_init(http_set);
+
+ io_loop_run(ioloop);
+
+ /* close server socket */
+ io_remove(&io_listen);
+ timeout_remove(&to);
+
+ http_server_deinit(&http_server);
+}
+
+/*
+ * Tests
+ */
+
+struct test_client_data {
+ unsigned int index;
+ test_client_init_t client_test;
+};
+
+static int test_open_server_fd(void)
+{
+ int fd = net_listen(&bind_ip, &bind_port, 128);
+ if (debug)
+ i_debug("server listening on %u", bind_port);
+ if (fd == -1) {
+ i_fatal("listen(%s:%u) failed: %m",
+ net_ip2addr(&bind_ip), bind_port);
+ }
+ return fd;
+}
+
+static int test_run_client(struct test_client_data *data)
+{
+ i_close_fd(&fd_listen);
+
+ i_set_failure_prefix("CLIENT[%u]: ", data->index + 1);
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ /* Wait a little for server setup */
+ i_sleep_msecs(100);
+
+ ioloop = io_loop_create();
+ data->client_test(data->index);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+
+ main_deinit();
+ return 0;
+}
+
+static void
+test_run_server(const struct http_server_settings *server_set,
+ test_server_init_t server_test)
+{
+ i_set_failure_prefix("SERVER: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ ioloop = io_loop_create();
+ server_test(server_set);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+}
+
+static void
+test_run_client_server(const struct http_server_settings *server_set,
+ test_server_init_t server_test,
+ test_client_init_t client_test,
+ unsigned int client_tests_count)
+{
+ unsigned int i;
+
+ fd_listen = test_open_server_fd();
+
+ if (client_tests_count > 0) {
+ for (i = 0; i < client_tests_count; i++) {
+ struct test_client_data data;
+
+ i_zero(&data);
+ data.index = i;
+ data.client_test = client_test;
+
+ /* Fork client */
+ test_subprocess_fork(test_run_client, &data, FALSE);
+ }
+ }
+
+ /* Run server */
+ test_run_server(server_set, server_test);
+
+ i_unset_failure_prefix();
+ i_close_fd(&fd_listen);
+ test_subprocess_kill_all(CLIENT_KILL_TIMEOUT_SECS);
+}
+
+/*
+ * Main
+ */
+
+static void main_init(void)
+{
+ /* nothing yet */
+}
+
+static void main_deinit(void)
+{
+ /* nothing yet; also called from sub-processes */
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int ret;
+
+ lib_init();
+ main_init();
+
+ while ((c = getopt(argc, argv, "D")) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D]", argv[0]);
+ }
+ }
+
+ test_subprocesses_init(debug);
+
+ /* listen on localhost */
+ i_zero(&bind_ip);
+ bind_ip.family = AF_INET;
+ bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK);
+
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ main_deinit();
+ lib_deinit();
+
+ return ret;
+}