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