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 <boost/url/url_view.hpp> 17 #include <nlohmann/json.hpp> 18 19 #include <cstddef> 20 #include <cstdint> 21 #include <filesystem> 22 #include <functional> 23 #include <optional> 24 #include <string> 25 #include <string_view> 26 #include <utility> 27 28 namespace crow 29 { 30 31 template <typename Adaptor, typename Handler> 32 class Connection; 33 34 namespace http = boost::beast::http; 35 36 enum class OpenCode 37 { 38 Success, 39 FileDoesNotExist, 40 InternalError, 41 }; 42 43 struct Response 44 { 45 template <typename Adaptor, typename Handler> 46 friend class Connection; 47 48 http::response<bmcweb::HttpBody> response; 49 50 nlohmann::json jsonValue; 51 using fields_type = http::header<false, http::fields>; fieldscrow::Response52 fields_type& fields() 53 { 54 return response.base(); 55 } 56 fieldscrow::Response57 const fields_type& fields() const 58 { 59 return response.base(); 60 } 61 addHeadercrow::Response62 void addHeader(std::string_view key, std::string_view value) 63 { 64 fields().insert(key, value); 65 } 66 addHeadercrow::Response67 void addHeader(http::field key, std::string_view value) 68 { 69 fields().insert(key, value); 70 } 71 clearHeadercrow::Response72 void clearHeader(http::field key) 73 { 74 fields().erase(key); 75 } 76 77 Response() = default; Responsecrow::Response78 Response(Response&& res) noexcept : 79 response(std::move(res.response)), jsonValue(std::move(res.jsonValue)), 80 requestExpectedEtag(std::move(res.requestExpectedEtag)), 81 currentOverrideEtag(std::move(res.currentOverrideEtag)), 82 completed(res.completed) 83 { 84 // See note in operator= move handler for why this is needed. 85 if (!res.completed) 86 { 87 completeRequestHandler = std::move(res.completeRequestHandler); 88 res.completeRequestHandler = nullptr; 89 } 90 } 91 92 ~Response() = default; 93 94 Response(const Response&) = delete; 95 Response& operator=(const Response& r) = delete; 96 operator =crow::Response97 Response& operator=(Response&& r) noexcept 98 { 99 BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}", 100 logPtr(this), logPtr(&r)); 101 if (this == &r) 102 { 103 return *this; 104 } 105 response = std::move(r.response); 106 jsonValue = std::move(r.jsonValue); 107 requestExpectedEtag = std::move(r.requestExpectedEtag); 108 currentOverrideEtag = std::move(r.currentOverrideEtag); 109 110 // Only need to move completion handler if not already completed 111 // Note, there are cases where we might move out of a Response object 112 // while in a completion handler for that response object. This check 113 // is intended to prevent destructing the functor we are currently 114 // executing from in that case. 115 if (!r.completed) 116 { 117 completeRequestHandler = std::move(r.completeRequestHandler); 118 r.completeRequestHandler = nullptr; 119 } 120 else 121 { 122 completeRequestHandler = nullptr; 123 } 124 completed = r.completed; 125 return *this; 126 } 127 resultcrow::Response128 void result(unsigned v) 129 { 130 fields().result(v); 131 } 132 resultcrow::Response133 void result(http::status v) 134 { 135 fields().result(v); 136 } 137 copyBodycrow::Response138 void copyBody(const Response& res) 139 { 140 response.body() = res.response.body(); 141 } 142 resultcrow::Response143 http::status result() const 144 { 145 return fields().result(); 146 } 147 resultIntcrow::Response148 unsigned resultInt() const 149 { 150 return fields().result_int(); 151 } 152 reasoncrow::Response153 std::string_view reason() const 154 { 155 return fields().reason(); 156 } 157 isCompletedcrow::Response158 bool isCompleted() const noexcept 159 { 160 return completed; 161 } 162 bodycrow::Response163 const std::string* body() 164 { 165 return &response.body().str(); 166 } 167 getHeaderValuecrow::Response168 std::string_view getHeaderValue(std::string_view key) const 169 { 170 return fields()[key]; 171 } 172 getHeaderValuecrow::Response173 std::string_view getHeaderValue(boost::beast::http::field key) const 174 { 175 return fields()[key]; 176 } 177 keepAlivecrow::Response178 void keepAlive(bool k) 179 { 180 response.keep_alive(k); 181 } 182 keepAlivecrow::Response183 bool keepAlive() const 184 { 185 return response.keep_alive(); 186 } 187 sizecrow::Response188 std::optional<uint64_t> size() 189 { 190 return response.body().payloadSize(); 191 } 192 preparePayloadcrow::Response193 void preparePayload(const boost::urls::url_view& urlView) 194 { 195 std::optional<uint64_t> pSize = response.body().payloadSize(); 196 197 using http::status; 198 using http::status_class; 199 using http::to_status_class; 200 bool is1XXReturn = to_status_class(result()) == 201 status_class::informational; 202 if (!pSize) 203 { 204 response.chunked(true); 205 return; 206 } 207 response.content_length(*pSize); 208 209 if ((*pSize > 0) && (is1XXReturn || result() == status::no_content || 210 result() == status::not_modified)) 211 { 212 BMCWEB_LOG_CRITICAL("{} Response content provided but code was " 213 "no-content or not_modified, which aren't " 214 "allowed to have a body for url : \"{}\"", 215 logPtr(this), urlView.path()); 216 response.content_length(0); 217 return; 218 } 219 } 220 clearcrow::Response221 void clear() 222 { 223 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this)); 224 response.clear(); 225 response.body().clear(); 226 227 jsonValue = nullptr; 228 completed = false; 229 requestExpectedEtag = std::nullopt; 230 currentOverrideEtag = std::nullopt; 231 } 232 setCurrentOverrideEtagcrow::Response233 void setCurrentOverrideEtag(std::string_view newEtag) 234 { 235 if (currentOverrideEtag) 236 { 237 BMCWEB_LOG_WARNING( 238 "Response override etag was incorrectly set twice"); 239 } 240 currentOverrideEtag = newEtag; 241 } 242 getCurrentEtagcrow::Response243 std::string getCurrentEtag() const 244 { 245 // Only set etag if this request succeeded 246 if (result() != http::status::ok) 247 { 248 return ""; 249 } 250 // and the json response isn't empty 251 if (jsonValue.empty()) 252 { 253 return ""; 254 } 255 256 if (currentOverrideEtag) 257 { 258 return currentOverrideEtag.value(); 259 } 260 261 size_t hashval = std::hash<nlohmann::json>{}(jsonValue); 262 return "\"" + intToHexString(hashval, 8) + "\""; 263 } 264 writecrow::Response265 void write(std::string&& bodyPart) 266 { 267 response.body().str() = std::move(bodyPart); 268 } 269 endcrow::Response270 void end() 271 { 272 if (completed) 273 { 274 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this)); 275 return; 276 } 277 completed = true; 278 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this)); 279 if (completeRequestHandler) 280 { 281 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this)); 282 completeRequestHandler(*this); 283 } 284 } 285 setCompleteRequestHandlercrow::Response286 void setCompleteRequestHandler(std::function<void(Response&)>&& handler) 287 { 288 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this)); 289 completeRequestHandler = std::move(handler); 290 291 // Now that we have a new completion handler attached, we're no longer 292 // complete 293 completed = false; 294 } 295 releaseCompleteRequestHandlercrow::Response296 std::function<void(Response&)> releaseCompleteRequestHandler() 297 { 298 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this), 299 static_cast<bool>(completeRequestHandler)); 300 std::function<void(Response&)> ret = completeRequestHandler; 301 completeRequestHandler = nullptr; 302 completed = true; 303 return ret; 304 } 305 setResponseEtagAndHandleNotModifiedcrow::Response306 void setResponseEtagAndHandleNotModified() 307 { 308 // Can only hash if we have content that's valid 309 if (jsonValue.empty() || result() != http::status::ok) 310 { 311 return; 312 } 313 std::string hexVal = getCurrentEtag(); 314 addHeader(http::field::etag, hexVal); 315 if (requestExpectedEtag && hexVal == *requestExpectedEtag) 316 { 317 jsonValue = nullptr; 318 result(http::status::not_modified); 319 } 320 } 321 getExpectedEtagcrow::Response322 std::optional<std::string_view> getExpectedEtag() const 323 { 324 return requestExpectedEtag; 325 } 326 setExpectedEtagcrow::Response327 void setExpectedEtag(std::string_view etag) 328 { 329 if (requestExpectedEtag) 330 { 331 BMCWEB_LOG_WARNING( 332 "Request expected etag was incorrectly set twice"); 333 } 334 requestExpectedEtag = etag; 335 } 336 openFilecrow::Response337 OpenCode openFile( 338 const std::filesystem::path& path, 339 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw, 340 bmcweb::CompressionType comp = bmcweb::CompressionType::Raw) 341 { 342 boost::beast::error_code ec; 343 response.body().open(path.c_str(), boost::beast::file_mode::read, ec); 344 response.body().encodingType = enc; 345 response.body().compressionType = comp; 346 if (ec) 347 { 348 BMCWEB_LOG_ERROR("Failed to open file {}, ec={}", path.c_str(), 349 ec.value()); 350 if (ec.value() == boost::system::errc::no_such_file_or_directory) 351 { 352 return OpenCode::FileDoesNotExist; 353 } 354 return OpenCode::InternalError; 355 } 356 return OpenCode::Success; 357 } 358 openFdcrow::Response359 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw) 360 { 361 boost::beast::error_code ec; 362 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 363 int retval = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 364 if (retval == -1) 365 { 366 BMCWEB_LOG_ERROR("Setting O_NONBLOCK failed"); 367 } 368 response.body().encodingType = enc; 369 response.body().setFd(fd, ec); 370 if (ec) 371 { 372 BMCWEB_LOG_ERROR("Failed to set fd"); 373 return false; 374 } 375 return true; 376 } 377 378 private: 379 std::optional<std::string> requestExpectedEtag; 380 std::optional<std::string> currentOverrideEtag; 381 bool completed = false; 382 std::function<void(Response&)> completeRequestHandler; 383 }; 384 } // namespace crow 385