blob: 93cd4ac41fb249f63f90fedc2573a57355843528 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous7045c8d2017-04-03 10:04:37 -07003#pragma once
Ed Tanousb2896142024-01-31 15:25:47 -08004#include "http_body.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07005#include "logging.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08006#include "utils/hex_utils.hpp"
Ed Tanous1abe55e2018-09-05 08:30:59 -07007
Ed Tanous0242baf2024-05-16 19:52:47 -07008#include <fcntl.h>
9
Ed Tanousd43cd0c2020-09-30 20:46:53 -070010#include <boost/beast/http/message.hpp>
Ed Tanousfaf100f2023-05-25 10:03:14 -070011#include <nlohmann/json.hpp>
Ed Tanous7045c8d2017-04-03 10:04:37 -070012
Ed Tanous8a9a25c2021-05-11 14:50:58 -070013#include <optional>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050014#include <string>
Ed Tanous8a9a25c2021-05-11 14:50:58 -070015#include <string_view>
Abhilash Raju8e3f7032023-07-17 08:53:11 -050016#include <utility>
Ed Tanous0242baf2024-05-16 19:52:47 -070017
Ed Tanous1abe55e2018-09-05 08:30:59 -070018namespace crow
19{
Ed Tanouse0d918b2018-03-27 17:41:04 -070020
Ed Tanous52cc1122020-07-18 13:51:21 -070021template <typename Adaptor, typename Handler>
Ed Tanous7045c8d2017-04-03 10:04:37 -070022class Connection;
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010023
Ed Tanous27b0cf92023-08-07 12:02:40 -070024namespace http = boost::beast::http;
25
Myung Baed51c61b2024-09-13 10:35:34 -050026enum class OpenCode
27{
28 Success,
29 FileDoesNotExist,
30 InternalError,
31};
32
Ed Tanous1abe55e2018-09-05 08:30:59 -070033struct Response
34{
Ed Tanous52cc1122020-07-18 13:51:21 -070035 template <typename Adaptor, typename Handler>
Ed Tanous1abe55e2018-09-05 08:30:59 -070036 friend class crow::Connection;
Ed Tanous7045c8d2017-04-03 10:04:37 -070037
Ed Tanousb2896142024-01-31 15:25:47 -080038 http::response<bmcweb::HttpBody> response;
Ed Tanouse0d918b2018-03-27 17:41:04 -070039
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 nlohmann::json jsonValue;
Ed Tanous27b0cf92023-08-07 12:02:40 -070041 using fields_type = http::header<false, http::fields>;
42 fields_type& fields()
43 {
Ed Tanous52e31622024-01-23 16:31:11 -080044 return response.base();
Ed Tanous27b0cf92023-08-07 12:02:40 -070045 }
46
47 const fields_type& fields() const
48 {
Ed Tanous52e31622024-01-23 16:31:11 -080049 return response.base();
Ed Tanous27b0cf92023-08-07 12:02:40 -070050 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070051
Ed Tanous26ccae32023-02-16 10:28:44 -080052 void addHeader(std::string_view key, std::string_view value)
Ed Tanous1abe55e2018-09-05 08:30:59 -070053 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070054 fields().insert(key, value);
Ed Tanous7045c8d2017-04-03 10:04:37 -070055 }
Ed Tanous1abe55e2018-09-05 08:30:59 -070056
Ed Tanous27b0cf92023-08-07 12:02:40 -070057 void addHeader(http::field key, std::string_view value)
Ed Tanous1abe55e2018-09-05 08:30:59 -070058 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070059 fields().insert(key, value);
Ed Tanous994fd862023-06-06 13:37:03 -070060 }
61
Ed Tanous27b0cf92023-08-07 12:02:40 -070062 void clearHeader(http::field key)
Ed Tanous994fd862023-06-06 13:37:03 -070063 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070064 fields().erase(key);
Ed Tanous2cd4cc12018-07-25 10:51:19 -070065 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070066
Ed Tanous52e31622024-01-23 16:31:11 -080067 Response() = default;
Ed Tanous13548d82022-07-22 09:50:44 -070068 Response(Response&& res) noexcept :
Ed Tanous27b0cf92023-08-07 12:02:40 -070069 response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
70 completed(res.completed)
Ed Tanous13548d82022-07-22 09:50:44 -070071 {
Ed Tanous13548d82022-07-22 09:50:44 -070072 // See note in operator= move handler for why this is needed.
73 if (!res.completed)
74 {
75 completeRequestHandler = std::move(res.completeRequestHandler);
76 res.completeRequestHandler = nullptr;
77 }
Ed Tanous13548d82022-07-22 09:50:44 -070078 }
79
Ed Tanousecd6a3a2022-01-07 09:18:40 -080080 ~Response() = default;
81
82 Response(const Response&) = delete;
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 Response& operator=(const Response& r) = delete;
84
85 Response& operator=(Response&& r) noexcept
86 {
Ed Tanous62598e32023-07-17 17:06:25 -070087 BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
88 logPtr(this), logPtr(&r));
Nan Zhou72374eb2022-01-27 17:06:51 -080089 if (this == &r)
90 {
91 return *this;
92 }
Ed Tanous27b0cf92023-08-07 12:02:40 -070093 response = std::move(r.response);
Ed Tanous1abe55e2018-09-05 08:30:59 -070094 jsonValue = std::move(r.jsonValue);
Ed Tanous499b5b42024-04-06 08:39:18 -070095 expectedHash = std::move(r.expectedHash);
Ed Tanous13548d82022-07-22 09:50:44 -070096
97 // Only need to move completion handler if not already completed
98 // Note, there are cases where we might move out of a Response object
99 // while in a completion handler for that response object. This check
100 // is intended to prevent destructing the functor we are currently
101 // executing from in that case.
102 if (!r.completed)
103 {
104 completeRequestHandler = std::move(r.completeRequestHandler);
105 r.completeRequestHandler = nullptr;
106 }
107 else
108 {
109 completeRequestHandler = nullptr;
110 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700111 completed = r.completed;
112 return *this;
113 }
114
Nan Zhou3590bd12022-08-12 18:05:09 +0000115 void result(unsigned v)
116 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700117 fields().result(v);
Nan Zhou3590bd12022-08-12 18:05:09 +0000118 }
119
Ed Tanous27b0cf92023-08-07 12:02:40 -0700120 void result(http::status v)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700122 fields().result(v);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700123 }
124
Ed Tanous27b0cf92023-08-07 12:02:40 -0700125 void copyBody(const Response& res)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700126 {
Ed Tanous52e31622024-01-23 16:31:11 -0800127 response.body() = res.response.body();
Ed Tanous27b0cf92023-08-07 12:02:40 -0700128 }
129
130 http::status result() const
131 {
132 return fields().result();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700133 }
134
Carson Labrado039a47e2022-04-05 16:03:20 +0000135 unsigned resultInt() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700136 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700137 return fields().result_int();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700138 }
139
Ed Tanousbb60f4d2022-06-27 10:39:09 -0700140 std::string_view reason() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700142 return fields().reason();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700143 }
144
145 bool isCompleted() const noexcept
146 {
147 return completed;
148 }
149
Ed Tanous27b0cf92023-08-07 12:02:40 -0700150 const std::string* body()
Ed Tanous1abe55e2018-09-05 08:30:59 -0700151 {
Ed Tanous52e31622024-01-23 16:31:11 -0800152 return &response.body().str();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700153 }
154
Carson Labrado46a81462022-04-27 21:11:37 +0000155 std::string_view getHeaderValue(std::string_view key) const
156 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700157 return fields()[key];
Carson Labrado46a81462022-04-27 21:11:37 +0000158 }
159
Ed Tanous499b5b42024-04-06 08:39:18 -0700160 std::string_view getHeaderValue(boost::beast::http::field key) const
161 {
162 return fields()[key];
163 }
164
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 void keepAlive(bool k)
166 {
Ed Tanous52e31622024-01-23 16:31:11 -0800167 response.keep_alive(k);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700168 }
169
Ed Tanousbb60f4d2022-06-27 10:39:09 -0700170 bool keepAlive() const
Ed Tanousceac6f72018-12-02 11:58:47 -0800171 {
Ed Tanous52e31622024-01-23 16:31:11 -0800172 return response.keep_alive();
Ed Tanous27b0cf92023-08-07 12:02:40 -0700173 }
174
Ed Tanous52e31622024-01-23 16:31:11 -0800175 std::optional<uint64_t> size()
176 {
177 return response.body().payloadSize();
178 }
179
180 void preparePayload()
Ed Tanous27b0cf92023-08-07 12:02:40 -0700181 {
182 // This code is a throw-free equivalent to
183 // beast::http::message::prepare_payload
Ed Tanous52e31622024-01-23 16:31:11 -0800184 std::optional<uint64_t> pSize = response.body().payloadSize();
Ed Tanous0242baf2024-05-16 19:52:47 -0700185
Ed Tanous27b0cf92023-08-07 12:02:40 -0700186 using http::status;
187 using http::status_class;
188 using http::to_status_class;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700189 bool is1XXReturn = to_status_class(result()) ==
190 status_class::informational;
Ed Tanous0242baf2024-05-16 19:52:47 -0700191 if (!pSize)
192 {
193 response.chunked(true);
194 return;
195 }
196 response.content_length(*pSize);
197
198 if (is1XXReturn || result() == status::no_content ||
199 result() == status::not_modified)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700200 {
201 BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
202 "no-content or not_modified, which aren't "
203 "allowed to have a body",
204 logPtr(this));
Ed Tanous52e31622024-01-23 16:31:11 -0800205 response.content_length(0);
206 return;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700207 }
Ed Tanous23a21a12020-07-25 04:45:05 +0000208 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209
210 void clear()
211 {
Ed Tanous62598e32023-07-17 17:06:25 -0700212 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
Ed Tanous52e31622024-01-23 16:31:11 -0800213 response.clear();
Ed Tanous06fc9be2024-03-27 19:58:11 -0700214 response.body().clear();
Ed Tanous52e31622024-01-23 16:31:11 -0800215
Ed Tanousa6695a82023-05-16 11:39:18 -0700216 jsonValue = nullptr;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 completed = false;
Ed Tanous291d7092022-04-13 12:34:57 -0700218 expectedHash = std::nullopt;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 }
220
Ed Tanous2d6cb562022-07-07 20:44:54 -0700221 std::string computeEtag() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222 {
Ed Tanous89f18002022-03-24 18:38:24 -0700223 // Only set etag if this request succeeded
Ed Tanous27b0cf92023-08-07 12:02:40 -0700224 if (result() != http::status::ok)
Ed Tanous89f18002022-03-24 18:38:24 -0700225 {
Ed Tanous2d6cb562022-07-07 20:44:54 -0700226 return "";
227 }
228 // and the json response isn't empty
229 if (jsonValue.empty())
230 {
231 return "";
232 }
233 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
234 return "\"" + intToHexString(hashval, 8) + "\"";
235 }
236
Ed Tanous27b0cf92023-08-07 12:02:40 -0700237 void write(std::string&& bodyPart)
238 {
Ed Tanous52e31622024-01-23 16:31:11 -0800239 response.body().str() = std::move(bodyPart);
Ed Tanous27b0cf92023-08-07 12:02:40 -0700240 }
241
Ed Tanous2d6cb562022-07-07 20:44:54 -0700242 void end()
243 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700244 if (completed)
245 {
Ed Tanous62598e32023-07-17 17:06:25 -0700246 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 return;
248 }
249 completed = true;
Ed Tanous62598e32023-07-17 17:06:25 -0700250 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700251 if (completeRequestHandler)
252 {
Ed Tanous62598e32023-07-17 17:06:25 -0700253 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
Nan Zhou72374eb2022-01-27 17:06:51 -0800254 completeRequestHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 }
256 }
257
Nan Zhou72374eb2022-01-27 17:06:51 -0800258 void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
John Edward Broadbent4147b8a2021-07-19 16:52:24 -0700259 {
Ed Tanous62598e32023-07-17 17:06:25 -0700260 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
Nan Zhou72374eb2022-01-27 17:06:51 -0800261 completeRequestHandler = std::move(handler);
Ed Tanous13548d82022-07-22 09:50:44 -0700262
263 // Now that we have a new completion handler attached, we're no longer
264 // complete
265 completed = false;
Nan Zhou72374eb2022-01-27 17:06:51 -0800266 }
267
268 std::function<void(Response&)> releaseCompleteRequestHandler()
269 {
Ed Tanous62598e32023-07-17 17:06:25 -0700270 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
271 static_cast<bool>(completeRequestHandler));
Nan Zhou72374eb2022-01-27 17:06:51 -0800272 std::function<void(Response&)> ret = completeRequestHandler;
273 completeRequestHandler = nullptr;
Ed Tanous13548d82022-07-22 09:50:44 -0700274 completed = true;
Nan Zhou72374eb2022-01-27 17:06:51 -0800275 return ret;
276 }
277
Ed Tanous291d7092022-04-13 12:34:57 -0700278 void setHashAndHandleNotModified()
279 {
280 // Can only hash if we have content that's valid
Ed Tanous27b0cf92023-08-07 12:02:40 -0700281 if (jsonValue.empty() || result() != http::status::ok)
Ed Tanous291d7092022-04-13 12:34:57 -0700282 {
283 return;
284 }
285 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
286 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
Ed Tanous27b0cf92023-08-07 12:02:40 -0700287 addHeader(http::field::etag, hexVal);
Ed Tanous291d7092022-04-13 12:34:57 -0700288 if (expectedHash && hexVal == *expectedHash)
289 {
Ed Tanousa6695a82023-05-16 11:39:18 -0700290 jsonValue = nullptr;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700291 result(http::status::not_modified);
Ed Tanous291d7092022-04-13 12:34:57 -0700292 }
293 }
294
295 void setExpectedHash(std::string_view hash)
296 {
297 expectedHash = hash;
298 }
299
Myung Baed51c61b2024-09-13 10:35:34 -0500300 OpenCode openFile(const std::filesystem::path& path,
301 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700302 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700303 boost::beast::error_code ec;
Ed Tanous52e31622024-01-23 16:31:11 -0800304 response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
305 response.body().encodingType = enc;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700306 if (ec)
307 {
Myung Baed51c61b2024-09-13 10:35:34 -0500308 BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(),
309 ec.value());
310 if (ec.value() == boost::system::errc::no_such_file_or_directory)
311 {
312 return OpenCode::FileDoesNotExist;
313 }
314 return OpenCode::InternalError;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700315 }
Myung Baed51c61b2024-09-13 10:35:34 -0500316 return OpenCode::Success;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600317 }
318
319 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
320 {
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600321 boost::beast::error_code ec;
Ed Tanous0242baf2024-05-16 19:52:47 -0700322 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
323 int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
324 if (retval == -1)
325 {
326 BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed");
327 }
Ed Tanous52e31622024-01-23 16:31:11 -0800328 response.body().encodingType = enc;
329 response.body().setFd(fd, ec);
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600330 if (ec)
331 {
332 BMCWEB_LOG_ERROR("Failed to set fd");
333 return false;
334 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600335 return true;
336 }
337
338 private:
Ed Tanous291d7092022-04-13 12:34:57 -0700339 std::optional<std::string> expectedHash;
Nan Zhou72374eb2022-01-27 17:06:51 -0800340 bool completed = false;
341 std::function<void(Response&)> completeRequestHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700342};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700343} // namespace crow