use crate::Result;
pub fn extract_blkx_data_entries(plist: &str) -> Result<Vec<String>> {
let Some(blkx_pos) = find_key(plist, "blkx") else {
return Err(crate::Error::InvalidImage(
"dmg: resource-fork plist has no <key>blkx</key>".into(),
));
};
let after_key = blkx_pos;
let Some(arr_start) = plist[after_key..].find("<array>") else {
return Err(crate::Error::InvalidImage(
"dmg: <key>blkx</key> is not followed by an <array>".into(),
));
};
let arr_start = after_key + arr_start + "<array>".len();
let Some(arr_end_rel) = plist[arr_start..].find("</array>") else {
return Err(crate::Error::InvalidImage(
"dmg: <key>blkx</key>'s <array> is not closed".into(),
));
};
let arr_end = arr_start + arr_end_rel;
let arr_body = &plist[arr_start..arr_end];
let mut out = Vec::new();
let mut cursor = 0usize;
while cursor < arr_body.len() {
let Some(open_rel) = arr_body[cursor..].find("<data>") else {
break;
};
let open = cursor + open_rel + "<data>".len();
let Some(close_rel) = arr_body[open..].find("</data>") else {
return Err(crate::Error::InvalidImage(
"dmg: <data> element inside blkx array is not closed".into(),
));
};
let close = open + close_rel;
out.push(arr_body[open..close].to_string());
cursor = close + "</data>".len();
}
Ok(out)
}
fn find_key(s: &str, name: &str) -> Option<usize> {
let bytes = s.as_bytes();
let mut i = 0usize;
while i + 5 <= bytes.len() {
let rel = s[i..].find("<key>")?;
let body_start = i + rel + "<key>".len();
let body_end_rel = s[body_start..].find("</key>")?;
let body_end = body_start + body_end_rel;
let body = s[body_start..body_end].trim();
if body == name {
return Some(body_end + "</key>".len());
}
i = body_end + "</key>".len();
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_blkx_data_in_order() {
let plist = r#"<?xml version="1.0"?>
<plist version="1.0">
<dict>
<key>resource-fork</key>
<dict>
<key>blkx</key>
<array>
<dict>
<key>Attributes</key><string>0x0050</string>
<key>Data</key>
<data>AAAA</data>
</dict>
<dict>
<key>Data</key>
<data>BBBB</data>
</dict>
</array>
</dict>
</dict>
</plist>"#;
let v = extract_blkx_data_entries(plist).unwrap();
assert_eq!(v, vec!["AAAA".to_string(), "BBBB".to_string()]);
}
#[test]
fn errors_when_blkx_missing() {
let plist = "<plist><dict><key>other</key><string>x</string></dict></plist>";
assert!(extract_blkx_data_entries(plist).is_err());
}
#[test]
fn errors_when_data_unclosed() {
let plist =
r#"<plist><dict><key>blkx</key><array><dict><data>oops</dict></array></dict></plist>"#;
assert!(extract_blkx_data_entries(plist).is_err());
}
}