/* * @Author: DI JUNKUN * @Date: 2025-11-11 * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. */ #include "version_checker.h" #include #include #include "rd_log.h" #include "linux_update_download.h" #include #include #include #include namespace crossdesk { namespace { void LogHttpError(const httplib::Result& result) { LOG_WARN("Failed to fetch version.json: error={}, message={}", static_cast(result.error()), httplib::to_string(result.error())); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT LOG_WARN("version.json SSL error={}, OpenSSL error={}", result.ssl_error(), result.ssl_openssl_error()); #endif } #if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && defined(__linux__) bool PathExists(const std::string& path) { if (path.empty()) { return false; } std::error_code ec; return std::filesystem::exists(path, ec); } std::string GetEnvPathIfExists(const char* key) { const char* value = std::getenv(key); if (!value) { return ""; } const std::string path = value; return PathExists(path) ? path : ""; } std::string FindFirstExistingPath( const std::vector& candidates) { for (const auto& candidate : candidates) { if (PathExists(candidate)) { return candidate; } } return ""; } void ConfigureLinuxCaCerts(httplib::Client* cli) { const std::string ca_file = [&]() { const std::string env_path = GetEnvPathIfExists("SSL_CERT_FILE"); if (!env_path.empty()) { return env_path; } return FindFirstExistingPath({ "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/etc/ssl/cert.pem", "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", }); }(); const std::string ca_dir = [&]() { const std::string env_path = GetEnvPathIfExists("SSL_CERT_DIR"); if (!env_path.empty()) { return env_path; } return FindFirstExistingPath({ "/etc/ssl/certs", "/etc/pki/tls/certs", "/etc/openssl/certs", }); }(); if (ca_file.empty() && ca_dir.empty()) { LOG_WARN("No Linux CA bundle found for version.json request; relying on OpenSSL defaults"); return; } cli->set_ca_cert_path(ca_file, ca_dir); LOG_INFO("Configured version.json TLS CA bundle: file={}, dir={}", ca_file.empty() ? "" : ca_file, ca_dir.empty() ? "" : ca_dir); } #endif } // namespace std::optional CheckUpdate() { httplib::Client cli("https://version.crossdesk.cn"); cli.set_connection_timeout(5); cli.set_read_timeout(5); cli.set_write_timeout(5); cli.set_max_timeout(15000); cli.set_follow_location(true); #if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && defined(__linux__) ConfigureLinuxCaCerts(&cli); #endif auto res = cli.Get("/version.json"); if (res) { if (res->status == 200) { try { const auto manifest = nlohmann::json::parse(res->body); auto info = ParseVersionInfo(manifest); if (info) { #if defined(__OHOS__) // A Linux package cannot be installed by Harmony's package manager. // Until the manifest has a Harmony artifact, use the release website. info->download_url = "https://crossdesk.cn"; #elif defined(__linux__) std::string architecture; #if defined(__x86_64__) architecture = "amd64"; #elif defined(__aarch64__) architecture = "arm64"; #endif std::error_code error; const auto executable = std::filesystem::read_symlink("/proc/self/exe", error); info->download_url = LinuxUpdateDownloadUrl( manifest, *info, architecture, error ? std::filesystem::path{} : executable.parent_path()); #endif LOG_INFO("Fetched version.json: latest_version={}, releaseDate={}, patch={}", info->version, info->release_date, info->patch); } else { LOG_WARN("version.json does not contain a valid version"); } return info; } catch (const std::exception& e) { LOG_WARN("Failed to parse version.json: {}", e.what()); return std::nullopt; } } else { LOG_WARN("Failed to fetch version.json: HTTP status={}", res->status); return std::nullopt; } } else { LogHttpError(res); return std::nullopt; } } } // namespace crossdesk