From d234fa36798244f2f68c0b8d0f831d554d8cdc6d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 16 Jul 2026 16:09:32 -0500 Subject: [PATCH 1/2] test(svg): Check bad link behavior --- Cargo.lock | 1 + crates/anstyle-svg/Cargo.toml | 1 + crates/anstyle-svg/src/lib.rs | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b864df8b..740bbc38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,6 +188,7 @@ name = "anstyle-svg" version = "1.1.0" dependencies = [ "anstyle 1.0.14", + "anstyle-hyperlink", "anstyle-lossy", "anstyle-parse 1.0.0", "html-escape", diff --git a/crates/anstyle-svg/Cargo.toml b/crates/anstyle-svg/Cargo.toml index a15fb76a..881233d5 100644 --- a/crates/anstyle-svg/Cargo.toml +++ b/crates/anstyle-svg/Cargo.toml @@ -31,6 +31,7 @@ html-escape = "0.2.13" unicode-width = "0.2.2" [dev-dependencies] +anstyle-hyperlink = { path = "../anstyle-hyperlink" } proptest = "1.7.0" snapbox = "0.6.23" diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index e3756847..9f0d95fd 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -489,6 +489,7 @@ fn write_fg_span(buffer: &mut String, span: &str, element: &adapter::Element, fr } write!(buffer, r#">"#).unwrap(); if let Some(hyperlink) = &element.url { + let hyperlink = sanitize_hyperlink(hyperlink); write!(buffer, r#""#).unwrap(); need_closing_a = true; } @@ -643,6 +644,26 @@ fn split_lines(styled: &[adapter::Element]) -> Vec> { lines } +fn sanitize_hyperlink(link: &str) -> String { + link.to_owned() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn link_injection() { + let link = anstyle_hyperlink::Hyperlink::with_url("https://example.com/\">"); + let input = format!("Hello {link}world{link:#}!"); + let mut styled = adapter::AnsiBytes::new(); + let elements = styled.extract_next(input.as_bytes()).collect::>(); + let actual = elements.into_iter().find_map(|e| e.url).unwrap(); + let actual = sanitize_hyperlink(&actual); + snapbox::assert_data_eq!(actual, snapbox::str![[r#"https://example.com/">"#]]); + } +} + #[doc = include_str!("../README.md")] #[cfg(doctest)] pub struct ReadmeDoctests; From 1c4609a7b3ac7cbe0fd60e3fb2b410db877758e0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 16 Jul 2026 16:25:24 -0500 Subject: [PATCH 2/2] fix(svg): Escape some link characters --- crates/anstyle-svg/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index 9f0d95fd..975f470e 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -645,7 +645,7 @@ fn split_lines(styled: &[adapter::Element]) -> Vec> { } fn sanitize_hyperlink(link: &str) -> String { - link.to_owned() + html_escape::encode_double_quoted_attribute(link).into_owned() } #[cfg(test)] @@ -660,7 +660,7 @@ mod test { let elements = styled.extract_next(input.as_bytes()).collect::>(); let actual = elements.into_iter().find_map(|e| e.url).unwrap(); let actual = sanitize_hyperlink(&actual); - snapbox::assert_data_eq!(actual, snapbox::str![[r#"https://example.com/">"#]]); + snapbox::assert_data_eq!(actual, snapbox::str!["https://example.com/">"]); } }