It allows forwarding the incoming payload to an output stream (e.g. iostream-temp) or to a buffer.
The maximum size of the payload is configurable. The client will get a 413 error if the maximum is exceeded.
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) {
i_assert(conn->conn.io == NULL);
i_assert(server->ioloop == NULL);
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+
/* destroy payload wrapper early to advance state */
if (conn->incoming_payload != NULL) {
i_stream_unref(&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;
conn->to_idle = io_loop_move_timeout(&conn->to_idle);
if (conn->io_resp_payload != NULL)
conn->io_resp_payload = io_loop_move_io(&conn->io_resp_payload);
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_switch_ioloop(conn->payload_handler);
if (conn->incoming_payload != NULL)
i_stream_switch_ioloop(conn->incoming_payload);
connection_switch_ioloop(&conn->conn);
#include "connection.h"
+#include "iostream-pump.h"
#include "http-server.h"
#include "llist.h"
+struct http_server_payload_handler;
struct http_server_request;
struct http_server_connection;
* Objects
*/
+struct http_server_payload_handler {
+ struct http_server_request *req;
+
+ void (*switch_ioloop)(struct http_server_payload_handler *handler);
+ void (*destroy)(struct http_server_payload_handler *handler);
+
+ bool in_callback:1;
+};
+
struct http_server_response {
struct http_server_request *request;
unsigned int request_queue_count;
struct istream *incoming_payload;
+ struct http_server_payload_handler *payload_handler;
+
struct io *io_resp_payload;
char *disconnect_reason;
void http_server_request_ready_to_respond(struct http_server_request *req);
void http_server_request_finished(struct http_server_request *req);
+/* payload handler */
+
+void http_server_payload_handler_destroy(
+ struct http_server_payload_handler **_handler);
+void http_server_payload_handler_switch_ioloop(
+ struct http_server_payload_handler *handler);
+
/*
* connection
*/
}
}
+static inline void
+http_server_request_error(struct http_server_request *req,
+ const char *format, ...) ATTR_FORMAT(2, 3);
+
+static inline void
+http_server_request_error(struct http_server_request *req,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ i_error("http-server: request %s: %s",
+ http_server_request_label(req),
+ t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
+static inline void
+http_server_request_client_error(struct http_server_request *req,
+ const char *format, ...) ATTR_FORMAT(2, 3);
+
+static inline void
+http_server_request_client_error(struct http_server_request *req,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ i_info("http-server: request %s: %s",
+ http_server_request_label(req),
+ t_strdup_vprintf(format, args));
+ va_end(args);
+}
+
/*
* Request
*/
i_assert(conn != NULL && req->response != NULL && req->response->submitted);
+ http_server_request_ref(req);
+
+ if (conn->payload_handler != NULL && conn->payload_handler->req == req)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+
switch (req->state) {
case HTTP_SERVER_REQUEST_STATE_NEW:
case HTTP_SERVER_REQUEST_STATE_QUEUED:
}
http_server_request_ready_to_respond(req);
break;
+ case HTTP_SERVER_REQUEST_STATE_READY_TO_RESPOND:
+ http_server_connection_trigger_responses(req->conn);
+ break;
case HTTP_SERVER_REQUEST_STATE_ABORTED:
break;
default:
i_unreached();
}
+
+ http_server_request_unref(&req);
}
void http_server_request_finished(struct http_server_request *req)
i_stream_unref(&req->req.payload);
return req->payload_input;
}
+
+/*
+ * Payload handling
+ */
+
+static void
+http_server_payload_handler_init(
+ struct http_server_payload_handler *handler ,
+ struct http_server_request *req)
+{
+ struct http_server_connection *conn = req->conn;
+
+ i_assert(conn->payload_handler == NULL);
+ i_assert(conn->in_req_callback);
+
+ conn->payload_handler = handler;
+
+ handler->req = req;
+}
+
+void http_server_payload_handler_destroy(
+ struct http_server_payload_handler **_handler)
+{
+ struct http_server_payload_handler *handler = *_handler;
+ struct http_server_connection *conn = handler->req->conn;
+
+ if (handler->in_callback) {
+ /* don't destroy handler while in callback */
+ return;
+ }
+
+ *_handler = NULL;
+ i_assert(conn->payload_handler == NULL);
+
+ if (handler->destroy != NULL)
+ handler->destroy(handler);
+}
+
+void http_server_payload_handler_switch_ioloop(
+ struct http_server_payload_handler *handler)
+{
+ if (handler->switch_ioloop != NULL)
+ handler->switch_ioloop(handler);
+}
+
+/* pump-based */
+
+struct http_server_payload_handler_pump {
+ struct http_server_payload_handler handler;
+
+ struct iostream_pump *pump;
+
+ void (*callback)(void *);
+ void *context;
+};
+
+static void
+payload_handler_pump_destroy(
+ struct http_server_payload_handler *handler)
+{
+ struct http_server_payload_handler_pump *phandler =
+ (struct http_server_payload_handler_pump *)handler;
+
+ iostream_pump_unref(&phandler->pump);
+}
+
+static void
+payload_handler_pump_switch_ioloop(
+ struct http_server_payload_handler *handler)
+{
+ struct http_server_payload_handler_pump *phandler =
+ (struct http_server_payload_handler_pump *)handler;
+
+ iostream_pump_switch_ioloop(phandler->pump);
+}
+
+static void
+payload_handler_pump_callback(bool success,
+ struct http_server_payload_handler_pump *phandler)
+{
+ struct http_server_payload_handler *handler = &phandler->handler;
+ struct http_server_request *req = handler->req;
+ struct http_server_connection *conn = req->conn;
+ struct istream *input = iostream_pump_get_input(phandler->pump);
+ struct ostream *output = iostream_pump_get_output(phandler->pump);
+
+ if (success) {
+ if (!i_stream_is_eof(conn->incoming_payload)) {
+ http_server_request_fail_close(req,
+ 413, "Payload Too Large");
+ } else {
+ unsigned int old_refcount = req->refcount;
+
+ handler->in_callback = TRUE;
+ phandler->callback(phandler->context);
+ req->callback_refcount += req->refcount - old_refcount;
+ handler->in_callback = FALSE;
+
+ i_assert(req->callback_refcount > 0 ||
+ (req->response != NULL && req->response->submitted));
+ }
+ } else if (input->stream_errno != 0) {
+ http_server_request_client_error(req,
+ "iostream_pump: read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ http_server_request_fail_close(req,
+ 400, "Bad Request");
+ } else {
+ if (output->stream_errno != 0) {
+ http_server_request_error(req,
+ "iostream_pump: write(%s) failed: %s",
+ o_stream_get_name(output),
+ o_stream_get_error(output));
+ }
+ http_server_request_fail_close(req,
+ 500, "Internal Server Error");
+ }
+
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+}
+
+#undef http_server_request_forward_payload
+void http_server_request_forward_payload(struct http_server_request *req,
+ struct ostream *output, uoff_t max_size,
+ void (*callback)(void *), void *context)
+{
+ struct http_server_connection *conn = req->conn;
+ struct istream *input = conn->incoming_payload;
+ struct http_server_payload_handler_pump *phandler;
+ uoff_t payload_size;
+ int ret;
+
+ i_assert(req->req.payload != NULL);
+
+ if (max_size == (uoff_t)-1) {
+ i_stream_ref(input);
+ } else {
+ if ((ret = i_stream_get_size(input, TRUE, &payload_size)) != 0) {
+ if (ret < 0) {
+ http_server_request_error(req,
+ "i_stream_get_size(%s) failed: %s",
+ i_stream_get_name(input), i_stream_get_error(input));
+ http_server_request_fail_close(req,
+ 500, "Internal Server Error");
+ return;
+ }
+ if (payload_size > max_size) {
+ http_server_request_fail_close(req,
+ 413, "Payload Too Large");
+ return;
+ }
+ }
+ input = i_stream_create_limit(input, max_size);
+ }
+
+ phandler = p_new(req->pool, struct http_server_payload_handler_pump, 1);
+ http_server_payload_handler_init(&phandler->handler, req);
+ phandler->handler.switch_ioloop = payload_handler_pump_switch_ioloop;
+ phandler->handler.destroy = payload_handler_pump_destroy;
+ phandler->callback = callback;
+ phandler->context = context;
+
+ phandler->pump = iostream_pump_create(input, output);
+ iostream_pump_set_completion_callback(phandler->pump,
+ payload_handler_pump_callback, phandler);
+ iostream_pump_start(phandler->pump);
+ i_stream_unref(&input);
+}
+
+#undef http_server_request_buffer_payload
+void http_server_request_buffer_payload(struct http_server_request *req,
+ buffer_t *buffer, uoff_t max_size,
+ void (*callback)(void *), void *context)
+{
+ struct ostream *output;
+
+ output = o_stream_create_buffer(buffer);
+ http_server_request_forward_payload(req,
+ output, max_size, callback, context);
+ o_stream_unref(&output);
+}
+
+/* raw */
+
+struct http_server_payload_handler_raw {
+ struct http_server_payload_handler handler;
+
+ struct io *io;
+
+ void (*callback)(void *context);
+ void *context;
+};
+
+static void
+payload_handler_raw_destroy(
+ struct http_server_payload_handler *handler)
+{
+ struct http_server_payload_handler_raw *rhandler =
+ (struct http_server_payload_handler_raw *)handler;
+
+ io_remove(&rhandler->io);
+}
+
+static void
+payload_handler_raw_switch_ioloop(
+ struct http_server_payload_handler *handler)
+{
+ struct http_server_payload_handler_raw *rhandler =
+ (struct http_server_payload_handler_raw *)handler;
+
+ rhandler->io = io_loop_move_io(&rhandler->io);
+}
+
+static void
+payload_handler_raw_input(
+ struct http_server_payload_handler_raw *rhandler)
+{
+ struct http_server_payload_handler *handler = &rhandler->handler;
+ struct http_server_request *req = handler->req;
+ struct http_server_connection *conn = req->conn;
+ struct istream *input = conn->incoming_payload;
+ unsigned int old_refcount = req->refcount;
+
+ handler->in_callback = TRUE;
+ rhandler->callback(rhandler->context);
+ req->callback_refcount += req->refcount - old_refcount;
+ handler->in_callback = FALSE;
+
+ if (input != NULL && input->stream_errno != 0) {
+ if (req->response == NULL) {
+ http_server_request_client_error(req,
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ http_server_request_fail_close(req,
+ 400, "Bad Request");
+ }
+ } else if (input == NULL ||
+ !i_stream_have_bytes_left(input)) {
+ i_assert(req->callback_refcount > 0 ||
+ (req->response != NULL && req->response->submitted));
+ } else {
+ return;
+ }
+
+ if (conn->payload_handler != NULL)
+ http_server_payload_handler_destroy(&conn->payload_handler);
+
+}
+
+#undef http_server_request_handle_payload
+void http_server_request_handle_payload(struct http_server_request *req,
+ void (*callback)(void *context), void *context)
+{
+ struct http_server_payload_handler_raw *rhandler;
+ struct http_server_connection *conn = req->conn;
+
+ rhandler = p_new(req->pool, struct http_server_payload_handler_raw, 1);
+ http_server_payload_handler_init(&rhandler->handler, req);
+ rhandler->handler.switch_ioloop = payload_handler_raw_switch_ioloop;
+ rhandler->handler.destroy = payload_handler_raw_destroy;
+ rhandler->callback = callback;
+ rhandler->context = context;
+
+ rhandler->io = io_add_istream(conn->incoming_payload,
+ payload_handler_raw_input, rhandler);
+ i_stream_set_input_pending(conn->incoming_payload, TRUE);
+}
http_server_request_get_payload_input(struct http_server_request *req,
bool blocking);
+/* Forward the incoming request payload to the provided output stream in the
+ background. Calls the provided callback once the payload was forwarded
+ successfully. If forwarding fails, the client is presented with an
+ appropriate error. If the payload size exceeds max_size, the client will
+ get a 413 error. Before the callback finishes, the application must either
+ have added a reference to the request or have submitted a response. */
+void http_server_request_forward_payload(struct http_server_request *req,
+ struct ostream *output, uoff_t max_size,
+ void (*callback)(void *), void *context);
+#define http_server_request_forward_payload(req, \
+ output, max_size, callback, context) \
+ http_server_request_forward_payload(req, output, max_size, \
+ (void(*)(void*))callback, context + \
+ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
+/* Forward the incoming request payload to the provided buffer in the
+ background. Behaves identical to http_server_request_forward_payload()
+ otherwise. */
+void http_server_request_buffer_payload(struct http_server_request *req,
+ buffer_t *buffer, uoff_t max_size,
+ void (*callback)(void *), void *context);
+#define http_server_request_buffer_payload(req, \
+ buffer, max_size, callback, context) \
+ http_server_request_buffer_payload(req, buffer, max_size, \
+ (void(*)(void*))callback, context + \
+ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
+/* Handle the incoming request payload by calling the callback each time
+ more data is available. Payload reading automatically finishes when the
+ request payload is fully read. Before the final callback finishes, the
+ application must either have added a reference to the request or have
+ submitted a response. */
+void http_server_request_handle_payload(struct http_server_request *req,
+ void (*callback)(void *context), void *context);
+#define http_server_request_handle_payload(req, callback, context) \
+ http_server_request_handle_payload(req,\
+ (void(*)(void*))callback, context + \
+ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
+
/* Get the authentication credentials provided in this request. Returns 0 if
the Authorization header is absent, returns -1 when that header cannot be
parsed, and returns 1 otherwise */
#include <unistd.h>
#include <dirent.h>
+enum payload_handling {
+ PAYLOAD_HANDLING_LOW_LEVEL,
+ PAYLOAD_HANDLING_FORWARD,
+ PAYLOAD_HANDLING_HANDLER,
+};
+
static bool debug = FALSE;
static bool blocking = FALSE;
+static enum payload_handling server_payload_handling =
+ PAYLOAD_HANDLING_LOW_LEVEL;
+
static bool request_100_continue = FALSE;
static size_t read_server_partial = 0;
static size_t read_client_partial = 0;
/* location: /echo */
static void
-client_request_read_echo_more(struct client_request *creq)
+client_request_finish_payload_in(struct client_request *creq)
{
struct http_server_response *resp;
struct istream *payload_input;
+
+ payload_input = iostream_temp_finish(&creq->payload_output, 4096);
+
+ resp = http_server_response_create
+ (creq->server_req, 200, "OK");
+ http_server_response_add_header(resp, "Content-Type", "text/plain");
+ http_server_response_set_payload(resp, payload_input);
+ http_server_response_submit(resp);
+
+ i_stream_unref(&payload_input);
+}
+
+static void
+client_request_read_echo(struct client_request *creq)
+{
enum ostream_send_istream_result res;
o_stream_set_max_buffer_size(creq->payload_output, IO_BLOCK_SIZE);
"Failed to write all echo payload [%s]", creq->path);
}
- io_remove(&creq->io);
+ client_request_finish_payload_in(creq);
i_stream_unref(&creq->payload_input);
+}
+
+static void
+client_request_read_echo_more(struct client_request *creq)
+{
+ client_request_read_echo(creq);
+
+ if (creq->payload_input != NULL)
+ return;
+
+ io_remove(&creq->io);
if (debug) {
i_debug("test server: echo: "
"finished receiving payload for %s", creq->path);
}
-
- payload_input = iostream_temp_finish(&creq->payload_output, 4096);
-
- resp = http_server_response_create
- (creq->server_req, 200, "OK");
- http_server_response_add_header(resp, "Content-Type", "text/plain");
- http_server_response_set_payload(resp, payload_input);
- http_server_response_submit(resp);
-
- i_stream_unref(&payload_input);
}
static void
} else {
creq->payload_output = payload_output;
- creq->payload_input =
- http_server_request_get_payload_input(req, FALSE);
- if (read_server_partial > 0) {
- struct istream *partial =
- i_stream_create_limit(creq->payload_input, read_server_partial);
- i_stream_unref(&creq->payload_input);
- creq->payload_input = partial;
- }
+ switch (server_payload_handling) {
+ case PAYLOAD_HANDLING_LOW_LEVEL:
+ creq->payload_input =
+ http_server_request_get_payload_input(req, FALSE);
+
+ if (read_server_partial > 0) {
+ struct istream *partial =
+ i_stream_create_limit(creq->payload_input, read_server_partial);
+ i_stream_unref(&creq->payload_input);
+ creq->payload_input = partial;
+ }
- creq->io = io_add_istream(creq->payload_input,
- client_request_read_echo_more, creq);
- client_request_read_echo_more(creq);
+ creq->io = io_add_istream(creq->payload_input,
+ client_request_read_echo_more, creq);
+ client_request_read_echo_more(creq);
+ break;
+ case PAYLOAD_HANDLING_FORWARD:
+ http_server_request_forward_payload(req,
+ payload_output, (size_t)-1,
+ client_request_finish_payload_in, creq);
+ break;
+ case PAYLOAD_HANDLING_HANDLER:
+ creq->payload_input =
+ http_server_request_get_payload_input(req, FALSE);
+ http_server_request_handle_payload(req,
+ client_request_read_echo, creq);
+ break;
+ }
}
}
request_100_continue = FALSE;
read_server_partial = 0;
client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
test_run_sequential(test_client_download);
test_run_pipeline(test_client_download);
test_run_parallel(test_client_download);
request_100_continue = FALSE;
read_server_partial = 0;
client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+
+ test_begin("http payload echo (server non-blocking; low-level)");
+ blocking = FALSE;
+ request_100_continue = FALSE;
+ read_server_partial = 0;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_LOW_LEVEL;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+
+ test_begin("http payload echo (server non-blocking; handler)");
+ blocking = FALSE;
+ request_100_continue = FALSE;
+ read_server_partial = 0;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_HANDLER;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
request_100_continue = TRUE;
read_server_partial = 0;
client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+
+ test_begin("http payload echo (server non-blocking; 100-continue; low-level)");
+ blocking = FALSE;
+ request_100_continue = TRUE;
+ read_server_partial = 0;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_LOW_LEVEL;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+
+ test_begin("http payload echo (server non-blocking; 100-continue; handler)");
+ blocking = FALSE;
+ request_100_continue = TRUE;
+ read_server_partial = 0;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_HANDLER;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
request_100_continue = FALSE;
read_server_partial = 1024;
client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
test_run_sequential(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_run_pipeline(test_client_echo);
test_run_parallel(test_client_echo);
test_end();
+
+ test_begin("http payload echo (server non-blocking; partial short; low-level)");
+ blocking = FALSE;
+ request_100_continue = FALSE;
+ read_server_partial = 1024;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_LOW_LEVEL;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+ test_begin("http payload echo (server non-blocking; partial long; low-level)");
+ read_server_partial = IO_BLOCK_SIZE + 1024;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+
+ test_begin("http payload echo (server non-blocking; partial short; handler)");
+ blocking = FALSE;
+ request_100_continue = FALSE;
+ read_server_partial = 1024;
+ client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_HANDLER;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
+ test_begin("http payload echo (server non-blocking; partial long; handler)");
+ read_server_partial = IO_BLOCK_SIZE + 1024;
+ test_run_sequential(test_client_echo);
+ test_run_pipeline(test_client_echo);
+ test_run_parallel(test_client_echo);
+ test_end();
}
static void test_echo_server_blocking_partial(void)
read_server_partial = 0;
read_client_partial = 1024;
client_ioloop_nesting = 0;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
test_run_sequential(test_client_download);
test_run_pipeline(test_client_download);
test_run_parallel(test_client_download);
request_100_continue = FALSE;
read_server_partial = 0;
read_client_partial = IO_BLOCK_SIZE + 1024;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
test_run_sequential(test_client_download);
test_run_pipeline(test_client_download);
test_run_parallel(test_client_download);
read_server_partial = 0;
read_client_partial = 0;
client_ioloop_nesting = 10;
+ server_payload_handling = PAYLOAD_HANDLING_FORWARD;
test_run_parallel(test_client_echo);
test_end();
}
test_end();
}
+/*
+ * Excessive payload length
+ */
+
+/* client */
+
+static void
+test_excessive_payload_length_connected1(struct client_connection *conn)
+{
+ (void)o_stream_send_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Content-Length: 150\r\n"
+ "\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n");
+}
+
+static void
+test_client_excessive_payload_length1(unsigned int index)
+{
+ test_client_connected = test_excessive_payload_length_connected1;
+ test_client_run(index);
+}
+
+static void
+test_excessive_payload_length_connected2(struct client_connection *conn)
+{
+ (void)o_stream_send_str(conn->conn.output,
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "32\r\n"
+ "Too long\r\nToo long\r\nToo long\r\nToo long\r\nToo long\r\n"
+ "\r\n"
+ "0\r\n"
+ "\r\n");
+}
+
+static void
+test_client_excessive_payload_length2(unsigned int index)
+{
+ test_client_connected = test_excessive_payload_length_connected2;
+ test_client_run(index);
+}
+
+
+/* server */
+
+struct _excessive_payload_length {
+ struct http_server_request *req;
+ buffer_t *buffer;
+ bool serviced:1;
+};
+
+static void
+test_server_excessive_payload_length_destroyed(
+ struct _excessive_payload_length *ctx)
+{
+ struct http_server_response *resp;
+ const char *reason;
+ int status;
+
+ resp = http_server_request_get_response(ctx->req);
+ test_assert(resp != NULL);
+ if (resp != NULL) {
+ http_server_response_get_status(resp, &status, &reason);
+ test_assert(status == 413);
+ }
+
+ test_assert(!ctx->serviced);
+ buffer_free(&ctx->buffer);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_excessive_payload_length_finished(
+ struct _excessive_payload_length *ctx)
+{
+ struct http_server_response *resp;
+
+ resp = http_server_response_create(ctx->req, 200, "OK");
+ http_server_response_submit(resp);
+ ctx->serviced = TRUE;
+}
+
+static void
+test_server_excessive_payload_length_request(
+ struct http_server_request *req)
+{
+ const struct http_request *hreq =
+ http_server_request_get(req);
+ struct _excessive_payload_length *ctx;
+
+ if (debug) {
+ i_debug("REQUEST: %s %s HTTP/%u.%u",
+ hreq->method, hreq->target_raw,
+ hreq->version_major, hreq->version_minor);
+ }
+
+ ctx = i_new(struct _excessive_payload_length, 1);
+ ctx->req = req;
+ ctx->buffer = buffer_create_dynamic(default_pool, 128);
+
+ http_server_request_set_destroy_callback(req,
+ test_server_excessive_payload_length_destroyed, ctx);
+ http_server_request_buffer_payload(req, ctx->buffer, 128,
+ test_server_excessive_payload_length_finished, ctx);
+}
+
+static void test_server_excessive_payload_length
+(const struct http_server_settings *server_set)
+{
+ test_server_request = test_server_excessive_payload_length_request;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_excessive_payload_length(void)
+{
+ struct http_server_settings http_server_set;
+
+ test_server_defaults(&http_server_set);
+ http_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("excessive payload length (length)");
+ test_run_client_server(&http_server_set,
+ test_server_excessive_payload_length,
+ test_client_excessive_payload_length1, 1);
+ test_end();
+
+ test_begin("excessive payload length (chunked)");
+ test_run_client_server(&http_server_set,
+ test_server_excessive_payload_length,
+ test_client_excessive_payload_length2, 1);
+ test_end();
+}
+
/*
* All tests
*/
test_slow_request,
test_hanging_request_payload,
test_hanging_response_payload,
+ test_excessive_payload_length,
NULL
};