/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
#ifndef HTTPSCLIENT_H
#define HTTPSCLIENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include // for SSL_set_tlsext_host_name
namespace ssl = boost::asio::ssl;
namespace asio = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
struct PooledStream {
boost::asio::ssl::stream stream;
boost::asio::ip::tcp::resolver resolver;
boost::asio::steady_timer connect_timer;
boost::asio::steady_timer handshake_timer;
PooledStream(boost::asio::io_context& ioc,
boost::asio::ssl::context& ssl_ctx)
: stream(ioc, ssl_ctx)
, resolver(ioc)
, connect_timer(ioc)
, handshake_timer(ioc)
{}
void prepare(const std::string& server,
const std::string& port,
std::function on_ready)
{
auto endpoints = resolver.resolve(server, port);
// — Step 1: async connect + 2s timeout
connect_timer.expires_after(std::chrono::seconds(2));
connect_timer.async_wait([this, on_ready](auto ec){
if (!ec) {
stream.lowest_layer().cancel();
on_ready(boost::asio::error::timed_out);
}
});
boost::asio::async_connect(
stream.lowest_layer(), endpoints,
[this, server, on_ready](auto ec, auto){
connect_timer.cancel();
if (ec) return on_ready(ec);
// ** SNI: must set the host name for TLS before handshake **
if (!SSL_set_tlsext_host_name(stream.native_handle(), server.c_str())) {
// pull the OpenSSL error and report it
auto err = ::ERR_get_error();
return on_ready(
boost::system::error_code(
static_cast(err),
boost::asio::error::get_ssl_category()
)
);
}
// — Step 2: async handshake + 2s timeout
handshake_timer.expires_after(std::chrono::seconds(2));
handshake_timer.async_wait([this](auto ec){
if (!ec) stream.lowest_layer().cancel();
});
stream.async_handshake(
boost::asio::ssl::stream_base::client,
[this, on_ready](auto ec){
handshake_timer.cancel();
on_ready(ec);
}
);
}
);
}
};
// --- ConnectionPool.h ---------------------------------------
class ConnectionPool {
public:
ConnectionPool(boost::asio::io_context& ioc,
boost::asio::ssl::context& ssl_ctx)
: ioc_(ioc), ssl_ctx_(ssl_ctx) {}
// Acquire a ready PooledStream (connected + handshaken),
// or create one and run prepare().
void acquire(const std::string& server,
const std::string& port,
std::function, boost::system::error_code)> cb)
{
std::string key = server + ":" + port;
{
std::lock_guard lk(mutex_);
auto &dq = free_[key];
if (!dq.empty()) {
auto ps = dq.front();
dq.pop_front();
return cb(ps, {});
}
}
// no free stream → make a new one and prepare it
auto ps = std::make_shared(ioc_, ssl_ctx_);
ps->prepare(server, port, [this, server, port, ps, cb](auto ec) {
if (ec) {
cb(nullptr, ec);
} else {
cb(ps, {});
}
});
}
// Return a stream to the free list
void release(const std::string& server,
const std::string& port,
std::shared_ptr ps)
{
std::string key = server + ":" + port;
// clear any leftover data in the buffer
// (you might want to reset ps->buffer here if it’s stored inside)
std::lock_guard lk(mutex_);
free_[key].push_back(ps);
}
private:
boost::asio::io_context& ioc_;
boost::asio::ssl::context& ssl_ctx_;
std::mutex mutex_;
std::unordered_map>> free_;
};
class HTTPSClient {
public:
HTTPSClient(const std::string& certFile, const std::string& keyFile);
~HTTPSClient();
// — Async overloads —
void sendRequest(
const std::string& server,
const std::string& port,
const std::string& target,
std::function done);
void sendPostRequest(
const std::string& server,
const std::string& port,
const std::string& target,
const std::string& jsonPayload,
std::function done);
// — Legacy synchronous wrappers —
std::string sendRequest(
const std::string& server,
const std::string& port,
const std::string& target);
std::string sendPostRequest(
const std::string& server,
const std::string& port,
const std::string& target,
const std::string& jsonPayload);
std::string getServer() const { return server; }
std::string getPort() const { return port; }
private:
std::unordered_map cookies;
std::shared_ptr sslCtx;
boost::asio::io_context ioc_;
boost::asio::executor_work_guard workGuard_;
ConnectionPool pool_;
std::thread runner_;
std::string certFile;
std::string keyFile;
std::string server;
std::string port;
void parseAndStoreCookies(const http::response& res);
std::shared_ptr createSSLContext(); // New helper function
std::string buildCookieHeader() const;
};
#endif // HTTPSCLIENT_H