use crate::ole::EntrySlice;
use super::{
constants::PropIdNameMap,
decode::{DataType, PtypDecoder},
named_prop::NamedPropertyMap,
storage::StorageType,
};
#[derive(Debug, PartialEq)]
pub struct Stream {
pub parent: StorageType,
pub key: String,
pub value: DataType,
}
impl Stream {
fn extract_id_and_datatype(name: &str) -> Option<(String, String)> {
let tag = name.split("_").filter(|&x| !x.is_empty()).nth(1)?;
if tag.len() < 8 {
return None;
}
let prop_id = String::from("0x") + &tag[..4];
let prop_datatype = String::from("0x") + &tag[tag.len() - 4..];
Some((prop_id, prop_datatype))
}
fn is_stream(name: &str) -> bool {
name.starts_with("__substg1.0")
}
pub fn create(
name: &str,
entry_slice: &mut EntrySlice,
prop_map: &PropIdNameMap,
named_props: &NamedPropertyMap,
parent: &StorageType,
) -> Option<Self> {
if !Self::is_stream(name) {
return None;
}
let (prop_id, prop_datatype) = Self::extract_id_and_datatype(name)?;
let key = prop_map
.get_canonical_name(&prop_id)
.or_else(|| {
let id = u16::from_str_radix(prop_id.trim_start_matches("0x"), 16).ok()?;
named_props.get(id)
})?
.to_string();
let value = PtypDecoder::decode(entry_slice, &prop_datatype).ok()?;
Some(Self {
parent: parent.clone(),
key,
value,
})
}
}
#[cfg(test)]
mod tests {
use super::{
super::constants::PropIdNameMap, super::decode::DataType,
super::named_prop::NamedPropertyMap, super::storage::StorageType, Stream,
};
use crate::ole::Reader;
#[test]
fn test_extract_id_and_datatype() {
let (prop_id, prop_datatype) =
Stream::extract_id_and_datatype("__substg1.0_3701000D").unwrap();
assert_eq!(prop_id, "0x3701");
assert_eq!(prop_datatype, "0x000D");
let (prop_id, prop_datatype) =
Stream::extract_id_and_datatype("__substg1.0_1016102F").unwrap();
assert_eq!(prop_id, "0x1016");
assert_eq!(prop_datatype, "0x102F");
assert!(Stream::extract_id_and_datatype("__substg1.0_").is_none());
assert!(Stream::extract_id_and_datatype("__substg1.0_ABC").is_none());
assert!(Stream::extract_id_and_datatype("").is_none());
}
#[test]
fn test_is_stream() {
assert!(!Stream::is_stream("__recip_version1.0_#00000000"));
assert!(Stream::is_stream("__substg1.0_3701000D"));
}
#[test]
fn test_create_stream() {
let parser = Reader::from_path("data/test_email.msg").unwrap();
let prop_map = PropIdNameMap::init();
let named_props = NamedPropertyMap::default();
let mut slice = parser
.iterate()
.filter(|x| x.name() == "__substg1.0_0C1F001F")
.next()
.and_then(|entry| parser.get_entry_slice(entry).ok())
.unwrap();
let stream = Stream::create(
"__substg1.0_0C1F001F",
&mut slice,
&prop_map,
&named_props,
&StorageType::RootEntry,
);
assert_eq!(
stream,
Some(Stream {
key: "SenderEmailAddress".to_string(),
value: DataType::PtypString("upgrade@asuswebstorage.com".to_string()),
parent: StorageType::RootEntry,
})
);
let mut slice = parser
.iterate()
.filter(|x| x.name() == "__substg1.0_3001001F")
.next()
.and_then(|entry| parser.get_entry_slice(entry).ok())
.unwrap();
let stream = Stream::create(
"__substg1.0_3001001F",
&mut slice,
&prop_map,
&named_props,
&StorageType::Recipient(1),
);
assert_eq!(
stream,
Some(Stream {
key: "DisplayName".to_string(),
value: DataType::PtypString("Sriram Govindan".to_string()),
parent: StorageType::Recipient(1)
})
)
}
#[test]
fn test_create_attachment() {
let parser = Reader::from_path("data/attachment.msg").unwrap();
let prop_map = PropIdNameMap::init();
let mut attachment = parser
.iterate()
.find(|x| x.name() == "__substg1.0_3703001F" && x.parent_node() == Some(7u32))
.and_then(|entry| parser.get_entry_slice(entry).ok())
.unwrap();
let stream = Stream::create(
"__substg1.0_3703001F",
&mut attachment,
&prop_map,
&NamedPropertyMap::default(),
&StorageType::Attachment(0),
);
assert_eq!(
stream,
Some(Stream {
key: "AttachExtension".to_string(),
value: DataType::PtypString(".doc".to_string()),
parent: StorageType::Attachment(0)
})
)
}
}