/* 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