blob: e657e2ef1b483537220b1c77a0518cffcf49c67d [file] [log] [blame]
#pragma once
#include "privileges.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 "crow/common.h"
#include "crow/http_request.h"
#include "crow/http_response.h"
#include "crow/logging.h"
#include "crow/utility.h"
#include "crow/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 = Response(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 = Response(boost::beast::http::status::not_found);
res.end();
}
#endif
uint32_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;
}
uint32_t methodsBitfield{1 << (int)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 = Response(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 = Response(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 = Response(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 = Response(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()
{
WebSocketRule* p = new WebSocketRule(((self_t*)this)->rule);
((self_t*)this)->ruleToUpgrade.reset(p);
return *p;
}
self_t& name(std::string name) noexcept
{
((self_t*)this)->nameStr = std::move(name);
return (self_t&)*this;
}
self_t& methods(boost::beast::http::verb method)
{
((self_t*)this)->methodsBitfield = 1 << (int)method;
return (self_t&)*this;
}
template <typename... MethodArgs>
self_t& methods(boost::beast::http::verb method, MethodArgs... args_method)
{
methods(args_method...);
((self_t*)this)->methodsBitfield |= 1 << (int)method;
return (self_t&)*this;
}
template <typename... MethodArgs>
self_t& requires(std::initializer_list<const char*> l)
{
((self_t*)this)->privilegesSet.emplace_back(l);
return (self_t&)*this;
}
template <typename... MethodArgs>
self_t& requires(const std::vector<redfish::Privileges>& p)
{
for (const redfish::Privileges& privilege : p)
{
((self_t*)this)->privilegesSet.emplace_back(privilege);
}
return (self_t&)*this;
}
};
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 rule) : BaseRule(std::move(rule))
{
}
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 = Response(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 = Response(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<unsigned, (int)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),
[](unsigned x) { return !x; });
}
};
Trie() : nodes(1)
{
}
private:
void optimizeNode(Node* node)
{
for (unsigned int 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,
pos + fragment.size());
}
else
{
if (req_url.compare(pos, fragment.size(), fragment) == 0)
{
findRouteIndexes(req_url, route_indexes, child,
pos + fragment.size());
}
}
}
}
std::pair<unsigned, RoutingParams>
find(const std::string_view req_url, const Node* node = nullptr,
unsigned 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[(int)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[(int)ParamType::INT]],
eptr - req_url.data(), params);
updateFound(ret);
params->intParams.pop_back();
}
}
}
if (node->paramChildrens[(int)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[(int)ParamType::UINT]],
eptr - req_url.data(), params);
updateFound(ret);
params->uintParams.pop_back();
}
}
}
if (node->paramChildrens[(int)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[(int)ParamType::DOUBLE]],
eptr - req_url.data(), params);
updateFound(ret);
params->doubleParams.pop_back();
}
}
}
if (node->paramChildrens[(int)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[(int)ParamType::STRING]],
epos, params);
updateFound(ret);
params->stringParams.pop_back();
}
}
if (node->paramChildrens[(int)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[(int)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)
{
unsigned 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)
{
if (!nodes[idx].paramChildrens[(int)x.first])
{
unsigned newNodeIdx = newNode();
nodes[idx].paramChildrens[(int)x.first] =
newNodeIdx;
}
idx = nodes[idx].paramChildrens[(int)x.first];
i += 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, int level)
{
for (int i = 0; i < (int)ParamType::MAX; i++)
{
if (n->paramChildrens[i])
{
BMCWEB_LOG_DEBUG << std::string(
2 * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/;
switch ((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(2 * level, ' ') /*<< "(" << kv.second << ") "*/
<< kv.first;
debugNodePrint(&nodes[kv.second], level + 1);
}
}
public:
void debugPrint()
{
debugNodePrint(head(), 0);
}
private:
const Node* head() const
{
return &nodes.front();
}
Node* head()
{
return &nodes.front();
}
unsigned newNode()
{
nodes.resize(nodes.size() + 1);
return 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, perMethods[method].rules.size() - 1);
// 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),
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<int>(req.method()) >= perMethods.size())
return;
PerMethod& perMethod = perMethods[(int)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 = Response(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 = Response(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() & (1 << (uint32_t)req.method())) ==
0)
{
BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
<< " with " << req.methodString() << "("
<< (uint32_t)req.method() << ") / "
<< rules[ruleIndex]->getMethods();
res = Response(boost::beast::http::status::not_found);
res.end();
return;
}
BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule
<< "' " << (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 = Response(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 = Response(boost::beast::http::status::internal_server_error);
res.end();
return;
}
}
void handle(const Request& req, Response& res)
{
if ((int)req.method() >= perMethods.size())
return;
PerMethod& perMethod = perMethods[(int)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 = Response(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() & (1 << (uint32_t)req.method())) ==
0)
{
BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
<< " with " << req.methodString() << "("
<< (uint32_t)req.method() << ") / "
<< rules[ruleIndex]->getMethods();
res = Response(boost::beast::http::status::not_found);
res.end();
return;
}
BMCWEB_LOG_DEBUG << "Matched rule '" << rules[ruleIndex]->rule << "' "
<< (uint32_t)req.method() << " / "
<< rules[ruleIndex]->getMethods();
// TODO: load user privileges from configuration as soon as its
// available now we are granting all privileges to everyone.
redfish::Privileges userPrivileges{"Login", "ConfigureManager",
"ConfigureSelf", "ConfigureUsers",
"ConfigureComponents"};
if (!rules[ruleIndex]->checkPrivileges(userPrivileges))
{
res.result(boost::beast::http::status::method_not_allowed);
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 = Response(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 = Response(boost::beast::http::status::internal_server_error);
res.end();
return;
}
}
void debugPrint()
{
for (int i = 0; i < perMethods.size(); i++)
{
BMCWEB_LOG_DEBUG << methodName((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