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; }