blob: 2bebd470b9fd1897fdaaf7b43e1aa92971c0ea8a [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous7045c8d2017-04-03 10:04:37 -07003#pragma once
4
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005#include "async_resp.hpp"
Ed Tanous08bbe112023-04-06 13:10:02 -07006#include "dbus_privileges.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07007#include "http_request.hpp"
8#include "http_response.hpp"
9#include "logging.hpp"
Ed Tanous08bbe112023-04-06 13:10:02 -070010#include "routing/baserule.hpp"
11#include "routing/dynamicrule.hpp"
Ed Tanous08bbe112023-04-06 13:10:02 -070012#include "routing/taggedrule.hpp"
Ed Tanous5b607ea2025-03-26 12:13:39 -070013#include "routing/trie.hpp"
Ed Tanous2c9efc32022-07-31 22:08:26 -070014#include "verb.hpp"
Ed Tanous1abe55e2018-09-05 08:30:59 -070015
Ed Tanousd7857202025-01-28 15:32:26 -080016#include <boost/beast/http/field.hpp>
17#include <boost/beast/http/status.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050018
Ed Tanousd9e89df2024-03-27 14:08:59 -070019#include <algorithm>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <array>
Ed Tanouse0d918b2018-03-27 17:41:04 -070021#include <cerrno>
Ed Tanous7045c8d2017-04-03 10:04:37 -070022#include <cstdint>
Ed Tanouse0d918b2018-03-27 17:41:04 -070023#include <cstdlib>
Ed Tanousd7857202025-01-28 15:32:26 -080024#include <format>
25#include <functional>
Ed Tanous7045c8d2017-04-03 10:04:37 -070026#include <memory>
Ed Tanous2c9efc32022-07-31 22:08:26 -070027#include <optional>
Ed Tanousd7857202025-01-28 15:32:26 -080028#include <stdexcept>
29#include <string>
Ed Tanousa3b9eb92024-06-03 08:39:37 -070030#include <string_view>
Ed Tanous7045c8d2017-04-03 10:04:37 -070031#include <tuple>
Ed Tanous7045c8d2017-04-03 10:04:37 -070032#include <utility>
33#include <vector>
Ed Tanous9140a672017-04-24 17:01:32 -070034
Ed Tanous1abe55e2018-09-05 08:30:59 -070035namespace crow
36{
Tanousf00032d2018-11-05 01:18:10 -030037
Ed Tanous1abe55e2018-09-05 08:30:59 -070038class Router
39{
40 public:
Ed Tanous0c0084a2019-10-24 15:57:51 -070041 Router() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070042
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 DynamicRule& newRuleDynamic(const std::string& rule)
44 {
45 std::unique_ptr<DynamicRule> ruleObject =
46 std::make_unique<DynamicRule>(rule);
47 DynamicRule* ptr = ruleObject.get();
Tanousf00032d2018-11-05 01:18:10 -030048 allRules.emplace_back(std::move(ruleObject));
Ed Tanous7045c8d2017-04-03 10:04:37 -070049
Ed Tanous1abe55e2018-09-05 08:30:59 -070050 return *ptr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070051 }
52
Ed Tanousd9e89df2024-03-27 14:08:59 -070053 template <uint64_t NumArgs>
Ed Tanouscfe3bc02023-06-26 12:47:24 -070054 auto& newRuleTagged(const std::string& rule)
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 {
Ed Tanousd9e89df2024-03-27 14:08:59 -070056 if constexpr (NumArgs == 0)
Ed Tanouscfe3bc02023-06-26 12:47:24 -070057 {
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 }
Ed Tanousd9e89df2024-03-27 14:08:59 -070064 else if constexpr (NumArgs == 1)
Ed Tanouscfe3bc02023-06-26 12:47:24 -070065 {
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 }
Ed Tanousd9e89df2024-03-27 14:08:59 -070072 else if constexpr (NumArgs == 2)
Ed Tanouscfe3bc02023-06-26 12:47:24 -070073 {
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 }
Ed Tanousd9e89df2024-03-27 14:08:59 -070080 else if constexpr (NumArgs == 3)
Ed Tanouscfe3bc02023-06-26 12:47:24 -070081 {
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 }
Ed Tanousd9e89df2024-03-27 14:08:59 -070088 else if constexpr (NumArgs == 4)
Ed Tanouscfe3bc02023-06-26 12:47:24 -070089 {
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 }
Ed Tanousd9e89df2024-03-27 14:08:59 -0700106 static_assert(NumArgs <= 5, "Max number of args supported is 5");
Ed Tanous7045c8d2017-04-03 10:04:37 -0700107 }
108
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700109 struct PerMethod
110 {
111 std::vector<BaseRule*> rules;
Rohit PAI81f915b2025-04-02 12:29:29 +0530112 Trie<crow::Node> trie;
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700113 // 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
Tanousf00032d2018-11-05 01:18:10 -0300131 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700132 {
Tanousf00032d2018-11-05 01:18:10 -0300133 if (ruleObject == nullptr)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700134 {
Tanousf00032d2018-11-05 01:18:10 -0300135 return;
136 }
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700137 for (size_t method = 0; method <= maxVerbIndex; method++)
Tanousf00032d2018-11-05 01:18:10 -0300138 {
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700139 size_t methodBit = 1 << method;
Ed Tanouse662eae2022-01-25 10:39:19 -0800140 if ((ruleObject->methodsBitfield & methodBit) > 0U)
Tanousf00032d2018-11-05 01:18:10 -0300141 {
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700142 perMethods[method].internalAdd(rule, ruleObject);
Tanousf00032d2018-11-05 01:18:10 -0300143 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700144 }
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700145
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 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700160 }
161
Ed Tanous1abe55e2018-09-05 08:30:59 -0700162 void validate()
163 {
Tanousf00032d2018-11-05 01:18:10 -0300164 for (std::unique_ptr<BaseRule>& rule : allRules)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 {
166 if (rule)
167 {
Tanousf00032d2018-11-05 01:18:10 -0300168 std::unique_ptr<BaseRule> upgraded = rule->upgrade();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700169 if (upgraded)
Ed Tanous3174e4d2020-10-07 11:41:22 -0700170 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700171 rule = std::move(upgraded);
Ed Tanous3174e4d2020-10-07 11:41:22 -0700172 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 rule->validate();
Tanousf00032d2018-11-05 01:18:10 -0300174 internalAddRuleObject(rule->rule, rule.get());
Ed Tanous1abe55e2018-09-05 08:30:59 -0700175 }
176 }
Tanousf00032d2018-11-05 01:18:10 -0300177 for (PerMethod& perMethod : perMethods)
178 {
179 perMethod.trie.validate();
180 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700181 }
182
Ed Tanous44e45182022-07-26 16:47:23 -0700183 struct FindRoute
184 {
185 BaseRule* rule = nullptr;
Ed Tanous15a42df2023-02-09 18:08:23 -0800186 std::vector<std::string> params;
Ed Tanous44e45182022-07-26 16:47:23 -0700187 };
188
189 struct FindRouteResponse
190 {
191 std::string allowHeader;
192 FindRoute route;
193 };
194
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700195 static FindRoute findRouteByPerMethod(std::string_view url,
196 const PerMethod& perMethod)
Ed Tanous759cf102022-07-31 16:36:52 -0700197 {
198 FindRoute route;
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700199
Rohit PAI81f915b2025-04-02 12:29:29 +0530200 Trie<crow::Node>::FindResult found = perMethod.trie.find(url);
Ed Tanousd9e89df2024-03-27 14:08:59 -0700201 if (found.ruleIndex >= perMethod.rules.size())
Ed Tanous759cf102022-07-31 16:36:52 -0700202 {
203 throw std::runtime_error("Trie internal structure corrupted!");
204 }
205 // Found a 404 route, switch that in
Ed Tanousd9e89df2024-03-27 14:08:59 -0700206 if (found.ruleIndex != 0U)
Ed Tanous759cf102022-07-31 16:36:52 -0700207 {
Ed Tanousd9e89df2024-03-27 14:08:59 -0700208 route.rule = perMethod.rules[found.ruleIndex];
209 route.params = std::move(found.params);
Ed Tanous759cf102022-07-31 16:36:52 -0700210 }
211 return route;
212 }
213
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700214 FindRouteResponse findRoute(const Request& req) const
Ed Tanous44e45182022-07-26 16:47:23 -0700215 {
216 FindRouteResponse findRoute;
217
Ed Tanous44e45182022-07-26 16:47:23 -0700218 // 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
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400223 static_assert(
224 maxVerbIndex < std::tuple_size_v<decltype(perMethods)>);
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700225 FindRoute route = findRouteByPerMethod(req.url().encoded_path(),
226 perMethods[perMethodIndex]);
Ed Tanous759cf102022-07-31 16:36:52 -0700227 if (route.rule == nullptr)
Ed Tanous44e45182022-07-26 16:47:23 -0700228 {
229 continue;
230 }
231 if (!findRoute.allowHeader.empty())
232 {
233 findRoute.allowHeader += ", ";
234 }
Ed Tanous2c9efc32022-07-31 22:08:26 -0700235 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex);
236 findRoute.allowHeader += httpVerbToString(thisVerb);
Ed Tanous44e45182022-07-26 16:47:23 -0700237 }
Ed Tanous50bfc912024-07-29 14:20:50 -0700238
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
Ed Tanous44e45182022-07-26 16:47:23 -0700257 return findRoute;
258 }
259
Ed Tanous1abe55e2018-09-05 08:30:59 -0700260 template <typename Adaptor>
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700261 void handleUpgrade(const std::shared_ptr<Request>& req,
P Dheeraj Srujan Kumara9f076e2021-10-18 22:45:37 +0530262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
263 Adaptor&& adaptor)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 {
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700265 PerMethod& perMethod = upgradeRoutes;
Rohit PAI81f915b2025-04-02 12:29:29 +0530266 Trie<crow::Node>& trie = perMethod.trie;
Tanousf00032d2018-11-05 01:18:10 -0300267 std::vector<BaseRule*>& rules = perMethod.rules;
268
Rohit PAI81f915b2025-04-02 12:29:29 +0530269 Trie<crow::Node>::FindResult found =
270 trie.find(req->url().encoded_path());
Ed Tanousd9e89df2024-03-27 14:08:59 -0700271 unsigned ruleIndex = found.ruleIndex;
Ed Tanouse662eae2022-01-25 10:39:19 -0800272 if (ruleIndex == 0U)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273 {
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700274 BMCWEB_LOG_DEBUG("Cannot match rules {}",
275 req->url().encoded_path());
P Dheeraj Srujan Kumara9f076e2021-10-18 22:45:37 +0530276 asyncResp->res.result(boost::beast::http::status::not_found);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 return;
278 }
279
280 if (ruleIndex >= rules.size())
Ed Tanous3174e4d2020-10-07 11:41:22 -0700281 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700282 throw std::runtime_error("Trie internal structure corrupted!");
Ed Tanous3174e4d2020-10-07 11:41:22 -0700283 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284
P Dheeraj Srujan Kumar7e9093e2021-09-18 01:19:04 +0530285 BaseRule& rule = *rules[ruleIndex];
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700287 BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}'", rule.rule);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700288
P Dheeraj Srujan Kumar7e9093e2021-09-18 01:19:04 +0530289 // 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.
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400291 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 });
Ed Tanous7045c8d2017-04-03 10:04:37 -0700297 }
298
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700299 void handle(const std::shared_ptr<Request>& req,
zhanghch058d1b46d2021-04-01 11:18:24 +0800300 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700301 {
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700302 FindRouteResponse foundRoute = findRoute(*req);
Ed Tanous44e45182022-07-26 16:47:23 -0700303
Ed Tanous759cf102022-07-31 16:36:52 -0700304 if (foundRoute.route.rule == nullptr)
Ed Tanous88a03c52022-03-14 10:16:07 -0700305 {
Ed Tanous759cf102022-07-31 16:36:52 -0700306 // Couldn't find a normal route with any verb, try looking for a 404
307 // route
308 if (foundRoute.allowHeader.empty())
Ed Tanous44e45182022-07-26 16:47:23 -0700309 {
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700310 foundRoute.route = findRouteByPerMethod(
311 req->url().encoded_path(), notFoundRoutes);
Ed Tanous759cf102022-07-31 16:36:52 -0700312 }
313 else
314 {
315 // See if we have a method not allowed (405) handler
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700316 foundRoute.route = findRouteByPerMethod(
317 req->url().encoded_path(), methodNotAllowedRoutes);
Ed Tanous44e45182022-07-26 16:47:23 -0700318 }
319 }
Ed Tanous759cf102022-07-31 16:36:52 -0700320
321 // Fill in the allow header if it's valid
322 if (!foundRoute.allowHeader.empty())
Ed Tanous44e45182022-07-26 16:47:23 -0700323 {
Ed Tanous88a03c52022-03-14 10:16:07 -0700324 asyncResp->res.addHeader(boost::beast::http::field::allow,
Ed Tanous44e45182022-07-26 16:47:23 -0700325 foundRoute.allowHeader);
Ed Tanous88a03c52022-03-14 10:16:07 -0700326 }
Tanousf00032d2018-11-05 01:18:10 -0300327
Ed Tanous44e45182022-07-26 16:47:23 -0700328 // If we couldn't find a real route or a 404 route, return a generic
329 // response
330 if (foundRoute.route.rule == nullptr)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700331 {
Ed Tanous44e45182022-07-26 16:47:23 -0700332 if (foundRoute.allowHeader.empty())
333 {
334 asyncResp->res.result(boost::beast::http::status::not_found);
335 }
336 else
Ed Tanous2634dcd2019-03-26 09:28:06 -0700337 {
Ed Tanous88a03c52022-03-14 10:16:07 -0700338 asyncResp->res.result(
339 boost::beast::http::status::method_not_allowed);
Ed Tanous2634dcd2019-03-26 09:28:06 -0700340 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700341 return;
342 }
343
Ed Tanous44e45182022-07-26 16:47:23 -0700344 BaseRule& rule = *foundRoute.route.rule;
Ed Tanous15a42df2023-02-09 18:08:23 -0800345 std::vector<std::string> params = std::move(foundRoute.route.params);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700346
Ed Tanous62598e32023-07-17 17:06:25 -0700347 BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule,
Ed Tanous50bfc912024-07-29 14:20:50 -0700348 req->methodString(), rule.getMethods());
Ed Tanous1abe55e2018-09-05 08:30:59 -0700349
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700350 if (req->session == nullptr)
James Feist7166bf02019-12-10 16:52:14 +0000351 {
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700352 rule.handle(*req, asyncResp, params);
James Feist7166bf02019-12-10 16:52:14 +0000353 return;
354 }
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700355 validatePrivilege(
356 req, asyncResp, rule,
357 [req, asyncResp, &rule, params = std::move(params)]() {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400358 rule.handle(*req, asyncResp, params);
359 });
Ed Tanous7045c8d2017-04-03 10:04:37 -0700360 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700361
Ed Tanous1abe55e2018-09-05 08:30:59 -0700362 void debugPrint()
363 {
Ed Tanous271584a2019-07-09 16:24:22 -0700364 for (size_t i = 0; i < perMethods.size(); i++)
Tanousf00032d2018-11-05 01:18:10 -0300365 {
Ed Tanousd9e89df2024-03-27 14:08:59 -0700366 BMCWEB_LOG_DEBUG("{}", httpVerbToString(static_cast<HttpVerb>(i)));
Tanousf00032d2018-11-05 01:18:10 -0300367 perMethods[i].trie.debugPrint();
368 }
Ed Tanous3dac7492017-08-02 13:46:20 -0700369 }
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700370
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 std::vector<const std::string*> getRoutes(const std::string& parent)
372 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700373 std::vector<const std::string*> ret;
Tanousf00032d2018-11-05 01:18:10 -0300374
375 for (const PerMethod& pm : perMethods)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700376 {
Tanousf00032d2018-11-05 01:18:10 -0300377 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 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 }
384 return ret;
385 }
386
387 private:
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700388 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods;
Ed Tanous888880a2020-08-24 13:48:50 -0700389
Ed Tanousa3b9eb92024-06-03 08:39:37 -0700390 PerMethod notFoundRoutes;
391 PerMethod upgradeRoutes;
392 PerMethod methodNotAllowedRoutes;
393
Tanousf00032d2018-11-05 01:18:10 -0300394 std::vector<std::unique_ptr<BaseRule>> allRules;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700395};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700396} // namespace crow