apple_flat_package/
package_info.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//! `PkgInfo` XML files.
6
7use {
8    crate::{distribution::Bundle, PkgResult},
9    serde::{Deserialize, Serialize},
10    std::io::Read,
11};
12
13/// Provides information about the package to install.
14///
15/// This includes authentication requirements, behavior after installation, etc.
16/// See the fields for more descriptions.
17#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct PackageInfo {
20    /// Authentication requirements for the package install.
21    ///
22    /// Values include `none` and `root`.
23    pub auth: String,
24
25    #[serde(rename = "deleteObsoleteLanguages")]
26    pub delete_obsolete_languages: Option<bool>,
27
28    /// Whether symlinks found at install time should be resolved instead of being replaced by a
29    /// real file or directory.
30    #[serde(rename = "followSymLinks")]
31    pub follow_symlinks: Option<bool>,
32
33    /// Format version of the package.
34    ///
35    /// Value is likely `2`.
36    pub format_version: u8,
37
38    /// Identifies the tool that assembled this package.
39    pub generator_version: Option<String>,
40
41    /// Uniform type identifier that defines the package.
42    ///
43    /// Should ideally be unique to this package.
44    pub identifier: String,
45
46    /// Default location where the payload hierarchy should be installed.
47    pub install_location: Option<String>,
48
49    /// Defines minimum OS version on which the package can be installed.
50    #[serde(rename = "minimumSystemVersion")]
51    pub minimum_system_version: Option<bool>,
52
53    /// Defines if permissions of existing directories should be updated with ones from the payload.
54    pub overwrite_permissions: Option<bool>,
55
56    /// Action to perform after install.
57    ///
58    /// Potential values can include `logout`, `restart`, and `shutdown`.
59    pub postinstall_action: Option<String>,
60
61    /// Preserve extended attributes on files.
62    pub preserve_xattr: Option<bool>,
63
64    /// Unknown.
65    ///
66    /// Probably has something to do with whether the installation tree can be relocated
67    /// without issue.
68    pub relocatable: Option<bool>,
69
70    /// Whether items in the package should be compressed after installation.
71    #[serde(rename = "useHFSPlusCompression")]
72    pub use_hfs_plus_compression: Option<bool>,
73
74    /// Version of the package.
75    ///
76    /// This is the version of the package itself, not the version of the application
77    /// being installed.
78    pub version: String,
79
80    // End of attributes. Beginning of elements.
81    #[serde(default)]
82    pub atomic_update_bundle: Vec<BundleRef>,
83
84    /// Versioning information about bundles within the payload.
85    #[serde(default)]
86    pub bundle: Vec<Bundle>,
87
88    #[serde(default)]
89    pub bundle_version: Vec<BundleRef>,
90
91    /// Files to not obsolete during install.
92    #[serde(default)]
93    pub dont_obsolete: Vec<File>,
94
95    /// Installs to process at next startup.
96    #[serde(default)]
97    pub install_at_startup: Vec<File>,
98
99    /// Files to be patched.
100    #[serde(default)]
101    pub patch: Vec<File>,
102
103    /// Provides information on the content being installed.
104    pub payload: Option<Payload>,
105
106    #[serde(default)]
107    pub relocate: Vec<BundleRef>,
108
109    /// Scripts to run before and after install.
110    #[serde(default)]
111    pub scripts: Scripts,
112
113    #[serde(default)]
114    pub strict_identifiers: Vec<BundleRef>,
115
116    #[serde(default)]
117    pub update_bundle: Vec<BundleRef>,
118
119    #[serde(default)]
120    pub upgrade_bundle: Vec<BundleRef>,
121}
122
123impl Default for PackageInfo {
124    fn default() -> Self {
125        Self {
126            auth: "none".into(),
127            delete_obsolete_languages: None,
128            follow_symlinks: None,
129            format_version: 2,
130            generator_version: Some("rust-apple-flat-package".to_string()),
131            identifier: "".to_string(),
132            install_location: None,
133            minimum_system_version: None,
134            overwrite_permissions: None,
135            postinstall_action: None,
136            preserve_xattr: None,
137            relocatable: None,
138            use_hfs_plus_compression: None,
139            version: "0".to_string(),
140            atomic_update_bundle: vec![],
141            bundle: vec![],
142            bundle_version: vec![],
143            dont_obsolete: vec![],
144            install_at_startup: vec![],
145            patch: vec![],
146            payload: None,
147            relocate: vec![],
148            scripts: Default::default(),
149            strict_identifiers: vec![],
150            update_bundle: vec![],
151            upgrade_bundle: vec![],
152        }
153    }
154}
155
156impl PackageInfo {
157    /// Parse Distribution XML from a reader.
158    pub fn from_reader(reader: impl Read) -> PkgResult<Self> {
159        let mut de = serde_xml_rs::Deserializer::new_from_reader(reader);
160
161        Ok(Self::deserialize(&mut de)?)
162    }
163
164    /// Parse Distribution XML from a string.
165    pub fn from_xml(s: &str) -> PkgResult<Self> {
166        let mut de = serde_xml_rs::Deserializer::new_from_reader(s.as_bytes())
167            .non_contiguous_seq_elements(true);
168
169        Ok(Self::deserialize(&mut de)?)
170    }
171}
172
173/// File record.
174#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
175#[serde(rename_all = "kebab-case")]
176pub struct File {
177    /// File path.
178    pub path: String,
179
180    /// Required SHA-1 of file.
181    pub required_sha1: Option<String>,
182
183    /// SHA-1 of file.
184    pub sha1: Option<String>,
185}
186
187#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
188pub struct Payload {
189    #[serde(rename = "numberOfFiles")]
190    pub number_of_files: u64,
191    #[serde(rename = "installKBytes")]
192    pub install_kbytes: u64,
193}
194
195#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
196pub struct BundleRef {
197    pub id: Option<String>,
198}
199
200/// Wrapper type to represent <scripts>.
201#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
202pub struct Scripts {
203    #[serde(rename = "$value")]
204    pub scripts: Vec<Script>,
205}
206
207/// An entry in <scripts>.
208#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
209pub enum Script {
210    #[serde(rename = "preinstall")]
211    PreInstall(PreInstall),
212
213    #[serde(rename = "postinstall")]
214    PostInstall(PostInstall),
215}
216
217/// A script to run before install.
218#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
219pub struct PreInstall {
220    /// Name of script to run.
221    pub file: String,
222
223    /// ID of bundle element to run before.
224    pub component_id: Option<String>,
225}
226
227/// A script to run after install.
228#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
229pub struct PostInstall {
230    /// Name of script to run.
231    pub file: String,
232
233    /// ID of bundle element to run after.
234    pub component_id: Option<String>,
235}
236
237#[cfg(test)]
238mod test {
239    use super::*;
240
241    #[test]
242    fn scripts_decode() {
243        const INPUT: &str = r#"
244            <?xml version="1.0" encoding="utf-8"?>
245            <pkg-info overwrite-permissions="true" relocatable="false" identifier="my-app" postinstall-action="none" version="1" format-version="2" generator-version="InstallCmds-807 (21D62)" install-location="/usr/bin/my-app" auth="root">
246                <payload numberOfFiles="123" installKBytes="123"/>
247                <bundle-version/>
248                <upgrade-bundle/>
249                <update-bundle/>
250                <atomic-update-bundle/>
251                <strict-identifier/>
252                <relocate/>
253                <scripts>
254                    <preinstall file="./preinstall"/>
255                    <postinstall file="./postinstall"/>
256                </scripts>
257            </pkg-info>
258        "#;
259
260        let info = PackageInfo::from_xml(INPUT.trim()).unwrap();
261
262        assert_eq!(
263            info.scripts.scripts,
264            vec![
265                Script::PreInstall(PreInstall {
266                    file: "./preinstall".into(),
267                    component_id: None,
268                }),
269                Script::PostInstall(PostInstall {
270                    file: "./postinstall".into(),
271                    component_id: None,
272                })
273            ]
274        );
275    }
276}