use std::sync::LazyLock;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct DdeField {
pub field_instruction: String,
pub command: String,
pub source: String,
pub quote_decoded: Option<String>,
}
static RE_QUOTE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?i)^\s*QUOTE\s+([\d\s]+)\s*$").unwrap()
});
const SAFE_FIELDS: &[&str] = &[
"ADDIN", "ADVANCE", "ASK", "AUTHOR", "AUTONUM", "AUTONUMLGL",
"AUTONUMOUT", "AUTOTEXT", "AUTOTEXTLIST", "BARCODE", "BIBLIOGRAPHY",
"BIDIOUTLINE", "BOOKMARK", "CITATION", "COMMENTS", "COMPARE",
"CREATEDATE", "DATABASE", "DATE", "DISPLAYBARCODE", "DOCPROPERTY",
"DOCVARIABLE", "EDITTIME", "EQ", "FILENAME", "FILESIZE", "FILLIN",
"FORMCHECKBOX", "FORMDROPDOWN", "FORMTEXT", "GLOSSARY", "GOTOBUTTON",
"GREETINGLINE", "HYPERLINK", "IF", "IMPORT", "INCLUDE",
"INCLUDEPICTURE", "INCLUDETEXT", "INDEX", "INFO", "KEYWORDS",
"LASTSAVEDBY", "LISTNUM", "MACROBUTTON", "MERGEBARCODE",
"MERGEFIELD", "MERGEFORMAT", "MERGEREC", "MERGESEQ", "NEXT",
"NEXTIF", "NOTEREF", "NUMCHARS", "NUMPAGES", "NUMWORDS", "PAGE",
"PAGEREF", "PRINT", "PRINTDATE", "PRIVATE", "RD", "REF",
"REVNUM", "SAVEDATE", "SECTION", "SECTIONPAGES", "SEQ", "SET",
"SHAPE", "SKIPIF", "STYLEREF", "SUBJECT", "SYMBOL", "TA",
"TEMPLATE", "TIME", "TITLE", "TOA", "TOC", "USERADDRESS",
"USERINITIALS", "USERNAME", "XE",
];
pub fn is_dde_field(instruction: &str) -> bool {
let trimmed = instruction.trim();
if trimmed.is_empty() {
return false;
}
let first_word = trimmed.split_whitespace().next().unwrap_or("");
let upper = first_word.to_uppercase();
if SAFE_FIELDS.iter().any(|&s| s == upper) {
return false;
}
if upper == "DDE" || upper == "DDEAUTO" {
return true;
}
false
}
pub fn parse_dde_field(instruction: &str) -> Option<DdeField> {
let trimmed = instruction.trim();
if trimmed.is_empty() {
return None;
}
let first_word = trimmed
.split_whitespace()
.next()
.unwrap_or("")
.to_uppercase();
if first_word != "DDE" && first_word != "DDEAUTO" {
return None;
}
let command = trimmed
.strip_prefix(&first_word)
.or_else(|| {
let lower = trimmed.to_lowercase();
let prefix = first_word.to_lowercase();
if lower.starts_with(&prefix) {
Some(&trimmed[prefix.len()..])
} else {
None
}
})
.unwrap_or("")
.trim()
.to_string();
let source = command
.split_whitespace()
.next()
.unwrap_or("")
.trim_matches('"')
.to_string();
Some(DdeField {
field_instruction: trimmed.to_string(),
command: command.clone(),
source,
quote_decoded: None,
})
}
pub fn decode_quote_field(instruction: &str) -> Option<String> {
let caps = RE_QUOTE.captures(instruction)?;
let numbers_str = caps.get(1)?.as_str();
let decoded: String = numbers_str
.split_whitespace()
.filter_map(|n| {
n.parse::<u32>()
.ok()
.and_then(char::from_u32)
})
.collect();
if decoded.is_empty() {
None
} else {
Some(decoded)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_dde_field() {
assert!(is_dde_field("DDE Excel test"));
assert!(is_dde_field("DDEAUTO cmd.exe /c calc"));
assert!(!is_dde_field("DATE"));
assert!(!is_dde_field("PAGE"));
assert!(!is_dde_field("HYPERLINK http://example.com"));
assert!(!is_dde_field(""));
}
#[test]
fn test_parse_dde_field() {
let field = parse_dde_field("DDEAUTO cmd.exe /c calc").unwrap();
assert_eq!(field.field_instruction, "DDEAUTO cmd.exe /c calc");
assert_eq!(field.command, "cmd.exe /c calc");
assert_eq!(field.source, "cmd.exe");
}
#[test]
fn test_parse_dde_field_with_quotes() {
let field = parse_dde_field(r#"DDE "Excel" "Sheet1!R1C1""#).unwrap();
assert_eq!(field.source, "Excel");
}
#[test]
fn test_parse_non_dde() {
assert!(parse_dde_field("DATE").is_none());
assert!(parse_dde_field("").is_none());
}
#[test]
fn test_decode_quote_field() {
let result = decode_quote_field("QUOTE 67 68 69").unwrap();
assert_eq!(result, "CDE");
}
#[test]
fn test_decode_quote_hello() {
let result = decode_quote_field("QUOTE 72 101 108 108 111").unwrap();
assert_eq!(result, "Hello");
}
#[test]
fn test_decode_quote_empty() {
assert!(decode_quote_field("QUOTE").is_none());
assert!(decode_quote_field("NOT A QUOTE").is_none());
}
#[test]
fn test_safe_fields_coverage() {
for &field in SAFE_FIELDS {
assert!(!is_dde_field(field), "{field} should be safe");
}
}
}