summaryrefslogtreecommitdiff
path: root/http
diff options
context:
space:
mode:
authorEd Tanous <ed@tanous.net>2024-03-15 19:05:04 +0300
committerEd Tanous <ed@tanous.net>2024-03-21 21:46:05 +0300
commit325310d3c5b7de591533a00d9cf26054fa0c0f9d (patch)
tree07c7171868a833fe552b5581ae680ac4e2347c6d /http
parent8983cf565b1695040938a30adcfb4f32f7972ae0 (diff)
downloadbmcweb-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.hpp101
-rw-r--r--http/nghttp2_adapters.hpp7
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()
{