mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-11-04 00:33:12 +00:00
* test: remove tests that do not pin user-visible behavior Apply the AGENTS.md test rules to the existing Rust and Flutter suites. A test is kept when it is the only coverage of user- or API-visible behavior, the regression test of a reported or fixed bug, a security boundary (one test per rejection class), or a check against an external reference. Tests that assert private state or the order of internal steps, pin review-round corner cases, or repeat what a kept test already covers are removed. Rust: 679 -> 298 tests. Dart: 118 -> 45 tests. Test-only helpers left without callers go with them (applyLastOnlineStateForTesting, terminalKeyboardRowWidth, debugBufferedInputCount, tokio's test-util dev feature), and cfg gates and visibility that existed only for the deleted tests are narrowed. Runtime behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep tests that pin security and user-visible behavior Restore six tests that review showed are the only coverage of their behavior: - update metadata rejects an artifact file name with path components; - ICE candidates from another session are dropped; - KCP delivers every multi-segment frame queued before a close; - set_files rejects an empty name in a multi-file transfer; - a lopsided elongated cursor keeps its centre hotspot (#16242); - toggling Show hidden during a pending same-path read issues a new read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep offline update and key-exchange downgrade rejections Restore three tests that are the only coverage of their rejection class: - offline update metadata rejects a signed artifact with an http or credential-bearing URL; - offline verification rejects a renamed local file and a version that does not match the release; - a key-exchange pick lowered in transit leaves both sides unable to decrypt, so a v0 fallback after a failed v1 decrypt cannot pass unnoticed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R * test: keep the decomposed-grapheme and reconnect-cancel tests Each catches a plausible wrong change that passes every kept test: mapping the first scalar under a locked Ctrl turns é into \x05, and a reconnect that does not fail pending reads leaves them hanging until the 30 s timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
211 lines
7.1 KiB
Rust
211 lines
7.1 KiB
Rust
use hbb_common::{
|
|
anyhow::{anyhow, Context},
|
|
bail, log, ResultType,
|
|
};
|
|
use std::{io::Read, path::Path};
|
|
|
|
const UPDATE_SIDECAR_MAX_BYTES: u64 = 1024 * 1024;
|
|
const PUBLIC_MACOS_BUNDLE_ID: &str = "com.carriez.rustdesk";
|
|
const MACOS_UPDATE_USAGE: &str = "Usage: --update [update.dmg --metadata rustdesk-update.json --signature rustdesk-update.json.sig]";
|
|
const LEGACY_UPDATE_WARNING: &str =
|
|
"legacy offline update: signed metadata not found; release hash was not verified";
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
enum MacosUpdateArguments<'a> {
|
|
CurrentApp,
|
|
LegacyDmg(&'a str),
|
|
VerifiedDmg {
|
|
dmg_path: &'a str,
|
|
metadata_path: &'a str,
|
|
signature_path: &'a str,
|
|
},
|
|
}
|
|
|
|
fn parse_macos_update_args(args: &[String]) -> ResultType<MacosUpdateArguments<'_>> {
|
|
match args {
|
|
[command] if command == "--update" => Ok(MacosUpdateArguments::CurrentApp),
|
|
[command, dmg_path] if command == "--update" && dmg_path.ends_with(".dmg") => {
|
|
Ok(MacosUpdateArguments::LegacyDmg(dmg_path))
|
|
}
|
|
[command, dmg_path, metadata_flag, metadata_path, signature_flag, signature_path]
|
|
if command == "--update"
|
|
&& dmg_path.ends_with(".dmg")
|
|
&& metadata_flag == "--metadata"
|
|
&& signature_flag == "--signature" =>
|
|
{
|
|
Ok(MacosUpdateArguments::VerifiedDmg {
|
|
dmg_path,
|
|
metadata_path,
|
|
signature_path,
|
|
})
|
|
}
|
|
_ => bail!(MACOS_UPDATE_USAGE),
|
|
}
|
|
}
|
|
|
|
fn expected_update_package_id(
|
|
is_custom_client: bool,
|
|
bundle_id: Option<String>,
|
|
) -> ResultType<String> {
|
|
let bundle_id = bundle_id
|
|
.filter(|value| !value.is_empty())
|
|
.ok_or_else(|| anyhow!("client bundle identifier is missing"))?;
|
|
if !is_custom_client && bundle_id == PUBLIC_MACOS_BUNDLE_ID {
|
|
return Ok(crate::update_metadata::PUBLIC_UPDATE_PACKAGE_ID.to_owned());
|
|
}
|
|
Ok(bundle_id)
|
|
}
|
|
|
|
fn installed_update_package_id() -> ResultType<String> {
|
|
expected_update_package_id(
|
|
crate::common::is_custom_client(),
|
|
crate::platform::macos::get_bundle_id(),
|
|
)
|
|
}
|
|
|
|
fn read_update_sidecar(path: &str) -> ResultType<Vec<u8>> {
|
|
let path_metadata = std::fs::symlink_metadata(path)
|
|
.with_context(|| format!("failed to inspect update sidecar {path}"))?;
|
|
if !path_metadata.file_type().is_file() {
|
|
bail!("update sidecar is not a regular file: {path}");
|
|
}
|
|
if path_metadata.len() > UPDATE_SIDECAR_MAX_BYTES {
|
|
bail!("update sidecar exceeds maximum size: {path}");
|
|
}
|
|
let file = std::fs::File::open(path)
|
|
.with_context(|| format!("failed to open update sidecar {path}"))?;
|
|
let mut bytes = Vec::new();
|
|
file.take(UPDATE_SIDECAR_MAX_BYTES.saturating_add(1))
|
|
.read_to_end(&mut bytes)?;
|
|
if bytes.len() as u64 > UPDATE_SIDECAR_MAX_BYTES {
|
|
bail!("update sidecar exceeds maximum size: {path}");
|
|
}
|
|
Ok(bytes)
|
|
}
|
|
|
|
fn update_current_app() -> ResultType<()> {
|
|
println!("Starting update process...");
|
|
log::info!("Starting update process...");
|
|
crate::platform::update_me()?;
|
|
println!(
|
|
"{}",
|
|
crate::client::translate("Updated successfully!".to_owned())
|
|
);
|
|
log::info!("Updated successfully!");
|
|
Ok(())
|
|
}
|
|
|
|
fn update_legacy_dmg(dmg_path: &str) -> ResultType<()> {
|
|
eprintln!("Warning: {LEGACY_UPDATE_WARNING}");
|
|
log::warn!("{LEGACY_UPDATE_WARNING}");
|
|
println!("Updating from DMG: {dmg_path}");
|
|
crate::platform::update_from_dmg(dmg_path)?;
|
|
println!("Update process from DMG started successfully.");
|
|
Ok(())
|
|
}
|
|
|
|
fn update_verified_dmg(
|
|
dmg_path: &str,
|
|
metadata_path: &str,
|
|
signature_path: &str,
|
|
) -> ResultType<()> {
|
|
let file_name = Path::new(dmg_path)
|
|
.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.ok_or_else(|| anyhow!("invalid update DMG file name"))?;
|
|
let metadata = read_update_sidecar(metadata_path)?;
|
|
let signature = read_update_sidecar(signature_path)?;
|
|
let expected_package_id = installed_update_package_id()?;
|
|
let artifact = crate::update_metadata::verify_offline_update_metadata(
|
|
&metadata,
|
|
&signature,
|
|
crate::update_metadata::OfflineUpdateMetadataRequirements {
|
|
local_file_name: file_name,
|
|
expected_package_id: &expected_package_id,
|
|
artifact: crate::updater::current_update_artifact_query(false),
|
|
},
|
|
)?;
|
|
println!("Updating from DMG verified by signed metadata: {dmg_path}");
|
|
crate::platform::macos::update_to_verified_dmg(
|
|
dmg_path,
|
|
&artifact.sha256,
|
|
Some(artifact.size),
|
|
)?;
|
|
println!("Verified update process started");
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn execute_macos_update(args: &[String]) -> ResultType<()> {
|
|
match parse_macos_update_args(args)? {
|
|
MacosUpdateArguments::CurrentApp => update_current_app(),
|
|
MacosUpdateArguments::LegacyDmg(dmg_path) => update_legacy_dmg(dmg_path),
|
|
MacosUpdateArguments::VerifiedDmg {
|
|
dmg_path,
|
|
metadata_path,
|
|
signature_path,
|
|
} => update_verified_dmg(dmg_path, metadata_path, signature_path),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn args(values: &[&str]) -> Vec<String> {
|
|
values.iter().map(|value| (*value).to_owned()).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn parses_current_legacy_and_verified_macos_updates() {
|
|
assert_eq!(
|
|
MacosUpdateArguments::CurrentApp,
|
|
parse_macos_update_args(&args(&["--update"])).unwrap()
|
|
);
|
|
assert_eq!(
|
|
MacosUpdateArguments::LegacyDmg("update.dmg"),
|
|
parse_macos_update_args(&args(&["--update", "update.dmg"])).unwrap()
|
|
);
|
|
assert_eq!(
|
|
MacosUpdateArguments::VerifiedDmg {
|
|
dmg_path: "update.dmg",
|
|
metadata_path: "rustdesk-update.json",
|
|
signature_path: "rustdesk-update.json.sig",
|
|
},
|
|
parse_macos_update_args(&args(&[
|
|
"--update",
|
|
"update.dmg",
|
|
"--metadata",
|
|
"rustdesk-update.json",
|
|
"--signature",
|
|
"rustdesk-update.json.sig",
|
|
]))
|
|
.unwrap()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolves_public_and_custom_update_package_identity() {
|
|
assert_eq!(
|
|
"rustdesk",
|
|
expected_update_package_id(false, Some("com.carriez.rustdesk".to_owned())).unwrap()
|
|
);
|
|
assert_eq!(
|
|
"com.example.rustdesk-custom",
|
|
expected_update_package_id(true, Some("com.example.rustdesk-custom".to_owned()))
|
|
.unwrap()
|
|
);
|
|
assert_eq!(
|
|
"com.example.same-name-custom",
|
|
expected_update_package_id(false, Some("com.example.same-name-custom".to_owned()))
|
|
.unwrap()
|
|
);
|
|
assert_eq!(
|
|
"com.carriez.rustdesk",
|
|
expected_update_package_id(true, Some("com.carriez.rustdesk".to_owned())).unwrap()
|
|
);
|
|
assert!(expected_update_package_id(false, None).is_err());
|
|
assert!(expected_update_package_id(true, None).is_err());
|
|
assert!(expected_update_package_id(true, Some(String::new())).is_err());
|
|
}
|
|
}
|