diff --git a/.gitmodules b/.gitmodules index 95da8a700..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "http-parser"] - path = http-parser - url = https://github.com/joyent/http-parser diff --git a/README.md b/README.md index c6fa8a458..1400c0da8 100644 --- a/README.md +++ b/README.md @@ -115,3 +115,21 @@ ctest #### OSX brew install boost google-perftools +### Attributions + +Crow uses the following libraries. + + qs_parse + + https://github.com/bartgrantham/qs_parse + + Copyright (c) 2010 Bart Grantham + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + diff --git a/amalgamate/crow_all.h b/amalgamate/crow_all.h index 7991dcc56..a0b15cf34 100644 --- a/amalgamate/crow_all.h +++ b/amalgamate/crow_all.h @@ -364,64 +364,314 @@ template #pragma once +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// qs_parse (modified) +// https://github.com/bartgrantham/qs_parse +// ---------------------------------------------------------------------------- +/* Similar to strncmp, but handles URL-encoding for either string */ +int qs_strncmp(const char * s, const char * qs, size_t n); +/* Finds the beginning of each key/value pair and stores a pointer in qs_kv. + * Also decodes the value portion of the k/v pair *in-place*. In a future + * enhancement it will also have a compile-time option of sorting qs_kv + * alphabetically by key. */ +int qs_parse(char * qs, char * qs_kv[], int qs_kv_size); -namespace crow + +/* Used by qs_parse to decode the value portion of a k/v pair */ +int qs_decode(char * qs); + + +/* Looks up the value according to the key on a pre-processed query string + * A future enhancement will be a compile-time option to look up the key + * in a pre-sorted qs_kv array via a binary search. */ +//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); + char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth); + + +/* Non-destructive lookup of value, based on key. User provides the + * destinaton string and length. */ +char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len); + +// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled +#undef _qsSORTING + +// isxdigit _is_ available in , but let's avoid another header instead +#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0) +#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0) +#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1) + +inline int qs_strncmp(const char * s, const char * qs, size_t n) { - namespace detail + int i=0; + unsigned char u1, u2, unyb, lnyb; + + while(n-- > 0) { - template - struct partial_context - : public black_magic::pop_back::template rebind - , public black_magic::last_element_type::type::context + u1 = (unsigned char) *s++; + u2 = (unsigned char) *qs++; + + if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; } + if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; } + + if ( u1 == '+' ) { u1 = ' '; } + if ( u1 == '%' ) // easier/safer than scanf { - using parent_context = typename black_magic::pop_back::template rebind<::crow::detail::partial_context>; - template - using partial = typename std::conditional>::type; + unyb = (unsigned char) *s++; + lnyb = (unsigned char) *s++; + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u1 = '\0'; + } - template - typename T::context& get() - { - return static_cast(*this); - } - }; - - template <> - struct partial_context<> + if ( u2 == '+' ) { u2 = ' '; } + if ( u2 == '%' ) // easier/safer than scanf { - template - using partial = partial_context; - }; + unyb = (unsigned char) *qs++; + lnyb = (unsigned char) *qs++; + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u2 = '\0'; + } - template - bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); - - template - struct context : private partial_context - //struct context : private Middlewares::context... // simple but less type-safe - { - template - friend typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); - template - friend typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); - - template - friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); - - template - typename T::context& get() - { - return static_cast(*this); - } - - template - using partial = typename partial_context::template partial; - }; + if ( u1 != u2 ) + return u1 - u2; + if ( u1 == '\0' ) + return 0; + i++; } + if ( CROW_QS_ISQSCHR(*qs) ) + return -1; + else + return 0; } +inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size) +{ + int i, j; + char * substr_ptr; + + for(i=0; i means x iterations of this loop -> means *x+1* k/v pairs + + // we only decode the values in place, the keys could have '='s in them + // which will hose our ability to distinguish keys from values later + for(j=0; j get_list (const std::string& name) const + { + std::vector ret; + std::string plus = name + "[]"; + char* element = nullptr; + + int count = 0; + while(1) + { + element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); + if (!element) + break; + ret.push_back(element); + } + return ret; + } + + + private: + std::string url_; + std::vector key_value_pairs_; + }; + +} // end namespace + + #pragma once @@ -435,8 +685,6 @@ namespace crow -using namespace std; - namespace crow { enum class LogLevel @@ -450,13 +698,13 @@ namespace crow class ILogHandler { public: - virtual void log(string message, LogLevel level) = 0; + virtual void log(std::string message, LogLevel level) = 0; }; class CerrLogHandler : public ILogHandler { public: - void log(string message, LogLevel level) override { - cerr << message; + void log(std::string message, LogLevel level) override { + std::cerr << message; } }; @@ -464,18 +712,18 @@ namespace crow private: // - static string timestamp() + static std::string timestamp() { char date[32]; time_t t = time(0); strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", gmtime(&t)); - return string(date); + return std::string(date); } public: - logger(string prefix, LogLevel level) : level_(level) { + logger(std::string prefix, LogLevel level) : level_(level) { #ifdef CROW_ENABLE_LOGGING stringstream_ << "(" << timestamp() << ") [" << prefix << "] "; #endif @@ -484,7 +732,7 @@ namespace crow ~logger() { #ifdef CROW_ENABLE_LOGGING if(level_ >= get_current_log_level()) { - stringstream_ << endl; + stringstream_ << std::endl; get_handler_ref()->log(stringstream_.str(), level_); } #endif @@ -530,7 +778,7 @@ namespace crow } // - ostringstream stringstream_; + std::ostringstream stringstream_; LogLevel level_; }; } @@ -566,6 +814,7 @@ namespace crow #include #include #include +#include #if defined(__GNUG__) || defined(__clang__) #define crow_json_likely(x) __builtin_expect(x, 1) @@ -5313,8 +5562,10 @@ namespace crow #pragma once +#include #include #include +#include @@ -5440,7 +5691,9 @@ constexpr crow::HTTPMethod operator "" _method(const char* str, size_t len) #pragma once +#include #include +#include namespace crow { @@ -5480,6 +5733,8 @@ namespace crow + + namespace crow { template @@ -5496,7 +5751,9 @@ namespace crow struct request { HTTPMethod method; + std::string raw_url; std::string url; + query_string url_params; ci_map headers; std::string body; @@ -5507,8 +5764,8 @@ namespace crow { } - request(HTTPMethod method, std::string url, ci_map headers, std::string body) - : method(method), url(std::move(url)), headers(std::move(headers)), body(std::move(body)) + request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body) + : method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body)) { } @@ -5532,6 +5789,10 @@ namespace crow #include #include #include +#include +#include + + @@ -5550,7 +5811,7 @@ namespace crow static int on_url(http_parser* self_, const char* at, size_t length) { HTTPParser* self = static_cast(self_); - self->url.insert(self->url.end(), at, at+length); + self->raw_url.insert(self->raw_url.end(), at, at+length); return 0; } static int on_header_field(http_parser* self_, const char* at, size_t length) @@ -5606,6 +5867,11 @@ namespace crow static int on_message_complete(http_parser* self_) { HTTPParser* self = static_cast(self_); + + // url params + self->url = self->raw_url.substr(0, self->raw_url.find("?")); + self->url_params = query_string(self->raw_url); + self->process_message(); return 0; } @@ -5641,10 +5907,12 @@ namespace crow void clear() { url.clear(); + raw_url.clear(); header_building_state = 0; header_field.clear(); header_value.clear(); headers.clear(); + url_params.clear(); body.clear(); } @@ -5660,7 +5928,7 @@ namespace crow request to_request() const { - return request{(HTTPMethod)method, std::move(url), std::move(headers), std::move(body)}; + return request{(HTTPMethod)method, std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)}; } bool check_version(int major, int minor) const @@ -5668,11 +5936,14 @@ namespace crow return http_major == major && http_minor == minor; } + std::string raw_url; std::string url; + int header_building_state = 0; std::string header_field; std::string header_value; ci_map headers; + query_string url_params; std::string body; Handler* handler_; @@ -5807,6 +6078,7 @@ namespace crow #include #include #include +#include @@ -6435,16 +6707,13 @@ public: void handle(const request& req, response& res) { - // remove url params - auto editedUrl = req.url.substr(0, req.url.find("?")); - - auto found = trie_.find(editedUrl); + auto found = trie_.find(req.url); unsigned rule_index = found.first; if (!rule_index) { - CROW_LOG_DEBUG << "Cannot match rules " << editedUrl; + CROW_LOG_DEBUG << "Cannot match rules " << req.url; res = response(404); res.end(); return; @@ -6455,7 +6724,7 @@ public: if ((rules_[rule_index]->methods() & (1<<(uint32_t)req.method)) == 0) { - CROW_LOG_DEBUG << "Rule found but method mismatch: " << editedUrl << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods(); + CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods(); res = response(404); res.end(); return; @@ -6480,6 +6749,71 @@ public: #pragma once + + + + + + + + +namespace crow +{ + namespace detail + { + template + struct partial_context + : public black_magic::pop_back::template rebind + , public black_magic::last_element_type::type::context + { + using parent_context = typename black_magic::pop_back::template rebind<::crow::detail::partial_context>; + template + using partial = typename std::conditional>::type; + + template + typename T::context& get() + { + return static_cast(*this); + } + }; + + template <> + struct partial_context<> + { + template + using partial = partial_context; + }; + + template + bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); + + template + struct context : private partial_context + //struct context : private Middlewares::context... // simple but less type-safe + { + template + friend typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); + template + friend typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); + + template + friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); + + template + typename T::context& get() + { + return static_cast(*this); + } + + template + using partial = typename partial_context::template partial; + }; + } +} + + + +#pragma once #include @@ -6665,6 +6999,7 @@ namespace crow #include #include #include +#include @@ -6900,7 +7235,7 @@ namespace crow void complete_request() { - CROW_LOG_INFO << "Response: " << this << ' ' << req_.url << ' ' << res.code << ' ' << close_connection_; + CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_; if (need_to_call_after_handlers_) { @@ -7173,6 +7508,7 @@ namespace crow #include #include #include +#include #include diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2d449e4bf..1fcec94b4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,6 +9,10 @@ if (Tcmalloc_FOUND) target_link_libraries(example ${Tcmalloc_LIBRARIES}) endif(Tcmalloc_FOUND) +add_executable(example_with_all example_with_all.cpp) +#target_link_libraries(example crow) +target_link_libraries(example_with_all ${Boost_LIBRARIES} ) + add_custom_command(OUTPUT example_test.py COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/example_test.py ${CMAKE_CURRENT_BINARY_DIR}/example_test.py diff --git a/examples/example.cpp b/examples/example.cpp index 48b381c62..686e82321 100644 --- a/examples/example.cpp +++ b/examples/example.cpp @@ -5,7 +5,7 @@ class ExampleLogHandler : public crow::ILogHandler { public: - void log(string message, crow::LogLevel level) override { + void log(std::string message, crow::LogLevel level) override { // cerr << "ExampleLogHandler -> " << message; } }; @@ -99,6 +99,23 @@ int main() return crow::response{os.str()}; }); + CROW_ROUTE(app, "/params") + ([](const crow::request& req){ + std::ostringstream os; + os << "Params: " << req.url_params << "\n\n"; + os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n"; + if(req.url_params.get("pew") != nullptr) { + double countD = boost::lexical_cast(req.url_params.get("pew")); + os << "The value of 'pew' is " << countD << '\n'; + } + auto count = req.url_params.get_list("count"); + os << "The key 'count' contains " << count.size() << " value(s).\n"; + for(const auto& countVal : count) { + os << " - " << countVal << '\n'; + } + return crow::response{os.str()}; + }); + // ignore all log crow::logger::setLogLevel(crow::LogLevel::DEBUG); //crow::logger::setHandler(std::make_shared()); diff --git a/examples/example_with_all.cpp b/examples/example_with_all.cpp new file mode 100644 index 000000000..55e271503 --- /dev/null +++ b/examples/example_with_all.cpp @@ -0,0 +1,94 @@ +#include "../amalgamate/crow_all.h" + +#include + +class ExampleLogHandler : public crow::ILogHandler { + public: + void log(std::string message, crow::LogLevel level) override { +// cerr << "ExampleLogHandler -> " << message; + } +}; + +int main() +{ + crow::SimpleApp app; + + CROW_ROUTE(app, "/") + .name("hello") + ([]{ + return "Hello World!"; + }); + + CROW_ROUTE(app, "/about") + ([](){ + return "About Crow example."; + }); + + // simple json response + CROW_ROUTE(app, "/json") + ([]{ + crow::json::wvalue x; + x["message"] = "Hello, World!"; + return x; + }); + + CROW_ROUTE(app,"/hello/") + ([](int count){ + if (count > 100) + return crow::response(400); + std::ostringstream os; + os << count << " bottles of beer!"; + return crow::response(os.str()); + }); + + CROW_ROUTE(app,"/add//") + ([](const crow::request& req, crow::response& res, int a, int b){ + std::ostringstream os; + os << a+b; + res.write(os.str()); + res.end(); + }); + + // Compile error with message "Handler type is mismatched with URL paramters" + //CROW_ROUTE(app,"/another/") + //([](int a, int b){ + //return crow::response(500); + //}); + + // more json example + CROW_ROUTE(app, "/add_json") + ([](const crow::request& req){ + auto x = crow::json::load(req.body); + if (!x) + return crow::response(400); + int sum = x["a"].i()+x["b"].i(); + std::ostringstream os; + os << sum; + return crow::response{os.str()}; + }); + + CROW_ROUTE(app, "/params") + ([](const crow::request& req){ + std::ostringstream os; + os << "Params: " << req.url_params << "\n\n"; + os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n"; + if(req.url_params.get("pew") != nullptr) { + double countD = boost::lexical_cast(req.url_params.get("pew")); + os << "The value of 'pew' is " << countD << '\n'; + } + auto count = req.url_params.get_list("count"); + os << "The key 'count' contains " << count.size() << " value(s).\n"; + for(const auto& countVal : count) { + os << " - " << countVal << '\n'; + } + return crow::response{os.str()}; + }); + + // ignore all log + crow::logger::setLogLevel(crow::LogLevel::DEBUG); + //crow::logger::setHandler(std::make_shared()); + + app.port(18080) + .multithreaded() + .run(); +} diff --git a/http-parser b/http-parser deleted file mode 160000 index 5b951d74b..000000000 --- a/http-parser +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5b951d74bd66ec9d38448e0a85b1cf8b85d97db3 diff --git a/include/ci_map.h b/include/ci_map.h index 9b48f0bf2..155d54f72 100644 --- a/include/ci_map.h +++ b/include/ci_map.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include namespace crow { diff --git a/include/common.h b/include/common.h index 5720b46b5..619f4d5c9 100644 --- a/include/common.h +++ b/include/common.h @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include "utility.h" namespace crow diff --git a/include/http_connection.h b/include/http_connection.h index 46eae18c3..db069c306 100644 --- a/include/http_connection.h +++ b/include/http_connection.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "http_parser_merged.h" @@ -232,7 +233,7 @@ namespace crow void complete_request() { - CROW_LOG_INFO << "Response: " << this << ' ' << req_.url << ' ' << res.code << ' ' << close_connection_; + CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_; if (need_to_call_after_handlers_) { diff --git a/include/http_request.h b/include/http_request.h index 331bc80ac..1319b2d0b 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -2,6 +2,7 @@ #include "common.h" #include "ci_map.h" +#include "query_string.h" namespace crow { @@ -19,7 +20,9 @@ namespace crow struct request { HTTPMethod method; + std::string raw_url; std::string url; + query_string url_params; ci_map headers; std::string body; @@ -30,8 +33,8 @@ namespace crow { } - request(HTTPMethod method, std::string url, ci_map headers, std::string body) - : method(method), url(std::move(url)), headers(std::move(headers)), body(std::move(body)) + request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body) + : method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body)) { } diff --git a/include/http_server.h b/include/http_server.h index 32e0e4257..bd35ba1f3 100644 --- a/include/http_server.h +++ b/include/http_server.h @@ -5,6 +5,7 @@ #include #include #include +#include #include diff --git a/include/json.h b/include/json.h index 9306e2aa0..d79fe8866 100644 --- a/include/json.h +++ b/include/json.h @@ -10,6 +10,7 @@ #include #include #include +#include #if defined(__GNUG__) || defined(__clang__) #define crow_json_likely(x) __builtin_expect(x, 1) diff --git a/include/logging.h b/include/logging.h index 8f5b833b5..0d7707188 100644 --- a/include/logging.h +++ b/include/logging.h @@ -9,8 +9,6 @@ #include "settings.h" -using namespace std; - namespace crow { enum class LogLevel @@ -24,13 +22,13 @@ namespace crow class ILogHandler { public: - virtual void log(string message, LogLevel level) = 0; + virtual void log(std::string message, LogLevel level) = 0; }; class CerrLogHandler : public ILogHandler { public: - void log(string message, LogLevel level) override { - cerr << message; + void log(std::string message, LogLevel level) override { + std::cerr << message; } }; @@ -38,18 +36,18 @@ namespace crow private: // - static string timestamp() + static std::string timestamp() { char date[32]; time_t t = time(0); strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", gmtime(&t)); - return string(date); + return std::string(date); } public: - logger(string prefix, LogLevel level) : level_(level) { + logger(std::string prefix, LogLevel level) : level_(level) { #ifdef CROW_ENABLE_LOGGING stringstream_ << "(" << timestamp() << ") [" << prefix << "] "; #endif @@ -58,7 +56,7 @@ namespace crow ~logger() { #ifdef CROW_ENABLE_LOGGING if(level_ >= get_current_log_level()) { - stringstream_ << endl; + stringstream_ << std::endl; get_handler_ref()->log(stringstream_.str(), level_); } #endif @@ -104,7 +102,7 @@ namespace crow } // - ostringstream stringstream_; + std::ostringstream stringstream_; LogLevel level_; }; } diff --git a/include/middleware_context.h b/include/middleware_context.h index 980a8216b..daaaa5c7d 100644 --- a/include/middleware_context.h +++ b/include/middleware_context.h @@ -1,6 +1,8 @@ #pragma once #include "utility.h" +#include "http_request.h" +#include "http_response.h" namespace crow { diff --git a/include/parser.h b/include/parser.h index 869061cfb..f6b748b55 100644 --- a/include/parser.h +++ b/include/parser.h @@ -3,7 +3,10 @@ #include #include #include +#include +#include +#include "http_parser_merged.h" #include "http_request.h" namespace crow @@ -20,7 +23,7 @@ namespace crow static int on_url(http_parser* self_, const char* at, size_t length) { HTTPParser* self = static_cast(self_); - self->url.insert(self->url.end(), at, at+length); + self->raw_url.insert(self->raw_url.end(), at, at+length); return 0; } static int on_header_field(http_parser* self_, const char* at, size_t length) @@ -76,6 +79,11 @@ namespace crow static int on_message_complete(http_parser* self_) { HTTPParser* self = static_cast(self_); + + // url params + self->url = self->raw_url.substr(0, self->raw_url.find("?")); + self->url_params = query_string(self->raw_url); + self->process_message(); return 0; } @@ -111,10 +119,12 @@ namespace crow void clear() { url.clear(); + raw_url.clear(); header_building_state = 0; header_field.clear(); header_value.clear(); headers.clear(); + url_params.clear(); body.clear(); } @@ -130,7 +140,7 @@ namespace crow request to_request() const { - return request{(HTTPMethod)method, std::move(url), std::move(headers), std::move(body)}; + return request{(HTTPMethod)method, std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)}; } bool check_version(int major, int minor) const @@ -138,11 +148,14 @@ namespace crow return http_major == major && http_minor == minor; } + std::string raw_url; std::string url; + int header_building_state = 0; std::string header_field; std::string header_value; ci_map headers; + query_string url_params; std::string body; Handler* handler_; diff --git a/include/query_string.h b/include/query_string.h new file mode 100644 index 000000000..d0a93ea9d --- /dev/null +++ b/include/query_string.h @@ -0,0 +1,308 @@ +#pragma once + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// qs_parse (modified) +// https://github.com/bartgrantham/qs_parse +// ---------------------------------------------------------------------------- +/* Similar to strncmp, but handles URL-encoding for either string */ +int qs_strncmp(const char * s, const char * qs, size_t n); + + +/* Finds the beginning of each key/value pair and stores a pointer in qs_kv. + * Also decodes the value portion of the k/v pair *in-place*. In a future + * enhancement it will also have a compile-time option of sorting qs_kv + * alphabetically by key. */ +int qs_parse(char * qs, char * qs_kv[], int qs_kv_size); + + +/* Used by qs_parse to decode the value portion of a k/v pair */ +int qs_decode(char * qs); + + +/* Looks up the value according to the key on a pre-processed query string + * A future enhancement will be a compile-time option to look up the key + * in a pre-sorted qs_kv array via a binary search. */ +//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); + char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth); + + +/* Non-destructive lookup of value, based on key. User provides the + * destinaton string and length. */ +char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len); + +// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled +#undef _qsSORTING + +// isxdigit _is_ available in , but let's avoid another header instead +#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0) +#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0) +#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1) + +inline int qs_strncmp(const char * s, const char * qs, size_t n) +{ + int i=0; + unsigned char u1, u2, unyb, lnyb; + + while(n-- > 0) + { + u1 = (unsigned char) *s++; + u2 = (unsigned char) *qs++; + + if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; } + if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; } + + if ( u1 == '+' ) { u1 = ' '; } + if ( u1 == '%' ) // easier/safer than scanf + { + unyb = (unsigned char) *s++; + lnyb = (unsigned char) *s++; + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u1 = '\0'; + } + + if ( u2 == '+' ) { u2 = ' '; } + if ( u2 == '%' ) // easier/safer than scanf + { + unyb = (unsigned char) *qs++; + lnyb = (unsigned char) *qs++; + if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) + u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); + else + u2 = '\0'; + } + + if ( u1 != u2 ) + return u1 - u2; + if ( u1 == '\0' ) + return 0; + i++; + } + if ( CROW_QS_ISQSCHR(*qs) ) + return -1; + else + return 0; +} + + +inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size) +{ + int i, j; + char * substr_ptr; + + for(i=0; i means x iterations of this loop -> means *x+1* k/v pairs + + // we only decode the values in place, the keys could have '='s in them + // which will hose our ability to distinguish keys from values later + for(j=0; j get_list (const std::string& name) const + { + std::vector ret; + std::string plus = name + "[]"; + char* element = nullptr; + + int count = 0; + while(1) + { + element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); + if (!element) + break; + ret.push_back(element); + } + return ret; + } + + + private: + std::string url_; + std::vector key_value_pairs_; + }; + +} // end namespace diff --git a/include/routing.h b/include/routing.h index e62cbce6a..9f607f173 100644 --- a/include/routing.h +++ b/include/routing.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "common.h" #include "http_response.h" @@ -629,16 +630,13 @@ public: void handle(const request& req, response& res) { - // remove url params - auto editedUrl = req.url.substr(0, req.url.find("?")); - - auto found = trie_.find(editedUrl); + auto found = trie_.find(req.url); unsigned rule_index = found.first; if (!rule_index) { - CROW_LOG_DEBUG << "Cannot match rules " << editedUrl; + CROW_LOG_DEBUG << "Cannot match rules " << req.url; res = response(404); res.end(); return; @@ -649,7 +647,7 @@ public: if ((rules_[rule_index]->methods() & (1<<(uint32_t)req.method)) == 0) { - CROW_LOG_DEBUG << "Rule found but method mismatch: " << editedUrl << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods(); + CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods(); res = response(404); res.end(); return; diff --git a/tests/unittest.cpp b/tests/unittest.cpp index dc04a5fcd..4dec72754 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -11,6 +11,7 @@ #include "json.h" #include "mustache.h" #include "middleware.h" +#include "query_string.h" using namespace std; using namespace crow; @@ -830,6 +831,123 @@ TEST(bug_quick_repeated_request) server.stop(); } +TEST(simple_url_params) +{ + static char buf[2048]; + + SimpleApp app; + + query_string last_url_params; + + CROW_ROUTE(app, "/params") + ([&last_url_params](const crow::request& req){ + last_url_params = move(req.url_params); + return "OK"; + }); + + ///params?h=1&foo=bar&lol&count[]=1&count[]=4&pew=5.2 + + decltype(app)::server_t server(&app, 45451); + auto _ = async(launch::async, [&]{server.run();}); + asio::io_service is; + std::string sendmsg; + + // check single presence + sendmsg = "GET /params?foobar\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_TRUE(last_url_params.get("missing") == nullptr); + ASSERT_TRUE(last_url_params.get("foobar") != nullptr); + ASSERT_TRUE(last_url_params.get_list("missing").empty()); + } + // check multiple presence + sendmsg = "GET /params?foo&bar&baz\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_TRUE(last_url_params.get("missing") == nullptr); + ASSERT_TRUE(last_url_params.get("foo") != nullptr); + ASSERT_TRUE(last_url_params.get("bar") != nullptr); + ASSERT_TRUE(last_url_params.get("baz") != nullptr); + } + // check single value + sendmsg = "GET /params?hello=world\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_EQUAL(string(last_url_params.get("hello")), "world"); + } + // check multiple value + sendmsg = "GET /params?hello=world&left=right&up=down\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_EQUAL(string(last_url_params.get("hello")), "world"); + ASSERT_EQUAL(string(last_url_params.get("left")), "right"); + ASSERT_EQUAL(string(last_url_params.get("up")), "down"); + } + // check multiple value, multiple types + sendmsg = "GET /params?int=100&double=123.45&boolean=1\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_EQUAL(boost::lexical_cast(last_url_params.get("int")), 100); + ASSERT_EQUAL(boost::lexical_cast(last_url_params.get("double")), 123.45); + ASSERT_EQUAL(boost::lexical_cast(last_url_params.get("boolean")), true); + } + // check single array value + sendmsg = "GET /params?tmnt[]=leonardo\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_TRUE(last_url_params.get("tmnt") == nullptr); + ASSERT_EQUAL(last_url_params.get_list("tmnt").size(), 1); + ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[0]), "leonardo"); + } + // check multiple array value + sendmsg = "GET /params?tmnt[]=leonardo&tmnt[]=donatello&tmnt[]=raphael\r\n\r\n"; + { + asio::ip::tcp::socket c(is); + + c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); + c.send(asio::buffer(sendmsg)); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + ASSERT_EQUAL(last_url_params.get_list("tmnt").size(), 3); + ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[0]), "leonardo"); + ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[1]), "donatello"); + ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[2]), "raphael"); + } + server.stop(); +} + int main() { return testmain();