xref: /openbmc/bmcweb/http/http_response.hpp (revision 0242baffb0c05ea448d8bde0c09950d069a272a7)
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