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