blob: d76ed49cc891d3f8a3c42713b296a5d2215f40c4 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Abhilash Rajub5f288d2023-11-08 22:32:44 -06003#pragma once
4
Ed Tanousf51d8632024-05-16 09:14:01 -07005#include "duplicatable_file_handle.hpp"
Ed Tanous52e31622024-01-23 16:31:11 -08006#include "logging.hpp"
Abhilash Rajub5f288d2023-11-08 22:32:44 -06007#include "utility.hpp"
8
Ed Tanous88c7c422024-04-06 08:52:40 -07009#include <fcntl.h>
Ed Tanous52e31622024-01-23 16:31:11 -080010
Ed Tanousd7857202025-01-28 15:32:26 -080011#include <boost/asio/buffer.hpp>
12#include <boost/beast/core/buffer_traits.hpp>
Ed Tanous52e31622024-01-23 16:31:11 -080013#include <boost/beast/core/buffers_range.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080014#include <boost/beast/core/error.hpp>
15#include <boost/beast/core/file_base.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060016#include <boost/beast/core/file_posix.hpp>
17#include <boost/beast/http/message.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080018#include <boost/none.hpp>
19#include <boost/optional/optional.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060020#include <boost/system/error_code.hpp>
21
Ed Tanousd7857202025-01-28 15:32:26 -080022#include <algorithm>
23#include <array>
24#include <cstddef>
Potin Lai608fb7b2024-08-09 16:05:06 +080025#include <cstdint>
Ed Tanousd7857202025-01-28 15:32:26 -080026#include <limits>
Potin Lai608fb7b2024-08-09 16:05:06 +080027#include <optional>
Ed Tanousd7857202025-01-28 15:32:26 -080028#include <string>
Ed Tanous52e31622024-01-23 16:31:11 -080029#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080030#include <utility>
Ed Tanous52e31622024-01-23 16:31:11 -080031
Abhilash Rajub5f288d2023-11-08 22:32:44 -060032namespace bmcweb
33{
Ed Tanousb2896142024-01-31 15:25:47 -080034struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060035{
Ed Tanous05016962024-03-19 11:53:11 -070036 // Body concept requires specific naming of classes
37 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060038 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080039 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060040 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070041 // NOLINTEND(readability-identifier-naming)
Potin Lai608fb7b2024-08-09 16:05:06 +080042
43 static std::uint64_t size(const value_type& body);
Abhilash Rajub5f288d2023-11-08 22:32:44 -060044};
45
46enum class EncodingType
47{
48 Raw,
49 Base64,
50};
51
Ed Tanousb2896142024-01-31 15:25:47 -080052class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060053{
Ed Tanousf51d8632024-05-16 09:14:01 -070054 DuplicatableFileHandle fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080055 std::optional<size_t> fileSize;
56 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060057
58 public:
Ed Tanousf51d8632024-05-16 09:14:01 -070059 value_type() = default;
60 explicit value_type(std::string_view s) : strBody(s) {}
61 explicit value_type(EncodingType e) : encodingType(e) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060062 EncodingType encodingType = EncodingType::Raw;
63
Ed Tanousf51d8632024-05-16 09:14:01 -070064 const boost::beast::file_posix& file() const
Ed Tanous52e31622024-01-23 16:31:11 -080065 {
Ed Tanousf51d8632024-05-16 09:14:01 -070066 return fileHandle.fileHandle;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060067 }
68
Ed Tanous52e31622024-01-23 16:31:11 -080069 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060070 {
Ed Tanous52e31622024-01-23 16:31:11 -080071 return strBody;
72 }
73
74 const std::string& str() const
75 {
76 return strBody;
77 }
78
79 std::optional<size_t> payloadSize() const
80 {
Ed Tanousf51d8632024-05-16 09:14:01 -070081 if (!fileHandle.fileHandle.is_open())
Ed Tanous52e31622024-01-23 16:31:11 -080082 {
83 return strBody.size();
84 }
85 if (fileSize)
86 {
87 if (encodingType == EncodingType::Base64)
88 {
89 return crow::utility::Base64Encoder::encodedSize(*fileSize);
90 }
91 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -060092 return fileSize;
93 }
94
Ed Tanous52e31622024-01-23 16:31:11 -080095 void clear()
96 {
97 strBody.clear();
98 strBody.shrink_to_fit();
Ed Tanousf51d8632024-05-16 09:14:01 -070099 fileHandle.fileHandle = boost::beast::file_posix();
Ed Tanous52e31622024-01-23 16:31:11 -0800100 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -0700101 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -0800102 }
103
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600104 void open(const char* path, boost::beast::file_mode mode,
105 boost::system::error_code& ec)
106 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700107 fileHandle.fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -0800108 if (ec)
109 {
110 return;
111 }
112 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700113 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800114 if (!ec2)
115 {
116 BMCWEB_LOG_INFO("File size was {} bytes", size);
117 fileSize = static_cast<size_t>(size);
118 }
119 else
120 {
121 BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
122 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700123
Ed Tanousf51d8632024-05-16 09:14:01 -0700124 int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
Ed Tanous88c7c422024-04-06 08:52:40 -0700125 POSIX_FADV_SEQUENTIAL);
126 if (fadvise != 0)
127 {
128 BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
129 }
Ed Tanous52e31622024-01-23 16:31:11 -0800130 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600131 }
132
133 void setFd(int fd, boost::system::error_code& ec)
134 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700135 fileHandle.fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800136
137 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700138 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800139 if (!ec2)
140 {
141 if (size != 0 && size < std::numeric_limits<size_t>::max())
142 {
143 fileSize = static_cast<size_t>(size);
144 }
145 }
146 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600147 }
148};
149
Ed Tanousb2896142024-01-31 15:25:47 -0800150class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600151{
152 public:
153 using const_buffers_type = boost::asio::const_buffer;
154
155 private:
156 std::string buf;
157 crow::utility::Base64Encoder encoder;
158
159 value_type& body;
Ed Tanous52e31622024-01-23 16:31:11 -0800160 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700161 // 64KB This number is arbitrary, and selected to try to optimize for larger
162 // files and fewer loops over per-connection reduction in memory usage.
163 // Nginx uses 16-32KB here, so we're in the range of what other webservers
164 // do.
165 constexpr static size_t readBufSize = 1024UL * 64UL;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600166 std::array<char, readBufSize> fileReadBuf{};
167
168 public:
169 template <bool IsRequest, class Fields>
170 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400171 value_type& bodyIn) : body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600172 {}
173
174 static void init(boost::beast::error_code& ec)
175 {
176 ec = {};
177 }
178
Patrick Williams504af5a2025-02-03 14:29:03 -0500179 boost::optional<std::pair<const_buffers_type, bool>> get(
180 boost::beast::error_code& ec)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600181 {
Ed Tanous52e31622024-01-23 16:31:11 -0800182 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
183 }
184
Patrick Williams504af5a2025-02-03 14:29:03 -0500185 boost::optional<std::pair<const_buffers_type, bool>> getWithMaxSize(
186 boost::beast::error_code& ec, size_t maxSize)
Ed Tanous52e31622024-01-23 16:31:11 -0800187 {
188 std::pair<const_buffers_type, bool> ret;
189 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600190 {
Ed Tanous52e31622024-01-23 16:31:11 -0800191 size_t remain = body.str().size() - sent;
192 size_t toReturn = std::min(maxSize, remain);
193 ret.first = const_buffers_type(&body.str()[sent], toReturn);
194
195 sent += toReturn;
196 ret.second = sent < body.str().size();
197 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
198 ret.second);
199 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600200 }
Ed Tanous52e31622024-01-23 16:31:11 -0800201 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700202 BMCWEB_LOG_INFO("Reading {}", readReq);
203 boost::system::error_code readEc;
204 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
205 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600206 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700207 if (readEc != boost::system::errc::operation_would_block &&
208 readEc != boost::system::errc::resource_unavailable_try_again)
209 {
210 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
211 readEc.message());
212 ec = readEc;
213 return boost::none;
214 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600215 }
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*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400249 value_type& body) : value(body)
Ed Tanous52e31622024-01-23 16:31:11 -0800250 {}
251
252 void init(const boost::optional<std::uint64_t>& contentLength,
253 boost::beast::error_code& ec)
254 {
255 if (contentLength)
256 {
257 if (!value.file().is_open())
258 {
259 value.str().reserve(static_cast<size_t>(*contentLength));
260 }
261 }
262 ec = {};
263 }
264
265 template <class ConstBufferSequence>
266 std::size_t put(const ConstBufferSequence& buffers,
267 boost::system::error_code& ec)
268 {
269 size_t extra = boost::beast::buffer_bytes(buffers);
270 for (const auto b : boost::beast::buffers_range_ref(buffers))
271 {
272 const char* ptr = static_cast<const char*>(b.data());
273 value.str() += std::string_view(ptr, b.size());
274 }
275 ec = {};
276 return extra;
277 }
278
279 static void finish(boost::system::error_code& ec)
280 {
281 ec = {};
282 }
283};
284
Potin Lai608fb7b2024-08-09 16:05:06 +0800285inline std::uint64_t HttpBody::size(const value_type& body)
286{
287 std::optional<size_t> payloadSize = body.payloadSize();
288 return payloadSize.value_or(0U);
289}
290
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600291} // namespace bmcweb