From c94ad49bc747e7a7170287b9f4c859e3638cf432 Mon Sep 17 00:00:00 2001 From: Ed Tanous Date: Thu, 10 Oct 2019 15:39:33 -0700 Subject: 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 Change-Id: Iceb57b26306cc8bdcfc77f3874246338864fd118 --- http/routing.h | 1334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1334 insertions(+) create mode 100644 http/routing.h (limited to 'http/routing.h') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 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&&) + { + 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(boost::beast::http::verb::get)}; + + std::vector privilegesSet; + + std::string rule; + std::string nameStr; + + std::unique_ptr ruleToUpgrade; + + friend class Router; + template friend struct RuleParameterTraits; +}; + +namespace detail +{ +namespace routing_handler_call_helper +{ +template struct CallPair +{ + using type = T; + static const int pos = Pos; +}; + +template struct CallParams +{ + H1& handler; + const RoutingParams& params; + const Request& req; + Response& res; +}; + +template +struct Call +{ +}; + +template +struct Call, + black_magic::S> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back< + CallPair>; + Call, + pushed>()(cparams); + } +}; + +template +struct Call, black_magic::S> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back< + CallPair>; + Call, + pushed>()(cparams); + } +}; + +template +struct Call, + black_magic::S> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back< + CallPair>; + Call, + pushed>()(cparams); + } +}; + +template +struct Call, black_magic::S> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S::template push_back< + CallPair>; + Call, + pushed>()(cparams); + } +}; + +template +struct Call, + black_magic::S> +{ + void operator()(F cparams) + { + cparams.handler( + cparams.req, cparams.res, + cparams.params.template get(Args1::pos)...); + } +}; + +template struct Wrapped +{ + template + void set( + Func f, + typename std::enable_if< + !std::is_same< + typename std::tuple_element<0, std::tuple>::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 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 + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple>::type, + const Request&>::value && + !std::is_same>::type, + Response&>::value, + int>::type = 0) + { + handler = ReqHandlerWrapper(std::move(f)); + /*handler = ( + [f = std::move(f)] + (const Request& req, Response& res, Args... args){ + res.result(f(req, args...)); + res.end(); + });*/ + } + + template + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple>::type, + const Request&>::value && + std::is_same>::type, + Response&>::value, + int>::type = 0) + { + handler = std::move(f); + } + + template struct HandlerTypeHelper + { + using type = + std::function; + using args_type = + black_magic::S...>; + }; + + template + struct HandlerTypeHelper + { + using type = + std::function; + using args_type = + black_magic::S...>; + }; + + template + struct HandlerTypeHelper + { + using type = + std::function; + using args_type = + black_magic::S...>; + }; + + typename HandlerTypeHelper::type handler; + + void operator()(const Request& req, Response& res, + const RoutingParams& params) + { + detail::routing_handler_call_helper::Call< + detail::routing_handler_call_helper::CallParams, + 0, 0, 0, 0, typename HandlerTypeHelper::args_type, + black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams{ + 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( + req, std::move(adaptor), openHandler, messageHandler, closeHandler, + errorHandler); + } +#ifdef BMCWEB_ENABLE_SSL + void handleUpgrade(const Request& req, Response&, + boost::beast::ssl_stream&& + adaptor) override + { + std::shared_ptr>> + myConnection = std::make_shared>>( + req, std::move(adaptor), openHandler, messageHandler, + closeHandler, errorHandler); + myConnection->start(); + } +#endif + + template self_t& onopen(Func f) + { + openHandler = f; + return *this; + } + + template self_t& onmessage(Func f) + { + messageHandler = f; + return *this; + } + + template self_t& onclose(Func f) + { + closeHandler = f; + return *this; + } + + template self_t& onerror(Func f) + { + errorHandler = f; + return *this; + } + + protected: + std::function openHandler; + std::function + messageHandler; + std::function + closeHandler; + std::function errorHandler; +}; + +template struct RuleParameterTraits +{ + using self_t = T; + WebSocketRule& websocket() + { + self_t* self = static_cast(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(this); + self->nameStr = std::move(name); + return *self; + } + + self_t& methods(boost::beast::http::verb method) + { + self_t* self = static_cast(this); + self->methodsBitfield = 1U << static_cast(method); + return *self; + } + + template + self_t& methods(boost::beast::http::verb method, MethodArgs... args_method) + { + self_t* self = static_cast(this); + methods(args_method...); + self->methodsBitfield |= 1U << static_cast(method); + return *self; + } + + template + self_t& requires(std::initializer_list l) + { + self_t* self = static_cast(this); + self->privilegesSet.emplace_back(l); + return *self; + } + + template + self_t& requires(const std::vector& p) + { + self_t* self = static_cast(this); + for (const redfish::Privileges& privilege : p) + { + self->privilegesSet.emplace_back(privilege); + } + return *self; + } +}; + +class DynamicRule : public BaseRule, public RuleParameterTraits +{ + 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 void operator()(Func f) + { + using function_t = utility::function_traits; + + erasedHandler = + wrap(std::move(f), black_magic::gen_seq()); + } + + // enable_if Arg1 == request && Arg2 == Response + // enable_if Arg1 == request && Arg2 != resposne + // enable_if Arg1 != request + + template + + std::function + wrap(Func f, black_magic::Seq) + { + using function_t = utility::function_traits; + + if (!black_magic::isParameterTagCompatible( + black_magic::getParameterTagRuntime(rule.c_str()), + black_magic::compute_parameter_tag_from_args_list< + typename function_t::template arg...>::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...>(); + ret.template set...>( + std::move(f)); + return ret; + } + + template void operator()(std::string name, Func&& f) + { + nameStr = std::move(name); + (*this).template operator()(std::forward(f)); + } + + private: + std::function + erasedHandler; +}; + +template +class TaggedRule : public BaseRule, + public RuleParameterTraits> +{ + public: + using self_t = TaggedRule; + + 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 std::enable_if< + black_magic::CallHelper>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper>::value || + black_magic::CallHelper< + Func, black_magic::S>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + !std::is_same()...))>::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 std::enable_if< + !black_magic::CallHelper>::value && + black_magic::CallHelper< + Func, black_magic::S>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper>::value || + black_magic::CallHelper< + Func, black_magic::S>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + !std::is_same(), + std::declval()...))>::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 std::enable_if< + !black_magic::CallHelper>::value && + !black_magic::CallHelper< + Func, black_magic::S>::value, + void>::type + operator()(Func&& f) + { + static_assert( + black_magic::CallHelper>::value || + black_magic::CallHelper< + Func, black_magic::S>::value || + black_magic::CallHelper< + Func, black_magic::S>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + std::is_same(), + std::declval(), + std::declval()...))>::value, + "Handler function with response argument should have void " + "return " + "type"); + + handler = std::move(f); + } + + template void operator()(std::string name, Func&& f) + { + nameStr = std::move(name); + (*this).template operator()(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, + 0, 0, 0, 0, black_magic::S, black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams{ + handler, params, req, res}); + } + + private: + std::function handler; +}; + +const int ruleSpecialRedirectSlash = 1; + +class Trie +{ + public: + struct Node + { + unsigned ruleIndex{}; + std::array(ParamType::MAX)> + paramChildrens{}; + boost::container::flat_map 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& kv : node->children) + { + Node* child = &nodes[kv.second]; + if (!child->isSimpleNode()) + { + mergeWithChild = false; + break; + } + } + if (mergeWithChild) + { + decltype(node->children) merged; + for (const std::pair& kv : node->children) + { + Node* child = &nodes[kv.second]; + for (const std::pair& childKv : + child->children) + { + merged[kv.first + childKv.first] = childKv.second; + } + } + node->children = std::move(merged); + optimizeNode(node); + } + else + { + for (const std::pair& 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& route_indexes, + const Node* node = nullptr, unsigned pos = 0) const + { + if (node == nullptr) + { + node = head(); + } + for (const std::pair& 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(pos + fragment.size())); + } + else + { + if (req_url.compare(pos, fragment.size(), fragment) == 0) + { + findRouteIndexes( + req_url, route_indexes, child, + static_cast(pos + fragment.size())); + } + } + } + } + + std::pair + find(const std::string_view req_url, const Node* node = nullptr, + size_t pos = 0, RoutingParams* params = nullptr) const + { + RoutingParams empty; + if (params == nullptr) + params = ∅ + + unsigned found{}; + RoutingParams matchParams; + + if (node == nullptr) + node = head(); + if (pos == req_url.size()) + return {node->ruleIndex, *params}; + + auto updateFound = + [&found, &matchParams](std::pair& ret) { + if (ret.first && (!found || found > ret.first)) + { + found = ret.first; + matchParams = std::move(ret.second); + } + }; + + if (node->paramChildrens[static_cast(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 ret = find( + req_url, + &nodes[node->paramChildrens[static_cast( + ParamType::INT)]], + static_cast(eptr - req_url.data()), params); + updateFound(ret); + params->intParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast(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 ret = find( + req_url, + &nodes[node->paramChildrens[static_cast( + ParamType::UINT)]], + static_cast(eptr - req_url.data()), params); + updateFound(ret); + params->uintParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast(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 ret = find( + req_url, + &nodes[node->paramChildrens[static_cast( + ParamType::DOUBLE)]], + static_cast(eptr - req_url.data()), params); + updateFound(ret); + params->doubleParams.pop_back(); + } + } + } + + if (node->paramChildrens[static_cast(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 ret = + find(req_url, + &nodes[node->paramChildrens[static_cast( + ParamType::STRING)]], + epos, params); + updateFound(ret); + params->stringParams.pop_back(); + } + } + + if (node->paramChildrens[static_cast(ParamType::PATH)]) + { + size_t epos = req_url.size(); + + if (epos != pos) + { + params->stringParams.emplace_back( + req_url.substr(pos, epos - pos)); + std::pair ret = + find(req_url, + &nodes[node->paramChildrens[static_cast( + ParamType::PATH)]], + epos, params); + updateFound(ret); + params->stringParams.pop_back(); + } + } + + for (const std::pair& 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 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, 7> + paramTraits = {{ + {ParamType::INT, ""}, + {ParamType::UINT, ""}, + {ParamType::DOUBLE, ""}, + {ParamType::DOUBLE, ""}, + {ParamType::STRING, ""}, + {ParamType::STRING, ""}, + {ParamType::PATH, ""}, + }}; + + for (const std::pair& x : paramTraits) + { + if (url.compare(i, x.second.size(), x.second) == 0) + { + size_t index = static_cast(x.first); + if (!nodes[idx].paramChildrens[index]) + { + unsigned newNodeIdx = newNode(); + nodes[idx].paramChildrens[index] = newNodeIdx; + } + idx = nodes[idx].paramChildrens[index]; + i += static_cast(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(ParamType::MAX); i++) + { + if (n->paramChildrens[i]) + { + BMCWEB_LOG_DEBUG << std::string( + 2U * level, ' ') /*<< "("<paramChildrens[i]<<") "*/; + switch (static_cast(i)) + { + case ParamType::INT: + BMCWEB_LOG_DEBUG << ""; + break; + case ParamType::UINT: + BMCWEB_LOG_DEBUG << ""; + break; + case ParamType::DOUBLE: + BMCWEB_LOG_DEBUG << ""; + break; + case ParamType::STRING: + BMCWEB_LOG_DEBUG << ""; + break; + case ParamType::PATH: + BMCWEB_LOG_DEBUG << ""; + break; + default: + BMCWEB_LOG_DEBUG << ""; + break; + } + + debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); + } + } + for (const std::pair& 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(nodes.size() - 1); + } + + std::vector nodes; +}; + +class Router +{ + public: + Router() + { + } + + DynamicRule& newRuleDynamic(const std::string& rule) + { + std::unique_ptr ruleObject = + std::make_unique(rule); + DynamicRule* ptr = ruleObject.get(); + allRules.emplace_back(std::move(ruleObject)); + + return *ptr; + } + + template + typename black_magic::Arguments::type::template rebind& + newRuleTagged(const std::string& rule) + { + using RuleT = typename black_magic::Arguments::type::template rebind< + TaggedRule>; + std::unique_ptr ruleObject = std::make_unique(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( + 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(perMethods[method].rules.size() - + 1)); + } + } + } + } + + void validate() + { + for (std::unique_ptr& rule : allRules) + { + if (rule) + { + std::unique_ptr upgraded = rule->upgrade(); + if (upgraded) + rule = std::move(upgraded); + rule->validate(); + internalAddRuleObject(rule->rule, rule.get()); + } + } + for (PerMethod& perMethod : perMethods) + { + perMethod.trie.validate(); + } + } + + template + void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) + { + if (static_cast(req.method()) >= perMethods.size()) + return; + + PerMethod& perMethod = perMethods[static_cast(req.method())]; + Trie& trie = perMethod.trie; + std::vector& rules = perMethod.rules; + + const std::pair& 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(req.method()))) == 0) + { + BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url + << " with " << req.methodString() << "(" + << static_cast(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(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(req.method()) >= perMethods.size()) + return; + PerMethod& perMethod = perMethods[static_cast(req.method())]; + Trie& trie = perMethod.trie; + std::vector& rules = perMethod.rules; + + const std::pair& 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& 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(req.method()))) == 0) + { + BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url + << " with " << req.methodString() << "(" + << static_cast(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(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(i)); + perMethods[i].trie.debugPrint(); + } + } + + std::vector getRoutes(const std::string& parent) + { + std::vector ret; + + for (const PerMethod& pm : perMethods) + { + std::vector x; + pm.trie.findRouteIndexes(parent, x); + for (unsigned index : x) + { + ret.push_back(&pm.rules[index]->rule); + } + } + return ret; + } + + private: + struct PerMethod + { + std::vector rules; + Trie trie; + // rule index 0, 1 has special meaning; preallocate it to avoid + // duplication. + PerMethod() : rules(2) + { + } + }; + std::array perMethods; + std::vector> allRules; +}; +} // namespace crow -- cgit v1.2.3