blob: 7be23f0809794b18a930155e671bcbb7ef719dee [file] [log] [blame]
Abhilash Rajub5f288d2023-11-08 22:32:44 -06001#pragma once
2
Ed Tanous52e31622024-01-23 16:31:11 -08003#include "logging.hpp"
Abhilash Rajub5f288d2023-11-08 22:32:44 -06004#include "utility.hpp"
5
Ed Tanous88c7c422024-04-06 08:52:40 -07006#include <fcntl.h>
Ed Tanous52e31622024-01-23 16:31:11 -08007#include <unistd.h>
8
9#include <boost/beast/core/buffers_range.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060010#include <boost/beast/core/file_posix.hpp>
11#include <boost/beast/http/message.hpp>
12#include <boost/system/error_code.hpp>
13
Ed Tanous52e31622024-01-23 16:31:11 -080014#include <string_view>
15
Abhilash Rajub5f288d2023-11-08 22:32:44 -060016namespace bmcweb
17{
Ed Tanousb2896142024-01-31 15:25:47 -080018struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060019{
Ed Tanous05016962024-03-19 11:53:11 -070020 // Body concept requires specific naming of classes
21 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060022 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080023 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060024 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070025 // NOLINTEND(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060026};
27
28enum class EncodingType
29{
30 Raw,
31 Base64,
32};
33
Ed Tanousb2896142024-01-31 15:25:47 -080034class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060035{
36 boost::beast::file_posix fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080037 std::optional<size_t> fileSize;
38 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060039
40 public:
41 EncodingType encodingType = EncodingType::Raw;
42
43 ~value_type() = default;
44 value_type() = default;
45 explicit value_type(EncodingType enc) : encodingType(enc) {}
Ed Tanous52e31622024-01-23 16:31:11 -080046 explicit value_type(std::string_view str) : strBody(str) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060047
Ed Tanous52e31622024-01-23 16:31:11 -080048 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()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060085 {
86 return fileHandle;
87 }
88
Ed Tanous52e31622024-01-23 16:31:11 -080089 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060090 {
Ed Tanous52e31622024-01-23 16:31:11 -080091 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 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600112 return fileSize;
113 }
114
Ed Tanous52e31622024-01-23 16:31:11 -0800115 void clear()
116 {
117 strBody.clear();
118 strBody.shrink_to_fit();
119 fileHandle = boost::beast::file_posix();
120 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -0700121 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -0800122 }
123
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600124 void open(const char* path, boost::beast::file_mode mode,
125 boost::system::error_code& ec)
126 {
127 fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -0800128 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 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700143
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 }
Ed Tanous52e31622024-01-23 16:31:11 -0800150 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600151 }
152
153 void setFd(int fd, boost::system::error_code& ec)
154 {
155 fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800156
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 = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600167 }
168};
169
Ed Tanousb2896142024-01-31 15:25:47 -0800170class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600171{
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;
Ed Tanous52e31622024-01-23 16:31:11 -0800180 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700181 // 64KB This number is arbitrary, and selected to try to optimize for larger
182 // files and fewer loops over per-connection reduction in memory usage.
183 // Nginx uses 16-32KB here, so we're in the range of what other webservers
184 // do.
185 constexpr static size_t readBufSize = 1024UL * 64UL;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600186 std::array<char, readBufSize> fileReadBuf{};
187
188 public:
189 template <bool IsRequest, class Fields>
190 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
191 value_type& bodyIn) :
Ed Tanous52e31622024-01-23 16:31:11 -0800192 body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600193 {}
194
195 static void init(boost::beast::error_code& ec)
196 {
197 ec = {};
198 }
199
200 boost::optional<std::pair<const_buffers_type, bool>>
201 get(boost::beast::error_code& ec)
202 {
Ed Tanous52e31622024-01-23 16:31:11 -0800203 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
204 }
205
206 boost::optional<std::pair<const_buffers_type, bool>>
207 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
208 {
209 std::pair<const_buffers_type, bool> ret;
210 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600211 {
Ed Tanous52e31622024-01-23 16:31:11 -0800212 size_t remain = body.str().size() - sent;
213 size_t toReturn = std::min(maxSize, remain);
214 ret.first = const_buffers_type(&body.str()[sent], toReturn);
215
216 sent += toReturn;
217 ret.second = sent < body.str().size();
218 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
219 ret.second);
220 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600221 }
Ed Tanous52e31622024-01-23 16:31:11 -0800222 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700223 BMCWEB_LOG_INFO("Reading {}", readReq);
224 boost::system::error_code readEc;
225 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
226 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600227 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700228 if (readEc != boost::system::errc::operation_would_block &&
229 readEc != boost::system::errc::resource_unavailable_try_again)
230 {
231 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
232 readEc.message());
233 ec = readEc;
234 return boost::none;
235 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600236 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600237
238 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800239 BMCWEB_LOG_INFO("Read {} bytes from file", read);
240 // If the number of bytes read equals the amount requested, we haven't
241 // reached EOF yet
242 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600243 if (body.encodingType == EncodingType::Base64)
244 {
245 buf.clear();
246 buf.reserve(
247 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
248 encoder.encode(chunkView, buf);
249 if (!ret.second)
250 {
251 encoder.finalize(buf);
252 }
253 ret.first = const_buffers_type(buf.data(), buf.size());
254 }
255 else
256 {
257 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
258 }
259 return ret;
260 }
261};
Ed Tanous52e31622024-01-23 16:31:11 -0800262
Ed Tanousb2896142024-01-31 15:25:47 -0800263class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800264{
265 value_type& value;
266
267 public:
268 template <bool IsRequest, class Fields>
269 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
270 value_type& body) :
271 value(body)
272 {}
273
274 void init(const boost::optional<std::uint64_t>& contentLength,
275 boost::beast::error_code& ec)
276 {
277 if (contentLength)
278 {
279 if (!value.file().is_open())
280 {
281 value.str().reserve(static_cast<size_t>(*contentLength));
282 }
283 }
284 ec = {};
285 }
286
287 template <class ConstBufferSequence>
288 std::size_t put(const ConstBufferSequence& buffers,
289 boost::system::error_code& ec)
290 {
291 size_t extra = boost::beast::buffer_bytes(buffers);
292 for (const auto b : boost::beast::buffers_range_ref(buffers))
293 {
294 const char* ptr = static_cast<const char*>(b.data());
295 value.str() += std::string_view(ptr, b.size());
296 }
297 ec = {};
298 return extra;
299 }
300
301 static void finish(boost::system::error_code& ec)
302 {
303 ec = {};
304 }
305};
306
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600307} // namespace bmcweb