use log::warn;
use quick_xml::escape::unescape;
use std::io::Cursor;
use zip::ZipArchive;
use crate::utils::zip_read::read_zip_entry_to_string_limited;
pub(crate) fn has_namespace(name: &[u8], namespace: &[u8]) -> bool {
name.windows(namespace.len()).any(|w| w == namespace)
}
pub(crate) fn open_office_archive<'a>(
content: &'a [u8],
file_path: &str,
) -> Result<ZipArchive<Cursor<&'a [u8]>>, zip::result::ZipError> {
let cursor = Cursor::new(content);
ZipArchive::new(cursor).map_err(|e| {
warn!("Failed to open Office file as ZIP archive {file_path}: {e:?}");
e
})
}
const XML_ENTITIES: &[(&str, &str)] = &[
("&", "&"),
("<", "<"),
(">", ">"),
(""", "\""),
("'", "'"),
];
pub(crate) fn decode_xml_entities_fallback(text: &str) -> String {
let mut result = text.to_string();
for (entity, replacement) in XML_ENTITIES {
result = result.replace(entity, replacement);
}
result
}
pub(crate) fn decode_xml_entities(text: &str) -> String {
match unescape(text) {
Ok(decoded) => decoded.into_owned(),
Err(_) => decode_xml_entities_fallback(text),
}
}
pub(crate) fn read_xml_from_archive<F>(
archive: &mut ZipArchive<Cursor<&[u8]>>,
file_path: &str,
file_type: &str,
stats_file_path: &str,
max_uncompressed_bytes: usize,
mut extractor: F,
) where
F: FnMut(&str),
{
let container = format!("{file_type} {stats_file_path}");
match archive.by_name(file_path) {
Ok(file) => match read_zip_entry_to_string_limited(
file,
max_uncompressed_bytes,
file_path,
&container,
) {
Ok(xml_content) => extractor(&xml_content),
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {}
Err(e) => warn!("Failed to read {file_path} from {file_type} {stats_file_path}: {e}"),
},
Err(e) => {
warn!("{file_path} not found in {file_type} {stats_file_path}: {e:?}");
}
}
}