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 (ifMatch == "*") 72 { 73 // Representing any resource 74 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match 75 return true; 76 } 77 if (req.req.method() != boost::beast::http::verb::patch && 78 req.req.method() != boost::beast::http::verb::post && 79 req.req.method() != boost::beast::http::verb::delete_) 80 { 81 messages::preconditionFailed(asyncResp->res); 82 return false; 83 } 84 boost::system::error_code ec; 85 86 // Try to GET the same resource 87 crow::Request newReq( 88 {boost::beast::http::verb::get, req.url().encoded_path(), 11}, 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 newReq.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 getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front( 105 afterIfMatchRequest, std::ref(app), asyncResp, req, ifMatch)); 106 107 app.handle(newReq, getReqAsyncResp); 108 return false; 109 } 110 111 // Sets up the Redfish Route and delegates some of the query parameter 112 // processing. |queryCapabilities| stores which query parameters will be 113 // handled by redfish-core/lib codes, then default query parameter handler won't 114 // process these parameters. 115 [[nodiscard]] inline bool setUpRedfishRouteWithDelegation( 116 crow::App& app, const crow::Request& req, 117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 118 query_param::Query& delegated, 119 const query_param::QueryCapabilities& queryCapabilities) 120 { 121 BMCWEB_LOG_DEBUG << "setup redfish route"; 122 123 // Section 7.4 of the redfish spec "Redfish Services shall process the 124 // [OData-Version header] in the following table as defined by the HTTP 1.1 125 // specification..." 126 // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION 127 std::string_view odataHeader = req.getHeaderValue("OData-Version"); 128 if (!odataHeader.empty() && odataHeader != "4.0") 129 { 130 messages::preconditionFailed(asyncResp->res); 131 return false; 132 } 133 134 asyncResp->res.addHeader("OData-Version", "4.0"); 135 136 std::optional<query_param::Query> queryOpt = 137 query_param::parseParameters(req.url().params(), asyncResp->res); 138 if (queryOpt == std::nullopt) 139 { 140 return false; 141 } 142 143 if (!handleIfMatch(app, req, asyncResp)) 144 { 145 return false; 146 } 147 148 bool needToCallHandlers = true; 149 150 #ifdef BMCWEB_ENABLE_REDFISH_AGGREGATION 151 needToCallHandlers = RedfishAggregator::beginAggregation(req, asyncResp) == 152 Result::LocalHandle; 153 154 // If the request should be forwarded to a satellite BMC then we don't want 155 // to write anything to the asyncResp since it will get overwritten later. 156 #endif 157 158 // If this isn't a get, no need to do anything with parameters 159 if (req.method() != boost::beast::http::verb::get) 160 { 161 return needToCallHandlers; 162 } 163 164 delegated = query_param::delegate(queryCapabilities, *queryOpt); 165 std::function<void(crow::Response&)> handler = 166 asyncResp->res.releaseCompleteRequestHandler(); 167 168 asyncResp->res.setCompleteRequestHandler( 169 [&app, handler(std::move(handler)), query{std::move(*queryOpt)}, 170 delegated{delegated}](crow::Response& resIn) mutable { 171 processAllParams(app, query, delegated, handler, resIn); 172 }); 173 174 return needToCallHandlers; 175 } 176 177 // Sets up the Redfish Route. All parameters are handled by the default handler. 178 [[nodiscard]] inline bool 179 setUpRedfishRoute(crow::App& app, const crow::Request& req, 180 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 181 { 182 // This route |delegated| is never used 183 query_param::Query delegated; 184 return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, 185 query_param::QueryCapabilities{}); 186 } 187 } // namespace redfish 188