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 encodingType = EncodingType::Raw; 121 } 122 123 void open(const char* path, boost::beast::file_mode mode, 124 boost::system::error_code& ec) 125 { 126 fileHandle.open(path, mode, ec); 127 if (ec) 128 { 129 return; 130 } 131 boost::system::error_code ec2; 132 uint64_t size = fileHandle.size(ec2); 133 if (!ec2) 134 { 135 BMCWEB_LOG_INFO("File size was {} bytes", size); 136 fileSize = static_cast<size_t>(size); 137 } 138 else 139 { 140 BMCWEB_LOG_WARNING("Failed to read file size on {}", path); 141 } 142 ec = {}; 143 } 144 145 void setFd(int fd, boost::system::error_code& ec) 146 { 147 fileHandle.native_handle(fd); 148 149 boost::system::error_code ec2; 150 uint64_t size = fileHandle.size(ec2); 151 if (!ec2) 152 { 153 if (size != 0 && size < std::numeric_limits<size_t>::max()) 154 { 155 fileSize = static_cast<size_t>(size); 156 } 157 } 158 ec = {}; 159 } 160 }; 161 162 class HttpBody::writer 163 { 164 public: 165 using const_buffers_type = boost::asio::const_buffer; 166 167 private: 168 std::string buf; 169 crow::utility::Base64Encoder encoder; 170 171 value_type& body; 172 size_t sent = 0; 173 constexpr static size_t readBufSize = 4096; 174 std::array<char, readBufSize> fileReadBuf{}; 175 176 public: 177 template <bool IsRequest, class Fields> 178 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/, 179 value_type& bodyIn) : 180 body(bodyIn) 181 {} 182 183 static void init(boost::beast::error_code& ec) 184 { 185 ec = {}; 186 } 187 188 boost::optional<std::pair<const_buffers_type, bool>> 189 get(boost::beast::error_code& ec) 190 { 191 return getWithMaxSize(ec, std::numeric_limits<size_t>::max()); 192 } 193 194 boost::optional<std::pair<const_buffers_type, bool>> 195 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize) 196 { 197 std::pair<const_buffers_type, bool> ret; 198 if (!body.file().is_open()) 199 { 200 size_t remain = body.str().size() - sent; 201 size_t toReturn = std::min(maxSize, remain); 202 ret.first = const_buffers_type(&body.str()[sent], toReturn); 203 204 sent += toReturn; 205 ret.second = sent < body.str().size(); 206 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(), 207 ret.second); 208 return ret; 209 } 210 size_t readReq = std::min(fileReadBuf.size(), maxSize); 211 size_t read = body.file().read(fileReadBuf.data(), readReq, ec); 212 if (ec) 213 { 214 BMCWEB_LOG_CRITICAL("Failed to read from file"); 215 return boost::none; 216 } 217 218 std::string_view chunkView(fileReadBuf.data(), read); 219 BMCWEB_LOG_INFO("Read {} bytes from file", read); 220 // If the number of bytes read equals the amount requested, we haven't 221 // reached EOF yet 222 ret.second = read == readReq; 223 if (body.encodingType == EncodingType::Base64) 224 { 225 buf.clear(); 226 buf.reserve( 227 crow::utility::Base64Encoder::encodedSize(chunkView.size())); 228 encoder.encode(chunkView, buf); 229 if (!ret.second) 230 { 231 encoder.finalize(buf); 232 } 233 ret.first = const_buffers_type(buf.data(), buf.size()); 234 } 235 else 236 { 237 ret.first = const_buffers_type(chunkView.data(), chunkView.size()); 238 } 239 return ret; 240 } 241 }; 242 243 class HttpBody::reader 244 { 245 value_type& value; 246 247 public: 248 template <bool IsRequest, class Fields> 249 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/, 250 value_type& body) : 251 value(body) 252 {} 253 254 void init(const boost::optional<std::uint64_t>& contentLength, 255 boost::beast::error_code& ec) 256 { 257 if (contentLength) 258 { 259 if (!value.file().is_open()) 260 { 261 value.str().reserve(static_cast<size_t>(*contentLength)); 262 } 263 } 264 ec = {}; 265 } 266 267 template <class ConstBufferSequence> 268 std::size_t put(const ConstBufferSequence& buffers, 269 boost::system::error_code& ec) 270 { 271 size_t extra = boost::beast::buffer_bytes(buffers); 272 for (const auto b : boost::beast::buffers_range_ref(buffers)) 273 { 274 const char* ptr = static_cast<const char*>(b.data()); 275 value.str() += std::string_view(ptr, b.size()); 276 } 277 ec = {}; 278 return extra; 279 } 280 281 static void finish(boost::system::error_code& ec) 282 { 283 ec = {}; 284 } 285 }; 286 287 } // namespace bmcweb 288