/src/bmcweb/http/http_response.hpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | // SPDX-FileCopyrightText: Copyright OpenBMC Authors |
3 | | #pragma once |
4 | | #include "http_body.hpp" |
5 | | #include "logging.hpp" |
6 | | #include "utils/hex_utils.hpp" |
7 | | |
8 | | #include <fcntl.h> |
9 | | |
10 | | #include <boost/beast/core/error.hpp> |
11 | | #include <boost/beast/core/file_base.hpp> |
12 | | #include <boost/beast/http/field.hpp> |
13 | | #include <boost/beast/http/fields.hpp> |
14 | | #include <boost/beast/http/message.hpp> |
15 | | #include <boost/beast/http/status.hpp> |
16 | | #include <boost/url/url_view.hpp> |
17 | | #include <nlohmann/json.hpp> |
18 | | |
19 | | #include <cstddef> |
20 | | #include <cstdint> |
21 | | #include <filesystem> |
22 | | #include <functional> |
23 | | #include <optional> |
24 | | #include <string> |
25 | | #include <string_view> |
26 | | #include <utility> |
27 | | |
28 | | namespace crow |
29 | | { |
30 | | |
31 | | template <typename Adaptor, typename Handler> |
32 | | class Connection; |
33 | | |
34 | | namespace http = boost::beast::http; |
35 | | |
36 | | enum class OpenCode |
37 | | { |
38 | | Success, |
39 | | FileDoesNotExist, |
40 | | InternalError, |
41 | | }; |
42 | | |
43 | | struct Response |
44 | | { |
45 | | template <typename Adaptor, typename Handler> |
46 | | friend class Connection; |
47 | | |
48 | | http::response<bmcweb::HttpBody> response; |
49 | | |
50 | | nlohmann::json jsonValue; |
51 | | using fields_type = http::header<false, http::fields>; |
52 | | fields_type& fields() |
53 | 0 | { |
54 | 0 | return response.base(); |
55 | 0 | } |
56 | | |
57 | | const fields_type& fields() const |
58 | 0 | { |
59 | 0 | return response.base(); |
60 | 0 | } |
61 | | |
62 | | void addHeader(std::string_view key, std::string_view value) |
63 | 0 | { |
64 | 0 | fields().insert(key, value); |
65 | 0 | } |
66 | | |
67 | | void addHeader(http::field key, std::string_view value) |
68 | 0 | { |
69 | 0 | fields().insert(key, value); |
70 | 0 | } |
71 | | |
72 | | void clearHeader(http::field key) |
73 | 0 | { |
74 | 0 | fields().erase(key); |
75 | 0 | } |
76 | | |
77 | | Response() = default; |
78 | | Response(Response&& res) noexcept : |
79 | | response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), |
80 | | requestExpectedEtag(std::move(res.requestExpectedEtag)), |
81 | | currentOverrideEtag(std::move(res.currentOverrideEtag)), |
82 | | completed(res.completed) |
83 | 0 | { |
84 | 0 | // See note in operator= move handler for why this is needed. |
85 | 0 | if (!res.completed) |
86 | 0 | { |
87 | 0 | completeRequestHandler = std::move(res.completeRequestHandler); |
88 | 0 | res.completeRequestHandler = nullptr; |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | ~Response() = default; |
93 | | |
94 | | Response(const Response&) = delete; |
95 | | Response& operator=(const Response& r) = delete; |
96 | | |
97 | | Response& operator=(Response&& r) noexcept |
98 | 0 | { |
99 | 0 | BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", |
100 | 0 | logPtr(this), logPtr(&r)); |
101 | 0 | if (this == &r) |
102 | 0 | { |
103 | 0 | return *this; |
104 | 0 | } |
105 | 0 | response = std::move(r.response); |
106 | 0 | jsonValue = std::move(r.jsonValue); |
107 | 0 | requestExpectedEtag = std::move(r.requestExpectedEtag); |
108 | 0 | currentOverrideEtag = std::move(r.currentOverrideEtag); |
109 | 0 |
|
110 | 0 | // Only need to move completion handler if not already completed |
111 | 0 | // Note, there are cases where we might move out of a Response object |
112 | 0 | // while in a completion handler for that response object. This check |
113 | 0 | // is intended to prevent destructing the functor we are currently |
114 | 0 | // executing from in that case. |
115 | 0 | if (!r.completed) |
116 | 0 | { |
117 | 0 | completeRequestHandler = std::move(r.completeRequestHandler); |
118 | 0 | r.completeRequestHandler = nullptr; |
119 | 0 | } |
120 | 0 | else |
121 | 0 | { |
122 | 0 | completeRequestHandler = nullptr; |
123 | 0 | } |
124 | 0 | completed = r.completed; |
125 | 0 | return *this; |
126 | 0 | } |
127 | | |
128 | | void result(unsigned v) |
129 | 0 | { |
130 | 0 | fields().result(v); |
131 | 0 | } |
132 | | |
133 | | void result(http::status v) |
134 | 0 | { |
135 | 0 | fields().result(v); |
136 | 0 | } |
137 | | |
138 | | void copyBody(const Response& res) |
139 | 0 | { |
140 | 0 | response.body() = res.response.body(); |
141 | 0 | } |
142 | | |
143 | | http::status result() const |
144 | 0 | { |
145 | 0 | return fields().result(); |
146 | 0 | } |
147 | | |
148 | | unsigned resultInt() const |
149 | 0 | { |
150 | 0 | return fields().result_int(); |
151 | 0 | } |
152 | | |
153 | | std::string_view reason() const |
154 | 0 | { |
155 | 0 | return fields().reason(); |
156 | 0 | } |
157 | | |
158 | | bool isCompleted() const noexcept |
159 | 0 | { |
160 | 0 | return completed; |
161 | 0 | } |
162 | | |
163 | | const std::string* body() |
164 | 0 | { |
165 | 0 | return &response.body().str(); |
166 | 0 | } |
167 | | |
168 | | std::string_view getHeaderValue(std::string_view key) const |
169 | 0 | { |
170 | 0 | return fields()[key]; |
171 | 0 | } |
172 | | |
173 | | std::string_view getHeaderValue(boost::beast::http::field key) const |
174 | 0 | { |
175 | 0 | return fields()[key]; |
176 | 0 | } |
177 | | |
178 | | void keepAlive(bool k) |
179 | 0 | { |
180 | 0 | response.keep_alive(k); |
181 | 0 | } |
182 | | |
183 | | bool keepAlive() const |
184 | 0 | { |
185 | 0 | return response.keep_alive(); |
186 | 0 | } |
187 | | |
188 | | std::optional<uint64_t> size() |
189 | 0 | { |
190 | 0 | return response.body().payloadSize(); |
191 | 0 | } |
192 | | |
193 | | void preparePayload(const boost::urls::url_view& urlView) |
194 | 0 | { |
195 | 0 | std::optional<uint64_t> pSize = response.body().payloadSize(); |
196 | 0 |
|
197 | 0 | using http::status; |
198 | 0 | using http::status_class; |
199 | 0 | using http::to_status_class; |
200 | 0 | bool is1XXReturn = to_status_class(result()) == |
201 | 0 | status_class::informational; |
202 | 0 | if (!pSize) |
203 | 0 | { |
204 | 0 | response.chunked(true); |
205 | 0 | return; |
206 | 0 | } |
207 | 0 | response.content_length(*pSize); |
208 | 0 |
|
209 | 0 | if ((*pSize > 0) && (is1XXReturn || result() == status::no_content || |
210 | 0 | result() == status::not_modified)) |
211 | 0 | { |
212 | 0 | BMCWEB_LOG_CRITICAL("{} Response content provided but code was " |
213 | 0 | "no-content or not_modified, which aren't " |
214 | 0 | "allowed to have a body for url : \"{}\"", |
215 | 0 | logPtr(this), urlView.path()); |
216 | 0 | response.content_length(0); |
217 | 0 | return; |
218 | 0 | } |
219 | 0 | } |
220 | | |
221 | | void clear() |
222 | 0 | { |
223 | 0 | BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); |
224 | 0 | response.clear(); |
225 | 0 | response.body().clear(); |
226 | 0 |
|
227 | 0 | jsonValue = nullptr; |
228 | 0 | completed = false; |
229 | 0 | requestExpectedEtag = std::nullopt; |
230 | 0 | currentOverrideEtag = std::nullopt; |
231 | 0 | } |
232 | | |
233 | | void setCurrentOverrideEtag(std::string_view newEtag) |
234 | 0 | { |
235 | 0 | if (currentOverrideEtag) |
236 | 0 | { |
237 | 0 | BMCWEB_LOG_WARNING( |
238 | 0 | "Response override etag was incorrectly set twice"); |
239 | 0 | } |
240 | 0 | currentOverrideEtag = newEtag; |
241 | 0 | } |
242 | | |
243 | | std::string getCurrentEtag() const |
244 | 0 | { |
245 | 0 | // Only set etag if this request succeeded |
246 | 0 | if (result() != http::status::ok) |
247 | 0 | { |
248 | 0 | return ""; |
249 | 0 | } |
250 | 0 | // and the json response isn't empty |
251 | 0 | if (jsonValue.empty()) |
252 | 0 | { |
253 | 0 | return ""; |
254 | 0 | } |
255 | 0 |
|
256 | 0 | if (currentOverrideEtag) |
257 | 0 | { |
258 | 0 | return currentOverrideEtag.value(); |
259 | 0 | } |
260 | 0 |
|
261 | 0 | size_t hashval = std::hash<nlohmann::json>{}(jsonValue); |
262 | 0 | return std::format("\"{:08X}\"", hashval); |
263 | 0 | } |
264 | | |
265 | | void write(std::string&& bodyPart) |
266 | 0 | { |
267 | 0 | response.body().str() = std::move(bodyPart); |
268 | 0 | } |
269 | | |
270 | | void end() |
271 | 0 | { |
272 | 0 | if (completed) |
273 | 0 | { |
274 | 0 | BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); |
275 | 0 | return; |
276 | 0 | } |
277 | 0 | completed = true; |
278 | 0 | BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); |
279 | 0 | if (completeRequestHandler) |
280 | 0 | { |
281 | 0 | BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); |
282 | 0 | completeRequestHandler(*this); |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | | void setCompleteRequestHandler(std::function<void(Response&)>&& handler) |
287 | 0 | { |
288 | 0 | BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); |
289 | 0 | completeRequestHandler = std::move(handler); |
290 | 0 |
|
291 | 0 | // Now that we have a new completion handler attached, we're no longer |
292 | 0 | // complete |
293 | 0 | completed = false; |
294 | 0 | } |
295 | | |
296 | | std::function<void(Response&)> releaseCompleteRequestHandler() |
297 | 0 | { |
298 | 0 | BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), |
299 | 0 | static_cast<bool>(completeRequestHandler)); |
300 | 0 | std::function<void(Response&)> ret = completeRequestHandler; |
301 | 0 | completeRequestHandler = nullptr; |
302 | 0 | completed = true; |
303 | 0 | return ret; |
304 | 0 | } |
305 | | |
306 | | void setResponseEtagAndHandleNotModified() |
307 | 0 | { |
308 | 0 | // Can only hash if we have content that's valid |
309 | 0 | if (jsonValue.empty() || result() != http::status::ok) |
310 | 0 | { |
311 | 0 | return; |
312 | 0 | } |
313 | 0 | std::string hexVal = getCurrentEtag(); |
314 | 0 | addHeader(http::field::etag, hexVal); |
315 | 0 | if (requestExpectedEtag && hexVal == *requestExpectedEtag) |
316 | 0 | { |
317 | 0 | jsonValue = nullptr; |
318 | 0 | result(http::status::not_modified); |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | | std::optional<std::string_view> getExpectedEtag() const |
323 | 0 | { |
324 | 0 | return requestExpectedEtag; |
325 | 0 | } |
326 | | |
327 | | void setExpectedEtag(std::string_view etag) |
328 | 0 | { |
329 | 0 | if (requestExpectedEtag) |
330 | 0 | { |
331 | 0 | BMCWEB_LOG_WARNING( |
332 | 0 | "Request expected etag was incorrectly set twice"); |
333 | 0 | } |
334 | 0 | requestExpectedEtag = etag; |
335 | 0 | } |
336 | | |
337 | | OpenCode openFile( |
338 | | const std::filesystem::path& path, |
339 | | bmcweb::EncodingType enc = bmcweb::EncodingType::Raw, |
340 | | bmcweb::CompressionType comp = bmcweb::CompressionType::Raw) |
341 | 0 | { |
342 | 0 | boost::beast::error_code ec; |
343 | 0 | response.body().open(path.c_str(), boost::beast::file_mode::read, ec); |
344 | 0 | response.body().encodingType = enc; |
345 | 0 | response.body().compressionType = comp; |
346 | 0 | if (ec) |
347 | 0 | { |
348 | 0 | BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(), |
349 | 0 | ec.value()); |
350 | 0 | if (ec.value() == boost::system::errc::no_such_file_or_directory) |
351 | 0 | { |
352 | 0 | return OpenCode::FileDoesNotExist; |
353 | 0 | } |
354 | 0 | return OpenCode::InternalError; |
355 | 0 | } |
356 | 0 | return OpenCode::Success; |
357 | 0 | } |
358 | | |
359 | | bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) |
360 | 0 | { |
361 | 0 | boost::beast::error_code ec; |
362 | 0 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) |
363 | 0 | int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); |
364 | 0 | if (retval == -1) |
365 | 0 | { |
366 | 0 | BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed"); |
367 | 0 | } |
368 | 0 | response.body().encodingType = enc; |
369 | 0 | response.body().setFd(fd, ec); |
370 | 0 | if (ec) |
371 | 0 | { |
372 | 0 | BMCWEB_LOG_ERROR("Failed to set fd"); |
373 | 0 | return false; |
374 | 0 | } |
375 | 0 | return true; |
376 | 0 | } |
377 | | |
378 | | private: |
379 | | std::optional<std::string> requestExpectedEtag; |
380 | | std::optional<std::string> currentOverrideEtag; |
381 | | bool completed = false; |
382 | | std::function<void(Response&)> completeRequestHandler; |
383 | | }; |
384 | | } // namespace crow |