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