#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace hex { class HttpRequest { public: class ResultBase { public: ResultBase() = default; explicit ResultBase(u32 statusCode) : m_statusCode(statusCode), m_valid(true) { } [[nodiscard]] u32 getStatusCode() const { return this->m_statusCode; } [[nodiscard]] bool isSuccess() const { return this->getStatusCode() == 200; } [[nodiscard]] bool isValid() const { return this->m_valid; } private: u32 m_statusCode = 0; bool m_valid = false; }; template class Result : public ResultBase { public: Result() = default; 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); } HttpRequest(const HttpRequest&) = delete; HttpRequest& operator=(const HttpRequest&) = delete; HttpRequest(HttpRequest &&other) noexcept { this->m_curl = other.m_curl; other.m_curl = nullptr; this->m_method = std::move(other.m_method); this->m_url = std::move(other.m_url); this->m_headers = std::move(other.m_headers); this->m_body = std::move(other.m_body); this->m_caCert = std::move(other.m_caCert); } HttpRequest& operator=(HttpRequest &&other) noexcept { this->m_curl = other.m_curl; other.m_curl = nullptr; this->m_method = std::move(other.m_method); this->m_url = std::move(other.m_url); this->m_headers = std::move(other.m_headers); this->m_body = std::move(other.m_body); this->m_caCert = std::move(other.m_caCert); return *this; } static void setCACert(std::string data) { HttpRequest::s_caCertData = std::move(data); } static void setProxy(std::string proxy) { HttpRequest::s_proxyUrl = std::move(proxy); } void setMethod(std::string method) { this->m_method = std::move(method); } void setUrl(std::string url) { this->m_url = std::move(url); } 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); } void setTimeout(u32 timeout) { this->m_timeout = timeout; } float getProgress() const { return this->m_progress; } void cancel() { this->m_canceled = true; } template std::future> downloadFile(const std::fs::path &path) { return std::async(std::launch::async, [this, path] { std::vector response; wolv::io::File file(path, wolv::io::File::Mode::Create); curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToFile); curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &file); return this->executeImpl(response); }); } std::future>> downloadFile() { return std::async(std::launch::async, [this] { std::vector response; curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); 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); std::vector responseData; curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); 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), mimeName]{ 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); std::vector responseData; curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); return this->executeImpl(responseData); }); } template std::future> execute() { return std::async(std::launch::async, [this] { std::vector responseData; curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); return this->executeImpl(responseData); }); } std::string urlEncode(const std::string &input) { auto escapedString = curl_easy_escape(this->m_curl, input.c_str(), std::strlen(input.c_str())); if (escapedString != nullptr) { std::string output = escapedString; curl_free(escapedString); return output; } return {}; } std::string urlDecode(const std::string &input) { auto unescapedString = curl_easy_unescape(this->m_curl, input.c_str(), std::strlen(input.c_str()), nullptr); if (unescapedString != nullptr) { std::string output = unescapedString; curl_free(unescapedString); return output; } return {}; } protected: void setDefaultConfig(); template Result executeImpl(std::vector &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()); setDefaultConfig(); 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"); ON_SCOPE_EXIT { curl_slist_free_all(headers); }; for (auto &[key, value] : this->m_headers) { std::string header = hex::format("{}: {}", key, value); headers = curl_slist_append(headers, header.c_str()); } curl_easy_setopt(this->m_curl, CURLOPT_HTTPHEADER, headers); { std::scoped_lock lock(this->m_transmissionMutex); auto result = curl_easy_perform(this->m_curl); if (result != CURLE_OK){ char *url = nullptr; curl_easy_getinfo(this->m_curl, CURLINFO_EFFECTIVE_URL, &url); log::error("Http request '{0} {1}' failed with error {2}: '{3}'", this->m_method, url, u32(result), curl_easy_strerror(result)); if (!HttpRequest::s_proxyUrl.empty()){ log::info("A custom proxy '{0}' is in use. Is it working correctly?", HttpRequest::s_proxyUrl); } return { }; } } long statusCode = 0; curl_easy_getinfo(this->m_curl, CURLINFO_RESPONSE_CODE, &statusCode); return Result(statusCode, { data.begin(), data.end() }); } [[maybe_unused]] static CURLcode sslCtxFunction(CURL *ctx, void *sslctx, void *userData); static size_t writeToVector(void *contents, size_t size, size_t nmemb, void *userdata); static size_t writeToFile(void *contents, size_t size, size_t nmemb, void *userdata); static int progressCallback(void *contents, curl_off_t dlTotal, curl_off_t dlNow, curl_off_t ulTotal, curl_off_t ulNow); private: CURL *m_curl; std::mutex m_transmissionMutex; std::string m_method; std::string m_url; std::string m_body; std::map m_headers; u32 m_timeout = 1000; std::atomic m_progress = 0.0F; std::atomic m_canceled = false; [[maybe_unused]] std::unique_ptr m_caCert; static std::string s_caCertData, s_proxyUrl; }; }