sit-rs 0.3.0

Rust-native extraction for StuffIt Expander archive files
Documentation
use std::{
    io::{self, Seek},
    rc::Rc,
};

use sit_algos::huffman::HuffmanReader;
use sit_algos::huffman_fixed::HuffmanReader as HuffmanFixedReader;
use sit_algos::lmzw::LmzwReader;
use sit_algos::lz_huffman::LzHuffmanReader;
use sit_algos::lzah::LzahReader;
use sit_algos::lzw::LzwReader;
use sit_algos::none::NoneReader;
use sit_algos::rle90::Rle90Reader;
use sit_algos::{arsenic::ArsenicReader, installer::InstallerReader};

use crate::{Error, VerifyingEntryReader, structs::Algorithm};

pub(crate) enum EntryReader<'a, R: io::Read + io::Seek> {
    None {
        reader: NoneReader<&'a mut R>,
    },
    Rle {
        reader: Rle90Reader<NoneReader<&'a mut R>>,
    },
    Lzw {
        reader: Box<LzwReader<NoneReader<&'a mut R>>>,
    },
    Huffman {
        reader: HuffmanReader<NoneReader<&'a mut R>>,
    },
    Lzah {
        reader: Box<LzahReader<NoneReader<&'a mut R>>>,
    },
    HuffmanFixed {
        reader: Box<HuffmanFixedReader<NoneReader<&'a mut R>>>,
    },
    Lmzw {
        reader: Box<LmzwReader<NoneReader<&'a mut R>>>,
    },
    LzHuffman {
        reader: Box<LzHuffmanReader<NoneReader<&'a mut R>>>,
    },
    Installer {
        reader: Box<InstallerReader<NoneReader<&'a mut R>>>,
    },
    Arsenic {
        reader: Box<ArsenicReader<'a, NoneReader<&'a mut R>>>,
    },
}

impl<'a, R: io::Read + io::Seek> EntryReader<'a, R> {
    pub(crate) fn try_from(
        reader: &'a mut Rc<R>,
        algo: Algorithm,
        uncompressed_size: u64,
        compressed_size: usize,
        offset: u64,
    ) -> Result<Self, Error> {
        unsafe { Rc::get_mut_unchecked(reader).seek(io::SeekFrom::Start(offset))? };

        unsafe {
            Ok(match algo {
                Algorithm::None => EntryReader::None {
                    reader: NoneReader::with_length(
                        Rc::get_mut_unchecked(reader),
                        offset,
                        compressed_size as u64,
                    ),
                },
                Algorithm::RLE => EntryReader::Rle {
                    reader: Rle90Reader::new(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    ),
                },
                Algorithm::LZW => EntryReader::Lzw {
                    reader: Box::new(LzwReader::new(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )),
                },
                Algorithm::Huffman => EntryReader::Huffman {
                    reader: HuffmanReader::try_from(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )?,
                },
                Algorithm::LZAH => EntryReader::Lzah {
                    reader: Box::new(LzahReader::new(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )),
                },
                Algorithm::HuffmanFixed => EntryReader::HuffmanFixed {
                    reader: Box::new(HuffmanFixedReader::try_from(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )?),
                },
                Algorithm::LZMW => EntryReader::Lmzw {
                    reader: Box::new(LmzwReader::new(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )),
                },
                Algorithm::LzHuffman => EntryReader::LzHuffman {
                    reader: Box::new(LzHuffmanReader::new(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )),
                },
                Algorithm::Installer => EntryReader::Installer {
                    reader: Box::new(InstallerReader::try_new(NoneReader::with_length(
                        Rc::get_mut_unchecked(reader),
                        offset,
                        compressed_size as u64,
                    ))?),
                },
                Algorithm::Arsenic => EntryReader::Arsenic {
                    reader: Box::new(ArsenicReader::try_from(
                        NoneReader::with_length(
                            Rc::get_mut_unchecked(reader),
                            offset,
                            compressed_size as u64,
                        ),
                        uncompressed_size,
                    )?),
                },
                Algorithm::Unknown(id) => {
                    return Err(Error::UnsupportedFeature(
                        crate::error::UnsupportedFeature::Algorithm(Algorithm::Unknown(id)),
                    ));
                }
            })
        }
    }

