deb_rs/file/
deb.rs

1use std::{
2    fs,
3    io::{Error, ErrorKind},
4};
5
6use debcontrol::{parse_str, Paragraph};
7use glob::glob;
8
9use crate::{
10    file::{extract, Control, PathItem, Version},
11    shared::{paragraph_contains, PackageWithVersion},
12};
13
14/**
15* This is the struct that should be used to parse `.deb` files.
16* It should first be created with <Deb::new> and then extracted with <Deb::extract>. Once
17* the file has been extracted you can then retrieve data from it.
18*
19* **Example**
20* ```rust
21* use std::io::Error;
22* use deb_rs::file::Deb;
23*
24* fn main() -> Result<(), Error> {
25*   let mut deb = Deb::new("./example/assets/gnome_clocks.deb");
26*   deb.extract()?;
27*
28*   deb.version()?; // Returns the version of the structure of the debian package.
29*   // NOTE: extract() will fail with versions that are not 2.0 as their structure is different
30*
31*   deb.retrieve_control()?; // Will return some general information about the contents of the package
32*
33*    deb.install_tree()?; // Returns an array of files that must be moved for the file package to work
34*
35*    Ok(())
36* }
37* ```
38*/
39pub struct Deb {
40    path: &'static str,
41    pub extracted_path: Option<String>,
42}
43
44/**
45* @todo Add support for prerm and postinst files
46*/
47impl Deb {
48    pub fn new(path: &'static str) -> Self {
49        Deb {
50            path,
51            extracted_path: None,
52        }
53    }
54
55    /**
56     * Extracts the deb file into a set of individual files
57     */
58    pub fn extract(&mut self) -> Result<&mut Self, Error> {
59        self.extracted_path = Some(extract(&self.path).unwrap());
60        Ok(self)
61    }
62
63    /**
64     * Checks if the deb file has been extracted, throws and error if it has not
65     */
66    fn extract_check(&self) -> Result<(), Error> {
67        if self.extracted_path.is_none() {
68            return Err(Error::new(
69          ErrorKind::Other,
70          "This deb file has not been extracted. Please run `extract()` before calling `retrieve_control`",
71          ));
72        };
73
74        Ok(())
75    }
76
77    /**
78     * Returns the version of the deb file. Note that the standard has been V2_0 (`2.0`) since debian `0.93`
79     */
80    pub fn version(&self) -> Result<Version, Error> {
81        self.extract_check()?;
82
83        let version = fs::read_to_string(format!(
84            "{}debian-binary",
85            self.extracted_path.as_ref().unwrap()
86        ))?;
87
88        let version = match &(*version) {
89            "1.0\n" => Version::V1_0,
90            "2.0\n" => Version::V2_0,
91            _ => Version::VUnknown,
92        };
93
94        Ok(version)
95    }
96
97    /**
98     * @todo Docs for this function
99     */
100    pub fn install_tree(&self) -> Result<Vec<PathItem>, Error> {
101        let mut install_tree = Vec::new();
102
103        let root = format!("{}data/", self.extracted_path.as_ref().unwrap());
104
105        for entry in glob(&format!("{}**/*", root)).expect("Failed to read glob pattern") {
106            match entry {
107                Ok(path) => {
108                    let path = path.as_path();
109                    let path_str = path.to_str().unwrap();
110                    let path_rel_to_root: Vec<&str> = path_str.split(&root).collect();
111                    let path_rel_to_root = format!("/{}", path_rel_to_root[1]);
112
113                    if path.is_file() {
114                        install_tree.push(PathItem {
115                            real: path_str.to_string(),
116                            move_to: path_rel_to_root,
117                        });
118                    }
119                }
120                Err(e) => println!("{:?}", e),
121            }
122        }
123
124        Ok(install_tree)
125    }
126
127    fn get_control_string(&self, control: &Paragraph, query: &str) -> String {
128        paragraph_contains(control.clone(), query.to_string())
129            .unwrap()
130            .value
131    }
132
133    fn str_option_to_number(&self, option: Option<String>) -> Option<u64> {
134        if let Some(option) = option {
135            Some(option.parse().unwrap())
136        } else {
137            None
138        }
139    }
140
141    fn get_control_option_str(&self, control: &Paragraph, query: &str) -> Option<String> {
142        let item = paragraph_contains(control.clone(), query.to_string());
143
144        if let Some(item) = item {
145            Some(item.value)
146        } else {
147            None
148        }
149    }
150
151    fn get_package_name(&self, control: &Paragraph, query: &str) -> Vec<PackageWithVersion> {
152        let item = paragraph_contains(control.clone(), query.to_string());
153
154        let mut deps = Vec::new();
155
156        if let Some(item) = item {
157            let input: Vec<&str> = item.value.split(',').collect();
158
159            input
160                .into_iter()
161                .for_each(|dep| deps.push(PackageWithVersion::from_str(dep)));
162        }
163
164        deps
165    }
166
167    /**
168     * @todo Docs for this function
169     */
170    pub fn retrieve_control(&self) -> Result<Control, Error> {
171        self.extract_check()?;
172
173        let control_raw = fs::read_to_string(format!(
174            "{}control/control",
175            self.extracted_path.as_ref().unwrap()
176        ))?;
177        let control = parse_str(&control_raw).unwrap()[0].clone();
178
179        let package = self.get_control_string(&control, "Package");
180        let version = self.get_control_string(&control, "Version");
181        let architecture = self.get_control_string(&control, "Architecture");
182        let maintainer = self.get_control_string(&control, "Maintainer");
183        let description = self
184            .get_control_string(&control, "Description")
185            .replace('\n', " ");
186
187        let source = self.get_control_option_str(&control, "Source");
188        let section = self.get_control_option_str(&control, "Section");
189        let priority = self.get_control_option_str(&control, "Priority");
190        let essential = self.get_control_option_str(&control, "Essential");
191        let install_size = self.get_control_option_str(&control, "Installed-Size");
192        let homepage = self.get_control_option_str(&control, "Homepage");
193        let built_using = self.get_control_option_str(&control, "Built-Using");
194
195        let install_size = self.str_option_to_number(install_size);
196
197        // Depends et al
198        let depends = self.get_package_name(&control, "Depends");
199        let pre_depends = self.get_package_name(&control, "Pre-Depends");
200        let recommends = self.get_package_name(&control, "Recommends");
201        let suggests = self.get_package_name(&control, "Suggests");
202        let enhances = self.get_package_name(&control, "Enhances");
203        let breaks = self.get_package_name(&control, "Breaks");
204        let conflicts = self.get_package_name(&control, "Conflicts");
205
206        Ok(Control {
207            package,
208            source,
209            version,
210            section,
211            priority,
212            architecture,
213            essential,
214            install_size,
215            maintainer,
216            description,
217            homepage,
218            built_using,
219            depends,
220            pre_depends,
221            recommends,
222            suggests,
223            breaks,
224            enhances,
225            conflicts,
226        })
227    }
228}