blob: 4462a501d4b15062a759da2879ca326bca79a5f5 [file] [log] [blame]
Abhilash Rajub5f288d2023-11-08 22:32:44 -06001#pragma once
2
Ed Tanousf51d8632024-05-16 09:14:01 -07003#include "duplicatable_file_handle.hpp"
Ed Tanous52e31622024-01-23 16:31:11 -08004#include "logging.hpp"
Abhilash Rajub5f288d2023-11-08 22:32:44 -06005#include "utility.hpp"
6
Ed Tanous88c7c422024-04-06 08:52:40 -07007#include <fcntl.h>
Ed Tanous52e31622024-01-23 16:31:11 -08008#include <unistd.h>
9
10#include <boost/beast/core/buffers_range.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060011#include <boost/beast/core/file_posix.hpp>
12#include <boost/beast/http/message.hpp>
13#include <boost/system/error_code.hpp>
14
Ed Tanous52e31622024-01-23 16:31:11 -080015#include <string_view>
16
Abhilash Rajub5f288d2023-11-08 22:32:44 -060017namespace bmcweb
18{
Ed Tanousb2896142024-01-31 15:25:47 -080019struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060020{
Ed Tanous05016962024-03-19 11:53:11 -070021 // Body concept requires specific naming of classes
22 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060023 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080024 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060025 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070026 // NOLINTEND(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060027};
28
29enum class EncodingType
30{
31 Raw,
32 Base64,
33};
34
Ed Tanousb2896142024-01-31 15:25:47 -080035class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060036{
Ed Tanousf51d8632024-05-16 09:14:01 -070037 DuplicatableFileHandle fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080038 std::optional<size_t> fileSize;
39 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060040
41 public:
Ed Tanousf51d8632024-05-16 09:14:01 -070042 value_type() = default;
43 explicit value_type(std::string_view s) : strBody(s) {}
44 explicit value_type(EncodingType e) : encodingType(e) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060045 EncodingType encodingType = EncodingType::Raw;
46
Ed Tanousf51d8632024-05-16 09:14:01 -070047 const boost::beast::file_posix& file() const
Ed Tanous52e31622024-01-23 16:31:11 -080048 {
Ed Tanousf51d8632024-05-16 09:14:01 -070049 return fileHandle.fileHandle;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060050 }
51
Ed Tanous52e31622024-01-23 16:31:11 -080052 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060053 {
Ed Tanous52e31622024-01-23 16:31:11 -080054 return strBody;
55 }
56
57 const std::string& str() const
58 {
59 return strBody;
60 }
61
62 std::optional<size_t> payloadSize() const
63 {
Ed Tanousf51d8632024-05-16 09:14:01 -070064 if (!fileHandle.fileHandle.is_open())
Ed Tanous52e31622024-01-23 16:31:11 -080065 {
66 return strBody.size();
67 }
68 if (fileSize)
69 {
70 if (encodingType == EncodingType::Base64)
71 {
72 return crow::utility::Base64Encoder::encodedSize(*fileSize);
73 }
74 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -060075 return fileSize;
76 }
77
Ed Tanous52e31622024-01-23 16:31:11 -080078 void clear()
79 {
80 strBody.clear();
81 strBody.shrink_to_fit();
Ed Tanousf51d8632024-05-16 09:14:01 -070082 fileHandle.fileHandle = boost::beast::file_posix();
Ed Tanous52e31622024-01-23 16:31:11 -080083 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -070084 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -080085 }
86
Abhilash Rajub5f288d2023-11-08 22:32:44 -060087 void open(const char* path, boost::beast::file_mode mode,
88 boost::system::error_code& ec)
89 {
Ed Tanousf51d8632024-05-16 09:14:01 -070090 fileHandle.fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -080091 if (ec)
92 {
93 return;
94 }
95 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -070096 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -080097 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 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700106
Ed Tanousf51d8632024-05-16 09:14:01 -0700107 int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
Ed Tanous88c7c422024-04-06 08:52:40 -0700108 POSIX_FADV_SEQUENTIAL);
109 if (fadvise != 0)
110 {
111 BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
112 }
Ed Tanous52e31622024-01-23 16:31:11 -0800113 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600114 }
115
116 void setFd(int fd, boost::system::error_code& ec)
117 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700118 fileHandle.fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800119
120 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700121 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800122 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 = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600130 }
131};
132
Ed Tanousb2896142024-01-31 15:25:47 -0800133class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600134{
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;
Ed Tanous52e31622024-01-23 16:31:11 -0800143 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700144 // 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;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600149 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) :
Ed Tanous52e31622024-01-23 16:31:11 -0800155 body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600156 {}
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 {
Ed Tanous52e31622024-01-23 16:31:11 -0800166 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())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600174 {
Ed Tanous52e31622024-01-23 16:31:11 -0800175 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;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600184 }
Ed Tanous52e31622024-01-23 16:31:11 -0800185 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700186 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)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600190 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700191 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 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600199 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600200
201 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800202 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;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600206 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};
Ed Tanous52e31622024-01-23 16:31:11 -0800225
Ed Tanousb2896142024-01-31 15:25:47 -0800226class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800227{
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
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600270} // namespace bmcweb