Files
rippled/src/libxrpl/net/HTTPClient.cpp
Pratik Mankawde 00f207cc79 Fixed asan and lsan issues
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
2025-12-17 14:54:55 +00:00

615 lines
17 KiB
C++

#include <xrpl/basics/Log.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/net/AutoSocket.h>
#include <xrpl/net/HTTPClient.h>
#include <xrpl/net/HTTPClientSSLContext.h>
#include <boost/asio.hpp>
#include <boost/asio/ip/resolver_query_base.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/regex.hpp>
#include <optional>
namespace xrpl {
static std::optional<HTTPClientSSLContext> httpClientSSLContext;
void
HTTPClient::initializeSSLContext(
std::string const& sslVerifyDir,
std::string const& sslVerifyFile,
bool sslVerify,
beast::Journal j)
{
httpClientSSLContext.emplace(sslVerifyDir, sslVerifyFile, sslVerify, j);
}
void
HTTPClient::cleanupSSLContext()
{
httpClientSSLContext.reset();
}
//------------------------------------------------------------------------------
//
// Fetch a web page via http or https.
//
//------------------------------------------------------------------------------
class HTTPClientImp : public std::enable_shared_from_this<HTTPClientImp>,
public HTTPClient
{
public:
HTTPClientImp(
boost::asio::io_context& io_context,
unsigned short const port,
std::size_t maxResponseSize,
beast::Journal& j)
: mSocket(io_context, httpClientSSLContext->context())
, mResolver(io_context)
, mHeader(maxClientHeaderBytes)
, mPort(port)
, maxResponseSize_(maxResponseSize)
, mDeadline(io_context)
, j_(j)
{
}
//--------------------------------------------------------------------------
void
makeGet(
std::string const& strPath,
boost::asio::streambuf& sb,
std::string const& strHost)
{
std::ostream osRequest(&sb);
osRequest << "GET " << strPath
<< " HTTP/1.0\r\n"
"Host: "
<< strHost
<< "\r\n"
"Accept: */*\r\n" // YYY Do we need this line?
"Connection: close\r\n\r\n";
}
//--------------------------------------------------------------------------
void
request(
bool bSSL,
std::deque<std::string> deqSites,
std::function<
void(boost::asio::streambuf& sb, std::string const& strHost)> build,
std::chrono::seconds timeout,
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)> complete)
{
mSSL = bSSL;
mDeqSites = deqSites;
mBuild = build;
mComplete = complete;
mTimeout = timeout;
httpsNext();
}
//--------------------------------------------------------------------------
void
get(bool bSSL,
std::deque<std::string> deqSites,
std::string const& strPath,
std::chrono::seconds timeout,
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)> complete)
{
mComplete = complete;
mTimeout = timeout;
request(
bSSL,
deqSites,
std::bind(
&HTTPClientImp::makeGet,
shared_from_this(),
strPath,
std::placeholders::_1,
std::placeholders::_2),
timeout,
complete);
}
//--------------------------------------------------------------------------
void
httpsNext()
{
JLOG(j_.trace()) << "Fetch: " << mDeqSites[0];
auto query = std::make_shared<Query>(
mDeqSites[0],
std::to_string(mPort),
boost::asio::ip::resolver_query_base::numeric_service);
mQuery = query;
try
{
mDeadline.expires_after(mTimeout);
}
catch (boost::system::system_error const& e)
{
mShutdown = e.code();
JLOG(j_.trace()) << "expires_after: " << mShutdown.message();
mDeadline.async_wait(std::bind(
&HTTPClientImp::handleDeadline,
shared_from_this(),
std::placeholders::_1));
}
if (!mShutdown)
{
JLOG(j_.trace()) << "Resolving: " << mDeqSites[0];
mResolver.async_resolve(
mQuery->host,
mQuery->port,
mQuery->flags,
std::bind(
&HTTPClientImp::handleResolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
if (mShutdown)
invokeComplete(mShutdown);
}
void
handleDeadline(boost::system::error_code const& ecResult)
{
if (ecResult == boost::asio::error::operation_aborted)
{
// Timer canceled because deadline no longer needed.
JLOG(j_.trace()) << "Deadline cancelled.";
// Aborter is done.
}
else if (ecResult)
{
JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": "
<< ecResult.message();
// Can't do anything sound.
abort();
}
else
{
JLOG(j_.trace()) << "Deadline arrived.";
// Mark us as shutting down.
// XXX Use our own error code.
mShutdown = boost::system::error_code{
boost::system::errc::bad_address,
boost::system::system_category()};
// Cancel any resolving.
mResolver.cancel();
// Stop the transaction.
mSocket.async_shutdown(std::bind(
&HTTPClientImp::handleShutdown,
shared_from_this(),
std::placeholders::_1));
}
}
void
handleShutdown(boost::system::error_code const& ecResult)
{
if (ecResult)
{
JLOG(j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": "
<< ecResult.message();
}
}
void
handleResolve(
boost::system::error_code const& ecResult,
boost::asio::ip::tcp::resolver::results_type result)
{
if (!mShutdown)
{
mShutdown = ecResult ? ecResult
: httpClientSSLContext->preConnectVerify(
mSocket.SSLSocket(), mDeqSites[0]);
}
if (mShutdown)
{
JLOG(j_.trace()) << "Resolve error: " << mDeqSites[0] << ": "
<< mShutdown.message();
invokeComplete(mShutdown);
}
else
{
JLOG(j_.trace()) << "Resolve complete.";
boost::asio::async_connect(
mSocket.lowest_layer(),
result,
std::bind(
&HTTPClientImp::handleConnect,
shared_from_this(),
std::placeholders::_1));
}
}
void
handleConnect(boost::system::error_code const& ecResult)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
JLOG(j_.trace()) << "Connect error: " << mShutdown.message();
}
if (!mShutdown)
{
JLOG(j_.trace()) << "Connected.";
mShutdown = httpClientSSLContext->postConnectVerify(
mSocket.SSLSocket(), mDeqSites[0]);
if (mShutdown)
{
JLOG(j_.trace()) << "postConnectVerify: " << mDeqSites[0]
<< ": " << mShutdown.message();
}
}
if (mShutdown)
{
invokeComplete(mShutdown);
}
else if (mSSL)
{
mSocket.async_handshake(
AutoSocket::ssl_socket::client,
std::bind(
&HTTPClientImp::handleRequest,
shared_from_this(),
std::placeholders::_1));
}
else
{
handleRequest(ecResult);
}
}
void
handleRequest(boost::system::error_code const& ecResult)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
JLOG(j_.trace()) << "Handshake error:" << mShutdown.message();
invokeComplete(mShutdown);
}
else
{
JLOG(j_.trace()) << "Session started.";
mBuild(mRequest, mDeqSites[0]);
mSocket.async_write(
mRequest,
std::bind(
&HTTPClientImp::handleWrite,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
}
void
handleWrite(
boost::system::error_code const& ecResult,
std::size_t bytes_transferred)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown)
{
JLOG(j_.trace()) << "Write error: " << mShutdown.message();
invokeComplete(mShutdown);
}
else
{
JLOG(j_.trace()) << "Wrote.";
mSocket.async_read_until(
mHeader,
"\r\n\r\n",
std::bind(
&HTTPClientImp::handleHeader,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
}
void
handleHeader(
boost::system::error_code const& ecResult,
std::size_t bytes_transferred)
{
std::string strHeader{
{std::istreambuf_iterator<char>(&mHeader)},
std::istreambuf_iterator<char>()};
JLOG(j_.trace()) << "Header: \"" << strHeader << "\"";
static boost::regex reStatus{
"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
static boost::regex reSize{
"\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase};
static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"};
boost::smatch smMatch;
// Match status code.
if (!boost::regex_match(strHeader, smMatch, reStatus))
{
// XXX Use our own error code.
JLOG(j_.trace()) << "No status code";
invokeComplete(boost::system::error_code{
boost::system::errc::bad_address,
boost::system::system_category()});
return;
}
mStatus = beast::lexicalCastThrow<int>(std::string(smMatch[1]));
if (boost::regex_match(strHeader, smMatch, reBody)) // we got some body
mBody = smMatch[1];
std::size_t const responseSize = [&] {
if (boost::regex_match(strHeader, smMatch, reSize))
return beast::lexicalCast<std::size_t>(
std::string(smMatch[1]), maxResponseSize_);
return maxResponseSize_;
}();
if (responseSize > maxResponseSize_)
{
JLOG(j_.trace()) << "Response field too large";
invokeComplete(boost::system::error_code{
boost::system::errc::value_too_large,
boost::system::system_category()});
return;
}
if (responseSize == 0)
{
// no body wanted or available
invokeComplete(ecResult, mStatus);
}
else if (mBody.size() >= responseSize)
{
// we got the whole thing
invokeComplete(ecResult, mStatus, mBody);
}
else
{
mSocket.async_read(
mResponse.prepare(responseSize - mBody.size()),
boost::asio::transfer_all(),
std::bind(
&HTTPClientImp::handleData,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
}
void
handleData(
boost::system::error_code const& ecResult,
std::size_t bytes_transferred)
{
if (!mShutdown)
mShutdown = ecResult;
if (mShutdown && mShutdown != boost::asio::error::eof)
{
JLOG(j_.trace()) << "Read error: " << mShutdown.message();
invokeComplete(mShutdown);
}
else
{
if (mShutdown)
{
JLOG(j_.trace()) << "Complete.";
}
else
{
mResponse.commit(bytes_transferred);
std::string strBody{
{std::istreambuf_iterator<char>(&mResponse)},
std::istreambuf_iterator<char>()};
invokeComplete(ecResult, mStatus, mBody + strBody);
}
}
}
// Call cancel the deadline timer and invoke the completion routine.
void
invokeComplete(
boost::system::error_code const& ecResult,
int iStatus = 0,
std::string const& strData = "")
{
boost::system::error_code ecCancel;
try
{
mDeadline.cancel();
}
catch (boost::system::system_error const& e)
{
JLOG(j_.trace())
<< "invokeComplete: Deadline cancel error: " << e.what();
ecCancel = e.code();
}
JLOG(j_.debug()) << "invokeComplete: Deadline popping: "
<< mDeqSites.size();
if (!mDeqSites.empty())
{
mDeqSites.pop_front();
}
bool bAgain = true;
if (mDeqSites.empty() || !ecResult)
{
// ecResult: !0 = had an error, last entry
// iStatus: result, if no error
// strData: data, if no error
bAgain = mComplete &&
mComplete(ecResult ? ecResult : ecCancel, iStatus, strData);
}
if (!mDeqSites.empty() && bAgain)
{
httpsNext();
}
}
private:
using pointer = std::shared_ptr<HTTPClient>;
bool mSSL;
AutoSocket mSocket;
boost::asio::ip::tcp::resolver mResolver;
struct Query
{
std::string host;
std::string port;
boost::asio::ip::resolver_query_base::flags flags;
};
std::shared_ptr<Query> mQuery;
boost::asio::streambuf mRequest;
boost::asio::streambuf mHeader;
boost::asio::streambuf mResponse;
std::string mBody;
unsigned short const mPort;
std::size_t const maxResponseSize_;
int mStatus;
std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
mBuild;
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)>
mComplete;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
// If not success, we are shutting down.
boost::system::error_code mShutdown;
std::deque<std::string> mDeqSites;
std::chrono::seconds mTimeout;
beast::Journal j_;
};
//------------------------------------------------------------------------------
void
HTTPClient::get(
bool bSSL,
boost::asio::io_context& io_context,
std::deque<std::string> deqSites,
unsigned short const port,
std::string const& strPath,
std::size_t responseMax,
std::chrono::seconds timeout,
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)> complete,
beast::Journal& j)
{
auto client =
std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
client->get(bSSL, deqSites, strPath, timeout, complete);
}
void
HTTPClient::get(
bool bSSL,
boost::asio::io_context& io_context,
std::string strSite,
unsigned short const port,
std::string const& strPath,
std::size_t responseMax,
std::chrono::seconds timeout,
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)> complete,
beast::Journal& j)
{
std::deque<std::string> deqSites(1, strSite);
auto client =
std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
client->get(bSSL, deqSites, strPath, timeout, complete);
}
void
HTTPClient::request(
bool bSSL,
boost::asio::io_context& io_context,
std::string strSite,
unsigned short const port,
std::function<void(boost::asio::streambuf& sb, std::string const& strHost)>
setRequest,
std::size_t responseMax,
std::chrono::seconds timeout,
std::function<bool(
boost::system::error_code const& ecResult,
int iStatus,
std::string const& strData)> complete,
beast::Journal& j)
{
std::deque<std::string> deqSites(1, strSite);
auto client =
std::make_shared<HTTPClientImp>(io_context, port, responseMax, j);
client->request(bSSL, deqSites, setRequest, timeout, complete);
}
} // namespace xrpl