use oxml::external::ExternalSource;
use oxml::{Limits, parse, parse_with_external};
const XXE: &str = r#"<!DOCTYPE d [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]><d>&secret;</d>"#;
const SPLIT: &str = r#"<!DOCTYPE letter SYSTEM "letter.dtd">
<letter><body>&salutation; and regards</body></letter>"#;
struct AllowList<'a> {
allowed: &'a [(&'a str, &'a str)],
}
impl ExternalSource for AllowList<'_> {
fn fetch(&self, system_id: &str, _public: Option<&str>) -> Option<&str> {
let hit = self
.allowed
.iter()
.find(|(id, _)| *id == system_id)
.map(|(_, content)| *content);
println!(
" fetch({system_id:?}) -> {}",
match hit {
Some(_) => "supplied",
None => "refused",
}
);
hit
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("== no source supplied ==");
let doc = parse(XXE)?;
println!(" XXE payload parsed, body = {:?}", doc.text(doc.root()));
println!(" (the entity expanded to nothing; no file was opened)");
println!("\n== a slice as a source ==");
let parts: &[(&str, &str)] = &[("greeting.ent", "hello")];
println!(
" fetch(\"greeting.ent\") = {:?}",
parts.fetch("greeting.ent", None)
);
let doc = parse_with_external(
r#"<!DOCTYPE d [<!ENTITY g SYSTEM "greeting.ent">]><d>&g;</d>"#,
Limits::default(),
&parts,
)?;
println!(" parsed body = {:?}", doc.text(doc.root()));
println!("\n== an external subset ==");
let source = AllowList {
allowed: &[("letter.dtd", r#"<!ENTITY salutation "Dear reader">"#)],
};
let doc = parse_with_external(SPLIT, Limits::default(), &source)?;
println!(" body = {:?}", doc.text(doc.root()));
println!("\n== the same document, subset refused ==");
let empty = AllowList { allowed: &[] };
let doc = parse_with_external(SPLIT, Limits::default(), &empty)?;
println!(" body = {:?}", doc.text(doc.root()));
println!(" (an unavailable identifier is not an error — supply the");
println!(" parts you have and leave the rest)");
Ok(())
}