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