From c1a969ceddba3afd952ca8b6d3351b452481be6f Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Mon, 20 Apr 2020 00:12:56 +0200 Subject: [PATCH] Fix router in regards to empty url segments --- src/HttpRouter.h | 40 +++++++++++++++++++++++++--------------- tests/HttpRouter.cpp | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/HttpRouter.h b/src/HttpRouter.h index dd62269..7c61698 100644 --- a/src/HttpRouter.h +++ b/src/HttpRouter.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "f2/function2.hpp" @@ -107,19 +108,25 @@ private: /* Set URL for router. Will reset any URL cache */ inline void setUrl(std::string_view url) { - /* Remove / from input URL */ - currentUrl = url.substr(std::min((unsigned int) url.length(), 1)); + + /* Todo: URL may also start with "http://domain/" or "*", not only "/" */ + + /* We expect to stand on a slash */ + currentUrl = url; urlSegmentTop = -1; } /* Lazily parse or read from cache */ - inline std::string_view getUrlSegment(int urlSegment) { + inline std::pair getUrlSegment(int urlSegment) { if (urlSegment > urlSegmentTop) { - /* Return empty segment if we are out of URL or stack space, but never for first url segment */ + /* Signal as STOP when we have no more URL or stack space */ if (!currentUrl.length() || urlSegment > 99) { - return {}; + return {{}, true}; } + /* We always stand on a slash here, so step over it */ + currentUrl.remove_prefix(1); + auto segmentLength = currentUrl.find('/'); if (segmentLength == std::string::npos) { segmentLength = currentUrl.length(); @@ -136,17 +143,20 @@ private: urlSegmentTop++; /* Update currentUrl */ - currentUrl = currentUrl.substr(segmentLength + 1); + currentUrl = currentUrl.substr(segmentLength); } } /* In any case we return it */ - return urlSegmentVector[urlSegment]; + return {urlSegmentVector[urlSegment], false}; } /* Executes as many handlers it can */ bool executeHandlers(Node *parent, int urlSegment, USERDATA &userData) { - /* If we have no more URL and not on first round, return where we may stand */ - if (urlSegment && !getUrlSegment(urlSegment).length()) { + + auto [segment, isStop] = getUrlSegment(urlSegment); + + /* If we are on STOP, return where we may stand */ + if (isStop) { /* We have reached accross the entire URL with no stoppage, execute */ for (int handler : parent->handlers) { if (handlers[handler & HANDLER_MASK](this)) { @@ -165,14 +175,14 @@ private: return true; } } - } else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) { + } else if (p->name.length() && p->name[0] == ':' && segment.length()) { /* Parameter match */ - routeParameters.push(getUrlSegment(urlSegment)); + routeParameters.push(segment); if (executeHandlers(p.get(), urlSegment + 1, userData)) { return true; } routeParameters.pop(); - } else if (p->name == getUrlSegment(urlSegment)) { + } else if (p->name == segment) { /* Static match */ if (executeHandlers(p.get(), urlSegment + 1, userData)) { return true; @@ -223,8 +233,8 @@ public: Node *node = getNode(&root, method, false); /* Iterate over all segments */ setUrl(pattern); - for (int i = 0; getUrlSegment(i).length() || i == 0; i++) { - node = getNode(node, std::string(getUrlSegment(i)), priority == HIGH_PRIORITY); + for (int i = 0; !getUrlSegment(i).second; i++) { + node = getNode(node, std::string(getUrlSegment(i).first), priority == HIGH_PRIORITY); } /* Insert handler in order sorted by priority (most significant 1 byte) */ node->handlers.insert(std::upper_bound(node->handlers.begin(), node->handlers.end(), (uint32_t) (priority | handlers.size())), (uint32_t) (priority | handlers.size())); @@ -237,4 +247,4 @@ public: } -#endif // UWS_HTTPROUTER_HPP \ No newline at end of file +#endif // UWS_HTTPROUTER_HPP diff --git a/tests/HttpRouter.cpp b/tests/HttpRouter.cpp index 61dbda8..3ab2e7c 100644 --- a/tests/HttpRouter.cpp +++ b/tests/HttpRouter.cpp @@ -128,6 +128,40 @@ void testUpgrade() { void testBugReports() { std::cout << "TestBugReports" << std::endl; + { + uWS::HttpRouter r; + std::string result; + + r.add({"get"}, "/foo//////bar/baz/qux", [&result](auto *) { + result += "MANYSLASH"; + return false; + }, r.MEDIUM_PRIORITY); + + r.add({"get"}, "/foo", [&result](auto *) { + result += "FOO"; + return false; + }, r.MEDIUM_PRIORITY); + + r.route("get", "/foo"); + r.route("get", "/foo/"); + r.route("get", "/foo//bar/baz/qux"); + r.route("get", "/foo//////bar/baz/qux"); + assert(result == "FOOMANYSLASH"); + } + + { + uWS::HttpRouter r; + std::string result; + + r.add({"get"}, "/test/*", [&result](auto *) { + result += "TEST"; + return false; + }, r.MEDIUM_PRIORITY); + + r.route("get", "/test/"); + assert(result == "TEST"); + } + { uWS::HttpRouter r; std::string result; @@ -293,7 +327,7 @@ void testParameters() { r.route("get", "/candy/lollipop/"); r.route("get", "/candy/lollipop"); r.route("get", "/candy/"); - assert(result == ""); + assert(result == "GLWGPW"); } int main() { @@ -302,4 +336,4 @@ int main() { testUpgrade(); testBugReports(); testParameters(); -} \ No newline at end of file +}