blob: 4b58f213b13a80c3acfc6ff7a00c2c8eab4b07b2 [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 Tanous52e31622024-01-23 16:31:11 -08006#include <unistd.h>
7
8#include <boost/beast/core/buffers_range.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -06009#include <boost/beast/core/file_posix.hpp>
10#include <boost/beast/http/message.hpp>
11#include <boost/system/error_code.hpp>
12
Ed Tanous52e31622024-01-23 16:31:11 -080013#include <string_view>
14
Abhilash Rajub5f288d2023-11-08 22:32:44 -060015namespace bmcweb
16{
Ed Tanousb2896142024-01-31 15:25:47 -080017struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060018{
Ed Tanous05016962024-03-19 11:53:11 -070019 // Body concept requires specific naming of classes
20 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060021 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080022 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060023 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070024 // NOLINTEND(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060025};
26
27enum class EncodingType
28{
29 Raw,
30 Base64,
31};
32
Ed Tanousb2896142024-01-31 15:25:47 -080033class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060034{
35 boost::beast::file_posix fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080036 std::optional<size_t> fileSize;
37 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060038
39 public:
40 EncodingType encodingType = EncodingType::Raw;
41
42 ~value_type() = default;
43 value_type() = default;
44 explicit value_type(EncodingType enc) : encodingType(enc) {}
Ed Tanous52e31622024-01-23 16:31:11 -080045 explicit value_type(std::string_view str) : strBody(str) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060046
Ed Tanous52e31622024-01-23 16:31:11 -080047 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()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060084 {
85 return fileHandle;
86 }
87
Ed Tanous52e31622024-01-23 16:31:11 -080088 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060089 {
Ed Tanous52e31622024-01-23 16:31:11 -080090 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 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600111 return fileSize;
112 }
113
Ed Tanous52e31622024-01-23 16:31:11 -0800114 void clear()
115 {
116 strBody.clear();
117 strBody.shrink_to_fit();
118 fileHandle = boost::beast::file_posix();
119 fileSize = std::nullopt;
120 }
121
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600122 void open(const char* path, boost::beast::file_mode mode,
123 boost::system::error_code& ec)
124 {
125 fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -0800126 if (ec)
127 {
128 return;
129 }
130 boost::system::error_code ec2;
131 uint64_t size = fileHandle.size(ec2);
132 if (!ec2)
133 {
134 BMCWEB_LOG_INFO("File size was {} bytes", size);
135 fileSize = static_cast<size_t>(size);
136 }
137 else
138 {
139 BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
140 }
141 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600142 }
143
144 void setFd(int fd, boost::system::error_code& ec)
145 {
146 fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800147
148 boost::system::error_code ec2;
149 uint64_t size = fileHandle.size(ec2);
150 if (!ec2)
151 {
152 if (size != 0 && size < std::numeric_limits<size_t>::max())
153 {
154 fileSize = static_cast<size_t>(size);
155 }
156 }
157 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600158 }
159};
160
Ed Tanousb2896142024-01-31 15:25:47 -0800161class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600162{
163 public:
164 using const_buffers_type = boost::asio::const_buffer;
165
166 private:
167 std::string buf;
168 crow::utility::Base64Encoder encoder;
169
170 value_type& body;
Ed Tanous52e31622024-01-23 16:31:11 -0800171 size_t sent = 0;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600172 constexpr static size_t readBufSize = 4096;
173 std::array<char, readBufSize> fileReadBuf{};
174
175 public:
176 template <bool IsRequest, class Fields>
177 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
178 value_type& bodyIn) :
Ed Tanous52e31622024-01-23 16:31:11 -0800179 body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600180 {}
181
182 static void init(boost::beast::error_code& ec)
183 {
184 ec = {};
185 }
186
187 boost::optional<std::pair<const_buffers_type, bool>>
188 get(boost::beast::error_code& ec)
189 {
Ed Tanous52e31622024-01-23 16:31:11 -0800190 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
191 }
192
193 boost::optional<std::pair<const_buffers_type, bool>>
194 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
195 {
196 std::pair<const_buffers_type, bool> ret;
197 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600198 {
Ed Tanous52e31622024-01-23 16:31:11 -0800199 size_t remain = body.str().size() - sent;
200 size_t toReturn = std::min(maxSize, remain);
201 ret.first = const_buffers_type(&body.str()[sent], toReturn);
202
203 sent += toReturn;
204 ret.second = sent < body.str().size();
205 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
206 ret.second);
207 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600208 }
Ed Tanous52e31622024-01-23 16:31:11 -0800209 size_t readReq = std::min(fileReadBuf.size(), maxSize);
210 size_t read = body.file().read(fileReadBuf.data(), readReq, ec);
211 if (ec)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600212 {
Ed Tanous52e31622024-01-23 16:31:11 -0800213 BMCWEB_LOG_CRITICAL("Failed to read from file");
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600214 return boost::none;
215 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600216
217 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800218 BMCWEB_LOG_INFO("Read {} bytes from file", read);
219 // If the number of bytes read equals the amount requested, we haven't
220 // reached EOF yet
221 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600222 if (body.encodingType == EncodingType::Base64)
223 {
224 buf.clear();
225 buf.reserve(
226 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
227 encoder.encode(chunkView, buf);
228 if (!ret.second)
229 {
230 encoder.finalize(buf);
231 }
232 ret.first = const_buffers_type(buf.data(), buf.size());
233 }
234 else
235 {
236 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
237 }
238 return ret;
239 }
240};
Ed Tanous52e31622024-01-23 16:31:11 -0800241
Ed Tanousb2896142024-01-31 15:25:47 -0800242class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800243{
244 value_type& value;
245
246 public:
247 template <bool IsRequest, class Fields>
248 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
249 value_type& body) :
250 value(body)
251 {}
252
253 void init(const boost::optional<std::uint64_t>& contentLength,
254 boost::beast::error_code& ec)
255 {
256 if (contentLength)
257 {
258 if (!value.file().is_open())
259 {
260 value.str().reserve(static_cast<size_t>(*contentLength));
261 }
262 }
263 ec = {};
264 }
265
266 template <class ConstBufferSequence>
267 std::size_t put(const ConstBufferSequence& buffers,
268 boost::system::error_code& ec)
269 {
270 size_t extra = boost::beast::buffer_bytes(buffers);
271 for (const auto b : boost::beast::buffers_range_ref(buffers))
272 {
273 const char* ptr = static_cast<const char*>(b.data());
274 value.str() += std::string_view(ptr, b.size());
275 }
276 ec = {};
277 return extra;
278 }
279
280 static void finish(boost::system::error_code& ec)
281 {
282 ec = {};
283 }
284};
285
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600286} // namespace bmcweb