1 #pragma once 2 #include "http_body.hpp" 3 #include "logging.hpp" 4 #include "utils/hex_utils.hpp" 5 6 #include <fcntl.h> 7 8 #include <boost/beast/http/message.hpp> 9 #include <nlohmann/json.hpp> 10 11 #include <optional> 12 #include <string> 13 #include <string_view> 14 #include <utility> 15 16 namespace crow 17 { 18 19 template <typename Adaptor, typename Handler> 20 class Connection; 21 22 namespace http = boost::beast::http; 23 24 enum class OpenCode 25 { 26 Success, 27 FileDoesNotExist, 28 InternalError, 29 }; 30 31 struct Response 32 { 33 template <typename Adaptor, typename Handler> 34 friend class crow::Connection; 35 36 http::response<bmcweb::HttpBody> response; 37 38 nlohmann::json jsonValue; 39 using fields_type = http::header<false, http::fields>; 40 fields_type& fields() 41 { 42 return response.base(); 43 } 44 45 const fields_type& fields() const 46 { 47 return response.base(); 48 } 49 50 void addHeader(std::string_view key, std::string_view value) 51 { 52 fields().insert(key, value); 53 } 54 55 void addHeader(http::field key, std::string_view value) 56 { 57 fields().insert(key, value); 58 } 59 60 void clearHeader(http::field key) 61 { 62 fields().erase(key); 63 } 64 65 Response() = default; 66 Response(Response&& res) noexcept : 67 response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 68 completed(res.completed) 69 { 70 // See note in operator= move handler for why this is needed. 71 if (!res.completed) 72 { 73 completeRequestHandler = std::move(res.completeRequestHandler); 74 res.completeRequestHandler = nullptr; 75 } 76 } 77 78 ~Response() = default; 79 80 Response(const Response&) = delete; 81 Response& operator=(const Response& r) = delete; 82 83 Response& operator=(Response&& r) noexcept 84 { 85 BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 86 logPtr(this), logPtr(&r)); 87 if (this == &r) 88 { 89 return *this; 90 } 91 response = std::move(r.response); 92 jsonValue = std::move(r.jsonValue); 93 expectedHash = std::move(r.expectedHash); 94 95 // Only need to move completion handler if not already completed 96 // Note, there are cases where we might move out of a Response object 97 // while in a completion handler for that response object. This check 98 // is intended to prevent destructing the functor we are currently 99 // executing from in that case. 100 if (!r.completed) 101 { 102 completeRequestHandler = std::move(r.completeRequestHandler); 103 r.completeRequestHandler = nullptr; 104 } 105 else 106 { 107 completeRequestHandler = nullptr; 108 } 109 completed = r.completed; 110 return *this; 111 } 112 113 void result(unsigned v) 114 { 115 fields().result(v); 116 } 117 118 void result(http::status v) 119 { 120 fields().result(v); 121 } 122 123 void copyBody(const Response& res) 124 { 125 response.body() = res.response.body(); 126 } 127 128 http::status result() const 129 { 130 return fields().result(); 131 } 132 133 unsigned resultInt() const 134 { 135 return fields().result_int(); 136 } 137 138 std::string_view reason() const 139 { 140 return fields().reason(); 141 } 142 143 bool isCompleted() const noexcept 144 { 145 return completed; 146 } 147 148 const std::string* body() 149 { 150 return &response.body().str(); 151 } 152 153 std::string_view getHeaderValue(std::string_view key) const 154 { 155 return fields()[key]; 156 } 157 158 std::string_view getHeaderValue(boost::beast::http::field key) const 159 { 160 return fields()[key]; 161 } 162 163 void keepAlive(bool k) 164 { 165 response.keep_alive(k); 166 } 167 168 bool keepAlive() const 169 { 170 return response.keep_alive(); 171 } 172 173 std::optional<uint64_t> size() 174 { 175 return response.body().payloadSize(); 176 } 177 178 void preparePayload() 179 { 180 // This code is a throw-free equivalent to 181 // beast::http::message::prepare_payload 182 std::optional<uint64_t> pSize = response.body().payloadSize(); 183 184 using http::status; 185 using http::status_class; 186 using http::to_status_class; 187 bool is1XXReturn = to_status_class(result()) == 188 status_class::informational; 189 if (!pSize) 190 { 191 response.chunked(true); 192 return; 193 } 194 response.content_length(*pSize); 195 196 if (is1XXReturn || result() == status::no_content || 197 result() == status::not_modified) 198 { 199 BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 200 "no-content or not_modified, which aren't " 201 "allowed to have a body", 202 logPtr(this)); 203 response.content_length(0); 204 return; 205 } 206 } 207 208 void clear() 209 { 210 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 211 response.clear(); 212 response.body().clear(); 213 214 jsonValue = nullptr; 215 completed = false; 216 expectedHash = std::nullopt; 217 } 218 219 std::string computeEtag() const 220 { 221 // Only set etag if this request succeeded 222 if (result() != http::status::ok) 223 { 224 return ""; 225 } 226 // and the json response isn't empty 227 if (jsonValue.empty()) 228 { 229 return ""; 230 } 231 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 232 return "\"" + intToHexString(hashval, 8) + "\""; 233 } 234 235 void write(std::string&& bodyPart) 236 { 237 response.body().str() = std::move(bodyPart); 238 } 239 240 void end() 241 { 242 if (completed) 243 { 244 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 245 return; 246 } 247 completed = true; 248 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 249 if (completeRequestHandler) 250 { 251 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 252 completeRequestHandler(*this); 253 } 254 } 255 256 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 257 { 258 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 259 completeRequestHandler = std::move(handler); 260 261 // Now that we have a new completion handler attached, we're no longer 262 // complete 263 completed = false; 264 } 265 266 std::function<void(Response&)> releaseCompleteRequestHandler() 267 { 268 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 269 static_cast<bool>(completeRequestHandler)); 270 std::function<void(Response&)> ret = completeRequestHandler; 271 completeRequestHandler = nullptr; 272 completed = true; 273 return ret; 274 } 275 276 void setHashAndHandleNotModified() 277 { 278 // Can only hash if we have content that's valid 279 if (jsonValue.empty() || result() != http::status::ok) 280 { 281 return; 282 } 283 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 284 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\""; 285 addHeader(http::field::etag, hexVal); 286 if (expectedHash && hexVal == *expectedHash) 287 { 288 jsonValue = nullptr; 289 result(http::status::not_modified); 290 } 291 } 292 293 void setExpectedHash(std::string_view hash) 294 { 295 expectedHash = hash; 296 } 297 298 OpenCode openFile(const std::filesystem::path& path, 299 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 300 { 301 boost::beast::error_code ec; 302 response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 303 response.body().encodingType = enc; 304 if (ec) 305 { 306 BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(), 307 ec.value()); 308 if (ec.value() == boost::system::errc::no_such_file_or_directory) 309 { 310 return OpenCode::FileDoesNotExist; 311 } 312 return OpenCode::InternalError; 313 } 314 return OpenCode::Success; 315 } 316 317 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 318 { 319 boost::beast::error_code ec; 320 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 321 int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 322 if (retval == -1) 323 { 324 BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed"); 325 } 326 response.body().encodingType = enc; 327 response.body().setFd(fd, ec); 328 if (ec) 329 { 330 BMCWEB_LOG_ERROR("Failed to set fd"); 331 return false; 332 } 333 return true; 334 } 335 336 private: 337 std::optional<std::string> expectedHash; 338 bool completed = false; 339 std::function<void(Response&)> completeRequestHandler; 340 }; 341 } // namespace crow 342