peview 0.1.1

A minimal and fast zero-copy parser for the PE32+ file format.
Documentation
use crate::{dir::*, error::*, header::*, mem::*, section::Section};
use alloc::vec::Vec;
use core::mem;

/// View of a PE32+ file
pub struct PeView<'a> {
    dos_header: &'a DosHeader,
    nt_header: &'a NtHeader,
    sections: Vec<Section<'a>>,
}

impl<'a> PeView<'a> {
    /// Creates a [`PeView`] of a PE32+ file by parsing and validating the
    /// specified raw byte buffer representing it.
    ///
    /// # Errors
    ///
    /// This function will return an error if the byte buffer does not
    /// represent a valid and complete PE32+ file.
    pub fn parse(bytes: &'a [u8]) -> Result<Self> {
        // Create an interface for easily reading the buffer
        let mut data = ByteReader::new(bytes);

        // Read and validate both the DOS- and NT-header
        let dos_header = data.read::<DosHeader>()?.validate()?;
        let nt_header = data
            .skip(SkipPos::Rel(dos_header.e_lfanew as _))
            .read::<NtHeader>()?
            .validate()?;

        // Jump to the RVA of the first section header
        data.skip(SkipPos::Rel(
            dos_header.e_lfanew as usize
                + mem::size_of::<u32>()
                + mem::size_of::<FileHeader>()
                + nt_header.file_header.size_of_optional_header as usize,
        ));

        // Allocate a vector for holding the sections
        let mut sections =
            Vec::with_capacity(nt_header.file_header.num_of_sections as _);

        // Iterate over each section header and save its section after validation
        for _ in 0..nt_header.file_header.num_of_sections {
            sections.push(Section::parse(
                bytes,
                data.read::<SectionHeader>()?
                    .validate(&nt_header.optional_header)?,
            )?)
        }

        Ok(Self {
            dos_header,
            nt_header,
            sections,
        })
    }

    /// Returns a reference to the DOS-header of this [`PeView`].
    pub fn dos_header(&self) -> &DosHeader {
        self.dos_header
    }

    /// Returns a reference to the NT-header of this [`PeView`].
    pub fn nt_header(&self) -> &NtHeader {
        self.nt_header
    }

    /// Returns a reference to the sections of this [`PeView`].
    pub fn sections(&self) -> &[Section] {
        self.sections.as_ref()
    }

    /// Returns a reference to a single section of this [`PeView`],
    /// who's raw data contains the specified RVA.
    ///
    /// Returns [`None`] if no such section is found.
    pub fn section_by_rva(&self, rva: u32) -> Option<&Section> {
        self.sections
            .iter()
            .find(|s| !s.empty() && s.contains_rva(rva))
    }

    /// Returns a reference to a single section of this [`PeView`],
    /// who's name is equal to the one specified.
    ///
    /// Returns [`None`] if no such section is found.
    pub fn section_by_name(&self, name: &str) -> Option<&Section> {
        self.sections.iter().find(|s| s.name() == name)
    }

    /// Checks if specified flag is contained in the file headers characteristics.
    pub fn has_flag(&self, flag: FileFlags) -> bool {
        self.nt_header.file_header.characteristics & flag as u16 == 1
    }

    /// Returns a reference to the data directory of the specified type.
    ///
    /// Returns [`None`] if the data directory is empty
    pub fn directory(&self, typ: DataDirectoryType) -> Option<&DataDirectory> {
        let directory =
            &self.nt_header.optional_header.data_directories[typ as usize];

        if directory.size > 0 {
            Some(directory)
        } else {
            None
        }
    }

    /// Returns the export table
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    /// - The [`DataDirectoryType::ExportTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
    /// - The .edata section is empty or not found ([`Error::SectionEmpty`])
    /// - The export table is malformed
    ///
    /// As soon as the iterator returned by this function returns an error,
    /// it is in an unrecoverable invalid state and further usage will result
    /// in potentially invalid results
    pub fn exports(&self) -> Result<ExportTable> {
        self.directory_table(DataDirectoryType::ExportTable)
    }

    /// Returns the import table
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    /// - The [`DataDirectoryType::ImportTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
    /// - The .idata section is empty or not found ([`Error::SectionEmpty`])
    /// - The import table is malformed
    ///
    /// As soon as the iterator returned by this function returns an error,
    /// it is in an unrecoverable invalid state and further usage will result
    /// in potentially invalid results
    pub fn imports(&self) -> Result<ImportTable> {
        self.directory_table(DataDirectoryType::ImportTable)
    }

    /// Returns the base relocation table
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    /// - The [`DataDirectoryType::RelocationTable`] data directory is empty ([`Error::DataDirectoryEmpty`])
    /// - The .reloc section is empty or not found ([`Error::SectionEmpty`])
    /// - The base relocation table is malformed
    ///
    /// As soon as the iterator returned by this function returns an error,
    /// it is in an unrecoverable invalid state and further usage will result
    /// in potentially invalid results
    pub fn relocations(&self) -> Result<RelocationTable> {
        self.directory_table(DataDirectoryType::RelocationTable)
    }

    /// Internal method for abstracting over the process of getting
    /// parsed tables for the raw data contained in the specified data directories
    fn directory_table<T>(&'a self, typ: DataDirectoryType) -> Result<T>
    where
        T: DataDirectoryTable<'a>,
    {
        // Get the data directory
        let directory = self.directory(typ).ok_or(Error::DataDirectoryEmpty)?;
        // Get the raw data of the required section
        let data = self
            .section_by_rva(directory.rva)
            .ok_or(Error::SectionEmpty)?
            .data()
            .as_ref()
            .unwrap();

        // Validate the length of the sections raw data
        if data.bytes().len() <= directory.size as usize {
            return Err(Error::InsufficientBuffer);
        }

        // Get a slice of the sections raw data which contains the required table
        let bytes = match typ {
            DataDirectoryType::ExportTable | DataDirectoryType::RelocationTable => {
                &data.bytes_at(directory.rva as _)?[..directory.size as _]
            }
            DataDirectoryType::ImportTable => data.bytes_at(directory.rva as _)?,
            _ => unimplemented!(),
        };

        // Return the actual table
        Ok(T::new(bytes, directory))
    }
}