Merge PR #2679: fix(annot): percent-encode non-ASCII bytes written into link /URI

This commit is contained in:
aiongg
2026-10-16 14:02:08 +00:00
2 changed files with 33 additions and 1 deletions
+14 -1
View File
@@ -178,6 +178,19 @@ fn write_style(d: &mut Dict, s: &LinkStyle) {
d.set(b"H".to_vec(), Object::name(s.highlight.code()));
}
/// Bytes of `u` as a 7-bit ASCII URI string (§12.6.4.8).
fn uri_bytes(u: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(u.len());
for &b in u.as_bytes() {
if (0x21..=0x7e).contains(&b) {
out.push(b);
} else {
out.extend_from_slice(format!("%{b:02X}").as_bytes());
}
}
out
}
fn write_action(doc: &Document, d: &mut Dict, a: &LinkAction) -> Result<(), AnnotError> {
match a {
LinkAction::Page(p) => {
@@ -194,7 +207,7 @@ fn write_action(doc: &Document, d: &mut Dict, a: &LinkAction) -> Result<(), Anno
let mut act = Dict::new();
act.set(b"S".to_vec(), Object::name("URI"));
// URIs are 7-bit ASCII strings (§12.6.4.8).
act.set(b"URI".to_vec(), PdfString::literal(u.as_bytes().to_vec()));
act.set(b"URI".to_vec(), PdfString::literal(uri_bytes(u)));
d.set(b"A".to_vec(), Object::Dict(act));
}
LinkAction::Other(_) => {}
+19
View File
@@ -588,6 +588,25 @@ fn links_are_added_edited_listed_and_removed() {
assert!(links::add(&mut doc, 0, [0.0, 0.0, 1.0, 1.0], &LinkAction::Page(0), &LinkStyle::default()).is_err(), "too small");
}
#[test]
fn link_uri_is_percent_encoded_to_7_bit_ascii() {
use crate::links::{self, LinkAction, LinkStyle};
let mut doc = fixture();
for url in ["https://example.com/ação", "https://example.com/a%20b"] {
links::add(&mut doc, 0, [10.0, 10.0, 110.0, 30.0], &LinkAction::Uri(url.into()), &LinkStyle::default()).unwrap();
}
let doc = reopen(&doc);
let urls: Vec<String> = links::list(&doc)
.into_iter()
.filter_map(|link| match link.action {
LinkAction::Uri(url) => Some(url),
_ => None,
})
.collect();
assert_eq!(urls, ["https://example.com/a%C3%A7%C3%A3o", "https://example.com/a%20b"]);
assert!(urls.iter().all(|url| url.is_ascii()));
}
#[test]
fn link_edit_rejects_non_finite_and_overflowed_rectangles_without_mutating_the_pdf() {
use crate::links::{self, LinkAction, LinkStyle};