xref: /openbmc/bmcweb/http/http_body.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3b2896149SEd Tanous #pragma once
4b2896149SEd Tanous 
5f51d8635SEd Tanous #include "duplicatable_file_handle.hpp"
6b2896149SEd Tanous #include "logging.hpp"
7b2896149SEd Tanous #include "utility.hpp"
8b2896149SEd Tanous 
988c7c427SEd Tanous #include <fcntl.h>
10b2896149SEd Tanous 
11d7857201SEd Tanous #include <boost/asio/buffer.hpp>
12d7857201SEd Tanous #include <boost/beast/core/buffer_traits.hpp>
13b2896149SEd Tanous #include <boost/beast/core/buffers_range.hpp>
14d7857201SEd Tanous #include <boost/beast/core/error.hpp>
15d7857201SEd Tanous #include <boost/beast/core/file_base.hpp>
16b2896149SEd Tanous #include <boost/beast/core/file_posix.hpp>
17b2896149SEd Tanous #include <boost/beast/http/message.hpp>
18d7857201SEd Tanous #include <boost/none.hpp>
19d7857201SEd Tanous #include <boost/optional/optional.hpp>
20b2896149SEd Tanous #include <boost/system/error_code.hpp>
21b2896149SEd Tanous 
22d7857201SEd Tanous #include <algorithm>
23d7857201SEd Tanous #include <array>
24d7857201SEd Tanous #include <cstddef>
25608fb7b7SPotin Lai #include <cstdint>
26d7857201SEd Tanous #include <limits>
27608fb7b7SPotin Lai #include <optional>
28d7857201SEd Tanous #include <string>
29b2896149SEd Tanous #include <string_view>
30d7857201SEd Tanous #include <utility>
31b2896149SEd Tanous 
32b2896149SEd Tanous namespace bmcweb
33b2896149SEd Tanous {
34b2896149SEd Tanous struct HttpBody
35b2896149SEd Tanous {
360501696eSEd Tanous     // Body concept requires specific naming of classes
370501696eSEd Tanous     // NOLINTBEGIN(readability-identifier-naming)
38b2896149SEd Tanous     class writer;
39b2896149SEd Tanous     class reader;
40b2896149SEd Tanous     class value_type;
410501696eSEd Tanous     // NOLINTEND(readability-identifier-naming)
42608fb7b7SPotin Lai 
43608fb7b7SPotin Lai     static std::uint64_t size(const value_type& body);
44b2896149SEd Tanous };
45b2896149SEd Tanous 
46b2896149SEd Tanous enum class EncodingType
47b2896149SEd Tanous {
48b2896149SEd Tanous     Raw,
49b2896149SEd Tanous     Base64,
50b2896149SEd Tanous };
51b2896149SEd Tanous 
52b2896149SEd Tanous class HttpBody::value_type
53b2896149SEd Tanous {
54f51d8635SEd Tanous     DuplicatableFileHandle fileHandle;
55b2896149SEd Tanous     std::optional<size_t> fileSize;
56b2896149SEd Tanous     std::string strBody;
57b2896149SEd Tanous 
58b2896149SEd Tanous   public:
59f51d8635SEd Tanous     value_type() = default;
value_type(std::string_view s)60f51d8635SEd Tanous     explicit value_type(std::string_view s) : strBody(s) {}
value_type(EncodingType e)61f51d8635SEd Tanous     explicit value_type(EncodingType e) : encodingType(e) {}
62b2896149SEd Tanous     EncodingType encodingType = EncodingType::Raw;
63b2896149SEd Tanous 
file() const64f51d8635SEd Tanous     const boost::beast::file_posix& file() const
65b2896149SEd Tanous     {
66f51d8635SEd Tanous         return fileHandle.fileHandle;
67b2896149SEd Tanous     }
68b2896149SEd Tanous 
str()69b2896149SEd Tanous     std::string& str()
70b2896149SEd Tanous     {
71b2896149SEd Tanous         return strBody;
72b2896149SEd Tanous     }
73b2896149SEd Tanous 
str() const74b2896149SEd Tanous     const std::string& str() const
75b2896149SEd Tanous     {
76b2896149SEd Tanous         return strBody;
77b2896149SEd Tanous     }
78b2896149SEd Tanous 
payloadSize() const79b2896149SEd Tanous     std::optional<size_t> payloadSize() const
80b2896149SEd Tanous     {
81f51d8635SEd Tanous         if (!fileHandle.fileHandle.is_open())
82b2896149SEd Tanous         {
83b2896149SEd Tanous             return strBody.size();
84b2896149SEd Tanous         }
85b2896149SEd Tanous         if (fileSize)
86b2896149SEd Tanous         {
87b2896149SEd Tanous             if (encodingType == EncodingType::Base64)
88b2896149SEd Tanous             {
89b2896149SEd Tanous                 return crow::utility::Base64Encoder::encodedSize(*fileSize);
90b2896149SEd Tanous             }
91b2896149SEd Tanous         }
92b2896149SEd Tanous         return fileSize;
93b2896149SEd Tanous     }
94b2896149SEd Tanous 
clear()95b2896149SEd Tanous     void clear()
96b2896149SEd Tanous     {
97b2896149SEd Tanous         strBody.clear();
98b2896149SEd Tanous         strBody.shrink_to_fit();
99f51d8635SEd Tanous         fileHandle.fileHandle = boost::beast::file_posix();
100b2896149SEd Tanous         fileSize = std::nullopt;
10106fc9bebSEd Tanous         encodingType = EncodingType::Raw;
102b2896149SEd Tanous     }
103b2896149SEd Tanous 
open(const char * path,boost::beast::file_mode mode,boost::system::error_code & ec)104b2896149SEd Tanous     void open(const char* path, boost::beast::file_mode mode,
105b2896149SEd Tanous               boost::system::error_code& ec)
106b2896149SEd Tanous     {
107f51d8635SEd Tanous         fileHandle.fileHandle.open(path, mode, ec);
108b2896149SEd Tanous         if (ec)
109b2896149SEd Tanous         {
110b2896149SEd Tanous             return;
111b2896149SEd Tanous         }
112b2896149SEd Tanous         boost::system::error_code ec2;
113f51d8635SEd Tanous         uint64_t size = fileHandle.fileHandle.size(ec2);
114b2896149SEd Tanous         if (!ec2)
115b2896149SEd Tanous         {
116b2896149SEd Tanous             BMCWEB_LOG_INFO("File size was {} bytes", size);
117b2896149SEd Tanous             fileSize = static_cast<size_t>(size);
118b2896149SEd Tanous         }
119b2896149SEd Tanous         else
120b2896149SEd Tanous         {
121b2896149SEd Tanous             BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
122b2896149SEd Tanous         }
12388c7c427SEd Tanous 
124f51d8635SEd Tanous         int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
12588c7c427SEd Tanous                                     POSIX_FADV_SEQUENTIAL);
12688c7c427SEd Tanous         if (fadvise != 0)
12788c7c427SEd Tanous         {
12888c7c427SEd Tanous             BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
12988c7c427SEd Tanous         }
130b2896149SEd Tanous         ec = {};
131b2896149SEd Tanous     }
132b2896149SEd Tanous 
setFd(int fd,boost::system::error_code & ec)133b2896149SEd Tanous     void setFd(int fd, boost::system::error_code& ec)
134b2896149SEd Tanous     {
135f51d8635SEd Tanous         fileHandle.fileHandle.native_handle(fd);
136b2896149SEd Tanous 
137b2896149SEd Tanous         boost::system::error_code ec2;
138f51d8635SEd Tanous         uint64_t size = fileHandle.fileHandle.size(ec2);
139b2896149SEd Tanous         if (!ec2)
140b2896149SEd Tanous         {
141b2896149SEd Tanous             if (size != 0 && size < std::numeric_limits<size_t>::max())
142b2896149SEd Tanous             {
143b2896149SEd Tanous                 fileSize = static_cast<size_t>(size);
144b2896149SEd Tanous             }
145b2896149SEd Tanous         }
146b2896149SEd Tanous         ec = {};
147b2896149SEd Tanous     }
148b2896149SEd Tanous };
149b2896149SEd Tanous 
150b2896149SEd Tanous class HttpBody::writer
151b2896149SEd Tanous {
152b2896149SEd Tanous   public:
153b2896149SEd Tanous     using const_buffers_type = boost::asio::const_buffer;
154b2896149SEd Tanous 
155b2896149SEd Tanous   private:
156b2896149SEd Tanous     std::string buf;
157b2896149SEd Tanous     crow::utility::Base64Encoder encoder;
158b2896149SEd Tanous 
159b2896149SEd Tanous     value_type& body;
160b2896149SEd Tanous     size_t sent = 0;
161e428b440SEd Tanous     // 64KB This number is arbitrary, and selected to try to optimize for larger
162e428b440SEd Tanous     // files and fewer loops over per-connection reduction in memory usage.
163e428b440SEd Tanous     // Nginx uses 16-32KB here, so we're in the range of what other webservers
164e428b440SEd Tanous     // do.
165e428b440SEd Tanous     constexpr static size_t readBufSize = 1024UL * 64UL;
166b2896149SEd Tanous     std::array<char, readBufSize> fileReadBuf{};
167b2896149SEd Tanous 
168b2896149SEd Tanous   public:
169b2896149SEd Tanous     template <bool IsRequest, class Fields>
writer(boost::beast::http::header<IsRequest,Fields> &,value_type & bodyIn)170b2896149SEd Tanous     writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
171bd79bce8SPatrick Williams            value_type& bodyIn) : body(bodyIn)
172b2896149SEd Tanous     {}
173b2896149SEd Tanous 
init(boost::beast::error_code & ec)174b2896149SEd Tanous     static void init(boost::beast::error_code& ec)
175b2896149SEd Tanous     {
176b2896149SEd Tanous         ec = {};
177b2896149SEd Tanous     }
178b2896149SEd Tanous 
get(boost::beast::error_code & ec)179*504af5a0SPatrick Williams     boost::optional<std::pair<const_buffers_type, bool>> get(
180*504af5a0SPatrick Williams         boost::beast::error_code& ec)
181b2896149SEd Tanous     {
182b2896149SEd Tanous         return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
183b2896149SEd Tanous     }
184b2896149SEd Tanous 
getWithMaxSize(boost::beast::error_code & ec,size_t maxSize)185*504af5a0SPatrick Williams     boost::optional<std::pair<const_buffers_type, bool>> getWithMaxSize(
186*504af5a0SPatrick Williams         boost::beast::error_code& ec, size_t maxSize)
187b2896149SEd Tanous     {
188b2896149SEd Tanous         std::pair<const_buffers_type, bool> ret;
189b2896149SEd Tanous         if (!body.file().is_open())
190b2896149SEd Tanous         {
191b2896149SEd Tanous             size_t remain = body.str().size() - sent;
192b2896149SEd Tanous             size_t toReturn = std::min(maxSize, remain);
193b2896149SEd Tanous             ret.first = const_buffers_type(&body.str()[sent], toReturn);
194b2896149SEd Tanous 
195b2896149SEd Tanous             sent += toReturn;
196b2896149SEd Tanous             ret.second = sent < body.str().size();
197b2896149SEd Tanous             BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
198b2896149SEd Tanous                             ret.second);
199b2896149SEd Tanous             return ret;
200b2896149SEd Tanous         }
201b2896149SEd Tanous         size_t readReq = std::min(fileReadBuf.size(), maxSize);
2020242baffSEd Tanous         BMCWEB_LOG_INFO("Reading {}", readReq);
2030242baffSEd Tanous         boost::system::error_code readEc;
2040242baffSEd Tanous         size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
2050242baffSEd Tanous         if (readEc)
206b2896149SEd Tanous         {
2070242baffSEd Tanous             if (readEc != boost::system::errc::operation_would_block &&
2080242baffSEd Tanous                 readEc != boost::system::errc::resource_unavailable_try_again)
2090242baffSEd Tanous             {
2100242baffSEd Tanous                 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
2110242baffSEd Tanous                                     readEc.message());
2120242baffSEd Tanous                 ec = readEc;
213b2896149SEd Tanous                 return boost::none;
214b2896149SEd Tanous             }
2150242baffSEd Tanous         }
216b2896149SEd Tanous 
217b2896149SEd Tanous         std::string_view chunkView(fileReadBuf.data(), read);
218b2896149SEd Tanous         BMCWEB_LOG_INFO("Read {} bytes from file", read);
219b2896149SEd Tanous         // If the number of bytes read equals the amount requested, we haven't
220b2896149SEd Tanous         // reached EOF yet
221b2896149SEd Tanous         ret.second = read == readReq;
222b2896149SEd Tanous         if (body.encodingType == EncodingType::Base64)
223b2896149SEd Tanous         {
224b2896149SEd Tanous             buf.clear();
225b2896149SEd Tanous             buf.reserve(
226b2896149SEd Tanous                 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
227b2896149SEd Tanous             encoder.encode(chunkView, buf);
228b2896149SEd Tanous             if (!ret.second)
229b2896149SEd Tanous             {
230b2896149SEd Tanous                 encoder.finalize(buf);
231b2896149SEd Tanous             }
232b2896149SEd Tanous             ret.first = const_buffers_type(buf.data(), buf.size());
233b2896149SEd Tanous         }
234b2896149SEd Tanous         else
235b2896149SEd Tanous         {
236b2896149SEd Tanous             ret.first = const_buffers_type(chunkView.data(), chunkView.size());
237b2896149SEd Tanous         }
238b2896149SEd Tanous         return ret;
239b2896149SEd Tanous     }
240b2896149SEd Tanous };
241b2896149SEd Tanous 
242b2896149SEd Tanous class HttpBody::reader
243b2896149SEd Tanous {
244b2896149SEd Tanous     value_type& value;
245b2896149SEd Tanous 
246b2896149SEd Tanous   public:
247b2896149SEd Tanous     template <bool IsRequest, class Fields>
reader(boost::beast::http::header<IsRequest,Fields> &,value_type & body)248b2896149SEd Tanous     reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
249bd79bce8SPatrick Williams            value_type& body) : value(body)
250b2896149SEd Tanous     {}
251b2896149SEd Tanous 
init(const boost::optional<std::uint64_t> & contentLength,boost::beast::error_code & ec)252b2896149SEd Tanous     void init(const boost::optional<std::uint64_t>& contentLength,
253b2896149SEd Tanous               boost::beast::error_code& ec)
254b2896149SEd Tanous     {
255b2896149SEd Tanous         if (contentLength)
256b2896149SEd Tanous         {
257b2896149SEd Tanous             if (!value.file().is_open())
258b2896149SEd Tanous             {
259b2896149SEd Tanous                 value.str().reserve(static_cast<size_t>(*contentLength));
260b2896149SEd Tanous             }
261b2896149SEd Tanous         }
262b2896149SEd Tanous         ec = {};
263b2896149SEd Tanous     }
264b2896149SEd Tanous 
265b2896149SEd Tanous     template <class ConstBufferSequence>
put(const ConstBufferSequence & buffers,boost::system::error_code & ec)266b2896149SEd Tanous     std::size_t put(const ConstBufferSequence& buffers,
267b2896149SEd Tanous                     boost::system::error_code& ec)
268b2896149SEd Tanous     {
269b2896149SEd Tanous         size_t extra = boost::beast::buffer_bytes(buffers);
270b2896149SEd Tanous         for (const auto b : boost::beast::buffers_range_ref(buffers))
271b2896149SEd Tanous         {
272b2896149SEd Tanous             const char* ptr = static_cast<const char*>(b.data());
273b2896149SEd Tanous             value.str() += std::string_view(ptr, b.size());
274b2896149SEd Tanous         }
275b2896149SEd Tanous         ec = {};
276b2896149SEd Tanous         return extra;
277b2896149SEd Tanous     }
278b2896149SEd Tanous 
finish(boost::system::error_code & ec)279b2896149SEd Tanous     static void finish(boost::system::error_code& ec)
280b2896149SEd Tanous     {
281b2896149SEd Tanous         ec = {};
282b2896149SEd Tanous     }
283b2896149SEd Tanous };
284b2896149SEd Tanous 
size(const value_type & body)285608fb7b7SPotin Lai inline std::uint64_t HttpBody::size(const value_type& body)
286608fb7b7SPotin Lai {
287608fb7b7SPotin Lai     std::optional<size_t> payloadSize = body.payloadSize();
288608fb7b7SPotin Lai     return payloadSize.value_or(0U);
289608fb7b7SPotin Lai }
290608fb7b7SPotin Lai 
291b2896149SEd Tanous } // namespace bmcweb
292