use oxml::{Edition, ErrorKind, Limits, MAX_DEPTH, parse, parse_with};
const BILLION_LAUGHS: &str = r#"<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY l0 "haha">
<!ENTITY l1 "&l0;&l0;&l0;&l0;&l0;&l0;&l0;&l0;&l0;&l0;">
<!ENTITY l2 "&l1;&l1;&l1;&l1;&l1;&l1;&l1;&l1;&l1;&l1;">
<!ENTITY l3 "&l2;&l2;&l2;&l2;&l2;&l2;&l2;&l2;&l2;&l2;">
<!ENTITY l4 "&l3;&l3;&l3;&l3;&l3;&l3;&l3;&l3;&l3;&l3;">
<!ENTITY l5 "&l4;&l4;&l4;&l4;&l4;&l4;&l4;&l4;&l4;&l4;">
<!ENTITY l6 "&l5;&l5;&l5;&l5;&l5;&l5;&l5;&l5;&l5;&l5;">
<!ENTITY l7 "&l6;&l6;&l6;&l6;&l6;&l6;&l6;&l6;&l6;&l6;">
<!ENTITY l8 "&l7;&l7;&l7;&l7;&l7;&l7;&l7;&l7;&l7;&l7;">
]>
<lolz>&l8;</lolz>
"#;
fn main() {
println!("default depth limit: {MAX_DEPTH}");
let started = std::time::Instant::now();
match parse(BILLION_LAUGHS) {
Ok(_) => println!("\nbillion laughs: expanded (this would be a bug)"),
Err(e) => println!("\nbillion laughs: {e}"),
}
println!(" refused in {:?}", started.elapsed());
let started = std::time::Instant::now();
let strict = parse_with(BILLION_LAUGHS, Limits::strict());
println!(
" under strict: {} in {:?}",
if strict.is_err() {
"refused"
} else {
"accepted"
},
started.elapsed()
);
let xxe =
r#"<!DOCTYPE d [<!ENTITY x SYSTEM "file:///etc/passwd">]><d>&x;</d>"#;
match parse(xxe) {
Ok(doc) => println!(
"XXE attempt: parsed, &x; is {:?}",
doc.text(doc.root())
),
Err(e) => println!("XXE attempt: {e}"),
}
println!("\n== depth ==");
let deep = format!("{}{}", "<a>".repeat(400), "</a>".repeat(400));
match parse(&deep) {
Ok(_) => println!(" 400 levels: accepted"),
Err(e) if e.kind == ErrorKind::DepthLimitExceeded => {
println!(" 400 levels: refused, {e}");
}
Err(e) => println!(" 400 levels: {e}"),
}
println!("\n== choosing a profile ==");
let shallow = format!("{}{}", "<a>".repeat(20), "</a>".repeat(20));
for (name, limits) in [
("default ", Limits::default()),
("strict ", Limits::strict()),
("permissive", Limits::permissive()),
] {
let ok = parse_with(&shallow, limits).is_ok();
println!(
" {name}: max_depth={:<5} 20 levels ok? {ok}",
limits.max_depth
);
}
println!("\n== one field at a time ==");
let mut tight = Limits::default();
tight.max_depth = 3;
let five = format!("{}{}", "<a>".repeat(5), "</a>".repeat(5));
match parse_with(&five, tight) {
Ok(_) => println!(" 5 levels under max_depth=3: accepted (a bug)"),
Err(e) => println!(" 5 levels under max_depth=3: {e}"),
}
println!("\n== which edition of XML 1.0 ==");
let doc = "<\u{2118}/>"; for (name, edition) in
[("fourth", Edition::Fourth), ("fifth", Edition::Fifth)]
{
let mut limits = Limits::default();
limits.edition = edition;
println!(" {name:<7}: {:?}", parse_with(doc, limits).is_ok());
}
println!("\n== parameter entities ==");
let width_bomb = format!(
"<!DOCTYPE d SYSTEM \"d.dtd\" [<!ENTITY % a \"<!-- {} -->\">\
<!ENTITY % b \"{}\"><!ENTITY % c \"{}\">%c;]><d/>",
"x".repeat(1024),
"%a;".repeat(100),
"%b;".repeat(100),
);
let started = std::time::Instant::now();
match parse(&width_bomb) {
Ok(_) => println!(" width bomb: expanded (this would be a bug)"),
Err(e) => println!(" width bomb: {e} in {:?}", started.elapsed()),
}
println!("\n== streaming ==");
let mut limits = Limits::default();
limits.max_construct_bytes = Some(16 * 1024);
let endless = std::io::Cursor::new(format!("<a>{}", "y".repeat(30_000)));
let mut reader = oxml::stream::Reader::from_reader_with(endless, limits)
.expect("the prolog is fine");
let outcome = loop {
match reader.next_event() {
Ok(Some(_)) => {}
Ok(None) => break "accepted (this would be a bug)".to_owned(),
Err(e) => break e.to_string(),
}
};
println!(" 30,000 bytes of text under a 16 KB cap: {outcome}");
}