diff --git a/crates/xfdf/src/lib.rs b/crates/xfdf/src/lib.rs
index 219b0de81..bc3ea01f4 100644
--- a/crates/xfdf/src/lib.rs
+++ b/crates/xfdf/src/lib.rs
@@ -354,7 +354,7 @@ fn xfdf_annots(doc: &Document, out: &mut String) {
&& le.len() == 2
{
let name = |o: &Object| o.as_name().map(|x| String::from_utf8_lossy(x).into_owned()).unwrap_or_else(|| "None".into());
- let _ = write!(attrs, " head=\"{}\" tail=\"{}\"", name(&le[0]), name(&le[1]));
+ let _ = write!(attrs, " head=\"{}\" tail=\"{}\"", esc(&name(&le[0])), esc(&name(&le[1])));
}
// A callout: its leader path, text-box inset, intent and (single-name) line ending.
let cl = nums_of(doc, d.get(b"CL"));
diff --git a/crates/xfdf/src/tests.rs b/crates/xfdf/src/tests.rs
index c98fab06a..a93f70c70 100644
--- a/crates/xfdf/src/tests.rs
+++ b/crates/xfdf/src/tests.rs
@@ -1758,3 +1758,26 @@ fn an_imported_redaction_rect_with_a_non_number_is_not_used() {
let mark = imported_redact_mark(fdf);
assert!(mark.get(b"QuadPoints").is_none(), "quads were drawn from a malformed /Rect: {mark:?}");
}
+
+/// Line-ending names are attribute values: a name holding a quote or angle bracket cannot add
+/// attributes or elements to the exported XFDF.
+#[test]
+fn xfdf_line_ending_names_are_escaped() {
+ let mut doc = blank();
+ let page = pdfcraft_model::pages(&doc)[0].obj;
+ let hostile = b"a\" tail=\"x\">pwned