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