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