forsmost 0.1.0

A Rust Foremost-style file carving command-line tool.
Documentation
use super::scan::{read_le_u16, read_le_u32, read_le_u64};
pub(super) const ASF_FILE_PROPERTIES_GUID: &[u8] =
    b"\xa1\xdc\xab\x8c\x47\xa9\xcf\x11\x8e\xe4\x00\xc0\x0c\x20\x53\x65";
pub(super) const ASF_FILE_SIZE_OFFSET: usize = 40;
pub(super) const ASF_HEADER_GUID: &[u8] =
    b"\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c";
pub(super) const BMP_SIZE_OFFSET: usize = 2;
pub(super) const BMP_DATA_OFFSET_OFFSET: usize = 10;
pub(super) const BMP_DATA_SIZE_OFFSET: usize = 34;
pub(super) const BMP_HEIGHT_OFFSET: usize = 22;
pub(super) const BMP_HEADER_LENGTH_OFFSET: usize = 14;
pub(super) const BMP_MIN_LEN: usize = 100;
pub(super) const BMP_VERTICAL_LIMIT: u32 = 2000;
pub(super) const BMP_WIDTH_OFFSET: usize = 18;
pub(super) const CFB_DIRECTORY_ENTRY_LEN: usize = 128;
pub(super) const CFB_DIRECTORY_SECTOR_LIMIT: usize = 300;
pub(super) const CFB_DIFAT_HEADER_ENTRIES: usize = 109;
pub(super) const CFB_DIFAT_OFFSET: usize = 76;
pub(super) const CFB_DIFAT_SECTOR: u32 = 0xffff_fffc;
pub(super) const CFB_END_OF_CHAIN: u32 = 0xffff_fffe;
pub(super) const CFB_FAT_SECTOR: u32 = 0xffff_fffd;
pub(super) const CFB_FREE_SECTOR: u32 = 0xffff_ffff;
pub(super) const CFB_HEADER_LEN: usize = 512;
pub(super) const CFB_MAX_EXTRA_DIFAT_SECTORS: u32 = 100;
pub(super) const CFB_MAX_FAT_SECTORS: u32 = 4096;
pub(super) const CFB_NO_STREAM: u32 = 0xffff_ffff;
pub(super) const CFB_ROOT: u8 = 5;
pub(super) const CFB_STREAM: u8 = 2;
pub(super) const CFB_VALID_BYTE_ORDER: u16 = 0xfffe;
pub(super) const ELF_32_HEADER_LEN: usize = 52;
pub(super) const ELF_32_PHDR_LEN: usize = 32;
pub(super) const ELF_32_SHDR_LEN: usize = 40;
pub(super) const ELF_64_HEADER_LEN: usize = 64;
pub(super) const ELF_64_PHDR_LEN: usize = 56;
pub(super) const ELF_64_SHDR_LEN: usize = 64;
pub(super) const ELF_CLASS_32: u8 = 1;
pub(super) const ELF_CLASS_64: u8 = 2;
pub(super) const ELF_DATA_BIG: u8 = 2;
pub(super) const ELF_DATA_LITTLE: u8 = 1;
pub(super) const ELF_HEADER_LEN_OFFSET_32: usize = 40;
pub(super) const ELF_HEADER_LEN_OFFSET_64: usize = 52;
pub(super) const ELF_MAGIC: &[u8] = b"\x7fELF";
pub(super) const ELF_PHDR_COUNT_OFFSET_32: usize = 44;
pub(super) const ELF_PHDR_COUNT_OFFSET_64: usize = 56;
pub(super) const ELF_PHDR_LEN_OFFSET_32: usize = 42;
pub(super) const ELF_PHDR_LEN_OFFSET_64: usize = 54;
pub(super) const ELF_PHDR_OFFSET_32: usize = 28;
pub(super) const ELF_PHDR_OFFSET_64: usize = 32;
pub(super) const ELF_SHDR_COUNT_OFFSET_32: usize = 48;
pub(super) const ELF_SHDR_COUNT_OFFSET_64: usize = 60;
pub(super) const ELF_SHDR_LEN_OFFSET_32: usize = 46;
pub(super) const ELF_SHDR_LEN_OFFSET_64: usize = 58;
pub(super) const ELF_SHDR_OFFSET_32: usize = 32;
pub(super) const ELF_SHDR_OFFSET_64: usize = 40;
pub(super) const ELF_VERSION: u8 = 1;
#[cfg(feature = "gzip")]
pub(super) const GZIP_HEADER_LEN: usize = 10;
#[cfg(feature = "gzip")]
pub(super) const GZIP_RESERVED_FLAGS: u8 = 0xe0;
pub(super) const GIF_HEADER_LEN: usize = 13;
pub(super) const HTML_ASCII_PROBE_LEN: usize = 16;
pub(super) const ISO_BMFF_MEDIA_BOXES: [&[u8; 4]; 3] = [b"mdat", b"moof", b"moov"];
pub(super) const ISO_BMFF_TOP_LEVEL_BOXES: [&[u8; 4]; 17] = [
    b"bloc", b"emsg", b"free", b"ftyp", b"mdat", b"meco", b"meta", b"mfra", b"moof", b"moov",
    b"pdin", b"prft", b"sidx", b"skip", b"ssix", b"styp", b"uuid",
];
pub(super) const JPEG_MIN_LEN: usize = 128;
pub(super) const KIBIBYTE: usize = 1024;
pub(super) const MEBIBYTE: usize = 1024 * KIBIBYTE;
pub(super) const MOV_REQUIRED_ATOMS: [&[u8; 4]; 8] = [
    b"free", b"mdat", b"wide", b"PICT", b"trak", b"moov", b"mp3\0", b"pnot",
];
pub(super) const MPEG_MARKER: &[u8] = b"\0\0\x01";
pub(super) const MPEG_MIN_LEN: usize = KIBIBYTE;
pub(super) const MPEG_SEARCH_WINDOW: usize = 2 * KIBIBYTE;
pub(super) const MP4_MIN_LEN: usize = KIBIBYTE;
pub(super) const PDF_MIN_PROBE_LEN: usize = 512;
pub(super) const PE_CHARACTERISTIC_DLL: u16 = 0x2000;
pub(super) const PE_CHARACTERISTIC_EXECUTABLE_IMAGE: u16 = 0x0002;
pub(super) const PE_CHARACTERISTIC_SYSTEM: u16 = 0x1000;
pub(super) const PE_CHARACTERISTICS_OFFSET: usize = 18;
pub(super) const PE_COFF_HEADER_LEN: usize = 20;
pub(super) const PE_MAX_LEN: usize = 4 * MEBIBYTE;
pub(super) const PE_MAX_OFFSET: usize = 1000;
pub(super) const PE_MIN_LEN: usize = 512;
pub(super) const PE_OFFSET_LOCATION: usize = 0x3c;
pub(super) const PE_SECTION_HEADER_LEN: usize = 40;
pub(super) const PE_SIGNATURE: &[u8] = b"PE\0\0";
pub(super) const PNG_CHUNK_OVERHEAD: usize = 12;
pub(super) const PNG_DIMENSION_LIMIT: u32 = 3000;
pub(super) const PNG_MIN_LEN: usize = 100;
pub(super) const PNG_SIGNATURE_LEN: usize = 8;
pub(super) const RAR_ENCRYPTED_SEARCH_LEN: usize = 50 * KIBIBYTE;
pub(super) const REG_SIZE_OFFSET: usize = 0x28;
pub(super) const WPD_DOCUMENT_AREA_OFFSET: usize = 4;
pub(super) const WPD_MIN_LEN: usize = 16;
pub(super) const ZIP_CONTENT_TYPES: &[u8] = b"[Content_Types].xml";
pub(super) const ZIP_CENTRAL_HEADER_LEN: usize = 46;
pub(super) const ZIP_CENTRAL_SIGNATURE: &[u8] = b"PK\x01\x02";
pub(super) const ZIP_EOCD_BASE_LEN: usize = 22;
pub(super) const ZIP_EOCD_COMMENT_OFFSET: usize = 20;
pub(super) const ZIP_EOCD_SIGNATURE: &[u8] = b"PK\x05\x06";
pub(super) const ZIP64_EOCD_LOCATOR_LEN: usize = 20;
pub(super) const ZIP64_EOCD_LOCATOR_SIGNATURE: &[u8] = b"PK\x06\x07";
pub(super) const ZIP64_EOCD_MIN_LEN: usize = 56;
pub(super) const ZIP64_EOCD_SIGNATURE: &[u8] = b"PK\x06\x06";
pub(super) const ZIP_LOCAL_HEADER_LEN: usize = 30;
pub(super) const ZIP_LOCAL_SIGNATURE: &[u8] = b"PK\x03\x04";

