From 46344b204a2ae6d257ea8de31967f7eaad015c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 19 Apr 2017 14:02:18 +0300 Subject: [PATCH] MXS-1220: Properly handle request with data The data was not processed correctly and instead an error was sent to the client as soon as the request arrived. Created a class that somewhat abstracts the internals of the client request processing. --- server/core/admin.cc | 145 ++++++++++++++++++++++++++-------- server/core/httprequest.cc | 1 + server/core/maxscale/admin.hh | 42 +++++++++- 3 files changed, 153 insertions(+), 35 deletions(-) diff --git a/server/core/admin.cc b/server/core/admin.cc index 8a9853f0a..692278de2 100644 --- a/server/core/admin.cc +++ b/server/core/admin.cc @@ -26,52 +26,63 @@ #include "maxscale/admin.hh" #include "maxscale/resource.hh" +#include "maxscale/http.hh" static struct MHD_Daemon* http_daemon = NULL; -int handle_client(void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) - +int kv_iter(void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) { - const char *admin_user = config_get_global_options()->admin_user; - const char *admin_pw = config_get_global_options()->admin_password; - bool admin_auth = config_get_global_options()->admin_auth; + size_t* rval = (size_t*) cls; - char* pw = NULL; - char* user = MHD_basic_auth_get_username_password(connection, &pw); - - if (admin_auth && (!user || !pw || strcmp(user, admin_user) || strcmp(pw, admin_pw))) + if (strcmp(key, "Content-Length") == 0) { - static char error_resp[] = "Access denied\r\n"; - struct MHD_Response *resp; + *rval = atoi(value); + return MHD_NO; + } - resp = MHD_create_response_from_buffer(sizeof (error_resp) - 1, error_resp, - MHD_RESPMEM_PERSISTENT); + return MHD_YES; +} - MHD_queue_basic_auth_fail_response(connection, "maxscale", resp); - MHD_destroy_response(resp); +static inline size_t request_data_length(struct MHD_Connection *connection) +{ + size_t rval = 0; + MHD_get_connection_values(connection, MHD_HEADER_KIND, kv_iter, &rval); + return rval; +} + +static bool modifies_data(struct MHD_Connection *connection, string method) +{ + return (method == "POST" || method == "PUT" || method == "PATCH") && + request_data_length(connection); +} + +int Client::process(string url, string method, const char* upload_data, size_t *upload_size) +{ + json_t* json = NULL; + + if (*upload_size) + { + m_data += upload_data; + *upload_size = 0; return MHD_YES; } - string verb(method); - json_t* json = NULL; + json_error_t err = {}; - if (verb == "POST" || verb == "PUT" || verb == "PATCH") + if (m_data.length() && + (json = json_loadb(m_data.c_str(), m_data.size(), 0, &err)) == NULL) { - json_error_t err = {}; - if ((json = json_loadb(upload_data, *upload_data_size, 0, &err)) == NULL) - { - return MHD_NO; - } + struct MHD_Response *response = + MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT); + MHD_queue_response(m_connection, MHD_HTTP_BAD_REQUEST, response); + MHD_destroy_response(response); + return MHD_YES; } - HttpRequest request(connection, url, method, json); + HttpRequest request(m_connection, url, method, json); HttpResponse reply = resource_handle_request(request); string data; @@ -98,9 +109,76 @@ int handle_client(void *cls, // This ETag is the base64 encoding of `not-yet-implemented` MHD_add_response_header(response, "ETag", "bm90LXlldC1pbXBsZW1lbnRlZAo"); - MHD_queue_response(connection, reply.get_code(), response); + int rval = MHD_queue_response(m_connection, reply.get_code(), response); MHD_destroy_response(response); - return MHD_YES; + + return rval; +} + +void close_client(void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + Client* client = static_cast(*con_cls); + delete client; +} + +bool do_auth(struct MHD_Connection *connection) +{ + const char *admin_user = config_get_global_options()->admin_user; + const char *admin_pw = config_get_global_options()->admin_password; + bool admin_auth = config_get_global_options()->admin_auth; + + char* pw = NULL; + char* user = MHD_basic_auth_get_username_password(connection, &pw); + bool rval = true; + + if (admin_auth && (!user || !pw || strcmp(user, admin_user) || strcmp(pw, admin_pw))) + { + rval = false; + static char error_resp[] = "Access denied\r\n"; + struct MHD_Response *resp = + MHD_create_response_from_buffer(sizeof(error_resp) - 1, error_resp, + MHD_RESPMEM_PERSISTENT); + + MHD_queue_basic_auth_fail_response(connection, "maxscale", resp); + MHD_destroy_response(resp); + } + + return rval; +} + +int handle_client(void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) + +{ + if (!do_auth(connection)) + { + return MHD_YES; + } + + if (*con_cls == NULL) + { + if ((*con_cls = new (std::nothrow) Client(connection)) == NULL) + { + return MHD_NO; + } + else if (modifies_data(connection, method)) + { + // The first call doesn't have any data + return MHD_YES; + } + } + + Client* client = static_cast(*con_cls); + return client->process(url, method, upload_data, upload_data_size); } bool mxs_admin_init() @@ -109,6 +187,7 @@ bool mxs_admin_init() config_get_global_options()->admin_port, NULL, NULL, handle_client, NULL, + MHD_OPTION_NOTIFY_COMPLETED, close_client, NULL, MHD_OPTION_END); return http_daemon != NULL; diff --git a/server/core/httprequest.cc b/server/core/httprequest.cc index ac4a7ef6b..c08a32b7a 100644 --- a/server/core/httprequest.cc +++ b/server/core/httprequest.cc @@ -84,6 +84,7 @@ static void process_uri(string& uri, deque& uri_parts) HttpRequest::HttpRequest(struct MHD_Connection *connection, string url, string method, json_t* data): m_json(data), + m_json_string(data ? mxs::json_dump(data, 0) : ""), m_resource(url), m_verb(method), m_connection(connection) diff --git a/server/core/maxscale/admin.hh b/server/core/maxscale/admin.hh index 73a9f35e8..a31006524 100644 --- a/server/core/maxscale/admin.hh +++ b/server/core/maxscale/admin.hh @@ -15,11 +15,49 @@ #include #include -#include +#include #include -#include "http.hh" +using std::string; + +class Client +{ +public: + + /** + * @brief Create a new client + * + * @param connection The connection handle for this client + */ + Client(struct MHD_Connection *connection): + m_connection(connection) + { + } + + ~Client() + { + } + + /** + * @brief Process a client request + * + * This function can be called multiple times if a PUT/POST/PATCH + * uploads a large amount of data. + * + * @param url Requested URL + * @param method Request method + * @param data Pointer to request data + * @param size Size of request data + * + * @return MHD_YES on success, MHD_NO on error + */ + int process(string url, string method, const char* data, size_t *size); + +private: + struct MHD_Connection* m_connection; /**< Connection handle */ + string m_data; /**< Uploaded data */ +}; /** * @brief Start the administrative interface