1use std::collections::hash_map::Entry::Occupied;
2use std::collections::hash_map::Entry::Vacant;
3use std::collections::HashMap;
4use std::io::Error;
5use std::io::ErrorKind;
6use std::io::Write;
7use std::path::Path;
8use std::time::SystemTime;
9
10use base64ct::Base64;
11use base64ct::Encoding;
12use normalize_path::NormalizePath;
13use serde::Deserialize;
14use serde::Serialize;
15use x509_cert::der::Encode;
16use x509_cert::Certificate;
17
18use crate::xml;
19use crate::ChecksumAlgo;
20use crate::Compression;
21use crate::File;
22use crate::FileType;
23use crate::HardLink;
24use crate::Signer;
25use crate::Walk;
26
27pub struct BuilderOptions {
29 file_checksum_algo: ChecksumAlgo,
30 toc_checksum_algo: ChecksumAlgo,
31}
32
33impl BuilderOptions {
34 pub fn new() -> Self {
36 Self {
37 file_checksum_algo: Default::default(),
38 toc_checksum_algo: Default::default(),
39 }
40 }
41
42 pub fn file_checksum_algo(mut self, algo: ChecksumAlgo) -> Self {
44 self.file_checksum_algo = algo;
45 self
46 }
47
48 pub fn toc_checksum_algo(mut self, algo: ChecksumAlgo) -> Self {
50 self.toc_checksum_algo = algo;
51 self
52 }
53
54 pub fn create<W: Write, S: Signer, X>(
56 self,
57 writer: W,
58 signer: Option<S>,
59 ) -> ExtendedBuilder<W, S, X> {
60 ExtendedBuilder::with_options(writer, signer, self)
61 }
62}
63
64impl Default for BuilderOptions {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70pub type Builder<W, S> = ExtendedBuilder<W, S, ()>;
72
73pub type UnsignedBuilder<W> = ExtendedBuilder<W, NoSigner, ()>;
75
76pub struct ExtendedBuilder<W: Write, S: Signer = NoSigner, X = ()> {
78 writer: W,
79 signer: Option<S>,
80 file_checksum_algo: ChecksumAlgo,
81 toc_checksum_algo: ChecksumAlgo,
82 files: Vec<File<X>>,
83 contents: Vec<Vec<u8>>,
84 inodes: HashMap<(u64, u64), usize>,
86 offset: u64,
87}
88
89impl<W: Write, S: Signer, X> ExtendedBuilder<W, S, X> {
90 pub fn with_options(writer: W, signer: Option<S>, options: BuilderOptions) -> Self {
92 let toc_checksum_len = options.toc_checksum_algo.hash_len();
93 let offset = if let Some(ref signer) = signer {
94 toc_checksum_len + signer.signature_len()
95 } else {
96 toc_checksum_len
97 };
98 ExtendedBuilder {
99 writer,
100 signer,
101 offset: offset as u64,
102 file_checksum_algo: options.file_checksum_algo,
103 toc_checksum_algo: options.toc_checksum_algo,
104 files: Default::default(),
105 contents: Default::default(),
106 inodes: Default::default(),
107 }
108 }
109
110 pub fn new(writer: W, signer: Option<S>) -> Self {
112 Self::with_options(writer, signer, Default::default())
113 }
114
115 pub fn new_unsigned(writer: W) -> Self {
117 Self::with_options(writer, None, Default::default())
118 }
119
120 pub fn files(&self) -> &[File<X>] {
122 &self.files[..]
123 }
124
125 pub fn append_dir_all<F, P>(
127 &mut self,
128 path: P,
129 compression: Compression,
131 mut extra: F,
132 ) -> Result<(), Error>
133 where
134 F: FnMut(&File<X>, &Path, &Path) -> Result<Option<X>, Error>,
135 P: AsRef<Path>,
136 {
137 let path = path.as_ref();
138 let mut next_id = self.files.len() as u64 + 1;
139 let mut next_offset = self.offset;
140 let mut tree = HashMap::new();
141 for entry in path.walk()? {
142 let entry = entry?;
143 let archive_path = entry
144 .path()
145 .strip_prefix(path)
146 .map_err(|_| ErrorKind::InvalidData)?
147 .normalize();
148 if archive_path == Path::new("") {
149 continue;
150 }
151 let (file, archived_contents) = File::<X>::new(
152 next_id,
153 path,
154 entry.path(),
155 Path::new(archive_path.file_name().unwrap_or_default()).to_path_buf(),
156 compression,
157 self.file_checksum_algo,
158 next_offset,
159 None,
160 )?;
161 next_id += 1;
162 next_offset += archived_contents.len() as u64;
163 let parent = archive_path
164 .parent()
165 .map(|x| x.to_path_buf())
166 .unwrap_or_default();
167 if parent == Path::new("") {
168 tree.insert(archive_path, (file, archived_contents, entry.path()));
169 continue;
170 }
171 let parent = tree.get_mut(&parent).ok_or(ErrorKind::InvalidData)?;
172 parent.0.children.push(file);
173 }
174 let mut files: Vec<_> = tree.into_iter().collect();
175 files.sort_unstable_by_key(|entry| entry.1 .0.id);
176 for (archive_path, (mut file, archived_contents, real_path)) in files.into_iter() {
177 file.extra = extra(&file, &archive_path, &real_path)?;
178 self.append_raw(file, archived_contents)?;
179 }
180 Ok(())
181 }
182
183 pub fn append_raw(
185 &mut self,
186 mut file: File<X>,
187 archived_contents: Vec<u8>,
188 ) -> Result<(), Error> {
189 self.handle_hard_links(&mut file);
190 self.offset += archived_contents.len() as u64;
191 self.files.push(file);
192 self.contents.push(archived_contents);
193 Ok(())
194 }
195
196 pub fn get_mut(&mut self) -> &mut W {
198 self.writer.by_ref()
199 }
200
201 pub fn get(&self) -> &W {
203 &self.writer
204 }
205
206 fn handle_hard_links(&mut self, file: &mut File<X>) {
207 match self.inodes.entry((file.deviceno, file.inode)) {
208 Vacant(v) => {
209 let i = self.files.len();
210 v.insert(i);
211 }
212 Occupied(o) => {
213 let i = *o.get();
214 let original_file = &mut self.files[i];
215 file.kind = FileType::HardLink(HardLink::Id(original_file.id));
216 if !matches!(original_file.kind, FileType::HardLink(..)) {
218 original_file.kind = FileType::HardLink(HardLink::Original);
219 }
220 }
221 }
222 }
223}
224
225impl<W: Write, S: Signer, X: Serialize + for<'a> Deserialize<'a> + Default>
226 ExtendedBuilder<W, S, X>
227{
228 pub fn finish(mut self) -> Result<W, Error> {
230 let checksum_len = self.toc_checksum_algo.hash_len() as u64;
231 let signature = match self.signer.as_ref() {
233 Some(signer) => Some(xml::Signature {
234 style: signer.signature_style().into(),
235 offset: checksum_len,
236 size: signer.signature_len() as u64,
237 key_info: xml::KeyInfo {
238 data: xml::X509Data {
239 certificates: signer
240 .certs()
241 .iter()
242 .map(|cert| -> Result<_, Error> {
243 let bytes = cert.to_der().map_err(|_| ErrorKind::InvalidData)?;
244 let string = Base64::encode_string(&bytes);
245 Ok(xml::X509Certificate { data: string })
246 })
247 .collect::<Result<Vec<_>, _>>()?,
248 },
249 },
250 }),
251 None => None,
252 };
253 let xar = xml::Xar::<X> {
254 toc: xml::Toc::<X> {
255 checksum: xml::TocChecksum {
256 algo: self.toc_checksum_algo,
257 offset: 0,
258 size: checksum_len,
259 },
260 files: self.files,
261 signature,
262 creation_time: xml::Timestamp(SystemTime::now()),
263 },
264 };
265 xar.write(
267 self.writer.by_ref(),
268 self.toc_checksum_algo,
269 self.signer.as_ref(),
270 )?;
271 for content in self.contents.into_iter() {
272 self.writer.write_all(&content)?;
273 }
274 Ok(self.writer)
275 }
276}
277
278pub struct NoSigner;
280
281impl Signer for NoSigner {
282 fn sign(&self, _data: &[u8]) -> Result<Vec<u8>, Error> {
283 Ok(Vec::new())
284 }
285
286 fn signature_style(&self) -> &str {
287 ""
288 }
289
290 fn signature_len(&self) -> usize {
291 0
292 }
293
294 fn certs(&self) -> &[Certificate] {
295 &[]
296 }
297}
298
299#[allow(unused)]
302pub fn no_extra_contents(
303 file: &File<()>,
304 archive_path: &Path,
305 file_system_path: &Path,
306) -> Result<Option<()>, Error> {
307 Ok(None)
308}