debian_packaging/deb/
reader.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*! .deb file reading functionality. */
6
7use {
8    crate::{
9        binary_package_control::BinaryPackageControlFile,
10        control::ControlParagraphReader,
11        error::{DebianError, Result},
12    },
13    std::{
14        io::{Cursor, Read},
15        ops::{Deref, DerefMut},
16    },
17};
18
19fn reader_from_filename(extension: &str, data: std::io::Cursor<Vec<u8>>) -> Result<Box<dyn Read>> {
20    match extension {
21        "" => Ok(Box::new(data)),
22        ".gz" => Ok(Box::new(libflate::gzip::Decoder::new(data)?)),
23        ".xz" => Ok(Box::new(xz2::read::XzDecoder::new(data))),
24        ".zst" => Ok(Box::new(zstd::Decoder::new(data)?)),
25        _ => Err(DebianError::DebUnknownCompression(extension.to_string())),
26    }
27}
28
29fn reader_from_filename_async(
30    extension: &str,
31    data: futures::io::Cursor<Vec<u8>>,
32) -> Result<Box<dyn futures::AsyncRead + Unpin>> {
33    match extension {
34        "" => Ok(Box::new(data)),
35        ".gz" => Ok(Box::new(
36            async_compression::futures::bufread::GzipDecoder::new(data),
37        )),
38        ".xz" => Ok(Box::new(
39            async_compression::futures::bufread::XzDecoder::new(data),
40        )),
41        ".zst" => Ok(Box::new(
42            async_compression::futures::bufread::ZstdDecoder::new(data),
43        )),
44        _ => Err(DebianError::DebUnknownCompression(extension.to_string())),
45    }
46}
47
48/// A reader of .deb files.
49///
50/// A .deb binary package file is an ar archive with 3 entries:
51///
52/// 1. `debian-binary` holding the version of the binary package format.
53/// 2. `control.tar` holding package metadata.
54/// 3. `data.tar[.<ext>]` holding file content.
55pub struct BinaryPackageReader<R: Read> {
56    archive: ar::Archive<R>,
57}
58
59impl<R: Read> BinaryPackageReader<R> {
60    /// Construct a new instance from a reader.
61    pub fn new(reader: R) -> Result<Self> {
62        Ok(Self {
63            archive: ar::Archive::new(reader),
64        })
65    }
66
67    /// Obtain the next entry from the underlying ar archive.
68    ///
69    /// The entry will be converted to an enum that richly represents its content.
70    pub fn next_entry(&mut self) -> Option<Result<BinaryPackageEntry>> {
71        if let Some(entry) = self.archive.next_entry() {
72            match entry {
73                Ok(mut entry) => {
74                    // We could do this in the domain of bytes. But filenames should be ASCII,
75                    // so converting to strings feels reasonably safe.
76                    let filename = String::from_utf8_lossy(entry.header().identifier()).to_string();
77
78                    let mut data = vec![];
79                    match entry.read_to_end(&mut data) {
80                        Ok(_) => {}
81                        Err(e) => {
82                            return Some(Err(e.into()));
83                        }
84                    }
85
86                    if filename == "debian-binary" {
87                        Some(Ok(BinaryPackageEntry::DebianBinary(std::io::Cursor::new(
88                            data,
89                        ))))
90                    } else if let Some(tail) = filename.strip_prefix("control.tar") {
91                        match reader_from_filename(tail, std::io::Cursor::new(data)) {
92                            Ok(res) => Some(Ok(BinaryPackageEntry::Control(ControlTarReader {
93                                archive: tar::Archive::new(res),
94                            }))),
95                            Err(e) => Some(Err(e)),
96                        }
97                    } else if let Some(tail) = filename.strip_prefix("data.tar") {
98                        match reader_from_filename_async(tail, futures::io::Cursor::new(data)) {
99                            Ok(res) => Some(Ok(BinaryPackageEntry::Data(DataTarReader {
100                                archive: async_tar::Archive::new(res),
101                            }))),
102                            Err(e) => Some(Err(e)),
103                        }
104                    } else {
105                        Some(Err(DebianError::DebUnknownBinaryPackageEntry(
106                            filename.to_string(),
107                        )))
108                    }
109                }
110                Err(e) => Some(Err(e.into())),
111            }
112        } else {
113            None
114        }
115    }
116}
117
118/// Represents an entry in a .deb archive.
119pub enum BinaryPackageEntry {
120    /// The `debian-binary` file.
121    DebianBinary(std::io::Cursor<Vec<u8>>),
122    /// The `control.tar` tar archive.
123    Control(ControlTarReader),
124    /// The `data.tar[.<ext>]` tar archive.
125    Data(DataTarReader),
126}
127
128/// A reader for `control.tar` files.
129pub struct ControlTarReader {
130    archive: tar::Archive<Box<dyn Read>>,
131}
132
133impl Deref for ControlTarReader {
134    type Target = tar::Archive<Box<dyn Read>>;
135
136    fn deref(&self) -> &Self::Target {
137        &self.archive
138    }
139}
140
141impl DerefMut for ControlTarReader {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        &mut self.archive
144    }
145}
146
147impl ControlTarReader {
148    /// Obtain the entries in the `control.tar` file.
149    ///
150    /// This can only be called once, immediately after the reader/archive is opened.
151    /// It is a glorified wrapper around [tar::Archive::entries()] and has the same
152    /// semantics.
153    pub fn entries(&mut self) -> Result<ControlTarEntries<'_>> {
154        let entries = self.archive.entries()?;
155
156        Ok(ControlTarEntries { entries })
157    }
158}
159
160/// Represents entries in a `control.tar` file.
161///
162/// Ideally this type wouldn't exist. It is a glorified wrapper around
163/// [tar::Entries] that is needed to placate the borrow checker.
164pub struct ControlTarEntries<'a> {
165    entries: tar::Entries<'a, Box<dyn Read>>,
166}
167
168impl<'a> Iterator for ControlTarEntries<'a> {
169    type Item = Result<ControlTarEntry<'a>>;
170
171    fn next(&mut self) -> Option<Self::Item> {
172        match self.entries.next() {
173            Some(Ok(entry)) => Some(Ok(ControlTarEntry { inner: entry })),
174            Some(Err(e)) => Some(Err(e.into())),
175            None => None,
176        }
177    }
178}
179
180/// A wrapper around [tar::Entry] for representing content in `control.tar` files.
181///
182/// Facilitates access to the raw [tar::Entry] as well as for obtaining a higher
183/// level type that decodes known files within `control.tar` files.
184pub struct ControlTarEntry<'a> {
185    inner: tar::Entry<'a, Box<dyn Read>>,
186}
187
188impl<'a> Deref for ControlTarEntry<'a> {
189    type Target = tar::Entry<'a, Box<dyn Read>>;
190
191    fn deref(&self) -> &Self::Target {
192        &self.inner
193    }
194}
195
196impl<'a> DerefMut for ControlTarEntry<'a> {
197    fn deref_mut(&mut self) -> &mut Self::Target {
198        &mut self.inner
199    }
200}
201
202impl<'a> ControlTarEntry<'a> {
203    /// Attempt to convert this tar entry to a [ControlTarFile].
204    ///
205    ///
206    pub fn to_control_file(&mut self) -> Result<(&'_ tar::Header, ControlTarFile)> {
207        let path_bytes = self.inner.path_bytes().to_vec();
208        let path = String::from_utf8_lossy(&path_bytes);
209
210        let mut data = vec![];
211        self.inner.read_to_end(&mut data)?;
212
213        match path.trim_start_matches("./") {
214            "control" => {
215                let mut reader = ControlParagraphReader::new(Cursor::new(data));
216                let paragraph = reader.next().ok_or(DebianError::ControlFileNoParagraph)??;
217                let control = BinaryPackageControlFile::from(paragraph);
218
219                Ok((self.inner.header(), ControlTarFile::Control(control)))
220            }
221            "conffiles" => Ok((self.inner.header(), ControlTarFile::Conffiles(data))),
222            "triggers" => Ok((self.inner.header(), ControlTarFile::Triggers(data))),
223            "shlibs" => Ok((self.inner.header(), ControlTarFile::Shlibs(data))),
224            "symbols" => Ok((self.inner.header(), ControlTarFile::Symbols(data))),
225            "preinst" => Ok((self.inner.header(), ControlTarFile::Preinst(data))),
226            "postinst" => Ok((self.inner.header(), ControlTarFile::Postinst(data))),
227            "prerm" => Ok((self.inner.header(), ControlTarFile::Prerm(data))),
228            "postrm" => Ok((self.inner.header(), ControlTarFile::Postrm(data))),
229            _ => Ok((self.inner.header(), ControlTarFile::Other(path_bytes, data))),
230        }
231    }
232}
233
234/// Represents a parsed file in a `control.tar` archive.
235///
236/// Each variant encodes a known file in a `control.tar` archive.
237pub enum ControlTarFile {
238    /// The `control` file.
239    Control(BinaryPackageControlFile<'static>),
240
241    /// The `conffiles` file.
242    Conffiles(Vec<u8>),
243
244    /// The `triggers` file.
245    Triggers(Vec<u8>),
246
247    /// The `shlibs` file.
248    Shlibs(Vec<u8>),
249
250    /// The `symbols` file.
251    Symbols(Vec<u8>),
252
253    /// The `preinst` file.
254    Preinst(Vec<u8>),
255
256    /// The `postinst` file.
257    Postinst(Vec<u8>),
258
259    /// The `prerm` file.
260    Prerm(Vec<u8>),
261
262    /// The `postrm` file.
263    Postrm(Vec<u8>),
264
265    /// An unclassified file.
266    ///
267    /// First element is the path name as bytes. Second is the raw file content.
268    Other(Vec<u8>, Vec<u8>),
269}
270
271/// A reader for `data.tar` files.
272pub struct DataTarReader {
273    archive: async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>>,
274}
275
276impl Deref for DataTarReader {
277    type Target = async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>>;
278
279    fn deref(&self) -> &Self::Target {
280        &self.archive
281    }
282}
283
284impl DerefMut for DataTarReader {
285    fn deref_mut(&mut self) -> &mut Self::Target {
286        &mut self.archive
287    }
288}
289
290impl DataTarReader {
291    /// Obtain the inner [async_tar::Archive] to which this instance is bound.
292    pub fn into_inner(self) -> async_tar::Archive<Box<dyn futures::io::AsyncRead + Unpin>> {
293        self.archive
294    }
295}
296
297/// Resolve the `control` file from the `control.tar` file within a `.deb` archive.
298pub fn resolve_control_file(reader: impl Read) -> Result<BinaryPackageControlFile<'static>> {
299    let mut reader = BinaryPackageReader::new(reader)?;
300
301    while let Some(entry) = reader.next_entry() {
302        if let BinaryPackageEntry::Control(mut control) = entry? {
303            for entry in control.entries()? {
304                if let ControlTarFile::Control(control) = entry?.to_control_file()?.1 {
305                    return Ok(control);
306                }
307            }
308        }
309    }
310
311    Err(DebianError::ControlFileNotFound)
312}