http-server-response.c revision 903f0b38884375179cea63b9d0821e295c38764a
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "lib.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "str.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "array.h"
49e358eebea107aad9919dcc4bd88cee8519ba2eTimo Sirainen#include "istream.h"
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include "ostream-private.h"
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen#include "http-date.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "http-transfer.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "http-server-private.h"
03f5c621d06d6b6d77a145196c9633a7aa64dc78Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainenstruct http_server_response_payload {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct http_server_response *resp;
da2aa032ccfa8e7e4a4380ef738014549f4d2c2dTimo Sirainen struct const_iovec *iov;
da2aa032ccfa8e7e4a4380ef738014549f4d2c2dTimo Sirainen unsigned int iov_count, iov_idx;
411d6baa37f31d90730e90c4a28c43e1974bbe58Timo Sirainen size_t iov_pos;
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen};
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen/*
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen * Logging
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainenstatic inline void
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainenhttp_server_response_debug(struct http_server_response *resp,
2526d52441ef368215ab6bf04fd0356d3b09d235Timo Sirainen const char *format, ...) ATTR_FORMAT(2, 3);
2526d52441ef368215ab6bf04fd0356d3b09d235Timo Sirainen
43834f87bf431198f986e86052a4f6e558fdb07dTimo Sirainenstatic inline void
43834f87bf431198f986e86052a4f6e558fdb07dTimo Sirainenhttp_server_response_debug(struct http_server_response *resp,
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen const char *format, ...)
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen{
09801f106cd531a28b4e03ec665e44c421264560Timo Sirainen va_list args;
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen if (resp->request->server->set.debug) {
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen va_start(args, format);
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen i_debug("http-server: request %s; %u response: %s",
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody http_server_request_label(resp->request), resp->status,
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody t_strdup_vprintf(format, args));
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody va_end(args);
212a34c06ff45952c008ae9eec387ced783de6cfPhil Carmody }
10c96a244935de4add8213ba0b894178dfb889a5Timo Sirainen}
bdcb00145ad87765e3fd22d4ebc4d2c029a326b9Timo Sirainen
bdcb00145ad87765e3fd22d4ebc4d2c029a326b9Timo Sirainen/*
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen * Response
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen */
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainenstruct http_server_response *
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenhttp_server_response_create(struct http_server_request *req,
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen unsigned int status, const char *reason)
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen{
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen struct http_server_response *resp;
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen if (req->response == NULL) {
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen resp = req->response = p_new
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen (req->pool, struct http_server_response, 1);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen } else {
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen /* was already composing a response, but decided to
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen start a new one (would usually be a failure response)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen resp = req->response;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen i_assert(!resp->submitted);
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen http_server_response_free(resp);
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen memset(resp, 0, sizeof(*resp));
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen }
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen resp->request = req;
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen resp->status = status;
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen resp->reason = p_strdup(req->pool, reason);
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen resp->headers = str_new(default_pool, 256);
27a44fcfd8d19bffe0f267f20a2b5d3fe7600fddTimo Sirainen resp->date = (time_t)-1;
27a44fcfd8d19bffe0f267f20a2b5d3fe7600fddTimo Sirainen
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen return resp;
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen}
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenvoid http_server_response_free(struct http_server_response *resp)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen http_server_response_debug(resp, "Destroy");
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen i_assert(!resp->payload_blocking);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen if (resp->payload_input != NULL)
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen i_stream_unref(&resp->payload_input);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen if (resp->payload_output != NULL)
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen o_stream_unref(&resp->payload_output);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen str_free(&resp->headers);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainenvoid http_server_response_add_header(struct http_server_response *resp,
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen const char *key, const char *value)
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen{
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen i_assert(!resp->submitted);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_assert(strchr(key, '\r') == NULL && strchr(key, '\n') == NULL);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_assert(strchr(value, '\r') == NULL && strchr(value, '\n') == NULL);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen /* mark presence of special headers */
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen switch (key[0]) {
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen case 'c': case 'C':
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen if (strcasecmp(key, "Connection") == 0)
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen resp->have_hdr_connection = TRUE;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen else if (strcasecmp(key, "Content-Length") == 0)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen resp->have_hdr_body_spec = TRUE;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen break;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen case 'd': case 'D':
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (strcasecmp(key, "Date") == 0)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen resp->have_hdr_date = TRUE;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen break;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen case 't': case 'T':
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (strcasecmp(key, "Transfer-Encoding") == 0)
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen resp->have_hdr_body_spec = TRUE;
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen break;
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen }
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen str_printfa(resp->headers, "%s: %s\r\n", key, value);
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen}
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainenvoid http_server_response_update_status(struct http_server_response *resp,
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen unsigned int status,
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen const char *reason)
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen{
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen i_assert(!resp->submitted);
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen resp->status = status;
7d207b1e77a7b5e3fda640e353acfc86d261fedfTimo Sirainen /* free not called because pool is alloconly */
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen resp->reason = p_strdup(resp->request->pool, reason);
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen}
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainenvoid http_server_response_set_date(struct http_server_response *resp,
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen time_t date)
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen{
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen i_assert(!resp->submitted);
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen resp->date = date;
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen}
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainenvoid http_server_response_set_payload(struct http_server_response *resp,
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen struct istream *input)
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen{
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen int ret;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen i_assert(!resp->submitted);
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen i_assert(resp->blocking_output == NULL);
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen i_assert(resp->payload_input == NULL);
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen i_stream_ref(input);
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen resp->payload_input = input;
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen if ((ret = i_stream_get_size(input, TRUE, &resp->payload_size)) <= 0) {
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen if (ret < 0) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_error("i_stream_get_size(%s) failed: %m",
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen i_stream_get_name(input));
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen }
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen resp->payload_size = 0;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen resp->payload_chunked = TRUE;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
4106a25399703eb6cbb166dcbd5bb932cb2f7ad2Timo Sirainen resp->payload_offset = input->v_offset;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen}
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainenvoid http_server_response_set_payload_data(struct http_server_response *resp,
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainen const unsigned char *data, size_t size)
699fdc186f982f70d990820796eaa0f12133e27cTimo Sirainen{
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen struct istream *input;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen unsigned char *payload_data;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen if (size == 0)
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen return;
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen payload_data = p_malloc(resp->request->pool, size);
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen memcpy(payload_data, data, size);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen input = i_stream_create_from_data(payload_data, size);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen http_server_response_set_payload(resp, input);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen i_stream_unref(&input);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen}
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenvoid 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,
bool close)
{
if (resp->date == (time_t)-1)
resp->date = ioloop_time;
resp->close = close;
resp->submitted = TRUE;
http_server_request_submit_response(resp->request);
}
void http_server_response_submit(struct http_server_response *resp)
{
i_assert(!resp->submitted);
http_server_response_debug(resp, "Submitted");
http_server_response_do_submit(resp, FALSE);
}
void http_server_response_submit_close(struct http_server_response *resp)
{
i_assert(!resp->submitted);
http_server_response_debug(resp, "Submitted");
http_server_response_do_submit(resp, TRUE);
}
void http_server_response_submit_tunnel(struct http_server_response *resp,
http_server_tunnel_callback_t callback, void *context)
{
i_assert(!resp->submitted);
http_server_response_debug(resp, "Started tunnelling");
resp->tunnel_callback = callback;
resp->tunnel_context = context;
http_server_response_do_submit(resp, TRUE);
}
static void
http_server_response_finish_payload_out(struct http_server_response *resp)
{
struct http_server_connection *conn = resp->request->conn;
if (resp->payload_output != NULL) {
o_stream_unref(&resp->payload_output);
resp->payload_output = NULL;
}
http_server_response_debug(resp, "Finished sending payload");
conn->output_locked = FALSE;
if (resp->payload_corked)
o_stream_uncork(conn->conn.output);
o_stream_set_flush_callback(conn->conn.output,
http_server_connection_output, conn);
http_server_request_finished(resp->request);
}
static int
http_server_response_output_direct(struct http_server_response_payload *rpay)
{
struct http_server_response *resp = rpay->resp;
struct http_server_connection *conn = resp->request->conn;
struct http_server *server = resp->request->server;
struct ostream *output = resp->payload_output;
struct const_iovec *iov;
unsigned int iov_count, i;
size_t bytes_left, block_len;
ssize_t ret;
if (http_server_connection_flush(conn) < 0)
return -1;
iov = &rpay->iov[rpay->iov_idx];
iov_count = rpay->iov_count - rpay->iov_idx;
if ((ret=o_stream_sendv(output, iov, iov_count)) < 0) {
const char *error = NULL;
if (output->stream_errno != EPIPE &&
output->stream_errno != ECONNRESET) {
error = t_strdup_printf("write(%s) failed: %s",
o_stream_get_name(output),
o_stream_get_error(output));
}
http_server_connection_write_failed(conn, error);
return -1;
}
if (ret > 0) {
bytes_left = ret;
for (i = 0; i < iov_count && bytes_left > 0; i++) {
block_len = iov[i].iov_len <= bytes_left ?
iov[i].iov_len : bytes_left;
bytes_left -= block_len;
}
rpay->iov_idx += i;
if (i < iov_count) {
i_assert(iov[i].iov_len > bytes_left);
iov[i].iov_base = PTR_OFFSET
(iov[i].iov_base, iov[i].iov_len - bytes_left);
iov[i].iov_len = bytes_left;
} else {
i_assert(rpay->iov_idx == rpay->iov_count);
i_assert(server->ioloop != NULL);
io_loop_stop(server->ioloop);
}
}
return 1;
}
static int
http_server_response_output_payload(
struct http_server_response **_resp,
const struct const_iovec *iov, unsigned int iov_count)
{
struct ioloop *prev_ioloop = current_ioloop;
struct http_server_response *resp = *_resp;
struct http_server_request *req = resp->request;
struct http_server *server = req->server;
struct http_server_connection *conn = req->conn;
struct http_server_response_payload rpay;
int ret;
i_assert(req->state < HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE ||
req->state == HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT);
i_assert(resp->payload_input == NULL);
/* Discard any remaining incoming payload */
if (http_server_connection_discard_payload(conn) < 0)
return -1;
req->req.payload = NULL;
http_server_connection_ref(conn);
http_server_request_ref(req);
resp->payload_blocking = TRUE;
memset(&rpay, 0, sizeof(rpay));
rpay.resp = resp;
if (iov == NULL) {
resp->payload_direct = FALSE;
if (req->state == HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT)
http_server_response_finish_payload_out(resp);
} else {
resp->payload_direct = TRUE;
rpay.iov = i_new(struct const_iovec, iov_count);
memcpy(rpay.iov, iov, sizeof(*iov)*iov_count);
rpay.iov_count = iov_count;
}
resp->payload_size = 0;
resp->payload_chunked = TRUE;
if (req->state < HTTP_SERVER_REQUEST_STATE_SUBMITTED_RESPONSE)
http_server_response_submit(resp);
if (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED) {
/* Wait for payload data to be written */
i_assert(server->ioloop == NULL);
server->ioloop = io_loop_create();
http_server_connection_switch_ioloop(conn);
do {
if (req->state < HTTP_SERVER_REQUEST_STATE_PAYLOAD_OUT) {
http_server_response_debug(resp,
"Preparing to send blocking payload");
http_server_connection_trigger_responses(conn);
} else if (resp->payload_output != NULL) {
http_server_response_debug(resp,
"Sending blocking payload");
o_stream_unset_flush_callback(conn->conn.output);
o_stream_set_flush_callback(resp->payload_output,
http_server_response_output_direct, &rpay);
o_stream_set_flush_pending(resp->payload_output, TRUE);
} else {
http_server_response_finish_payload_out(resp);
i_assert(req->state >= HTTP_SERVER_REQUEST_STATE_FINISHED);
break;
}
io_loop_run(server->ioloop);
if (rpay.iov_count > 0 && rpay.iov_idx >= rpay.iov_count)
break;
} while (req->state < HTTP_SERVER_REQUEST_STATE_FINISHED);
io_loop_set_current(prev_ioloop);
http_server_connection_switch_ioloop(conn);
io_loop_set_current(server->ioloop);
io_loop_destroy(&server->ioloop);
}
switch (req->state) {
case HTTP_SERVER_REQUEST_STATE_FINISHED:
ret = 1;
break;
case HTTP_SERVER_REQUEST_STATE_ABORTED:
http_server_response_debug(resp,
"Request aborted while sending blocking payload");
ret = -1;
break;
default:
ret = 0;
break;
}
resp->payload_blocking = FALSE;
resp->payload_direct = FALSE;
/* callback may have messed with our pointer,
so unref using local variable */
if (!http_server_request_unref(&req))
*_resp = NULL;
http_server_connection_unref(&conn);
i_free(rpay.iov);
/* 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;
struct const_iovec iov;
i_assert(resp->blocking_output == NULL);
resp->payload_corked = TRUE;
i_assert(data != NULL);
memset(&iov, 0, sizeof(iov));
iov.iov_base = data;
iov.iov_len = size;
return http_server_response_output_payload(_resp, &iov, 1);
}
int http_server_response_finish_payload(struct http_server_response **_resp)
{
struct http_server_response *resp = *_resp;
i_assert(resp->blocking_output == NULL);
return http_server_response_output_payload(_resp, NULL, 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;
http_server_request_abort(&req,
"Aborted sending response payload");
*_resp = NULL;
}
static void
http_server_response_payload_input(struct http_server_response *resp)
{
struct http_server_connection *conn = resp->request->conn;
if (conn->io_resp_payload != NULL)
io_remove(&conn->io_resp_payload);
(void)http_server_connection_output(conn);
}
int http_server_response_send_more(struct http_server_response *resp,
const char **error_r)
{
struct http_server_connection *conn = resp->request->conn;
struct ostream *output = resp->payload_output;
off_t ret;
*error_r = NULL;
i_assert(!resp->payload_blocking);
i_assert(resp->payload_input != NULL);
i_assert(resp->payload_output != NULL);
if (conn->io_resp_payload != 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);
ret = o_stream_send_istream(output, resp->payload_input);
o_stream_set_max_buffer_size(output, (size_t)-1);
if (resp->payload_input->stream_errno != 0) {
/* we're in the middle of sending a response, so the connection
will also have to be aborted */
errno = resp->payload_input->stream_errno;
*error_r = t_strdup_printf("read(%s) failed: %m",
i_stream_get_name(resp->payload_input));
ret = -1;
} else if (output->stream_errno != 0) {
/* failed to send response */
errno = output->stream_errno;
if (errno != EPIPE && errno != ECONNRESET) {
*error_r = t_strdup_printf("write(%s) failed: %m",
o_stream_get_name(output));
}
ret = -1;
} else {
i_assert(ret >= 0);
}
if (ret < 0 || i_stream_is_eof(resp->payload_input)) {
/* finished sending */
if (ret >= 0 && !resp->payload_chunked &&
resp->payload_input->v_offset - resp->payload_offset !=
resp->payload_size) {
*error_r = t_strdup_printf(
"Input stream %s size changed unexpectedly",
i_stream_get_name(resp->payload_input));
ret = -1;
}
/* finished sending payload */
http_server_response_finish_payload_out(resp);
} else if (i_stream_get_data_size(resp->payload_input) > 0) {
/* output is blocking */
conn->output_locked = TRUE;
o_stream_set_flush_pending(output, TRUE);
//http_server_response_debug(resp, "Partially sent payload");
} else {
/* input is blocking */
conn->output_locked = TRUE;
conn->io_resp_payload = io_add_istream(resp->payload_input,
http_server_response_payload_input, resp);
}
return ret < 0 ? -1 : 0;
}
static int http_server_response_send_real(struct http_server_response *resp,
const char **error_r)
{
struct http_server_request *req = resp->request;
struct http_server_connection *conn = req->conn;
struct http_server *server = req->server;
struct ostream *output = conn->conn.output;
string_t *rtext = t_str_new(256);
struct const_iovec iov[3];
int ret = 0;
*error_r = NULL;
i_assert(!conn->output_locked);
/* 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 (resp->payload_input != NULL || resp->payload_direct) {
if (resp->payload_chunked) {
if (http_server_request_version_equals(req, 1, 0)) {
/* cannot use Transfer-Encoding */
resp->payload_output = output;
o_stream_ref(output);
/* connection close marks end of payload */
resp->close = TRUE;
} else {
if (!resp->have_hdr_body_spec)
str_append(rtext, "Transfer-Encoding: chunked\r\n");
resp->payload_output =
http_transfer_chunked_ostream_create(output);
}
} else {
/* send Content-Length if we have specified a payload,
even if it's 0 bytes. */
if (!resp->have_hdr_body_spec) {
str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n",
resp->payload_size);
}
resp->payload_output = output;
o_stream_ref(output);
}
} else if (resp->tunnel_callback == NULL && resp->status / 100 != 1
&& resp->status != 204 && resp->status != 304
&& !http_request_method_is(&req->req, "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.
*/
if (!resp->have_hdr_body_spec)
str_append(rtext, "Content-Length: 0\r\n");
}
if (!resp->have_hdr_connection) {
if (resp->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_ref(output);
o_stream_cork(output);
if (o_stream_sendv(output, iov, N_ELEMENTS(iov)) < 0) {
if (errno != EPIPE && errno != ECONNRESET) {
*error_r = t_strdup_printf("write(%s) failed: %m",
o_stream_get_name(output));
}
ret = -1;
}
if (ret >= 0) {
http_server_response_debug(resp, "Sent header");
if (resp->payload_blocking) {
/* blocking payload */
conn->output_locked = TRUE;
if (server->ioloop != NULL)
io_loop_stop(server->ioloop);
} else if (resp->payload_output != NULL) {
/* non-blocking payload */
if (http_server_response_send_more(resp, error_r) < 0)
ret = -1;
} else {
/* no payload to send */
conn->output_locked = FALSE;
http_server_response_finish_payload_out(resp);
}
}
if (!resp->payload_corked)
o_stream_uncork(output);
o_stream_unref(&output);
return ret;
}
int http_server_response_send(struct http_server_response *resp,
const char **error_r)
{
char *errstr = NULL;
int ret;
T_BEGIN {
ret = http_server_response_send_real(resp, error_r);
if (ret < 0)
errstr = i_strdup(*error_r);
} T_END;
*error_r = t_strdup(errstr);
i_free(errstr);
return ret;
}
/*
* Payload output stream
*/
struct http_server_ostream {
struct ostream_private ostream;
struct http_server_response *resp;
};
static ssize_t
http_server_ostream_sendv(struct ostream_private *stream,
const struct const_iovec *iov, unsigned int iov_count)
{
struct http_server_ostream *hsostream =
(struct http_server_ostream *)stream;
unsigned int i;
ssize_t ret;
if (http_server_response_output_payload
(&hsostream->resp, iov, iov_count) < 0) {
if (stream->parent->stream_errno != 0) {
o_stream_copy_error_from_parent(stream);
} else {
io_stream_set_error(&stream->iostream,
"HTTP connection broke while sending payload");
stream->ostream.stream_errno = EIO;
}
return -1;
}
ret = 0;
for (i = 0; i < iov_count; i++)
ret += iov[i].iov_len;
stream->ostream.offset += ret;
return ret;
}
static void http_server_ostream_close(struct iostream_private *stream,
bool close_parent ATTR_UNUSED)
{
struct http_server_ostream *hsostream =
(struct http_server_ostream *)stream;
struct ostream_private *ostream = &hsostream->ostream;
if (hsostream->resp == NULL)
return;
hsostream->resp->blocking_output = NULL;
if (http_server_response_output_payload
(&hsostream->resp, NULL, 0) < 0) {
if (ostream->parent->stream_errno != 0) {
o_stream_copy_error_from_parent(ostream);
} else {
io_stream_set_error(&ostream->iostream,
"HTTP connection broke while sending payload");
ostream->ostream.stream_errno = EIO;
}
}
hsostream->resp = NULL;
}
static void http_server_ostream_destroy(struct iostream_private *stream)
{
struct http_server_ostream *hsostream =
(struct http_server_ostream *)stream;
if (hsostream->resp != NULL) {
hsostream->resp->blocking_output = NULL;
http_server_response_abort_payload(&hsostream->resp);
}
}
struct ostream *
http_server_response_get_payload_output(struct http_server_response *resp,
bool blocking)
{
struct http_server_connection *conn = resp->request->conn;
struct http_server_ostream *hsostream;
i_assert(resp->payload_input == NULL);
i_assert(resp->blocking_output == NULL);
i_assert(blocking == TRUE); // FIXME: support non-blocking
hsostream = i_new(struct http_server_ostream, 1);
hsostream->ostream.sendv = http_server_ostream_sendv;
hsostream->ostream.iostream.close = http_server_ostream_close;
hsostream->ostream.iostream.destroy = http_server_ostream_destroy;
hsostream->resp = resp;
resp->blocking_output =
o_stream_create(&hsostream->ostream, conn->conn.output, -1);
return resp->blocking_output;
}
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);
}