#[derive(Debug)]
pub struct Recovered<'a> {
    pub bytes: &'a [u8],
    pub comment: String,
    pub next_index: usize,
    pub suffix: String,
    pub write: bool,
}

#[derive(Debug)]
pub(super) struct MovScan {
    pub(super) end: usize,
    pub(super) write: bool,
}

#[derive(Debug)]
pub(super) struct PeDetails {
    pub(super) len: usize,
    pub(super) suffix: &'static str,
}

#[derive(Debug)]
pub(super) enum PdfLinearizedScan {
    Found(usize),
    Missing,
    NeedMore,
    Skip,
}

#[derive(Debug)]
pub(super) struct OleDetails {
    pub(super) len: usize,
    pub(super) suffix: String,
    pub(super) write: bool,
}

#[derive(Debug)]
pub(super) struct OleDirectoryEntry {
    pub(super) kind: u8,
    pub(super) name: String,
    pub(super) size: u64,
    pub(super) start_sector: u32,
}

#[derive(Debug)]
pub(super) struct ZipClassification {
    pub(super) comment: Option<String>,
    pub(super) office_2007: bool,
    pub(super) office_2007_suffix: Option<&'static str>,
    pub(super) open_office: bool,
    pub(super) suffix: &'static str,
}

#[derive(Debug)]
pub(super) enum ZipScan {
    Search {
        classification: ZipClassification,
        search_start: usize,
    },
    SearchCentralDirectory {
        classification: ZipClassification,
        search_start: usize,
    },
    Skip(usize),
}

