fix(annot): validate finite link areas before add or edit (#940)
* fix(annot): validate finite link areas before add or edit * Test: find the edited link by its /Annots index, not its position in the list --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
57c9e3a9b4
commit
0059ed657c
|
|
@ -202,13 +202,24 @@ fn write_action(doc: &Document, d: &mut Dict, a: &LinkAction) -> Result<(), Anno
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Check link geometry before writing PDF objects: a user or automation call can supply
|
||||
/// non-finite coordinates or a finite pair whose difference overflows to infinity.
|
||||
fn checked_rect(rect: [f64; 4]) -> Result<[f64; 4], AnnotError> {
|
||||
if !rect.iter().all(|v| v.is_finite()) {
|
||||
return Err(AnnotError::Invalid("the link area must have finite coordinates".into()));
|
||||
}
|
||||
let r = [rect[0].min(rect[2]), rect[1].min(rect[3]), rect[0].max(rect[2]), rect[1].max(rect[3])];
|
||||
let (width, height) = (r[2] - r[0], r[3] - r[1]);
|
||||
if !width.is_finite() || !height.is_finite() || width < 2.0 || height < 2.0 {
|
||||
return Err(AnnotError::Invalid("the link area is too small or too large".into()));
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Add a link over `rect` (user space). Returns its index in the page's `/Annots`.
|
||||
pub fn add(doc: &mut Document, page: usize, rect: [f64; 4], action: &LinkAction, style: &LinkStyle) -> Result<usize, AnnotError> {
|
||||
let p = page_ref(doc, page)?;
|
||||
let r = [rect[0].min(rect[2]), rect[1].min(rect[3]), rect[0].max(rect[2]), rect[1].max(rect[3])];
|
||||
if !r.iter().all(|v| v.is_finite()) || r[2] - r[0] < 2.0 || r[3] - r[1] < 2.0 {
|
||||
return Err(AnnotError::Invalid("the link area is too small".into()));
|
||||
}
|
||||
let r = checked_rect(rect)?;
|
||||
if matches!(action, LinkAction::Other(_)) {
|
||||
return Err(AnnotError::Invalid("choose a page or a web address".into()));
|
||||
}
|
||||
|
|
@ -250,10 +261,7 @@ pub fn set(
|
|||
let (_, r) = link_at(doc, page, index)?;
|
||||
let mut d = doc.get(r).as_dict().cloned().unwrap_or_default();
|
||||
if let Some(rect) = rect {
|
||||
let rr = [rect[0].min(rect[2]), rect[1].min(rect[3]), rect[0].max(rect[2]), rect[1].max(rect[3])];
|
||||
if rr[2] - rr[0] < 2.0 || rr[3] - rr[1] < 2.0 {
|
||||
return Err(AnnotError::Invalid("the link area is too small".into()));
|
||||
}
|
||||
let rr = checked_rect(rect)?;
|
||||
d.set(b"Rect".to_vec(), Object::Array(rr.iter().map(|v| Object::Real(*v)).collect()));
|
||||
}
|
||||
if let Some(s) = style {
|
||||
|
|
|
|||
|
|
@ -574,6 +574,31 @@ 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_edit_rejects_non_finite_and_overflowed_rectangles_without_mutating_the_pdf() {
|
||||
use crate::links::{self, LinkAction, LinkStyle};
|
||||
let mut doc = fixture();
|
||||
let original_rect = [10.0, 20.0, 110.0, 70.0];
|
||||
let index = links::add(&mut doc, 0, original_rect, &LinkAction::Uri("https://example.org".into()), &LinkStyle::default()).unwrap();
|
||||
let before = links::list(&doc);
|
||||
let changed_action = LinkAction::Uri("https://other.example".into());
|
||||
for rect in [
|
||||
[f64::NAN, 0.0, 100.0, 30.0],
|
||||
[0.0, 0.0, f64::INFINITY, 30.0],
|
||||
[f64::NEG_INFINITY, 0.0, 100.0, 30.0],
|
||||
[-1e308, 0.0, 1e308, 30.0],
|
||||
[0.0, 0.0, 1.5, 30.0],
|
||||
] {
|
||||
assert!(links::set(&mut doc, 0, index, Some(rect), Some(&changed_action), None).is_err(), "edit accepted {rect:?}");
|
||||
assert_eq!(links::list(&doc), before, "invalid edit changed an existing link");
|
||||
assert!(links::add(&mut doc, 0, rect, &changed_action, &LinkStyle::default()).is_err(), "add accepted {rect:?}");
|
||||
}
|
||||
// A reversed but otherwise valid rectangle is normalized on edit.
|
||||
links::set(&mut doc, 0, index, Some([110.0, 70.0, 10.0, 20.0]), None, None).unwrap();
|
||||
let edited = links::list(&doc).into_iter().find(|l| l.page == 0 && l.index == index).map(|l| l.rect);
|
||||
assert_eq!(edited, Some(original_rect));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn urls_are_found_in_text() {
|
||||
let t: Vec<char> = "See https://example.org/a?b=1, or www.rust-lang.org. Not a.b or http:/x.".chars().collect();
|
||||
|
|
|
|||
Loading…
Reference in New Issue