1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 304e438cbSEd Tanous #pragma once 4b2896149SEd Tanous #include "http_body.hpp" 504e438cbSEd Tanous #include "logging.hpp" 63ccb3adbSEd Tanous #include "utils/hex_utils.hpp" 704e438cbSEd Tanous 80242baffSEd Tanous #include <fcntl.h> 90242baffSEd Tanous 1004e438cbSEd Tanous #include <boost/beast/http/message.hpp> 11faf100f9SEd Tanous #include <nlohmann/json.hpp> 1204e438cbSEd Tanous 138a9a25c8SEd Tanous #include <optional> 1404e438cbSEd Tanous #include <string> 158a9a25c8SEd Tanous #include <string_view> 168e3f7032SAbhilash Raju #include <utility> 170242baffSEd Tanous 1804e438cbSEd Tanous namespace crow 1904e438cbSEd Tanous { 2004e438cbSEd Tanous 2104e438cbSEd Tanous template <typename Adaptor, typename Handler> 2204e438cbSEd Tanous class Connection; 2304e438cbSEd Tanous 2427b0cf90SEd Tanous namespace http = boost::beast::http; 2527b0cf90SEd Tanous 26d51c61b4SMyung Bae enum class OpenCode 27d51c61b4SMyung Bae { 28d51c61b4SMyung Bae Success, 29d51c61b4SMyung Bae FileDoesNotExist, 30d51c61b4SMyung Bae InternalError, 31d51c61b4SMyung Bae }; 32d51c61b4SMyung Bae 3304e438cbSEd Tanous struct Response 3404e438cbSEd Tanous { 3504e438cbSEd Tanous template <typename Adaptor, typename Handler> 3604e438cbSEd Tanous friend class crow::Connection; 3704e438cbSEd Tanous 38b2896149SEd Tanous http::response<bmcweb::HttpBody> response; 3904e438cbSEd Tanous 4004e438cbSEd Tanous nlohmann::json jsonValue; 4127b0cf90SEd Tanous using fields_type = http::header<false, http::fields>; fieldscrow::Response4227b0cf90SEd Tanous fields_type& fields() 4327b0cf90SEd Tanous { 4452e31629SEd Tanous return response.base(); 4527b0cf90SEd Tanous } 4627b0cf90SEd Tanous fieldscrow::Response4727b0cf90SEd Tanous const fields_type& fields() const 4827b0cf90SEd Tanous { 4952e31629SEd Tanous return response.base(); 5027b0cf90SEd Tanous } 5104e438cbSEd Tanous addHeadercrow::Response5226ccae32SEd Tanous void addHeader(std::string_view key, std::string_view value) 5304e438cbSEd Tanous { 5427b0cf90SEd Tanous fields().insert(key, value); 5504e438cbSEd Tanous } 5604e438cbSEd Tanous addHeadercrow::Response5727b0cf90SEd Tanous void addHeader(http::field key, std::string_view value) 5804e438cbSEd Tanous { 5927b0cf90SEd Tanous fields().insert(key, value); 60994fd86aSEd Tanous } 61994fd86aSEd Tanous clearHeadercrow::Response6227b0cf90SEd Tanous void clearHeader(http::field key) 63994fd86aSEd Tanous { 6427b0cf90SEd Tanous fields().erase(key); 6504e438cbSEd Tanous } 6604e438cbSEd Tanous 6752e31629SEd Tanous Response() = default; Responsecrow::Response6813548d85SEd Tanous Response(Response&& res) noexcept : 6927b0cf90SEd Tanous response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 7027b0cf90SEd Tanous completed(res.completed) 7113548d85SEd Tanous { 7213548d85SEd Tanous // See note in operator= move handler for why this is needed. 7313548d85SEd Tanous if (!res.completed) 7413548d85SEd Tanous { 7513548d85SEd Tanous completeRequestHandler = std::move(res.completeRequestHandler); 7613548d85SEd Tanous res.completeRequestHandler = nullptr; 7713548d85SEd Tanous } 7813548d85SEd Tanous } 7913548d85SEd Tanous 80ecd6a3a2SEd Tanous ~Response() = default; 81ecd6a3a2SEd Tanous 82ecd6a3a2SEd Tanous Response(const Response&) = delete; 8304e438cbSEd Tanous Response& operator=(const Response& r) = delete; 8404e438cbSEd Tanous operator =crow::Response8504e438cbSEd Tanous Response& operator=(Response&& r) noexcept 8604e438cbSEd Tanous { 8762598e31SEd Tanous BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 8862598e31SEd Tanous logPtr(this), logPtr(&r)); 8972374eb7SNan Zhou if (this == &r) 9072374eb7SNan Zhou { 9172374eb7SNan Zhou return *this; 9272374eb7SNan Zhou } 9327b0cf90SEd Tanous response = std::move(r.response); 9404e438cbSEd Tanous jsonValue = std::move(r.jsonValue); 95499b5b4dSEd Tanous expectedHash = std::move(r.expectedHash); 9613548d85SEd Tanous 9713548d85SEd Tanous // Only need to move completion handler if not already completed 9813548d85SEd Tanous // Note, there are cases where we might move out of a Response object 9913548d85SEd Tanous // while in a completion handler for that response object. This check 10013548d85SEd Tanous // is intended to prevent destructing the functor we are currently 10113548d85SEd Tanous // executing from in that case. 10213548d85SEd Tanous if (!r.completed) 10313548d85SEd Tanous { 10472374eb7SNan Zhou completeRequestHandler = std::move(r.completeRequestHandler); 10572374eb7SNan Zhou r.completeRequestHandler = nullptr; 10613548d85SEd Tanous } 10713548d85SEd Tanous else 10813548d85SEd Tanous { 10913548d85SEd Tanous completeRequestHandler = nullptr; 11013548d85SEd Tanous } 11113548d85SEd Tanous completed = r.completed; 11204e438cbSEd Tanous return *this; 11304e438cbSEd Tanous } 11404e438cbSEd Tanous resultcrow::Response1153590bd1dSNan Zhou void result(unsigned v) 1163590bd1dSNan Zhou { 11727b0cf90SEd Tanous fields().result(v); 1183590bd1dSNan Zhou } 1193590bd1dSNan Zhou resultcrow::Response12027b0cf90SEd Tanous void result(http::status v) 12104e438cbSEd Tanous { 12227b0cf90SEd Tanous fields().result(v); 12304e438cbSEd Tanous } 12404e438cbSEd Tanous copyBodycrow::Response12527b0cf90SEd Tanous void copyBody(const Response& res) 12604e438cbSEd Tanous { 12752e31629SEd Tanous response.body() = res.response.body(); 12827b0cf90SEd Tanous } 12927b0cf90SEd Tanous resultcrow::Response13027b0cf90SEd Tanous http::status result() const 13127b0cf90SEd Tanous { 13227b0cf90SEd Tanous return fields().result(); 13304e438cbSEd Tanous } 13404e438cbSEd Tanous resultIntcrow::Response135039a47e3SCarson Labrado unsigned resultInt() const 13604e438cbSEd Tanous { 13727b0cf90SEd Tanous return fields().result_int(); 13804e438cbSEd Tanous } 13904e438cbSEd Tanous reasoncrow::Response140bb60f4deSEd Tanous std::string_view reason() const 14104e438cbSEd Tanous { 14227b0cf90SEd Tanous return fields().reason(); 14304e438cbSEd Tanous } 14404e438cbSEd Tanous isCompletedcrow::Response14504e438cbSEd Tanous bool isCompleted() const noexcept 14604e438cbSEd Tanous { 14704e438cbSEd Tanous return completed; 14804e438cbSEd Tanous } 14904e438cbSEd Tanous bodycrow::Response15027b0cf90SEd Tanous const std::string* body() 15104e438cbSEd Tanous { 15252e31629SEd Tanous return &response.body().str(); 15304e438cbSEd Tanous } 15404e438cbSEd Tanous getHeaderValuecrow::Response15546a81465SCarson Labrado std::string_view getHeaderValue(std::string_view key) const 15646a81465SCarson Labrado { 15727b0cf90SEd Tanous return fields()[key]; 15846a81465SCarson Labrado } 15946a81465SCarson Labrado getHeaderValuecrow::Response160499b5b4dSEd Tanous std::string_view getHeaderValue(boost::beast::http::field key) const 161499b5b4dSEd Tanous { 162499b5b4dSEd Tanous return fields()[key]; 163499b5b4dSEd Tanous } 164499b5b4dSEd Tanous keepAlivecrow::Response16504e438cbSEd Tanous void keepAlive(bool k) 16604e438cbSEd Tanous { 16752e31629SEd Tanous response.keep_alive(k); 16804e438cbSEd Tanous } 16904e438cbSEd Tanous keepAlivecrow::Response170bb60f4deSEd Tanous bool keepAlive() const 17104e438cbSEd Tanous { 17252e31629SEd Tanous return response.keep_alive(); 17327b0cf90SEd Tanous } 17427b0cf90SEd Tanous sizecrow::Response17552e31629SEd Tanous std::optional<uint64_t> size() 17652e31629SEd Tanous { 17752e31629SEd Tanous return response.body().payloadSize(); 17852e31629SEd Tanous } 17952e31629SEd Tanous preparePayloadcrow::Response18052e31629SEd Tanous void preparePayload() 18127b0cf90SEd Tanous { 18227b0cf90SEd Tanous // This code is a throw-free equivalent to 18327b0cf90SEd Tanous // beast::http::message::prepare_payload 18452e31629SEd Tanous std::optional<uint64_t> pSize = response.body().payloadSize(); 1850242baffSEd Tanous 18627b0cf90SEd Tanous using http::status; 18727b0cf90SEd Tanous using http::status_class; 18827b0cf90SEd Tanous using http::to_status_class; 18927b0cf90SEd Tanous bool is1XXReturn = to_status_class(result()) == 19027b0cf90SEd Tanous status_class::informational; 1910242baffSEd Tanous if (!pSize) 1920242baffSEd Tanous { 1930242baffSEd Tanous response.chunked(true); 1940242baffSEd Tanous return; 1950242baffSEd Tanous } 1960242baffSEd Tanous response.content_length(*pSize); 1970242baffSEd Tanous 1980242baffSEd Tanous if (is1XXReturn || result() == status::no_content || 1990242baffSEd Tanous result() == status::not_modified) 20027b0cf90SEd Tanous { 20127b0cf90SEd Tanous BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 20227b0cf90SEd Tanous "no-content or not_modified, which aren't " 20327b0cf90SEd Tanous "allowed to have a body", 20427b0cf90SEd Tanous logPtr(this)); 20552e31629SEd Tanous response.content_length(0); 20652e31629SEd Tanous return; 20727b0cf90SEd Tanous } 20804e438cbSEd Tanous } 20904e438cbSEd Tanous clearcrow::Response21004e438cbSEd Tanous void clear() 21104e438cbSEd Tanous { 21262598e31SEd Tanous BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 21352e31629SEd Tanous response.clear(); 21406fc9bebSEd Tanous response.body().clear(); 21552e31629SEd Tanous 216a6695a84SEd Tanous jsonValue = nullptr; 21704e438cbSEd Tanous completed = false; 218291d709dSEd Tanous expectedHash = std::nullopt; 21904e438cbSEd Tanous } 22004e438cbSEd Tanous computeEtagcrow::Response2212d6cb56bSEd Tanous std::string computeEtag() const 22204e438cbSEd Tanous { 22389f18008SEd Tanous // Only set etag if this request succeeded 22427b0cf90SEd Tanous if (result() != http::status::ok) 22589f18008SEd Tanous { 2262d6cb56bSEd Tanous return ""; 22789f18008SEd Tanous } 2282d6cb56bSEd Tanous // and the json response isn't empty 2292d6cb56bSEd Tanous if (jsonValue.empty()) 2302d6cb56bSEd Tanous { 2312d6cb56bSEd Tanous return ""; 2322d6cb56bSEd Tanous } 2332d6cb56bSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 2342d6cb56bSEd Tanous return "\"" + intToHexString(hashval, 8) + "\""; 2352d6cb56bSEd Tanous } 2362d6cb56bSEd Tanous writecrow::Response23727b0cf90SEd Tanous void write(std::string&& bodyPart) 23827b0cf90SEd Tanous { 23952e31629SEd Tanous response.body().str() = std::move(bodyPart); 24027b0cf90SEd Tanous } 24127b0cf90SEd Tanous endcrow::Response2422d6cb56bSEd Tanous void end() 2432d6cb56bSEd Tanous { 24404e438cbSEd Tanous if (completed) 24504e438cbSEd Tanous { 24662598e31SEd Tanous BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 24704e438cbSEd Tanous return; 24804e438cbSEd Tanous } 24904e438cbSEd Tanous completed = true; 25062598e31SEd Tanous BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 25104e438cbSEd Tanous if (completeRequestHandler) 25204e438cbSEd Tanous { 25362598e31SEd Tanous BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 25472374eb7SNan Zhou completeRequestHandler(*this); 25504e438cbSEd Tanous } 25604e438cbSEd Tanous } 25704e438cbSEd Tanous setCompleteRequestHandlercrow::Response25872374eb7SNan Zhou void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 2594147b8acSJohn Edward Broadbent { 26062598e31SEd Tanous BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 26172374eb7SNan Zhou completeRequestHandler = std::move(handler); 26213548d85SEd Tanous 26313548d85SEd Tanous // Now that we have a new completion handler attached, we're no longer 26413548d85SEd Tanous // complete 26513548d85SEd Tanous completed = false; 26672374eb7SNan Zhou } 26772374eb7SNan Zhou releaseCompleteRequestHandlercrow::Response26872374eb7SNan Zhou std::function<void(Response&)> releaseCompleteRequestHandler() 26972374eb7SNan Zhou { 27062598e31SEd Tanous BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 27162598e31SEd Tanous static_cast<bool>(completeRequestHandler)); 27272374eb7SNan Zhou std::function<void(Response&)> ret = completeRequestHandler; 27372374eb7SNan Zhou completeRequestHandler = nullptr; 27413548d85SEd Tanous completed = true; 27572374eb7SNan Zhou return ret; 27672374eb7SNan Zhou } 27772374eb7SNan Zhou setHashAndHandleNotModifiedcrow::Response278291d709dSEd Tanous void setHashAndHandleNotModified() 279291d709dSEd Tanous { 280291d709dSEd Tanous // Can only hash if we have content that's valid 28127b0cf90SEd Tanous if (jsonValue.empty() || result() != http::status::ok) 282291d709dSEd Tanous { 283291d709dSEd Tanous return; 284291d709dSEd Tanous } 285291d709dSEd Tanous size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 286291d709dSEd Tanous std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 28727b0cf90SEd Tanous addHeader(http::field::etag, hexVal); 288291d709dSEd Tanous if (expectedHash && hexVal == *expectedHash) 289291d709dSEd Tanous { 290a6695a84SEd Tanous jsonValue = nullptr; 29127b0cf90SEd Tanous result(http::status::not_modified); 292291d709dSEd Tanous } 293291d709dSEd Tanous } 294291d709dSEd Tanous setExpectedHashcrow::Response295291d709dSEd Tanous void setExpectedHash(std::string_view hash) 296291d709dSEd Tanous { 297291d709dSEd Tanous expectedHash = hash; 298291d709dSEd Tanous } 299291d709dSEd Tanous openFilecrow::Response300d51c61b4SMyung Bae OpenCode openFile(const std::filesystem::path& path, 301b5f288d2SAbhilash Raju bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 30227b0cf90SEd Tanous { 30327b0cf90SEd Tanous boost::beast::error_code ec; 30452e31629SEd Tanous response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 30552e31629SEd Tanous response.body().encodingType = enc; 30627b0cf90SEd Tanous if (ec) 30727b0cf90SEd Tanous { 308d51c61b4SMyung Bae BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(), 309d51c61b4SMyung Bae ec.value()); 310d51c61b4SMyung Bae if (ec.value() == boost::system::errc::no_such_file_or_directory) 311d51c61b4SMyung Bae { 312d51c61b4SMyung Bae return OpenCode::FileDoesNotExist; 31327b0cf90SEd Tanous } 314d51c61b4SMyung Bae return OpenCode::InternalError; 315d51c61b4SMyung Bae } 316d51c61b4SMyung Bae return OpenCode::Success; 317b5f288d2SAbhilash Raju } 318b5f288d2SAbhilash Raju openFdcrow::Response319b5f288d2SAbhilash Raju bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 320b5f288d2SAbhilash Raju { 321b5f288d2SAbhilash Raju boost::beast::error_code ec; 3220242baffSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 3230242baffSEd Tanous int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 3240242baffSEd Tanous if (retval == -1) 3250242baffSEd Tanous { 3260242baffSEd Tanous BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed"); 3270242baffSEd Tanous } 32852e31629SEd Tanous response.body().encodingType = enc; 32952e31629SEd Tanous response.body().setFd(fd, ec); 330b5f288d2SAbhilash Raju if (ec) 331b5f288d2SAbhilash Raju { 332b5f288d2SAbhilash Raju BMCWEB_LOG_ERROR("Failed to set fd"); 333b5f288d2SAbhilash Raju return false; 334b5f288d2SAbhilash Raju } 335b5f288d2SAbhilash Raju return true; 336b5f288d2SAbhilash Raju } 337b5f288d2SAbhilash Raju 338b5f288d2SAbhilash Raju private: 339291d709dSEd Tanous std::optional<std::string> expectedHash; 34072374eb7SNan Zhou bool completed = false; 34172374eb7SNan Zhou std::function<void(Response&)> completeRequestHandler; 34204e438cbSEd Tanous }; 34304e438cbSEd Tanous } // namespace crow 344