impl MovScan {
    pub(super) const fn skip(end: usize) -> Self {
        Self { end, write: false }
    }

    pub(super) const fn write(end: usize) -> Self {
        Self { end, write: true }
    }
}

impl OleDirectoryEntry {
    pub(super) fn parse(entry: &[u8]) -> Option<Self> {
        let kind = *entry.get(66)?;
        if kind == 0 {
            return None;
        }
        let name_len = read_le_u16(entry, 64)? as usize;
        if !(2..=64).contains(&name_len) {
            return None;
        }
        let name_bytes = entry.get(..name_len.saturating_sub(2))?;
        let mut units = Vec::with_capacity(name_bytes.len() / 2);
        for chunk in name_bytes.chunks_exact(2) {
            units.push(u16::from_le_bytes(chunk.try_into().ok()?));
        }
        let name = String::from_utf16_lossy(&units);
        let start_sector = read_le_u32(entry, 116)?;
        let size = read_le_u64(entry, 120).or_else(|| read_le_u32(entry, 120).map(u64::from))?;

        Some(Self {
            kind,
            name,
            size,
            start_sector,
        })
    }
}

impl ZipClassification {
    pub(super) const fn new() -> Self {
        Self {
            comment: None,
            office_2007: false,
            office_2007_suffix: None,
            open_office: false,
            suffix: "zip",
        }
    }
}