apple_flat_package/
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//! Reading support for Apple flat package (`.pkg`) installers.
6
7use {
8    crate::{
9        component_package::ComponentPackageReader, distribution::Distribution, Error, PkgResult,
10    },
11    apple_xar::reader::XarReader,
12    std::{
13        fmt::Debug,
14        io::{Cursor, Read, Seek},
15    },
16};
17
18/// The type of a flat package.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum PkgFlavor {
21    /// A *component* installer.
22    ///
23    /// This consists of a single component.
24    Component,
25
26    /// A *product* installer.
27    ///
28    /// This consists of multiple components, described by a `Distribution` file.
29    Product,
30}
31
32/// Read-only interface to a single flat package XAR archive.
33pub struct PkgReader<R: Read + Seek + Sized + Debug> {
34    xar: XarReader<R>,
35    flavor: PkgFlavor,
36}
37
38impl<R: Read + Seek + Sized + Debug> PkgReader<R> {
39    /// Construct an instance from a reader.
40    ///
41    /// The reader will read the contents of a XAR archive. This is likely
42    /// a `.pkg` file.
43    pub fn new(reader: R) -> PkgResult<Self> {
44        let xar = XarReader::new(reader)?;
45
46        let flavor = if xar.find_file("Distribution")?.is_some() {
47            PkgFlavor::Product
48        } else {
49            PkgFlavor::Component
50        };
51
52        Ok(Self { xar, flavor })
53    }
54
55    /// Return the inner reader, consuming self.
56    pub fn into_inner(self) -> XarReader<R> {
57        self.xar
58    }
59
60    /// Obtain the flavor of the flat package.
61    pub fn flavor(&self) -> PkgFlavor {
62        self.flavor
63    }
64
65    /// Obtain the parsed `Distribution` XML file describing the installer.
66    ///
67    /// Not all flat packages have a `Distribution` file, so this may resolve to
68    /// `None`.
69    pub fn distribution(&mut self) -> PkgResult<Option<Distribution>> {
70        if let Some(xml_data) = self.xar.get_file_data_from_path("Distribution")? {
71            Ok(Some(Distribution::from_reader(Cursor::new(xml_data))?))
72        } else {
73            Ok(None)
74        }
75    }
76
77    /// Attempt to resolve a component given a path prefix.
78    ///
79    /// If a component is found under a given path, `Some` is returned. Otherwise
80    /// `None` is returned.
81    ///
82    /// Pass in `""` to resolve the root component.
83    ///
84    /// A *found* component is defined by the presence of 1 or more well-known files
85    /// in components (`Bom`, `PackageInfo`, `Payload`, etc).
86    ///
87    pub fn resolve_component(
88        &mut self,
89        path_prefix: &str,
90    ) -> PkgResult<Option<ComponentPackageReader>> {
91        let prefix = if path_prefix.is_empty() {
92            "".to_string()
93        } else {
94            format!("{path_prefix}/")
95        };
96
97        let mut bom_data = None;
98        let mut package_info_data = None;
99        let mut payload_data = None;
100        let mut scripts_data = None;
101
102        for (filename, file) in self
103            .xar
104            .files()?
105            .into_iter()
106            .filter(|(filename, _)| filename.starts_with(&prefix))
107        {
108            let mut data = Vec::<u8>::with_capacity(file.size.unwrap_or(0) as _);
109            self.xar
110                .write_file_data_decoded_from_file(&file, &mut data)?;
111
112            let filename = filename.strip_prefix(&prefix).expect("prefix should match");
113
114            match filename {
115                "Bom" => {
116                    bom_data = Some(data);
117                }
118                "PackageInfo" => {
119                    package_info_data = Some(data);
120                }
121                "Payload" => {
122                    payload_data = Some(data);
123                }
124                "Scripts" => {
125                    scripts_data = Some(data);
126                }
127                _ => {}
128            }
129        }
130
131        if bom_data.is_some()
132            || package_info_data.is_some()
133            || payload_data.is_some()
134            || scripts_data.is_some()
135        {
136            Ok(Some(ComponentPackageReader::from_file_data(
137                bom_data,
138                package_info_data,
139                payload_data,
140                scripts_data,
141            )?))
142        } else {
143            Ok(None)
144        }
145    }
146
147    /// Obtain the *root* component in this installer.
148    ///
149    /// The *root component* is defined as a a collection of `Bom`, `PackageInfo`,
150    /// `Payload`, and `Scripts` files at the root directory of the XAR archive
151    /// this instance was constructed from.
152    ///
153    /// This will likely resolve to `None` for *product packages` and `Some`
154    /// for *component packages*.
155    pub fn root_component(&mut self) -> PkgResult<Option<ComponentPackageReader>> {
156        self.resolve_component("")
157    }
158
159    /// Obtain *component package* instances in this flat package.
160    ///
161    /// This looks for `.pkg` directories in the root directory of the XAR archive
162    /// and resolves a [ComponentPackageReader] for each. If there are no `.pkg`
163    /// directories, this will return an empty vec.
164    ///
165    /// Generally, this function will return something for *product packages*
166    /// whereas [root_component()] will return something for *component packages*.
167    pub fn component_packages(&mut self) -> PkgResult<Vec<ComponentPackageReader>> {
168        // TODO obtain instances from Distribution XML instead of scanning filenames.
169        let components = self
170            .xar
171            .files()?
172            .into_iter()
173            .filter_map(|(filename, _)| {
174                if filename.ends_with(".pkg") && !filename.contains('/') {
175                    Some(filename)
176                } else {
177                    None
178                }
179            })
180            .collect::<Vec<_>>();
181
182        let mut res = vec![];
183
184        for component in components {
185            res.push(
186                self.resolve_component(&component)?
187                    .ok_or(Error::ComponentResolution)?,
188            );
189        }
190
191        Ok(res)
192    }
193}