oletools_rs 0.1.0

Rust port of oletools — analysis tools for Microsoft Office files (VBA macros, DDE, OLE objects, RTF exploits)
Documentation
//! OLE directory listing (oledir).
//!
//! Displays the directory tree of an OLE file with details about
//! each entry (type, size, CLSID).

use crate::error::Result;
use crate::ole::clsid;
use crate::ole::container::OleFile;

/// An entry in the OLE directory listing.
#[derive(Debug, Clone)]
pub struct DirectoryEntry {
    /// Full path within the OLE container.
    pub path: String,
    /// Entry type: "Root Entry", "Storage", or "Stream".
    pub entry_type: EntryType,
    /// Size in bytes.
    pub size: u64,
    /// CLSID string (if set).
    pub clsid: Option<String>,
    /// Human-readable CLSID description (if known).
    pub clsid_description: Option<&'static str>,
}

/// Type of OLE directory entry.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryType {
    RootEntry,
    Storage,
    Stream,
}

impl std::fmt::Display for EntryType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EntryType::RootEntry => write!(f, "Root Entry"),
            EntryType::Storage => write!(f, "Storage"),
            EntryType::Stream => write!(f, "Stream"),
        }
    }
}

/// List all directory entries in an OLE file.
pub fn list_directory(ole: &OleFile) -> Result<Vec<DirectoryEntry>> {
    let entries = ole.list_entries();
    let mut result = Vec::with_capacity(entries.len() + 1);

    // Add root entry
    let root_clsid = ole.root_clsid();
    let root_clsid_str = if root_clsid.is_nil() {
        None
    } else {
        Some(root_clsid.to_string())
    };
    let root_desc = clsid::lookup_clsid(&root_clsid);

    result.push(DirectoryEntry {
        path: "/".to_string(),
        entry_type: EntryType::RootEntry,
        size: 0,
        clsid: root_clsid_str,
        clsid_description: root_desc,
    });

    for entry in entries {
        let entry_type = if entry.is_stream {
            EntryType::Stream
        } else {
            EntryType::Storage
        };

        let clsid_str = if entry.clsid.is_nil() {
            None
        } else {
            Some(entry.clsid.to_string())
        };

        let clsid_desc = if entry.clsid.is_nil() {
            None
        } else {
            clsid::lookup_clsid(&entry.clsid)
        };

        result.push(DirectoryEntry {
            path: entry.path,
            entry_type,
            size: entry.size,
            clsid: clsid_str,
            clsid_description: clsid_desc,
        });
    }

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_entry_type_display() {
        assert_eq!(EntryType::RootEntry.to_string(), "Root Entry");
        assert_eq!(EntryType::Storage.to_string(), "Storage");
        assert_eq!(EntryType::Stream.to_string(), "Stream");
    }
}