use super::super::package::Result;
use super::fib::FileInformationBlock;
use crate::ole::binary::PlcfParser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FieldType {
EmbeddedObject = 58,
Hyperlink = 88,
PageRef = 37,
Other(u8),
}
impl From<u8> for FieldType {
fn from(value: u8) -> Self {
match value {
58 => FieldType::EmbeddedObject,
88 => FieldType::Hyperlink,
37 => FieldType::PageRef,
other => FieldType::Other(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldBoundary {
Begin = 0x13,
Separator = 0x14,
End = 0x15,
}
#[derive(Debug, Clone)]
pub struct FieldDescriptor {
pub boundary_type: u8, pub field_type: u8, pub flags: u8, }
impl FieldDescriptor {
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 2 {
return None;
}
let byte0 = bytes[0];
let byte1 = bytes[1];
let boundary_type = byte0 & 0x1F; let flags = (byte0 >> 5) & 0x07; let field_type = byte1;
Some(Self {
boundary_type,
field_type,
flags,
})
}
pub fn is_begin(&self) -> bool {
self.boundary_type == 0x13 }
pub fn is_separator(&self) -> bool {
self.boundary_type == 0x14 }
pub fn is_end(&self) -> bool {
self.boundary_type == 0x15 }
}
#[derive(Debug, Clone)]
pub struct Field {
pub start_cp: u32,
pub separator_cp: Option<u32>,
pub end_cp: u32,
pub field_type: FieldType,
pub has_separator: bool,
}
impl Field {
pub fn code_range(&self) -> (u32, u32) {
let end = self.separator_cp.unwrap_or(self.end_cp);
(self.start_cp + 1, end)
}
pub fn result_range(&self) -> Option<(u32, u32)> {
self.separator_cp.map(|sep| (sep + 1, self.end_cp))
}
pub fn is_embedded_object(&self) -> bool {
self.field_type == FieldType::EmbeddedObject
}
}
pub struct FieldsTable {
main_document_fields: Vec<Field>,
}
impl FieldsTable {
pub fn parse(fib: &FileInformationBlock, table_stream: &[u8]) -> Result<Self> {
let main_fields = if let Some((offset, length)) = fib.get_table_pointer(11) {
if length > 0 && (offset as usize) < table_stream.len() {
let fields_data = &table_stream[offset as usize..];
let fields_len = length.min((table_stream.len() - offset as usize) as u32) as usize;
if fields_len >= 4 {
Self::parse_fields_plcf(&fields_data[..fields_len])
} else {
Vec::new()
}
} else {
Vec::new()
}
} else {
Vec::new()
};
Ok(Self {
main_document_fields: main_fields,
})
}
fn parse_fields_plcf(data: &[u8]) -> Vec<Field> {
if data.len() < 6 {
return Vec::new();
}
let plcf = PlcfParser::parse(data, 2);
if plcf.is_none() {
return Vec::new();
}
let plcf = plcf.unwrap();
let mut fields = Vec::new();
let mut i = 0;
while i < plcf.count() {
if let Some((cp, cp_end)) = plcf.range(i)
&& let Some(fld_data) = plcf.property(i)
&& let Some(descriptor) = FieldDescriptor::from_bytes(fld_data)
&& descriptor.is_begin() {
let start_cp = cp;
let field_type = FieldType::from(descriptor.field_type);
let mut separator_cp = None;
let mut end_cp = cp_end;
let mut has_separator = false;
let mut j = i + 1;
while j < plcf.count() {
if let Some((sep_cp, _)) = plcf.range(j)
&& let Some(next_fld) = plcf.property(j)
&& let Some(next_desc) = FieldDescriptor::from_bytes(next_fld) {
if next_desc.is_separator() {
separator_cp = Some(sep_cp);
has_separator = true;
} else if next_desc.is_end() {
end_cp = sep_cp;
i = j; break;
}
}
j += 1;
}
fields.push(Field {
start_cp,
separator_cp,
end_cp,
field_type,
has_separator,
});
}
i += 1;
}
fields
}
pub fn main_document_fields(&self) -> &[Field] {
&self.main_document_fields
}
pub fn find_field_at_position(&self, cp: u32) -> Option<&Field> {
self.main_document_fields
.iter()
.find(|f| f.start_cp <= cp && cp <= f.end_cp)
}
pub fn get_embedded_object_fields(&self) -> Vec<&Field> {
self.main_document_fields
.iter()
.filter(|f| f.is_embedded_object())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_descriptor_parsing() {
let bytes = [0x13, 58];
let desc = FieldDescriptor::from_bytes(&bytes).unwrap();
assert!(desc.is_begin());
assert_eq!(desc.field_type, 58);
let bytes = [0x14, 0];
let desc = FieldDescriptor::from_bytes(&bytes).unwrap();
assert!(desc.is_separator());
let bytes = [0x15, 0];
let desc = FieldDescriptor::from_bytes(&bytes).unwrap();
assert!(desc.is_end());
}
#[test]
fn test_field_type_conversion() {
assert_eq!(FieldType::from(58), FieldType::EmbeddedObject);
assert_eq!(FieldType::from(88), FieldType::Hyperlink);
match FieldType::from(99) {
FieldType::Other(99) => {},
_ => panic!("Expected Other(99)"),
}
}
}