#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, missing_docs)]
mod error;
pub use crate::error::{Error, Result};
mod parser;
use cfb::CompoundFile;
use parser::cp_to_string;
use std::{
cell::RefCell,
io::{Cursor, Read},
path::Path,
};
pub struct Project {
pub information: Information,
pub references: Vec<Reference>,
pub modules: Vec<Module>,
container: RefCell<CompoundFile<Cursor<Vec<u8>>>>,
}
#[derive(Debug)]
pub enum SysKind {
Win16,
Win32,
MacOs,
Win64,
}
#[derive(Debug)]
pub struct ReferenceControl {
name: Option<String>,
libid_original: Option<String>,
libid_twiddled: String,
name_extended: Option<String>,
libid_extended: String,
guid: Vec<u8>, cookie: u32,
}
#[derive(Debug)]
pub struct ReferenceOriginal {
name: Option<String>,
libid_original: String,
}
#[derive(Debug)]
pub struct ReferenceRegistered {
name: Option<String>,
libid: String,
}
#[derive(Debug)]
pub struct ReferenceProject {
name: Option<String>,
libid_absolute: String,
libid_relative: String,
major_version: u32,
minor_version: u16,
}
#[derive(Debug)]
pub enum Reference {
Control(ReferenceControl),
Original(ReferenceOriginal),
Registered(ReferenceRegistered),
Project(ReferenceProject),
}
#[derive(Debug)]
pub struct Information {
pub sys_kind: SysKind,
lcid: u32,
lcid_invoke: u32,
code_page: u16,
name: String,
doc_string: String,
help_file_1: String,
help_context: u32,
lib_flags: u32,
version_major: u32,
version_minor: u16,
constants: String,
}
#[derive(Debug)]
pub enum ModuleType {
Procedural,
DocClsDesigner,
}
#[derive(Debug)]
pub struct Module {
pub name: String,
pub stream_name: String,
pub doc_string: String,
pub text_offset: usize,
pub help_context: u32,
pub module_type: ModuleType,
pub read_only: bool,
pub private: bool,
}
impl Project {
pub fn decompress_stream_from<P>(&self, stream_path: P, offset: usize) -> Result<Vec<u8>>
where
P: AsRef<Path>,
{
let data = self.read_stream(stream_path)?;
let data = parser::decompress(&data[offset..])
.map_err(|_| Error::Decompressor)?
.1;
Ok(data)
}
pub fn list(&self) -> Result<Vec<(String, String)>> {
let mut result = Vec::new();
for entry in self
.container
.borrow()
.walk_storage("/")
.map_err(Error::Cfb)?
{
result.push((
entry.name().to_owned(),
entry.path().to_str().unwrap_or_default().to_owned(),
));
}
Ok(result)
}
pub fn module_source(&self, name: &str) -> Result<String> {
let source_raw = self.module_source_raw(name)?;
let source = cp_to_string(&source_raw, self.information.code_page);
Ok(source)
}
pub fn module_source_raw(&self, name: &str) -> Result<Vec<u8>> {
let module = self
.modules
.iter()
.find(|&module| module.name == name)
.ok_or_else(|| Error::ModuleNotFound(name.to_owned()))?;
let path = format!("/VBA\\{}", &module.stream_name);
let offset = module.text_offset;
let src_code = self.decompress_stream_from(&path, offset)?;
Ok(src_code)
}
pub fn read_stream<P>(&self, stream_path: P) -> Result<Vec<u8>>
where
P: AsRef<Path>,
{
let mut buffer = Vec::new();
self.container
.borrow_mut()
.open_stream(stream_path)
.map_err(Error::Cfb)?
.read_to_end(&mut buffer)
.map_err(Error::Cfb)?;
Ok(buffer)
}
}
pub fn open_project(raw: Vec<u8>) -> Result<Project> {
let cursor = Cursor::new(raw);
let mut container = CompoundFile::open(cursor).map_err(Error::Cfb)?;
const DIR_STREAM_PATH: &str = r#"/VBA\dir"#;
let mut buffer = Vec::new();
container
.open_stream(DIR_STREAM_PATH)
.map_err(Error::Cfb)?
.read_to_end(&mut buffer)
.map_err(Error::Cfb)?;
let (remainder, buffer) = parser::decompress(&buffer).map_err(|_| Error::Decompressor)?;
debug_assert!(remainder.is_empty());
let (remainder, information) =
parser::parse_project_information(&buffer).map_err(|_| Error::Parser)?;
debug_assert_eq!(remainder.len(), 0, "Stream not fully consumed");
Ok(Project {
information: information.information,
references: information.references,
modules: information.modules,
container: RefCell::new(container),
})
}
#[cfg(test)]
mod tests;