1 #pragma once
2 
3 #include "bmcweb_config.h"
4 
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "error_messages.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "logging.hpp"
11 #include "utils/query_param.hpp"
12 
13 #include <boost/beast/http/verb.hpp>
14 #include <boost/url/params_view.hpp>
15 #include <boost/url/url_view.hpp>
16 
17 #include <functional>
18 #include <memory>
19 #include <optional>
20 #include <string>
21 #include <string_view>
22 #include <type_traits>
23 #include <utility>
24 
25 // IWYU pragma: no_forward_declare crow::App
26 // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
27 // IWYU pragma: no_include <boost/url/impl/url_view.hpp>
28 
29 #include "redfish_aggregator.hpp"
30 
31 namespace redfish
32 {
33 inline void afterIfMatchRequest(
34     crow::App& app, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
35     const std::shared_ptr<crow::Request>& req, const std::string& ifMatchHeader,
36     const crow::Response& resIn)
37 {
38     std::string computedEtag = resIn.computeEtag();
39     BMCWEB_LOG_DEBUG("User provided if-match etag {} computed etag {}",
40                      ifMatchHeader, computedEtag);
41     if (computedEtag != ifMatchHeader)
42     {
43         messages::preconditionFailed(asyncResp->res);
44         return;
45     }
46     // Restart the request without if-match
47     req->clearHeader(boost::beast::http::field::if_match);
48     BMCWEB_LOG_DEBUG("Restarting request");
49     app.handle(req, asyncResp);
50 }
51 
52 inline bool handleIfMatch(crow::App& app, const crow::Request& req,
53                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
54 {
55     if (req.session == nullptr)
56     {
57         // If the user isn't authenticated, don't even attempt to parse match
58         // parameters
59         return true;
60     }
61 
62     std::string ifMatch{
63         req.getHeaderValue(boost::beast::http::field::if_match)};
64     if (ifMatch.empty())
65     {
66         // No If-Match header.  Nothing to do
67         return true;
68     }
69     if (ifMatch == "*")
70     {
71         // Representing any resource
72         // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match
73         return true;
74     }
75     if (req.method() != boost::beast::http::verb::patch &&
76         req.method() != boost::beast::http::verb::post &&
77         req.method() != boost::beast::http::verb::delete_)
78     {
79         messages::preconditionFailed(asyncResp->res);
80         return false;
81     }
82     boost::system::error_code ec;
83 
84     // Try to GET the same resource
85     auto getReq = std::make_shared<crow::Request>(
86         crow::Request::Body{boost::beast::http::verb::get,
87                             req.url().encoded_path(), 11},
88         ec);
89 
90     if (ec)
91     {
92         messages::internalError(asyncResp->res);
93         return false;
94     }
95 
96     // New request has the same credentials as the old request
97     getReq->session = req.session;
98 
99     // Construct a new response object to fill in, and check the hash of before
100     // we modify the Resource.
101     std::shared_ptr<bmcweb::AsyncResp> getReqAsyncResp =
102         std::make_shared<bmcweb::AsyncResp>();
103 
104     // Ideally we would have a shared_ptr to the original Request which we could
105     // modify to remove the If-Match and restart it. But instead we have to make
106     // a full copy to restart it.
107     getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front(
108         afterIfMatchRequest, std::ref(app), asyncResp,
109         std::make_shared<crow::Request>(req), std::move(ifMatch)));
110 
111     app.handle(getReq, getReqAsyncResp);
112     return false;
113 }
114 
115 // Sets up the Redfish Route and delegates some of the query parameter
116 // processing. |queryCapabilities| stores which query parameters will be
117 // handled by redfish-core/lib codes, then default query parameter handler won't
118 // process these parameters.
119 [[nodiscard]] inline bool setUpRedfishRouteWithDelegation(
120     crow::App& app, const crow::Request& req,
121     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
122     query_param::Query& delegated,
123     const query_param::QueryCapabilities& queryCapabilities)
124 {
125     BMCWEB_LOG_DEBUG("setup redfish route");
126 
127     // Section 7.4 of the redfish spec "Redfish Services shall process the
128     // [OData-Version header] in the following table as defined by the HTTP 1.1
129     // specification..."
130     // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION
131     std::string_view odataHeader = req.getHeaderValue("OData-Version");
132     if (!odataHeader.empty() && odataHeader != "4.0")
133     {
134         messages::preconditionFailed(asyncResp->res);
135         return false;
136     }
137 
138     asyncResp->res.addHeader("OData-Version", "4.0");
139 
140     std::optional<query_param::Query> queryOpt =
141         query_param::parseParameters(req.url().params(), asyncResp->res);
142     if (!queryOpt)
143     {
144         return false;
145     }
146 
147     if (!handleIfMatch(app, req, asyncResp))
148     {
149         return false;
150     }
151 
152     bool needToCallHandlers = true;
153 
154     if constexpr (BMCWEB_REDFISH_AGGREGATION)
155     {
156         needToCallHandlers = RedfishAggregator::beginAggregation(
157                                  req, asyncResp) == Result::LocalHandle;
158 
159         // If the request should be forwarded to a satellite BMC then we don't
160         // want to write anything to the asyncResp since it will get overwritten
161         // later.
162     }
163 
164     // If this isn't a get, no need to do anything with parameters
165     if (req.method() != boost::beast::http::verb::get)
166     {
167         return needToCallHandlers;
168     }
169 
170     delegated = query_param::delegate(queryCapabilities, *queryOpt);
171     std::function<void(crow::Response&)> handler =
172         asyncResp->res.releaseCompleteRequestHandler();
173 
174     asyncResp->res.setCompleteRequestHandler(
175         [&app, handler(std::move(handler)), query{std::move(*queryOpt)},
176          delegated{delegated}](crow::Response& resIn) mutable {
177         processAllParams(app, query, delegated, handler, resIn);
178     });
179 
180     return needToCallHandlers;
181 }
182 
183 // Sets up the Redfish Route. All parameters are handled by the default handler.
184 [[nodiscard]] inline bool
185     setUpRedfishRoute(crow::App& app, const crow::Request& req,
186                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
187 {
188     // This route |delegated| is never used
189     query_param::Query delegated;
190     return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated,
191                                            query_param::QueryCapabilities{});
192 }
193 } // namespace redfish
194