blob: 288c1fef2d3f7de935750ad1fadf45f374259745 [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"
Ed Tanousb2539062024-03-12 16:58:35 -07008#include "zstd_decompressor.hpp"
Abhilash Rajub5f288d2023-11-08 22:32:44 -06009
Ed Tanous88c7c422024-04-06 08:52:40 -070010#include <fcntl.h>
Ed Tanous52e31622024-01-23 16:31:11 -080011
Ed Tanousd7857202025-01-28 15:32:26 -080012#include <boost/asio/buffer.hpp>
13#include <boost/beast/core/buffer_traits.hpp>
Ed Tanous52e31622024-01-23 16:31:11 -080014#include <boost/beast/core/buffers_range.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080015#include <boost/beast/core/error.hpp>
16#include <boost/beast/core/file_base.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060017#include <boost/beast/core/file_posix.hpp>
18#include <boost/beast/http/message.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080019#include <boost/none.hpp>
20#include <boost/optional/optional.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060021#include <boost/system/error_code.hpp>
22
Ed Tanousd7857202025-01-28 15:32:26 -080023#include <algorithm>
24#include <array>
25#include <cstddef>
Potin Lai608fb7b2024-08-09 16:05:06 +080026#include <cstdint>
Ed Tanousd7857202025-01-28 15:32:26 -080027#include <limits>
Potin Lai608fb7b2024-08-09 16:05:06 +080028#include <optional>
Ed Tanousd7857202025-01-28 15:32:26 -080029#include <string>
Ed Tanous52e31622024-01-23 16:31:11 -080030#include <string_view>
Ed Tanousd7857202025-01-28 15:32:26 -080031#include <utility>
Ed Tanous52e31622024-01-23 16:31:11 -080032
Abhilash Rajub5f288d2023-11-08 22:32:44 -060033namespace bmcweb
34{
Ed Tanousb2896142024-01-31 15:25:47 -080035struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060036{
Ed Tanous05016962024-03-19 11:53:11 -070037 // Body concept requires specific naming of classes
38 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060039 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080040 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060041 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070042 // NOLINTEND(readability-identifier-naming)
Potin Lai608fb7b2024-08-09 16:05:06 +080043
44 static std::uint64_t size(const value_type& body);
Abhilash Rajub5f288d2023-11-08 22:32:44 -060045};
46
47enum class EncodingType
48{
49 Raw,
50 Base64,
51};
52
Ed Tanousb2539062024-03-12 16:58:35 -070053enum class CompressionType
54{
55 Raw,
56 Gzip,
57 Zstd,
58};
59
Ed Tanousb2896142024-01-31 15:25:47 -080060class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060061{
Ed Tanousf51d8632024-05-16 09:14:01 -070062 DuplicatableFileHandle fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080063 std::optional<size_t> fileSize;
64 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060065
66 public:
Ed Tanousf51d8632024-05-16 09:14:01 -070067 value_type() = default;
68 explicit value_type(std::string_view s) : strBody(s) {}
69 explicit value_type(EncodingType e) : encodingType(e) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060070 EncodingType encodingType = EncodingType::Raw;
Ed Tanousb2539062024-03-12 16:58:35 -070071 CompressionType compressionType = CompressionType::Raw;
72 CompressionType clientCompressionType = CompressionType::Raw;
73
74 ~value_type() = default;
75
76 explicit value_type(EncodingType enc, CompressionType comp) :
77 encodingType(enc), compressionType(comp)
78 {}
79
80 value_type(const value_type& other) noexcept = default;
81 value_type& operator=(const value_type& other) noexcept = default;
82 value_type(value_type&& other) noexcept = default;
83 value_type& operator=(value_type&& other) noexcept = default;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060084
Ed Tanousf51d8632024-05-16 09:14:01 -070085 const boost::beast::file_posix& file() const
Ed Tanous52e31622024-01-23 16:31:11 -080086 {
Ed Tanousf51d8632024-05-16 09:14:01 -070087 return fileHandle.fileHandle;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060088 }
89
Ed Tanous52e31622024-01-23 16:31:11 -080090 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060091 {
Ed Tanous52e31622024-01-23 16:31:11 -080092 return strBody;
93 }
94
95 const std::string& str() const
96 {
97 return strBody;
98 }
99
100 std::optional<size_t> payloadSize() const
101 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700102 if (!fileHandle.fileHandle.is_open())
Ed Tanous52e31622024-01-23 16:31:11 -0800103 {
104 return strBody.size();
105 }
106 if (fileSize)
107 {
108 if (encodingType == EncodingType::Base64)
109 {
110 return crow::utility::Base64Encoder::encodedSize(*fileSize);
111 }
112 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600113 return fileSize;
114 }
115
Ed Tanous52e31622024-01-23 16:31:11 -0800116 void clear()
117 {
118 strBody.clear();
119 strBody.shrink_to_fit();
Ed Tanousf51d8632024-05-16 09:14:01 -0700120 fileHandle.fileHandle = boost::beast::file_posix();
Ed Tanous52e31622024-01-23 16:31:11 -0800121 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -0700122 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -0800123 }
124
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600125 void open(const char* path, boost::beast::file_mode mode,
126 boost::system::error_code& ec)
127 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700128 fileHandle.fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -0800129 if (ec)
130 {
131 return;
132 }
133 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700134 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800135 if (!ec2)
136 {
137 BMCWEB_LOG_INFO("File size was {} bytes", size);
138 fileSize = static_cast<size_t>(size);
139 }
140 else
141 {
142 BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
143 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700144
Ed Tanousf51d8632024-05-16 09:14:01 -0700145 int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
Ed Tanous88c7c422024-04-06 08:52:40 -0700146 POSIX_FADV_SEQUENTIAL);
147 if (fadvise != 0)
148 {
149 BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
150 }
Ed Tanous52e31622024-01-23 16:31:11 -0800151 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600152 }
153
154 void setFd(int fd, boost::system::error_code& ec)
155 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700156 fileHandle.fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800157
158 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700159 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800160 if (!ec2)
161 {
162 if (size != 0 && size < std::numeric_limits<size_t>::max())
163 {
164 fileSize = static_cast<size_t>(size);
165 }
166 }
167 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600168 }
169};
170
Ed Tanousb2896142024-01-31 15:25:47 -0800171class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600172{
173 public:
174 using const_buffers_type = boost::asio::const_buffer;
175
176 private:
177 std::string buf;
178 crow::utility::Base64Encoder encoder;
179
Ed Tanousb2539062024-03-12 16:58:35 -0700180 std::optional<ZstdDecompressor> zstd;
181
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600182 value_type& body;
Ed Tanous52e31622024-01-23 16:31:11 -0800183 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700184 // 64KB This number is arbitrary, and selected to try to optimize for larger
185 // files and fewer loops over per-connection reduction in memory usage.
186 // Nginx uses 16-32KB here, so we're in the range of what other webservers
187 // do.
188 constexpr static size_t readBufSize = 1024UL * 64UL;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600189 std::array<char, readBufSize> fileReadBuf{};
190
191 public:
192 template <bool IsRequest, class Fields>
193 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400194 value_type& bodyIn) : body(bodyIn)
Ed Tanousb2539062024-03-12 16:58:35 -0700195 {
196 if (body.compressionType == CompressionType::Zstd &&
197 body.clientCompressionType != CompressionType::Zstd)
198 {
199 zstd.emplace();
200 }
201 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600202
203 static void init(boost::beast::error_code& ec)
204 {
205 ec = {};
206 }
207
Patrick Williams504af5a2025-02-03 14:29:03 -0500208 boost::optional<std::pair<const_buffers_type, bool>> get(
209 boost::beast::error_code& ec)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600210 {
Ed Tanous52e31622024-01-23 16:31:11 -0800211 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
212 }
213
Patrick Williams504af5a2025-02-03 14:29:03 -0500214 boost::optional<std::pair<const_buffers_type, bool>> getWithMaxSize(
215 boost::beast::error_code& ec, size_t maxSize)
Ed Tanous52e31622024-01-23 16:31:11 -0800216 {
217 std::pair<const_buffers_type, bool> ret;
218 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600219 {
Ed Tanous52e31622024-01-23 16:31:11 -0800220 size_t remain = body.str().size() - sent;
221 size_t toReturn = std::min(maxSize, remain);
222 ret.first = const_buffers_type(&body.str()[sent], toReturn);
223
224 sent += toReturn;
225 ret.second = sent < body.str().size();
226 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
227 ret.second);
228 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600229 }
Ed Tanous52e31622024-01-23 16:31:11 -0800230 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700231 BMCWEB_LOG_INFO("Reading {}", readReq);
232 boost::system::error_code readEc;
233 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
234 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600235 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700236 if (readEc != boost::system::errc::operation_would_block &&
237 readEc != boost::system::errc::resource_unavailable_try_again)
238 {
239 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
240 readEc.message());
241 ec = readEc;
242 return boost::none;
243 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600244 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600245
246 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800247 BMCWEB_LOG_INFO("Read {} bytes from file", read);
248 // If the number of bytes read equals the amount requested, we haven't
249 // reached EOF yet
250 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600251 if (body.encodingType == EncodingType::Base64)
252 {
253 buf.clear();
254 buf.reserve(
255 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
256 encoder.encode(chunkView, buf);
257 if (!ret.second)
258 {
259 encoder.finalize(buf);
260 }
261 ret.first = const_buffers_type(buf.data(), buf.size());
262 }
263 else
264 {
265 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
266 }
Ed Tanousb2539062024-03-12 16:58:35 -0700267
268 if (zstd)
269 {
270 std::optional<const_buffers_type> decompressed =
271 zstd->decompress(ret.first);
272 if (!decompressed)
273 {
274 return boost::none;
275 }
276 ret.first = *decompressed;
277 }
278
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600279 return ret;
280 }
281};
Ed Tanous52e31622024-01-23 16:31:11 -0800282
Ed Tanousb2896142024-01-31 15:25:47 -0800283class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800284{
285 value_type& value;
286
287 public:
288 template <bool IsRequest, class Fields>
289 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400290 value_type& body) : value(body)
Ed Tanous52e31622024-01-23 16:31:11 -0800291 {}
292
293 void init(const boost::optional<std::uint64_t>& contentLength,
294 boost::beast::error_code& ec)
295 {
296 if (contentLength)
297 {
298 if (!value.file().is_open())
299 {
300 value.str().reserve(static_cast<size_t>(*contentLength));
301 }
302 }
303 ec = {};
304 }
305
306 template <class ConstBufferSequence>
307 std::size_t put(const ConstBufferSequence& buffers,
308 boost::system::error_code& ec)
309 {
310 size_t extra = boost::beast::buffer_bytes(buffers);
311 for (const auto b : boost::beast::buffers_range_ref(buffers))
312 {
313 const char* ptr = static_cast<const char*>(b.data());
314 value.str() += std::string_view(ptr, b.size());
315 }
316 ec = {};
317 return extra;
318 }
319
320 static void finish(boost::system::error_code& ec)
321 {
322 ec = {};
323 }
324};
325
Potin Lai608fb7b2024-08-09 16:05:06 +0800326inline std::uint64_t HttpBody::size(const value_type& body)
327{
328 std::optional<size_t> payloadSize = body.payloadSize();
329 return payloadSize.value_or(0U);
330}
331
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600332} // namespace bmcweb