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( 82 {boost::beast::http::verb::get, req.url().encoded_path(), 11}, ec); 83 84 if (ec) 85 { 86 messages::internalError(asyncResp->res); 87 return false; 88 } 89 90 // New request has the same credentials as the old request 91 newReq.session = req.session; 92 93 // Construct a new response object to fill in, and check the hash of before 94 // we modify the Resource. 95 std::shared_ptr<bmcweb::AsyncResp> getReqAsyncResp = 96 std::make_shared<bmcweb::AsyncResp>(); 97 98 getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front( 99 afterIfMatchRequest, std::ref(app), asyncResp, req, ifMatch)); 100 101 app.handle(newReq, getReqAsyncResp); 102 return false; 103 } 104 105 // Sets up the Redfish Route and delegates some of the query parameter 106 // processing. |queryCapabilities| stores which query parameters will be 107 // handled by redfish-core/lib codes, then default query parameter handler won't 108 // process these parameters. 109 [[nodiscard]] inline bool setUpRedfishRouteWithDelegation( 110 crow::App& app, const crow::Request& req, 111 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 112 query_param::Query& delegated, 113 const query_param::QueryCapabilities& queryCapabilities) 114 { 115 BMCWEB_LOG_DEBUG << "setup redfish route"; 116 117 // Section 7.4 of the redfish spec "Redfish Services shall process the 118 // [OData-Version header] in the following table as defined by the HTTP 1.1 119 // specification..." 120 // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION 121 std::string_view odataHeader = req.getHeaderValue("OData-Version"); 122 if (!odataHeader.empty() && odataHeader != "4.0") 123 { 124 messages::preconditionFailed(asyncResp->res); 125 return false; 126 } 127 128 asyncResp->res.addHeader("OData-Version", "4.0"); 129 130 std::optional<query_param::Query> queryOpt = 131 query_param::parseParameters(req.url().params(), asyncResp->res); 132 if (queryOpt == std::nullopt) 133 { 134 return false; 135 } 136 137 if (!handleIfMatch(app, req, asyncResp)) 138 { 139 return false; 140 } 141 142 bool needToCallHandlers = true; 143 144 #ifdef BMCWEB_ENABLE_REDFISH_AGGREGATION 145 needToCallHandlers = RedfishAggregator::getInstance().beginAggregation( 146 req, asyncResp) == Result::LocalHandle; 147 148 // If the request should be forwarded to a satellite BMC then we don't want 149 // to write anything to the asyncResp since it will get overwritten later. 150 #endif 151 152 // If this isn't a get, no need to do anything with parameters 153 if (req.method() != boost::beast::http::verb::get) 154 { 155 return needToCallHandlers; 156 } 157 158 delegated = query_param::delegate(queryCapabilities, *queryOpt); 159 std::function<void(crow::Response&)> handler = 160 asyncResp->res.releaseCompleteRequestHandler(); 161 162 asyncResp->res.setCompleteRequestHandler( 163 [&app, handler(std::move(handler)), 164 query{std::move(*queryOpt)}](crow::Response& resIn) mutable { 165 processAllParams(app, query, handler, resIn); 166 }); 167 168 return needToCallHandlers; 169 } 170 171 // Sets up the Redfish Route. All parameters are handled by the default handler. 172 [[nodiscard]] inline bool 173 setUpRedfishRoute(crow::App& app, const crow::Request& req, 174 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 175 { 176 // This route |delegated| is never used 177 query_param::Query delegated; 178 return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, 179 query_param::QueryCapabilities{}); 180 } 181 } // namespace redfish 182