summaryrefslogtreecommitdiffstats
path: root/src/lib-http/http-server-connection.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-http/http-server-connection.c
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-http/http-server-connection.c')
-rw-r--r--src/lib-http/http-server-connection.c1183
1 files changed, 1183 insertions, 0 deletions
diff --git a/src/lib-http/http-server-connection.c b/src/lib-http/http-server-connection.c
new file mode 100644
index 0000000..ad704ea
--- /dev/null
+++ b/src/lib-http/http-server-connection.c
@@ -0,0 +1,1183 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "array.h"
+#include "str.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-timeout.h"
+#include "ostream.h"
+#include "connection.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "master-service.h"
+#include "master-service-ssl.h"
+#include "http-date.h"
+#include "http-url.h"
+#include "http-request-parser.h"
+
+#include "http-server-private.h"
+
+static void
+http_server_connection_disconnect(struct http_server_connection *conn,
+ const char *reason);
+
+static bool
+http_server_connection_unref_is_closed(struct http_server_connection *conn);
+
+/*
+ * Logging
+ */
+
+static inline void
+http_server_connection_client_error(struct http_server_connection *conn,
+ const char *format, ...) ATTR_FORMAT(2, 3);
+
+static inline void
+http_server_connection_client_error(struct http_server_connection *conn,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ e_info(conn->event, "%s", t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
+/*
+ * Connection
+ */
+
+static void http_server_connection_input(struct connection *_conn);
+
+static void
+http_server_connection_update_stats(struct http_server_connection *conn)
+{
+ if (conn->conn.input != NULL)
+ conn->stats.input = conn->conn.input->v_offset;
+ if (conn->conn.output != NULL)
+ conn->stats.output = conn->conn.output->offset;
+}
+
+const struct http_server_stats *
+http_server_connection_get_stats(struct http_server_connection *conn)
+{
+ http_server_connection_update_stats(conn);
+ return &conn->stats;
+}
+
+void http_server_connection_input_set_pending(
+ struct http_server_connection *conn)
+{
+ i_stream_set_input_pending(conn->conn.input, TRUE);
+}
+
+void http_server_connection_input_halt(struct http_server_connection *conn)
+{
+ connection_input_halt(&conn->conn);
+}
+
+void http_server_connection_input_resume(struct http_server_connection *conn)
+{
+ if (conn->closed || conn->input_broken || conn->close_indicated ||
+ conn->incoming_payload != NULL) {
+ /* Connection not usable */
+ return;
+ }
+
+ if (conn->in_req_callback) {
+ struct http_server_request *req = conn->request_queue_tail;
+
+ /* Currently running request callback for this connection. Only
+ handle discarded request payload. */
+ if (req == NULL ||
+ req->state != HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE)
+ return;
+ if (!http_server_connection_pending_payload(conn))
+ return;
+ }
+
+ connection_input_resume(&conn->conn);
+}
+
+static void
+http_server_connection_idle_timeout(struct http_server_connection *conn)
+{
+ http_server_connection_client_error(
+ conn, "Disconnected for inactivity");
+ http_server_connection_close(&conn, "Disconnected for inactivity");
+}
+
+void http_server_connection_start_idle_timeout(
+ struct http_server_connection *conn)
+{
+ unsigned int timeout_msecs =
+ conn->server->set.max_client_idle_time_msecs;
+
+ if (conn->to_idle == NULL && timeout_msecs > 0) {
+ conn->to_idle = timeout_add(timeout_msecs,
+ http_server_connection_idle_timeout,
+ conn);
+ }
+}
+
+void http_server_connection_reset_idle_timeout(
+ struct http_server_connection *conn)
+{
+ if (conn->to_idle != NULL)
+ timeout_reset(conn->to_idle);
+}
+
+void http_server_connection_stop_idle_timeout(
+ struct http_server_connection *conn)
+{
+ timeout_remove(&conn->to_idle);
+}
+
+bool http_server_connection_shut_down(struct http_server_connection *conn)
+{
+ if (conn->request_queue_head == NULL ||
+ conn->request_queue_head->state == HTTP_SERVER_REQUEST_STATE_NEW) {
+ http_server_connection_close(&conn, "Server shutting down");
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void http_server_connection_ready(struct http_server_connection *conn)
+{
+ const struct http_server_settings *set = &conn->server->set;
+ struct http_url base_url;
+ struct stat st;
+
+ if (conn->server->set.rawlog_dir != NULL &&
+ stat(conn->server->set.rawlog_dir, &st) == 0) {
+ iostream_rawlog_create(conn->server->set.rawlog_dir,
+ &conn->conn.input, &conn->conn.output);
+ }
+
+ i_zero(&base_url);
+ if (set->default_host != NULL)
+ base_url.host.name = set->default_host;
+ else
+ base_url.host.name = my_hostname;
+ base_url.have_ssl = conn->ssl;
+
+ conn->http_parser = http_request_parser_init(
+ conn->conn.input, &base_url, &conn->server->set.request_limits,
+ HTTP_REQUEST_PARSE_FLAG_STRICT);
+ o_stream_set_finish_via_child(conn->conn.output, FALSE);
+ o_stream_set_flush_callback(conn->conn.output,
+ http_server_connection_output, conn);
+}
+
+static void http_server_connection_destroy(struct connection *_conn)
+{
+ struct http_server_connection *conn =
+ (struct http_server_connection *)_conn;
+
+ http_server_connection_disconnect(conn, NULL);
+ http_server_connection_unref(&conn);
+}
+
+static void http_server_payload_destroyed(struct http_server_request *req)
+{
+ struct http_server_connection *conn = req->conn;
+ int stream_errno;
+
+ i_assert(conn != NULL);
+ i_assert(conn->request_queue_tail == req ||
+ req->state >= HTTP_SERVER_REQUEST_STATE_FINISHED);
+ i_assert(conn->conn.io == NULL);
+
+ e_debug(conn->event, "Request payload stream destroyed");
+
+ /* Caller is allowed to change the socket fd to blocking while reading
+ the payload. make sure here that it's switched back. */
+ net_set_nonblock(conn->conn.fd_in, TRUE);
+
+ stream_errno = conn->incoming_payload->stream_errno;
+ conn->incoming_payload = NULL;
+
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+
+ /* Handle errors in transfer stream */
+ if (req->response == NULL && stream_errno != 0 &&
+ conn->conn.input->stream_errno == 0) {
+ switch (stream_errno) {
+ case EMSGSIZE:
+ conn->input_broken = TRUE;
+ http_server_connection_client_error(
+ conn, "Client sent excessively large request");
+ http_server_request_fail_close(req, 413,
+ "Payload Too Large");
+ return;
+ case EIO:
+ conn->input_broken = TRUE;
+ http_server_connection_client_error(
+ conn, "Client sent invalid request payload");
+ http_server_request_fail_close(req, 400,
+ "Bad Request");
+ return;
+ default:
+ break;
+ }
+ }
+
+ /* Resource stopped reading payload; update state */
+ switch (req->state) {
+ case HTTP_SERVER_REQUEST_STATE_QUEUED:
+ case HTTP_SERVER_REQUEST_STATE_PAYLOAD_IN:
+ /* Finished reading request */
+ req->state = HTTP_SERVER_REQUEST_STATE_PROCESSING;
+ http_server_connection_stop_idle_timeout(conn);
+ if (req->response != NULL && req->response->submitted)
+ http_server_request_submit_response(req);
+ break;
+ case HTTP_SERVER_REQUEST_STATE_PROCESSING:
+ /* No response submitted yet */
+ break;
+ case HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE:
+ /* Response submitted, but not all payload is necessarily read
+ */
+ if (http_server_request_is_complete(req))
+ http_server_request_ready_to_respond(req);
+ break;
+ case HTTP_SERVER_REQUEST_STATE_READY_TO_RESPOND:
+ case HTTP_SERVER_REQUEST_STATE_FINISHED:
+ case HTTP_SERVER_REQUEST_STATE_ABORTED:
+ /* Nothing to do */
+ break;
+ default:
+ i_unreached();
+ }
+
+ /* Input stream may have pending input. */
+ http_server_connection_input_resume(conn);
+ http_server_connection_input_set_pending(conn);
+}
+
+static bool
+http_server_connection_handle_request(struct http_server_connection *conn,
+ struct http_server_request *req)
+{
+ const struct http_server_settings *set = &conn->server->set;
+ unsigned int old_refcount;
+ struct istream *payload;
+
+ i_assert(!conn->in_req_callback);
+ i_assert(conn->incoming_payload == NULL);
+
+ if (req->req.version_major != 1) {
+ http_server_request_fail(req, 505,
+ "HTTP Version Not Supported");
+ return TRUE;
+ }
+
+ req->state = HTTP_SERVER_REQUEST_STATE_QUEUED;
+
+ if (req->req.payload != NULL) {
+ /* Wrap the stream to capture the destroy event without
+ destroying the actual payload stream. */
+ conn->incoming_payload = req->req.payload =
+ i_stream_create_timeout(
+ req->req.payload,
+ set->max_client_idle_time_msecs);
+ /* We've received the request itself, and we can't reset the
+ timeout during the payload reading. */
+ http_server_connection_stop_idle_timeout(conn);
+ } else {
+ conn->incoming_payload = req->req.payload =
+ i_stream_create_from_data("", 0);
+ }
+ i_stream_add_destroy_callback(req->req.payload,
+ http_server_payload_destroyed, req);
+ /* The callback may add its own I/O, so we need to remove our one before
+ calling it. */
+ http_server_connection_input_halt(conn);
+
+ old_refcount = req->refcount;
+ conn->in_req_callback = TRUE;
+ T_BEGIN {
+ http_server_request_callback(req);
+ } T_END;
+ if (conn->closed) {
+ /* The callback managed to get this connection destroyed/closed
+ */
+ return FALSE;
+ }
+ conn->in_req_callback = FALSE;
+ req->callback_refcount = req->refcount - old_refcount;
+
+ if (req->req.payload != NULL) {
+ /* Send 100 Continue when appropriate */
+ if (req->req.expect_100_continue && !req->payload_halted &&
+ req->response == NULL) {
+ http_server_connection_output_trigger(conn);
+ }
+
+ /* Delegate payload handling to request handler */
+ if (req->state < HTTP_SERVER_REQUEST_STATE_PAYLOAD_IN)
+ req->state = HTTP_SERVER_REQUEST_STATE_PAYLOAD_IN;
+ payload = req->req.payload;
+ req->req.payload = NULL;
+ i_stream_unref(&payload);
+ }
+
+ if (req->state < HTTP_SERVER_REQUEST_STATE_PROCESSING &&
+ (conn->incoming_payload == NULL ||
+ !i_stream_have_bytes_left(conn->incoming_payload))) {
+ /* Finished reading request */
+ req->state = HTTP_SERVER_REQUEST_STATE_PROCESSING;
+ if (req->response != NULL && req->response->submitted)
+ http_server_request_submit_response(req);
+ }
+
+ i_assert(conn->incoming_payload != NULL || req->callback_refcount > 0 ||
+ (req->response != NULL && req->response->submitted));
+
+ if (conn->incoming_payload == NULL) {
+ http_server_connection_input_resume(conn);
+ http_server_connection_input_set_pending(conn);
+ return TRUE;
+ }
+
+ /* Request payload is still being uploaded by the client */
+ return FALSE;
+}
+
+static int
+http_server_connection_ssl_init(struct http_server_connection *conn)
+{
+ struct http_server *server = conn->server;
+ const char *error;
+ int ret;
+
+ if (http_server_init_ssl_ctx(server, &error) < 0) {
+ e_error(conn->event, "Couldn't initialize SSL: %s", error);
+ return -1;
+ }
+
+ e_debug(conn->event, "Starting SSL handshake");
+
+ http_server_connection_input_halt(conn);
+ if (server->ssl_ctx == NULL) {
+ ret = master_service_ssl_init(master_service,
+ &conn->conn.input,
+ &conn->conn.output,
+ &conn->ssl_iostream, &error);
+ } else {
+ ret = io_stream_create_ssl_server(server->ssl_ctx,
+ server->set.ssl,
+ &conn->conn.input,
+ &conn->conn.output,
+ &conn->ssl_iostream, &error);
+ }
+ if (ret < 0) {
+ e_error(conn->event,
+ "Couldn't initialize SSL server for %s: %s",
+ conn->conn.name, error);
+ return -1;
+ }
+ http_server_connection_input_resume(conn);
+
+ if (ssl_iostream_handshake(conn->ssl_iostream) < 0) {
+ e_error(conn->event, "SSL handshake failed: %s",
+ ssl_iostream_get_last_error(conn->ssl_iostream));
+ return -1;
+ }
+
+ http_server_connection_ready(conn);
+ return 0;
+}
+
+static bool
+http_server_connection_pipeline_is_full(struct http_server_connection *conn)
+{
+ return ((conn->request_queue_count >=
+ conn->server->set.max_pipelined_requests) ||
+ conn->server->shutting_down);
+}
+
+static void
+http_server_connection_pipeline_handle_full(struct http_server_connection *conn)
+{
+ if (conn->server->shutting_down) {
+ e_debug(conn->event, "Pipeline full "
+ "(%u requests pending; server shutting down)",
+ conn->request_queue_count);
+ } else {
+ e_debug(conn->event, "Pipeline full "
+ "(%u requests pending; %u maximum)",
+ conn->request_queue_count,
+ conn->server->set.max_pipelined_requests);
+ }
+ http_server_connection_input_halt(conn);
+}
+
+static bool
+http_server_connection_check_input(struct http_server_connection *conn)
+{
+ struct istream *input = conn->conn.input;
+ int stream_errno;
+
+ if (input == NULL)
+ return FALSE;
+ stream_errno = input->stream_errno;
+
+ if (input->eof || stream_errno != 0) {
+ /* Connection input broken; output may still be intact */
+ if (stream_errno != 0 && stream_errno != EPIPE &&
+ stream_errno != ECONNRESET) {
+ http_server_connection_client_error(
+ conn, "Connection lost: read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ http_server_connection_close(&conn, "Read failure");
+ } else {
+ e_debug(conn->event, "Connection lost: "
+ "Remote disconnected");
+
+ if (conn->request_queue_head == NULL) {
+ /* No pending requests; close */
+ http_server_connection_close(
+ &conn, "Remote closed connection");
+ } else if (conn->request_queue_head->state <
+ HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE) {
+ /* Unfinished request; close */
+ http_server_connection_close(&conn,
+ "Remote closed connection unexpectedly");
+ } else {
+ /* A request is still processing; only drop
+ input io for now. The other end may only have
+ shutdown one direction */
+ conn->input_broken = TRUE;
+ http_server_connection_input_halt(conn);
+ }
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+http_server_connection_finish_request(struct http_server_connection *conn)
+{
+ struct http_server_request *req;
+ enum http_request_parse_error error_code;
+ const char *error;
+ int ret;
+
+ req = conn->request_queue_tail;
+ if (req != NULL &&
+ req->state == HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE) {
+
+ e_debug(conn->event, "Finish receiving request");
+
+ ret = http_request_parse_finish_payload(conn->http_parser,
+ &error_code, &error);
+ if (ret <= 0 && !http_server_connection_check_input(conn))
+ return FALSE;
+ if (ret < 0) {
+ http_server_connection_ref(conn);
+
+ http_server_connection_client_error(
+ conn, "Client sent invalid request: %s", error);
+
+ switch (error_code) {
+ case HTTP_REQUEST_PARSE_ERROR_PAYLOAD_TOO_LARGE:
+ conn->input_broken = TRUE;
+ http_server_request_fail_close(
+ req, 413, "Payload Too Large");
+ break;
+ case HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST:
+ conn->input_broken = TRUE;
+ http_server_request_fail_close(
+ req, 400, "Bad request");
+ break;
+ default:
+ i_unreached();
+ }
+
+ if (http_server_connection_unref_is_closed(conn)) {
+ /* Connection got closed */
+ return FALSE;
+ }
+
+ if (conn->input_broken || conn->close_indicated)
+ http_server_connection_input_halt(conn);
+ return FALSE;
+ }
+ if (ret == 0)
+ return FALSE;
+ http_server_request_ready_to_respond(req);
+ }
+
+ return TRUE;
+}
+
+static void http_server_connection_input(struct connection *_conn)
+{
+ struct http_server_connection *conn =
+ (struct http_server_connection *)_conn;
+ struct http_server_request *req;
+ enum http_request_parse_error error_code;
+ const char *error;
+ bool cont;
+ int ret;
+
+ if (conn->server->shutting_down) {
+ if (!http_server_connection_shut_down(conn))
+ http_server_connection_pipeline_handle_full(conn);
+ return;
+ }
+
+ i_assert(!conn->input_broken && conn->incoming_payload == NULL);
+ i_assert(!conn->close_indicated);
+
+ http_server_connection_reset_idle_timeout(conn);
+
+ if (conn->ssl && conn->ssl_iostream == NULL) {
+ if (http_server_connection_ssl_init(conn) < 0) {
+ /* SSL failed */
+ http_server_connection_close(
+ &conn, "SSL Initialization failed");
+ return;
+ }
+ }
+
+ /* Finish up pending request */
+ if (!http_server_connection_finish_request(conn))
+ return;
+
+ /* Stop handling input here when running ioloop from within request
+ callback; we cannot read the next request, since that could mean
+ recursing request callbacks. */
+ if (conn->in_req_callback) {
+ http_server_connection_input_halt(conn);
+ return;
+ }
+
+ /* Create request object if none was created already */
+ if (conn->request_queue_tail != NULL &&
+ conn->request_queue_tail->state == HTTP_SERVER_REQUEST_STATE_NEW) {
+ if (conn->request_queue_count >
+ conn->server->set.max_pipelined_requests) {
+ /* Pipeline full */
+ http_server_connection_pipeline_handle_full(conn);
+ return;
+ }
+ /* Continue last unfinished request */
+ req = conn->request_queue_tail;
+ } else {
+ if (conn->request_queue_count >=
+ conn->server->set.max_pipelined_requests) {
+ /* Pipeline full */
+ http_server_connection_pipeline_handle_full(conn);
+ return;
+ }
+ /* Start new request */
+ req = http_server_request_new(conn);
+ }
+
+ /* Parse requests */
+ ret = 1;
+ while (!conn->close_indicated && ret != 0) {
+ http_server_connection_ref(conn);
+ while ((ret = http_request_parse_next(
+ conn->http_parser, req->pool, &req->req,
+ &error_code, &error)) > 0) {
+ conn->stats.request_count++;
+ http_server_request_received(req);
+
+ http_server_request_immune_ref(req);
+ T_BEGIN {
+ cont = http_server_connection_handle_request(conn, req);
+ } T_END;
+ if (!cont) {
+ /* Connection closed or request body not read
+ yet. The request may be destroyed now. */
+ http_server_request_immune_unref(&req);
+ http_server_connection_unref(&conn);
+ return;
+ }
+ if (req->req.connection_close)
+ conn->close_indicated = TRUE;
+ http_server_request_immune_unref(&req);
+
+ if (conn->closed) {
+ /* Connection got closed in destroy callback */
+ break;
+ }
+
+ if (conn->close_indicated) {
+ /* Client indicated it will close after this
+ request; stop trying to read more. */
+ break;
+ }
+
+ /* Finish up pending request if possible */
+ if (!http_server_connection_finish_request(conn)) {
+ http_server_connection_unref(&conn);
+ return;
+ }
+
+ if (http_server_connection_pipeline_is_full(conn)) {
+ /* Pipeline full */
+ http_server_connection_pipeline_handle_full(conn);
+ http_server_connection_unref(&conn);
+ return;
+ }
+
+ /* Start new request */
+ req = http_server_request_new(conn);
+ }
+
+ if (http_server_connection_unref_is_closed(conn)) {
+ /* Connection got closed */
+ return;
+ }
+
+ if (ret <= 0 && !http_server_connection_check_input(conn))
+ return;
+
+ if (ret < 0) {
+ http_server_connection_ref(conn);
+
+ http_server_connection_client_error(
+ conn, "Client sent invalid request: %s", error);
+
+ switch (error_code) {
+ case HTTP_REQUEST_PARSE_ERROR_BROKEN_REQUEST:
+ conn->input_broken = TRUE;
+ /* fall through */
+ case HTTP_REQUEST_PARSE_ERROR_BAD_REQUEST:
+ http_server_request_fail(
+ req, 400, "Bad Request");
+ break;
+ case HTTP_REQUEST_PARSE_ERROR_METHOD_TOO_LONG:
+ conn->input_broken = TRUE;
+ /* fall through */
+ case HTTP_REQUEST_PARSE_ERROR_NOT_IMPLEMENTED:
+ http_server_request_fail(
+ req, 501, "Not Implemented");
+ break;
+ case HTTP_REQUEST_PARSE_ERROR_TARGET_TOO_LONG:
+ conn->input_broken = TRUE;
+ http_server_request_fail_close(
+ req, 414, "URI Too Long");
+ break;
+ case HTTP_REQUEST_PARSE_ERROR_EXPECTATION_FAILED:
+ http_server_request_fail(
+ req, 417, "Expectation Failed");
+ break;
+ case HTTP_REQUEST_PARSE_ERROR_PAYLOAD_TOO_LARGE:
+ conn->input_broken = TRUE;
+ http_server_request_fail_close(
+ req, 413, "Payload Too Large");
+ break;
+ default:
+ i_unreached();
+ }
+
+ if (http_server_connection_unref_is_closed(conn)) {
+ /* Connection got closed */
+ return;
+ }
+ }
+
+ if (conn->input_broken || conn->close_indicated) {
+ http_server_connection_input_halt(conn);
+ return;
+ }
+ }
+}
+
+void http_server_connection_handle_output_error(
+ struct http_server_connection *conn)
+{
+ struct ostream *output = conn->conn.output;
+
+ if (conn->closed)
+ return;
+
+ if (output->stream_errno != EPIPE &&
+ output->stream_errno != ECONNRESET) {
+ e_error(conn->event, "Connection lost: write(%s) failed: %s",
+ o_stream_get_name(output),
+ o_stream_get_error(output));
+ http_server_connection_close(
+ &conn, "Write failure");
+ } else {
+ e_debug(conn->event, "Connection lost: Remote disconnected");
+ http_server_connection_close(
+ &conn, "Remote closed connection unexpectedly");
+ }
+}
+
+enum _output_result {
+ /* Error */
+ _OUTPUT_ERROR = -1,
+ /* Output blocked */
+ _OUTPUT_BLOCKED = 0,
+ /* Successful, but no more responses are ready to be sent */
+ _OUTPUT_FINISHED = 1,
+ /* Successful and more responses can be sent */
+ _OUTPUT_AVAILABLE = 2,
+};
+
+static enum _output_result
+http_server_connection_next_response(struct http_server_connection *conn)
+{
+ struct http_server_request *req;
+ int ret;
+
+ if (conn->output_locked)
+ return _OUTPUT_FINISHED;
+
+ req = conn->request_queue_head;
+ if (req == NULL || req->state == HTTP_SERVER_REQUEST_STATE_NEW) {
+ /* No requests pending */
+ e_debug(conn->event, "No more requests pending");
+ http_server_connection_start_idle_timeout(conn);
+ return _OUTPUT_FINISHED;
+ }
+ if (req->state < HTTP_SERVER_REQUEST_STATE_READY_TO_RESPOND) {
+ if (req->state == HTTP_SERVER_REQUEST_STATE_PROCESSING) {
+ /* Server is causing idle time */
+ e_debug(conn->event, "Not ready to respond: "
+ "Server is processing");
+ http_server_connection_stop_idle_timeout(conn);
+ } else {
+ /* Client is causing idle time */
+ e_debug(conn->event, "Not ready to respond: "
+ "Waiting for client");
+ http_server_connection_start_idle_timeout(conn);
+ }
+
+ /* send 100 Continue if appropriate */
+ if (req->state >= HTTP_SERVER_REQUEST_STATE_QUEUED &&
+ conn->incoming_payload != NULL &&
+ req->response == NULL && req->req.version_minor >= 1 &&
+ req->req.expect_100_continue && !req->payload_halted &&
+ !req->sent_100_continue) {
+ static const char *response =
+ "HTTP/1.1 100 Continue\r\n\r\n";
+ struct ostream *output = conn->conn.output;
+
+ if (o_stream_send(output, response,
+ strlen(response)) < 0) {
+ http_server_connection_handle_output_error(conn);
+ return _OUTPUT_ERROR;
+ }
+
+ e_debug(conn->event, "Sent 100 Continue");
+ req->sent_100_continue = TRUE;
+ }
+ return _OUTPUT_FINISHED;
+ }
+
+ i_assert(req->state == HTTP_SERVER_REQUEST_STATE_READY_TO_RESPOND &&
+ req->response != NULL);
+
+ e_debug(conn->event, "Sending response");
+ http_server_connection_start_idle_timeout(conn);
+
+ http_server_request_immune_ref(req);
+ ret = http_server_response_send(req->response);
+ http_server_request_immune_unref(&req);
+
+ if (ret < 0)
+ return _OUTPUT_ERROR;
+
+ http_server_connection_reset_idle_timeout(conn);
+ if (ret == 0)
+ return _OUTPUT_BLOCKED;
+ if (conn->output_locked)
+ return _OUTPUT_FINISHED;
+ return _OUTPUT_AVAILABLE;
+}
+
+static int
+http_server_connection_send_responses(struct http_server_connection *conn)
+{
+ enum _output_result ores = _OUTPUT_AVAILABLE;
+
+ http_server_connection_ref(conn);
+
+ /* Send more responses until no more responses remain, the output
+ blocks again, or the connection is closed */
+ while (!conn->closed && ores == _OUTPUT_AVAILABLE)
+ ores = http_server_connection_next_response(conn);
+
+ if (http_server_connection_unref_is_closed(conn) ||
+ ores == _OUTPUT_ERROR)
+ return -1;
+
+ /* Accept more requests if possible */
+ if (conn->incoming_payload == NULL &&
+ (conn->request_queue_count <
+ conn->server->set.max_pipelined_requests) &&
+ !conn->server->shutting_down)
+ http_server_connection_input_resume(conn);
+
+ switch (ores) {
+ case _OUTPUT_ERROR:
+ case _OUTPUT_AVAILABLE:
+ break;
+ case _OUTPUT_BLOCKED:
+ return 0;
+ case _OUTPUT_FINISHED:
+ return 1;
+ }
+ i_unreached();
+}
+
+int http_server_connection_flush(struct http_server_connection *conn)
+{
+ struct ostream *output = conn->conn.output;
+ int ret;
+
+ if ((ret = o_stream_flush(output)) <= 0) {
+ if (ret < 0)
+ http_server_connection_handle_output_error(conn);
+ return ret;
+ }
+
+ http_server_connection_reset_idle_timeout(conn);
+ return 0;
+}
+
+int http_server_connection_output(struct http_server_connection *conn)
+{
+ bool pipeline_was_full =
+ http_server_connection_pipeline_is_full(conn);
+ int ret = 1;
+
+ if (http_server_connection_flush(conn) < 0)
+ return -1;
+
+ if (!conn->output_locked) {
+ ret = http_server_connection_send_responses(conn);
+ if (ret < 0)
+ return -1;
+ } else if (conn->request_queue_head != NULL) {
+ struct http_server_request *req = conn->request_queue_head;
+ struct http_server_response *resp = req->response;
+
+ i_assert(resp != NULL);
+
+ http_server_connection_ref(conn);
+
+ http_server_request_immune_ref(req);
+ ret = http_server_response_send_more(resp);
+ http_server_request_immune_unref(&req);
+
+ if (http_server_connection_unref_is_closed(conn) || ret < 0)
+ return -1;
+
+ if (!conn->output_locked) {
+ /* Room for more responses */
+ ret = http_server_connection_send_responses(conn);
+ if (ret < 0)
+ return -1;
+ } else if (conn->io_resp_payload != NULL) {
+ /* Server is causing idle time */
+ e_debug(conn->event, "Not ready to continue response: "
+ "Server is producing response");
+ http_server_connection_stop_idle_timeout(conn);
+ } else {
+ /* Client is causing idle time */
+ e_debug(conn->event, "Not ready to continue response: "
+ "Waiting for client");
+ http_server_connection_start_idle_timeout(conn);
+ }
+ }
+
+ if (conn->server->shutting_down &&
+ http_server_connection_shut_down(conn))
+ return 1;
+
+ if (!http_server_connection_pipeline_is_full(conn)) {
+ http_server_connection_input_resume(conn);
+ if (pipeline_was_full && conn->conn.io != NULL)
+ http_server_connection_input_set_pending(conn);
+ }
+
+ return ret;
+}
+
+void http_server_connection_output_trigger(struct http_server_connection *conn)
+{
+ if (conn->conn.output == NULL)
+ return;
+ o_stream_set_flush_pending(conn->conn.output, TRUE);
+}
+
+void http_server_connection_output_halt(struct http_server_connection *conn)
+{
+ conn->output_halted = TRUE;
+
+ if (conn->conn.output == NULL)
+ return;
+
+ o_stream_unset_flush_callback(conn->conn.output);
+}
+
+void http_server_connection_output_resume(struct http_server_connection *conn)
+{
+ if (!conn->output_halted)
+ return;
+ conn->output_halted = FALSE;
+
+ if (conn->conn.output == NULL)
+ return;
+ o_stream_set_flush_callback(conn->conn.output,
+ http_server_connection_output, conn);
+}
+
+bool http_server_connection_pending_payload(
+ struct http_server_connection *conn)
+{
+ return http_request_parser_pending_payload(conn->http_parser);
+}
+
+static struct connection_settings http_server_connection_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE,
+ .log_connection_id = TRUE,
+};
+
+static const struct connection_vfuncs http_server_connection_vfuncs = {
+ .destroy = http_server_connection_destroy,
+ .input = http_server_connection_input
+};
+
+struct connection_list *http_server_connection_list_init(void)
+{
+ return connection_list_init(&http_server_connection_set,
+ &http_server_connection_vfuncs);
+}
+
+struct http_server_connection *
+http_server_connection_create(struct http_server *server,
+ int fd_in, int fd_out, bool ssl,
+ const struct http_server_callbacks *callbacks,
+ void *context)
+{
+ const struct http_server_settings *set = &server->set;
+ struct http_server_connection *conn;
+ struct event *conn_event;
+
+ i_assert(!server->shutting_down);
+
+ conn = i_new(struct http_server_connection, 1);
+ conn->refcount = 1;
+ conn->server = server;
+ conn->ioloop = current_ioloop;
+ conn->ssl = ssl;
+ conn->callbacks = callbacks;
+ conn->context = context;
+
+ net_set_nonblock(fd_in, TRUE);
+ if (fd_in != fd_out)
+ net_set_nonblock(fd_out, TRUE);
+ (void)net_set_tcp_nodelay(fd_out, TRUE);
+
+ if (set->socket_send_buffer_size > 0 &&
+ net_set_send_buffer_size(fd_out,
+ set->socket_send_buffer_size) < 0) {
+ e_error(conn->event,
+ "net_set_send_buffer_size(%zu) failed: %m",
+ set->socket_send_buffer_size);
+ }
+ if (set->socket_recv_buffer_size > 0 &&
+ net_set_recv_buffer_size(fd_in,
+ set->socket_recv_buffer_size) < 0) {
+ e_error(conn->event,
+ "net_set_recv_buffer_size(%zu) failed: %m",
+ set->socket_recv_buffer_size);
+ }
+
+ conn_event = event_create(server->event);
+ conn->conn.event_parent = conn_event;
+ connection_init_server(server->conn_list, &conn->conn, NULL,
+ fd_in, fd_out);
+ conn->event = conn->conn.event;
+ event_unref(&conn_event);
+
+ if (!ssl)
+ http_server_connection_ready(conn);
+ http_server_connection_start_idle_timeout(conn);
+
+ e_debug(conn->event, "Connection created");
+ return conn;
+}
+
+void http_server_connection_ref(struct http_server_connection *conn)
+{
+ i_assert(conn->refcount > 0);
+ conn->refcount++;
+}
+
+static void
+http_server_connection_disconnect(struct http_server_connection *conn,
+ const char *reason)
+{
+ struct http_server_request *req, *req_next;
+
+ if (conn->closed)
+ return;
+
+ if (reason == NULL)
+ reason = "Connection closed";
+ e_debug(conn->event, "Disconnected: %s", reason);
+ conn->disconnect_reason = i_strdup(reason);
+ conn->closed = TRUE;
+
+ /* Preserve statistics */
+ http_server_connection_update_stats(conn);
+
+ if (conn->incoming_payload != NULL) {
+ /* The stream is still accessed by lib-http caller. */
+ i_stream_remove_destroy_callback(conn->incoming_payload,
+ http_server_payload_destroyed);
+ conn->incoming_payload = NULL;
+ }
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+
+ /* Drop all requests before connection is closed */
+ req = conn->request_queue_head;
+ while (req != NULL) {
+ req_next = req->next;
+ http_server_request_abort(&req, reason);
+ req = req_next;
+ }
+
+ timeout_remove(&conn->to_input);
+ timeout_remove(&conn->to_idle);
+ io_remove(&conn->io_resp_payload);
+ if (conn->conn.output != NULL)
+ o_stream_uncork(conn->conn.output);
+
+ if (conn->http_parser != NULL)
+ http_request_parser_deinit(&conn->http_parser);
+ connection_disconnect(&conn->conn);
+}
+
+bool http_server_connection_unref(struct http_server_connection **_conn)
+{
+ struct http_server_connection *conn = *_conn;
+
+ i_assert(conn->refcount > 0);
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return TRUE;
+
+ http_server_connection_disconnect(conn, NULL);
+
+ e_debug(conn->event, "Connection destroy");
+
+ ssl_iostream_destroy(&conn->ssl_iostream);
+ connection_deinit(&conn->conn);
+
+ if (conn->callbacks != NULL &&
+ conn->callbacks->connection_destroy != NULL) T_BEGIN {
+ conn->callbacks->connection_destroy(conn->context,
+ conn->disconnect_reason);
+ } T_END;
+
+ i_free(conn->disconnect_reason);
+ i_free(conn);
+ return FALSE;
+}
+
+static bool
+http_server_connection_unref_is_closed(struct http_server_connection *conn)
+{
+ bool closed = conn->closed;
+
+ if (!http_server_connection_unref(&conn))
+ closed = TRUE;
+ return closed;
+}
+
+void http_server_connection_close(struct http_server_connection **_conn,
+ const char *reason)
+{
+ struct http_server_connection *conn = *_conn;
+
+ http_server_connection_disconnect(conn, reason);
+ http_server_connection_unref(_conn);
+}
+
+void http_server_connection_tunnel(struct http_server_connection **_conn,
+ http_server_tunnel_callback_t callback,
+ void *context)
+{
+ struct http_server_connection *conn = *_conn;
+ struct http_server_tunnel tunnel;
+
+ /* Preserve statistics */
+ http_server_connection_update_stats(conn);
+
+ i_zero(&tunnel);
+ tunnel.input = conn->conn.input;
+ tunnel.output = conn->conn.output;
+ tunnel.fd_in = conn->conn.fd_in;
+ tunnel.fd_out = conn->conn.fd_out;
+
+ conn->conn.input = NULL;
+ conn->conn.output = NULL;
+ conn->conn.fd_in = conn->conn.fd_out = -1;
+ http_server_connection_close(_conn, "Tunnel initiated");
+
+ callback(context, &tunnel);
+}
+
+struct ioloop *
+http_server_connection_switch_ioloop_to(struct http_server_connection *conn,
+ struct ioloop *ioloop)
+{
+ struct ioloop *prev_ioloop = conn->ioloop;
+
+ if (conn->ioloop_switching != NULL)
+ return conn->ioloop_switching;
+
+ conn->ioloop = ioloop;
+ conn->ioloop_switching = prev_ioloop;
+ connection_switch_ioloop_to(&conn->conn, ioloop);
+ if (conn->to_input != NULL) {
+ conn->to_input =
+ io_loop_move_timeout_to(ioloop, &conn->to_input);
+ }
+ if (conn->to_idle != NULL) {
+ conn->to_idle =
+ io_loop_move_timeout_to(ioloop, &conn->to_idle);
+ }
+ if (conn->io_resp_payload != NULL) {
+ conn->io_resp_payload =
+ io_loop_move_io_to(ioloop, &conn->io_resp_payload);
+ }
+ if (conn->payload_handler != NULL) {
+ http_server_payload_handler_switch_ioloop(
+ conn->payload_handler, ioloop);
+ }
+ if (conn->incoming_payload != NULL)
+ i_stream_switch_ioloop_to(conn->incoming_payload, ioloop);
+ conn->ioloop_switching = NULL;
+
+ return prev_ioloop;
+}
+
+struct ioloop *
+http_server_connection_switch_ioloop(struct http_server_connection *conn)
+{
+ return http_server_connection_switch_ioloop_to(conn, current_ioloop);
+}