use crate::Provenance;
use std::io::Cursor;
use winreg_core::hive::Hive;
use winreg_core::key::Key;
const MP2_PATH: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints2";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserMount {
pub volume_guid: String,
pub last_mounted: Option<i64>,
pub source: Provenance,
}
#[must_use]
pub fn parse_mountpoints2(hive: &Hive<Cursor<Vec<u8>>>, file: &str) -> Vec<UserMount> {
let Ok(Some(mp2)) = hive.open_key(MP2_PATH) else {
return Vec::new();
};
let Ok(subkeys) = mp2.subkeys() else {
return Vec::new(); };
let mut out = Vec::new();
for sub in subkeys {
let name = sub.name();
if !(name.starts_with('{') && name.ends_with('}')) {
continue;
}
out.push(UserMount {
volume_guid: name.to_ascii_lowercase(),
last_mounted: last_written_epoch(&sub),
source: Provenance {
file: file.to_string(),
line: 0,
key_path: Some(format!("{MP2_PATH}\\{name}")),
},
});
}
out
}
fn last_written_epoch(key: &Key<'_>) -> Option<i64> {
Some(key.last_written()?.as_second())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn synthetic_mountpoints2_decodes_guid_mounts_only() {
const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_mountpoints2.hive");
let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
let mounts = parse_mountpoints2(&hive, "NTUSER.DAT");
assert_eq!(mounts.len(), 1);
assert_eq!(
mounts[0].volume_guid,
"{a2f2048e-d228-11e4-b630-000c29ff2429}"
);
assert_eq!(mounts[0].last_mounted, Some(1_427_230_953));
assert!(mounts[0].source.key_path.is_some());
}
#[test]
fn a_hive_without_mountpoints2_yields_nothing() {
const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_usb_system.hive");
let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
assert!(parse_mountpoints2(&hive, "NTUSER.DAT").is_empty());
}
}