/* 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 "settings.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->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,
					     MASTER_SERVICE_SHUTTING_DOWN_MSG);
		return TRUE;
	}
	return FALSE;
}

static void http_server_connection_ready(struct http_server_connection *conn)
{
	const struct http_server_settings *set = conn->set;
	struct http_url base_url;
	struct stat st;

	if (set->rawlog_dir != NULL &&
	    stat(set->rawlog_dir, &st) == 0) {
		iostream_rawlog_create(conn->event, "http_server_rawlog_dir",
				       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;

	const struct http_request_limits limits = {
		.max_target_length = set->request_max_target_length,
		.max_payload_size = set->request_max_payload_size,
		.header.max_size = set->request_hdr_max_size,
		.header.max_field_size = set->request_hdr_max_field_size,
		.header.max_fields = set->request_hdr_max_fields,
	};
	conn->http_parser = http_request_parser_init(
		conn->conn.input, &base_url, &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->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 || !req->response->submitted)) {
			e_debug(conn->event, "Trigger 100-continue response");
			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;
	struct ssl_iostream_context *ssl_ctx;
	const char *error;
	int ret;

	e_debug(conn->event, "Starting SSL handshake");

	http_server_connection_input_halt(conn);
	const char *const names[] = {
		"http/1.1",
		NULL
	};
	if (server->ssl_set == NULL) {
		const struct ssl_iostream_server_autocreate_parameters parameters = {
			.event_parent = server->event,
			.application_protocols = names,
		};
		ret = io_stream_autocreate_ssl_server(&parameters,
						      &conn->conn.input,
						      &conn->conn.output,
						      &conn->ssl_iostream,
						      &error);
	} else if ((ret = ssl_iostream_server_context_cache_get(server->ssl_set,
								&ssl_ctx, &error)) < 0) {
		/* pass */
	} else {
		if (ret > 0)
			ssl_iostream_context_set_application_protocols(ssl_ctx, names);
		ret = io_stream_create_ssl_server(ssl_ctx,
						  server->event,
						  &conn->conn.input,
						  &conn->conn.output,
						  &conn->ssl_iostream, &error);
		ssl_iostream_context_unref(&ssl_ctx);
	}
	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->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->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->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->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->response->submitted) &&
		    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->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)
{
	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->set = server->set;
	pool_ref(server->set->pool);
	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 (conn->set->socket_send_buffer_size > 0 &&
	    net_set_send_buffer_size(fd_out,
				     conn->set->socket_send_buffer_size) < 0) {
		e_error(conn->event,
			"net_set_send_buffer_size(%"PRIuUOFF_T") failed: %m",
			conn->set->socket_send_buffer_size);
	}
	if (conn->set->socket_recv_buffer_size > 0 &&
	    net_set_recv_buffer_size(fd_in,
				     conn->set->socket_recv_buffer_size) < 0) {
		e_error(conn->event,
			"net_set_recv_buffer_size(%"PRIuUOFF_T") failed: %m",
			conn->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;

	settings_free(conn->set);
	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;
	if (conn == NULL)
		return;

	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);
}
