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