oletools_rs 0.1.0

Rust port of oletools — analysis tools for Microsoft Office files (VBA macros, DDE, OLE objects, RTF exploits)
Documentation
//! VBA Module — represents a single VBA code module extracted from an OLE stream.

use crate::common::codepages;
use crate::error::Result;
use crate::vba::decompressor;
use crate::vba::project::ModuleType;

/// A fully extracted VBA module with its source code.
#[derive(Debug, Clone)]
pub struct VbaModule {
    /// Module name (e.g., "ThisDocument", "Module1").
    pub name: String,
    /// Stream name within the VBA storage.
    pub stream_name: String,
    /// Decompressed VBA source code.
    pub source_code: String,
    /// Module type (Standard, Class, Document, Form).
    pub module_type: ModuleType,
    /// Whether the module is marked as private.
    pub is_private: bool,
}

impl VbaModule {
    /// Extract the VBA source code from a raw stream.
    ///
    /// `stream_data` is the full content of the module stream.
    /// `text_offset` is the offset where compressed source code begins.
    /// `codepage` is the codepage used for string decoding.
    pub fn extract_source(
        stream_data: &[u8],
        text_offset: u32,
        codepage: u16,
    ) -> Result<String> {
        let offset = text_offset as usize;
        if offset >= stream_data.len() {
            return Ok(String::new());
        }

        let compressed = &stream_data[offset..];
        let decompressed = decompressor::decompress_stream(compressed)?;

        let source = if let Some(encoding) = codepages::codepage_to_encoding(codepage) {
            let (decoded, _, _) = encoding.decode(&decompressed);
            decoded.into_owned()
        } else {
            String::from_utf8_lossy(&decompressed).into_owned()
        };

        Ok(source)
    }
}

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

    #[test]
    fn test_extract_source_empty_stream() {
        let result = VbaModule::extract_source(&[], 0, 1252);
        // Empty stream should return an error (no signature byte)
        assert!(result.is_err() || result.unwrap().is_empty());
    }

    #[test]
    fn test_extract_source_offset_beyond_stream() {
        let data = vec![0x01, 0x02, 0x03];
        let result = VbaModule::extract_source(&data, 100, 1252);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "");
    }
}