104e438cbSEd Tanous #pragma once 2*b5f288d2SAbhilash Raju #include "http_file_body.hpp" 304e438cbSEd Tanous #include "logging.hpp" 43ccb3adbSEd Tanous #include "utils/hex_utils.hpp" 504e438cbSEd Tanous 604e438cbSEd Tanous #include <boost/beast/http/message.hpp> 727b0cf90SEd Tanous #include <boost/beast/http/message_generator.hpp> 8d4b6c660SEd Tanous #include <boost/beast/http/string_body.hpp> 927b0cf90SEd Tanous #include <boost/variant2/variant.hpp> 10faf100f9SEd Tanous #include <nlohmann/json.hpp> 1104e438cbSEd Tanous 128a9a25c8SEd Tanous #include <optional> 1304e438cbSEd Tanous #include <string> 148a9a25c8SEd Tanous #include <string_view> 158e3f7032SAbhilash Raju #include <utility> 1604e438cbSEd Tanous namespace crow 1704e438cbSEd Tanous { 1804e438cbSEd Tanous 1904e438cbSEd Tanous template <typename Adaptor, typename Handler> 2004e438cbSEd Tanous class Connection; 2104e438cbSEd Tanous 2227b0cf90SEd Tanous namespace http = boost::beast::http; 2327b0cf90SEd Tanous 2404e438cbSEd Tanous struct Response 2504e438cbSEd Tanous { 2604e438cbSEd Tanous template <typename Adaptor, typename Handler> 2704e438cbSEd Tanous friend class crow::Connection; 2804e438cbSEd Tanous 2927b0cf90SEd Tanous using string_response = http::response<http::string_body>; 30*b5f288d2SAbhilash Raju using file_response = http::response<bmcweb::FileBody>; 3127b0cf90SEd Tanous 3227b0cf90SEd Tanous // Use boost variant2 because it doesn't have valueless by exception 3327b0cf90SEd Tanous boost::variant2::variant<string_response, file_response> response; 3404e438cbSEd Tanous 3504e438cbSEd Tanous nlohmann::json jsonValue; 3627b0cf90SEd Tanous using fields_type = http::header<false, http::fields>; 3727b0cf90SEd Tanous fields_type& fields() 3827b0cf90SEd Tanous { 3927b0cf90SEd Tanous return boost::variant2::visit( 4027b0cf90SEd Tanous [](auto&& r) -> fields_type& { return r.base(); }, response); 4127b0cf90SEd Tanous } 4227b0cf90SEd Tanous 4327b0cf90SEd Tanous const fields_type& fields() const 4427b0cf90SEd Tanous { 4527b0cf90SEd Tanous return boost::variant2::visit( 4627b0cf90SEd Tanous [](auto&& r) -> const fields_type& { return r.base(); }, response); 4727b0cf90SEd Tanous } 4804e438cbSEd Tanous 4926ccae32SEd Tanous void addHeader(std::string_view key, std::string_view value) 5004e438cbSEd Tanous { 5127b0cf90SEd Tanous fields().insert(key, value); 5204e438cbSEd Tanous } 5304e438cbSEd Tanous 5427b0cf90SEd Tanous void addHeader(http::field key, std::string_view value) 5504e438cbSEd Tanous { 5627b0cf90SEd Tanous fields().insert(key, value); 57994fd86aSEd Tanous } 58994fd86aSEd Tanous 5927b0cf90SEd Tanous void clearHeader(http::field key) 60994fd86aSEd Tanous { 6127b0cf90SEd Tanous fields().erase(key); 6204e438cbSEd Tanous } 6304e438cbSEd Tanous 6427b0cf90SEd Tanous Response() : response(string_response()) {} 6513548d85SEd Tanous Response(Response&& res) noexcept : 6627b0cf90SEd Tanous response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 6727b0cf90SEd Tanous completed(res.completed) 6813548d85SEd Tanous { 6913548d85SEd Tanous // See note in operator= move handler for why this is needed. 7013548d85SEd Tanous if (!res.completed) 7113548d85SEd Tanous { 7213548d85SEd Tanous completeRequestHandler = std::move(res.completeRequestHandler); 7313548d85SEd Tanous res.completeRequestHandler = nullptr; 7413548d85SEd Tanous } 7513548d85SEd Tanous isAliveHelper = res.isAliveHelper; 7613548d85SEd Tanous res.isAliveHelper = nullptr; 7713548d85SEd Tanous } 7813548d85SEd Tanous 79ecd6a3a2SEd Tanous ~Response() = default; 80ecd6a3a2SEd Tanous 81ecd6a3a2SEd Tanous Response(const Response&) = delete; 8204e438cbSEd Tanous Response& operator=(const Response& r) = delete; 8304e438cbSEd Tanous 8404e438cbSEd Tanous Response& operator=(Response&& r) noexcept 8504e438cbSEd Tanous { 8662598e31SEd Tanous BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 8762598e31SEd Tanous logPtr(this), logPtr(&r)); 8872374eb7SNan Zhou if (this == &r) 8972374eb7SNan Zhou { 9072374eb7SNan Zhou return *this; 9172374eb7SNan Zhou } 9227b0cf90SEd Tanous response = std::move(r.response); 9304e438cbSEd Tanous jsonValue = std::move(r.jsonValue); 9413548d85SEd Tanous 9513548d85SEd Tanous // Only need to move completion handler if not already completed 9613548d85SEd Tanous // Note, there are cases where we might move out of a Response object 9713548d85SEd Tanous // while in a completion handler for that response object. This check 9813548d85SEd Tanous // is intended to prevent destructing the functor we are currently 9913548d85SEd Tanous // executing from in that case. 10013548d85SEd Tanous if (!r.completed) 10113548d85SEd Tanous { 10272374eb7SNan Zhou completeRequestHandler = std::move(r.completeRequestHandler); 10372374eb7SNan Zhou r.completeRequestHandler = nullptr; 10413548d85SEd Tanous } 10513548d85SEd Tanous else 10613548d85SEd Tanous { 10713548d85SEd Tanous completeRequestHandler = nullptr; 10813548d85SEd Tanous } 10913548d85SEd Tanous completed = r.completed; 11013548d85SEd Tanous isAliveHelper = std::move(r.isAliveHelper); 11172374eb7SNan Zhou r.isAliveHelper = nullptr; 11204e438cbSEd Tanous return *this; 11304e438cbSEd Tanous } 11404e438cbSEd Tanous 1153590bd1dSNan Zhou void result(unsigned v) 1163590bd1dSNan Zhou { 11727b0cf90SEd Tanous fields().result(v); 1183590bd1dSNan Zhou } 1193590bd1dSNan Zhou 12027b0cf90SEd Tanous void result(http::status v) 12104e438cbSEd Tanous { 12227b0cf90SEd Tanous fields().result(v); 12304e438cbSEd Tanous } 12404e438cbSEd Tanous 12527b0cf90SEd Tanous void copyBody(const Response& res) 12604e438cbSEd Tanous { 12727b0cf90SEd Tanous const string_response* s = 12827b0cf90SEd Tanous boost::variant2::get_if<string_response>(&(res.response)); 12927b0cf90SEd Tanous if (s == nullptr) 13027b0cf90SEd Tanous { 13127b0cf90SEd Tanous BMCWEB_LOG_ERROR("Unable to copy a file"); 13227b0cf90SEd Tanous return; 13327b0cf90SEd Tanous } 13427b0cf90SEd Tanous string_response* myString = 13527b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 13627b0cf90SEd Tanous if (myString == nullptr) 13727b0cf90SEd Tanous { 13827b0cf90SEd Tanous myString = &response.emplace<string_response>(); 13927b0cf90SEd Tanous } 14027b0cf90SEd Tanous myString->body() = s->body(); 14127b0cf90SEd Tanous } 14227b0cf90SEd Tanous 14327b0cf90SEd Tanous http::status result() const 14427b0cf90SEd Tanous { 14527b0cf90SEd Tanous return fields().result(); 14604e438cbSEd Tanous } 14704e438cbSEd Tanous 148039a47e3SCarson Labrado unsigned resultInt() const 14904e438cbSEd Tanous { 15027b0cf90SEd Tanous return fields().result_int(); 15104e438cbSEd Tanous } 15204e438cbSEd Tanous 153bb60f4deSEd Tanous std::string_view reason() const 15404e438cbSEd Tanous { 15527b0cf90SEd Tanous return fields().reason(); 15604e438cbSEd Tanous } 15704e438cbSEd Tanous 15804e438cbSEd Tanous bool isCompleted() const noexcept 15904e438cbSEd Tanous { 16004e438cbSEd Tanous return completed; 16104e438cbSEd Tanous } 16204e438cbSEd Tanous 16327b0cf90SEd Tanous const std::string* body() 16404e438cbSEd Tanous { 16527b0cf90SEd Tanous string_response* body = 16627b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 16727b0cf90SEd Tanous if (body == nullptr) 16827b0cf90SEd Tanous { 16927b0cf90SEd Tanous return nullptr; 17027b0cf90SEd Tanous } 17127b0cf90SEd Tanous return &body->body(); 17204e438cbSEd Tanous } 17304e438cbSEd Tanous 17446a81465SCarson Labrado std::string_view getHeaderValue(std::string_view key) const 17546a81465SCarson Labrado { 17627b0cf90SEd Tanous return fields()[key]; 17746a81465SCarson Labrado } 17846a81465SCarson Labrado 17904e438cbSEd Tanous void keepAlive(bool k) 18004e438cbSEd Tanous { 18127b0cf90SEd Tanous return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); }, 18227b0cf90SEd Tanous response); 18304e438cbSEd Tanous } 18404e438cbSEd Tanous 185bb60f4deSEd Tanous bool keepAlive() const 18604e438cbSEd Tanous { 18727b0cf90SEd Tanous return boost::variant2::visit([](auto&& r) { return r.keep_alive(); }, 18827b0cf90SEd Tanous response); 18927b0cf90SEd Tanous } 19027b0cf90SEd Tanous 19127b0cf90SEd Tanous uint64_t getContentLength(boost::optional<uint64_t> pSize) 19227b0cf90SEd Tanous { 19327b0cf90SEd Tanous // This code is a throw-free equivalent to 19427b0cf90SEd Tanous // beast::http::message::prepare_payload 19527b0cf90SEd Tanous using http::status; 19627b0cf90SEd Tanous using http::status_class; 19727b0cf90SEd Tanous using http::to_status_class; 19827b0cf90SEd Tanous if (!pSize) 19927b0cf90SEd Tanous { 20027b0cf90SEd Tanous return 0; 20127b0cf90SEd Tanous } 20227b0cf90SEd Tanous bool is1XXReturn = to_status_class(result()) == 20327b0cf90SEd Tanous status_class::informational; 20427b0cf90SEd Tanous if (*pSize > 0 && (is1XXReturn || result() == status::no_content || 20527b0cf90SEd Tanous result() == status::not_modified)) 20627b0cf90SEd Tanous { 20727b0cf90SEd Tanous BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 20827b0cf90SEd Tanous "no-content or not_modified, which aren't " 20927b0cf90SEd Tanous "allowed to have a body", 21027b0cf90SEd Tanous logPtr(this)); 21127b0cf90SEd Tanous return 0; 21227b0cf90SEd Tanous } 21327b0cf90SEd Tanous return *pSize; 21427b0cf90SEd Tanous } 21527b0cf90SEd Tanous 21627b0cf90SEd Tanous uint64_t size() 21727b0cf90SEd Tanous { 21827b0cf90SEd Tanous return boost::variant2::visit( 21927b0cf90SEd Tanous [](auto&& res) -> uint64_t { return res.body().size(); }, response); 22004e438cbSEd Tanous } 22104e438cbSEd Tanous 22204e438cbSEd Tanous void preparePayload() 22304e438cbSEd Tanous { 22427b0cf90SEd Tanous boost::variant2::visit( 22527b0cf90SEd Tanous [this](auto&& r) { 22627b0cf90SEd Tanous r.content_length(getContentLength(r.payload_size())); 22727b0cf90SEd Tanous }, 22827b0cf90SEd Tanous response); 22904e438cbSEd Tanous } 23004e438cbSEd Tanous 23104e438cbSEd Tanous void clear() 23204e438cbSEd Tanous { 23362598e31SEd Tanous BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 23427b0cf90SEd Tanous response.emplace<string_response>(); 235a6695a84SEd Tanous jsonValue = nullptr; 23604e438cbSEd Tanous completed = false; 237291d709dSEd Tanous expectedHash = std::nullopt; 23804e438cbSEd Tanous } 23904e438cbSEd Tanous 2402d6cb56bSEd Tanous std::string computeEtag() const 24104e438cbSEd Tanous { 24289f18008SEd Tanous // Only set etag if this request succeeded 24327b0cf90SEd Tanous if (result() != http::status::ok) 24489f18008SEd Tanous { 2452d6cb56bSEd Tanous return ""; 24689f18008SEd Tanous } 2472d6cb56bSEd Tanous // and the json response isn't empty 2482d6cb56bSEd Tanous if (jsonValue.empty()) 2492d6cb56bSEd Tanous { 2502d6cb56bSEd Tanous return ""; 2512d6cb56bSEd Tanous } 2522d6cb56bSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 2532d6cb56bSEd Tanous return "\"" + intToHexString(hashval, 8) + "\""; 2542d6cb56bSEd Tanous } 2552d6cb56bSEd Tanous 25627b0cf90SEd Tanous void write(std::string&& bodyPart) 25727b0cf90SEd Tanous { 25827b0cf90SEd Tanous string_response* str = 25927b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 26027b0cf90SEd Tanous if (str != nullptr) 26127b0cf90SEd Tanous { 26227b0cf90SEd Tanous str->body() += bodyPart; 26327b0cf90SEd Tanous return; 26427b0cf90SEd Tanous } 2658e3f7032SAbhilash Raju http::header<false> headTemp = std::move(fields()); 2668e3f7032SAbhilash Raju string_response& stringResponse = 2678e3f7032SAbhilash Raju response.emplace<string_response>(std::move(headTemp)); 2688e3f7032SAbhilash Raju stringResponse.body() = std::move(bodyPart); 26927b0cf90SEd Tanous } 27027b0cf90SEd Tanous 2712d6cb56bSEd Tanous void end() 2722d6cb56bSEd Tanous { 2732d6cb56bSEd Tanous std::string etag = computeEtag(); 2742d6cb56bSEd Tanous if (!etag.empty()) 2752d6cb56bSEd Tanous { 27627b0cf90SEd Tanous addHeader(http::field::etag, etag); 27789f18008SEd Tanous } 27804e438cbSEd Tanous if (completed) 27904e438cbSEd Tanous { 28062598e31SEd Tanous BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 28104e438cbSEd Tanous return; 28204e438cbSEd Tanous } 28304e438cbSEd Tanous completed = true; 28462598e31SEd Tanous BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 28504e438cbSEd Tanous if (completeRequestHandler) 28604e438cbSEd Tanous { 28762598e31SEd Tanous BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 28872374eb7SNan Zhou completeRequestHandler(*this); 28904e438cbSEd Tanous } 29004e438cbSEd Tanous } 29104e438cbSEd Tanous 292bb60f4deSEd Tanous bool isAlive() const 29304e438cbSEd Tanous { 29404e438cbSEd Tanous return isAliveHelper && isAliveHelper(); 29504e438cbSEd Tanous } 29604e438cbSEd Tanous 29772374eb7SNan Zhou void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 2984147b8acSJohn Edward Broadbent { 29962598e31SEd Tanous BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 30072374eb7SNan Zhou completeRequestHandler = std::move(handler); 30113548d85SEd Tanous 30213548d85SEd Tanous // Now that we have a new completion handler attached, we're no longer 30313548d85SEd Tanous // complete 30413548d85SEd Tanous completed = false; 30572374eb7SNan Zhou } 30672374eb7SNan Zhou 30772374eb7SNan Zhou std::function<void(Response&)> releaseCompleteRequestHandler() 30872374eb7SNan Zhou { 30962598e31SEd Tanous BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 31062598e31SEd Tanous static_cast<bool>(completeRequestHandler)); 31172374eb7SNan Zhou std::function<void(Response&)> ret = completeRequestHandler; 31272374eb7SNan Zhou completeRequestHandler = nullptr; 31313548d85SEd Tanous completed = true; 31472374eb7SNan Zhou return ret; 31572374eb7SNan Zhou } 31672374eb7SNan Zhou 31772374eb7SNan Zhou void setIsAliveHelper(std::function<bool()>&& handler) 31872374eb7SNan Zhou { 31972374eb7SNan Zhou isAliveHelper = std::move(handler); 32072374eb7SNan Zhou } 32172374eb7SNan Zhou 32272374eb7SNan Zhou std::function<bool()> releaseIsAliveHelper() 32372374eb7SNan Zhou { 32472374eb7SNan Zhou std::function<bool()> ret = std::move(isAliveHelper); 32572374eb7SNan Zhou isAliveHelper = nullptr; 32672374eb7SNan Zhou return ret; 3274147b8acSJohn Edward Broadbent } 3284147b8acSJohn Edward Broadbent 329291d709dSEd Tanous void setHashAndHandleNotModified() 330291d709dSEd Tanous { 331291d709dSEd Tanous // Can only hash if we have content that's valid 33227b0cf90SEd Tanous if (jsonValue.empty() || result() != http::status::ok) 333291d709dSEd Tanous { 334291d709dSEd Tanous return; 335291d709dSEd Tanous } 336291d709dSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 337291d709dSEd Tanous std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 33827b0cf90SEd Tanous addHeader(http::field::etag, hexVal); 339291d709dSEd Tanous if (expectedHash && hexVal == *expectedHash) 340291d709dSEd Tanous { 341a6695a84SEd Tanous jsonValue = nullptr; 34227b0cf90SEd Tanous result(http::status::not_modified); 343291d709dSEd Tanous } 344291d709dSEd Tanous } 345291d709dSEd Tanous 346291d709dSEd Tanous void setExpectedHash(std::string_view hash) 347291d709dSEd Tanous { 348291d709dSEd Tanous expectedHash = hash; 349291d709dSEd Tanous } 350291d709dSEd Tanous 35127b0cf90SEd Tanous using message_generator = http::message_generator; 35227b0cf90SEd Tanous message_generator generator() 35327b0cf90SEd Tanous { 35427b0cf90SEd Tanous return boost::variant2::visit( 35527b0cf90SEd Tanous [](auto& r) -> message_generator { return std::move(r); }, 35627b0cf90SEd Tanous response); 35727b0cf90SEd Tanous } 35827b0cf90SEd Tanous 359*b5f288d2SAbhilash Raju bool openFile(const std::filesystem::path& path, 360*b5f288d2SAbhilash Raju bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 36127b0cf90SEd Tanous { 362*b5f288d2SAbhilash Raju file_response::body_type::value_type body(enc); 36327b0cf90SEd Tanous boost::beast::error_code ec; 364*b5f288d2SAbhilash Raju body.open(path.c_str(), boost::beast::file_mode::read, ec); 36527b0cf90SEd Tanous if (ec) 36627b0cf90SEd Tanous { 36727b0cf90SEd Tanous return false; 36827b0cf90SEd Tanous } 369*b5f288d2SAbhilash Raju updateFileBody(std::move(body)); 370*b5f288d2SAbhilash Raju return true; 371*b5f288d2SAbhilash Raju } 372*b5f288d2SAbhilash Raju 373*b5f288d2SAbhilash Raju bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 374*b5f288d2SAbhilash Raju { 375*b5f288d2SAbhilash Raju file_response::body_type::value_type body(enc); 376*b5f288d2SAbhilash Raju boost::beast::error_code ec; 377*b5f288d2SAbhilash Raju body.setFd(fd, ec); 378*b5f288d2SAbhilash Raju if (ec) 379*b5f288d2SAbhilash Raju { 380*b5f288d2SAbhilash Raju BMCWEB_LOG_ERROR("Failed to set fd"); 381*b5f288d2SAbhilash Raju return false; 382*b5f288d2SAbhilash Raju } 383*b5f288d2SAbhilash Raju updateFileBody(std::move(body)); 384*b5f288d2SAbhilash Raju return true; 385*b5f288d2SAbhilash Raju } 386*b5f288d2SAbhilash Raju 387*b5f288d2SAbhilash Raju private: 388*b5f288d2SAbhilash Raju void updateFileBody(file_response::body_type::value_type file) 389*b5f288d2SAbhilash Raju { 39027b0cf90SEd Tanous // store the headers on stack temporarily so we can reconstruct the new 39127b0cf90SEd Tanous // base with the old headers copied in. 39227b0cf90SEd Tanous http::header<false> headTemp = std::move(fields()); 39327b0cf90SEd Tanous file_response& fileResponse = 39427b0cf90SEd Tanous response.emplace<file_response>(std::move(headTemp)); 39527b0cf90SEd Tanous fileResponse.body() = std::move(file); 39627b0cf90SEd Tanous } 39727b0cf90SEd Tanous 398291d709dSEd Tanous std::optional<std::string> expectedHash; 39972374eb7SNan Zhou bool completed = false; 40072374eb7SNan Zhou std::function<void(Response&)> completeRequestHandler; 40104e438cbSEd Tanous std::function<bool()> isAliveHelper; 40204e438cbSEd Tanous }; 40304e438cbSEd Tanous } // namespace crow 404