xref: /openbmc/bmcweb/http/http_body.hpp (revision b6164cbec4dd7f5c4e6e7667b203874e11cd8b3c)
1 #pragma once
2 
3 #include "logging.hpp"
4 #include "utility.hpp"
5 
6 #include <unistd.h>
7 
8 #include <boost/beast/core/buffers_range.hpp>
9 #include <boost/beast/core/file_posix.hpp>
10 #include <boost/beast/http/message.hpp>
11 #include <boost/system/error_code.hpp>
12 
13 #include <string_view>
14 
15 namespace bmcweb
16 {
17 struct HttpBody
18 {
19     class writer;
20     class reader;
21     class value_type;
22 };
23 
24 enum class EncodingType
25 {
26     Raw,
27     Base64,
28 };
29 
30 class HttpBody::value_type
31 {
32     boost::beast::file_posix fileHandle;
33     std::optional<size_t> fileSize;
34     std::string strBody;
35 
36   public:
37     EncodingType encodingType = EncodingType::Raw;
38 
39     ~value_type() = default;
40     value_type() = default;
41     explicit value_type(EncodingType enc) : encodingType(enc) {}
42     explicit value_type(std::string_view str) : strBody(str) {}
43 
44     value_type(value_type&& other) noexcept :
45         fileHandle(std::move(other.fileHandle)), fileSize(other.fileSize),
46         strBody(std::move(other.strBody)), encodingType(other.encodingType)
47     {}
48 
49     value_type& operator=(value_type&& other) noexcept
50     {
51         fileHandle = std::move(other.fileHandle);
52         fileSize = other.fileSize;
53         strBody = std::move(other.strBody);
54         encodingType = other.encodingType;
55 
56         return *this;
57     }
58 
59     // Overload copy constructor, because posix doesn't have dup(), but linux
60     // does
61     value_type(const value_type& other) :
62         fileSize(other.fileSize), strBody(other.strBody),
63         encodingType(other.encodingType)
64     {
65         fileHandle.native_handle(dup(other.fileHandle.native_handle()));
66     }
67 
68     value_type& operator=(const value_type& other)
69     {
70         if (this != &other)
71         {
72             fileSize = other.fileSize;
73             strBody = other.strBody;
74             encodingType = other.encodingType;
75             fileHandle.native_handle(dup(other.fileHandle.native_handle()));
76         }
77         return *this;
78     }
79 
80     const boost::beast::file_posix& file()
81     {
82         return fileHandle;
83     }
84 
85     std::string& str()
86     {
87         return strBody;
88     }
89 
90     const std::string& str() const
91     {
92         return strBody;
93     }
94 
95     std::optional<size_t> payloadSize() const
96     {
97         if (!fileHandle.is_open())
98         {
99             return strBody.size();
100         }
101         if (fileSize)
102         {
103             if (encodingType == EncodingType::Base64)
104             {
105                 return crow::utility::Base64Encoder::encodedSize(*fileSize);
106             }
107         }
108         return fileSize;
109     }
110 
111     void clear()
112     {
113         strBody.clear();
114         strBody.shrink_to_fit();
115         fileHandle = boost::beast::file_posix();
116         fileSize = std::nullopt;
117     }
118 
119     void open(const char* path, boost::beast::file_mode mode,
120               boost::system::error_code& ec)
121     {
122         fileHandle.open(path, mode, ec);
123         if (ec)
124         {
125             return;
126         }
127         boost::system::error_code ec2;
128         uint64_t size = fileHandle.size(ec2);
129         if (!ec2)
130         {
131             BMCWEB_LOG_INFO("File size was {} bytes", size);
132             fileSize = static_cast<size_t>(size);
133         }
134         else
135         {
136             BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
137         }
138         ec = {};
139     }
140 
141     void setFd(int fd, boost::system::error_code& ec)
142     {
143         fileHandle.native_handle(fd);
144 
145         boost::system::error_code ec2;
146         uint64_t size = fileHandle.size(ec2);
147         if (!ec2)
148         {
149             if (size != 0 && size < std::numeric_limits<size_t>::max())
150             {
151                 fileSize = static_cast<size_t>(size);
152             }
153         }
154         ec = {};
155     }
156 };
157 
158 class HttpBody::writer
159 {
160   public:
161     using const_buffers_type = boost::asio::const_buffer;
162 
163   private:
164     std::string buf;
165     crow::utility::Base64Encoder encoder;
166 
167     value_type& body;
168     size_t sent = 0;
169     constexpr static size_t readBufSize = 4096;
170     std::array<char, readBufSize> fileReadBuf{};
171 
172   public:
173     template <bool IsRequest, class Fields>
174     writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
175            value_type& bodyIn) :
176         body(bodyIn)
177     {}
178 
179     static void init(boost::beast::error_code& ec)
180     {
181         ec = {};
182     }
183 
184     boost::optional<std::pair<const_buffers_type, bool>>
185         get(boost::beast::error_code& ec)
186     {
187         return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
188     }
189 
190     boost::optional<std::pair<const_buffers_type, bool>>
191         getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
192     {
193         std::pair<const_buffers_type, bool> ret;
194         if (!body.file().is_open())
195         {
196             size_t remain = body.str().size() - sent;
197             size_t toReturn = std::min(maxSize, remain);
198             ret.first = const_buffers_type(&body.str()[sent], toReturn);
199 
200             sent += toReturn;
201             ret.second = sent < body.str().size();
202             BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
203                             ret.second);
204             return ret;
205         }
206         size_t readReq = std::min(fileReadBuf.size(), maxSize);
207         size_t read = body.file().read(fileReadBuf.data(), readReq, ec);
208         if (ec)
209         {
210             BMCWEB_LOG_CRITICAL("Failed to read from file");
211             return boost::none;
212         }
213 
214         std::string_view chunkView(fileReadBuf.data(), read);
215         BMCWEB_LOG_INFO("Read {} bytes from file", read);
216         // If the number of bytes read equals the amount requested, we haven't
217         // reached EOF yet
218         ret.second = read == readReq;
219         if (body.encodingType == EncodingType::Base64)
220         {
221             buf.clear();
222             buf.reserve(
223                 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
224             encoder.encode(chunkView, buf);
225             if (!ret.second)
226             {
227                 encoder.finalize(buf);
228             }
229             ret.first = const_buffers_type(buf.data(), buf.size());
230         }
231         else
232         {
233             ret.first = const_buffers_type(chunkView.data(), chunkView.size());
234         }
235         return ret;
236     }
237 };
238 
239 class HttpBody::reader
240 {
241     value_type& value;
242 
243   public:
244     template <bool IsRequest, class Fields>
245     reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
246            value_type& body) :
247         value(body)
248     {}
249 
250     void init(const boost::optional<std::uint64_t>& contentLength,
251               boost::beast::error_code& ec)
252     {
253         if (contentLength)
254         {
255             if (!value.file().is_open())
256             {
257                 value.str().reserve(static_cast<size_t>(*contentLength));
258             }
259         }
260         ec = {};
261     }
262 
263     template <class ConstBufferSequence>
264     std::size_t put(const ConstBufferSequence& buffers,
265                     boost::system::error_code& ec)
266     {
267         size_t extra = boost::beast::buffer_bytes(buffers);
268         for (const auto b : boost::beast::buffers_range_ref(buffers))
269         {
270             const char* ptr = static_cast<const char*>(b.data());
271             value.str() += std::string_view(ptr, b.size());
272         }
273         ec = {};
274         return extra;
275     }
276 
277     static void finish(boost::system::error_code& ec)
278     {
279         ec = {};
280     }
281 };
282 
283 } // namespace bmcweb
284