From e248178349be2dfaea7ce857cf93c5e08ffb3258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 17 Apr 2017 05:22:01 +0300 Subject: [PATCH] MXS-1220: Split requested resource into parts When a client requests a resource, the HttpRequest class now splits the requested resource into parts. This should help with the resource validation and navigation. Added test that checks that the resources are correctly split into the correct number of arguments and that the argument contents are correct. --- server/core/httprequest.cc | 169 ++++++++++++++++++---------- server/core/maxscale/httprequest.hh | 28 +++-- server/core/test/testhttp.cc | 68 +++++++++++ 3 files changed, 200 insertions(+), 65 deletions(-) diff --git a/server/core/httprequest.cc b/server/core/httprequest.cc index f644d526d..30c495bf4 100644 --- a/server/core/httprequest.cc +++ b/server/core/httprequest.cc @@ -50,6 +50,107 @@ static inline string& trim(string& str) } } +static void process_uri(string& uri, deque& uri_parts) +{ + /** Clean up trailing slashes in requested resource */ + while (uri.length() > 1 && *uri.rbegin() == '/') + { + uri.erase(uri.find_last_of("/")); + } + + string my_uri = uri; + + while (my_uri.length() && *my_uri.begin() == '/') + { + my_uri.erase(my_uri.begin()); + } + + if (my_uri.length() == 0) + { + /** Special handling for the / resource */ + uri_parts.push_back(""); + } + else + { + while (my_uri.length() > 0) + { + size_t pos = my_uri.find("/"); + string part = pos == string::npos ? my_uri : my_uri.substr(0, pos); + my_uri.erase(0, pos == string::npos ? pos : pos + 1); + uri_parts.push_back(part); + } + } +} + +static bool process_options(string& uri, map& options) +{ + size_t pos = uri.find("?"); + + if (pos != string::npos) + { + string optionstr = uri.substr(pos + 1); + uri.erase(pos); + + char buf[optionstr.size() + 1]; + strcpy(buf, optionstr.c_str()); + char* saved; + char* tok = strtok_r(buf, ",", &saved); + + while (tok && *tok) + { + string opt(tok); + pos = opt.find("="); + + if (pos != string::npos) + { + string key = opt.substr(0, pos - 1); + string value = opt.substr(pos + 1); + options[key] = value; + } + else + { + /** Invalid option */ + return false; + } + + tok = strtok_r(NULL, ",", &saved); + } + } + + return true; +} + +static bool process_headers(string& data, map& headers) +{ + size_t pos; + + while ((pos = data.find("\r\n")) != string::npos) + { + string header_line = data.substr(0, pos); + data.erase(0, pos + 2); + + if (header_line.length() == 0) + { + /** End of headers */ + break; + } + + if ((pos = header_line.find(":")) != string::npos) + { + string key = header_line.substr(0, pos); + header_line.erase(0, pos + 1); + headers[key] = mxs::trim(header_line); + } + else + { + /** Invalid header */ + return false; + } + } + + return true; +} + HttpRequest* HttpRequest::parse(string data) { size_t pos = data.find("\r\n"); @@ -80,76 +181,29 @@ HttpRequest* HttpRequest::parse(string data) string uri = request_line.substr(0, pos); request_line.erase(0, pos + 1); - /** Process request options */ - pos = uri.find("?"); map options; - if (pos != string::npos) + /** Process request options */ + if (!process_options(uri, options)) { - string optionstr = uri.substr(pos + 1); - uri.erase(pos); - - char buf[optionstr.size() + 1]; - strcpy(buf, optionstr.c_str()); - char* saved; - char* tok = strtok_r(buf, ",", &saved); - - while (tok && *tok) - { - string opt(tok); - pos = opt.find("="); - - if (pos != string::npos) - { - string key = opt.substr(0, pos - 1); - string value = opt.substr(pos + 1); - options[key] = value; - } - else - { - /** Invalid option */ - return NULL; - } - - tok = strtok_r(NULL, ",", &saved); - } + return NULL; } - /** Clean up trailing slashes in requested resource */ - while (uri.length() > 1 && *uri.rbegin() == '/') - { - pos = uri.find_last_of("/"); - uri.erase(pos); - } + /** Split the URI into separate parts */ + deque uri_parts; + process_uri(uri, uri_parts); pos = request_line.find("\r\n"); string http_version = request_line.substr(0, pos); request_line.erase(0, pos + 2); + map headers; - while ((pos = data.find("\r\n")) != string::npos) + /** Process request headers */ + if (!process_headers(data, headers)) { - string header_line = data.substr(0, pos); - data.erase(0, pos + 2); - - if (header_line.length() == 0) - { - /** End of headers */ - break; - } - - if ((pos = header_line.find(":")) != string::npos) - { - string key = header_line.substr(0, pos); - header_line.erase(0, pos + 1); - headers[key] = mxs::trim(header_line); - } - else - { - /** Invalid header */ - return NULL; - } + return NULL; } /** @@ -192,6 +246,7 @@ HttpRequest* HttpRequest::parse(string data) request->m_json.reset(body); request->m_json_string = data; request->m_resource = uri; + request->m_resource_parts = uri_parts; request->m_verb = verb_value; } diff --git a/server/core/maxscale/httprequest.hh b/server/core/maxscale/httprequest.hh index b1061309b..52aaa5a60 100644 --- a/server/core/maxscale/httprequest.hh +++ b/server/core/maxscale/httprequest.hh @@ -14,8 +14,9 @@ #include -#include +#include #include +#include #include #include @@ -26,6 +27,7 @@ using std::shared_ptr; using std::string; using std::map; +using std::deque; using mxs::Closer; class HttpRequest; @@ -132,22 +134,32 @@ public: /** * @brief Get request resource * - * @return The request resoure + * @return The request resource */ const string& get_resource() const { return m_resource; } + /** + * @brief Get request resource parts + * + * @return The request resource split into parts + */ + const deque& get_resource_parts() const + { + return m_resource_parts; + } private: HttpRequest(); HttpRequest(const HttpRequest&); HttpRequest& operator = (const HttpRequest&); - map m_headers; /**< Request headers */ - map m_options; /**< Request options */ - Closer m_json; /**< Request body */ - string m_json_string; /**< String version of @c m_json */ - string m_resource; /**< Requested resource */ - enum http_verb m_verb; /**< Request method */ + map m_headers; /**< Request headers */ + map m_options; /**< Request options */ + Closer m_json; /**< Request body */ + string m_json_string; /**< String version of @c m_json */ + string m_resource; /**< Requested resource */ + deque m_resource_parts; /**< @c m_resource split into parts */ + enum http_verb m_verb; /**< Request method */ }; diff --git a/server/core/test/testhttp.cc b/server/core/test/testhttp.cc index 244764fc9..16a81ed22 100644 --- a/server/core/test/testhttp.cc +++ b/server/core/test/testhttp.cc @@ -385,6 +385,73 @@ int test_response() return 0; } +struct +{ + const char* input; + const char* value; + int offset; + size_t size; +} resource_parts[] = +{ + {"/", "", 0, 1}, + {"/?a=b", "", 0, 1}, + + {"/servers/", "servers", 0, 1}, + {"/servers", "servers", 0, 1}, + {"servers", "servers", 0, 1}, + + {"/servers/my-server", "servers", 0, 2}, + {"/servers/my-server/", "servers", 0, 2}, + {"servers/my-server", "servers", 0, 2}, + {"/servers/my-server", "my-server", 1, 2}, + {"/servers/my-server/", "my-server", 1, 2}, + {"servers/my-server", "my-server", 1, 2}, + + {"/servers/my-server/user", "servers", 0, 3}, + {"/servers/my-server/user", "servers", 0, 3}, + {"servers/my-server/user", "servers", 0, 3}, + {"/servers/my-server/user", "my-server", 1, 3}, + {"/servers/my-server/user/", "my-server", 1, 3}, + {"servers/my-server/user", "my-server", 1, 3}, + {"/servers/my-server/user", "user", 2, 3}, + {"/servers/my-server/user/", "user", 2, 3}, + {"servers/my-server/user", "user", 2, 3}, + + {"/servers?a=b", "servers", 0, 1}, + {"/servers/?a=b", "servers", 0, 1}, + {"servers/?a=b", "servers", 0, 1}, + {"/servers/my-server?a=b", "my-server", 1, 2}, + {"/servers/my-server/?a=b", "my-server", 1, 2}, + {"servers/my-server/?a=b", "my-server", 1, 2}, + {"/servers/my-server/user?a=b", "user", 2, 3}, + {"/servers/my-server/user/?a=b", "user", 2, 3}, + {"servers/my-server/user?a=b", "user", 2, 3}, + + {} +}; + +int test_resource_parts() +{ + for (int i = 0; resource_parts[i].input; i++) + { + stringstream ss; + ss << "GET " << resource_parts[i].input << " HTTP/1.1\r\n\r\n"; + SHttpRequest request(HttpRequest::parse(ss.str())); + + TEST(request.get(), "Request should be OK: %s", ss.str().c_str()); + + TEST(request->get_resource_parts().size() == resource_parts[i].size, + "Request should have %lu parts: %lu", resource_parts[i].size, + request->get_resource_parts().size()); + + string value = request->get_resource_parts()[resource_parts[i].offset]; + TEST(value == resource_parts[i].value, + "Request part at %d should be '%s': %s", resource_parts[i].offset, + resource_parts[i].value, value.c_str()); + } + return 0; +} + int main(int argc, char** argv) { int rc = 0; @@ -393,6 +460,7 @@ int main(int argc, char** argv) rc += test_headers(); rc += test_message_body(); rc += test_response(); + rc += test_resource_parts(); return rc; }