io: hostile-input tests for the SVG and DXF readers (#469) (#1083)

Adds tests that feed truncated, malformed and deeply nested SVG and DXF input to the readers and check that none of it panics.
This commit is contained in:
Yuu authored and GitHub committed 2026-10-14 13:40:25 -04:00
1 parent 94faf9ce1b
commit f425089983
2 files changed
+69

No files matched your search

+33
View File
@@ -455,6 +455,39 @@ pub fn write_dxf(geom: &[Geom2]) -> String {
mod tests {
use super::*;
/// A DXF is group codes and values the user did not write, and blocks recurse. None of these
/// may panic (solidcraft#469).
#[test]
fn hostile_dxf_never_panics() {
let good = write_dxf(&[Geom2::Line(Vec2::ZERO, Vec2::new(10.0, 0.0)), Geom2::Circle(Vec2::new(5.0, 5.0), 2.0)]);
let mut cases: Vec<String> = vec![
String::new(),
"0".into(),
"0\n".into(),
"garbage".into(),
"0\nSECTION\n2\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nEOF\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n4\n10\n0\n20\n0\n0\nENDSEC\n0\nEOF\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n-1\n0\nENDSEC\n0\nEOF\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n99999999\n0\nENDSEC\n0\nEOF\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nCIRCLE\n10\nnan\n20\ninf\n40\n-1\n0\nENDSEC\n0\nEOF\n".into(),
"0\nSECTION\n2\nENTITIES\n0\nCIRCLE\n10\n1e400\n20\n1e400\n40\n1e400\n0\nENDSEC\n0\nEOF\n".into(),
format!("0\nSECTION\n2\nBLOCKS\n{}0\nENDSEC\n0\nEOF\n", "0\nBLOCK\n2\nb\n0\nENDBLK\n".repeat(300)),
"0\nSECTION\n2\nBLOCKS\n0\nBLOCK\n2\na\n1\nINSERT\n2\na\n0\nENDBLK\n0\nENDSEC\n0\nEOF\n".into(),
format!("0\nSECTION\n2\nENTITIES\n{}0\nENDSEC\n0\nEOF\n", "0\nLINE\n10\n0\n20\n0\n11\n1\n21\n1\n".repeat(5000)),
];
for cut in (0..good.len()).step_by(11) {
cases.push(good.get(..cut).unwrap_or("").to_string());
}
for (from, to) in [("0\n", "1\n"), ("SECTION", "SECTI0N"), ("EOF", "EOFF")] {
cases.push(good.replacen(from, to, 3));
}
for c in &cases {
let r = std::panic::catch_unwind(|| read_dxf(c));
assert!(r.is_ok(), "panicked on {:?}", c.get(..c.len().min(200)));
}
}
#[test]
fn round_trip_and_polyline_bulges() {
let g = vec![
+36
View File
@@ -498,6 +498,42 @@ pub fn read_svg(text: &str) -> Result<Vec<Geom2>> {
mod tests {
use super::*;
/// The parser recurses over groups and reads numbers out of a document the user did not write,
/// so none of these may panic: `catch_unwind` turns one into a failed test, the way
/// `hostile_files_never_panic` does for STEP (solidcraft#469).
#[test]
fn hostile_svg_never_panics() {
let good = r#"<svg xmlns="http://www.w3.org/2000/svg" width="100mm" height="50mm" viewBox="0 0 100 50">
<g transform="translate(10 5)"><rect x="1" y="2" width="3" height="4"/><path d="M0 0 L5 5 Z"/></g>
</svg>"#;
let mut cases: Vec<String> = vec![
String::new(),
"<svg".into(),
"<svg><".into(),
"<svg><path d=".into(),
"<svg><path d='M 1 1 L'/></svg>".into(),
"<svg><path d='M nan nan L inf inf'/></svg>".into(),
"<svg><path d='M 1e400 1e400 L 1 1'/></svg>".into(),
"<svg><rect width='-1' height='-1'/></svg>".into(),
"<svg><use href='#a'/><g id='a'><use href='#a'/></g></svg>".into(),
format!("<svg>{}{}</svg>", "<g>".repeat(300), "</g>".repeat(300)),
format!("<svg>{}</svg>", "<g>".repeat(300)),
"<svg><![CDATA[<g>]]></svg>".into(),
"<?xml version='1.0'?><!DOCTYPE svg [<!ENTITY x SYSTEM 'file:///etc/passwd'>]><svg>&x;</svg>".into(),
format!("<svg><path d='{}'/></svg>", "L 1 1 ".repeat(20_000)),
];
for cut in (0..good.len()).step_by(13) {
cases.push(good.get(..cut).unwrap_or("").to_string());
}
for (from, to) in [("10mm", "-10mm"), ("0 0 100 50", "0 0 0 0"), ("<g ", "<x "), ("rect", "rectx")] {
cases.push(good.replacen(from, to, 3));
}
for c in &cases {
let r = std::panic::catch_unwind(|| read_svg(c));
assert!(r.is_ok(), "panicked on {:?}", c.get(..c.len().min(200)));
}
}
#[test]
fn shapes_paths_and_units() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="100mm" height="50mm" viewBox="0 0 100 50">