]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: server: Implemented API for handling the incoming request payload in the...
authorStephan Bosch <stephan.bosch@dovecot.fi>
Mon, 27 Mar 2017 22:03:04 +0000 (00:03 +0200)
committerGitLab <gitlab@git.dovecot.net>
Tue, 4 Jul 2017 12:56:17 +0000 (15:56 +0300)
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.

src/lib-http/http-server-connection.c
src/lib-http/http-server-private.h
src/lib-http/http-server-request.c
src/lib-http/http-server.h
src/lib-http/test-http-payload.c
src/lib-http/test-http-server-errors.c

index ddc41648a36844a88a7ce07b6fb826eb1aed0049..cbe9c70a058596de7183e9e2214199a110b01ac0 100644 (file)
@@ -218,6 +218,9 @@ static void http_server_payload_destroyed(struct http_server_request *req)
        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) {
@@ -774,6 +777,9 @@ int http_server_connection_discard_payload(
        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);
@@ -1147,6 +1153,8 @@ http_server_connection_disconnect(struct http_server_connection *conn,
                                                 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;
@@ -1255,6 +1263,8 @@ void http_server_connection_switch_ioloop(struct http_server_connection *conn)
                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);
index 538ea6d0df52ec832383ab7b9b41a5b45f150194..193babcd1af47c165e140c18a6cdf81f4b254489 100644 (file)
@@ -3,9 +3,11 @@
 
 #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;
 
@@ -51,6 +53,15 @@ enum http_server_request_state {
  * 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;
 
@@ -128,6 +139,8 @@ struct http_server_connection {
        unsigned int request_queue_count;
 
        struct istream *incoming_payload;
+       struct http_server_payload_handler *payload_handler;
+
        struct io *io_resp_payload;
 
        char *disconnect_reason;
@@ -211,6 +224,13 @@ void http_server_request_submit_response(struct http_server_request *req);
 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
  */
index 8c8196f53e0ced2d717a84ffd2a07c47ae5bda64..5423a32ec3c141a51696999f5e64b7d696443133 100644 (file)
@@ -31,6 +31,40 @@ http_server_request_debug(struct http_server_request *req,
        }
 }
 
+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
  */
@@ -245,6 +279,11 @@ void http_server_request_submit_response(struct http_server_request *req)
 
        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:
@@ -257,11 +296,16 @@ void http_server_request_submit_response(struct http_server_request *req)
                }
                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)
@@ -520,3 +564,273 @@ http_server_request_get_payload_input(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);
+}
index 688714fb3a38e7d8a45bda87b47256cbbdcf5aaa..76dcf1f8dc55f1e51c8099720960bab6f9b4cdb6 100644 (file)
@@ -166,6 +166,43 @@ struct istream *
 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 */
index 4fe761debdabe2eeba258cf3a01b40a9ed81796c..e24274ee33081138519ffb2f6864702e76d6e621 100644 (file)
 #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;
@@ -268,10 +277,25 @@ client_handle_download_request(
 /* 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);
@@ -292,23 +316,24 @@ client_request_read_echo_more(struct client_request *creq)
                        "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
@@ -390,19 +415,35 @@ client_handle_echo_request(struct client_request *creq,
 
        } 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;
+               }
        }
 }
 
@@ -1344,6 +1385,7 @@ static void test_download_server_nonblocking(void)
        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);
@@ -1370,6 +1412,29 @@ static void test_echo_server_nonblocking(void)
        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);
@@ -1396,6 +1461,29 @@ static void test_echo_server_nonblocking_sync(void)
        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);
@@ -1422,6 +1510,7 @@ static void test_echo_server_nonblocking_partial(void)
        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);
@@ -1432,6 +1521,40 @@ static void test_echo_server_nonblocking_partial(void)
        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)
@@ -1461,6 +1584,7 @@ static void test_download_client_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);
@@ -1470,6 +1594,7 @@ static void test_download_client_partial(void)
        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);
@@ -1484,6 +1609,7 @@ static void test_download_client_nested_ioloop(void)
        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();
 }
index 2f26d5904075a3f9f688d4521e401c48d2186bba..450e365175ff192a7b0cf1c043cc7eef33361fcf 100644 (file)
@@ -426,6 +426,154 @@ static void test_hanging_response_payload(void)
        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
  */
@@ -434,6 +582,7 @@ static void (*const test_functions[])(void) = {
        test_slow_request,
        test_hanging_request_payload,
        test_hanging_response_payload,
+       test_excessive_payload_length,
        NULL
 };