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