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