rublon-ssh/PAM/ssh/include/rublon/curl.hpp
Bartosz Wieczorek 700845e17a refactor
2023-08-22 13:34:40 +02:00

97 lines
3.5 KiB
C++

#pragma once
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <curl/curl.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <memory_resource>
#include <rublon/utils.hpp>
#include <rublon/error.hpp>
#include <tl/expected.hpp>
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::map< std::pmr::string, std::pmr::string > headers;
std::pmr::string body;
Request(std::pmr::memory_resource & mr) : headers{&mr}, body{&mr} {}
};
struct Response {
std::map< std::string, std::string > headers;
std::string body;
};
class CURL {
std::unique_ptr< ::CURL, void (*)(::CURL *) > curl;
std::function< Response() > _responseFactory;
public:
CURL(std::function< Response() > responseFactory)
: curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)},
_responseFactory{std::move(responseFactory)} {}
tl::expected< Response, Error > request(std::string_view uri, const Request & request) const {
std::array< char, 16 * 1024 > buffer = {};
std::pmr::monotonic_buffer_resource mr{buffer.data(), buffer.size()};
std::pmr::string response_data{&mr};
response_data.reserve(15000);
/// TODO this can be done on stack using pmr
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()));
});
curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1);
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< u_int32_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, "%s Request send, uri:%s body:\n%s\n", "CURL", uri.data(), request.body.c_str());
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{Error{SocketError{SocketError::Timeout}}};
}
log(LogLevel::Debug, "Response:\n%s\n", response_data.c_str());
Response response;
long size;
curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size);
response.headers = details::headers({response_data.data(), static_cast< std::size_t >(size)});
response.body = response_data.substr(size);
return response;
}
};
} // namespace rublon