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