diff options
Diffstat (limited to 'http/routing.h')
-rw-r--r-- | http/routing.h | 1334 |
1 files changed, 1334 insertions, 0 deletions
diff --git a/http/routing.h b/http/routing.h new file mode 100644 index 0000000000..b2355e9540 --- /dev/null +++ b/http/routing.h @@ -0,0 +1,1334 @@ +#pragma once + +#include "privileges.hpp" +#include "sessions.hpp" + +#include <boost/container/flat_map.hpp> +#include <boost/container/small_vector.hpp> +#include <boost/lexical_cast.hpp> +#include <cerrno> +#include <cstdint> +#include <cstdlib> +#include <limits> +#include <memory> +#include <tuple> +#include <utility> +#include <vector> + +#include "common.h" +#include "http_request.h" +#include "http_response.h" +#include "logging.h" +#include "utility.h" +#include "websocket.h" + +namespace crow +{ + +constexpr int maxHttpVerbCount = + static_cast<int>(boost::beast::http::verb::unlink); + +class BaseRule +{ + public: + BaseRule(std::string thisRule) : rule(std::move(thisRule)) + { + } + + virtual ~BaseRule() + { + } + + virtual void validate() = 0; + std::unique_ptr<BaseRule> upgrade() + { + if (ruleToUpgrade) + return std::move(ruleToUpgrade); + return {}; + } + + virtual void handle(const Request&, Response&, const RoutingParams&) = 0; + virtual void handleUpgrade(const Request&, Response& res, + boost::asio::ip::tcp::socket&&) + { + res.result(boost::beast::http::status::not_found); + res.end(); + } +#ifdef BMCWEB_ENABLE_SSL + virtual void + handleUpgrade(const Request&, Response& res, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&) + { + res.result(boost::beast::http::status::not_found); + res.end(); + } +#endif + + size_t getMethods() + { + return methodsBitfield; + } + + bool checkPrivileges(const redfish::Privileges& userPrivileges) + { + // If there are no privileges assigned, assume no privileges + // required + if (privilegesSet.empty()) + { + return true; + } + + for (const redfish::Privileges& requiredPrivileges : privilegesSet) + { + if (userPrivileges.isSupersetOf(requiredPrivileges)) + { + return true; + } + } + return false; + } + + size_t methodsBitfield{ + 1 << static_cast<size_t>(boost::beast::http::verb::get)}; + + std::vector<redfish::Privileges> privilegesSet; + + std::string rule; + std::string nameStr; + + std::unique_ptr<BaseRule> ruleToUpgrade; + + friend class Router; + template <typename T> friend struct RuleParameterTraits; +}; + +namespace detail +{ +namespace routing_handler_call_helper +{ +template <typename T, int Pos> struct CallPair +{ + using type = T; + static const int pos = Pos; +}; + +template <typename H1> struct CallParams +{ + H1& handler; + const RoutingParams& params; + const Request& req; + Response& res; +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename S1, typename S2> +struct Call +{ +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename... Args1, typename... Args2> +struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, + black_magic::S<Args2...>> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S<Args2...>::template push_back< + CallPair<int64_t, NInt>>; + Call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, + pushed>()(cparams); + } +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename... Args1, typename... Args2> +struct Call<F, NInt, NUint, NDouble, NString, + black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S<Args2...>::template push_back< + CallPair<uint64_t, NUint>>; + Call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, + pushed>()(cparams); + } +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename... Args1, typename... Args2> +struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, + black_magic::S<Args2...>> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S<Args2...>::template push_back< + CallPair<double, NDouble>>; + Call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, + pushed>()(cparams); + } +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename... Args1, typename... Args2> +struct Call<F, NInt, NUint, NDouble, NString, + black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S<Args2...>::template push_back< + CallPair<std::string, NString>>; + Call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, + pushed>()(cparams); + } +}; + +template <typename F, int NInt, int NUint, int NDouble, int NString, + typename... Args1> +struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>, + black_magic::S<Args1...>> +{ + void operator()(F cparams) + { + cparams.handler( + cparams.req, cparams.res, + cparams.params.template get<typename Args1::type>(Args1::pos)...); + } +}; + +template <typename Func, typename... ArgsWrapped> struct Wrapped +{ + template <typename... Args> + void set( + Func f, + typename std::enable_if< + !std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value, + int>::type = 0) + { + handler = [f = std::move(f)](const Request&, Response& res, + Args... args) { + res.result(f(args...)); + res.end(); + }; + } + + template <typename Req, typename... Args> struct ReqHandlerWrapper + { + ReqHandlerWrapper(Func f) : f(std::move(f)) + { + } + + void operator()(const Request& req, Response& res, Args... args) + { + res.result(f(req, args...)); + res.end(); + } + + Func f; + }; + + template <typename... Args> + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value && + !std::is_same<typename std::tuple_element< + 1, std::tuple<Args..., void, void>>::type, + Response&>::value, + int>::type = 0) + { + handler = ReqHandlerWrapper<Args...>(std::move(f)); + /*handler = ( + [f = std::move(f)] + (const Request& req, Response& res, Args... args){ + res.result(f(req, args...)); + res.end(); + });*/ + } + + template <typename... Args> + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value && + std::is_same<typename std::tuple_element< + 1, std::tuple<Args..., void, void>>::type, + Response&>::value, + int>::type = 0) + { + handler = std::move(f); + } + + template <typename... Args> struct HandlerTypeHelper + { + using type = + std::function<void(const crow::Request&, crow::Response&, Args...)>; + using args_type = + black_magic::S<typename black_magic::promote_t<Args>...>; + }; + + template <typename... Args> + struct HandlerTypeHelper<const Request&, Args...> + { + using type = + std::function<void(const crow::Request&, crow::Response&, Args...)>; + using args_type = + black_magic::S<typename black_magic::promote_t<Args>...>; + }; + + template <typename... Args> + struct HandlerTypeHelper<const Request&, Response&, Args...> + { + using type = + std::function<void(const crow::Request&, crow::Response&, Args...)>; + using args_type = + black_magic::S<typename black_magic::promote_t<Args>...>; + }; + + typename HandlerTypeHelper<ArgsWrapped...>::type handler; + + void operator()(const Request& req, Response& res, + const RoutingParams& params) + { + detail::routing_handler_call_helper::Call< + detail::routing_handler_call_helper::CallParams<decltype(handler)>, + 0, 0, 0, 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, + black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams<decltype(handler)>{ + handler, params, req, res}); + } +}; +} // namespace routing_handler_call_helper +} // namespace detail + +class WebSocketRule : public BaseRule +{ + using self_t = WebSocketRule; + + public: + WebSocketRule(std::string rule) : BaseRule(std::move(rule)) + { + } + + void validate() override + { + } + + void handle(const Request&, Response& res, const RoutingParams&) override + { + res.result(boost::beast::http::status::not_found); + res.end(); + } + + void handleUpgrade(const Request& req, Response&, + boost::asio::ip::tcp::socket&& adaptor) override + { + new crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>( + req, std::move(adaptor), openHandler, messageHandler, closeHandler, + errorHandler); + } +#ifdef BMCWEB_ENABLE_SSL + void handleUpgrade(const Request& req, Response&, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& + adaptor) override + { + std::shared_ptr<crow::websocket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> + myConnection = std::make_shared<crow::websocket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( + req, std::move(adaptor), openHandler, messageHandler, + closeHandler, errorHandler); + myConnection->start(); + } +#endif + + template <typename Func> self_t& onopen(Func f) + { + openHandler = f; + return *this; + } + + template <typename Func> self_t& onmessage(Func f) + { + messageHandler = f; + return *this; + } + + template <typename Func> self_t& onclose(Func f) + { + closeHandler = f; + return *this; + } + + template <typename Func> self_t& onerror(Func f) + { + errorHandler = f; + return *this; + } + + protected: + std::function<void(crow::websocket::Connection&)> openHandler; + std::function<void(crow::websocket::Connection&, const std::string&, bool)> + messageHandler; + std::function<void(crow::websocket::Connection&, const std::string&)> + closeHandler; + std::function<void(crow::websocket::Connection&)> errorHandler; +}; + +template <typename T> struct RuleParameterTraits +{ + using self_t = T; + WebSocketRule& websocket() + { + self_t* self = static_cast<self_t*>(this); + WebSocketRule* p = new WebSocketRule(self->rule); + self->ruleToUpgrade.reset(p); + return *p; + } + + self_t& name(std::string name) noexcept + { + self_t* self = static_cast<self_t*>(this); + self->nameStr = std::move(name); + return *self; + } + + self_t& methods(boost::beast::http::verb method) + { + self_t* self = static_cast<self_t*>(this); + self->methodsBitfield = 1U << static_cast<size_t>(method); + return *self; + } + + template <typename... MethodArgs> + self_t& methods(boost::beast::http::verb method, MethodArgs... args_method) + { + self_t* self = static_cast<self_t*>(this); + methods(args_method...); + self->methodsBitfield |= 1U << static_cast<size_t>(method); + return *self; + } + + template <typename... MethodArgs> + self_t& requires(std::initializer_list<const char*> l) + { + self_t* self = static_cast<self_t*>(this); + self->privilegesSet.emplace_back(l); + return *self; + } + + template <typename... MethodArgs> + self_t& requires(const std::vector<redfish::Privileges>& p) + { + self_t* self = static_cast<self_t*>(this); + for (const redfish::Privileges& privilege : p) + { + self->privilegesSet.emplace_back(privilege); + } + return *self; + } +}; + +class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> +{ + public: + DynamicRule(std::string rule) : BaseRule(std::move(rule)) + { + } + + void validate() override + { + if (!erasedHandler) + { + throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + + "no handler for url " + rule); + } + } + + void handle(const Request& req, Response& res, + const RoutingParams& params) override + { + erasedHandler(req, res, params); + } + + template <typename Func> void operator()(Func f) + { + using function_t = utility::function_traits<Func>; + + erasedHandler = + wrap(std::move(f), black_magic::gen_seq<function_t::arity>()); + } + + // enable_if Arg1 == request && Arg2 == Response + // enable_if Arg1 == request && Arg2 != resposne + // enable_if Arg1 != request + + template <typename Func, unsigned... Indices> + + std::function<void(const Request&, Response&, const RoutingParams&)> + wrap(Func f, black_magic::Seq<Indices...>) + { + using function_t = utility::function_traits<Func>; + + if (!black_magic::isParameterTagCompatible( + black_magic::getParameterTagRuntime(rule.c_str()), + black_magic::compute_parameter_tag_from_args_list< + typename function_t::template arg<Indices>...>::value)) + { + throw std::runtime_error("routeDynamic: Handler type is mismatched " + "with URL parameters: " + + rule); + } + auto ret = detail::routing_handler_call_helper::Wrapped< + Func, typename function_t::template arg<Indices>...>(); + ret.template set<typename function_t::template arg<Indices>...>( + std::move(f)); + return ret; + } + + template <typename Func> void operator()(std::string name, Func&& f) + { + nameStr = std::move(name); + (*this).template operator()<Func>(std::forward(f)); + } + + private: + std::function<void(const Request&, Response&, const RoutingParams&)> + erasedHandler; +}; + +template <typename... Args> +class TaggedRule : public BaseRule, + public RuleParameterTraits<TaggedRule<Args...>> +{ + public: + using self_t = TaggedRule<Args...>; + + TaggedRule(std::string ruleIn) : BaseRule(std::move(ruleIn)) + { + } + + void validate() override + { + if (!handler) + { + throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + + "no handler for url " + rule); + } + } + + template <typename Func> + typename std::enable_if< + black_magic::CallHelper<Func, black_magic::S<Args...>>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper<Func, black_magic::S<Args...>>::value || + black_magic::CallHelper< + Func, black_magic::S<crow::Request, Args...>>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + !std::is_same<void, decltype(f(std::declval<Args>()...))>::value, + "Handler function cannot have void return type; valid return " + "types: " + "string, int, crow::resposne, nlohmann::json"); + + handler = [f = std::move(f)](const Request&, Response& res, + Args... args) { + res.result(f(args...)); + res.end(); + }; + } + + template <typename Func> + typename std::enable_if< + !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && + black_magic::CallHelper< + Func, black_magic::S<crow::Request, Args...>>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper<Func, black_magic::S<Args...>>::value || + black_magic::CallHelper< + Func, black_magic::S<crow::Request, Args...>>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + !std::is_same<void, decltype(f(std::declval<crow::Request>(), + std::declval<Args>()...))>::value, + "Handler function cannot have void return type; valid return " + "types: " + "string, int, crow::resposne,nlohmann::json"); + + handler = [f = std::move(f)](const crow::Request& req, + crow::Response& res, Args... args) { + res.result(f(req, args...)); + res.end(); + }; + } + + template <typename Func> + typename std::enable_if< + !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && + !black_magic::CallHelper< + Func, black_magic::S<crow::Request, Args...>>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper<Func, black_magic::S<Args...>>::value || + black_magic::CallHelper< + Func, black_magic::S<crow::Request, Args...>>::value || + black_magic::CallHelper< + Func, black_magic::S<crow::Request, crow::Response&, + Args...>>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + std::is_same<void, decltype(f(std::declval<crow::Request>(), + std::declval<crow::Response&>(), + std::declval<Args>()...))>::value, + "Handler function with response argument should have void " + "return " + "type"); + + handler = std::move(f); + } + + template <typename Func> void operator()(std::string name, Func&& f) + { + nameStr = std::move(name); + (*this).template operator()<Func>(std::forward(f)); + } + + void handle(const Request& req, Response& res, + const RoutingParams& params) override + { + detail::routing_handler_call_helper::Call< + detail::routing_handler_call_helper::CallParams<decltype(handler)>, + 0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams<decltype(handler)>{ + handler, params, req, res}); + } + + private: + std::function<void(const crow::Request&, crow::Response&, Args...)> handler; +}; + +const int ruleSpecialRedirectSlash = 1; + +class Trie +{ + public: + struct Node + { + unsigned ruleIndex{}; + std::array<size_t, static_cast<size_t>(ParamType::MAX)> + paramChildrens{}; + boost::container::flat_map<std::string, unsigned> children; + + bool isSimpleNode() const + { + return !ruleIndex && std::all_of(std::begin(paramChildrens), + std::end(paramChildrens), + [](size_t x) { return !x; }); + } + }; + + Trie() : nodes(1) + { + } + + private: + void optimizeNode(Node* node) + { + for (size_t x : node->paramChildrens) + { + if (!x) + continue; + Node* child = &nodes[x]; + optimizeNode(child); + } + if (node->children.empty()) + return; + bool mergeWithChild = true; + for (const std::pair<std::string, unsigned>& kv : node->children) + { + Node* child = &nodes[kv.second]; + if (!child->isSimpleNode()) + { + mergeWithChild = false; + break; + } + } + if (mergeWithChild) + { + decltype(node->children) merged; + for (const std::pair<std::string, unsigned>& kv : node->children) + { + Node* child = &nodes[kv.second]; + for (const std::pair<std::string, unsigned>& childKv : + child->children) + { + merged[kv.first + childKv.first] = childKv.second; + } + } + node->children = std::move(merged); + optimizeNode(node); + } + else + { + for (const std::pair<std::string, unsigned>& kv : node->children) + { + Node* child = &nodes[kv.second]; + optimizeNode(child); + } + } + } + + void optimize() + { + optimizeNode(head()); + } + + public: + void validate() + { + if (!head()->isSimpleNode()) + throw std::runtime_error( + "Internal error: Trie header should be simple!"); + optimize(); + } + + void findRouteIndexes(const std::string& req_url, + std::vector<unsigned>& route_indexes, + const Node* node = nullptr, unsigned pos = 0) const + { + if (node == nullptr) + { + node = head(); + } + for (const std::pair<std::string, unsigned>& kv : node->children) + { + const std::string& fragment = kv.first; + const Node* child = &nodes[kv.second]; + if (pos >= req_url.size()) + { + if (child->ruleIndex != 0 && fragment != "/") + { + route_indexes.push_back(child->ruleIndex); + } + findRouteIndexes(req_url, route_indexes, child, + static_cast<unsigned>(pos + fragment.size())); + } + else + { + if (req_url.compare(pos, fragment.size(), fragment) == 0) + { + findRouteIndexes( + req_url, route_indexes, child, + static_cast<unsigned>(pos + fragment.size())); + } + } + } + } + + std::pair<unsigned, RoutingParams> + find(const std::string_view req_url, const Node* node = nullptr, + size_t pos = 0, RoutingParams* params = nullptr) const + { + RoutingParams empty; + if (params == nullptr) + params = ∅ + + unsigned found{}; + RoutingParams matchParams; + + if (node == nullptr) + node = head(); + if (pos == req_url.size()) + return {node->ruleIndex, *params}; + + auto updateFound = + [&found, &matchParams](std::pair<unsigned, RoutingParams>& ret) { + if (ret.first && (!found || found > ret.first)) + { + found = ret.first; + matchParams = std::move(ret.second); + } + }; + + if (node->paramChildrens[static_cast<size_t>(ParamType::INT)]) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+' || c == '-') + { + char* eptr; + errno = 0; + long long int value = + std::strtoll(req_url.data() + pos, &eptr, 10); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + params->intParams.push_back(value); + std::pair<unsigned, RoutingParams> ret = find( + req_url, + &nodes[node->paramChildrens[static_cast<size_t>( + ParamType::INT)]], + static_cast<size_t>(eptr - req_url.data()), params); + updateFound(ret); + params->intParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast<size_t>(ParamType::UINT)]) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+') + { + char* eptr; + errno = 0; + unsigned long long int value = + std::strtoull(req_url.data() + pos, &eptr, 10); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + params->uintParams.push_back(value); + std::pair<unsigned, RoutingParams> ret = find( + req_url, + &nodes[node->paramChildrens[static_cast<size_t>( + ParamType::UINT)]], + static_cast<size_t>(eptr - req_url.data()), params); + updateFound(ret); + params->uintParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast<size_t>(ParamType::DOUBLE)]) + { + char c = req_url[pos]; + if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') + { + char* eptr; + errno = 0; + double value = std::strtod(req_url.data() + pos, &eptr); + if (errno != ERANGE && eptr != req_url.data() + pos) + { + params->doubleParams.push_back(value); + std::pair<unsigned, RoutingParams> ret = find( + req_url, + &nodes[node->paramChildrens[static_cast<size_t>( + ParamType::DOUBLE)]], + static_cast<size_t>(eptr - req_url.data()), params); + updateFound(ret); + params->doubleParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)]) + { + size_t epos = pos; + for (; epos < req_url.size(); epos++) + { + if (req_url[epos] == '/') + break; + } + + if (epos != pos) + { + params->stringParams.emplace_back( + req_url.substr(pos, epos - pos)); + std::pair<unsigned, RoutingParams> ret = + find(req_url, + &nodes[node->paramChildrens[static_cast<size_t>( + ParamType::STRING)]], + epos, params); + updateFound(ret); + params->stringParams.pop_back(); + } + } + + if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)]) + { + size_t epos = req_url.size(); + + if (epos != pos) + { + params->stringParams.emplace_back( + req_url.substr(pos, epos - pos)); + std::pair<unsigned, RoutingParams> ret = + find(req_url, + &nodes[node->paramChildrens[static_cast<size_t>( + ParamType::PATH)]], + epos, params); + updateFound(ret); + params->stringParams.pop_back(); + } + } + + for (const std::pair<std::string, unsigned>& kv : node->children) + { + const std::string& fragment = kv.first; + const Node* child = &nodes[kv.second]; + + if (req_url.compare(pos, fragment.size(), fragment) == 0) + { + std::pair<unsigned, RoutingParams> ret = + find(req_url, child, pos + fragment.size(), params); + updateFound(ret); + } + } + + return {found, matchParams}; + } + + void add(const std::string& url, unsigned ruleIndex) + { + size_t idx = 0; + + for (unsigned i = 0; i < url.size(); i++) + { + char c = url[i]; + if (c == '<') + { + const static std::array<std::pair<ParamType, std::string>, 7> + paramTraits = {{ + {ParamType::INT, "<int>"}, + {ParamType::UINT, "<uint>"}, + {ParamType::DOUBLE, "<float>"}, + {ParamType::DOUBLE, "<double>"}, + {ParamType::STRING, "<str>"}, + {ParamType::STRING, "<string>"}, + {ParamType::PATH, "<path>"}, + }}; + + for (const std::pair<ParamType, std::string>& x : paramTraits) + { + if (url.compare(i, x.second.size(), x.second) == 0) + { + size_t index = static_cast<size_t>(x.first); + if (!nodes[idx].paramChildrens[index]) + { + unsigned newNodeIdx = newNode(); + nodes[idx].paramChildrens[index] = newNodeIdx; + } + idx = nodes[idx].paramChildrens[index]; + i += static_cast<unsigned>(x.second.size()); + break; + } + } + + i--; + } + else + { + std::string piece(&c, 1); + if (!nodes[idx].children.count(piece)) + { + unsigned newNodeIdx = newNode(); + nodes[idx].children.emplace(piece, newNodeIdx); + } + idx = nodes[idx].children[piece]; + } + } + if (nodes[idx].ruleIndex) + throw std::runtime_error("handler already exists for " + url); + nodes[idx].ruleIndex = ruleIndex; + } + + private: + void debugNodePrint(Node* n, size_t level) + { + for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++) + { + if (n->paramChildrens[i]) + { + BMCWEB_LOG_DEBUG << std::string( + 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/; + switch (static_cast<ParamType>(i)) + { + case ParamType::INT: + BMCWEB_LOG_DEBUG << "<int>"; + break; + case ParamType::UINT: + BMCWEB_LOG_DEBUG << "<uint>"; + break; + case ParamType::DOUBLE: + BMCWEB_LOG_DEBUG << "<float>"; + break; + case ParamType::STRING: + BMCWEB_LOG_DEBUG << "<str>"; + break; + case ParamType::PATH: + BMCWEB_LOG_DEBUG << "<path>"; + break; + default: + BMCWEB_LOG_DEBUG << "<ERROR>"; + break; + } + + debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); + } + } + for (const std::pair<std::string, unsigned>& kv : n->children) + { + BMCWEB_LOG_DEBUG + << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ + << kv.first; + debugNodePrint(&nodes[kv.second], level + 1); + } + } + + public: + void debugPrint() + { + debugNodePrint(head(), 0U); + } + + private: + const Node* head() const + { + return &nodes.front(); + } + + Node* head() + { + return &nodes.front(); + } + + unsigned newNode() + { + nodes.resize(nodes.size() + 1); + return static_cast<unsigned>(nodes.size() - 1); + } + + std::vector<Node> nodes; +}; + +class Router +{ + public: + Router() + { + } + + DynamicRule& newRuleDynamic(const std::string& rule) + { + std::unique_ptr<DynamicRule> ruleObject = + std::make_unique<DynamicRule>(rule); + DynamicRule* ptr = ruleObject.get(); + allRules.emplace_back(std::move(ruleObject)); + + return *ptr; + } + + template <uint64_t N> + typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& + newRuleTagged(const std::string& rule) + { + using RuleT = typename black_magic::Arguments<N>::type::template rebind< + TaggedRule>; + std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); + RuleT* ptr = ruleObject.get(); + allRules.emplace_back(std::move(ruleObject)); + + return *ptr; + } + + void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) + { + if (ruleObject == nullptr) + { + return; + } + for (uint32_t method = 0, method_bit = 1; method < maxHttpVerbCount; + method++, method_bit <<= 1) + { + if (ruleObject->methodsBitfield & method_bit) + { + perMethods[method].rules.emplace_back(ruleObject); + perMethods[method].trie.add( + rule, static_cast<unsigned>( + perMethods[method].rules.size() - 1U)); + // directory case: + // request to `/about' url matches `/about/' rule + if (rule.size() > 2 && rule.back() == '/') + { + perMethods[method].trie.add( + rule.substr(0, rule.size() - 1), + static_cast<unsigned>(perMethods[method].rules.size() - + 1)); + } + } + } + } + + void validate() + { + for (std::unique_ptr<BaseRule>& rule : allRules) + { + if (rule) + { + std::unique_ptr<BaseRule> upgraded = rule->upgrade(); + if (upgraded) + rule = std::move(upgraded); + rule->validate(); + internalAddRuleObject(rule->rule, rule.get()); + } + } + for (PerMethod& perMethod : perMethods) + { + perMethod.trie.validate(); + } + } + + template <typename Adaptor> + void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) + { + if (static_cast<size_t>(req.method()) >= perMethods.size()) + return; + + PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())]; + Trie& trie = perMethod.trie; + std::vector<BaseRule*>& rules = perMethod.rules; + + const std::pair<unsigned, RoutingParams>& found = trie.find(req.url); + unsigned ruleIndex = found.first; + if (!ruleIndex) + { + BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + if (ruleIndex >= rules.size()) + throw std::runtime_error("Trie internal structure corrupted!"); + + if (ruleIndex == ruleSpecialRedirectSlash) + { + BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: " + << req.url; + res.result(boost::beast::http::status::moved_permanently); + + // TODO absolute url building + if (req.getHeaderValue("Host").empty()) + { + res.addHeader("Location", std::string(req.url) + "/"); + } + else + { + res.addHeader( + "Location", + req.isSecure + ? "https://" + : "http://" + std::string(req.getHeaderValue("Host")) + + std::string(req.url) + "/"); + } + res.end(); + return; + } + + if ((rules[ruleIndex]->getMethods() & + (1U << static_cast<size_t>(req.method()))) == 0) + { + BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url + << " with " << req.methodString() << "(" + << static_cast<uint32_t>(req.method()) << ") / " + << rules[ruleIndex]->getMethods(); + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule + << "' " << static_cast<uint32_t>(req.method()) << " / " + << rules[ruleIndex]->getMethods(); + + // any uncaught exceptions become 500s + try + { + rules[ruleIndex]->handleUpgrade(req, res, std::move(adaptor)); + } + catch (std::exception& e) + { + BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); + res.result(boost::beast::http::status::internal_server_error); + res.end(); + return; + } + catch (...) + { + BMCWEB_LOG_ERROR + << "An uncaught exception occurred. The type was unknown " + "so no information was available."; + res.result(boost::beast::http::status::internal_server_error); + res.end(); + return; + } + } + + void handle(const Request& req, Response& res) + { + if (static_cast<size_t>(req.method()) >= perMethods.size()) + return; + PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())]; + Trie& trie = perMethod.trie; + std::vector<BaseRule*>& rules = perMethod.rules; + + const std::pair<unsigned, RoutingParams>& found = trie.find(req.url); + + unsigned ruleIndex = found.first; + + if (!ruleIndex) + { + // Check to see if this url exists at any verb + for (const PerMethod& p : perMethods) + { + const std::pair<unsigned, RoutingParams>& found = + p.trie.find(req.url); + if (found.first > 0) + { + res.result(boost::beast::http::status::method_not_allowed); + res.end(); + return; + } + } + BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; + res.result(boost::beast::http::status::not_found); + res.end(); + return; + } + + if (ruleIndex >= rules.size()) + throw std::runtime_error("Trie internal structure corrupted!"); + + if (ruleIndex == ruleSpecialRedirectSlash) + { + BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: " + << req.url; + res.result(boost::beast::http::status::moved_permanently); + + // TODO absolute url building + if (req.getHeaderValue("Host").empty()) + { + res.addHeader("Location", std::string(req.url) + "/"); + } + else + { + res.addHeader("Location", + (req.isSecure ? "https://" : "http://") + + std::string(req.getHeaderValue("Host")) + + std::string(req.url) + "/"); + } + res.end(); + return; + } + + if ((rules[ruleIndex]->getMethods() & + (1U << static_cast<uint32_t>(req.method()))) == 0) + { + BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url + << " with " << req.methodString() << "(" + << static_cast<uint32_t>(req.method()) << ") / " + << rules[ruleIndex]->getMethods(); + res.result(boost::beast::http::status::method_not_allowed); + res.end(); + return; + } + + BMCWEB_LOG_DEBUG << "Matched rule '" << rules[ruleIndex]->rule << "' " + << static_cast<uint32_t>(req.method()) << " / " + << rules[ruleIndex]->getMethods(); + + redfish::Privileges userPrivileges; + if (req.session != nullptr) + { + // Get the user role from the session. + const std::string& userRole = + persistent_data::UserRoleMap::getInstance().getUserRole( + req.session->username); + + BMCWEB_LOG_DEBUG << "USER ROLE=" << userRole; + + // Get the user privileges from the role + userPrivileges = redfish::getUserPrivileges(userRole); + } + + if (!rules[ruleIndex]->checkPrivileges(userPrivileges)) + { + res.result(boost::beast::http::status::forbidden); + res.end(); + return; + } + + // any uncaught exceptions become 500s + try + { + rules[ruleIndex]->handle(req, res, found.second); + } + catch (std::exception& e) + { + BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); + res.result(boost::beast::http::status::internal_server_error); + res.end(); + return; + } + catch (...) + { + BMCWEB_LOG_ERROR + << "An uncaught exception occurred. The type was unknown " + "so no information was available."; + res.result(boost::beast::http::status::internal_server_error); + res.end(); + return; + } + } + + void debugPrint() + { + for (size_t i = 0; i < perMethods.size(); i++) + { + BMCWEB_LOG_DEBUG + << methodName(static_cast<boost::beast::http::verb>(i)); + perMethods[i].trie.debugPrint(); + } + } + + std::vector<const std::string*> getRoutes(const std::string& parent) + { + std::vector<const std::string*> ret; + + for (const PerMethod& pm : perMethods) + { + std::vector<unsigned> x; + pm.trie.findRouteIndexes(parent, x); + for (unsigned index : x) + { + ret.push_back(&pm.rules[index]->rule); + } + } + return ret; + } + + private: + struct PerMethod + { + std::vector<BaseRule*> rules; + Trie trie; + // rule index 0, 1 has special meaning; preallocate it to avoid + // duplication. + PerMethod() : rules(2) + { + } + }; + std::array<PerMethod, maxHttpVerbCount> perMethods; + std::vector<std::unique_ptr<BaseRule>> allRules; +}; +} // namespace crow |