From ded17cf91d360b3391fdd9ccad450bfc5d197e86 Mon Sep 17 00:00:00 2001 From: Rudxain <76864299+Rudxain@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:40:31 -0400 Subject: [PATCH] refactor(core): allow "android" `PackageId` --- crates/uad-core/src/adb.rs | 27 +++++++++++++++------------ crates/uad-core/src/sync.rs | 15 +++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/uad-core/src/adb.rs b/crates/uad-core/src/adb.rs index 1104b61..e7d223c 100644 --- a/crates/uad-core/src/adb.rs +++ b/crates/uad-core/src/adb.rs @@ -38,7 +38,7 @@ //! For comprehensive info about ADB, //! [see this](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/docs/) -use serde::{Deserialize, Serialize}; +use std::rc::Rc; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; @@ -275,14 +275,17 @@ pub const fn is_pkg_component(s: &[u8]) -> bool { /// String with the invariant of being a valid package-name. /// See [`PackageId::new`] for validation details. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)] -pub struct PackageId(Box); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PackageId(Rc); impl PackageId { - /// Creates a package-ID if it's valid according to: + /// Creates a package-ID if it's `"android"` or valid according to: /// - /// - #[must_use] - pub fn new(p_id: Box) -> Option { + pub fn new(p_id: &str) -> Option { + if p_id == "android" { + return Some(Self(p_id.into())); + } let mut components = p_id.split('.'); for _ in 0..2 { if !components @@ -293,7 +296,7 @@ impl PackageId { } } if components.all(|comp| is_pkg_component(comp.as_bytes())) { - Some(Self(p_id)) + Some(Self(p_id.into())) } else { None } @@ -337,11 +340,11 @@ pub const PM_CLEAR_PACK: &str = "pm clear"; #[must_use] pub struct PmCommand(ShellCommand); impl PmCommand { - /// `list packages -s` sub-command, [`PACK_PREFIX`] stripped. + /// `list packages -s` sub-command, [`PACK_PREFIX`] stripped from each element. /// /// `Ok` variant: - /// - isn't guaranteed to contain valid pack-IDs, - /// as "android" can be printed but it's invalid + /// - isn't 100% guaranteed to contain valid pack-IDs + /// but you can assume it does (except in `unsafe {}` blocks) /// - isn't sorted /// - duplicates never _seem_ to happen, but don't assume uniqueness pub fn list_packages_sys( @@ -366,7 +369,7 @@ impl PmCommand { .map(|p_ln| { debug_assert!(p_ln.starts_with(PACK_PREFIX)); let p = &p_ln[PACK_PREFIX.len()..]; - debug_assert!(PackageId::new(p.into()).is_some() || p == "android"); + debug_assert!(PackageId::new(p).is_some()); String::from(p) }) .collect() @@ -473,7 +476,7 @@ mod tests { "the.🎂.is.a.lie", "EXCLAMATION!!!!", ] { - assert_eq!(PackageId::new(p_id.into()), None); + assert_eq!(PackageId::new(p_id), None); } } @@ -490,7 +493,7 @@ mod tests { "com.github.w1nst0n", "this_.String_.is_.not_.real_", ] { - assert_ne!(PackageId::new(p_id.into()), None); + assert_ne!(PackageId::new(p_id), None); } } } diff --git a/crates/uad-core/src/sync.rs b/crates/uad-core/src/sync.rs index eb3f7e5..9bdd7cc 100644 --- a/crates/uad-core/src/sync.rs +++ b/crates/uad-core/src/sync.rs @@ -1,5 +1,5 @@ use crate::{ - adb::{ACommand as AdbCommand, PM_CLEAR_PACK}, + adb::{ACommand as AdbCommand, PM_CLEAR_PACK, PackageId}, uad_lists::PackageState, }; use log::{error, info}; @@ -207,19 +207,14 @@ pub fn apply_pkg_state_commands( #[must_use] pub fn request_builder(commands: &[&str], package: &str, user: Option) -> Vec { // Defense-in-depth: `package` is interpolated verbatim into a device-shell - // action, so refuse any name carrying a character that can't appear in a valid - // Android application-ID (`[A-Za-z0-9_.]`). Every current caller already + // action, so refuse any invalid name/ID. Every current caller already // reconciles the name against the live device package list before reaching // here, so this rejects nothing legitimate — it just keeps the no-injection // guarantee local to the sink instead of relying on each caller to sanitise. // Fail closed: emit no command for a malformed name rather than an injectable // device-shell string. - if package.is_empty() - || !package - .bytes() - .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_') - { - error!("request_builder: refusing package name with invalid characters: {package:?}"); + if PackageId::new(package).is_some() { + error!("request_builder: refusing invalid package name: {package:?}"); return Vec::new(); } let maybe_user_flag = user_flag(user); @@ -300,7 +295,7 @@ pub fn capture_cross_user_states( /// Detect cross-user behavior and return appropriate notification message. /// This handles unexpected cross-user behavior: /// - Case A: Uninstall → Restore (package appears on other users) -/// - Case B: Uninstall → Uninstall (package disappears from other users that previously had it) +/// - Case B: Uninstall → Uninstall (package disappears from other users that previously had it) /// - Case C: Restore → Restore (package appears on other users) #[must_use] pub fn detect_cross_user_behavior(