xref: /openbmc/bmcweb/http/http_response.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
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/http/message.hpp>
11 #include <nlohmann/json.hpp>
12 
13 #include <optional>
14 #include <string>
15 #include <string_view>
16 #include <utility>
17 
18 namespace crow
19 {
20 
21 template <typename Adaptor, typename Handler>
22 class Connection;
23 
24 namespace http = boost::beast::http;
25 
26 enum class OpenCode
27 {
28     Success,
29     FileDoesNotExist,
30     InternalError,
31 };
32 
33 struct Response
34 {
35     template <typename Adaptor, typename Handler>
36     friend class crow::Connection;
37 
38     http::response<bmcweb::HttpBody> response;
39 
40     nlohmann::json jsonValue;
41     using fields_type = http::header<false, http::fields>;
fieldscrow::Response42     fields_type& fields()
43     {
44         return response.base();
45     }
46 
fieldscrow::Response47     const fields_type& fields() const
48     {
49         return response.base();
50     }
51 
addHeadercrow::Response52     void addHeader(std::string_view key, std::string_view value)
53     {
54         fields().insert(key, value);
55     }
56 
addHeadercrow::Response57     void addHeader(http::field key, std::string_view value)
58     {
59         fields().insert(key, value);
60     }
61 
clearHeadercrow::Response62     void clearHeader(http::field key)
63     {
64         fields().erase(key);
65     }
66 
67     Response() = default;
Responsecrow::Response68     Response(Response&& res) noexcept :
69         response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
70         completed(res.completed)
71     {
72         // 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         }
78     }
79 
80     ~Response() = default;
81 
82     Response(const Response&) = delete;
83     Response& operator=(const Response& r) = delete;
84 
operator =crow::Response85     Response& operator=(Response&& r) noexcept
86     {
87         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
88                          logPtr(this), logPtr(&r));
89         if (this == &r)
90         {
91             return *this;
92         }
93         response = std::move(r.response);
94         jsonValue = std::move(r.jsonValue);
95         expectedHash = std::move(r.expectedHash);
96 
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         }
111         completed = r.completed;
112         return *this;
113     }
114 
resultcrow::Response115     void result(unsigned v)
116     {
117         fields().result(v);
118     }
119 
resultcrow::Response120     void result(http::status v)
121     {
122         fields().result(v);
123     }
124 
copyBodycrow::Response125     void copyBody(const Response& res)
126     {
127         response.body() = res.response.body();
128     }
129 
resultcrow::Response130     http::status result() const
131     {
132         return fields().result();
133     }
134 
resultIntcrow::Response135     unsigned resultInt() const
136     {
137         return fields().result_int();
138     }
139 
reasoncrow::Response140     std::string_view reason() const
141     {
142         return fields().reason();
143     }
144 
isCompletedcrow::Response145     bool isCompleted() const noexcept
146     {
147         return completed;
148     }
149 
bodycrow::Response150     const std::string* body()
151     {
152         return &response.body().str();
153     }
154 
getHeaderValuecrow::Response155     std::string_view getHeaderValue(std::string_view key) const
156     {
157         return fields()[key];
158     }
159 
getHeaderValuecrow::Response160     std::string_view getHeaderValue(boost::beast::http::field key) const
161     {
162         return fields()[key];
163     }
164 
keepAlivecrow::Response165     void keepAlive(bool k)
166     {
167         response.keep_alive(k);
168     }
169 
keepAlivecrow::Response170     bool keepAlive() const
171     {
172         return response.keep_alive();
173     }
174 
sizecrow::Response175     std::optional<uint64_t> size()
176     {
177         return response.body().payloadSize();
178     }
179 
preparePayloadcrow::Response180     void preparePayload()
181     {
182         // This code is a throw-free equivalent to
183         // beast::http::message::prepare_payload
184         std::optional<uint64_t> pSize = response.body().payloadSize();
185 
186         using http::status;
187         using http::status_class;
188         using http::to_status_class;
189         bool is1XXReturn = to_status_class(result()) ==
190                            status_class::informational;
191         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)
200         {
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));
205             response.content_length(0);
206             return;
207         }
208     }
209 
clearcrow::Response210     void clear()
211     {
212         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
213         response.clear();
214         response.body().clear();
215 
216         jsonValue = nullptr;
217         completed = false;
218         expectedHash = std::nullopt;
219     }
220 
computeEtagcrow::Response221     std::string computeEtag() const
222     {
223         // Only set etag if this request succeeded
224         if (result() != http::status::ok)
225         {
226             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 
writecrow::Response237     void write(std::string&& bodyPart)
238     {
239         response.body().str() = std::move(bodyPart);
240     }
241 
endcrow::Response242     void end()
243     {
244         if (completed)
245         {
246             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
247             return;
248         }
249         completed = true;
250         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
251         if (completeRequestHandler)
252         {
253             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
254             completeRequestHandler(*this);
255         }
256     }
257 
setCompleteRequestHandlercrow::Response258     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
259     {
260         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
261         completeRequestHandler = std::move(handler);
262 
263         // Now that we have a new completion handler attached, we're no longer
264         // complete
265         completed = false;
266     }
267 
releaseCompleteRequestHandlercrow::Response268     std::function<void(Response&)> releaseCompleteRequestHandler()
269     {
270         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
271                          static_cast<bool>(completeRequestHandler));
272         std::function<void(Response&)> ret = completeRequestHandler;
273         completeRequestHandler = nullptr;
274         completed = true;
275         return ret;
276     }
277 
setHashAndHandleNotModifiedcrow::Response278     void setHashAndHandleNotModified()
279     {
280         // Can only hash if we have content that's valid
281         if (jsonValue.empty() || result() != http::status::ok)
282         {
283             return;
284         }
285         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
286         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
287         addHeader(http::field::etag, hexVal);
288         if (expectedHash && hexVal == *expectedHash)
289         {
290             jsonValue = nullptr;
291             result(http::status::not_modified);
292         }
293     }
294 
setExpectedHashcrow::Response295     void setExpectedHash(std::string_view hash)
296     {
297         expectedHash = hash;
298     }
299 
openFilecrow::Response300     OpenCode openFile(const std::filesystem::path& path,
301                       bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
302     {
303         boost::beast::error_code ec;
304         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
305         response.body().encodingType = enc;
306         if (ec)
307         {
308             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;
315         }
316         return OpenCode::Success;
317     }
318 
openFdcrow::Response319     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
320     {
321         boost::beast::error_code ec;
322         // 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         }
328         response.body().encodingType = enc;
329         response.body().setFd(fd, ec);
330         if (ec)
331         {
332             BMCWEB_LOG_ERROR("Failed to set fd");
333             return false;
334         }
335         return true;
336     }
337 
338   private:
339     std::optional<std::string> expectedHash;
340     bool completed = false;
341     std::function<void(Response&)> completeRequestHandler;
342 };
343 } // namespace crow
344