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}