axfive_libzip/
source.rs

1use crate::error::ZipErrorT;
2use crate::ffi;
3use crate::Error;
4use crate::Result;
5use std::ffi::{c_void, CStr, CString};
6use std::marker::PhantomData;
7use std::path::Path;
8use std::ptr::null_mut;
9
10/// Borrows or owns a source
11#[derive(Debug)]
12pub struct Source<'a> {
13    handle: *mut ffi::zip_source_t,
14    phantom: PhantomData<&'a ()>,
15}
16
17impl<'a> Source<'a> {
18    /// Indicate that the ownership has been taken by zip_open_from_source, zip_file_add, or
19    /// zip_file_replace, and therefore shouldn't be freed.
20    pub(crate) fn taken(mut self) {
21        self.handle = null_mut();
22    }
23
24    pub(crate) fn handle_mut(&mut self) -> *mut ffi::zip_source_t {
25        self.handle
26    }
27
28    pub(crate) fn from_reader(reader: Box<dyn std::io::Read + 'a>, size: Option<usize>) -> Result<Self> {
29        let mut error = ZipErrorT::default();
30        let reader_source = ReaderSource {
31            reader,
32            error: ZipErrorT::default(),
33            size,
34        };
35        let handle = unsafe {
36            ffi::zip_source_function_create(
37                Some(zip_source_callback_fn),
38                Box::into_raw(Box::new(reader_source)) as *mut c_void,
39                &mut *error,
40            )
41        };
42        if handle.is_null() {
43            Err(error.into())
44        } else {
45            Ok(Source {
46                handle,
47                phantom: PhantomData,
48            })
49        }
50    }
51
52    pub fn from_reader_with_size(reader: Box<dyn std::io::Read + 'a>, size: usize) -> Result<Self> {
53        Self::from_reader(reader, Some(size))
54    }
55}
56
57impl<'a> TryFrom<&'a [u8]> for Source<'a> {
58    type Error = Error;
59
60    fn try_from(buffer: &[u8]) -> Result<Source<'_>> {
61        let mut error = ZipErrorT::default();
62        let handle = unsafe {
63            ffi::zip_source_buffer_create(buffer.as_ptr() as _, buffer.len() as _, 0, &mut *error)
64        };
65        if handle.is_null() {
66            Err(error.into())
67        } else {
68            Ok(Source {
69                handle,
70                phantom: PhantomData,
71            })
72        }
73    }
74}
75
76impl TryFrom<&CStr> for Source<'static> {
77    type Error = Error;
78
79    fn try_from(filename: &CStr) -> Result<Source<'static>> {
80        let mut error = ZipErrorT::default();
81        let handle = unsafe { ffi::zip_source_file_create(filename.as_ptr(), 0, 0, &mut *error) };
82        if handle.is_null() {
83            Err(error.into())
84        } else {
85            Ok(Source {
86                handle,
87                phantom: PhantomData,
88            })
89        }
90    }
91}
92
93/// Open a zip file from a path.
94/// This is less efficient than the &CStr variant, so that should be preferred when you can
95/// construct a &CStr type directly or cache one.  If you would just be converting a path to a
96/// cstring and then discarding it, this method might be preferable because that's all this does.
97/// This will also panic if the Path contains any null bytes.
98impl TryFrom<&Path> for Source<'static> {
99    type Error = Error;
100
101    fn try_from(filename: &Path) -> Result<Source<'static>> {
102        let filename = CString::new(filename.to_string_lossy().into_owned())
103            .expect("The path could not be converted into a CString");
104        filename.as_ref().try_into()
105    }
106}
107
108struct ReaderSource<'a> {
109    size: Option<usize>,
110    reader: Box<dyn std::io::Read + 'a>,
111    error: ZipErrorT<ffi::zip_error_t>,
112}
113
114unsafe extern "C" fn zip_source_callback_fn(
115    userdata: *mut c_void,
116    buf: *mut c_void,
117    len: ffi::zip_uint64_t,
118    cmd: ffi::zip_source_cmd_t,
119) -> ffi::zip_int64_t {
120    let data = unsafe { &mut *(userdata as *mut ReaderSource) };
121    match cmd {
122        ffi::zip_source_cmd_ZIP_SOURCE_OPEN => 0,
123        ffi::zip_source_cmd_ZIP_SOURCE_READ => {
124            let buf = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, len as usize) };
125            match data.reader.read(buf) {
126                Ok(n) => n as i64,
127                Err(e) => {
128                    unsafe {
129                        ffi::zip_error_set(
130                            &mut *data.error,
131                            ffi::ZIP_ER_READ as i32,
132                            e.raw_os_error().unwrap_or(0),
133                        );
134                    }
135                    -1
136                }
137            }
138        }
139        ffi::zip_source_cmd_ZIP_SOURCE_CLOSE => 0,
140        ffi::zip_source_cmd_ZIP_SOURCE_STAT => {
141            let buf = buf as *mut ffi::zip_stat_t;
142            unsafe {
143                ffi::zip_stat_init(buf);
144                if let Some(size) = data.size {
145                    (*buf).size = size as u64;
146                    (*buf).valid = ffi::ZIP_STAT_SIZE as u64;
147                }
148            }
149            std::mem::size_of::<ffi::zip_stat_t>() as i64
150        }
151        ffi::zip_source_cmd_ZIP_SOURCE_ERROR => {
152            let n = unsafe { ffi::zip_error_to_data(&*data.error, buf, len) };
153            unsafe { ffi::zip_error_init(&mut *data.error) };
154            n
155        }
156        ffi::zip_source_cmd_ZIP_SOURCE_FREE => {
157            drop(unsafe { Box::from_raw(data) });
158            0
159        }
160        _ => {
161            unsafe {
162                ffi::zip_error_set(&mut *data.error, ffi::ZIP_ER_OPNOTSUPP as i32, 0);
163            }
164            -1
165        }
166    }
167}
168
169impl TryFrom<Box<dyn std::io::Read>> for Source<'static> {
170    type Error = Error;
171
172    fn try_from(reader: Box<dyn std::io::Read>) -> Result<Source<'static>> {
173        Self::from_reader(reader, None)
174    }
175}
176
177impl TryFrom<Box<[u8]>> for Source<'static> {
178    type Error = Error;
179
180    fn try_from(reader: Box<[u8]>) -> Result<Source<'static>> {
181        let size = reader.len();
182        let reader = Box::new(std::io::Cursor::new(reader));
183        Self::from_reader_with_size(reader, size)
184    }
185}
186
187impl Drop for Source<'_> {
188    fn drop(&mut self) {
189        if !self.handle.is_null() {
190            unsafe {
191                ffi::zip_source_free(self.handle);
192            }
193        }
194    }
195}