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