debpkg/lib.rs
1//! # debpkg
2//!
3//! A library to parse binary debian packages
4//!
5//! This library provides utilties to parse [binary debian
6//! packages](https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.en.html#s-deb-format)
7//! abstracted over a reader. This API provides a streaming interface to avoid
8//! loading the entire debian package into RAM.
9//!
10//! This library only parses binary debian packages. It does not attempt to
11//! write binary debian packages.
12//!
13//! # Supported Debian Package Versions
14//!
15//! This package only supports version 2.0 of debian packages. Older versions
16//! are not currently supported.
17//!
18//! # Examples
19//!
20//! Parsing a debian package
21//!
22//! ```no_run
23//! let file = std::fs::File::open("test.deb").unwrap();
24//! let mut pkg = debpkg::DebPkg::parse(file).unwrap();
25//! let mut control_tar = pkg.control().unwrap();
26//! let control = debpkg::Control::extract(control_tar).unwrap();
27//! println!("Package Name: {}", control.name());
28//! println!("Package Version: {}", control.version());
29//! let arch = control.get("Architecture").unwrap();
30//! println!("Package Architecture: {}", arch);
31//!
32//! let mut data = pkg.data().unwrap();
33//! let dir = tempfile::TempDir::new().unwrap();
34//! data.unpack(dir).unwrap();
35//! ```
36
37use std::io::Read;
38
39mod error;
40pub use error::Error;
41
42mod control;
43pub use control::Control;
44
45mod debian_binary;
46use debian_binary::{parse_debian_binary_contents, DebianBinaryVersion};
47
48type Result<T> = std::result::Result<T, Error>;
49
50enum ReadState {
51 Opened,
52 ControlRead,
53 DataRead,
54}
55
56/// A debian package represented by the control data the archive holding all the
57/// information
58pub struct DebPkg<R: Read> {
59 /// How far we've read through the debian package. This is especially
60 /// important when R only implements Read and not Seek since we will not be
61 /// able to Read backwards.
62 state: ReadState,
63
64 /// The major and minor fomat version of the debian package
65 format_version: DebianBinaryVersion,
66
67 /// The ar archive in which the debian package is contained
68 archive: ar::Archive<R>,
69}
70
71fn validate_debian_binary<'a, R: 'a + Read>(
72 entry: &mut ar::Entry<'a, R>,
73) -> Result<DebianBinaryVersion> {
74 let identifier = "debian-binary";
75
76 if entry.header().identifier() == identifier.as_bytes() {
77 parse_debian_binary_contents(entry)
78 } else {
79 Err(Error::MissingDebianBinary)
80 }
81}
82
83impl<'a, R: 'a + Read> DebPkg<R> {
84 /// Parses a debian package out of reader
85 ///
86 /// # Arguments
87 ///
88 /// * `reader` - A type which implements `std::io::Read` and `std::io::Seek`
89 /// and is formatted as an ar archive
90 ///
91 /// # Example
92 ///
93 /// ```no_run
94 /// use debpkg::DebPkg;
95 /// let file = std::fs::File::open("test.deb").unwrap();
96 /// let pkg = DebPkg::parse(file).unwrap();
97 /// ```
98 pub fn parse(reader: R) -> Result<DebPkg<R>> {
99 let mut archive = ar::Archive::new(reader);
100 let mut debian_binary_entry = match archive.next_entry() {
101 Some(Ok(entry)) => entry,
102 Some(Err(err)) => return Err(Error::Io(err)),
103 None => return Err(Error::MissingDebianBinary),
104 };
105 let format_version = validate_debian_binary(&mut debian_binary_entry)?;
106 drop(debian_binary_entry);
107
108 Ok(DebPkg {
109 state: ReadState::Opened,
110 format_version,
111 archive,
112 })
113 }
114
115 /// Returns the format version of the binary debian package
116 pub fn format_version(&self) -> (u32, u32) {
117 (self.format_version.major, self.format_version.minor)
118 }
119
120 /// Returns the control tar
121 ///
122 /// # Arguments
123 ///
124 /// * `self` - A `DebPkg` created by a call to `DebPkg::parse`
125 ///
126 /// # Example
127 ///
128 /// ```no_run
129 /// use debpkg::DebPkg;
130 /// let file = std::fs::File::open("test.deb").unwrap();
131 /// let mut pkg = DebPkg::parse(file).unwrap();
132 /// let mut control_tar = pkg.control().unwrap();
133 /// for file in control_tar.entries().unwrap() {
134 /// println!("{}", file.unwrap().path().unwrap().display());
135 /// }
136 /// ```
137 ///
138 pub fn control(&'a mut self) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
139 match self.state {
140 ReadState::Opened => {
141 let entry = match self.archive.next_entry() {
142 Some(entry) => entry?,
143 None => return Err(Error::MissingControlArchive),
144 };
145
146 self.state = ReadState::ControlRead;
147 get_tar_from_entry(entry)
148 }
149 ReadState::ControlRead | ReadState::DataRead => Err(Error::ControlAlreadyRead),
150 }
151 }
152
153 /// Returns the data tar
154 ///
155 /// Must only be called
156 ///
157 /// # Arguments
158 ///
159 /// * `self` - A `DebPkg` created by a call to `DebPkg::parse`
160 ///
161 /// # Example
162 ///
163 /// ```no_run
164 /// use debpkg::DebPkg;
165 /// let file = std::fs::File::open("test.deb").unwrap();
166 /// let mut pkg = DebPkg::parse(file).unwrap();
167 /// let mut data_tar = pkg.data().unwrap();
168 /// for file in data_tar.entries().unwrap() {
169 /// println!("{}", file.unwrap().path().unwrap().display());
170 /// }
171 /// ```
172 ///
173 pub fn data(&'a mut self) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
174 match self.control() {
175 Ok(_) => (),
176 Err(Error::ControlAlreadyRead) => (),
177 Err(e) => return Err(e),
178 };
179
180 match self.state {
181 ReadState::Opened => unreachable!(),
182 ReadState::ControlRead => {
183 let entry = match self.archive.next_entry() {
184 Some(entry) => entry?,
185 None => return Err(Error::MissingDataArchive),
186 };
187
188 self.state = ReadState::DataRead;
189 get_tar_from_entry(entry)
190 }
191 ReadState::DataRead => Err(Error::DataAlreadyRead),
192 }
193 }
194}
195
196fn get_tar_from_entry<'a, R: 'a + Read>(
197 entry: ar::Entry<'a, R>,
198) -> Result<tar::Archive<Box<dyn Read + 'a>>> {
199 let mut reader = entry.take(1024);
200 let mut first_1kb = vec![];
201 reader.read_to_end(&mut first_1kb)?;
202
203 let is_tar = infer::archive::is_tar(&first_1kb);
204 let is_gz = infer::archive::is_gz(&first_1kb);
205 let is_xz = infer::archive::is_xz(&first_1kb);
206 let is_bz2 = infer::archive::is_bz2(&first_1kb);
207 let is_zst = infer::archive::is_zst(&first_1kb);
208
209 let entry = std::io::Cursor::new(first_1kb).chain(reader.into_inner());
210
211 if is_tar {
212 let entry: Box<dyn Read> = Box::new(entry);
213 Ok(tar::Archive::new(entry))
214 } else if is_gz {
215 let gz: Box<dyn Read> = Box::new(flate2::read::GzDecoder::new(entry));
216 Ok(tar::Archive::new(gz))
217 } else if is_xz {
218 let xz: Box<dyn Read> = Box::new(xz2::read::XzDecoder::new_multi_decoder(entry));
219 Ok(tar::Archive::new(xz))
220 } else if is_bz2 {
221 let bz2: Box<dyn Read> = Box::new(bzip2::read::BzDecoder::new(entry));
222 Ok(tar::Archive::new(bz2))
223 } else if is_zst {
224 let zstd: Box<dyn Read> = Box::new(zstd::stream::read::Decoder::new(entry)?);
225 Ok(tar::Archive::new(zstd))
226 } else {
227 Err(Error::UnknownEntryFormat)
228 }
229}