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