use serde::{Deserialize, Serialize};
#[cfg(windows)]
const SIDECAR_KEY: &str = r"SOFTWARE\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\Device\Setup\Apps\Tracking\Sidecar";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EspAppFlipBackup {
pub app_id: String,
pub installation_state: Option<u32>,
pub error_hresult: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EspAppFlipResult {
pub app_id: String,
pub installation_state: u32,
pub backup: EspAppFlipBackup,
}
pub fn validate_app_id(app_id: &str) -> Result<(), String> {
let safe = !app_id.is_empty()
&& app_id.len() <= 256
&& app_id
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-');
if safe {
Ok(())
} else {
Err(format!(
"Refusing to write an unexpected app identifier: {app_id:?}"
))
}
}
#[cfg(windows)]
fn open_app_key(app_id: &str) -> Result<winreg::RegKey, String> {
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE};
use winreg::RegKey;
validate_app_id(app_id)?;
let path = format!(r"{SIDECAR_KEY}\{app_id}");
RegKey::predef(HKEY_LOCAL_MACHINE)
.open_subkey_with_flags(&path, KEY_READ | KEY_WRITE)
.map_err(|error| {
format!("Could not open the app's ESP tracking key (run elevated): {error}")
})
}
#[cfg(windows)]
pub fn flip_app_installed(app_id: &str) -> Result<EspAppFlipResult, String> {
let key = open_app_key(app_id)?;
let backup = EspAppFlipBackup {
app_id: app_id.to_string(),
installation_state: key.get_value::<u32, _>("InstallationState").ok(),
error_hresult: key.get_value::<u32, _>("ErrorHresult").ok(),
};
key.set_value("InstallationState", &3u32)
.map_err(|error| format!("Failed to set InstallationState: {error}"))?;
let _ = key.delete_value("ErrorHresult");
Ok(EspAppFlipResult {
app_id: app_id.to_string(),
installation_state: 3,
backup,
})
}
#[cfg(windows)]
pub fn restore_app_state(backup: &EspAppFlipBackup) -> Result<(), String> {
let key = open_app_key(&backup.app_id)?;
match backup.installation_state {
Some(value) => key
.set_value("InstallationState", &value)
.map_err(|error| format!("Failed to restore InstallationState: {error}"))?,
None => {
let _ = key.delete_value("InstallationState");
}
}
match backup.error_hresult {
Some(value) => key
.set_value("ErrorHresult", &value)
.map_err(|error| format!("Failed to restore ErrorHresult: {error}"))?,
None => {
let _ = key.delete_value("ErrorHresult");
}
}
Ok(())
}
#[cfg(not(windows))]
pub fn flip_app_installed(app_id: &str) -> Result<EspAppFlipResult, String> {
validate_app_id(app_id)?;
Err("Forcing an app's ESP state is only available on Windows.".to_string())
}
#[cfg(not(windows))]
pub fn restore_app_state(backup: &EspAppFlipBackup) -> Result<(), String> {
validate_app_id(&backup.app_id)?;
Err("Restoring an app's ESP state is only available on Windows.".to_string())
}
#[cfg(test)]
mod tests {
use super::validate_app_id;
#[test]
fn accepts_a_decorated_win32_app_id() {
assert!(validate_app_id("Win32App_431bae97-f077-4f2d-9102-78ed781451e9_1").is_ok());
}
#[test]
fn rejects_separators_traversal_and_stray_characters() {
assert!(validate_app_id("").is_err());
assert!(validate_app_id(r"Win32App\..\..\Software").is_err());
assert!(validate_app_id("foo/bar").is_err());
assert!(validate_app_id("..").is_err());
assert!(validate_app_id("dot.segment").is_err());
assert!(validate_app_id("has space").is_err());
assert!(validate_app_id("{58976ff3-87ca-4691-bdfc-671f37f07cb6}").is_err());
}
}