peripheral_core/
mountpoints2.rs1use crate::Provenance;
10use std::io::Cursor;
11use winreg_core::hive::Hive;
12use winreg_core::key::Key;
13
14const MP2_PATH: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints2";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct UserMount {
20 pub volume_guid: String,
22 pub last_mounted: Option<i64>,
25 pub source: Provenance,
27}
28
29#[must_use]
33pub fn parse_mountpoints2(hive: &Hive<Cursor<Vec<u8>>>, file: &str) -> Vec<UserMount> {
34 let Ok(Some(mp2)) = hive.open_key(MP2_PATH) else {
35 return Vec::new();
36 };
37 let Ok(subkeys) = mp2.subkeys() else {
38 return Vec::new(); };
40 let mut out = Vec::new();
41 for sub in subkeys {
42 let name = sub.name();
43 if !(name.starts_with('{') && name.ends_with('}')) {
44 continue;
45 }
46 out.push(UserMount {
47 volume_guid: name.to_ascii_lowercase(),
48 last_mounted: last_written_epoch(&sub),
49 source: Provenance {
50 file: file.to_string(),
51 line: 0,
52 key_path: Some(format!("{MP2_PATH}\\{name}")),
53 },
54 });
55 }
56 out
57}
58
59fn last_written_epoch(key: &Key<'_>) -> Option<i64> {
61 Some(key.last_written()?.as_second())
62}
63
64#[cfg(test)]
65#[allow(clippy::unwrap_used, clippy::expect_used)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn synthetic_mountpoints2_decodes_guid_mounts_only() {
71 const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_mountpoints2.hive");
74 let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
75 let mounts = parse_mountpoints2(&hive, "NTUSER.DAT");
76 assert_eq!(mounts.len(), 1);
77 assert_eq!(
78 mounts[0].volume_guid,
79 "{a2f2048e-d228-11e4-b630-000c29ff2429}"
80 );
81 assert_eq!(mounts[0].last_mounted, Some(1_427_230_953));
82 assert!(mounts[0].source.key_path.is_some());
83 }
84
85 #[test]
86 fn a_hive_without_mountpoints2_yields_nothing() {
87 const HIVE: &[u8] = include_bytes!("../../tests/data/synthetic_usb_system.hive");
89 let hive = Hive::from_bytes(HIVE.to_vec()).expect("valid REGF");
90 assert!(parse_mountpoints2(&hive, "NTUSER.DAT").is_empty());
91 }
92}