blob: 9da892fd5a711e59bed18b0fd599c5b54f1aa15b [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#include <unistd.h>
11
12#include <boost/beast/core/buffers_range.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060013#include <boost/beast/core/file_posix.hpp>
14#include <boost/beast/http/message.hpp>
15#include <boost/system/error_code.hpp>
16
Potin Lai608fb7b2024-08-09 16:05:06 +080017#include <cstdint>
18#include <optional>
Ed Tanous52e31622024-01-23 16:31:11 -080019#include <string_view>
20
Abhilash Rajub5f288d2023-11-08 22:32:44 -060021namespace bmcweb
22{
Ed Tanousb2896142024-01-31 15:25:47 -080023struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060024{
Ed Tanous05016962024-03-19 11:53:11 -070025 // Body concept requires specific naming of classes
26 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060027 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080028 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060029 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070030 // NOLINTEND(readability-identifier-naming)
Potin Lai608fb7b2024-08-09 16:05:06 +080031
32 static std::uint64_t size(const value_type& body);
Abhilash Rajub5f288d2023-11-08 22:32:44 -060033};
34
35enum class EncodingType
36{
37 Raw,
38 Base64,
39};
40
Ed Tanousb2896142024-01-31 15:25:47 -080041class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060042{
Ed Tanousf51d8632024-05-16 09:14:01 -070043 DuplicatableFileHandle fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080044 std::optional<size_t> fileSize;
45 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060046
47 public:
Ed Tanousf51d8632024-05-16 09:14:01 -070048 value_type() = default;
49 explicit value_type(std::string_view s) : strBody(s) {}
50 explicit value_type(EncodingType e) : encodingType(e) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060051 EncodingType encodingType = EncodingType::Raw;
52
Ed Tanousf51d8632024-05-16 09:14:01 -070053 const boost::beast::file_posix& file() const
Ed Tanous52e31622024-01-23 16:31:11 -080054 {
Ed Tanousf51d8632024-05-16 09:14:01 -070055 return fileHandle.fileHandle;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060056 }
57
Ed Tanous52e31622024-01-23 16:31:11 -080058 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060059 {
Ed Tanous52e31622024-01-23 16:31:11 -080060 return strBody;
61 }
62
63 const std::string& str() const
64 {
65 return strBody;
66 }
67
68 std::optional<size_t> payloadSize() const
69 {
Ed Tanousf51d8632024-05-16 09:14:01 -070070 if (!fileHandle.fileHandle.is_open())
Ed Tanous52e31622024-01-23 16:31:11 -080071 {
72 return strBody.size();
73 }
74 if (fileSize)
75 {
76 if (encodingType == EncodingType::Base64)
77 {
78 return crow::utility::Base64Encoder::encodedSize(*fileSize);
79 }
80 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -060081 return fileSize;
82 }
83
Ed Tanous52e31622024-01-23 16:31:11 -080084 void clear()
85 {
86 strBody.clear();
87 strBody.shrink_to_fit();
Ed Tanousf51d8632024-05-16 09:14:01 -070088 fileHandle.fileHandle = boost::beast::file_posix();
Ed Tanous52e31622024-01-23 16:31:11 -080089 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -070090 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -080091 }
92
Abhilash Rajub5f288d2023-11-08 22:32:44 -060093 void open(const char* path, boost::beast::file_mode mode,
94 boost::system::error_code& ec)
95 {
Ed Tanousf51d8632024-05-16 09:14:01 -070096 fileHandle.fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -080097 if (ec)
98 {
99 return;
100 }
101 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700102 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800103 if (!ec2)
104 {
105 BMCWEB_LOG_INFO("File size was {} bytes", size);
106 fileSize = static_cast<size_t>(size);
107 }
108 else
109 {
110 BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
111 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700112
Ed Tanousf51d8632024-05-16 09:14:01 -0700113 int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
Ed Tanous88c7c422024-04-06 08:52:40 -0700114 POSIX_FADV_SEQUENTIAL);
115 if (fadvise != 0)
116 {
117 BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
118 }
Ed Tanous52e31622024-01-23 16:31:11 -0800119 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600120 }
121
122 void setFd(int fd, boost::system::error_code& ec)
123 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700124 fileHandle.fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800125
126 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700127 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800128 if (!ec2)
129 {
130 if (size != 0 && size < std::numeric_limits<size_t>::max())
131 {
132 fileSize = static_cast<size_t>(size);
133 }
134 }
135 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600136 }
137};
138
Ed Tanousb2896142024-01-31 15:25:47 -0800139class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600140{
141 public:
142 using const_buffers_type = boost::asio::const_buffer;
143
144 private:
145 std::string buf;
146 crow::utility::Base64Encoder encoder;
147
148 value_type& body;
Ed Tanous52e31622024-01-23 16:31:11 -0800149 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700150 // 64KB This number is arbitrary, and selected to try to optimize for larger
151 // files and fewer loops over per-connection reduction in memory usage.
152 // Nginx uses 16-32KB here, so we're in the range of what other webservers
153 // do.
154 constexpr static size_t readBufSize = 1024UL * 64UL;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600155 std::array<char, readBufSize> fileReadBuf{};
156
157 public:
158 template <bool IsRequest, class Fields>
159 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400160 value_type& bodyIn) : body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600161 {}
162
163 static void init(boost::beast::error_code& ec)
164 {
165 ec = {};
166 }
167
168 boost::optional<std::pair<const_buffers_type, bool>>
169 get(boost::beast::error_code& ec)
170 {
Ed Tanous52e31622024-01-23 16:31:11 -0800171 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
172 }
173
174 boost::optional<std::pair<const_buffers_type, bool>>
175 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
176 {
177 std::pair<const_buffers_type, bool> ret;
178 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600179 {
Ed Tanous52e31622024-01-23 16:31:11 -0800180 size_t remain = body.str().size() - sent;
181 size_t toReturn = std::min(maxSize, remain);
182 ret.first = const_buffers_type(&body.str()[sent], toReturn);
183
184 sent += toReturn;
185 ret.second = sent < body.str().size();
186 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
187 ret.second);
188 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600189 }
Ed Tanous52e31622024-01-23 16:31:11 -0800190 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700191 BMCWEB_LOG_INFO("Reading {}", readReq);
192 boost::system::error_code readEc;
193 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
194 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600195 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700196 if (readEc != boost::system::errc::operation_would_block &&
197 readEc != boost::system::errc::resource_unavailable_try_again)
198 {
199 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
200 readEc.message());
201 ec = readEc;
202 return boost::none;
203 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600204 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600205
206 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800207 BMCWEB_LOG_INFO("Read {} bytes from file", read);
208 // If the number of bytes read equals the amount requested, we haven't
209 // reached EOF yet
210 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600211 if (body.encodingType == EncodingType::Base64)
212 {
213 buf.clear();
214 buf.reserve(
215 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
216 encoder.encode(chunkView, buf);
217 if (!ret.second)
218 {
219 encoder.finalize(buf);
220 }
221 ret.first = const_buffers_type(buf.data(), buf.size());
222 }
223 else
224 {
225 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
226 }
227 return ret;
228 }
229};
Ed Tanous52e31622024-01-23 16:31:11 -0800230
Ed Tanousb2896142024-01-31 15:25:47 -0800231class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800232{
233 value_type& value;
234
235 public:
236 template <bool IsRequest, class Fields>
237 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400238 value_type& body) : value(body)
Ed Tanous52e31622024-01-23 16:31:11 -0800239 {}
240
241 void init(const boost::optional<std::uint64_t>& contentLength,
242 boost::beast::error_code& ec)
243 {
244 if (contentLength)
245 {
246 if (!value.file().is_open())
247 {
248 value.str().reserve(static_cast<size_t>(*contentLength));
249 }
250 }
251 ec = {};
252 }
253
254 template <class ConstBufferSequence>
255 std::size_t put(const ConstBufferSequence& buffers,
256 boost::system::error_code& ec)
257 {
258 size_t extra = boost::beast::buffer_bytes(buffers);
259 for (const auto b : boost::beast::buffers_range_ref(buffers))
260 {
261 const char* ptr = static_cast<const char*>(b.data());
262 value.str() += std::string_view(ptr, b.size());
263 }
264 ec = {};
265 return extra;
266 }
267
268 static void finish(boost::system::error_code& ec)
269 {
270 ec = {};
271 }
272};
273
Potin Lai608fb7b2024-08-09 16:05:06 +0800274inline std::uint64_t HttpBody::size(const value_type& body)
275{
276 std::optional<size_t> payloadSize = body.payloadSize();
277 return payloadSize.value_or(0U);
278}
279
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600280} // namespace bmcweb