summaryrefslogtreecommitdiffstats
path: root/src/lib-http/http-server-response.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-http/http-server-response.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/src/lib-http/http-server-response.c b/src/lib-http/http-server-response.c
new file mode 100644
index 0000000..8d55593
--- /dev/null
+++ b/src/lib-http/http-server-response.c
@@ -0,0 +1,801 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "istream.h"
+#include "ostream-private.h"
+#include "http-date.h"
+#include "http-transfer.h"
+#include "http-server-private.h"
+
+struct http_server_response_payload {
+ struct http_server_response *resp;
+ struct const_iovec *iov;
+ unsigned int iov_count, iov_idx;
+ size_t iov_pos;
+};
+
+/*
+ * Response
+ */
+
+static void http_server_response_update_event(struct http_server_response *resp)
+{
+ event_add_int(resp->event, "status", resp->status);
+ event_set_append_log_prefix(resp->event,
+ t_strdup_printf("%u response: ",
+ resp->status));
+}
+
+struct http_server_response *
+http_server_response_create(struct http_server_request *req,
+ unsigned int status, const char *reason)
+{
+ struct http_server_response *resp;
+
+ i_assert(req->state < HTTP_SERVER_REQUEST_STATE_SENT_RESPONSE);
+
+ if (req->response == NULL) {
+ resp = req->response = p_new(req->pool,
+ struct http_server_response, 1);
+ } else {
+ /* Was already composing a response, but decided to
+ start a new one (would usually be a failure response)
+ */
+ resp = req->response;
+
+ ARRAY_TYPE(string) perm_headers = resp->perm_headers;
+ i_zero(&resp->perm_headers);
+
+ http_server_response_request_free(resp);
+ i_zero(resp);
+
+ resp->perm_headers = perm_headers;
+ }
+
+ resp->request = req;
+ resp->status = status;
+ resp->reason = p_strdup(req->pool, reason);
+ resp->headers = str_new(default_pool, 256);
+ resp->date = (time_t)-1;
+ resp->event = event_create(req->event);
+ http_server_response_update_event(resp);
+
+ if (array_is_created(&resp->perm_headers)) {
+ unsigned int i, count;
+ char *const *headers = array_get(&resp->perm_headers, &count);
+ for (i = 0; i < count; i += 2)
+ http_server_response_add_header(resp, headers[i],
+ headers[i+1]);
+ }
+ return resp;
+}
+
+void http_server_response_request_free(struct http_server_response *resp)
+{
+ e_debug(resp->event, "Free");
+
+ /* Cannot be destroyed while payload output stream still exists */
+ i_assert(resp->payload_stream == NULL);
+
+ i_stream_unref(&resp->payload_input);
+ o_stream_unref(&resp->payload_output);
+ event_unref(&resp->event);
+ str_free(&resp->headers);
+
+ if (array_is_created(&resp->perm_headers)) {
+ char *headers;
+
+ array_foreach_elem(&resp->perm_headers, headers)
+ i_free(headers);
+ array_free(&resp->perm_headers);
+ }
+}
+
+void http_server_response_request_destroy(struct http_server_response *resp)
+{
+ e_debug(resp->event, "Destroy");
+
+ if (resp->payload_stream != NULL)
+ http_server_ostream_response_destroyed(resp->payload_stream);
+}
+
+void http_server_response_request_abort(struct http_server_response *resp,
+ const char *reason)
+{
+ if (reason == NULL)
+ e_debug(resp->event, "Abort");
+ else
+ e_debug(resp->event, "Abort: %s", reason);
+
+ if (resp->payload_stream != NULL) {
+ http_server_ostream_set_error(resp->payload_stream,
+ EPIPE, reason);
+ }
+}
+
+void http_server_response_ref(struct http_server_response *resp)
+{
+ http_server_request_ref(resp->request);
+}
+
+bool http_server_response_unref(struct http_server_response **_resp)
+{
+ struct http_server_response *resp = *_resp;
+ struct http_server_request *req;
+
+ *_resp = NULL;
+ if (resp == NULL)
+ return FALSE;
+
+ req = resp->request;
+ return http_server_request_unref(&req);
+}
+
+void http_server_response_add_header(struct http_server_response *resp,
+ const char *key, const char *value)
+{
+ i_assert(!resp->submitted);
+ i_assert(strchr(key, '\r') == NULL && strchr(key, '\n') == NULL);
+ i_assert(strchr(value, '\r') == NULL && strchr(value, '\n') == NULL);
+
+ /* Mark presence of special headers */
+ switch (key[0]) {
+ case 'c': case 'C':
+ if (strcasecmp(key, "Connection") == 0)
+ resp->have_hdr_connection = TRUE;
+ else if (strcasecmp(key, "Content-Length") == 0)
+ resp->have_hdr_body_spec = TRUE;
+ break;
+ case 'd': case 'D':
+ if (strcasecmp(key, "Date") == 0)
+ resp->have_hdr_date = TRUE;
+ break;
+ case 't': case 'T':
+ if (strcasecmp(key, "Transfer-Encoding") == 0)
+ resp->have_hdr_body_spec = TRUE;
+ break;
+ }
+ str_printfa(resp->headers, "%s: %s\r\n", key, value);
+}
+
+void http_server_response_update_status(struct http_server_response *resp,
+ unsigned int status,
+ const char *reason)
+{
+ i_assert(!resp->submitted);
+ resp->status = status;
+ /* Free not called because pool is alloconly */
+ resp->reason = p_strdup(resp->request->pool, reason);
+}
+
+void http_server_response_set_date(struct http_server_response *resp,
+ time_t date)
+{
+ i_assert(!resp->submitted);
+
+ resp->date = date;
+}
+
+void http_server_response_set_payload(struct http_server_response *resp,
+ struct istream *input)
+{
+ int ret;
+
+ i_assert(!resp->submitted);
+ i_assert(resp->payload_input == NULL);
+ i_assert(resp->payload_stream == NULL);
+
+ i_stream_ref(input);
+ resp->payload_input = input;
+ if ((ret = i_stream_get_size(input, TRUE, &resp->payload_size)) <= 0) {
+ if (ret < 0) {
+ e_error(resp->event, "i_stream_get_size(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ }
+ resp->payload_size = 0;
+ resp->payload_chunked = TRUE;
+ } else {
+ i_assert(input->v_offset <= resp->payload_size);
+ resp->payload_size -= input->v_offset;
+ }
+ resp->payload_offset = input->v_offset;
+}
+
+void http_server_response_set_payload_data(struct http_server_response *resp,
+ const unsigned char *data,
+ size_t size)
+{
+ struct istream *input;
+ unsigned char *payload_data;
+
+ i_assert(!resp->submitted);
+ i_assert(resp->payload_input == NULL);
+ i_assert(resp->payload_stream == NULL);
+
+ if (size == 0)
+ return;
+
+ payload_data = p_malloc(resp->request->pool, size);
+ memcpy(payload_data, data, size);
+ input = i_stream_create_from_data(payload_data, size);
+
+ http_server_response_set_payload(resp, input);
+ i_stream_unref(&input);
+}
+
+struct ostream *
+http_server_response_get_payload_output(struct http_server_response *resp,
+ size_t max_buffer_size, bool blocking)
+{
+ struct http_server_request *req = resp->request;
+ struct http_server_connection *conn = req->conn;
+ struct ostream *output;
+
+ i_assert(conn != NULL);
+ i_assert(!resp->submitted);
+ i_assert(resp->payload_input == NULL);
+ i_assert(resp->payload_stream == NULL);
+
+ output = http_server_ostream_create(resp, max_buffer_size, blocking);
+ o_stream_set_name(output,
+ t_strdup_printf("(conn %s: request %s: %u response payload)",
+ conn->conn.label,
+ http_server_request_label(req), resp->status));
+ return output;
+}
+
+void http_server_response_add_auth(struct http_server_response *resp,
+ const struct http_auth_challenge *chlng)
+{
+ struct http_auth_challenge *new;
+ pool_t pool = resp->request->pool;
+
+ if (!array_is_created(&resp->auth_challenges))
+ p_array_init(&resp->auth_challenges, pool, 4);
+
+ new = array_append_space(&resp->auth_challenges);
+ http_auth_challenge_copy(pool, new, chlng);
+}
+
+void http_server_response_add_auth_basic(struct http_server_response *resp,
+ const char *realm)
+{
+ struct http_auth_challenge chlng;
+
+ http_auth_basic_challenge_init(&chlng, realm);
+ http_server_response_add_auth(resp, &chlng);
+}
+
+static void
+http_server_response_do_submit(struct http_server_response *resp)
+{
+ i_assert(!resp->submitted);
+ if (resp->date == (time_t)-1)
+ resp->date = ioloop_time;
+ resp->submitted = TRUE;
+ http_server_request_submit_response(resp->request);
+}
+
+void http_server_response_submit(struct http_server_response *resp)
+{
+ e_debug(resp->event, "Submitted");
+
+ http_server_response_do_submit(resp);
+}
+
+void http_server_response_submit_close(struct http_server_response *resp)
+{
+ http_server_request_connection_close(resp->request, TRUE);
+ http_server_response_submit(resp);
+}
+
+void http_server_response_submit_tunnel(struct http_server_response *resp,
+ http_server_tunnel_callback_t callback,
+ void *context)
+{
+ e_debug(resp->event, "Started tunnelling");
+
+ resp->tunnel_callback = callback;
+ resp->tunnel_context = context;
+ http_server_request_connection_close(resp->request, TRUE);
+ http_server_response_do_submit(resp);
+}
+
+static int
+http_server_response_flush_payload(struct http_server_response *resp)
+{
+ struct http_server_request *req = resp->request;
+ struct http_server_connection *conn = req->conn;
+ int ret;
+
+ if (resp->payload_output != conn->conn.output &&
+ (ret = o_stream_finish(resp->payload_output)) <= 0) {
+ if (ret < 0)
+ http_server_connection_handle_output_error(conn);
+ else
+ http_server_connection_start_idle_timeout(conn);
+ return ret;
+ }
+
+ return 1;
+}
+
+void http_server_response_request_finished(struct http_server_response *resp)
+{
+ e_debug(resp->event, "Finished");
+
+ if (resp->payload_stream != NULL)
+ http_server_ostream_response_finished(resp->payload_stream);
+
+ event_add_int(resp->request->event, "status_code", resp->status);
+}
+
+int http_server_response_finish_payload_out(struct http_server_response *resp)
+{
+ struct http_server_request *req = resp->request;
+ struct http_server_connection *conn = req->conn;
+ int ret;
+
+ if (req->state >= HTTP_SERVER_REQUEST_STATE_FINISHED)
+ return 1;
+
+ resp->payload_finished = TRUE;
+
+ if (resp->payload_output != NULL) {
+ ret = http_server_response_flush_payload(resp);
+ if (ret < 0)
+ return -1;
+ if (ret == 0) {
+ e_debug(resp->event,
+ "Not quite finished sending payload");
+ return 0;
+ }
+ o_stream_unref(&resp->payload_output);
+ resp->payload_output = NULL;
+ }
+
+ e_debug(resp->event, "Finished sending payload");
+
+ http_server_connection_ref(conn);
+ conn->output_locked = FALSE;
+ if (conn->conn.output != NULL && !conn->conn.output->closed) {
+ if (resp->payload_corked &&
+ o_stream_uncork_flush(conn->conn.output) < 0)
+ http_server_connection_handle_output_error(conn);
+ o_stream_set_flush_callback(conn->conn.output,
+ http_server_connection_output,
+ conn);
+ }
+
+ if (conn->request_queue_head == NULL ||
+ (conn->request_queue_head->state !=
+ HTTP_SERVER_REQUEST_STATE_PROCESSING))
+ http_server_connection_start_idle_timeout(conn);
+
+ http_server_request_finished(resp->request);
+ http_server_connection_unref(&conn);
+ return 1;
+}
+
+static int
+http_server_response_output_payload(struct http_server_response **_resp,
+ const unsigned char *data, size_t size)
+{
+ struct http_server_response *resp = *_resp;
+ struct http_server_request *req = resp->request;
+ struct ostream *output;
+ ssize_t sret;
+ int ret;
+
+ i_assert(req->state < HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE ||
+ req->state == HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT);
+
+ http_server_response_ref(resp);
+
+ if (resp->payload_stream == NULL) {
+ output = http_server_response_get_payload_output(
+ resp, IO_BLOCK_SIZE, TRUE);
+ } else {
+ output = http_server_ostream_get_output(resp->payload_stream);
+ }
+
+ if (data != NULL) {
+ if ((sret = o_stream_send(output, data, size)) < 0) {
+ *_resp = NULL;
+ o_stream_destroy(&output);
+ http_server_response_unref(&resp);
+ return -1;
+ }
+ i_assert((size_t)sret == size);
+ } else {
+ if ((ret = o_stream_finish(output)) < 0) {
+ *_resp = NULL;
+ o_stream_destroy(&output);
+ http_server_response_unref(&resp);
+ return -1;
+ }
+ i_assert(ret > 0);
+ }
+
+ switch (req->state) {
+ case HTTP_SERVER_REQUEST_STATE_FINISHED:
+ ret = 1;
+ break;
+ case HTTP_SERVER_REQUEST_STATE_ABORTED:
+ e_debug(resp->event,
+ "Request aborted while sending blocking payload");
+ ret = -1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ if (data == NULL)
+ o_stream_destroy(&output);
+
+ /* Callback may have messed with our pointer, so unref using local
+ variable */
+ if (!http_server_response_unref(&resp))
+ *_resp = NULL;
+
+ /* Return status */
+ return ret;
+}
+
+int http_server_response_send_payload(struct http_server_response **_resp,
+ const unsigned char *data, size_t size)
+{
+ struct http_server_response *resp = *_resp;
+ int ret;
+
+ resp->payload_corked = TRUE;
+
+ i_assert(data != NULL);
+
+ ret = http_server_response_output_payload(&resp, data, size);
+ if (ret < 0)
+ *_resp = NULL;
+ else {
+ i_assert(ret == 0);
+ i_assert(resp != NULL);
+ }
+ return ret;
+}
+
+int http_server_response_finish_payload(struct http_server_response **_resp)
+{
+ struct http_server_response *resp = *_resp;
+ int ret;
+
+ *_resp = NULL;
+ ret = http_server_response_output_payload(&resp, NULL, 0);
+ i_assert(ret != 0);
+ return ret < 0 ? -1 : 0;
+}
+
+void http_server_response_abort_payload(struct http_server_response **_resp)
+{
+ struct http_server_response *resp = *_resp;
+ struct http_server_request *req = resp->request;
+
+ *_resp = NULL;
+
+ http_server_request_abort(&req, "Aborted sending response payload");
+}
+
+static void
+http_server_response_payload_input(struct http_server_response *resp)
+{
+ struct http_server_connection *conn = resp->request->conn;
+
+ io_remove(&conn->io_resp_payload);
+
+ (void)http_server_connection_output(conn);
+}
+
+int http_server_response_send_more(struct http_server_response *resp)
+{
+ struct http_server_connection *conn = resp->request->conn;
+ struct ostream *output = resp->payload_output;
+ enum ostream_send_istream_result res;
+
+ i_assert(resp->payload_output != NULL);
+
+ if (resp->payload_finished) {
+ e_debug(resp->event, "Finish sending payload (more)");
+ return http_server_response_finish_payload_out(resp);
+ }
+
+ if (resp->payload_stream != NULL) {
+ conn->output_locked = TRUE;
+ return http_server_ostream_continue(resp->payload_stream);
+ }
+
+ i_assert(resp->payload_input != NULL);
+ io_remove(&conn->io_resp_payload);
+
+ /* Chunked ostream needs to write to the parent stream's buffer */
+ o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
+ res = o_stream_send_istream(output, resp->payload_input);
+ o_stream_set_max_buffer_size(output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ /* Finished sending */
+ if (!resp->payload_chunked &&
+ (resp->payload_input->v_offset - resp->payload_offset) !=
+ resp->payload_size) {
+ e_error(resp->event,
+ "Payload stream %s size changed unexpectedly",
+ i_stream_get_name(resp->payload_input));
+ http_server_connection_close(
+ &conn, "Payload read failure");
+ return -1;
+ }
+ /* Finished sending payload */
+ e_debug(resp->event, "Finish sending payload");
+ return http_server_response_finish_payload_out(resp);
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ /* Input is blocking (server needs to act; disable timeout) */
+ conn->output_locked = TRUE;
+ http_server_connection_stop_idle_timeout(conn);
+ conn->io_resp_payload = io_add_istream(resp->payload_input,
+ http_server_response_payload_input, resp);
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ /* Output is blocking (client needs to act; enable timeout) */
+ conn->output_locked = TRUE;
+ http_server_connection_start_idle_timeout(conn);
+ o_stream_set_flush_pending(output, TRUE);
+ //e_debug(resp->event, "Partially sent payload");
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ /* We're in the middle of sending a response, so the connection
+ will also have to be aborted */
+ e_error(resp->event, "read(%s) failed: %s",
+ i_stream_get_name(resp->payload_input),
+ i_stream_get_error(resp->payload_input));
+ http_server_connection_close(&conn,
+ "Payload read failure");
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* Failed to send response */
+ http_server_connection_handle_output_error(conn);
+ return -1;
+ }
+ i_unreached();
+}
+
+static int http_server_response_send_real(struct http_server_response *resp)
+{
+ struct http_server_request *req = resp->request;
+ struct http_server_connection *conn = req->conn;
+ string_t *rtext = t_str_new(256);
+ struct const_iovec iov[3];
+ uoff_t content_length = 0;
+ bool chunked = FALSE, send_content_length = FALSE, close = FALSE;
+ bool is_head = http_request_method_is(&req->req, "HEAD");
+ int ret;
+
+ i_assert(!conn->output_locked);
+
+ /* Determine response payload to send */
+ if (resp->payload_input != NULL) {
+ i_assert(resp->tunnel_callback == NULL &&
+ resp->status / 100 != 1 &&
+ resp->status != 204 && resp->status != 304);
+ if (resp->payload_chunked) {
+ if (http_server_request_version_equals(req, 1, 0)) {
+ /* Connection close marks end of payload
+ */
+ close = TRUE;
+ } else {
+ /* Input stream with unknown size */
+ chunked = TRUE;
+ }
+ } else {
+ /* Send Content-Length if we have specified a payload,
+ even if it's 0 bytes. */
+ content_length = resp->payload_size;
+ send_content_length = TRUE;
+ }
+ } else if (resp->payload_stream != NULL) {
+ /* HTTP payload output stream */
+ if (!http_server_ostream_get_size(resp->payload_stream,
+ &content_length)) {
+ /* size not known at this point */
+ chunked = TRUE;
+ } else {
+ /* output stream already finished, so data is
+ pre-buffered */
+ send_content_length = TRUE;
+ }
+ } else if (resp->tunnel_callback == NULL && resp->status / 100 != 1 &&
+ resp->status != 204 && resp->status != 304 && !is_head) {
+ /* RFC 7230, Section 3.3: Message Body
+
+ Responses to the HEAD request method (Section 4.3.2 of
+ [RFC7231]) never include a message body because the
+ associated response header fields (e.g., Transfer-Encoding,
+ Content-Length, etc.), if present, indicate only what their
+ values would have been if the request method had been GET
+ (Section 4.3.1 of [RFC7231]). 2xx (Successful) responses to a
+ CONNECT request method (Section 4.3.6 of [RFC7231]) switch to
+ tunnel mode instead of having a message body. All 1xx
+ (Informational), 204 (No Content), and 304 (Not Modified)
+ responses do not include a message body. All other responses
+ do include a message body, although the body might be of zero
+ length.
+
+ RFC 7230, Section 3.3.2: Content-Length
+
+ A server MUST NOT send a Content-Length header field in any
+ 2xx (Successful) response to a CONNECT request (Section 4.3.6
+ of [RFC7231]).
+
+ -> Create empty body if it is missing.
+ */
+ send_content_length = TRUE;
+ }
+
+ /* Initialize output payload stream if needed */
+ if (is_head) {
+ e_debug(resp->event, "A HEAD response has no payload");
+ } else if (chunked) {
+ i_assert(resp->payload_input != NULL ||
+ resp->payload_stream != NULL);
+
+ e_debug(resp->event, "Will send payload in chunks");
+
+ resp->payload_output =
+ http_transfer_chunked_ostream_create(conn->conn.output);
+ } else if (send_content_length) {
+ i_assert(resp->payload_input != NULL || content_length == 0 ||
+ resp->payload_stream != NULL);
+
+ e_debug(resp->event,
+ "Will send payload with explicit size %"PRIuUOFF_T,
+ content_length);
+
+ if (content_length > 0) {
+ resp->payload_output = conn->conn.output;
+ o_stream_ref(conn->conn.output);
+ }
+ } else if (close) {
+ i_assert(resp->payload_input != NULL);
+
+ e_debug(resp->event,
+ "Will close connection after sending payload "
+ "(HTTP/1.0)");
+
+ resp->payload_output = conn->conn.output;
+ o_stream_ref(conn->conn.output);
+ } else {
+ e_debug(resp->event, "Response has no payload");
+ }
+
+ /* Create status line */
+ str_append(rtext, "HTTP/1.1 ");
+ str_printfa(rtext, "%u", resp->status);
+ str_append(rtext, " ");
+ str_append(rtext, resp->reason);
+
+ /* Create special headers implicitly if not set explicitly using
+ http_server_response_add_header() */
+ if (!resp->have_hdr_date) {
+ str_append(rtext, "\r\nDate: ");
+ str_append(rtext, http_date_create(resp->date));
+ str_append(rtext, "\r\n");
+ }
+ if (array_is_created(&resp->auth_challenges)) {
+ str_append(rtext, "WWW-Authenticate: ");
+ http_auth_create_challenges(rtext, &resp->auth_challenges);
+ str_append(rtext, "\r\n");
+ }
+ if (chunked) {
+ if (!resp->have_hdr_body_spec)
+ str_append(rtext, "Transfer-Encoding: chunked\r\n");
+ } else if (send_content_length) {
+ if (!resp->have_hdr_body_spec) {
+ str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n",
+ content_length);
+ }
+ }
+ if (!resp->have_hdr_connection) {
+ close = (close || req->req.connection_close ||
+ req->connection_close || req->conn->input_broken);
+ if (close && resp->tunnel_callback == NULL)
+ str_append(rtext, "Connection: close\r\n");
+ else if (http_server_request_version_equals(req, 1, 0))
+ str_append(rtext, "Connection: Keep-Alive\r\n");
+ }
+
+ /* Status line + implicit headers */
+ iov[0].iov_base = str_data(rtext);
+ iov[0].iov_len = str_len(rtext);
+ /* Explicit headers */
+ iov[1].iov_base = str_data(resp->headers);
+ iov[1].iov_len = str_len(resp->headers);
+ /* End of header */
+ iov[2].iov_base = "\r\n";
+ iov[2].iov_len = 2;
+
+ req->state = HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT;
+ o_stream_cork(conn->conn.output);
+
+ if (o_stream_sendv(conn->conn.output, iov, N_ELEMENTS(iov)) < 0) {
+ http_server_connection_handle_output_error(conn);
+ return -1;
+ }
+
+ e_debug(resp->event, "Sent header");
+
+ if (resp->payload_stream != NULL)
+ http_server_ostream_output_available(resp->payload_stream);
+ if (resp->payload_output != NULL) {
+ /* Non-blocking payload */
+ ret = http_server_response_send_more(resp);
+ if (ret < 0)
+ return -1;
+ } else {
+ /* No payload to send */
+ e_debug(resp->event, "No payload to send");
+ if (resp->payload_stream != NULL) {
+ ret = http_server_ostream_continue(resp->payload_stream);
+ if (ret < 0)
+ return -1;
+ }
+ conn->output_locked = FALSE;
+ ret = http_server_response_finish_payload_out(resp);
+ if (ret < 0)
+ return -1;
+ }
+
+ if (conn->conn.output != NULL && !resp->payload_corked &&
+ o_stream_uncork_flush(conn->conn.output) < 0) {
+ http_server_connection_handle_output_error(conn);
+ return -1;
+ }
+ return ret;
+}
+
+int http_server_response_send(struct http_server_response *resp)
+{
+ int ret;
+
+ T_BEGIN {
+ ret = http_server_response_send_real(resp);
+ } T_END;
+ return ret;
+}
+
+void http_server_response_get_status(struct http_server_response *resp,
+ int *status_r, const char **reason_r)
+{
+ i_assert(resp != NULL);
+ *status_r = resp->status;
+ *reason_r = resp->reason;
+}
+
+uoff_t http_server_response_get_total_size(struct http_server_response *resp)
+{
+ i_assert(resp != NULL);
+ return resp->payload_size + str_len(resp->headers);
+}
+
+void http_server_response_add_permanent_header(struct http_server_response *resp,
+ const char *key, const char *value)
+{
+ http_server_response_add_header(resp, key, value);
+
+ if (!array_is_created(&resp->perm_headers))
+ i_array_init(&resp->perm_headers, 4);
+ char *key_dup = i_strdup(key);
+ char *value_dup = i_strdup(value);
+ array_push_back(&resp->perm_headers, &key_dup);
+ array_push_back(&resp->perm_headers, &value_dup);
+}