rublon-ssh/PAM/ssh/include/rublon/curl.hpp
Bartosz Wieczorek 3c3c4131c6 Fix build on sun
2025-10-06 15:57:55 +00:00

182 lines
7.0 KiB
C++

#pragma once
#include <rublon/configuration.hpp>
#include <rublon/error.hpp>
#include <rublon/utils.hpp>
#include <tl/expected.hpp>
#include <curl/curl.h>
namespace rublon {
namespace {
size_t WriteMemoryCallback(void * contents, size_t size, size_t nmemb, void * userp) {
const size_t realsize = size * nmemb;
reinterpret_cast< std::pmr::string * >(userp)->append(static_cast< const char * >(contents), realsize);
return realsize;
}
} // namespace
struct Request {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string > headers;
std::pmr::string body;
public:
Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Request(const Request & res) = delete;
Request & operator=(const Request & res) = delete;
Request(Request && res) = delete;
Request & operator=(Request &&) = delete;
};
struct Response {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers;
std::pmr::string body;
public:
Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Response(const Response & res) = delete;
Response & operator=(const Response & res) = delete;
Response(Response && res) noexcept = default;
Response & operator=(Response && res) noexcept = default;
};
class CURL {
std::unique_ptr< ::CURL, void (*)(::CURL *) > curl;
std::reference_wrapper< const Configuration > _config;
const Configuration & conf() const noexcept {
return _config.get();
}
public:
CURL(const Configuration & config)
: curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)}, _config{config} {}
CURL(const CURL &) = delete;
CURL(CURL &&) = delete;
CURL & operator=(const CURL &) = delete;
CURL & operator=(CURL &&) = delete;
tl::expected< std::reference_wrapper< Response >, ConnectionError >
request(std::string_view uri, const Request & request, Response & response) const {
using namespace memory::literals;
memory::Monotonic_8k_Resource memoryResource;
std::pmr::string response_data{&memoryResource};
response_data.reserve(4_kB);
auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all);
std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) {
log(LogLevel::Debug, "%s header: %s: %s", "CURL", header.first.c_str(), header.second.c_str());
curl_headers.reset(curl_slist_append(curl_headers.release(), (header.first + ": " + header.second).c_str()));
});
// Optional: Build full proxy URL if proxy is enabled
if(conf().proxyEnabled) {
// configuration reader check if proxy has needed fields
assert(conf().proxyType.has_value());
assert(conf().proxyHost.has_value());
log(LogLevel::Debug, "CURL using proxy");
std::pmr::string proxyUrl{&memoryResource};
proxyUrl.reserve(conservative_estimate(conf().proxyType, conf().proxyHost, conf().proxyPort) + 10);
if(conf().proxyType == "http" || conf().proxyType == "https" || conf().proxyType == "socks4" || conf().proxyType == "socks5") {
proxyUrl = *conf().proxyType;
proxyUrl += "://";
proxyUrl += *conf().proxyHost;
if(conf().proxyPort.value_or(0) > 0) {
proxyUrl += ":";
proxyUrl += std::to_string(*conf().proxyPort);
}
log(LogLevel::Debug, "CURL using %s", proxyUrl.c_str());
curl_easy_setopt(curl.get(), CURLOPT_PROXY, proxyUrl.c_str());
if(conf().proxyType == "socks4") {
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
} else if(conf().proxyType == "socks5") {
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
} else {
curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
}
if(conf().proxyAuthRequired) {
assert(conf().proxyUsername.has_value());
assert(conf().proxyPass.has_value());
std::pmr::string proxyAuth{&memoryResource};
proxyAuth.reserve(conservative_estimate(conf().proxyUsername, conf().proxyPass));
proxyAuth += *conf().proxyUsername;
if(conf().proxyPass->size()) {
// can proxy have name but no pass?
proxyAuth += ":";
proxyAuth += *conf().proxyPass;
}
curl_easy_setopt(curl.get(), CURLOPT_PROXYUSERPWD, proxyAuth.c_str());
}
}
}
curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 0);
curl_easy_setopt(curl.get(), CURLOPT_URL, uri.data());
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get());
curl_easy_setopt(curl.get(), CURLOPT_POST, 1);
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, request.body.data());
curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, static_cast< uint32_t >(request.body.size()));
curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1);
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data);
log(LogLevel::Debug, "Sending request to %s", uri.data());
for(const auto & [name, value] : request.headers) {
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
}
log(LogLevel::Debug, "Body %s", request.body.c_str());
const auto res = curl_easy_perform(curl.get());
if(res != CURLE_OK) {
log(LogLevel::Error, "%s no response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res));
return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}};
}
long http_code = 0;
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
if(http_code >= 500) {
log(LogLevel::Error, "%s response with code %d ", "CURL", http_code);
return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}};
}
long size{};
curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size);
details::headers(response_data, response.headers);
response.body = response_data.substr(size);
log(LogLevel::Debug, "Received %d bytes", response_data.size());
for(const auto & [name, value] : response.headers) {
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
}
log(LogLevel::Debug, "Body %s", response.body.c_str());
return response;
}
};
} // namespace rublon