use crate::error::Result;
use crate::msodde::field_parser::DdeField;
use crate::ole::container::OleFile;
const SUPBOOK_RECORD_TYPE: u16 = 0x01AE;
pub fn process_xls(data: &[u8]) -> Result<Vec<DdeField>> {
let mut ole = OleFile::from_bytes(data)?;
let streams = ole.list_streams();
let mut fields = Vec::new();
let workbook_stream = streams
.iter()
.find(|s| {
let lower = s.to_lowercase();
lower.ends_with("workbook") || lower.ends_with("book")
})
.cloned();
if let Some(stream_path) = workbook_stream
&& let Ok(stream_data) = ole.open_stream(&stream_path) {
let extracted = scan_supbook_records(&stream_data);
fields.extend(extracted);
}
Ok(fields)
}
fn scan_supbook_records(data: &[u8]) -> Vec<DdeField> {
let mut fields = Vec::new();
let mut pos = 0;
let len = data.len();
while pos + 4 <= len {
let record_type = u16::from_le_bytes([data[pos], data[pos + 1]]);
let record_size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if pos + record_size > len {
break;
}
if record_type == SUPBOOK_RECORD_TYPE && record_size >= 4 {
let record_data = &data[pos..pos + record_size];
let ctab = u16::from_le_bytes([record_data[0], record_data[1]]);
let cch = u16::from_le_bytes([record_data[2], record_data[3]]);
if cch > 0 && record_size > 4 {
let path_start = 4;
let is_dde = if path_start < record_data.len() {
record_data[path_start] == 0x01
} else {
false
};
if is_dde {
let app_name = extract_dde_app_name(record_data, path_start + 1, cch as usize);
fields.push(DdeField {
field_instruction: format!(
"SupBook DDE link (ctab={}, cch={})",
ctab, cch
),
command: app_name.clone(),
source: app_name,
quote_decoded: None,
});
}
}
}
pos += record_size;
}
fields
}
fn extract_dde_app_name(data: &[u8], start: usize, max_len: usize) -> String {
let end = std::cmp::min(start + max_len, data.len());
if start >= end {
return String::new();
}
let bytes = &data[start..end];
let name: String = bytes
.iter()
.take_while(|&&b| b != 0 && b != 0x01)
.map(|&b| b as char)
.collect();
name
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_supbook_dde() {
let mut data = Vec::new();
data.extend_from_slice(&SUPBOOK_RECORD_TYPE.to_le_bytes());
let mut body = Vec::new();
body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&4u16.to_le_bytes()); body.push(0x01); body.extend_from_slice(b"cmd");
data.extend_from_slice(&(body.len() as u16).to_le_bytes());
data.extend_from_slice(&body);
let fields = scan_supbook_records(&data);
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].source, "cmd");
}
#[test]
fn test_scan_supbook_non_dde() {
let mut data = Vec::new();
data.extend_from_slice(&SUPBOOK_RECORD_TYPE.to_le_bytes());
let mut body = Vec::new();
body.extend_from_slice(&1u16.to_le_bytes()); body.extend_from_slice(&5u16.to_le_bytes()); body.push(0x00); body.extend_from_slice(b"test");
data.extend_from_slice(&(body.len() as u16).to_le_bytes());
data.extend_from_slice(&body);
let fields = scan_supbook_records(&data);
assert!(fields.is_empty());
}
#[test]
fn test_scan_empty() {
let fields = scan_supbook_records(&[]);
assert!(fields.is_empty());
}
#[test]
fn test_scan_truncated() {
let fields = scan_supbook_records(&[0x01, 0x02]);
assert!(fields.is_empty());
}
}