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 = 157 RedfishAggregator::beginAggregation(req, asyncResp) == 158 Result::LocalHandle; 159 160 // If the request should be forwarded to a satellite BMC then we don't 161 // want to write anything to the asyncResp since it will get overwritten 162 // later. 163 } 164 165 // If this isn't a get, no need to do anything with parameters 166 if (req.method() != boost::beast::http::verb::get) 167 { 168 return needToCallHandlers; 169 } 170 171 delegated = query_param::delegate(queryCapabilities, *queryOpt); 172 std::function<void(crow::Response&)> handler = 173 asyncResp->res.releaseCompleteRequestHandler(); 174 175 asyncResp->res.setCompleteRequestHandler( 176 [&app, handler(std::move(handler)), query{std::move(*queryOpt)}, 177 delegated{delegated}](crow::Response& resIn) mutable { 178 processAllParams(app, query, delegated, handler, resIn); 179 }); 180 181 return needToCallHandlers; 182 } 183 184 // Sets up the Redfish Route. All parameters are handled by the default handler. 185 [[nodiscard]] inline bool 186 setUpRedfishRoute(crow::App& app, const crow::Request& req, 187 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 188 { 189 // This route |delegated| is never used 190 query_param::Query delegated; 191 return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, 192 query_param::QueryCapabilities{}); 193 } 194 } // namespace redfish 195