// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "http_download.h" #include "logger.h" #include #include #include #include namespace fs = std::filesystem; namespace dragonx { namespace util { namespace { size_t writeStringCb(void* contents, size_t size, size_t nmemb, void* userp) { static_cast(userp)->append(static_cast(contents), size * nmemb); return size * nmemb; } size_t writeFileCb(void* contents, size_t size, size_t nmemb, void* userp) { return std::fwrite(contents, size, nmemb, static_cast(userp)); } // Bridge libcurl's C progress callback to the caller's std::function: publish live byte counts // and abort the transfer (return non-zero) when the callback returns false (cancellation). int xferCb(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t, curl_off_t) { auto* fn = static_cast*>(clientp); return (*fn)(static_cast(dlnow), static_cast(dltotal)) ? 0 : 1; } } // namespace std::string httpGetString(const std::string& url, const char* logTag) { CURL* curl = curl_easy_init(); if (!curl) return {}; std::string result; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0"); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); const CURLcode res = curl_easy_perform(curl); long httpCode = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) { DEBUG_LOGF("%s GET %s failed: %s (HTTP %ld)\n", logTag, url.c_str(), curl_easy_strerror(res), httpCode); return {}; } return result; } bool httpDownloadToFile(const std::string& url, const std::string& destPath, const char* logTag, long long maxBytes, const std::function& onProgress) { FILE* fp = std::fopen(destPath.c_str(), "wb"); if (!fp) return false; CURL* curl = curl_easy_init(); if (!curl) { std::fclose(fp); return false; } curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFileCb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0"); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L); curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE, static_cast(maxBytes)); // refuse oversized bodies // Live progress + cancellation: the callback publishes byte counts and aborts on cancel(). curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferCb); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &onProgress); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); const CURLcode res = curl_easy_perform(curl); long httpCode = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); curl_easy_cleanup(curl); std::fclose(fp); if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) { DEBUG_LOGF("%s download %s failed: %s (HTTP %ld)\n", logTag, url.c_str(), curl_easy_strerror(res), httpCode); std::error_code ec; fs::remove(destPath, ec); return false; } return true; } } // namespace util } // namespace dragonx