104e438cbSEd Tanous #pragma once 204e438cbSEd Tanous #include "logging.hpp" 33ccb3adbSEd Tanous #include "utils/hex_utils.hpp" 404e438cbSEd Tanous 5*27b0cf90SEd Tanous #include <boost/beast/http/file_body.hpp> 604e438cbSEd Tanous #include <boost/beast/http/message.hpp> 7*27b0cf90SEd Tanous #include <boost/beast/http/message_generator.hpp> 8d4b6c660SEd Tanous #include <boost/beast/http/string_body.hpp> 9*27b0cf90SEd 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> 1504e438cbSEd Tanous 1604e438cbSEd Tanous namespace crow 1704e438cbSEd Tanous { 1804e438cbSEd Tanous 1904e438cbSEd Tanous template <typename Adaptor, typename Handler> 2004e438cbSEd Tanous class Connection; 2104e438cbSEd Tanous 22*27b0cf90SEd Tanous namespace http = boost::beast::http; 23*27b0cf90SEd Tanous 2404e438cbSEd Tanous struct Response 2504e438cbSEd Tanous { 2604e438cbSEd Tanous template <typename Adaptor, typename Handler> 2704e438cbSEd Tanous friend class crow::Connection; 2804e438cbSEd Tanous 29*27b0cf90SEd Tanous using string_response = http::response<http::string_body>; 30*27b0cf90SEd Tanous using file_response = http::response<http::file_body>; 31*27b0cf90SEd Tanous 32*27b0cf90SEd Tanous // Use boost variant2 because it doesn't have valueless by exception 33*27b0cf90SEd Tanous boost::variant2::variant<string_response, file_response> response; 3404e438cbSEd Tanous 3504e438cbSEd Tanous nlohmann::json jsonValue; 36*27b0cf90SEd Tanous using fields_type = http::header<false, http::fields>; 37*27b0cf90SEd Tanous fields_type& fields() 38*27b0cf90SEd Tanous { 39*27b0cf90SEd Tanous return boost::variant2::visit( 40*27b0cf90SEd Tanous [](auto&& r) -> fields_type& { return r.base(); }, response); 41*27b0cf90SEd Tanous } 42*27b0cf90SEd Tanous 43*27b0cf90SEd Tanous const fields_type& fields() const 44*27b0cf90SEd Tanous { 45*27b0cf90SEd Tanous return boost::variant2::visit( 46*27b0cf90SEd Tanous [](auto&& r) -> const fields_type& { return r.base(); }, response); 47*27b0cf90SEd Tanous } 4804e438cbSEd Tanous 4926ccae32SEd Tanous void addHeader(std::string_view key, std::string_view value) 5004e438cbSEd Tanous { 51*27b0cf90SEd Tanous fields().insert(key, value); 5204e438cbSEd Tanous } 5304e438cbSEd Tanous 54*27b0cf90SEd Tanous void addHeader(http::field key, std::string_view value) 5504e438cbSEd Tanous { 56*27b0cf90SEd Tanous fields().insert(key, value); 57994fd86aSEd Tanous } 58994fd86aSEd Tanous 59*27b0cf90SEd Tanous void clearHeader(http::field key) 60994fd86aSEd Tanous { 61*27b0cf90SEd Tanous fields().erase(key); 6204e438cbSEd Tanous } 6304e438cbSEd Tanous 64*27b0cf90SEd Tanous Response() : response(string_response()) {} 6513548d85SEd Tanous Response(Response&& res) noexcept : 66*27b0cf90SEd Tanous response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 67*27b0cf90SEd 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 } 92*27b0cf90SEd 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 { 117*27b0cf90SEd Tanous fields().result(v); 1183590bd1dSNan Zhou } 1193590bd1dSNan Zhou 120*27b0cf90SEd Tanous void result(http::status v) 12104e438cbSEd Tanous { 122*27b0cf90SEd Tanous fields().result(v); 12304e438cbSEd Tanous } 12404e438cbSEd Tanous 125*27b0cf90SEd Tanous void copyBody(const Response& res) 12604e438cbSEd Tanous { 127*27b0cf90SEd Tanous const string_response* s = 128*27b0cf90SEd Tanous boost::variant2::get_if<string_response>(&(res.response)); 129*27b0cf90SEd Tanous if (s == nullptr) 130*27b0cf90SEd Tanous { 131*27b0cf90SEd Tanous BMCWEB_LOG_ERROR("Unable to copy a file"); 132*27b0cf90SEd Tanous return; 133*27b0cf90SEd Tanous } 134*27b0cf90SEd Tanous string_response* myString = 135*27b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 136*27b0cf90SEd Tanous if (myString == nullptr) 137*27b0cf90SEd Tanous { 138*27b0cf90SEd Tanous myString = &response.emplace<string_response>(); 139*27b0cf90SEd Tanous } 140*27b0cf90SEd Tanous myString->body() = s->body(); 141*27b0cf90SEd Tanous } 142*27b0cf90SEd Tanous 143*27b0cf90SEd Tanous http::status result() const 144*27b0cf90SEd Tanous { 145*27b0cf90SEd Tanous return fields().result(); 14604e438cbSEd Tanous } 14704e438cbSEd Tanous 148039a47e3SCarson Labrado unsigned resultInt() const 14904e438cbSEd Tanous { 150*27b0cf90SEd Tanous return fields().result_int(); 15104e438cbSEd Tanous } 15204e438cbSEd Tanous 153bb60f4deSEd Tanous std::string_view reason() const 15404e438cbSEd Tanous { 155*27b0cf90SEd Tanous return fields().reason(); 15604e438cbSEd Tanous } 15704e438cbSEd Tanous 15804e438cbSEd Tanous bool isCompleted() const noexcept 15904e438cbSEd Tanous { 16004e438cbSEd Tanous return completed; 16104e438cbSEd Tanous } 16204e438cbSEd Tanous 163*27b0cf90SEd Tanous const std::string* body() 16404e438cbSEd Tanous { 165*27b0cf90SEd Tanous string_response* body = 166*27b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 167*27b0cf90SEd Tanous if (body == nullptr) 168*27b0cf90SEd Tanous { 169*27b0cf90SEd Tanous return nullptr; 170*27b0cf90SEd Tanous } 171*27b0cf90SEd Tanous return &body->body(); 17204e438cbSEd Tanous } 17304e438cbSEd Tanous 17446a81465SCarson Labrado std::string_view getHeaderValue(std::string_view key) const 17546a81465SCarson Labrado { 176*27b0cf90SEd Tanous return fields()[key]; 17746a81465SCarson Labrado } 17846a81465SCarson Labrado 17904e438cbSEd Tanous void keepAlive(bool k) 18004e438cbSEd Tanous { 181*27b0cf90SEd Tanous return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); }, 182*27b0cf90SEd Tanous response); 18304e438cbSEd Tanous } 18404e438cbSEd Tanous 185bb60f4deSEd Tanous bool keepAlive() const 18604e438cbSEd Tanous { 187*27b0cf90SEd Tanous return boost::variant2::visit([](auto&& r) { return r.keep_alive(); }, 188*27b0cf90SEd Tanous response); 189*27b0cf90SEd Tanous } 190*27b0cf90SEd Tanous 191*27b0cf90SEd Tanous uint64_t getContentLength(boost::optional<uint64_t> pSize) 192*27b0cf90SEd Tanous { 193*27b0cf90SEd Tanous // This code is a throw-free equivalent to 194*27b0cf90SEd Tanous // beast::http::message::prepare_payload 195*27b0cf90SEd Tanous using http::status; 196*27b0cf90SEd Tanous using http::status_class; 197*27b0cf90SEd Tanous using http::to_status_class; 198*27b0cf90SEd Tanous if (!pSize) 199*27b0cf90SEd Tanous { 200*27b0cf90SEd Tanous return 0; 201*27b0cf90SEd Tanous } 202*27b0cf90SEd Tanous bool is1XXReturn = to_status_class(result()) == 203*27b0cf90SEd Tanous status_class::informational; 204*27b0cf90SEd Tanous if (*pSize > 0 && (is1XXReturn || result() == status::no_content || 205*27b0cf90SEd Tanous result() == status::not_modified)) 206*27b0cf90SEd Tanous { 207*27b0cf90SEd Tanous BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 208*27b0cf90SEd Tanous "no-content or not_modified, which aren't " 209*27b0cf90SEd Tanous "allowed to have a body", 210*27b0cf90SEd Tanous logPtr(this)); 211*27b0cf90SEd Tanous return 0; 212*27b0cf90SEd Tanous } 213*27b0cf90SEd Tanous return *pSize; 214*27b0cf90SEd Tanous } 215*27b0cf90SEd Tanous 216*27b0cf90SEd Tanous uint64_t size() 217*27b0cf90SEd Tanous { 218*27b0cf90SEd Tanous return boost::variant2::visit( 219*27b0cf90SEd Tanous [](auto&& res) -> uint64_t { return res.body().size(); }, response); 22004e438cbSEd Tanous } 22104e438cbSEd Tanous 22204e438cbSEd Tanous void preparePayload() 22304e438cbSEd Tanous { 224*27b0cf90SEd Tanous boost::variant2::visit( 225*27b0cf90SEd Tanous [this](auto&& r) { 226*27b0cf90SEd Tanous r.content_length(getContentLength(r.payload_size())); 227*27b0cf90SEd Tanous }, 228*27b0cf90SEd Tanous response); 22904e438cbSEd Tanous } 23004e438cbSEd Tanous 23104e438cbSEd Tanous void clear() 23204e438cbSEd Tanous { 23362598e31SEd Tanous BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 234*27b0cf90SEd 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 243*27b0cf90SEd 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 256*27b0cf90SEd Tanous void write(std::string&& bodyPart) 257*27b0cf90SEd Tanous { 258*27b0cf90SEd Tanous string_response* str = 259*27b0cf90SEd Tanous boost::variant2::get_if<string_response>(&response); 260*27b0cf90SEd Tanous if (str != nullptr) 261*27b0cf90SEd Tanous { 262*27b0cf90SEd Tanous str->body() += bodyPart; 263*27b0cf90SEd Tanous return; 264*27b0cf90SEd Tanous } 265*27b0cf90SEd Tanous response.emplace<string_response>(result(), 11, std::move(bodyPart)); 266*27b0cf90SEd Tanous } 267*27b0cf90SEd Tanous 2682d6cb56bSEd Tanous void end() 2692d6cb56bSEd Tanous { 2702d6cb56bSEd Tanous std::string etag = computeEtag(); 2712d6cb56bSEd Tanous if (!etag.empty()) 2722d6cb56bSEd Tanous { 273*27b0cf90SEd Tanous addHeader(http::field::etag, etag); 27489f18008SEd Tanous } 27504e438cbSEd Tanous if (completed) 27604e438cbSEd Tanous { 27762598e31SEd Tanous BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 27804e438cbSEd Tanous return; 27904e438cbSEd Tanous } 28004e438cbSEd Tanous completed = true; 28162598e31SEd Tanous BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 28204e438cbSEd Tanous if (completeRequestHandler) 28304e438cbSEd Tanous { 28462598e31SEd Tanous BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 28572374eb7SNan Zhou completeRequestHandler(*this); 28604e438cbSEd Tanous } 28704e438cbSEd Tanous } 28804e438cbSEd Tanous 289bb60f4deSEd Tanous bool isAlive() const 29004e438cbSEd Tanous { 29104e438cbSEd Tanous return isAliveHelper && isAliveHelper(); 29204e438cbSEd Tanous } 29304e438cbSEd Tanous 29472374eb7SNan Zhou void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 2954147b8acSJohn Edward Broadbent { 29662598e31SEd Tanous BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 29772374eb7SNan Zhou completeRequestHandler = std::move(handler); 29813548d85SEd Tanous 29913548d85SEd Tanous // Now that we have a new completion handler attached, we're no longer 30013548d85SEd Tanous // complete 30113548d85SEd Tanous completed = false; 30272374eb7SNan Zhou } 30372374eb7SNan Zhou 30472374eb7SNan Zhou std::function<void(Response&)> releaseCompleteRequestHandler() 30572374eb7SNan Zhou { 30662598e31SEd Tanous BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 30762598e31SEd Tanous static_cast<bool>(completeRequestHandler)); 30872374eb7SNan Zhou std::function<void(Response&)> ret = completeRequestHandler; 30972374eb7SNan Zhou completeRequestHandler = nullptr; 31013548d85SEd Tanous completed = true; 31172374eb7SNan Zhou return ret; 31272374eb7SNan Zhou } 31372374eb7SNan Zhou 31472374eb7SNan Zhou void setIsAliveHelper(std::function<bool()>&& handler) 31572374eb7SNan Zhou { 31672374eb7SNan Zhou isAliveHelper = std::move(handler); 31772374eb7SNan Zhou } 31872374eb7SNan Zhou 31972374eb7SNan Zhou std::function<bool()> releaseIsAliveHelper() 32072374eb7SNan Zhou { 32172374eb7SNan Zhou std::function<bool()> ret = std::move(isAliveHelper); 32272374eb7SNan Zhou isAliveHelper = nullptr; 32372374eb7SNan Zhou return ret; 3244147b8acSJohn Edward Broadbent } 3254147b8acSJohn Edward Broadbent 326291d709dSEd Tanous void setHashAndHandleNotModified() 327291d709dSEd Tanous { 328291d709dSEd Tanous // Can only hash if we have content that's valid 329*27b0cf90SEd Tanous if (jsonValue.empty() || result() != http::status::ok) 330291d709dSEd Tanous { 331291d709dSEd Tanous return; 332291d709dSEd Tanous } 333291d709dSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 334291d709dSEd Tanous std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 335*27b0cf90SEd Tanous addHeader(http::field::etag, hexVal); 336291d709dSEd Tanous if (expectedHash && hexVal == *expectedHash) 337291d709dSEd Tanous { 338a6695a84SEd Tanous jsonValue = nullptr; 339*27b0cf90SEd Tanous result(http::status::not_modified); 340291d709dSEd Tanous } 341291d709dSEd Tanous } 342291d709dSEd Tanous 343291d709dSEd Tanous void setExpectedHash(std::string_view hash) 344291d709dSEd Tanous { 345291d709dSEd Tanous expectedHash = hash; 346291d709dSEd Tanous } 347291d709dSEd Tanous 348*27b0cf90SEd Tanous using message_generator = http::message_generator; 349*27b0cf90SEd Tanous message_generator generator() 350*27b0cf90SEd Tanous { 351*27b0cf90SEd Tanous return boost::variant2::visit( 352*27b0cf90SEd Tanous [](auto& r) -> message_generator { return std::move(r); }, 353*27b0cf90SEd Tanous response); 354*27b0cf90SEd Tanous } 355*27b0cf90SEd Tanous 356*27b0cf90SEd Tanous bool openFile(const std::filesystem::path& path) 357*27b0cf90SEd Tanous { 358*27b0cf90SEd Tanous http::file_body::value_type file; 359*27b0cf90SEd Tanous boost::beast::error_code ec; 360*27b0cf90SEd Tanous file.open(path.c_str(), boost::beast::file_mode::read, ec); 361*27b0cf90SEd Tanous if (ec) 362*27b0cf90SEd Tanous { 363*27b0cf90SEd Tanous return false; 364*27b0cf90SEd Tanous } 365*27b0cf90SEd Tanous // store the headers on stack temporarily so we can reconstruct the new 366*27b0cf90SEd Tanous // base with the old headers copied in. 367*27b0cf90SEd Tanous http::header<false> headTemp = std::move(fields()); 368*27b0cf90SEd Tanous file_response& fileResponse = 369*27b0cf90SEd Tanous response.emplace<file_response>(std::move(headTemp)); 370*27b0cf90SEd Tanous fileResponse.body() = std::move(file); 371*27b0cf90SEd Tanous return true; 372*27b0cf90SEd Tanous } 373*27b0cf90SEd Tanous 37404e438cbSEd Tanous private: 375291d709dSEd Tanous std::optional<std::string> expectedHash; 37672374eb7SNan Zhou bool completed = false; 37772374eb7SNan Zhou std::function<void(Response&)> completeRequestHandler; 37804e438cbSEd Tanous std::function<bool()> isAliveHelper; 37904e438cbSEd Tanous }; 38004e438cbSEd Tanous } // namespace crow 381