Files
crossdesk/apps/desktop/src/version_checker/version_checker.cpp
T

163 lines
4.3 KiB
C++

/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#include "version_checker.h"
#include <httplib.h>
#include <nlohmann/json.hpp>
#include "rd_log.h"
#include "linux_update_download.h"
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>
namespace crossdesk {
namespace {
void LogHttpError(const httplib::Result& result) {
LOG_WARN("Failed to fetch version.json: error={}, message={}",
static_cast<int>(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<std::string>& 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() ? "<none>" : ca_file,
ca_dir.empty() ? "<none>" : ca_dir);
}
#endif
} // namespace
std::optional<VersionInfo> 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