    pub fn verifying(self, checksum: u16) -> VerifyingEntryReader<'a, R> {
        // Arsenic compression uses 32-bit checksums interleaved with the compressed blocks
        // so it get's special treatment
        let is_arsenic = matches!(self, EntryReader::Arsenic { .. });

        VerifyingEntryReader::new(self, checksum, is_arsenic)
    }

    pub(crate) fn ended(&mut self) -> Result<bool, io::Error> {
        Ok(self.stream_position()? >= self.stream_len()?)
    }
}

impl<'a, R: io::Read + io::Seek> io::Read for EntryReader<'a, R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self {
            EntryReader::None { reader, .. } => reader.read(buf),
            EntryReader::Rle { reader, .. } => reader.read(buf),
            EntryReader::Lzw { reader, .. } => reader.read(buf),
            EntryReader::Huffman { reader, .. } => reader.read(buf),
            EntryReader::Lzah { reader, .. } => reader.read(buf),
            EntryReader::HuffmanFixed { reader, .. } => reader.read(buf),
            EntryReader::Lmzw { reader, .. } => reader.read(buf),
            EntryReader::LzHuffman { reader, .. } => reader.read(buf),
            EntryReader::Installer { reader } => reader.read(buf),
            EntryReader::Arsenic { reader, .. } => reader.read(buf),
        }
    }
}

impl<'a, R: io::Read + io::Seek> io::Seek for EntryReader<'a, R> {
    #[inline]
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        match self {
            EntryReader::None { reader, .. } => reader.seek(pos),
            EntryReader::Rle { reader, .. } => reader.seek(pos),
            EntryReader::Lzw { reader, .. } => reader.seek(pos),
            EntryReader::Huffman { reader, .. } => reader.seek(pos),
            EntryReader::Lzah { reader, .. } => reader.seek(pos),
            EntryReader::HuffmanFixed { reader, .. } => reader.seek(pos),
            EntryReader::Lmzw { reader, .. } => reader.seek(pos),
            EntryReader::LzHuffman { reader, .. } => reader.seek(pos),
            EntryReader::Installer { reader } => reader.seek(pos),
            EntryReader::Arsenic { reader, .. } => reader.seek(pos),
        }
    }

    #[inline]
    fn stream_position(&mut self) -> io::Result<u64> {
        match self {
            EntryReader::None { reader, .. } => reader.stream_position(),
            EntryReader::Rle { reader, .. } => reader.stream_position(),
            EntryReader::Lzw { reader, .. } => reader.stream_position(),
            EntryReader::Huffman { reader, .. } => reader.stream_position(),
            EntryReader::Lzah { reader, .. } => reader.stream_position(),
            EntryReader::HuffmanFixed { reader, .. } => reader.stream_position(),
            EntryReader::Lmzw { reader, .. } => reader.stream_position(),
            EntryReader::LzHuffman { reader, .. } => reader.stream_position(),
            EntryReader::Installer { reader, .. } => reader.stream_position(),
            EntryReader::Arsenic { reader, .. } => reader.stream_position(),
        }
    }

    #[inline]
    fn stream_len(&mut self) -> io::Result<u64> {
        match self {
            EntryReader::None { reader, .. } => reader.stream_len(),
            EntryReader::Rle { reader, .. } => reader.stream_len(),
            EntryReader::Lzw { reader, .. } => reader.stream_len(),
            EntryReader::Huffman { reader, .. } => reader.stream_len(),
            EntryReader::Lzah { reader, .. } => reader.stream_len(),
            EntryReader::HuffmanFixed { reader, .. } => reader.stream_len(),
            EntryReader::Lmzw { reader, .. } => reader.stream_len(),
            EntryReader::LzHuffman { reader, .. } => reader.stream_len(),
            EntryReader::Installer { reader, .. } => reader.stream_len(),
            EntryReader::Arsenic { reader, .. } => reader.stream_len(),
        }
    }
}