Skip to main content

msi_extract/
lib.rs

1use std::{
2    collections::HashMap,
3    fs::File,
4    io::{Read, Seek},
5    path::{Path, PathBuf},
6};
7
8use cab::Cabinet;
9use msi::{Package, Select, StreamReader};
10
11#[derive(thiserror::Error, Debug)]
12pub enum Error {
13    #[error(transparent)]
14    Contract(#[from] std::io::Error),
15    #[error("Interal error")]
16    Other,
17}
18struct MsiTraverser {
19    f_c: HashMap<String, String>,
20    c_d: HashMap<String, String>,
21    d_p: HashMap<String, String>,
22    f_name: HashMap<String, String>,
23    d_name: HashMap<String, String>,
24}
25
26impl MsiTraverser {
27    fn new<F>(package: &mut Package<F>) -> Result<Self, Error>
28    where
29        F: Read + Seek,
30    {
31        let mut f_c = HashMap::new();
32        let mut c_d = HashMap::new();
33        let mut d_p = HashMap::new();
34        let mut f_name = HashMap::new();
35        let mut d_name = HashMap::new();
36
37        for r in package.select_rows(Select::table("File"))? {
38            if let Some(file) = r["File"].as_str().map(|x| x.to_owned())
39                && let Some(filename) = r["FileName"].as_str().map(|x| x.to_owned())
40            {
41                f_name.insert(file, filename);
42            };
43            if let Some(file) = r["File"].as_str().map(|x| x.to_owned())
44                && let Some(component) = r["Component_"].as_str().map(|x| x.to_owned())
45            {
46                f_c.insert(file, component);
47            };
48        }
49
50        for r in package.select_rows(Select::table("Component"))? {
51            if let Some(comp) = r["Component"].as_str().map(|x| x.to_owned())
52                && let Some(dir) = r["Directory_"].as_str().map(|x| x.to_owned())
53            {
54                c_d.insert(comp, dir);
55            };
56        }
57        for r in package.select_rows(Select::table("Directory"))? {
58            if let Some(dir) = r["Directory"].as_str().map(|x| x.to_owned())
59                && let Some(dirpar) = r["Directory_Parent"].as_str().map(|x| x.to_owned())
60            {
61                d_p.insert(dir, dirpar);
62            };
63            if let Some(dir) = r["Directory"].as_str().map(|x| x.to_owned())
64                && let Some(name) = r["DefaultDir"].as_str().map(|x| match x.split_once('|') {
65                    Some((_a, b)) => match b.split_once(':') {
66                        Some((c, _d)) => c.to_owned(),
67                        None => b.to_owned(),
68                    },
69                    None => x.to_owned(),
70                })
71            {
72                d_name.insert(dir, name);
73            };
74        }
75
76        Ok(Self {
77            f_c,
78            c_d,
79            d_p,
80            f_name,
81            d_name,
82        })
83    }
84
85    fn dir_path(&mut self, dir: &str) -> Result<Vec<String>, Error> {
86        let mut output = vec![self.d_name.get(dir).ok_or(Error::Other)?.clone()];
87        let mut cur = dir.to_owned();
88        while let Some(parent) = self.d_p.get(&cur) {
89            cur = parent.clone();
90            output.push(
91                self.d_name
92                    .get(&cur)
93                    .map(|x| x.to_owned())
94                    .unwrap_or_else(|| {
95                        println!("hey got to {}", &cur);
96                        "".to_string()
97                    })
98                    .clone(),
99            );
100        }
101        let _ = output.pop();
102        output.reverse();
103        Ok(output)
104    }
105    fn resolve(&mut self, file: &str) -> Result<PathBuf, Error> {
106        let file_name = self
107            .f_name
108            .get(file)
109            .map(|s| match s.split_once('|') {
110                Some((_a, b)) => b,
111                None => s,
112            })
113            .ok_or(Error::Other)?
114            .to_owned();
115
116        let dir = self
117            .f_c
118            .get(file)
119            .and_then(|c| self.c_d.get(c))
120            .ok_or(Error::Other)?
121            .clone();
122        let mut output = PathBuf::new();
123        output.extend(self.dir_path(&dir)?);
124        output.push(file_name);
125        Ok(output)
126    }
127}
128
129pub struct MsiExtractor<F> {
130    _package: msi::Package<F>,
131    cab: Cabinet<StreamReader<F>>,
132    traverser: MsiTraverser,
133}
134
135impl<F> MsiExtractor<F>
136where
137    F: Read + Seek,
138{
139    pub fn from_msi(mut package: Package<F>) -> Result<Self, Error> {
140        let traverser = MsiTraverser::new(&mut package)?;
141        let cab_name = package
142            .streams()
143            .find(|s| s.ends_with(".cab"))
144            .ok_or(Error::Other)?;
145        let stream = package.read_stream(&cab_name)?;
146        let cab = Cabinet::new(stream)?;
147
148        Ok(MsiExtractor {
149            _package: package,
150            cab,
151            traverser,
152        })
153    }
154    pub fn from_reader(reader: F) -> Result<MsiExtractor<F>, Error> {
155        let mut package = msi::Package::open(reader)?;
156        let traverser = MsiTraverser::new(&mut package)?;
157        let cab_name = package
158            .streams()
159            .find(|s| s.ends_with(".cab"))
160            .ok_or(Error::Other)?;
161        let stream = package.read_stream(&cab_name)?;
162        let cab = Cabinet::new(stream)?;
163
164        Ok(MsiExtractor {
165            _package: package,
166            cab,
167            traverser,
168        })
169    }
170    pub fn to<P: AsRef<Path>>(&mut self, target: P) {
171        let files: Vec<String> = self
172            .cab
173            .folder_entries()
174            .flat_map(|f| f.file_entries())
175            .map(|f| f.name().to_owned())
176            .collect();
177        files.iter().for_each(|file| {
178            if let Ok(path) = self.traverser.resolve(file)
179                && let target = target.as_ref().join(path)
180                && let Some(dir) = target.parent()
181                && let Ok(()) = std::fs::create_dir_all(dir)
182                && let Ok(mut target_file) = File::create(&target)
183                && let Ok(mut data) = self.cab.read_file(file)
184            {
185                let _ = std::io::copy(&mut data, &mut target_file);
186            }
187        });
188    }
189}
190
191impl MsiExtractor<File> {
192    pub fn from_path<P>(path: P) -> Result<MsiExtractor<File>, Error>
193    where
194        P: AsRef<Path>,
195    {
196        let reader = File::open(path)?;
197        let mut package = msi::Package::open(reader)?;
198        let traverser = MsiTraverser::new(&mut package)?;
199        let cab_name = package
200            .streams()
201            .find(|s| s.ends_with(".cab"))
202            .ok_or(Error::Other)?;
203        let stream = package.read_stream(&cab_name)?;
204        let cab = Cabinet::new(stream)?;
205
206        Ok(MsiExtractor {
207            _package: package,
208            cab,
209            traverser,
210        })
211    }
212}