axfive-libzip 0.2.5

Bindings for system libzip
Documentation
use crate::error::ZipErrorT;
use crate::ffi;
use crate::Error;
use crate::Result;
use std::ffi::{c_void, CStr, CString};
use std::marker::PhantomData;
use std::path::Path;
use std::ptr::null_mut;

/// Borrows or owns a source
#[derive(Debug)]
pub struct Source<'a> {
    handle: *mut ffi::zip_source_t,
    phantom: PhantomData<&'a ()>,
}

impl<'a> Source<'a> {
    /// Indicate that the ownership has been taken by zip_open_from_source, zip_file_add, or
    /// zip_file_replace, and therefore shouldn't be freed.
    pub(crate) fn taken(mut self) {
        self.handle = null_mut();
    }

    pub(crate) fn handle_mut(&mut self) -> *mut ffi::zip_source_t {
        self.handle
    }

    pub(crate) fn from_reader(reader: Box<dyn std::io::Read + 'a>, size: Option<usize>) -> Result<Self> {
        let mut error = ZipErrorT::default();
        let reader_source = ReaderSource {
            reader,
            error: ZipErrorT::default(),
            size,
        };
        let handle = unsafe {
            ffi::zip_source_function_create(
                Some(zip_source_callback_fn),
                Box::into_raw(Box::new(reader_source)) as *mut c_void,
                &mut *error,
            )
        };
        if handle.is_null() {
            Err(error.into())
        } else {
            Ok(Source {
                handle,
                phantom: PhantomData,
            })
        }
    }

    pub fn from_reader_with_size(reader: Box<dyn std::io::Read + 'a>, size: usize) -> Result<Self> {
        Self::from_reader(reader, Some(size))
    }
}

impl<'a> TryFrom<&'a [u8]> for Source<'a> {
    type Error = Error;

    fn try_from(buffer: &[u8]) -> Result<Source<'_>> {
        let mut error = ZipErrorT::default();
        let handle = unsafe {
            ffi::zip_source_buffer_create(buffer.as_ptr() as _, buffer.len() as _, 0, &mut *error)
        };
        if handle.is_null() {
            Err(error.into())
        } else {
            Ok(Source {
                handle,
                phantom: PhantomData,
            })
        }
    }
}

impl TryFrom<&CStr> for Source<'static> {
    type Error = Error;

    fn try_from(filename: &CStr) -> Result<Source<'static>> {
        let mut error = ZipErrorT::default();
        let handle = unsafe { ffi::zip_source_file_create(filename.as_ptr(), 0, 0, &mut *error) };
        if handle.is_null() {
            Err(error.into())
        } else {
            Ok(Source {
                handle,
                phantom: PhantomData,
            })
        }
    }
}

/// Open a zip file from a path.
/// This is less efficient than the &CStr variant, so that should be preferred when you can
/// construct a &CStr type directly or cache one.  If you would just be converting a path to a
/// cstring and then discarding it, this method might be preferable because that's all this does.
/// This will also panic if the Path contains any null bytes.
impl TryFrom<&Path> for Source<'static> {
    type Error = Error;

    fn try_from(filename: &Path) -> Result<Source<'static>> {
        let filename = CString::new(filename.to_string_lossy().into_owned())
            .expect("The path could not be converted into a CString");
        filename.as_ref().try_into()
    }
}

struct ReaderSource<'a> {
    size: Option<usize>,
    reader: Box<dyn std::io::Read + 'a>,
    error: ZipErrorT<ffi::zip_error_t>,
}

unsafe extern "C" fn zip_source_callback_fn(
    userdata: *mut c_void,
    buf: *mut c_void,
    len: ffi::zip_uint64_t,
    cmd: ffi::zip_source_cmd_t,
) -> ffi::zip_int64_t {
    let data = unsafe { &mut *(userdata as *mut ReaderSource) };
    match cmd {
        ffi::zip_source_cmd_ZIP_SOURCE_OPEN => 0,
        ffi::zip_source_cmd_ZIP_SOURCE_READ => {
            let buf = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, len as usize) };
            match data.reader.read(buf) {
                Ok(n) => n as i64,
                Err(e) => {
                    unsafe {
                        ffi::zip_error_set(
                            &mut *data.error,
                            ffi::ZIP_ER_READ as i32,
                            e.raw_os_error().unwrap_or(0),
                        );
                    }
                    -1
                }
            }
        }
        ffi::zip_source_cmd_ZIP_SOURCE_CLOSE => 0,
        ffi::zip_source_cmd_ZIP_SOURCE_STAT => {
            let buf = buf as *mut ffi::zip_stat_t;
            unsafe {
                ffi::zip_stat_init(buf);
                if let Some(size) = data.size {
                    (*buf).size = size as u64;
                    (*buf).valid = ffi::ZIP_STAT_SIZE as u64;
                }
            }
            std::mem::size_of::<ffi::zip_stat_t>() as i64
        }
        ffi::zip_source_cmd_ZIP_SOURCE_ERROR => {
            let n = unsafe { ffi::zip_error_to_data(&*data.error, buf, len) };
            unsafe { ffi::zip_error_init(&mut *data.error) };
            n
        }
        ffi::zip_source_cmd_ZIP_SOURCE_FREE => {
            drop(unsafe { Box::from_raw(data) });
            0
        }
        _ => {
            unsafe {
                ffi::zip_error_set(&mut *data.error, ffi::ZIP_ER_OPNOTSUPP as i32, 0);
            }
            -1
        }
    }
}

impl TryFrom<Box<dyn std::io::Read>> for Source<'static> {
    type Error = Error;

    fn try_from(reader: Box<dyn std::io::Read>) -> Result<Source<'static>> {
        Self::from_reader(reader, None)
    }
}

impl TryFrom<Box<[u8]>> for Source<'static> {
    type Error = Error;

    fn try_from(reader: Box<[u8]>) -> Result<Source<'static>> {
        let size = reader.len();
        let reader = Box::new(std::io::Cursor::new(reader));
        Self::from_reader_with_size(reader, size)
    }
}

impl Drop for Source<'_> {
    fn drop(&mut self) {
        if !self.handle.is_null() {
            unsafe {
                ffi::zip_source_free(self.handle);
            }
        }
    }
}