correct a regression with HTTP GET due to previous HTTP hardening;

pull/128/head
Bryan Biedenkapp 2 months ago
parent 47fff42173
commit f3652d39a7

@ -390,7 +390,10 @@ void HTTPPayload::ensureDefaultHeaders(const std::string& contentType)
else {
headers.add("User-Agent", std::string(("DVM/" __VER__)));
headers.add("Accept", "*/*");
if (::strtoupper(method) != HTTP_GET) {
// GET requests normally have no entity body. If a caller does attach one,
// however, it still needs explicit framing; otherwise a strict server will
// correctly treat the unannounced bytes as data beyond the request.
if (::strtoupper(method) != HTTP_GET || !content.empty()) {
headers.add("Content-Type", std::string(contentType));
headers.add("Content-Length", std::to_string(content.size()));
}

@ -163,19 +163,19 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
#if defined(ENABLE_SSL)
if (s_enableSSL) {
sslClient = new SecureHTTPClient<RESTDispatcherType>(address, port);
sslClient->setHandler(m_dispatcher);
if (!sslClient->open()) {
delete sslClient;
return ERRNO_SOCK_OPEN;
}
sslClient->setHandler(m_dispatcher);
} else {
#endif // ENABLE_SSL
client = new HTTPClient<RESTDispatcherType>(address, port);
client->setHandler(m_dispatcher);
if (!client->open()) {
delete client;
return ERRNO_SOCK_OPEN;
}
client->setHandler(m_dispatcher);
#if defined(ENABLE_SSL)
}
#endif // ENABLE_SSL
@ -276,19 +276,19 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
#if defined(ENABLE_SSL)
if (s_enableSSL) {
sslClient = new SecureHTTPClient<RESTDispatcherType>(address, port);
sslClient->setHandler(m_dispatcher);
if (!sslClient->open()) {
delete sslClient;
return ERRNO_SOCK_OPEN;
}
sslClient->setHandler(m_dispatcher);
} else {
#endif // ENABLE_SSL
client = new HTTPClient<RESTDispatcherType>(address, port);
client->setHandler(m_dispatcher);
if (!client->open()) {
delete client;
return ERRNO_SOCK_OPEN;
}
client->setHandler(m_dispatcher);
#if defined(ENABLE_SSL)
}
#endif // ENABLE_SSL

@ -11,6 +11,7 @@
#include <catch2/catch_test_macros.hpp>
#include "common/restapi/http/HTTPLexer.h"
#include "common/restapi/http/HTTPClient.h"
#include "common/restapi/http/HTTPPayload.h"
#include "common/restapi/http/HTTPServer.h"
@ -44,6 +45,12 @@ namespace {
HTTPPayload request;
};
struct ResponseState {
std::atomic<bool> received { false };
std::mutex mutex;
HTTPPayload response;
};
/**
* @brief Sends a raw HTTP request to the specified port using the provided fragments and returns the response.
* @param port The port to which the HTTP request should be sent.
@ -122,6 +129,25 @@ private:
std::shared_ptr<RequestState> m_state;
};
class ClientResponseHandler {
public:
ClientResponseHandler() : m_state(std::make_shared<ResponseState>()) { }
explicit ClientResponseHandler(std::shared_ptr<ResponseState> state) : m_state(std::move(state)) { }
void handleRequest(const HTTPPayload& response, HTTPPayload& reply)
{
(void)reply;
{
std::lock_guard<std::mutex> lock(m_state->mutex);
m_state->response = response;
}
m_state->received = true;
}
private:
std::shared_ptr<ResponseState> m_state;
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
@ -246,6 +272,45 @@ TEST_CASE("HTTP status payload applies default REST headers", "[restapi][http][p
REQUIRE(payload.headers.find("Server").empty() == false);
}
TEST_CASE("HTTP client frames a GET request carrying JSON", "[restapi][http][client][e2e]")
{
LoopbackHTTPServer fixture;
auto responseState = std::make_shared<ResponseState>();
HTTPClient<ClientResponseHandler> client("127.0.0.1", fixture.port);
client.setHandler(ClientResponseHandler(responseState));
REQUIRE(client.open());
json::object body = json::object();
HTTPPayload request = HTTPPayload::requestPayload(HTTP_GET, "/echo");
request.payload(body);
REQUIRE(request.content.empty() == false);
REQUIRE(request.headers.find("Content-Type") == "application/json");
REQUIRE(request.headers.find("Content-Length") == std::to_string(request.content.size()));
REQUIRE(client.request(request));
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!responseState->received && std::chrono::steady_clock::now() < deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
client.close();
REQUIRE(responseState->received);
REQUIRE(fixture.state->calls == 1U);
{
std::lock_guard<std::mutex> lock(fixture.state->mutex);
REQUIRE(fixture.state->request.method == HTTP_GET);
REQUIRE(fixture.state->request.uri == "/echo");
REQUIRE(fixture.state->request.content == request.content);
}
{
std::lock_guard<std::mutex> lock(responseState->mutex);
REQUIRE(responseState->response.status == HTTPPayload::OK);
REQUIRE(responseState->response.content == request.content);
}
}
TEST_CASE("HTTP server handles a complete request over TCP", "[restapi][http][e2e]")
{
LoopbackHTTPServer fixture;

Loading…
Cancel
Save

Powered by TurnKey Linux.