1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "async_resp.hpp" 6 #include "dbus_privileges.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 #include "routing/baserule.hpp" 11 #include "routing/dynamicrule.hpp" 12 #include "routing/taggedrule.hpp" 13 #include "routing/trie.hpp" 14 #include "verb.hpp" 15 16 #include <boost/beast/http/field.hpp> 17 #include <boost/beast/http/status.hpp> 18 19 #include <algorithm> 20 #include <array> 21 #include <cerrno> 22 #include <cstdint> 23 #include <cstdlib> 24 #include <format> 25 #include <functional> 26 #include <memory> 27 #include <optional> 28 #include <stdexcept> 29 #include <string> 30 #include <string_view> 31 #include <tuple> 32 #include <utility> 33 #include <vector> 34 35 namespace crow 36 { 37 38 class Router 39 { 40 public: 41 Router() = default; 42 43 DynamicRule& newRuleDynamic(const std::string& rule) 44 { 45 std::unique_ptr<DynamicRule> ruleObject = 46 std::make_unique<DynamicRule>(rule); 47 DynamicRule* ptr = ruleObject.get(); 48 allRules.emplace_back(std::move(ruleObject)); 49 50 return *ptr; 51 } 52 53 template <uint64_t NumArgs> 54 auto& newRuleTagged(const std::string& rule) 55 { 56 if constexpr (NumArgs == 0) 57 { 58 using RuleT = TaggedRule<>; 59 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 60 RuleT* ptr = ruleObject.get(); 61 allRules.emplace_back(std::move(ruleObject)); 62 return *ptr; 63 } 64 else if constexpr (NumArgs == 1) 65 { 66 using RuleT = TaggedRule<std::string>; 67 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 68 RuleT* ptr = ruleObject.get(); 69 allRules.emplace_back(std::move(ruleObject)); 70 return *ptr; 71 } 72 else if constexpr (NumArgs == 2) 73 { 74 using RuleT = TaggedRule<std::string, std::string>; 75 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 76 RuleT* ptr = ruleObject.get(); 77 allRules.emplace_back(std::move(ruleObject)); 78 return *ptr; 79 } 80 else if constexpr (NumArgs == 3) 81 { 82 using RuleT = TaggedRule<std::string, std::string, std::string>; 83 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 84 RuleT* ptr = ruleObject.get(); 85 allRules.emplace_back(std::move(ruleObject)); 86 return *ptr; 87 } 88 else if constexpr (NumArgs == 4) 89 { 90 using RuleT = 91 TaggedRule<std::string, std::string, std::string, std::string>; 92 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 93 RuleT* ptr = ruleObject.get(); 94 allRules.emplace_back(std::move(ruleObject)); 95 return *ptr; 96 } 97 else 98 { 99 using RuleT = TaggedRule<std::string, std::string, std::string, 100 std::string, std::string>; 101 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 102 RuleT* ptr = ruleObject.get(); 103 allRules.emplace_back(std::move(ruleObject)); 104 return *ptr; 105 } 106 static_assert(NumArgs <= 5, "Max number of args supported is 5"); 107 } 108 109 struct PerMethod 110 { 111 std::vector<BaseRule*> rules; 112 Trie<crow::Node> trie; 113 // rule index 0 has special meaning; preallocate it to avoid 114 // duplication. 115 PerMethod() : rules(1) {} 116 117 void internalAdd(std::string_view rule, BaseRule* ruleObject) 118 { 119 rules.emplace_back(ruleObject); 120 trie.add(rule, static_cast<unsigned>(rules.size() - 1U)); 121 // directory case: 122 // request to `/about' url matches `/about/' rule 123 if (rule.size() > 2 && rule.back() == '/') 124 { 125 trie.add(rule.substr(0, rule.size() - 1), 126 static_cast<unsigned>(rules.size() - 1)); 127 } 128 } 129 }; 130 131 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 132 { 133 if (ruleObject == nullptr) 134 { 135 return; 136 } 137 for (size_t method = 0; method <= maxVerbIndex; method++) 138 { 139 size_t methodBit = 1 << method; 140 if ((ruleObject->methodsBitfield & methodBit) > 0U) 141 { 142 perMethods[method].internalAdd(rule, ruleObject); 143 } 144 } 145 146 if (ruleObject->isNotFound) 147 { 148 notFoundRoutes.internalAdd(rule, ruleObject); 149 } 150 151 if (ruleObject->isMethodNotAllowed) 152 { 153 methodNotAllowedRoutes.internalAdd(rule, ruleObject); 154 } 155 156 if (ruleObject->isUpgrade) 157 { 158 upgradeRoutes.internalAdd(rule, ruleObject); 159 } 160 } 161 162 void validate() 163 { 164 for (std::unique_ptr<BaseRule>& rule : allRules) 165 { 166 if (rule) 167 { 168 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 169 if (upgraded) 170 { 171 rule = std::move(upgraded); 172 } 173 rule->validate(); 174 internalAddRuleObject(rule->rule, rule.get()); 175 } 176 } 177 for (PerMethod& perMethod : perMethods) 178 { 179 perMethod.trie.validate(); 180 } 181 } 182 183 struct FindRoute 184 { 185 BaseRule* rule = nullptr; 186 std::vector<std::string> params; 187 }; 188 189 struct FindRouteResponse 190 { 191 std::string allowHeader; 192 FindRoute route; 193 }; 194 195 static FindRoute findRouteByPerMethod(std::string_view url, 196 const PerMethod& perMethod) 197 { 198 FindRoute route; 199 200 Trie<crow::Node>::FindResult found = perMethod.trie.find(url); 201 if (found.ruleIndex >= perMethod.rules.size()) 202 { 203 throw std::runtime_error("Trie internal structure corrupted!"); 204 } 205 // Found a 404 route, switch that in 206 if (found.ruleIndex != 0U) 207 { 208 route.rule = perMethod.rules[found.ruleIndex]; 209 route.params = std::move(found.params); 210 } 211 return route; 212 } 213 214 FindRouteResponse findRoute(const Request& req) const 215 { 216 FindRouteResponse findRoute; 217 218 // Check to see if this url exists at any verb 219 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 220 perMethodIndex++) 221 { 222 // Make sure it's safe to deference the array at that index 223 static_assert( 224 maxVerbIndex < std::tuple_size_v<decltype(perMethods)>); 225 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 226 perMethods[perMethodIndex]); 227 if (route.rule == nullptr) 228 { 229 continue; 230 } 231 if (!findRoute.allowHeader.empty()) 232 { 233 findRoute.allowHeader += ", "; 234 } 235 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 236 findRoute.allowHeader += httpVerbToString(thisVerb); 237 } 238 239 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 240 if (!verb) 241 { 242 return findRoute; 243 } 244 size_t reqMethodIndex = static_cast<size_t>(*verb); 245 if (reqMethodIndex >= perMethods.size()) 246 { 247 return findRoute; 248 } 249 250 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 251 perMethods[reqMethodIndex]); 252 if (route.rule != nullptr) 253 { 254 findRoute.route = route; 255 } 256 257 return findRoute; 258 } 259 260 template <typename Adaptor> 261 void handleUpgrade(const std::shared_ptr<Request>& req, 262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 263 Adaptor&& adaptor) 264 { 265 PerMethod& perMethod = upgradeRoutes; 266 Trie<crow::Node>& trie = perMethod.trie; 267 std::vector<BaseRule*>& rules = perMethod.rules; 268 269 Trie<crow::Node>::FindResult found = 270 trie.find(req->url().encoded_path()); 271 unsigned ruleIndex = found.ruleIndex; 272 if (ruleIndex == 0U) 273 { 274 BMCWEB_LOG_DEBUG("Cannot match rules {}", 275 req->url().encoded_path()); 276 asyncResp->res.result(boost::beast::http::status::not_found); 277 return; 278 } 279 280 if (ruleIndex >= rules.size()) 281 { 282 throw std::runtime_error("Trie internal structure corrupted!"); 283 } 284 285 BaseRule& rule = *rules[ruleIndex]; 286 287 BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}'", rule.rule); 288 289 // TODO(ed) This should be able to use std::bind_front, but it doesn't 290 // appear to work with the std::move on adaptor. 291 validatePrivilege( 292 req, asyncResp, rule, 293 [req, &rule, asyncResp, 294 adaptor = std::forward<Adaptor>(adaptor)]() mutable { 295 rule.handleUpgrade(*req, asyncResp, std::move(adaptor)); 296 }); 297 } 298 299 void handle(const std::shared_ptr<Request>& req, 300 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 301 { 302 FindRouteResponse foundRoute = findRoute(*req); 303 304 if (foundRoute.route.rule == nullptr) 305 { 306 // Couldn't find a normal route with any verb, try looking for a 404 307 // route 308 if (foundRoute.allowHeader.empty()) 309 { 310 foundRoute.route = findRouteByPerMethod( 311 req->url().encoded_path(), notFoundRoutes); 312 } 313 else 314 { 315 // See if we have a method not allowed (405) handler 316 foundRoute.route = findRouteByPerMethod( 317 req->url().encoded_path(), methodNotAllowedRoutes); 318 } 319 } 320 321 // Fill in the allow header if it's valid 322 if (!foundRoute.allowHeader.empty()) 323 { 324 asyncResp->res.addHeader(boost::beast::http::field::allow, 325 foundRoute.allowHeader); 326 } 327 328 // If we couldn't find a real route or a 404 route, return a generic 329 // response 330 if (foundRoute.route.rule == nullptr) 331 { 332 if (foundRoute.allowHeader.empty()) 333 { 334 asyncResp->res.result(boost::beast::http::status::not_found); 335 } 336 else 337 { 338 asyncResp->res.result( 339 boost::beast::http::status::method_not_allowed); 340 } 341 return; 342 } 343 344 BaseRule& rule = *foundRoute.route.rule; 345 std::vector<std::string> params = std::move(foundRoute.route.params); 346 347 BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule, 348 req->methodString(), rule.getMethods()); 349 350 if (req->session == nullptr) 351 { 352 rule.handle(*req, asyncResp, params); 353 return; 354 } 355 validatePrivilege( 356 req, asyncResp, rule, 357 [req, asyncResp, &rule, params = std::move(params)]() { 358 rule.handle(*req, asyncResp, params); 359 }); 360 } 361 362 void debugPrint() 363 { 364 for (size_t i = 0; i < perMethods.size(); i++) 365 { 366 BMCWEB_LOG_DEBUG("{}", httpVerbToString(static_cast<HttpVerb>(i))); 367 perMethods[i].trie.debugPrint(); 368 } 369 } 370 371 std::vector<const std::string*> getRoutes(const std::string& parent) 372 { 373 std::vector<const std::string*> ret; 374 375 for (const PerMethod& pm : perMethods) 376 { 377 std::vector<unsigned> x; 378 pm.trie.findRouteIndexes(parent, x); 379 for (unsigned index : x) 380 { 381 ret.push_back(&pm.rules[index]->rule); 382 } 383 } 384 return ret; 385 } 386 387 private: 388 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods; 389 390 PerMethod notFoundRoutes; 391 PerMethod upgradeRoutes; 392 PerMethod methodNotAllowedRoutes; 393 394 std::vector<std::unique_ptr<BaseRule>> allRules; 395 }; 396 } // namespace crow 397