xref: /openbmc/bmcweb/http/http_response.hpp (revision 08fad5d9dc59323a8916ff97a035221621047d8c)
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>;
fieldscrow::Response52     fields_type& fields()
53     {
54         return response.base();
55     }
56 
fieldscrow::Response57     const fields_type& fields() const
58     {
59         return response.base();
60     }
61 
addHeadercrow::Response62     void addHeader(std::string_view key, std::string_view value)
63     {
64         fields().insert(key, value);
65     }
66 
addHeadercrow::Response67     void addHeader(http::field key, std::string_view value)
68     {
69         fields().insert(key, value);
70     }
71 
clearHeadercrow::Response72     void clearHeader(http::field key)
73     {
74         fields().erase(key);
75     }
76 
77     Response() = default;
Responsecrow::Response78     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     {
84         // See note in operator= move handler for why this is needed.
85         if (!res.completed)
86         {
87             completeRequestHandler = std::move(res.completeRequestHandler);
88             res.completeRequestHandler = nullptr;
89         }
90     }
91 
92     ~Response() = default;
93 
94     Response(const Response&) = delete;
95     Response& operator=(const Response& r) = delete;
96 
operator =crow::Response97     Response& operator=(Response&& r) noexcept
98     {
99         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
100                          logPtr(this), logPtr(&r));
101         if (this == &r)
102         {
103             return *this;
104         }
105         response = std::move(r.response);
106         jsonValue = std::move(r.jsonValue);
107         requestExpectedEtag = std::move(r.requestExpectedEtag);
108         currentOverrideEtag = std::move(r.currentOverrideEtag);
109 
110         // Only need to move completion handler if not already completed
111         // Note, there are cases where we might move out of a Response object
112         // while in a completion handler for that response object.  This check
113         // is intended to prevent destructing the functor we are currently
114         // executing from in that case.
115         if (!r.completed)
116         {
117             completeRequestHandler = std::move(r.completeRequestHandler);
118             r.completeRequestHandler = nullptr;
119         }
120         else
121         {
122             completeRequestHandler = nullptr;
123         }
124         completed = r.completed;
125         return *this;
126     }
127 
resultcrow::Response128     void result(unsigned v)
129     {
130         fields().result(v);
131     }
132 
resultcrow::Response133     void result(http::status v)
134     {
135         fields().result(v);
136     }
137 
copyBodycrow::Response138     void copyBody(const Response& res)
139     {
140         response.body() = res.response.body();
141     }
142 
resultcrow::Response143     http::status result() const
144     {
145         return fields().result();
146     }
147 
resultIntcrow::Response148     unsigned resultInt() const
149     {
150         return fields().result_int();
151     }
152 
reasoncrow::Response153     std::string_view reason() const
154     {
155         return fields().reason();
156     }
157 
isCompletedcrow::Response158     bool isCompleted() const noexcept
159     {
160         return completed;
161     }
162 
bodycrow::Response163     const std::string* body()
164     {
165         return &response.body().str();
166     }
167 
getHeaderValuecrow::Response168     std::string_view getHeaderValue(std::string_view key) const
169     {
170         return fields()[key];
171     }
172 
getHeaderValuecrow::Response173     std::string_view getHeaderValue(boost::beast::http::field key) const
174     {
175         return fields()[key];
176     }
177 
keepAlivecrow::Response178     void keepAlive(bool k)
179     {
180         response.keep_alive(k);
181     }
182 
keepAlivecrow::Response183     bool keepAlive() const
184     {
185         return response.keep_alive();
186     }
187 
sizecrow::Response188     std::optional<uint64_t> size()
189     {
190         return response.body().payloadSize();
191     }
192 
preparePayloadcrow::Response193     void preparePayload(const boost::urls::url_view& urlView)
194     {
195         std::optional<uint64_t> pSize = response.body().payloadSize();
196 
197         using http::status;
198         using http::status_class;
199         using http::to_status_class;
200         bool is1XXReturn = to_status_class(result()) ==
201                            status_class::informational;
202         if (!pSize)
203         {
204             response.chunked(true);
205             return;
206         }
207         response.content_length(*pSize);
208 
209         if ((*pSize > 0) && (is1XXReturn || result() == status::no_content ||
210                              result() == status::not_modified))
211         {
212             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
213                                 "no-content or not_modified, which aren't "
214                                 "allowed to have a body for url : \"{}\"",
215                                 logPtr(this), urlView.path());
216             response.content_length(0);
217             return;
218         }
219     }
220 
clearcrow::Response221     void clear()
222     {
223         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
224         response.clear();
225         response.body().clear();
226 
227         jsonValue = nullptr;
228         completed = false;
229         requestExpectedEtag = std::nullopt;
230         currentOverrideEtag = std::nullopt;
231     }
232 
setCurrentOverrideEtagcrow::Response233     void setCurrentOverrideEtag(std::string_view newEtag)
234     {
235         if (currentOverrideEtag)
236         {
237             BMCWEB_LOG_WARNING(
238                 "Response override etag was incorrectly set twice");
239         }
240         currentOverrideEtag = newEtag;
241     }
242 
getCurrentEtagcrow::Response243     std::string getCurrentEtag() const
244     {
245         // Only set etag if this request succeeded
246         if (result() != http::status::ok)
247         {
248             return "";
249         }
250         // and the json response isn't empty
251         if (jsonValue.empty())
252         {
253             return "";
254         }
255 
256         if (currentOverrideEtag)
257         {
258             return currentOverrideEtag.value();
259         }
260 
261         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
262         return "\"" + intToHexString(hashval, 8) + "\"";
263     }
264 
writecrow::Response265     void write(std::string&& bodyPart)
266     {
267         response.body().str() = std::move(bodyPart);
268     }
269 
endcrow::Response270     void end()
271     {
272         if (completed)
273         {
274             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
275             return;
276         }
277         completed = true;
278         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
279         if (completeRequestHandler)
280         {
281             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
282             completeRequestHandler(*this);
283         }
284     }
285 
setCompleteRequestHandlercrow::Response286     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
287     {
288         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
289         completeRequestHandler = std::move(handler);
290 
291         // Now that we have a new completion handler attached, we're no longer
292         // complete
293         completed = false;
294     }
295 
releaseCompleteRequestHandlercrow::Response296     std::function<void(Response&)> releaseCompleteRequestHandler()
297     {
298         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
299                          static_cast<bool>(completeRequestHandler));
300         std::function<void(Response&)> ret = completeRequestHandler;
301         completeRequestHandler = nullptr;
302         completed = true;
303         return ret;
304     }
305 
setResponseEtagAndHandleNotModifiedcrow::Response306     void setResponseEtagAndHandleNotModified()
307     {
308         // Can only hash if we have content that's valid
309         if (jsonValue.empty() || result() != http::status::ok)
310         {
311             return;
312         }
313         std::string hexVal = getCurrentEtag();
314         addHeader(http::field::etag, hexVal);
315         if (requestExpectedEtag && hexVal == *requestExpectedEtag)
316         {
317             jsonValue = nullptr;
318             result(http::status::not_modified);
319         }
320     }
321 
getExpectedEtagcrow::Response322     std::optional<std::string_view> getExpectedEtag() const
323     {
324         return requestExpectedEtag;
325     }
326 
setExpectedEtagcrow::Response327     void setExpectedEtag(std::string_view etag)
328     {
329         if (requestExpectedEtag)
330         {
331             BMCWEB_LOG_WARNING(
332                 "Request expected etag was incorrectly set twice");
333         }
334         requestExpectedEtag = etag;
335     }
336 
openFilecrow::Response337     OpenCode openFile(
338         const std::filesystem::path& path,
339         bmcweb::EncodingType enc = bmcweb::EncodingType::Raw,
340         bmcweb::CompressionType comp = bmcweb::CompressionType::Raw)
341     {
342         boost::beast::error_code ec;
343         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
344         response.body().encodingType = enc;
345         response.body().compressionType = comp;
346         if (ec)
347         {
348             BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(),
349                              ec.value());
350             if (ec.value() == boost::system::errc::no_such_file_or_directory)
351             {
352                 return OpenCode::FileDoesNotExist;
353             }
354             return OpenCode::InternalError;
355         }
356         return OpenCode::Success;
357     }
358 
openFdcrow::Response359     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
360     {
361         boost::beast::error_code ec;
362         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
363         int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
364         if (retval == -1)
365         {
366             BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed");
367         }
368         response.body().encodingType = enc;
369         response.body().setFd(fd, ec);
370         if (ec)
371         {
372             BMCWEB_LOG_ERROR("Failed to set fd");
373             return false;
374         }
375         return true;
376     }
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
385