summaryrefslogtreecommitdiff
path: root/http/routing.h
diff options
context:
space:
mode:
authorEd Tanous <ed.tanous@intel.com>2019-10-11 01:39:33 +0300
committerEd Tanous <ed.tanous@intel.com>2019-10-18 19:20:11 +0300
commitc94ad49bc747e7a7170287b9f4c859e3638cf432 (patch)
tree17ca83806e5b546f20c3478862fe1aa699e2ae22 /http/routing.h
parent789771dda22c256afa9e46ffe4c859bb87532af1 (diff)
downloadbmcweb-c94ad49bc747e7a7170287b9f4c859e3638cf432.tar.xz
Make references to crow less obvious
Recently, a number of people in the community have made the (admittedly easy) mistake that we use a significant portion of crow. Today, we use crow for the router, and the "app" structure, and even those have been significantly modified to meet the bmc needs. All other components have been replaced with Boost beast. This commit removes the crow mentions from the Readme, and moves the crow folder to "http" to camouflage it a little. No code content has changed. Tested: Code compiles. No functional change made to any executable code. Signed-off-by: Ed Tanous <ed.tanous@intel.com> Change-Id: Iceb57b26306cc8bdcfc77f3874246338864fd118
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