1 #pragma once 2 3 #include "logging.hpp" 4 #include "utility.hpp" 5 6 #include <unistd.h> 7 8 #include <boost/beast/core/buffers_range.hpp> 9 #include <boost/beast/core/file_posix.hpp> 10 #include <boost/beast/http/message.hpp> 11 #include <boost/system/error_code.hpp> 12 13 #include <string_view> 14 15 namespace bmcweb 16 { 17 struct HttpBody 18 { 19 // Body concept requires specific naming of classes 20 // NOLINTBEGIN(readability-identifier-naming) 21 class writer; 22 class reader; 23 class value_type; 24 // NOLINTEND(readability-identifier-naming) 25 }; 26 27 enum class EncodingType 28 { 29 Raw, 30 Base64, 31 }; 32 33 class HttpBody::value_type 34 { 35 boost::beast::file_posix fileHandle; 36 std::optional<size_t> fileSize; 37 std::string strBody; 38 39 public: 40 EncodingType encodingType = EncodingType::Raw; 41 42 ~value_type() = default; 43 value_type() = default; 44 explicit value_type(EncodingType enc) : encodingType(enc) {} 45 explicit value_type(std::string_view str) : strBody(str) {} 46 47 value_type(value_type&& other) noexcept : 48 fileHandle(std::move(other.fileHandle)), fileSize(other.fileSize), 49 strBody(std::move(other.strBody)), encodingType(other.encodingType) 50 {} 51 52 value_type& operator=(value_type&& other) noexcept 53 { 54 fileHandle = std::move(other.fileHandle); 55 fileSize = other.fileSize; 56 strBody = std::move(other.strBody); 57 encodingType = other.encodingType; 58 59 return *this; 60 } 61 62 // Overload copy constructor, because posix doesn't have dup(), but linux 63 // does 64 value_type(const value_type& other) : 65 fileSize(other.fileSize), strBody(other.strBody), 66 encodingType(other.encodingType) 67 { 68 fileHandle.native_handle(dup(other.fileHandle.native_handle())); 69 } 70 71 value_type& operator=(const value_type& other) 72 { 73 if (this != &other) 74 { 75 fileSize = other.fileSize; 76 strBody = other.strBody; 77 encodingType = other.encodingType; 78 fileHandle.native_handle(dup(other.fileHandle.native_handle())); 79 } 80 return *this; 81 } 82 83 const boost::beast::file_posix& file() 84 { 85 return fileHandle; 86 } 87 88 std::string& str() 89 { 90 return strBody; 91 } 92 93 const std::string& str() const 94 { 95 return strBody; 96 } 97 98 std::optional<size_t> payloadSize() const 99 { 100 if (!fileHandle.is_open()) 101 { 102 return strBody.size(); 103 } 104 if (fileSize) 105 { 106 if (encodingType == EncodingType::Base64) 107 { 108 return crow::utility::Base64Encoder::encodedSize(*fileSize); 109 } 110 } 111 return fileSize; 112 } 113 114 void clear() 115 { 116 strBody.clear(); 117 strBody.shrink_to_fit(); 118 fileHandle = boost::beast::file_posix(); 119 fileSize = std::nullopt; 120 } 121 122 void open(const char* path, boost::beast::file_mode mode, 123 boost::system::error_code& ec) 124 { 125 fileHandle.open(path, mode, ec); 126 if (ec) 127 { 128 return; 129 } 130 boost::system::error_code ec2; 131 uint64_t size = fileHandle.size(ec2); 132 if (!ec2) 133 { 134 BMCWEB_LOG_INFO("File size was {} bytes", size); 135 fileSize = static_cast<size_t>(size); 136 } 137 else 138 { 139 BMCWEB_LOG_WARNING("Failed to read file size on {}", path); 140 } 141 ec = {}; 142 } 143 144 void setFd(int fd, boost::system::error_code& ec) 145 { 146 fileHandle.native_handle(fd); 147 148 boost::system::error_code ec2; 149 uint64_t size = fileHandle.size(ec2); 150 if (!ec2) 151 { 152 if (size != 0 && size < std::numeric_limits<size_t>::max()) 153 { 154 fileSize = static_cast<size_t>(size); 155 } 156 } 157 ec = {}; 158 } 159 }; 160 161 class HttpBody::writer 162 { 163 public: 164 using const_buffers_type = boost::asio::const_buffer; 165 166 private: 167 std::string buf; 168 crow::utility::Base64Encoder encoder; 169 170 value_type& body; 171 size_t sent = 0; 172 constexpr static size_t readBufSize = 4096; 173 std::array<char, readBufSize> fileReadBuf{}; 174 175 public: 176 template <bool IsRequest, class Fields> 177 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/, 178 value_type& bodyIn) : 179 body(bodyIn) 180 {} 181 182 static void init(boost::beast::error_code& ec) 183 { 184 ec = {}; 185 } 186 187 boost::optional<std::pair<const_buffers_type, bool>> 188 get(boost::beast::error_code& ec) 189 { 190 return getWithMaxSize(ec, std::numeric_limits<size_t>::max()); 191 } 192 193 boost::optional<std::pair<const_buffers_type, bool>> 194 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize) 195 { 196 std::pair<const_buffers_type, bool> ret; 197 if (!body.file().is_open()) 198 { 199 size_t remain = body.str().size() - sent; 200 size_t toReturn = std::min(maxSize, remain); 201 ret.first = const_buffers_type(&body.str()[sent], toReturn); 202 203 sent += toReturn; 204 ret.second = sent < body.str().size(); 205 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(), 206 ret.second); 207 return ret; 208 } 209 size_t readReq = std::min(fileReadBuf.size(), maxSize); 210 size_t read = body.file().read(fileReadBuf.data(), readReq, ec); 211 if (ec) 212 { 213 BMCWEB_LOG_CRITICAL("Failed to read from file"); 214 return boost::none; 215 } 216 217 std::string_view chunkView(fileReadBuf.data(), read); 218 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; 222 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 }; 241 242 class HttpBody::reader 243 { 244 value_type& value; 245 246 public: 247 template <bool IsRequest, class Fields> 248 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/, 249 value_type& body) : 250 value(body) 251 {} 252 253 void init(const boost::optional<std::uint64_t>& contentLength, 254 boost::beast::error_code& ec) 255 { 256 if (contentLength) 257 { 258 if (!value.file().is_open()) 259 { 260 value.str().reserve(static_cast<size_t>(*contentLength)); 261 } 262 } 263 ec = {}; 264 } 265 266 template <class ConstBufferSequence> 267 std::size_t put(const ConstBufferSequence& buffers, 268 boost::system::error_code& ec) 269 { 270 size_t extra = boost::beast::buffer_bytes(buffers); 271 for (const auto b : boost::beast::buffers_range_ref(buffers)) 272 { 273 const char* ptr = static_cast<const char*>(b.data()); 274 value.str() += std::string_view(ptr, b.size()); 275 } 276 ec = {}; 277 return extra; 278 } 279 280 static void finish(boost::system::error_code& ec) 281 { 282 ec = {}; 283 } 284 }; 285 286 } // namespace bmcweb 287