diff options
author | Ed Tanous <ed@tanous.net> | 2024-03-15 19:05:04 +0300 |
---|---|---|
committer | Ed Tanous <ed@tanous.net> | 2024-03-21 21:46:05 +0300 |
commit | 325310d3c5b7de591533a00d9cf26054fa0c0f9d (patch) | |
tree | 07c7171868a833fe552b5581ae680ac4e2347c6d /http | |
parent | 8983cf565b1695040938a30adcfb4f32f7972ae0 (diff) | |
download | bmcweb-325310d3c5b7de591533a00d9cf26054fa0c0f9d.tar.xz |
Allow reading http2 bodies
This allows http2 connections to now host authenticated endpoints.
Note, this work exposed that the http2 path was not calling
preparePayload() and responses were therefore missing the
Content-Length header. preparePayload is now called, and Content-Length
is added to the unit tests.
This commit also allows a full Redfish Service Validator test to pass
entirely using HTTP2.
Tested: Unit tests pass.
Curl /redfish/v1/Managers/bmc/LogServices/Journal/Entries
(which returns a payload larger than 16kB) succeeds and returns the
data.
Manually logging in with both basic and session authentication succeeds
over http2.
A modified Redfish-Service-Validator, changed to use httpx as its
backend, (thus using http2) succeeds.
Change-Id: I956f3ff8f442e9826312c6147d7599ab136a8e7c
Signed-off-by: Ed Tanous <ed@tanous.net>
Diffstat (limited to 'http')
-rw-r--r-- | http/http2_connection.hpp | 101 | ||||
-rw-r--r-- | http/nghttp2_adapters.hpp | 7 |
2 files changed, 98 insertions, 10 deletions
diff --git a/http/http2_connection.hpp b/http/http2_connection.hpp index ed3748059b..a698d9eb0d 100644 --- a/http/http2_connection.hpp +++ b/http/http2_connection.hpp @@ -4,6 +4,7 @@ #include "async_resp.hpp" #include "authentication.hpp" #include "complete_response_fields.hpp" +#include "http_body.hpp" #include "http_response.hpp" #include "http_utility.hpp" #include "logging.hpp" @@ -38,6 +39,7 @@ namespace crow struct Http2StreamData { Request req{}; + std::optional<bmcweb::HttpBody::reader> reqReader; Response res{}; std::optional<bmcweb::HttpBody::writer> writer; }; @@ -108,11 +110,12 @@ class HTTP2Connection : stream.writer->getWithMaxSize(ec, length); if (ec) { + BMCWEB_LOG_CRITICAL("Failed to get buffer"); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } - if (!out) { + BMCWEB_LOG_ERROR("Empty file, setting EOF"); *dataFlags |= NGHTTP2_DATA_FLAG_EOF; return 0; } @@ -120,20 +123,28 @@ class HTTP2Connection : BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size()); if (length < out->first.size()) { + BMCWEB_LOG_CRITICAL( + "Buffer overflow that should never happen happened"); // Should never happen because of length limit on get() above return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } - + boost::asio::mutable_buffer writeableBuf(buf, length); BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size()); - memcpy(buf, out->first.data(), out->first.size()); + size_t copied = boost::asio::buffer_copy(writeableBuf, out->first); + if (copied != out->first.size()) + { + BMCWEB_LOG_ERROR( + "Couldn't copy all {} bytes into buffer, only copied {}", + out->first.size(), copied); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } if (!out->second) { - BMCWEB_LOG_DEBUG("Setting OEF and nocopy flag"); + BMCWEB_LOG_DEBUG("Setting EOF flag"); *dataFlags |= NGHTTP2_DATA_FLAG_EOF; - //*dataFlags |= NGHTTP2_DATA_FLAG_NO_COPY; } - return static_cast<ssize_t>(out->first.size()); + return static_cast<ssize_t>(copied); } nghttp2_nv headerFromStringViews(std::string_view name, @@ -161,6 +172,7 @@ class HTTP2Connection : completeResponseFields(thisReq, thisRes); thisRes.addHeader(boost::beast::http::field::date, getCachedDateStr()); + thisRes.preparePayload(); boost::beast::http::fields& fields = thisRes.fields(); std::string code = std::to_string(thisRes.resultInt()); @@ -200,6 +212,7 @@ class HTTP2Connection : callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic); callbacks.setOnHeaderCallback(onHeaderCallbackStatic); callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic); + callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic); nghttp2_session session(callbacks); session.setUserData(this); @@ -217,7 +230,17 @@ class HTTP2Connection : close(); return -1; } - + if (it->second.reqReader) + { + boost::beast::error_code ec; + it->second.reqReader->finish(ec); + if (ec) + { + BMCWEB_LOG_CRITICAL("Failed to finalize payload"); + close(); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + } crow::Request& thisReq = it->second.req; BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), thisReq.url().encoded_path()); @@ -235,11 +258,69 @@ class HTTP2Connection : }); auto asyncResp = std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); - handler->handle(thisReq, asyncResp); +#ifndef BMCWEB_INSECURE_DISABLE_AUTHX + thisReq.session = crow::authentication::authenticate( + {}, thisRes, thisReq.method(), thisReq.req, nullptr); + if (!crow::authentication::isOnAllowlist(thisReq.url().path(), + thisReq.method()) && + thisReq.session == nullptr) + { + BMCWEB_LOG_WARNING("Authentication failed"); + forward_unauthorized::sendUnauthorized( + thisReq.url().encoded_path(), + thisReq.getHeaderValue("X-Requested-With"), + thisReq.getHeaderValue("Accept"), thisRes); + } + else +#endif // BMCWEB_INSECURE_DISABLE_AUTHX + { + handler->handle(thisReq, asyncResp); + } + return 0; + } + int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId, + const uint8_t* data, size_t len) + { + auto thisStream = streams.find(streamId); + if (thisStream == streams.end()) + { + BMCWEB_LOG_ERROR("Unknown stream{}", streamId); + close(); + return -1; + } + if (!thisStream->second.reqReader) + { + thisStream->second.reqReader.emplace( + thisStream->second.req.req.base(), + thisStream->second.req.req.body()); + } + boost::beast::error_code ec; + thisStream->second.reqReader->put(boost::asio::const_buffer(data, len), + ec); + if (ec) + { + BMCWEB_LOG_CRITICAL("Failed to write payload"); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } return 0; } + static int onDataChunkRecvStatic(nghttp2_session* /* session */, + uint8_t flags, int32_t streamId, + const uint8_t* data, size_t len, + void* userData) + { + BMCWEB_LOG_DEBUG("on_frame_recv_callback"); + if (userData == nullptr) + { + BMCWEB_LOG_CRITICAL("user data was null?"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return userPtrToSelf(userData).onDataChunkRecvCallback(flags, streamId, + data, len); + } + int onFrameRecvCallback(const nghttp2_frame& frame) { BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); @@ -450,8 +531,8 @@ class HTTP2Connection : return; } isWriting = true; - adaptor.async_write_some( - boost::asio::buffer(data.data(), data.size()), + boost::asio::async_write( + adaptor, boost::asio::const_buffer(data.data(), data.size()), std::bind_front(afterWriteBuffer, shared_from_this())); } diff --git a/http/nghttp2_adapters.hpp b/http/nghttp2_adapters.hpp index aeb18d60ab..3235372157 100644 --- a/http/nghttp2_adapters.hpp +++ b/http/nghttp2_adapters.hpp @@ -84,6 +84,13 @@ struct nghttp2_session_callbacks ptr, afterSendFrame); } + void setOnDataChunkRecvCallback( + nghttp2_on_data_chunk_recv_callback afterDataChunkRecv) + { + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + ptr, afterDataChunkRecv); + } + private: nghttp2_session_callbacks* get() { |