diff --git a/crates/meta/src/gpx.rs b/crates/meta/src/gpx.rs index a8e8e9071..e63499033 100644 --- a/crates/meta/src/gpx.rs +++ b/crates/meta/src/gpx.rs @@ -96,7 +96,9 @@ struct Pending { /// Parse a GPX document. pub fn parse_gpx(s: &str) -> Result { let mut reader = quick_xml::Reader::from_str(s); - reader.config_mut().check_end_names = false; + // Mismatched end tags must be rejected; otherwise the path stack can + // attribute a point's time or segment to the wrong enclosing element. + reader.config_mut().check_end_names = true; let mut path: Vec> = Vec::new(); let mut seen_gpx = false; let mut log = Tracklog::default(); @@ -169,6 +171,9 @@ pub fn parse_gpx(s: &str) -> Result { if !seen_gpx { return Err(GpxError::NotGpx); } + if !path.is_empty() { + return Err(GpxError::Xml("unclosed GPX elements".into())); + } // stable: points logged at the same second keep their file order log.points.sort_by(|a, b| a.time.total_cmp(&b.time)); Ok(log) @@ -362,6 +367,12 @@ mod tests { fn rejects_non_gpx_and_bad_input() { assert_eq!(parse_gpx(""), Err(GpxError::NotGpx)); assert!(parse_gpx("").is_err()); + // A partial logger write must not be mistaken for a complete track, + // even if it contains a valid timed point. + let point = r#""#; + assert!(matches!(parse_gpx(point), Err(GpxError::Xml(_)))); + let wrong_close = r#""#; + assert!(matches!(parse_gpx(wrong_close), Err(GpxError::Xml(_)))); assert!( parse_gpx("") .unwrap()