summaryrefslogtreecommitdiff
path: root/http/routing.h
diff options
context:
space:
mode:
Diffstat (limited to 'http/routing.h')
-rw-r--r--http/routing.h1334
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 = &empty;
+
+ 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