axfive_libzip/
archive.rs

1use crate::error::ZipErrorT;
2use crate::ffi;
3use crate::file::{Compression, Encoding, File, LocateFlag, OpenFlag as FileOpenFlag};
4use crate::source::Source;
5use crate::Error;
6use crate::Result;
7use std::ffi::CStr;
8use std::marker::PhantomData;
9use std::ptr::null_mut;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
12pub enum OpenFlag {
13    CheckConsistency,
14    Create,
15    Exclusive,
16    Truncate,
17    ReadOnly,
18}
19
20#[derive(Debug)]
21pub struct Archive<'a> {
22    handle: *mut ffi::zip_t,
23    phantom: PhantomData<&'a ()>,
24}
25
26impl<'a> Archive<'a> {
27    pub fn open<F>(mut source: Source<'a>, flags: F) -> Result<Archive<'a>>
28    where
29        F: AsRef<[OpenFlag]>,
30    {
31        let mut flags_value = 0;
32        for flag in flags.as_ref() {
33            match flag {
34                OpenFlag::CheckConsistency => flags_value |= ffi::ZIP_CHECKCONS,
35                OpenFlag::Create => flags_value |= ffi::ZIP_CREATE,
36                OpenFlag::Exclusive => flags_value |= ffi::ZIP_EXCL,
37                OpenFlag::Truncate => flags_value |= ffi::ZIP_TRUNCATE,
38                OpenFlag::ReadOnly => flags_value |= ffi::ZIP_RDONLY,
39            }
40        }
41
42        unsafe {
43            let mut error = ZipErrorT::default();
44            let handle =
45                ffi::zip_open_from_source(source.handle_mut(), flags_value as _, &mut *error);
46
47            if handle.is_null() {
48                Err(error.into())
49            } else {
50                source.taken();
51                Ok(Archive {
52                    handle,
53                    phantom: PhantomData,
54                })
55            }
56        }
57    }
58
59    fn error(&mut self) -> ZipErrorT<&mut ffi::zip_error_t> {
60        unsafe {
61            let error = ffi::zip_get_error(self.handle);
62            (&mut *error).into()
63        }
64    }
65
66    /// Closes and consumes a zip file.  If this fails, an error and the failed-to-close zipfile
67    /// will be returned
68    fn close_mut(&mut self) -> Result<()> {
69        if self.handle.is_null() {
70            Ok(())
71        } else {
72            let result = unsafe { ffi::zip_close(self.handle) };
73            if result == 0 {
74                self.handle = null_mut();
75                Ok(())
76            } else {
77                Err(self.error().into())
78            }
79        }
80    }
81
82    /// Closes and consumes a zip file.
83    /// If this fails, the failed-to-close zipfile and an error will be returned.
84    pub fn close(mut self) -> std::result::Result<(), (Self, Error)> {
85        match self.close_mut() {
86            Ok(()) => Ok(()),
87            Err(e) => Err((self, e)),
88        }
89    }
90
91    /// Internal non-consuming discard, to facilitate drop
92    fn discard_mut(&mut self) {
93        if !self.handle.is_null() {
94            unsafe {
95                ffi::zip_discard(self.handle);
96            }
97        }
98    }
99
100    /// Discard and consume the zipfile.  This will never fail.
101    pub fn discard(mut self) {
102        self.discard_mut()
103    }
104
105    /// Set the permissions on the entry
106    /// The mode parameter should include the entry type: 0o100000 for regular file, 0o40000 for directory
107    fn set_mode(&mut self, index: u64, mode: u32) -> Result<()> {
108        let result = unsafe {
109            ffi::zip_file_set_external_attributes(
110                self.handle,
111                index,
112                0,
113                ffi::ZIP_OPSYS_UNIX as u8,
114                mode << 16,
115            )
116        };
117        if result == -1 {
118            Err(self.error().into())
119        } else {
120            Ok(())
121        }
122    }
123
124    /// Set the last modified time on the entry
125    /// The mtime parameter is given as a big-endian u32 DOS timestamp
126    fn set_mtime(&mut self, index: u64, mtime: u32) -> Result<()> {
127        let result = unsafe {
128            ffi::zip_file_set_dostime(
129                self.handle,
130                index,
131                (mtime & 0xffff) as u16,
132                (mtime >> 16) as u16,
133                0,
134            )
135        };
136        if result == -1 {
137            Err(self.error().into())
138        } else {
139            Ok(())
140        }
141    }
142
143    /// Add a file to the zip archive.
144    /// Returns the index of the new file.
145    /// The mtime parameter is given as a big-endian u32 DOS timestamp
146    pub fn add<'b: 'a, N>(
147        &mut self,
148        name: N,
149        mut source: Source<'b>,
150        encoding: Encoding,
151        compression: Compression,
152        mode: Option<u16>,
153        mtime: Option<u32>,
154        overwrite: bool,
155    ) -> Result<u64>
156    where
157        N: AsRef<CStr>,
158    {
159        let mut flags = match encoding {
160            Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
161            Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
162            Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
163        };
164        if overwrite {
165            flags |= ffi::ZIP_FL_OVERWRITE;
166        }
167
168        let response = unsafe {
169            ffi::zip_file_add(
170                self.handle,
171                name.as_ref().as_ptr(),
172                source.handle_mut(),
173                flags as _,
174            )
175        };
176        let index = if response == -1 {
177            return Err(self.error().into());
178        } else {
179            source.taken();
180            response as u64
181        };
182
183        let (comp, flags) = match compression {
184            Compression::Default => (ffi::ZIP_CM_DEFAULT as u32, 0),
185            Compression::Store => (ffi::ZIP_CM_STORE, 0),
186            Compression::Bzip2(i) => (ffi::ZIP_CM_BZIP2, i),
187            Compression::Deflate(i) => (ffi::ZIP_CM_DEFLATE, i),
188            Compression::Xz(i) => (ffi::ZIP_CM_XZ, i),
189            Compression::Zstd(i) => (ffi::ZIP_CM_ZSTD, i),
190        };
191        let result =
192            unsafe { ffi::zip_set_file_compression(self.handle, index, comp as i32, flags) };
193        if result == -1 {
194            return Err(self.error().into());
195        }
196
197        if let Some(mode) = mode {
198            self.set_mode(index, mode as u32 | 0o100000)?;
199        }
200        if let Some(mtime) = mtime {
201            self.set_mtime(index, mtime)?;
202        }
203
204        Ok(index)
205    }
206
207    /// Add a directory entry to the zip archive.
208    /// Returns the index of the new entry.
209    /// The mtime parameter is given as a big-endian u32 DOS timestamp
210    /// The source must outlive the archive, because according to the libzip
211    /// interface, changes are only written when the archive is closed, and
212    /// the source may be used until then.
213    /// If you need to work with a short-lived source, consider closing and
214    /// re-opening the archive for each operation.
215    pub fn add_dir_entry<N>(
216        &mut self,
217        name: N,
218        encoding: Encoding,
219        mode: Option<u16>,
220        mtime: Option<u32>,
221    ) -> Result<u64>
222    where
223        N: AsRef<CStr>,
224    {
225        let flags = match encoding {
226            Encoding::Guess => ffi::ZIP_FL_ENC_GUESS,
227            Encoding::Utf8 => ffi::ZIP_FL_ENC_UTF_8,
228            Encoding::Cp437 => ffi::ZIP_FL_ENC_CP437,
229        };
230
231        let response = unsafe {
232            ffi::zip_dir_add(
233                self.handle,
234                name.as_ref().as_ptr(),
235                flags as _,
236            )
237        };
238        let index = if response == -1 {
239            return Err(self.error().into());
240        } else {
241            response as u64
242        };
243
244        if let Some(mode) = mode {
245            self.set_mode(index, mode as u32 | 0o40000)?;
246        }
247        if let Some(mtime) = mtime {
248            self.set_mtime(index, mtime)?;
249        }
250
251        Ok(index)
252    }
253
254    /// Replace a file in the zip archive.
255    /// The source must outlive the archive, because according to the libzip
256    /// interface, changes are only written when the archive is closed, and
257    /// the source may be used until then.
258    /// If you need to work with a short-lived source, consider closing and
259    /// re-opening the archive for each operation.
260    pub fn replace<'b: 'a>(&mut self, index: u64, mut source: Source<'b>) -> Result<()> {
261        let response =
262            unsafe { ffi::zip_file_replace(self.handle, index as _, source.handle_mut(), 0) };
263        if response == -1 {
264            Err(self.error().into())
265        } else {
266            source.taken();
267            Ok(())
268        }
269    }
270
271    /// Add a file to the zip archive.
272    /// Returns the index of the new file.
273    pub fn open_file<N, O, L>(
274        &mut self,
275        name: N,
276        open_flags: O,
277        locate_flags: L,
278    ) -> Result<File<'_>>
279    where
280        N: AsRef<CStr>,
281        O: AsRef<[FileOpenFlag]>,
282        L: AsRef<[LocateFlag]>,
283    {
284        let mut flags_value = 0;
285        for flag in open_flags.as_ref() {
286            match flag {
287                FileOpenFlag::Compressed => flags_value |= ffi::ZIP_FL_COMPRESSED,
288                FileOpenFlag::Unchanged => flags_value |= ffi::ZIP_FL_UNCHANGED,
289            }
290        }
291        for flag in locate_flags.as_ref() {
292            match flag {
293                LocateFlag::NoCase => flags_value |= ffi::ZIP_FL_NOCASE,
294                LocateFlag::NoDir => flags_value |= ffi::ZIP_FL_NODIR,
295                LocateFlag::EncodingRaw => flags_value |= ffi::ZIP_FL_ENC_RAW,
296                LocateFlag::EncodingGuess => flags_value |= ffi::ZIP_FL_ENC_GUESS,
297                LocateFlag::EncodingStrict => flags_value |= ffi::ZIP_FL_ENC_STRICT,
298            }
299        }
300        let handle =
301            unsafe { ffi::zip_fopen(self.handle, name.as_ref().as_ptr(), flags_value as _) };
302        if handle.is_null() {
303            Err(self.error().into())
304        } else {
305            Ok(File {
306                handle,
307                phantom: PhantomData,
308            })
309        }
310    }
311}
312
313/// Closes the archive, silently discarding on error.
314/// It's strongly recommended to use the [Archive::close] method instead and validate that no
315/// errors have occurred.
316impl Drop for Archive<'_> {
317    fn drop(&mut self) {
318        if self.close_mut().is_err() {
319            self.discard_mut()
320        }
321    }
322}