104e438cbSEd Tanous #pragma once 2b2896149SEd Tanous #include "http_body.hpp" 304e438cbSEd Tanous #include "logging.hpp" 43ccb3adbSEd Tanous #include "utils/hex_utils.hpp" 504e438cbSEd Tanous 6*0242baffSEd Tanous #include <fcntl.h> 7*0242baffSEd Tanous 804e438cbSEd Tanous #include <boost/beast/http/message.hpp> 9faf100f9SEd Tanous #include <nlohmann/json.hpp> 1004e438cbSEd Tanous 118a9a25c8SEd Tanous #include <optional> 1204e438cbSEd Tanous #include <string> 138a9a25c8SEd Tanous #include <string_view> 148e3f7032SAbhilash Raju #include <utility> 15*0242baffSEd Tanous 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 29b2896149SEd Tanous http::response<bmcweb::HttpBody> response; 3004e438cbSEd Tanous 3104e438cbSEd Tanous nlohmann::json jsonValue; 3227b0cf90SEd Tanous using fields_type = http::header<false, http::fields>; 3327b0cf90SEd Tanous fields_type& fields() 3427b0cf90SEd Tanous { 3552e31629SEd Tanous return response.base(); 3627b0cf90SEd Tanous } 3727b0cf90SEd Tanous 3827b0cf90SEd Tanous const fields_type& fields() const 3927b0cf90SEd Tanous { 4052e31629SEd Tanous return response.base(); 4127b0cf90SEd Tanous } 4204e438cbSEd Tanous 4326ccae32SEd Tanous void addHeader(std::string_view key, std::string_view value) 4404e438cbSEd Tanous { 4527b0cf90SEd Tanous fields().insert(key, value); 4604e438cbSEd Tanous } 4704e438cbSEd Tanous 4827b0cf90SEd Tanous void addHeader(http::field key, std::string_view value) 4904e438cbSEd Tanous { 5027b0cf90SEd Tanous fields().insert(key, value); 51994fd86aSEd Tanous } 52994fd86aSEd Tanous 5327b0cf90SEd Tanous void clearHeader(http::field key) 54994fd86aSEd Tanous { 5527b0cf90SEd Tanous fields().erase(key); 5604e438cbSEd Tanous } 5704e438cbSEd Tanous 5852e31629SEd Tanous Response() = default; 5913548d85SEd Tanous Response(Response&& res) noexcept : 6027b0cf90SEd Tanous response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 6127b0cf90SEd Tanous completed(res.completed) 6213548d85SEd Tanous { 6313548d85SEd Tanous // See note in operator= move handler for why this is needed. 6413548d85SEd Tanous if (!res.completed) 6513548d85SEd Tanous { 6613548d85SEd Tanous completeRequestHandler = std::move(res.completeRequestHandler); 6713548d85SEd Tanous res.completeRequestHandler = nullptr; 6813548d85SEd Tanous } 6913548d85SEd Tanous } 7013548d85SEd Tanous 71ecd6a3a2SEd Tanous ~Response() = default; 72ecd6a3a2SEd Tanous 73ecd6a3a2SEd Tanous Response(const Response&) = delete; 7404e438cbSEd Tanous Response& operator=(const Response& r) = delete; 7504e438cbSEd Tanous 7604e438cbSEd Tanous Response& operator=(Response&& r) noexcept 7704e438cbSEd Tanous { 7862598e31SEd Tanous BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 7962598e31SEd Tanous logPtr(this), logPtr(&r)); 8072374eb7SNan Zhou if (this == &r) 8172374eb7SNan Zhou { 8272374eb7SNan Zhou return *this; 8372374eb7SNan Zhou } 8427b0cf90SEd Tanous response = std::move(r.response); 8504e438cbSEd Tanous jsonValue = std::move(r.jsonValue); 86499b5b4dSEd Tanous expectedHash = std::move(r.expectedHash); 8713548d85SEd Tanous 8813548d85SEd Tanous // Only need to move completion handler if not already completed 8913548d85SEd Tanous // Note, there are cases where we might move out of a Response object 9013548d85SEd Tanous // while in a completion handler for that response object. This check 9113548d85SEd Tanous // is intended to prevent destructing the functor we are currently 9213548d85SEd Tanous // executing from in that case. 9313548d85SEd Tanous if (!r.completed) 9413548d85SEd Tanous { 9572374eb7SNan Zhou completeRequestHandler = std::move(r.completeRequestHandler); 9672374eb7SNan Zhou r.completeRequestHandler = nullptr; 9713548d85SEd Tanous } 9813548d85SEd Tanous else 9913548d85SEd Tanous { 10013548d85SEd Tanous completeRequestHandler = nullptr; 10113548d85SEd Tanous } 10213548d85SEd Tanous completed = r.completed; 10304e438cbSEd Tanous return *this; 10404e438cbSEd Tanous } 10504e438cbSEd Tanous 1063590bd1dSNan Zhou void result(unsigned v) 1073590bd1dSNan Zhou { 10827b0cf90SEd Tanous fields().result(v); 1093590bd1dSNan Zhou } 1103590bd1dSNan Zhou 11127b0cf90SEd Tanous void result(http::status v) 11204e438cbSEd Tanous { 11327b0cf90SEd Tanous fields().result(v); 11404e438cbSEd Tanous } 11504e438cbSEd Tanous 11627b0cf90SEd Tanous void copyBody(const Response& res) 11704e438cbSEd Tanous { 11852e31629SEd Tanous response.body() = res.response.body(); 11927b0cf90SEd Tanous } 12027b0cf90SEd Tanous 12127b0cf90SEd Tanous http::status result() const 12227b0cf90SEd Tanous { 12327b0cf90SEd Tanous return fields().result(); 12404e438cbSEd Tanous } 12504e438cbSEd Tanous 126039a47e3SCarson Labrado unsigned resultInt() const 12704e438cbSEd Tanous { 12827b0cf90SEd Tanous return fields().result_int(); 12904e438cbSEd Tanous } 13004e438cbSEd Tanous 131bb60f4deSEd Tanous std::string_view reason() const 13204e438cbSEd Tanous { 13327b0cf90SEd Tanous return fields().reason(); 13404e438cbSEd Tanous } 13504e438cbSEd Tanous 13604e438cbSEd Tanous bool isCompleted() const noexcept 13704e438cbSEd Tanous { 13804e438cbSEd Tanous return completed; 13904e438cbSEd Tanous } 14004e438cbSEd Tanous 14127b0cf90SEd Tanous const std::string* body() 14204e438cbSEd Tanous { 14352e31629SEd Tanous return &response.body().str(); 14404e438cbSEd Tanous } 14504e438cbSEd Tanous 14646a81465SCarson Labrado std::string_view getHeaderValue(std::string_view key) const 14746a81465SCarson Labrado { 14827b0cf90SEd Tanous return fields()[key]; 14946a81465SCarson Labrado } 15046a81465SCarson Labrado 151499b5b4dSEd Tanous std::string_view getHeaderValue(boost::beast::http::field key) const 152499b5b4dSEd Tanous { 153499b5b4dSEd Tanous return fields()[key]; 154499b5b4dSEd Tanous } 155499b5b4dSEd Tanous 15604e438cbSEd Tanous void keepAlive(bool k) 15704e438cbSEd Tanous { 15852e31629SEd Tanous response.keep_alive(k); 15904e438cbSEd Tanous } 16004e438cbSEd Tanous 161bb60f4deSEd Tanous bool keepAlive() const 16204e438cbSEd Tanous { 16352e31629SEd Tanous return response.keep_alive(); 16427b0cf90SEd Tanous } 16527b0cf90SEd Tanous 16652e31629SEd Tanous std::optional<uint64_t> size() 16752e31629SEd Tanous { 16852e31629SEd Tanous return response.body().payloadSize(); 16952e31629SEd Tanous } 17052e31629SEd Tanous 17152e31629SEd Tanous void preparePayload() 17227b0cf90SEd Tanous { 17327b0cf90SEd Tanous // This code is a throw-free equivalent to 17427b0cf90SEd Tanous // beast::http::message::prepare_payload 17552e31629SEd Tanous std::optional<uint64_t> pSize = response.body().payloadSize(); 176*0242baffSEd Tanous 17727b0cf90SEd Tanous using http::status; 17827b0cf90SEd Tanous using http::status_class; 17927b0cf90SEd Tanous using http::to_status_class; 18027b0cf90SEd Tanous bool is1XXReturn = to_status_class(result()) == 18127b0cf90SEd Tanous status_class::informational; 182*0242baffSEd Tanous if (!pSize) 183*0242baffSEd Tanous { 184*0242baffSEd Tanous response.chunked(true); 185*0242baffSEd Tanous return; 186*0242baffSEd Tanous } 187*0242baffSEd Tanous response.content_length(*pSize); 188*0242baffSEd Tanous 189*0242baffSEd Tanous if (is1XXReturn || result() == status::no_content || 190*0242baffSEd Tanous result() == status::not_modified) 19127b0cf90SEd Tanous { 19227b0cf90SEd Tanous BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 19327b0cf90SEd Tanous "no-content or not_modified, which aren't " 19427b0cf90SEd Tanous "allowed to have a body", 19527b0cf90SEd Tanous logPtr(this)); 19652e31629SEd Tanous response.content_length(0); 19752e31629SEd Tanous return; 19827b0cf90SEd Tanous } 19904e438cbSEd Tanous } 20004e438cbSEd Tanous 20104e438cbSEd Tanous void clear() 20204e438cbSEd Tanous { 20362598e31SEd Tanous BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 20452e31629SEd Tanous response.clear(); 20506fc9bebSEd Tanous response.body().clear(); 20652e31629SEd Tanous 207a6695a84SEd Tanous jsonValue = nullptr; 20804e438cbSEd Tanous completed = false; 209291d709dSEd Tanous expectedHash = std::nullopt; 21004e438cbSEd Tanous } 21104e438cbSEd Tanous 2122d6cb56bSEd Tanous std::string computeEtag() const 21304e438cbSEd Tanous { 21489f18008SEd Tanous // Only set etag if this request succeeded 21527b0cf90SEd Tanous if (result() != http::status::ok) 21689f18008SEd Tanous { 2172d6cb56bSEd Tanous return ""; 21889f18008SEd Tanous } 2192d6cb56bSEd Tanous // and the json response isn't empty 2202d6cb56bSEd Tanous if (jsonValue.empty()) 2212d6cb56bSEd Tanous { 2222d6cb56bSEd Tanous return ""; 2232d6cb56bSEd Tanous } 2242d6cb56bSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 2252d6cb56bSEd Tanous return "\"" + intToHexString(hashval, 8) + "\""; 2262d6cb56bSEd Tanous } 2272d6cb56bSEd Tanous 22827b0cf90SEd Tanous void write(std::string&& bodyPart) 22927b0cf90SEd Tanous { 23052e31629SEd Tanous response.body().str() = std::move(bodyPart); 23127b0cf90SEd Tanous } 23227b0cf90SEd Tanous 2332d6cb56bSEd Tanous void end() 2342d6cb56bSEd Tanous { 23504e438cbSEd Tanous if (completed) 23604e438cbSEd Tanous { 23762598e31SEd Tanous BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 23804e438cbSEd Tanous return; 23904e438cbSEd Tanous } 24004e438cbSEd Tanous completed = true; 24162598e31SEd Tanous BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 24204e438cbSEd Tanous if (completeRequestHandler) 24304e438cbSEd Tanous { 24462598e31SEd Tanous BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 24572374eb7SNan Zhou completeRequestHandler(*this); 24604e438cbSEd Tanous } 24704e438cbSEd Tanous } 24804e438cbSEd Tanous 24972374eb7SNan Zhou void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 2504147b8acSJohn Edward Broadbent { 25162598e31SEd Tanous BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 25272374eb7SNan Zhou completeRequestHandler = std::move(handler); 25313548d85SEd Tanous 25413548d85SEd Tanous // Now that we have a new completion handler attached, we're no longer 25513548d85SEd Tanous // complete 25613548d85SEd Tanous completed = false; 25772374eb7SNan Zhou } 25872374eb7SNan Zhou 25972374eb7SNan Zhou std::function<void(Response&)> releaseCompleteRequestHandler() 26072374eb7SNan Zhou { 26162598e31SEd Tanous BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 26262598e31SEd Tanous static_cast<bool>(completeRequestHandler)); 26372374eb7SNan Zhou std::function<void(Response&)> ret = completeRequestHandler; 26472374eb7SNan Zhou completeRequestHandler = nullptr; 26513548d85SEd Tanous completed = true; 26672374eb7SNan Zhou return ret; 26772374eb7SNan Zhou } 26872374eb7SNan Zhou 269291d709dSEd Tanous void setHashAndHandleNotModified() 270291d709dSEd Tanous { 271291d709dSEd Tanous // Can only hash if we have content that's valid 27227b0cf90SEd Tanous if (jsonValue.empty() || result() != http::status::ok) 273291d709dSEd Tanous { 274291d709dSEd Tanous return; 275291d709dSEd Tanous } 276291d709dSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 277291d709dSEd Tanous std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 27827b0cf90SEd Tanous addHeader(http::field::etag, hexVal); 279291d709dSEd Tanous if (expectedHash && hexVal == *expectedHash) 280291d709dSEd Tanous { 281a6695a84SEd Tanous jsonValue = nullptr; 28227b0cf90SEd Tanous result(http::status::not_modified); 283291d709dSEd Tanous } 284291d709dSEd Tanous } 285291d709dSEd Tanous 286291d709dSEd Tanous void setExpectedHash(std::string_view hash) 287291d709dSEd Tanous { 288291d709dSEd Tanous expectedHash = hash; 289291d709dSEd Tanous } 290291d709dSEd Tanous 291b5f288d2SAbhilash Raju bool openFile(const std::filesystem::path& path, 292b5f288d2SAbhilash Raju bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 29327b0cf90SEd Tanous { 29427b0cf90SEd Tanous boost::beast::error_code ec; 29552e31629SEd Tanous response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 29652e31629SEd Tanous response.body().encodingType = enc; 29727b0cf90SEd Tanous if (ec) 29827b0cf90SEd Tanous { 29952e31629SEd Tanous BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str()); 30027b0cf90SEd Tanous return false; 30127b0cf90SEd Tanous } 302b5f288d2SAbhilash Raju return true; 303b5f288d2SAbhilash Raju } 304b5f288d2SAbhilash Raju 305b5f288d2SAbhilash Raju bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 306b5f288d2SAbhilash Raju { 307b5f288d2SAbhilash Raju boost::beast::error_code ec; 308*0242baffSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 309*0242baffSEd Tanous int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 310*0242baffSEd Tanous if (retval == -1) 311*0242baffSEd Tanous { 312*0242baffSEd Tanous BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed"); 313*0242baffSEd Tanous } 31452e31629SEd Tanous response.body().encodingType = enc; 31552e31629SEd Tanous response.body().setFd(fd, ec); 316b5f288d2SAbhilash Raju if (ec) 317b5f288d2SAbhilash Raju { 318b5f288d2SAbhilash Raju BMCWEB_LOG_ERROR("Failed to set fd"); 319b5f288d2SAbhilash Raju return false; 320b5f288d2SAbhilash Raju } 321b5f288d2SAbhilash Raju return true; 322b5f288d2SAbhilash Raju } 323b5f288d2SAbhilash Raju 324b5f288d2SAbhilash Raju private: 325291d709dSEd Tanous std::optional<std::string> expectedHash; 32672374eb7SNan Zhou bool completed = false; 32772374eb7SNan Zhou std::function<void(Response&)> completeRequestHandler; 32804e438cbSEd Tanous }; 32904e438cbSEd Tanous } // namespace crow 330