diff --git a/lib/libimhex/include/hex/helpers/http_requests.hpp b/lib/libimhex/include/hex/helpers/http_requests.hpp new file mode 100644 index 000000000..428dcd20d --- /dev/null +++ b/lib/libimhex/include/hex/helpers/http_requests.hpp @@ -0,0 +1,275 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace hex { + + class HttpRequest { + public: + + class ResultBase { + public: + explicit ResultBase(u32 statusCode) : m_statusCode(statusCode) { } + + [[nodiscard]] u32 getStatusCode() const { + return this->m_statusCode; + } + + [[nodiscard]] bool isSuccess() const { + return this->getStatusCode() == 200; + } + + private: + u32 m_statusCode; + }; + + template + class Result : public ResultBase { + public: + Result(u32 statusCode, T data) : ResultBase(statusCode), m_data(std::move(data)) { } + + [[nodiscard]] + const T& getData() const { + return this->m_data; + } + + private: + T m_data; + }; + + HttpRequest(std::string method, std::string url) : m_method(std::move(method)), m_url(std::move(url)) { + AT_FIRST_TIME { + curl_global_init(CURL_GLOBAL_ALL); + }; + + AT_FINAL_CLEANUP { + curl_global_cleanup(); + }; + + this->m_curl = curl_easy_init(); + } + + ~HttpRequest() { + curl_easy_cleanup(this->m_curl); + } + + static void setCaCert(std::string data) { + HttpRequest::s_caCertData = std::move(data); + } + + void addHeader(std::string key, std::string value) { + this->m_headers[std::move(key)] = std::move(value); + } + + void setBody(std::string body) { + this->m_body = std::move(body); + } + + template + std::future> downloadFile(const std::fs::path &path) { + return std::async(std::launch::async, [this, path] { + T response; + + wolv::io::File file(path, wolv::io::File::Mode::Create); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, [](void *contents, size_t size, size_t nmemb, void *userdata){ + auto &file = *reinterpret_cast(userdata); + + file.write(reinterpret_cast(contents), size * nmemb); + + return size * nmemb; + }); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &file); + + this->executeImpl(response); + + return Result(200, std::move(response)); + }); + } + + std::future>> downloadFile() { + return std::async(std::launch::async, [this] { + std::vector response; + + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, [](void *contents, size_t size, size_t nmemb, void *userdata){ + auto &response = *reinterpret_cast*>(userdata); + + response.insert(response.end(), reinterpret_cast(contents), reinterpret_cast(contents) + size * nmemb); + + return size * nmemb; + }); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &response); + + return this->executeImpl>(response); + }); + } + + template + std::future> uploadFile(const std::fs::path &path, const std::string &mimeName = "filename") { + return std::async(std::launch::async, [this, path, mimeName]{ + auto fileName = wolv::util::toUTF8String(path.filename()); + + curl_mime *mime = curl_mime_init(this->m_curl); + curl_mimepart *part = curl_mime_addpart(mime); + + wolv::io::File file(path, wolv::io::File::Mode::Read); + + curl_mime_data_cb(part, file.getSize(), + [](char *buffer, size_t size, size_t nitems, void *arg) -> size_t { + auto file = static_cast(arg); + + return fread(buffer, size, nitems, file); + }, + [](void *arg, curl_off_t offset, int origin) -> int { + auto file = static_cast(arg); + + if (fseek(file, offset, origin) != 0) + return CURL_SEEKFUNC_CANTSEEK; + else + return CURL_SEEKFUNC_OK; + }, + [](void *arg) { + auto file = static_cast(arg); + + fclose(file); + }, + file.getHandle()); + curl_mime_filename(part, fileName.c_str()); + curl_mime_name(part, mimeName.c_str()); + + curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); + + T responseData; + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToContainer); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + + return this->executeImpl(responseData); + }); + } + template + std::future> uploadFile(std::vector data, const std::string &mimeName = "filename") { + return std::async(std::launch::async, [this, data = std::move(data)]{ + curl_mime *mime = curl_mime_init(this->m_curl); + curl_mimepart *part = curl_mime_addpart(mime); + + curl_mime_data(part, reinterpret_cast(data.data()), data.size()); + curl_mime_filename(part, "data.bin"); + curl_mime_name(part, mimeName.c_str()); + + curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); + + T responseData; + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToContainer); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + + return this->executeImpl(responseData); + }); + } + + template + std::future> execute() { + return std::async(std::launch::async, [this] { + T data; + + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToContainer); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &data); + + return this->executeImpl(data); + }); + } + + protected: + template + Result executeImpl(T &data) { + curl_easy_setopt(this->m_curl, CURLOPT_URL, this->m_url.c_str()); + curl_easy_setopt(this->m_curl, CURLOPT_CUSTOMREQUEST, this->m_method.c_str()); + + curl_easy_setopt(this->m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + curl_easy_setopt(this->m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(this->m_curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(this->m_curl, CURLOPT_USERAGENT, "ImHex/1.0"); + curl_easy_setopt(this->m_curl, CURLOPT_DEFAULT_PROTOCOL, "https"); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(this->m_curl, CURLOPT_TIMEOUT_MS, 0L); + curl_easy_setopt(this->m_curl, CURLOPT_CONNECTTIMEOUT_MS, 10000); + curl_easy_setopt(this->m_curl, CURLOPT_NOSIGNAL, 1L); + + #if defined(IMHEX_USE_BUNDLED_CA) + curl_easy_setopt(this->m_curl, CURLOPT_CAINFO, nullptr); + curl_easy_setopt(this->m_curl, CURLOPT_CAPATH, nullptr); + curl_easy_setopt(this->m_curl, CURLOPT_SSLCERTTYPE, "PEM"); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_CTX_FUNCTION, sslCtxFunction); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_CTX_DATA, &this->m_caCert); + #endif + + if (!this->m_body.empty()) { + curl_easy_setopt(this->m_curl, CURLOPT_POSTFIELDS, this->m_body.c_str()); + } + + curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Cache-Control: no-cache"); + + for (auto &[key, value] : this->m_headers) { + std::string header = key + ": " + value; + headers = curl_slist_append(headers, header.c_str()); + } + curl_easy_setopt(this->m_curl, CURLOPT_HTTPHEADER, headers); + + auto result = curl_easy_perform(this->m_curl); + printf("curl result: %d\n", result); + + u32 statusCode = 0; + curl_easy_getinfo(this->m_curl, CURLINFO_RESPONSE_CODE, &statusCode); + + return Result(statusCode, std::move(data)); + } + + [[maybe_unused]] + static CURLcode sslCtxFunction(CURL *ctx, void *sslctx, void *userData) { + hex::unused(ctx, userData); + + auto *cfg = static_cast(sslctx); + + auto crt = static_cast(userData); + mbedtls_x509_crt_init(crt); + + mbedtls_x509_crt_parse(crt, reinterpret_cast(HttpRequest::s_caCertData.data()), HttpRequest::s_caCertData.size()); + + mbedtls_ssl_conf_ca_chain(cfg, crt, nullptr); + + return CURLE_OK; + } + + template + static size_t writeToContainer(void *contents, size_t size, size_t nmemb, void *userdata) { + auto &response = *reinterpret_cast(userdata); + auto startSize = response.size(); + + response.resize(startSize + size * nmemb); + std::memcpy(response.data() + startSize, contents, size * nmemb); + + return size * nmemb; + } + + private: + CURL *m_curl; + + std::string m_method; + std::string m_url; + std::string m_body; + std::map m_headers; + + mbedtls_x509_crt m_caCert; + static inline std::string s_caCertData; + }; + +} \ No newline at end of file