fix(update): fix(updater): fetch and cache SHA256 before manual update download
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
ee78cc18fd
commit
c6f4c35155
3 changed files with 146 additions and 17 deletions
|
|
@ -9,6 +9,12 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
|
||||
const _eventKeyUpdateMe = 'update-me';
|
||||
const _eventKeyUpdateMeReady = 'update-me-ready';
|
||||
const _githubRateLimitErrorMarker =
|
||||
'GitHub API rate limit may have been reached';
|
||||
// Since this is a rare case,
|
||||
// we will not add a translation for this user message and will simply show it in English.
|
||||
const _githubRateLimitUserMessage =
|
||||
'The download frequency limit may have been reached. Please try again later.';
|
||||
|
||||
void handleUpdate(String releasePageUrl) {
|
||||
String downloadUrl = releasePageUrl.replaceAll('tag', 'download');
|
||||
|
|
@ -75,9 +81,12 @@ void handleUpdate(String releasePageUrl) {
|
|||
|
||||
void _showUpdateError(String releasePageUrl, String error,
|
||||
{String messageKey = 'download-new-version-failed-tip',
|
||||
bool showRetry = true}) {
|
||||
bool showRetry = true,
|
||||
bool showErrorDetail = true,
|
||||
String? userMessage}) {
|
||||
debugPrint('Update error: $error');
|
||||
final dialogManager = gFFI.dialogManager;
|
||||
final visibleError = userMessage ?? (showErrorDetail ? error : null);
|
||||
|
||||
jumplink() {
|
||||
launchUrl(Uri.parse(releasePageUrl));
|
||||
|
|
@ -99,8 +108,10 @@ void _showUpdateError(String releasePageUrl, String error,
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
msgboxContent('custom-nocancel-nook-hasclose', 'Error', messageKey),
|
||||
const SizedBox(height: 8),
|
||||
Text(error),
|
||||
if (visibleError != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(visibleError),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -198,7 +209,12 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
|
||||
void _onError(String error) {
|
||||
cancelQueryTimer();
|
||||
_showUpdateError(widget.releasePageUrl, error);
|
||||
if (error.contains(_githubRateLimitErrorMarker)) {
|
||||
_showUpdateError(widget.releasePageUrl, error,
|
||||
userMessage: _githubRateLimitUserMessage);
|
||||
} else {
|
||||
_showUpdateError(widget.releasePageUrl, error, showErrorDetail: false);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDownloadData() {
|
||||
|
|
|
|||
|
|
@ -2932,14 +2932,27 @@ pub fn main_set_common(_key: String, _value: String) {
|
|||
let download_url = _value.clone();
|
||||
let event_key = "download-new-version".to_owned();
|
||||
let data = if let Some(download_file) = get_download_file_from_url(&download_url) {
|
||||
std::fs::remove_file(&download_file).ok();
|
||||
match crate::hbbs_http::downloader::download_file(
|
||||
download_url,
|
||||
Some(PathBuf::from(download_file)),
|
||||
Some(Duration::from_secs(3)),
|
||||
) {
|
||||
Ok(id) => HashMap::from([("name", event_key), ("id", id)]),
|
||||
Err(e) => HashMap::from([("name", event_key), ("error", e.to_string())]),
|
||||
match crate::updater::download_file_expected_sha256(&download_url) {
|
||||
Ok(_) => {
|
||||
std::fs::remove_file(&download_file).ok();
|
||||
match crate::hbbs_http::downloader::download_file(
|
||||
download_url,
|
||||
Some(PathBuf::from(download_file)),
|
||||
Some(Duration::from_secs(3)),
|
||||
) {
|
||||
Ok(id) => HashMap::from([("name", event_key), ("id", id)]),
|
||||
Err(e) => {
|
||||
HashMap::from([("name", event_key), ("error", e.to_string())])
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => HashMap::from([
|
||||
("name", event_key),
|
||||
(
|
||||
"error",
|
||||
format!("Failed to get new version file SHA256, {}", e),
|
||||
),
|
||||
]),
|
||||
}
|
||||
} else {
|
||||
HashMap::from([
|
||||
|
|
@ -2968,6 +2981,7 @@ pub fn main_set_common(_key: String, _value: String) {
|
|||
let error = format!("Failed to get new version file SHA256, {}", e);
|
||||
log::error!("{}", error);
|
||||
push_update_me_error(error);
|
||||
crate::updater::clear_download_file_expected_sha256(&_value);
|
||||
fs::remove_file(f).ok();
|
||||
return;
|
||||
}
|
||||
|
|
@ -2990,6 +3004,7 @@ pub fn main_set_common(_key: String, _value: String) {
|
|||
let error = format!("Failed to update to new version, {}", e);
|
||||
log::error!("{}", error);
|
||||
push_update_me_error(error);
|
||||
crate::updater::clear_download_file_expected_sha256(&_value);
|
||||
fs::remove_file(f).ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
src/updater.rs
108
src/updater.rs
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{common::do_check_software_update, hbbs_http::create_http_client_with_url};
|
||||
use hbb_common::{bail, config, log, ResultType};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
|
|
@ -18,6 +19,7 @@ enum UpdateMsg {
|
|||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TX_MSG : Mutex<Sender<UpdateMsg>> = Mutex::new(start_auto_update_check());
|
||||
static ref DOWNLOAD_FILE_SHA256_CACHE: Mutex<HashMap<String, String>> = Default::default();
|
||||
}
|
||||
|
||||
static CONTROLLING_SESSION_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
|
@ -430,16 +432,75 @@ fn fetch_github_release_metadata(api_url: &str) -> ResultType<String> {
|
|||
.header(reqwest::header::USER_AGENT, "rustdesk-updater")
|
||||
.send()?;
|
||||
if !response.status().is_success() {
|
||||
bail!(
|
||||
"Failed to get GitHub release metadata: {}",
|
||||
response.status()
|
||||
);
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::FORBIDDEN
|
||||
|| status == reqwest::StatusCode::TOO_MANY_REQUESTS
|
||||
{
|
||||
bail!(
|
||||
"Failed to get GitHub release metadata: {}. GitHub API rate limit may have been reached. Please retry later or download from the release page.",
|
||||
status
|
||||
);
|
||||
}
|
||||
bail!("Failed to get GitHub release metadata: {}", status);
|
||||
}
|
||||
Ok(response.text()?)
|
||||
}
|
||||
|
||||
fn normalize_sha256_hex(sha256: &str) -> ResultType<String> {
|
||||
let sha256 = sha256.trim().to_ascii_lowercase();
|
||||
if sha256.len() != 64 || !sha256.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
bail!("Update file SHA256 is malformed");
|
||||
}
|
||||
Ok(sha256)
|
||||
}
|
||||
|
||||
fn cache_download_file_expected_sha256(
|
||||
download_url: &str,
|
||||
expected_sha256: &str,
|
||||
) -> ResultType<String> {
|
||||
let expected_sha256 = normalize_sha256_hex(expected_sha256)?;
|
||||
DOWNLOAD_FILE_SHA256_CACHE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(download_url.to_owned(), expected_sha256.clone());
|
||||
Ok(expected_sha256)
|
||||
}
|
||||
|
||||
fn cached_download_file_expected_sha256(download_url: &str) -> Option<String> {
|
||||
DOWNLOAD_FILE_SHA256_CACHE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(download_url)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn clear_download_file_expected_sha256(download_url: &str) {
|
||||
DOWNLOAD_FILE_SHA256_CACHE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(download_url);
|
||||
}
|
||||
|
||||
pub fn refresh_download_file_expected_sha256(download_url: &str) -> ResultType<String> {
|
||||
let expected_sha256 = fetch_github_asset_sha256(download_url, download_url)?;
|
||||
cache_download_file_expected_sha256(download_url, &expected_sha256)
|
||||
}
|
||||
|
||||
pub fn download_file_expected_sha256(download_url: &str) -> ResultType<String> {
|
||||
fetch_github_asset_sha256(download_url, download_url)
|
||||
match refresh_download_file_expected_sha256(download_url) {
|
||||
Ok(expected_sha256) => Ok(expected_sha256),
|
||||
Err(e) => {
|
||||
if let Some(expected_sha256) = cached_download_file_expected_sha256(download_url) {
|
||||
log::warn!(
|
||||
"Failed to refresh update file SHA256 for {}, using cached value: {}",
|
||||
download_url,
|
||||
e
|
||||
);
|
||||
return Ok(expected_sha256);
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn github_release_api_url(update_url: &str) -> ResultType<String> {
|
||||
|
|
@ -634,6 +695,43 @@ mod tests {
|
|||
assert!(github_release_asset_sha256(malformed, "rustdesk.exe").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn download_file_sha256_cache_roundtrips_and_clears() {
|
||||
let download_url = format!(
|
||||
"https://github.com/rustdesk/rustdesk/releases/download/test/rustdesk-cache-test-{}-{}.exe",
|
||||
std::process::id(),
|
||||
hbb_common::rand::random::<u64>()
|
||||
);
|
||||
let expected_sha256 = "ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789";
|
||||
clear_download_file_expected_sha256(&download_url);
|
||||
|
||||
let cached = cache_download_file_expected_sha256(&download_url, expected_sha256).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cached,
|
||||
"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
||||
);
|
||||
assert_eq!(
|
||||
cached_download_file_expected_sha256(&download_url),
|
||||
Some(cached)
|
||||
);
|
||||
clear_download_file_expected_sha256(&download_url);
|
||||
assert_eq!(cached_download_file_expected_sha256(&download_url), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn download_file_sha256_cache_rejects_malformed_digest() {
|
||||
let download_url = format!(
|
||||
"https://github.com/rustdesk/rustdesk/releases/download/test/rustdesk-cache-test-{}-{}.exe",
|
||||
std::process::id(),
|
||||
hbb_common::rand::random::<u64>()
|
||||
);
|
||||
clear_download_file_expected_sha256(&download_url);
|
||||
|
||||
assert!(cache_download_file_expected_sha256(&download_url, "sha256:not-hex").is_err());
|
||||
assert_eq!(cached_download_file_expected_sha256(&download_url), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_file_sha256_rejects_mismatched_file() {
|
||||
let file_path = std::env::temp_dir().join(format!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue