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