xref: /openbmc/bmcweb/http/http_response.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
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 <nlohmann/json.hpp>
17 
18 #include <cstddef>
19 #include <cstdint>
20 #include <filesystem>
21 #include <functional>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 
27 namespace crow
28 {
29 
30 template <typename Adaptor, typename Handler>
31 class Connection;
32 
33 namespace http = boost::beast::http;
34 
35 enum class OpenCode
36 {
37     Success,
38     FileDoesNotExist,
39     InternalError,
40 };
41 
42 struct Response
43 {
44     template <typename Adaptor, typename Handler>
45     friend class crow::Connection;
46 
47     http::response<bmcweb::HttpBody> response;
48 
49     nlohmann::json jsonValue;
50     using fields_type = http::header<false, http::fields>;
fieldscrow::Response51     fields_type& fields()
52     {
53         return response.base();
54     }
55 
fieldscrow::Response56     const fields_type& fields() const
57     {
58         return response.base();
59     }
60 
addHeadercrow::Response61     void addHeader(std::string_view key, std::string_view value)
62     {
63         fields().insert(key, value);
64     }
65 
addHeadercrow::Response66     void addHeader(http::field key, std::string_view value)
67     {
68         fields().insert(key, value);
69     }
70 
clearHeadercrow::Response71     void clearHeader(http::field key)
72     {
73         fields().erase(key);
74     }
75 
76     Response() = default;
Responsecrow::Response77     Response(Response&& res) noexcept :
78         response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
79         completed(res.completed)
80     {
81         // See note in operator= move handler for why this is needed.
82         if (!res.completed)
83         {
84             completeRequestHandler = std::move(res.completeRequestHandler);
85             res.completeRequestHandler = nullptr;
86         }
87     }
88 
89     ~Response() = default;
90 
91     Response(const Response&) = delete;
92     Response& operator=(const Response& r) = delete;
93 
operator =crow::Response94     Response& operator=(Response&& r) noexcept
95     {
96         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
97                          logPtr(this), logPtr(&r));
98         if (this == &r)
99         {
100             return *this;
101         }
102         response = std::move(r.response);
103         jsonValue = std::move(r.jsonValue);
104         expectedHash = std::move(r.expectedHash);
105 
106         // Only need to move completion handler if not already completed
107         // Note, there are cases where we might move out of a Response object
108         // while in a completion handler for that response object.  This check
109         // is intended to prevent destructing the functor we are currently
110         // executing from in that case.
111         if (!r.completed)
112         {
113             completeRequestHandler = std::move(r.completeRequestHandler);
114             r.completeRequestHandler = nullptr;
115         }
116         else
117         {
118             completeRequestHandler = nullptr;
119         }
120         completed = r.completed;
121         return *this;
122     }
123 
resultcrow::Response124     void result(unsigned v)
125     {
126         fields().result(v);
127     }
128 
resultcrow::Response129     void result(http::status v)
130     {
131         fields().result(v);
132     }
133 
copyBodycrow::Response134     void copyBody(const Response& res)
135     {
136         response.body() = res.response.body();
137     }
138 
resultcrow::Response139     http::status result() const
140     {
141         return fields().result();
142     }
143 
resultIntcrow::Response144     unsigned resultInt() const
145     {
146         return fields().result_int();
147     }
148 
reasoncrow::Response149     std::string_view reason() const
150     {
151         return fields().reason();
152     }
153 
isCompletedcrow::Response154     bool isCompleted() const noexcept
155     {
156         return completed;
157     }
158 
bodycrow::Response159     const std::string* body()
160     {
161         return &response.body().str();
162     }
163 
getHeaderValuecrow::Response164     std::string_view getHeaderValue(std::string_view key) const
165     {
166         return fields()[key];
167     }
168 
getHeaderValuecrow::Response169     std::string_view getHeaderValue(boost::beast::http::field key) const
170     {
171         return fields()[key];
172     }
173 
keepAlivecrow::Response174     void keepAlive(bool k)
175     {
176         response.keep_alive(k);
177     }
178 
keepAlivecrow::Response179     bool keepAlive() const
180     {
181         return response.keep_alive();
182     }
183 
sizecrow::Response184     std::optional<uint64_t> size()
185     {
186         return response.body().payloadSize();
187     }
188 
preparePayloadcrow::Response189     void preparePayload()
190     {
191         // This code is a throw-free equivalent to
192         // beast::http::message::prepare_payload
193         std::optional<uint64_t> pSize = response.body().payloadSize();
194 
195         using http::status;
196         using http::status_class;
197         using http::to_status_class;
198         bool is1XXReturn = to_status_class(result()) ==
199                            status_class::informational;
200         if (!pSize)
201         {
202             response.chunked(true);
203             return;
204         }
205         response.content_length(*pSize);
206 
207         if (is1XXReturn || result() == status::no_content ||
208             result() == status::not_modified)
209         {
210             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
211                                 "no-content or not_modified, which aren't "
212                                 "allowed to have a body",
213                                 logPtr(this));
214             response.content_length(0);
215             return;
216         }
217     }
218 
clearcrow::Response219     void clear()
220     {
221         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
222         response.clear();
223         response.body().clear();
224 
225         jsonValue = nullptr;
226         completed = false;
227         expectedHash = std::nullopt;
228     }
229 
computeEtagcrow::Response230     std::string computeEtag() const
231     {
232         // Only set etag if this request succeeded
233         if (result() != http::status::ok)
234         {
235             return "";
236         }
237         // and the json response isn't empty
238         if (jsonValue.empty())
239         {
240             return "";
241         }
242         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
243         return "\"" + intToHexString(hashval, 8) + "\"";
244     }
245 
writecrow::Response246     void write(std::string&& bodyPart)
247     {
248         response.body().str() = std::move(bodyPart);
249     }
250 
endcrow::Response251     void end()
252     {
253         if (completed)
254         {
255             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
256             return;
257         }
258         completed = true;
259         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
260         if (completeRequestHandler)
261         {
262             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
263             completeRequestHandler(*this);
264         }
265     }
266 
setCompleteRequestHandlercrow::Response267     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
268     {
269         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
270         completeRequestHandler = std::move(handler);
271 
272         // Now that we have a new completion handler attached, we're no longer
273         // complete
274         completed = false;
275     }
276 
releaseCompleteRequestHandlercrow::Response277     std::function<void(Response&)> releaseCompleteRequestHandler()
278     {
279         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
280                          static_cast<bool>(completeRequestHandler));
281         std::function<void(Response&)> ret = completeRequestHandler;
282         completeRequestHandler = nullptr;
283         completed = true;
284         return ret;
285     }
286 
setHashAndHandleNotModifiedcrow::Response287     void setHashAndHandleNotModified()
288     {
289         // Can only hash if we have content that's valid
290         if (jsonValue.empty() || result() != http::status::ok)
291         {
292             return;
293         }
294         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
295         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
296         addHeader(http::field::etag, hexVal);
297         if (expectedHash && hexVal == *expectedHash)
298         {
299             jsonValue = nullptr;
300             result(http::status::not_modified);
301         }
302     }
303 
setExpectedHashcrow::Response304     void setExpectedHash(std::string_view hash)
305     {
306         expectedHash = hash;
307     }
308 
openFilecrow::Response309     OpenCode openFile(const std::filesystem::path& path,
310                       bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
311     {
312         boost::beast::error_code ec;
313         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
314         response.body().encodingType = enc;
315         if (ec)
316         {
317             BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(),
318                              ec.value());
319             if (ec.value() == boost::system::errc::no_such_file_or_directory)
320             {
321                 return OpenCode::FileDoesNotExist;
322             }
323             return OpenCode::InternalError;
324         }
325         return OpenCode::Success;
326     }
327 
openFdcrow::Response328     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
329     {
330         boost::beast::error_code ec;
331         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
332         int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
333         if (retval == -1)
334         {
335             BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed");
336         }
337         response.body().encodingType = enc;
338         response.body().setFd(fd, ec);
339         if (ec)
340         {
341             BMCWEB_LOG_ERROR("Failed to set fd");
342             return false;
343         }
344         return true;
345     }
346 
347   private:
348     std::optional<std::string> expectedHash;
349     bool completed = false;
350     std::function<void(Response&)> completeRequestHandler;
351 };
352 } // namespace crow
353