diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-http/test-http-client-errors.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-http/test-http-client-errors.c')
-rw-r--r-- | src/lib-http/test-http-client-errors.c | 3944 |
1 files changed, 3944 insertions, 0 deletions
diff --git a/src/lib-http/test-http-client-errors.c b/src/lib-http/test-http-client-errors.c new file mode 100644 index 0000000..e127041 --- /dev/null +++ b/src/lib-http/test-http-client-errors.c @@ -0,0 +1,3944 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "istream-chain.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-client.h" + +#include <unistd.h> +#include <sys/signal.h> + +#define CLIENT_PROGRESS_TIMEOUT 10 +#define SERVER_KILL_TIMEOUT_SECS 20 + +static void main_deinit(void); + +/* + * Types + */ + +struct server_connection { + struct connection conn; + void *context; + + pool_t pool; + bool version_sent:1; +}; + +typedef void (*test_server_init_t)(unsigned int index); +typedef bool +(*test_client_init_t)(const struct http_client_settings *client_set); +typedef void (*test_dns_init_t)(void); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t *bind_ports = 0; +static struct ioloop *ioloop; +static bool debug = FALSE; + +/* server */ +static struct io *io_listen; +static int fd_listen = -1; +static struct connection_list *server_conn_list; +static size_t server_read_max = 0; +static unsigned int server_index; +static int (*test_server_init)(struct server_connection *conn); +static void (*test_server_deinit)(struct server_connection *conn); +static void (*test_server_input)(struct server_connection *conn); + +/* client */ +static struct timeout *to_client_progress = NULL; +static struct http_client *http_client = NULL; + +/* + * Forward declarations + */ + +/* server */ +static void test_server_run(unsigned int index); +static void server_connection_deinit(struct server_connection **_conn); + +/* client */ +static void test_client_defaults(struct http_client_settings *http_set); +static void test_client_deinit(void); + +/* test*/ +static void +test_run_client_server(const struct http_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) ATTR_NULL(3); + +/* + * Utility + */ + +static void +test_client_assert_response(const struct http_response *resp, + bool condition) +{ + const char *reason = (resp->reason != NULL ? resp->reason : "<NULL>"); + + test_assert(resp->reason != NULL && *resp->reason != '\0'); + + if (!condition) + i_error("BAD RESPONSE: %u %s", resp->status, reason); + else if (debug) + i_debug("RESPONSE: %u %s", resp->status, resp->reason); +} + +/* + * Unconfigured SSL + */ + +/* client */ + +struct _unconfigured_ssl { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_response(const struct http_response *resp, + struct _unconfigured_ssl *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_unconfigured_ssl(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _unconfigured_ssl *ctx; + + ctx = i_new(struct _unconfigured_ssl, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "127.0.0.1", "/unconfigured-ssl.txt", + test_client_unconfigured_ssl_response, ctx); + http_client_request_set_ssl(hreq, TRUE); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "127.0.0.1", "/unconfigured-ssl2.txt", + test_client_unconfigured_ssl_response, ctx); + http_client_request_set_ssl(hreq, TRUE); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("unconfigured ssl"); + test_run_client_server(&http_client_set, + test_client_unconfigured_ssl, NULL, 0, NULL); + test_end(); +} + +/* + * Unconfigured SSL abort + */ + +/* client */ + +struct _unconfigured_ssl_abort { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_abort_response1( + const struct http_response *resp, + struct _unconfigured_ssl_abort *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("RESPONSE: %u %s", resp->status, resp->reason); + + test_out_quiet("inappropriate callback", FALSE); +} + +static void +test_client_unconfigured_ssl_abort_response2( + const struct http_response *resp, struct _unconfigured_ssl_abort *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED); + + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_unconfigured_ssl_abort( + const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _unconfigured_ssl_abort *ctx; + + ctx = i_new(struct _unconfigured_ssl_abort, 1); + ctx->count = 1; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "127.0.0.1", "/unconfigured-ssl.txt", + test_client_unconfigured_ssl_abort_response1, ctx); + http_client_request_set_ssl(hreq, TRUE); + http_client_request_submit(hreq); + http_client_request_abort(&hreq); + + hreq = http_client_request( + http_client, "GET", "127.0.0.1", "/unconfigured-ssl2.txt", + test_client_unconfigured_ssl_abort_response2, ctx); + http_client_request_set_ssl(hreq, TRUE); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl_abort(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("unconfigured ssl abort"); + test_run_client_server(&http_client_set, + test_client_unconfigured_ssl_abort, + NULL, 0, NULL); + test_end(); +} + +/* + * Invalid URL + */ + +/* client */ + +struct _invalid_url { + unsigned int count; +}; + +static void +test_client_invalid_url_response(const struct http_response *resp, + struct _invalid_url *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_INVALID_URL); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_invalid_url(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _invalid_url *ctx; + + ctx = i_new(struct _invalid_url, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request_url_str( + http_client, "GET", "imap://example.com/INBOX", + test_client_invalid_url_response, ctx); + http_client_request_submit(hreq); + + hreq = http_client_request_url_str( + http_client, "GET", "http:/www.example.com", + test_client_invalid_url_response, ctx); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_invalid_url(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("invalid url"); + test_run_client_server(&http_client_set, + test_client_invalid_url, NULL, 0, NULL); + test_end(); +} + +/* + * Host lookup failed + */ + +/* client */ + +struct _host_lookup_failed { + unsigned int count; +}; + +static void +test_client_host_lookup_failed_response(const struct http_response *resp, + struct _host_lookup_failed *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_host_lookup_failed(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _host_lookup_failed *ctx; + + ctx = i_new(struct _host_lookup_failed, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "host.in-addr.arpa", + "/host-lookup-failed.txt", + test_client_host_lookup_failed_response, ctx); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "host.in-addr.arpa", + "/host-lookup-failed2.txt", + test_client_host_lookup_failed_response, ctx); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("host lookup failed"); + test_run_client_server(&http_client_set, + test_client_host_lookup_failed, NULL, 0, NULL); + test_end(); +} + +/* + * Connection refused + */ + +/* server */ + +static void +test_server_connection_refused(unsigned int index ATTR_UNUSED) +{ + i_close_fd(&fd_listen); + + test_subprocess_notify_signal_send_parent(SIGUSR1); +} + +/* client */ + +struct _connection_refused { + unsigned int count; + struct timeout *to; +}; + +static void +test_client_connection_refused_response(const struct http_response *resp, + struct _connection_refused *ctx) +{ + test_assert(ctx->to == NULL); + timeout_remove(&ctx->to); + + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static void +test_client_connection_refused_timeout(struct _connection_refused *ctx) +{ + if (debug) + i_debug("TIMEOUT (ok)"); + timeout_remove(&ctx->to); +} + +static bool +test_client_connection_refused(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _connection_refused *ctx; + + /* wait for the server side to close the socket */ + test_subprocess_notify_signal_wait(SIGUSR1, 10000); + + ctx = i_new(struct _connection_refused, 1); + ctx->count = 2; + + if (client_set->max_connect_attempts > 0) { + ctx->to = timeout_add_short(250, + test_client_connection_refused_timeout, ctx); + } + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-refused.txt", + test_client_connection_refused_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-refused2.txt", + test_client_connection_refused_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("connection refused"); + test_subprocess_notify_signal_reset(SIGUSR1); + test_run_client_server(&http_client_set, + test_client_connection_refused, + test_server_connection_refused, 1, NULL); + test_end(); + + http_client_set.max_connect_attempts = 3; + + test_begin("connection refused backoff"); + test_subprocess_notify_signal_reset(SIGUSR1); + test_run_client_server(&http_client_set, + test_client_connection_refused, + test_server_connection_refused, 1, NULL); + test_end(); +} + +/* + * Connection lost prematurely + */ + +/* server */ + +static void +test_server_connection_lost_prematurely_input(struct server_connection *conn) +{ + server_connection_deinit(&conn); +} + +static void test_server_connection_lost_prematurely(unsigned int index) +{ + test_server_input = test_server_connection_lost_prematurely_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_prematurely { + unsigned int count; + struct timeout *to; +}; + +static void +test_client_connection_lost_prematurely_response( + const struct http_response *resp, + struct _connection_lost_prematurely *ctx) +{ + test_assert(ctx->to == NULL); + timeout_remove(&ctx->to); + + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static void +test_client_connection_lost_prematurely_timeout( + struct _connection_lost_prematurely *ctx) +{ + if (debug) + i_debug("TIMEOUT (ok)"); + timeout_remove(&ctx->to); +} + +static bool +test_client_connection_lost_prematurely( + const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _connection_lost_prematurely *ctx; + + ctx = i_new(struct _connection_lost_prematurely, 1); + ctx->count = 2; + + ctx->to = timeout_add_short( + 250, test_client_connection_lost_prematurely_timeout, ctx); + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-refused-retry.txt", + test_client_connection_lost_prematurely_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-refused-retry2.txt", + test_client_connection_lost_prematurely_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_connection_lost_prematurely(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_connect_attempts = 3; + http_client_set.max_attempts = 3; + + test_begin("connection lost prematurely"); + test_run_client_server(&http_client_set, + test_client_connection_lost_prematurely, + test_server_connection_lost_prematurely, 1, + NULL); + test_end(); +} + +/* + * Connection timed out + */ + +/* client */ + +struct _connection_timed_out { + unsigned int count; +}; + +static void +test_client_connection_timed_out_response(const struct http_response *resp, + struct _connection_timed_out *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_timed_out(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _connection_timed_out *ctx; + + ctx = i_new(struct _connection_timed_out, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "192.168.0.0", "/connection-timed-out.txt", + test_client_connection_timed_out_response, ctx); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "192.168.0.0", "/connection-timed-out2.txt", + test_client_connection_timed_out_response, ctx); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_connection_timed_out(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.connect_timeout_msecs = 1000; + http_client_set.max_attempts = 1; + + test_begin("connection timed out"); + test_run_client_server(&http_client_set, + test_client_connection_timed_out, NULL, 0, NULL); + test_end(); +} + +/* + * Invalid redirect + */ + +/* server */ + +/* -> not accepted */ + +static void test_invalid_redirect_input1(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 302 Redirect\r\n" + "Location: http://localhost:4444\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_invalid_redirect1(unsigned int index) +{ + test_server_input = test_invalid_redirect_input1; + test_server_run(index); +} + +/* -> bad location */ + +static void test_invalid_redirect_input2(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 302 Redirect\r\n" + "Location: unix:/var/run/dovecot/auth-master\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_invalid_redirect2(unsigned int index) +{ + test_server_input = test_invalid_redirect_input2; + test_server_run(index); +} + +/* -> too many */ + +static void test_invalid_redirect_input3(struct server_connection *conn) +{ + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 302 Redirect\r\n" + "Location: http://%s:%u/friep.txt\r\n" + "\r\n", + net_ip2addr(&bind_ip), bind_ports[server_index+1]); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + server_connection_deinit(&conn); +} + +static void test_server_invalid_redirect3(unsigned int index) +{ + test_server_input = test_invalid_redirect_input3; + test_server_run(index); +} + +/* client */ + +static void +test_client_invalid_redirect_response(const struct http_response *resp, + void *context ATTR_UNUSED) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_INVALID_REDIRECT); + + io_loop_stop(ioloop); +} + +static bool +test_client_invalid_redirect(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/invalid-redirect.txt", + test_client_invalid_redirect_response, NULL); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_invalid_redirect(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("invalid redirect: not accepted"); + http_client_set.max_redirects = 0; + test_run_client_server(&http_client_set, + test_client_invalid_redirect, + test_server_invalid_redirect1, 1, NULL); + test_end(); + + test_begin("invalid redirect: bad location"); + http_client_set.max_redirects = 1; + test_run_client_server(&http_client_set, + test_client_invalid_redirect, + test_server_invalid_redirect2, 1, NULL); + test_end(); + + test_begin("invalid redirect: too many"); + http_client_set.max_redirects = 1; + test_run_client_server(&http_client_set, + test_client_invalid_redirect, + test_server_invalid_redirect3, 3, NULL); + test_end(); +} + +/* + * Unseekable redirect + */ + +/* server */ + +static void test_unseekable_redirect_input(struct server_connection *conn) +{ + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 302 Redirect\r\n" + "Location: http://%s:%u/frml.txt\r\n" + "\r\n", + net_ip2addr(&bind_ip), bind_ports[server_index+1]); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + server_connection_deinit(&conn); +} + +static void test_server_unseekable_redirect(unsigned int index) +{ + test_server_input = test_unseekable_redirect_input; + test_server_run(index); +} + +/* client */ + +static void +test_client_unseekable_redirect_response(const struct http_response *resp, + void *context ATTR_UNUSED) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_ABORTED); + + io_loop_stop(ioloop); +} + +static bool +test_client_unseekable_redirect(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct istream *input; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data("FROP", 4); + input->seekable = FALSE; + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/unseekable-redirect.txt", + test_client_unseekable_redirect_response, NULL); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, FALSE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_unseekable_redirect(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_redirects = 1; + + test_begin("unseekable redirect"); + test_run_client_server(&http_client_set, + test_client_unseekable_redirect, + test_server_unseekable_redirect, 2, NULL); + test_end(); +} + +/* + * Unseekable retry + */ + +/* server */ + +static void test_unseekable_retry_input(struct server_connection *conn) +{ + server_connection_deinit(&conn); +} + +static void test_server_unseekable_retry(unsigned int index) +{ + test_server_input = test_unseekable_retry_input; + test_server_run(index); +} + +/* client */ + +static void +test_client_unseekable_retry_response(const struct http_response *resp, + void *context ATTR_UNUSED) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_ABORTED); + + io_loop_stop(ioloop); +} + +static bool +test_client_unseekable_retry(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct istream *input; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data("FROP", 4); + input->seekable = FALSE; + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/unseekable-retry.txt", + test_client_unseekable_retry_response, NULL); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, FALSE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_unseekable_retry(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_attempts = 3; + + test_begin("unseekable retry"); + test_run_client_server(&http_client_set, + test_client_unseekable_retry, + test_server_unseekable_retry, 2, NULL); + test_end(); +} + +/* + * Broken payload + */ + +/* server */ + +static void test_broken_payload_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 18\r\n" + "\r\n" + "Everything is OK\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_broken_payload(unsigned int index) +{ + test_server_input = test_broken_payload_input; + test_server_run(index); +} + +/* client */ + +static void +test_client_broken_payload_response(const struct http_response *resp, + void *context ATTR_UNUSED) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_BROKEN_PAYLOAD); + + io_loop_stop(ioloop); +} + +static bool +test_client_broken_payload(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct istream *input; + + test_expect_errors(1); + + http_client = http_client_init(client_set); + + input = i_stream_create_error_str(EIO, "Moehahahaha!!"); + i_stream_set_name(input, "PURE EVIL"); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/broken-payload.txt", + test_client_broken_payload_response, NULL); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, FALSE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_broken_payload(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("broken payload"); + test_run_client_server(&http_client_set, + test_client_broken_payload, + test_server_broken_payload, 1, NULL); + test_end(); +} + +/* + * Retry payload + */ + +/* server */ + +struct _retry_payload_sctx { + bool eoh; +}; + +static int test_retry_payload_init(struct server_connection *conn) +{ + struct _retry_payload_sctx *ctx; + + ctx = p_new(conn->pool, struct _retry_payload_sctx, 1); + conn->context = ctx; + return 0; +} + +static void test_retry_payload_input(struct server_connection *conn) +{ + struct _retry_payload_sctx *ctx = conn->context; + const char *line; + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (*line == '\0') { + ctx->eoh = TRUE; + continue; + } + if (ctx->eoh) + break; + } + + if (conn->conn.input->stream_errno != 0) { + i_fatal("server: Stream error: %s", + i_stream_get_error(conn->conn.input)); + } + if (line == NULL) { + if (conn->conn.input->eof) + i_fatal("server: Client stream ended prematurely"); + return; + } + + i_assert(ctx->eoh); + + if (strcmp(line, "This is the payload we expect.") == 0) { + if (debug) + i_debug("Expected payload received"); + o_stream_nsend_str(conn->conn.output, + "HTTP/1.1 500 Oh no!\r\n" + "Connection: close\r\n" + "Content-Length: 17\r\n" + "\r\n" + "Expected result\r\n"); + } else { + i_error("Unexpected payload received: `%s'", + str_sanitize(line, 128)); + o_stream_nsend_str(conn->conn.output, + "HTTP/1.1 501 Oh no!\r\n" + "Connection: close\r\n" + "Content-Length: 19\r\n" + "\r\n" + "Unexpected result\r\n"); + } + server_connection_deinit(&conn); +} + +static void test_server_retry_payload(unsigned int index) +{ + test_server_init = test_retry_payload_init; + test_server_input = test_retry_payload_input; + test_server_run(index); +} + +/* client */ + +struct _retry_payload_ctx { + unsigned int count; +}; + +struct _retry_payload_request_ctx { + struct _retry_payload_ctx *ctx; + struct http_client_request *req; +}; + +static void +test_client_retry_payload_response(const struct http_response *resp, + struct _retry_payload_request_ctx *rctx) +{ + struct _retry_payload_ctx *ctx = rctx->ctx; + + test_client_assert_response(resp, resp->status == 500); + + if (http_client_request_try_retry(rctx->req)) { + if (debug) + i_debug("retrying"); + return; + } + i_free(rctx); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_retry_payload(const struct http_client_settings *client_set) +{ + static const char payload[] = "This is the payload we expect.\r\n"; + struct _retry_payload_ctx *ctx; + struct _retry_payload_request_ctx *rctx; + struct http_client_request *hreq; + struct istream *input; + + ctx = i_new(struct _retry_payload_ctx, 1); + ctx->count = 1; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data(payload, sizeof(payload)-1); + + rctx = i_new(struct _retry_payload_request_ctx, 1); + rctx->ctx = ctx; + + rctx->req = hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), "/retry-payload.txt", + test_client_retry_payload_response, rctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, FALSE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_retry_payload(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_attempts = 2; + + server_read_max = 0; + + test_begin("retry payload"); + test_run_client_server(&http_client_set, + test_client_retry_payload, + test_server_retry_payload, 1, NULL); + test_end(); +} + +/* + * Connection lost + */ + +/* server */ + +static void test_connection_lost_input(struct server_connection *conn) +{ + ssize_t ret; + + if (server_read_max == 0) { + server_connection_deinit(&conn); + return; + } + + i_stream_set_max_buffer_size(conn->conn.input, server_read_max); + ret = i_stream_read(conn->conn.input); + if (ret == -2) { + server_connection_deinit(&conn); + return; + } + if (ret < 0) { + i_assert(conn->conn.input->eof); + if (conn->conn.input->stream_errno == 0) + i_fatal("server: Client stream ended prematurely"); + else + i_fatal("server: Stream error: %s", + i_stream_get_error(conn->conn.input)); + } +} + +static void test_server_connection_lost(unsigned int index) +{ + test_server_input = test_connection_lost_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_ctx { + unsigned int count; +}; + +struct _connection_lost_request_ctx { + struct _connection_lost_ctx *ctx; + struct http_client_request *req; +}; + +static void +test_client_connection_lost_response(const struct http_response *resp, + struct _connection_lost_request_ctx *rctx) +{ + struct _connection_lost_ctx *ctx = rctx->ctx; + + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST); + + if (http_client_request_try_retry(rctx->req)) { + if (debug) + i_debug("retrying"); + return; + } + i_free(rctx); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_lost(const struct http_client_settings *client_set) +{ + static const char payload[] = + "This is a useless payload that only serves as a means to give " + "the server the opportunity to close the connection before the " + "payload is finished."; + struct _connection_lost_ctx *ctx; + struct _connection_lost_request_ctx *rctx; + struct http_client_request *hreq; + struct istream *input; + + ctx = i_new(struct _connection_lost_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data(payload, sizeof(payload)-1); + + rctx = i_new(struct _connection_lost_request_ctx, 1); + rctx->ctx = ctx; + + rctx->req = hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost.txt", + test_client_connection_lost_response, rctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, FALSE); + http_client_request_submit(hreq); + + rctx = i_new(struct _connection_lost_request_ctx, 1); + rctx->ctx = ctx; + + rctx->req = hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost2.txt", + test_client_connection_lost_response, rctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_connection_lost(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + server_read_max = 0; + + test_begin("connection lost: one attempt"); + http_client_set.max_attempts = 1; + test_run_client_server(&http_client_set, + test_client_connection_lost, + test_server_connection_lost, 1, NULL); + test_end(); + + test_begin("connection lost: two attempts"); + http_client_set.max_attempts = 2; + test_run_client_server(&http_client_set, + test_client_connection_lost, + test_server_connection_lost, 1, NULL); + test_end(); + + test_begin("connection lost: three attempts"); + http_client_set.max_attempts = 3; + test_run_client_server(&http_client_set, + test_client_connection_lost, + test_server_connection_lost, 1, NULL); + test_end(); + + test_begin("connection lost: manual retry"); + http_client_set.max_attempts = 3; + http_client_set.no_auto_retry = TRUE; + test_run_client_server(&http_client_set, + test_client_connection_lost, + test_server_connection_lost, 1, NULL); + test_end(); +} + +/* + * Connection lost after 100-continue + */ + +/* server */ + +static void test_connection_lost_100_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 100 Continue\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_connection_lost_100(unsigned int index) +{ + test_server_input = test_connection_lost_100_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_100_ctx { + unsigned int count; +}; + +static void +test_client_connection_lost_100_response(const struct http_response *resp, + struct _connection_lost_100_ctx *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_lost_100( + const struct http_client_settings *client_set) +{ + static const char payload[] = + "This is a useless payload that only serves as a means to give " + "the server the opportunity to close the connection before the " + "payload is finished."; + struct _connection_lost_100_ctx *ctx; + struct http_client_request *hreq; + struct istream *input; + + ctx = i_new(struct _connection_lost_100_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data(payload, sizeof(payload)-1); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost.txt", + test_client_connection_lost_100_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, TRUE); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost2.txt", + test_client_connection_lost_100_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, TRUE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_connection_lost_100(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + server_read_max = 0; + + test_begin("connection lost after 100-continue"); + http_client_set.max_attempts = 1; + test_run_client_server(&http_client_set, + test_client_connection_lost_100, + test_server_connection_lost_100, 1, NULL); + test_end(); +} + +/* + * Connection lost in sub-ioloop + */ + +/* server */ + +static void +test_connection_lost_sub_ioloop_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_connection_lost_sub_ioloop(unsigned int index) +{ + test_server_input = test_connection_lost_sub_ioloop_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_sub_ioloop_ctx { + unsigned int count; +}; + +static void +test_client_connection_lost_sub_ioloop_response2( + const struct http_response *resp, struct ioloop *sub_ioloop) +{ + test_client_assert_response( + resp, + (resp->status == 200 || + resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST)); + + io_loop_stop(sub_ioloop); +} + +static void +test_client_connection_lost_sub_ioloop_response( + const struct http_response *resp, + struct _connection_lost_sub_ioloop_ctx *ctx) +{ + struct http_client_request *hreq; + struct ioloop *sub_ioloop; + + if (debug) + i_debug("RESPONSE: %u %s", resp->status, resp->reason); + + test_assert(resp->status == 200 || + resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECTION_LOST); + test_assert(resp->reason != NULL && *resp->reason != '\0'); + + sub_ioloop = io_loop_create(); + http_client_switch_ioloop(http_client); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost-sub-ioloop3.txt", + test_client_connection_lost_sub_ioloop_response2, sub_ioloop); + http_client_request_set_port(hreq, bind_ports[1]); + http_client_request_submit(hreq); + + io_loop_run(sub_ioloop); + io_loop_set_current(ioloop); + http_client_switch_ioloop(http_client); + io_loop_set_current(sub_ioloop); + io_loop_destroy(&sub_ioloop); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_lost_sub_ioloop( + const struct http_client_settings *client_set) +{ + static const char payload[] = + "This is a useless payload that only serves as a means to give " + "the server the opportunity to close the connection before the " + "payload is finished."; + struct _connection_lost_sub_ioloop_ctx *ctx; + struct http_client_request *hreq; + struct istream *input; + + ctx = i_new(struct _connection_lost_sub_ioloop_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + input = i_stream_create_from_data(payload, sizeof(payload)-1); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost-sub-ioloop.txt", + test_client_connection_lost_sub_ioloop_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, TRUE); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/connection-lost-sub-ioloop2.txt", + test_client_connection_lost_sub_ioloop_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_payload(hreq, input, TRUE); + http_client_request_submit(hreq); + + i_stream_unref(&input); + return TRUE; +} + +/* test */ + +static void test_connection_lost_sub_ioloop(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + server_read_max = 0; + + test_begin("connection lost while running sub-ioloop"); + http_client_set.max_attempts = 1; + test_run_client_server(&http_client_set, + test_client_connection_lost_sub_ioloop, + test_server_connection_lost_sub_ioloop, 2, NULL); + test_end(); +} + +/* + * Early success + */ + +/* server */ + +static void test_early_success_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 18\r\n" + "\r\n" + "Everything is OK\r\n"; + + i_sleep_msecs(200); + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_early_success(unsigned int index) +{ + test_server_input = test_early_success_input; + test_server_run(index); +} + +/* client */ + +struct _early_success_ctx { + unsigned int count; +}; + +static void +test_client_early_success_response(const struct http_response *resp, + struct _early_success_ctx *ctx) +{ + if (ctx->count == 2) { + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_BAD_RESPONSE); + } else { + test_client_assert_response(resp, resp->status == 200); + } + + if (--ctx->count == 0) { + io_loop_stop(ioloop); + i_free(ctx); + } +} + +static bool +test_client_early_success(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _early_success_ctx *ctx; + string_t *payload; + unsigned int i; + + ctx = i_new(struct _early_success_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/early-success.txt", + test_client_early_success_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + + T_BEGIN { + struct istream_chain *chain; + struct istream *input, *chain_input; + + payload = t_str_new(64*3000); + for (i = 0; i < 3000; i++) { + str_append(payload, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"); + } + + chain_input = i_stream_create_chain(&chain, IO_BLOCK_SIZE); + + input = i_stream_create_copy_from_data(str_data(payload), + str_len(payload)); + i_stream_chain_append(chain, input); + i_stream_unref(&input); + + http_client_request_set_payload(hreq, chain_input, FALSE); + i_stream_unref(&chain_input); + } T_END; + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/early-success2.txt", + test_client_early_success_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_early_success(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.socket_send_buffer_size = 4096; + + test_begin("early succes"); + test_run_client_server(&http_client_set, + test_client_early_success, + test_server_early_success, 1, NULL); + test_end(); +} + +/* + * Bad response + */ + +/* server */ + +static void test_bad_response_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 666 Really bad response\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_bad_response(unsigned int index) +{ + test_server_input = test_bad_response_input; + test_server_run(index); +} + +/* client */ + +struct _bad_response_ctx { + unsigned int count; +}; + +static void +test_client_bad_response_response(const struct http_response *resp, + struct _bad_response_ctx *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_BAD_RESPONSE); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_bad_response(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _bad_response_ctx *ctx; + + ctx = i_new(struct _bad_response_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/bad-response.txt", + test_client_bad_response_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/bad-response2.txt", + test_client_bad_response_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_bad_response(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("bad response"); + test_run_client_server(&http_client_set, + test_client_bad_response, + test_server_bad_response, 1, NULL); + test_end(); +} + +/* + * Request timed out + */ + +/* server */ + +static void +test_request_timed_out_input(struct server_connection *conn ATTR_UNUSED) +{ + /* do nothing */ +} + +static void test_server_request_timed_out(unsigned int index) +{ + test_server_input = test_request_timed_out_input; + test_server_run(index); +} + +/* client */ + +struct _request_timed_out1_ctx { + unsigned int count; +}; + +static void +test_client_request_timed_out1_response(const struct http_response *resp, + struct _request_timed_out1_ctx *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_TIMED_OUT); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_request_timed_out1(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _request_timed_out1_ctx *ctx; + + ctx = i_new(struct _request_timed_out1_ctx, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-timed-out1-1.txt", + test_client_request_timed_out1_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-timed-out1-2.txt", + test_client_request_timed_out1_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +struct _request_timed_out2_ctx { + struct timeout *to; + unsigned int count; + unsigned int max_parallel_connections; +}; + +static void +test_client_request_timed_out2_timeout(struct _request_timed_out2_ctx *ctx) +{ + timeout_remove(&ctx->to); + i_debug("TIMEOUT"); +} + +static void +test_client_request_timed_out2_response(const struct http_response *resp, + struct _request_timed_out2_ctx *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_TIMED_OUT); + test_assert(ctx->to != NULL); + + if (--ctx->count > 0) { + if (ctx->to != NULL && ctx->max_parallel_connections <= 1) + timeout_reset(ctx->to); + } else { + timeout_remove(&ctx->to); + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_request_timed_out2(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _request_timed_out2_ctx *ctx; + + ctx = i_new(struct _request_timed_out2_ctx, 1); + ctx->count = 2; + ctx->max_parallel_connections = + client_set->max_parallel_connections; + + ctx->to = timeout_add(2000, + test_client_request_timed_out2_timeout, ctx); + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-timed-out2-1.txt", + test_client_request_timed_out2_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_attempt_timeout_msecs(hreq, 1000); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-timed-out2-2.txt", + test_client_request_timed_out2_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_set_attempt_timeout_msecs(hreq, 1000); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_request_timed_out(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("request timed out: one attempt"); + http_client_set.request_timeout_msecs = 1000; + http_client_set.max_attempts = 1; + test_run_client_server(&http_client_set, + test_client_request_timed_out1, + test_server_request_timed_out, 1, NULL); + test_end(); + + test_begin("request timed out: two attempts"); + http_client_set.request_timeout_msecs = 1000; + http_client_set.max_attempts = 1; + test_run_client_server(&http_client_set, + test_client_request_timed_out1, + test_server_request_timed_out, 1, NULL); + test_end(); + + test_begin("request absolutely timed out"); + http_client_set.request_timeout_msecs = 0; + http_client_set.request_absolute_timeout_msecs = 2000; + http_client_set.max_attempts = 3; + test_run_client_server(&http_client_set, + test_client_request_timed_out1, + test_server_request_timed_out, 1, NULL); + test_end(); + + test_begin("request double timed out"); + http_client_set.request_timeout_msecs = 500; + http_client_set.request_absolute_timeout_msecs = 2000; + http_client_set.max_attempts = 3; + test_run_client_server(&http_client_set, + test_client_request_timed_out1, + test_server_request_timed_out, 1, NULL); + test_end(); + + test_begin("request timed out: specific timeout"); + http_client_set.request_timeout_msecs = 3000; + http_client_set.request_absolute_timeout_msecs = 0; + http_client_set.max_attempts = 1; + http_client_set.max_parallel_connections = 1; + test_run_client_server(&http_client_set, + test_client_request_timed_out2, + test_server_request_timed_out, 1, NULL); + test_end(); + + test_begin("request timed out: specific timeout (parallel)"); + http_client_set.request_timeout_msecs = 3000; + http_client_set.request_absolute_timeout_msecs = 0; + http_client_set.max_attempts = 1; + http_client_set.max_parallel_connections = 4; + test_run_client_server(&http_client_set, + test_client_request_timed_out2, + test_server_request_timed_out, 1, NULL); + test_end(); +} + +/* + * Request aborted early + */ + +/* server */ + +static void +test_request_aborted_early_input(struct server_connection *conn ATTR_UNUSED) +{ + static const char *resp = + "HTTP/1.1 404 Not Found\r\n" + "\r\n"; + + /* wait one second to respond */ + i_sleep_intr_secs(1); + + /* respond */ + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_request_aborted_early(unsigned int index) +{ + test_server_input = test_request_aborted_early_input; + test_server_run(index); +} + +/* client */ + +struct _request_aborted_early_ctx { + struct http_client_request *req1, *req2; + struct timeout *to; +}; + +static void +test_client_request_aborted_early_response( + const struct http_response *resp, + struct _request_aborted_early_ctx *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("RESPONSE: %u %s", resp->status, resp->reason); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_request_aborted_early_timeout( + struct _request_aborted_early_ctx *ctx) +{ + timeout_remove(&ctx->to); + + if (ctx->req1 != NULL) { + /* abort early */ + http_client_request_abort(&ctx->req1); /* sent */ + http_client_request_abort(&ctx->req2); /* only queued */ + + /* wait a little for server to actually respond to an + already aborted request */ + ctx->to = timeout_add_short( + 1000, test_client_request_aborted_early_timeout, ctx); + } else { + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_request_aborted_early(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _request_aborted_early_ctx *ctx; + + ctx = i_new(struct _request_aborted_early_ctx, 1); + + http_client = http_client_init(client_set); + + hreq = ctx->req1 = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-aborted-early.txt", + test_client_request_aborted_early_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = ctx->req2 = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-aborted-early2.txt", + test_client_request_aborted_early_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + ctx->to = timeout_add_short( + 500, test_client_request_aborted_early_timeout, ctx); + return TRUE; +} + +/* test */ + +static void test_request_aborted_early(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("request aborted early"); + test_run_client_server(&http_client_set, + test_client_request_aborted_early, + test_server_request_aborted_early, 1, NULL); + test_end(); +} + +/* + * Request failed blocking + */ + +/* server */ + +static void +test_request_failed_blocking_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 500 Internal Server Error\r\n" + "\r\n"; + + /* respond */ + o_stream_nsend_str(conn->conn.output, resp); + i_sleep_intr_secs(10); + server_connection_deinit(&conn); +} + +static void test_server_request_failed_blocking(unsigned int index) +{ + test_server_input = test_request_failed_blocking_input; + test_server_run(index); +} + +/* client */ + +struct _request_failed_blocking_ctx { + struct http_client_request *req; +}; + +static void +test_client_request_failed_blocking_response( + const struct http_response *resp, + struct _request_failed_blocking_ctx *ctx ATTR_UNUSED) +{ + test_client_assert_response(resp, resp->status == 500); +} + +static bool +test_client_request_failed_blocking( + const struct http_client_settings *client_set) +{ + static const char *payload = "This a test payload!"; + struct http_client_request *hreq; + struct _request_failed_blocking_ctx *ctx; + unsigned int n; + string_t *data; + + data = str_new(default_pool, 1000000); + for (n = 0; n < 50000; n++) + str_append(data, payload); + + ctx = i_new(struct _request_failed_blocking_ctx, 1); + + http_client = http_client_init(client_set); + + hreq = ctx->req = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/request-failed-blocking.txt", + test_client_request_failed_blocking_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + + test_assert(http_client_request_send_payload(&hreq, + str_data(data), str_len(data)) < 0); + i_assert(hreq == NULL); + + str_free(&data); + i_free(ctx); + return FALSE; +} + +/* test */ + +static void test_request_failed_blocking(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.socket_send_buffer_size = 4096; + + test_begin("request failed blocking"); + test_run_client_server(&http_client_set, + test_client_request_failed_blocking, + test_server_request_failed_blocking, 1, NULL); + test_end(); +} + +/* + * Client deinit early + */ + +/* server */ + +static void +test_client_deinit_early_input(struct server_connection *conn ATTR_UNUSED) +{ + static const char *resp = + "HTTP/1.1 404 Not Found\r\n" + "\r\n"; + + /* wait one second to respond */ + i_sleep_intr_secs(1); + + /* respond */ + o_stream_nsend_str(conn->conn.output, resp); + server_connection_deinit(&conn); +} + +static void test_server_client_deinit_early(unsigned int index) +{ + test_server_input = test_client_deinit_early_input; + test_server_run(index); +} + +/* client */ + +struct _client_deinit_early_ctx { + struct timeout *to; +}; + +static void +test_client_client_deinit_early_response( + const struct http_response *resp, + struct _client_deinit_early_ctx *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("RESPONSE: %u %s", resp->status, resp->reason); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_client_deinit_early_timeout(struct _client_deinit_early_ctx *ctx) +{ + timeout_remove(&ctx->to); + + /* deinit early */ + http_client_deinit(&http_client); + + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_client_deinit_early(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _client_deinit_early_ctx *ctx; + + ctx = i_new(struct _client_deinit_early_ctx, 1); + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/client-deinit-early.txt", + test_client_client_deinit_early_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/client-deinit-early2.txt", + test_client_client_deinit_early_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + ctx->to = timeout_add_short( + 500, test_client_client_deinit_early_timeout, ctx); + return TRUE; +} + +/* test */ + +static void test_client_deinit_early(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + + test_begin("client deinit early"); + test_run_client_server(&http_client_set, + test_client_client_deinit_early, + test_server_client_deinit_early, 1, NULL); + test_end(); +} + +/* + * Retry with delay + */ + +/* server */ + +static void test_retry_with_delay_input(struct server_connection *conn) +{ + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 500 Internal Server Error\r\n" + "\r\n"); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + server_connection_deinit(&conn); +} + +static void test_server_retry_with_delay(unsigned int index) +{ + test_server_input = test_retry_with_delay_input; + test_server_run(index); +} + +/* client */ + +struct _client_retry_with_delay_ctx { + struct http_client_request *req; + unsigned int retries; + struct timeval time; +}; + +static void +test_client_retry_with_delay_response( + const struct http_response *resp, + struct _client_retry_with_delay_ctx *ctx) +{ + int real_delay, exp_delay; + + test_client_assert_response(resp, resp->status == 500); + + if (ctx->retries > 0) { + /* check delay */ + real_delay = timeval_diff_msecs(&ioloop_timeval, &ctx->time); + exp_delay = (1 << (ctx->retries-1)) * 50; + if (real_delay < exp_delay-2) { + i_fatal("Retry delay is too short %d < %d", + real_delay, exp_delay); + } + } + + http_client_request_delay_msecs(ctx->req, (1 << ctx->retries) * 50); + ctx->time = ioloop_timeval; + if (http_client_request_try_retry(ctx->req)) { + ctx->retries++; + if (debug) + i_debug("retrying"); + return; + } + + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_retry_with_delay(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _client_retry_with_delay_ctx *ctx; + + ctx = i_new(struct _client_retry_with_delay_ctx, 1); + ctx->time = ioloop_timeval; + + http_client = http_client_init(client_set); + + ctx->req = hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/retry-with-delay.txt", + test_client_retry_with_delay_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_retry_with_delay(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_attempts = 3; + + test_begin("retry with delay"); + test_run_client_server(&http_client_set, + test_client_retry_with_delay, + test_server_retry_with_delay, 1, NULL); + test_end(); +} + +/* + * DNS service failure + */ + +/* client */ + +struct _dns_service_failure { + unsigned int count; +}; + +static void +test_client_dns_service_failure_response( + const struct http_response *resp, + struct _dns_service_failure *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_service_failure(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _dns_service_failure *ctx; + + ctx = i_new(struct _dns_service_failure, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-service-failure.txt", + test_client_dns_service_failure_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-service-failure2.txt", + test_client_dns_service_failure_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_dns_service_failure(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.dns_client_socket_path = "./frop"; + + test_begin("dns service failure"); + test_run_client_server(&http_client_set, + test_client_dns_service_failure, + NULL, 0, NULL); + test_end(); +} + +/* + * DNS timeout + */ + +/* dns */ + +static void test_dns_timeout_input(struct server_connection *conn ATTR_UNUSED) +{ + /* hang */ + i_sleep_intr_secs(100); + server_connection_deinit(&conn); +} + +static void test_dns_dns_timeout(void) +{ + test_server_input = test_dns_timeout_input; + test_server_run(0); +} + +/* client */ + +struct _dns_timeout { + unsigned int count; +}; + +static void +test_client_dns_timeout_response( + const struct http_response *resp, + struct _dns_timeout *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_timeout(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _dns_timeout *ctx; + + ctx = i_new(struct _dns_timeout, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-timeout.txt", + test_client_dns_timeout_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-timeout2.txt", + test_client_dns_timeout_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_dns_timeout(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.request_timeout_msecs = 2000; + http_client_set.connect_timeout_msecs = 2000; + http_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns timeout"); + test_run_client_server(&http_client_set, + test_client_dns_timeout, NULL, 0, + test_dns_dns_timeout); + test_end(); +} + +/* + * DNS lookup failure + */ + +/* dns */ + +static void +test_dns_lookup_failure_input(struct server_connection *conn) +{ + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + o_stream_nsend_str(conn->conn.output, + t_strdup_printf("%d\tFAIL\n", EAI_FAIL)); + server_connection_deinit(&conn); +} + +static void test_dns_dns_lookup_failure(void) +{ + test_server_input = test_dns_lookup_failure_input; + test_server_run(0); +} + +/* client */ + +struct _dns_lookup_failure { + unsigned int count; +}; + +static void +test_client_dns_lookup_failure_response(const struct http_response *resp, + struct _dns_lookup_failure *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_lookup_failure(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _dns_lookup_failure *ctx; + + ctx = i_new(struct _dns_lookup_failure, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-lookup-failure.txt", + test_client_dns_lookup_failure_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "example.com", "/dns-lookup-failure2.txt", + test_client_dns_lookup_failure_response, ctx); + http_client_request_set_port(hreq, 80); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_dns_lookup_failure(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns lookup failure"); + test_run_client_server(&http_client_set, + test_client_dns_lookup_failure, NULL, 0, + test_dns_dns_lookup_failure); + test_end(); +} + +/* + * DNS lookup ttl + */ + +/* dns */ + +static void +test_dns_lookup_ttl_input(struct server_connection *conn) +{ + static unsigned int count = 0; + const char *line; + + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (str_begins(line, "VERSION")) + continue; + if (debug) + i_debug("DNS REQUEST %u: %s", count, line); + + if (count == 0) { + o_stream_nsend_str(conn->conn.output, + "0\t127.0.0.1\n"); + } else { + o_stream_nsend_str( + conn->conn.output, + t_strdup_printf("%d\tFAIL\n", EAI_FAIL)); + if (count > 4) { + server_connection_deinit(&conn); + return; + } + } + count++; + } +} + +static void test_dns_dns_lookup_ttl(void) +{ + test_server_input = test_dns_lookup_ttl_input; + test_server_run(0); +} + +/* server */ + +static void +test_server_dns_lookup_ttl_input(struct server_connection *conn) +{ + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "\r\n"); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + server_connection_deinit(&conn); +} + +static void test_server_dns_lookup_ttl(unsigned int index) +{ + test_server_input = test_server_dns_lookup_ttl_input; + test_server_run(index); +} + +/* client */ + +struct _dns_lookup_ttl { + struct http_client *client; + unsigned int count; + struct timeout *to; +}; + +static void +test_client_dns_lookup_ttl_response_stage2(const struct http_response *resp, + struct _dns_lookup_ttl *ctx) +{ + test_client_assert_response( + resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static void test_client_dns_lookup_ttl_stage2_start(struct _dns_lookup_ttl *ctx) +{ + struct http_client_request *hreq; + + timeout_remove(&ctx->to); + + ctx->count = 2; + + hreq = http_client_request( + ctx->client, "GET", "example.com", + "/dns-lookup-ttl-stage2.txt", + test_client_dns_lookup_ttl_response_stage2, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + ctx->client, "GET", "example.com", + "/dns-lookup-ttl2-stage2.txt", + test_client_dns_lookup_ttl_response_stage2, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); +} + +static void +test_client_dns_lookup_ttl_response_stage1(const struct http_response *resp, + struct _dns_lookup_ttl *ctx) +{ + test_client_assert_response(resp, resp->status == 200); + + if (--ctx->count == 0) { + ctx->to = timeout_add(2000, + test_client_dns_lookup_ttl_stage2_start, ctx); + } +} + +static bool +test_client_dns_lookup_ttl(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _dns_lookup_ttl *ctx; + + ctx = i_new(struct _dns_lookup_ttl, 1); + ctx->count = 2; + + ctx->client = http_client = http_client_init(client_set); + + hreq = http_client_request( + ctx->client, "GET", "example.com", + "/dns-lookup-ttl-stage1.txt", + test_client_dns_lookup_ttl_response_stage1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + ctx->client, "GET", "example.com", + "/dns-lookup-ttl2-stage1.txt", + test_client_dns_lookup_ttl_response_stage1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_dns_lookup_ttl(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.dns_client_socket_path = "./dns-test"; + http_client_set.dns_ttl_msecs = 1000; + + test_begin("dns lookup ttl"); + test_run_client_server(&http_client_set, + test_client_dns_lookup_ttl, + test_server_dns_lookup_ttl, 1, + test_dns_dns_lookup_ttl); + test_end(); +} + +/* + * Peer reuse failure + */ + +/* server */ + +static void test_peer_reuse_failure_input(struct server_connection *conn) +{ + static unsigned int seq = 0; + static const char *resp = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + if (seq++ > 2) { + server_connection_deinit(&conn); + io_loop_stop(current_ioloop); + } +} + +static void test_server_peer_reuse_failure(unsigned int index) +{ + test_server_input = test_peer_reuse_failure_input; + test_server_run(index); +} + +/* client */ + +struct _peer_reuse_failure { + struct timeout *to; + bool first:1; +}; + +static void +test_client_peer_reuse_failure_response2(const struct http_response *resp, + struct _peer_reuse_failure *ctx) +{ + test_client_assert_response( + resp, http_response_is_internal_error(resp)); + + i_free(ctx); + io_loop_stop(ioloop); +} + +static void +test_client_peer_reuse_failure_next(struct _peer_reuse_failure *ctx) +{ + struct http_client_request *hreq; + + timeout_remove(&ctx->to); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), + "/peer-reuse-next.txt", + test_client_peer_reuse_failure_response2, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); +} + +static void +test_client_peer_reuse_failure_response1(const struct http_response *resp, + struct _peer_reuse_failure *ctx) +{ + if (ctx->first) { + test_client_assert_response(resp, resp->status == 200); + + ctx->first = FALSE; + ctx->to = timeout_add_short( + 500, test_client_peer_reuse_failure_next, ctx); + } else { + test_client_assert_response( + resp, http_response_is_internal_error(resp)); + } + + test_assert(resp->reason != NULL && *resp->reason != '\0'); +} + +static bool +test_client_peer_reuse_failure(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _peer_reuse_failure *ctx; + + ctx = i_new(struct _peer_reuse_failure, 1); + ctx->first = TRUE; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), "/peer-reuse.txt", + test_client_peer_reuse_failure_response1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), "/peer-reuse.txt", + test_client_peer_reuse_failure_response1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", net_ip2addr(&bind_ip), "/peer-reuse.txt", + test_client_peer_reuse_failure_response1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_peer_reuse_failure(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_connect_attempts = 1; + http_client_set.max_idle_time_msecs = 500; + + test_begin("peer reuse failure"); + test_run_client_server(&http_client_set, + test_client_peer_reuse_failure, + test_server_peer_reuse_failure, 1, NULL); + test_end(); +} + +/* + * Reconnect failure + */ + +/* dns */ + +static void test_dns_reconnect_failure_input(struct server_connection *conn) +{ + static unsigned int count = 0; + const char *line; + + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (str_begins(line, "VERSION")) + continue; + if (debug) + i_debug("DNS REQUEST %u: %s", count, line); + + if (count == 0) { + o_stream_nsend_str(conn->conn.output, + "0\t127.0.0.1\n"); + } else { + o_stream_nsend_str( + conn->conn.output, + t_strdup_printf("%d\tFAIL\n", EAI_FAIL)); + if (count > 4) { + server_connection_deinit(&conn); + return; + } + } + count++; + } +} + +static void test_dns_reconnect_failure(void) +{ + test_server_input = test_dns_reconnect_failure_input; + test_server_run(0); +} + +/* server */ + +static void test_reconnect_failure_input(struct server_connection *conn) +{ + static const char *resp = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 18\r\n" + "\r\n" + "Everything is OK\r\n"; + + o_stream_nsend_str(conn->conn.output, resp); + io_loop_stop(current_ioloop); + io_remove(&io_listen); + i_close_fd(&fd_listen); + server_connection_deinit(&conn); +} + +static void test_server_reconnect_failure(unsigned int index) +{ + test_server_input = test_reconnect_failure_input; + test_server_run(index); +} + +/* client */ + +struct _reconnect_failure_ctx { + struct timeout *to; +}; + +static void +test_client_reconnect_failure_response2(const struct http_response *resp, + struct _reconnect_failure_ctx *ctx) +{ + test_client_assert_response( + resp, resp->status == HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED); + + io_loop_stop(ioloop); + i_free(ctx); +} + +static void +test_client_reconnect_failure_next(struct _reconnect_failure_ctx *ctx) +{ + struct http_client_request *hreq; + + if (debug) + i_debug("NEXT REQUEST"); + + timeout_remove(&ctx->to); + + hreq = http_client_request( + http_client, "GET", "example.com", "/reconnect-failure-2.txt", + test_client_reconnect_failure_response2, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); +} + +static void +test_client_reconnect_failure_response1(const struct http_response *resp, + struct _reconnect_failure_ctx *ctx) +{ + test_client_assert_response(resp, resp->status == 200); + + ctx->to = timeout_add_short( + 5000, test_client_reconnect_failure_next, ctx); +} + +static bool +test_client_reconnect_failure(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _reconnect_failure_ctx *ctx; + + ctx = i_new(struct _reconnect_failure_ctx, 1); + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "example.com", "/reconnect-failure-1.txt", + test_client_reconnect_failure_response1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_reconnect_failure(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.dns_client_socket_path = "./dns-test"; + http_client_set.dns_ttl_msecs = 10000; + http_client_set.max_idle_time_msecs = 1000; + http_client_set.max_attempts = 1; + http_client_set.request_timeout_msecs = 1000; + + test_begin("reconnect failure"); + test_run_client_server(&http_client_set, + test_client_reconnect_failure, + test_server_reconnect_failure, 1, + test_dns_reconnect_failure); + test_end(); +} + +/* + * Multi IP attempts + */ + +/* dns */ + +static void test_multi_ip_attempts_input(struct server_connection *conn) +{ + unsigned int count = 0; + const char *line; + + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (str_begins(line, "VERSION")) + continue; + if (debug) + i_debug("DNS REQUEST %u: %s", count, line); + + if (strcmp(line, "IP\ttest1.local") == 0) { + o_stream_nsend_str(conn->conn.output, + "0\t127.0.0.4\t127.0.0.3\t" + "127.0.0.2\t127.0.0.1\n"); + continue; + } + + o_stream_nsend_str(conn->conn.output, + "0\t10.255.255.1\t192.168.0.0\t" + "192.168.255.255\t127.0.0.1\n"); + } +} + +static void test_dns_multi_ip_attempts(void) +{ + test_server_input = test_multi_ip_attempts_input; + test_server_run(0); +} + +/* server */ + +static void test_server_multi_ip_attempts_input(struct server_connection *conn) +{ + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "\r\n"); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + server_connection_deinit(&conn); +} + +static void test_server_multi_ip_attempts(unsigned int index) +{ + test_server_input = test_server_multi_ip_attempts_input; + test_server_run(index); +} + +/* client */ + +struct _multi_ip_attempts { + unsigned int count; +}; + +static void +test_client_multi_ip_attempts_response(const struct http_response *resp, + struct _multi_ip_attempts *ctx) +{ + test_client_assert_response(resp, resp->status == 200); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_multi_ip_attempts1(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _multi_ip_attempts *ctx; + + ctx = i_new(struct _multi_ip_attempts, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "test1.local", "/multi-ip-attempts.txt", + test_client_multi_ip_attempts_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "test1.local", "/multi-ip-attempts2.txt", + test_client_multi_ip_attempts_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +static bool +test_client_multi_ip_attempts2(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _multi_ip_attempts *ctx; + + ctx = i_new(struct _multi_ip_attempts, 1); + ctx->count = 2; + + http_client = http_client_init(client_set); + + hreq = http_client_request( + http_client, "GET", "test2.local", "/multi-ip-attempts.txt", + test_client_multi_ip_attempts_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + http_client, "GET", "test2.local", "/multi-ip-attempts2.txt", + test_client_multi_ip_attempts_response, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_multi_ip_attempts(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.connect_timeout_msecs = 1000; + http_client_set.request_timeout_msecs = 1000; + http_client_set.dns_client_socket_path = "./dns-test"; + http_client_set.max_connect_attempts = 4; + + test_begin("multi IP attempts (connection refused)"); + test_run_client_server(&http_client_set, + test_client_multi_ip_attempts1, + test_server_multi_ip_attempts, 1, + test_dns_multi_ip_attempts); + test_end(); + + test_begin("multi IP attempts (connect timeout)"); + test_run_client_server(&http_client_set, + test_client_multi_ip_attempts2, + test_server_multi_ip_attempts, 1, + test_dns_multi_ip_attempts); + test_end(); + + http_client_set.soft_connect_timeout_msecs = 100; + + test_begin("multi IP attempts (soft connect timeout)"); + test_run_client_server(&http_client_set, + test_client_multi_ip_attempts2, + test_server_multi_ip_attempts, 1, + test_dns_multi_ip_attempts); + test_end(); +} + +/* + * Idle connections + */ + +/* server */ + +struct _idle_connections_sctx { + bool eoh; +}; + +static int test_idle_connections_init(struct server_connection *conn) +{ + struct _idle_connections_sctx *ctx; + + ctx = p_new(conn->pool, struct _idle_connections_sctx, 1); + conn->context = ctx; + return 0; +} + +static void test_idle_connections_input(struct server_connection *conn) +{ + struct _idle_connections_sctx *ctx = conn->context; + const char *line; + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (*line == '\0') { + ctx->eoh = TRUE; + break; + } + } + + if (conn->conn.input->stream_errno != 0) { + i_fatal("server: Stream error: %s", + i_stream_get_error(conn->conn.input)); + } + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + i_assert(ctx->eoh); + ctx->eoh = FALSE; + + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n"); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + if (o_stream_flush(conn->conn.output) < 0) { + i_fatal("server: Flush error: %s", + o_stream_get_error(conn->conn.output)); + } +} + +static void test_server_idle_connections(unsigned int index) +{ + test_server_init = test_idle_connections_init; + test_server_input = test_idle_connections_input; + test_server_run(index); +} + +/* client */ + +struct _idle_connections { + struct http_client *client; + unsigned int max, count; + struct timeout *to; +}; + +static void +test_client_idle_connections_response_stage2(const struct http_response *resp, + struct _idle_connections *ctx) +{ + test_client_assert_response( + resp, resp->status == 200); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static void test_client_idle_connections_stage2_start(struct _idle_connections *ctx) +{ + struct http_client_request *hreq; + unsigned int i; + + if (debug) + i_debug("STAGE 2"); + + timeout_remove(&ctx->to); + + ctx->count = ctx->max; + + for (i = 0; i < ctx->count; i++) { + hreq = http_client_request( + ctx->client, "GET", net_ip2addr(&bind_ip), + t_strdup_printf("/idle-connections-stage2-%d.txt", i), + test_client_idle_connections_response_stage2, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + } +} + +static void +test_client_idle_connections_response_stage1(const struct http_response *resp, + struct _idle_connections *ctx) +{ + test_client_assert_response(resp, resp->status == 200); + + if (--ctx->count == 0) { + if (debug) + i_debug("START STAGE 2"); + ctx->to = timeout_add_short( + 550, test_client_idle_connections_stage2_start, ctx); + } +} + +static bool +test_client_idle_connections(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _idle_connections *ctx; + unsigned int i; + + if (debug) + i_debug("STAGE 1"); + + ctx = i_new(struct _idle_connections, 1); + ctx->max = client_set->max_parallel_connections; + ctx->count = client_set->max_parallel_connections; + + ctx->client = http_client = http_client_init(client_set); + + for (i = 0; i < ctx->count; i++) { + hreq = http_client_request( + ctx->client, "GET", net_ip2addr(&bind_ip), + t_strdup_printf("/idle-connections-stage1-%d.txt", i), + test_client_idle_connections_response_stage1, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + } + + return TRUE; +} + +/* test */ + +static void test_idle_connections(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.max_idle_time_msecs = 1000; + + test_begin("idle connections (max 1)"); + http_client_set.max_parallel_connections = 1; + test_run_client_server(&http_client_set, + test_client_idle_connections, + test_server_idle_connections, 1, NULL); + test_end(); + + test_begin("idle connections (max 2)"); + http_client_set.max_parallel_connections = 2; + test_run_client_server(&http_client_set, + test_client_idle_connections, + test_server_idle_connections, 1, NULL); + test_end(); + + test_begin("idle connections (max 4)"); + http_client_set.max_parallel_connections = 4; + test_run_client_server(&http_client_set, + test_client_idle_connections, + test_server_idle_connections, 1, NULL); + test_end(); + + test_begin("idle connections (max 8)"); + http_client_set.max_parallel_connections = 8; + test_run_client_server(&http_client_set, + test_client_idle_connections, + test_server_idle_connections, 1, NULL); + test_end(); +} + +/* + * Idle hosts + */ + +/* dns */ + +static void +test_dns_idle_hosts_input(struct server_connection *conn) +{ + const char *line; + + if (!conn->version_sent) { + conn->version_sent = TRUE; + o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); + } + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (str_begins(line, "VERSION")) + continue; + if (debug) + i_debug("DNS REQUEST: %s", line); + + if (strcmp(line, "IP\thosta") == 0) { + o_stream_nsend_str(conn->conn.output, + "0\t127.0.0.1\n"); + } else { + i_sleep_msecs(300); + o_stream_nsend_str( + conn->conn.output, + t_strdup_printf("%d\tFAIL\n", EAI_FAIL)); + } + } +} + +static void test_dns_idle_hosts(void) +{ + test_server_input = test_dns_idle_hosts_input; + test_server_run(0); +} + +/* server */ + +struct _idle_hosts_sctx { + bool eoh; +}; + +static int test_idle_hosts_init(struct server_connection *conn) +{ + struct _idle_hosts_sctx *ctx; + + ctx = p_new(conn->pool, struct _idle_hosts_sctx, 1); + conn->context = ctx; + return 0; +} + +static void test_idle_hosts_input(struct server_connection *conn) +{ + struct _idle_hosts_sctx *ctx = conn->context; + const char *line; + + while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { + if (*line == '\0') { + ctx->eoh = TRUE; + break; + } + } + + if (conn->conn.input->stream_errno != 0) { + i_fatal("server: Stream error: %s", + i_stream_get_error(conn->conn.input)); + } + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + i_assert(ctx->eoh); + ctx->eoh = FALSE; + + string_t *resp; + + resp = t_str_new(512); + str_printfa(resp, + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n"); + o_stream_nsend(conn->conn.output, str_data(resp), str_len(resp)); + if (o_stream_flush(conn->conn.output) < 0) { + i_fatal("server: Flush error: %s", + o_stream_get_error(conn->conn.output)); + } +} + +static void test_server_idle_hosts(unsigned int index) +{ + test_server_init = test_idle_hosts_init; + test_server_input = test_idle_hosts_input; + test_server_run(index); +} + +/* client */ + +struct _idle_hosts { + struct http_client *client; + struct http_client_request *hostb_req; + unsigned int count; +}; + +static void +test_client_idle_hosts_response_hosta(const struct http_response *resp, + struct _idle_hosts *ctx) +{ + test_client_assert_response(resp, resp->status == 200); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static void +test_client_idle_hosts_response_hostb(const struct http_response *resp, + struct _idle_hosts *ctx) +{ + test_client_assert_response(resp, + resp->status == HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED); + + if (http_client_request_try_retry(ctx->hostb_req)) { + if (debug) + i_debug("retrying"); + return; + } + + ctx->hostb_req = NULL; +} + +static bool +test_client_idle_hosts(const struct http_client_settings *client_set) +{ + struct http_client_request *hreq; + struct _idle_hosts *ctx; + + ctx = i_new(struct _idle_hosts, 1); + ctx->count = 2; + + ctx->client = http_client = http_client_init(client_set); + + hreq = http_client_request( + ctx->client, "GET", "hosta", + t_strdup_printf("/idle-hosts-a1.txt"), + test_client_idle_hosts_response_hosta, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = ctx->hostb_req = http_client_request( + ctx->client, "GET", "hostb", + t_strdup_printf("/idle-hosts-b.txt"), + test_client_idle_hosts_response_hostb, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_submit(hreq); + + hreq = http_client_request( + ctx->client, "GET", "hosta", + t_strdup_printf("/idle-hosts-a2.txt"), + test_client_idle_hosts_response_hosta, ctx); + http_client_request_set_port(hreq, bind_ports[0]); + http_client_request_delay_msecs(hreq, 600); + http_client_request_submit(hreq); + + return TRUE; +} + +/* test */ + +static void test_idle_hosts(void) +{ + struct http_client_settings http_client_set; + + test_client_defaults(&http_client_set); + http_client_set.dns_client_socket_path = "./dns-test"; + http_client_set.dns_ttl_msecs = 400; + http_client_set.max_parallel_connections = 1; + http_client_set.max_idle_time_msecs = 100; + http_client_set.max_attempts = 2; + + test_begin("idle hosts"); + test_run_client_server(&http_client_set, + test_client_idle_hosts, + test_server_idle_hosts, 1, + test_dns_idle_hosts); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_unconfigured_ssl, + test_unconfigured_ssl_abort, + test_invalid_url, + test_host_lookup_failed, + test_connection_refused, + test_connection_lost_prematurely, + test_connection_timed_out, + test_invalid_redirect, + test_unseekable_redirect, + test_unseekable_retry, + test_broken_payload, + test_retry_payload, + test_connection_lost, + test_connection_lost_100, + test_connection_lost_sub_ioloop, + test_early_success, + test_bad_response, + test_request_timed_out, + test_request_aborted_early, + test_request_failed_blocking, + test_client_deinit_early, + test_retry_with_delay, + test_dns_service_failure, + test_dns_timeout, + test_dns_lookup_failure, + test_dns_lookup_ttl, + test_peer_reuse_failure, + test_reconnect_failure, + test_multi_ip_attempts, + test_idle_connections, + test_idle_hosts, + NULL +}; + +/* + * Test client + */ + +static void test_client_defaults(struct http_client_settings *http_set) +{ + /* client settings */ + i_zero(http_set); + http_set->max_idle_time_msecs = 5*1000; + http_set->max_parallel_connections = 1; + http_set->max_pipelined_requests = 1; + http_set->max_redirects = 0; + http_set->max_attempts = 1; + http_set->debug = debug; +} + +static void test_client_progress_timeout(void *context ATTR_UNUSED) +{ + /* Terminate test due to lack of progress */ + test_assert(FALSE); + timeout_remove(&to_client_progress); + io_loop_stop(current_ioloop); +} + +static bool +test_client_init(test_client_init_t client_test, + const struct http_client_settings *client_set) +{ + i_assert(client_test != NULL); + if (!client_test(client_set)) + return FALSE; + + to_client_progress = timeout_add(CLIENT_PROGRESS_TIMEOUT*1000, + test_client_progress_timeout, NULL); + return TRUE; +} + +static void test_client_deinit(void) +{ + timeout_remove(&to_client_progress); + + if (http_client != NULL) + http_client_deinit(&http_client); +} + +static void +test_client_run(test_client_init_t client_test, + const struct http_client_settings *client_set) +{ + if (test_client_init(client_test, client_set)) + io_loop_run(ioloop); + test_client_deinit(); +} + +/* + * Test server + */ + +/* client connection */ + +static void server_connection_input(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + + test_server_input(conn); +} + +static void server_connection_init(int fd) +{ + struct server_connection *conn; + pool_t pool; + + net_set_nonblock(fd, TRUE); + + pool = pool_alloconly_create("server connection", 512); + conn = p_new(pool, struct server_connection, 1); + conn->pool = pool; + + connection_init_server(server_conn_list, &conn->conn, + "server connection", fd, fd); + + if (test_server_init != NULL) { + if (test_server_init(conn) != 0) + return; + } +} + +static void server_connection_deinit(struct server_connection **_conn) +{ + struct server_connection *conn = *_conn; + + *_conn = NULL; + + if (test_server_deinit != NULL) + test_server_deinit(conn); + + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void server_connection_destroy(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + + server_connection_deinit(&conn); +} + +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"); + } + + server_connection_init(fd); +} + +/* */ + +static struct connection_settings server_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE +}; + +static const struct connection_vfuncs server_connection_vfuncs = { + .destroy = server_connection_destroy, + .input = server_connection_input +}; + +static void test_server_run(unsigned int index) +{ + server_index = index; + + /* open server socket */ + io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL); + + server_conn_list = connection_list_init(&server_connection_set, + &server_connection_vfuncs); + + io_loop_run(ioloop); + + /* close server socket */ + io_remove(&io_listen); + + connection_list_deinit(&server_conn_list); +} + +/* + * Tests + */ + +struct test_server_data { + unsigned int index; + test_server_init_t server_test; +}; + +static int test_open_server_fd(in_port_t *bind_port) +{ + 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_server(struct test_server_data *data) +{ + i_set_failure_prefix("SERVER[%u]: ", data->index + 1); + + if (debug) + i_debug("PID=%s", my_pid); + + test_subprocess_notify_signal_send_parent(SIGHUP); + ioloop = io_loop_create(); + data->server_test(data->index); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + main_deinit(); + return 0; +} + +static int test_run_dns(test_dns_init_t dns_test) +{ + i_set_failure_prefix("DNS: "); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + dns_test(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + main_deinit(); + return 0; +} + +static void +test_run_client(const struct http_client_settings *client_set, + test_client_init_t client_test) +{ + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + test_client_run(client_test, client_set); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct http_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) +{ + unsigned int i; + + test_subprocess_notify_signal_reset(SIGHUP); + test_server_init = NULL; + test_server_deinit = NULL; + test_server_input = NULL; + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + for (i = 0; i < server_tests_count; i++) + fds[i] = test_open_server_fd(&bind_ports[i]); + + for (i = 0; i < server_tests_count; i++) { + struct test_server_data data; + + i_zero(&data); + data.index = i; + data.server_test = server_test; + + /* Fork server */ + fd_listen = fds[i]; + test_subprocess_fork(test_run_server, &data, FALSE); + i_close_fd(&fd_listen); + test_subprocess_notify_signal_wait(SIGHUP, 10000); + test_subprocess_notify_signal_reset(SIGHUP); + } + } + + if (dns_test != NULL) { + int fd; + + i_unlink_if_exists("./dns-test"); + fd = net_listen_unix("./dns-test", 128); + if (fd == -1) { + i_fatal("listen(./dns-test) failed: %m"); + } + + /* Fork DNS service */ + fd_listen = fd; + test_subprocess_fork(test_run_dns, dns_test, FALSE); + i_close_fd(&fd_listen); + } + + /* Run client */ + test_run_client(client_set, client_test); + + i_unset_failure_prefix(); + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + i_free(bind_ports); + + i_unlink_if_exists("./dns-test"); +} + +/* + * 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; +} |