mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 18:14:25 +00:00
M8: tests pin the Type 3 cleanup's refusal of bytes it can't read
The Type 3 cleanup refuses a redaction when its scan of a page or form,
or its read of a kept glyph's program, meets bytes the parser can't
read. Nothing tested either refusal: cargo-mutants found both checks
could be dropped with every test passing, once 8bf63c879 moved them
after the loop.
unreadable_bytes_in_a_form_the_type3_cleanup_scans_refuse_the_redaction
leaves a string open in a form only that scan reads (resources of its
own, outside the mark), hiding the glyph the form shows after it, and
unreadable_bytes_in_a_kept_type3_glyph_program_refuse_the_redaction
leaves one open in the program of the glyph the page keeps, hiding the
image it draws. Each fails with its check disabled; with the checks off,
the glyph loses its program and the font its image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8bf63c8792
commit
20e138fbf5
@@ -382,3 +382,52 @@ fn an_image_a_type3_glyph_with_parameters_by_reference_draws_is_kept() {
|
||||
assert!(!full_save_holds(&doc, b"SECRET_IMG_A"), "the image only the redacted glyph drew survived");
|
||||
assert!(full_save_holds(&doc, b"PUBLIC_IMG_B"), "glyph B still draws its image");
|
||||
}
|
||||
|
||||
/// Whether `out` is the redaction refused with a syntax error whose detail holds `detail`.
|
||||
fn refused_for(out: &Result<Report, RedactError>, detail: &str) -> bool {
|
||||
matches!(out, Err(RedactError::Cos(pdfcraft_cos::CosError::Syntax { detail: d, .. })) if d.contains(detail))
|
||||
}
|
||||
|
||||
/// The font's cleanup scans every page and form for the glyphs they show. A string left open in a
|
||||
/// form nothing else reads (resources of its own, outside the mark) runs to the end of the
|
||||
/// stream, hiding the glyph the form shows after it, so the redaction is refused: with the check
|
||||
/// off, that glyph loses its program.
|
||||
#[test]
|
||||
fn unreadable_bytes_in_a_form_the_type3_cleanup_scans_refuse_the_redaction() {
|
||||
let mut doc = pdf(vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
|
||||
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 4 0 R /Resources 5 0 R >>".to_vec(),
|
||||
stream("", b"BT /T3 10 Tf 10 100 Td (A) Tj ET /Fm Do"),
|
||||
b"<< /Font << /T3 6 0 R >> /XObject << /Fm 9 0 R >> >>".to_vec(),
|
||||
TYPE3.as_bytes().to_vec(),
|
||||
stream("", b"% SECRET_TYPE3_GLYPH_A"),
|
||||
stream("", b"% PUBLIC_TYPE3_GLYPH_B"),
|
||||
stream("/Type /XObject /Subtype /Form /BBox [0 0 300 50] /Resources << /Font << /T3 6 0 R >> >>", b"BT /T3 10 Tf 10 20 Td ( (B) Tj ET"),
|
||||
]);
|
||||
mark(&mut doc, 0, &[[9.0, 95.0, 16.0, 110.0]], "");
|
||||
let out = apply(&mut doc, None);
|
||||
assert!(refused_for(&out, "content stream syntax"), "{out:?}");
|
||||
}
|
||||
|
||||
/// The font's cleanup reads the program of each glyph it keeps, for the resources it uses. A
|
||||
/// string left open there runs to the end of the program, hiding the image it draws after it, so
|
||||
/// the redaction is refused: with the check off, the font's resources lose that image.
|
||||
#[test]
|
||||
fn unreadable_bytes_in_a_kept_type3_glyph_program_refuse_the_redaction() {
|
||||
let font = TYPE3.replace("/CharProcs", "/Resources << /XObject << /ImB 9 0 R >> >> /CharProcs");
|
||||
let mut doc = pdf(vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
|
||||
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 4 0 R /Resources 5 0 R >>".to_vec(),
|
||||
stream("", b"BT /T3 10 Tf 10 100 Td (A) Tj 0 100 Td (B) Tj ET"),
|
||||
b"<< /Font << /T3 6 0 R >> >>".to_vec(),
|
||||
font.into_bytes(),
|
||||
stream("", b"% SECRET_TYPE3_GLYPH_A"),
|
||||
stream("", b"500 0 d0 ( /ImB Do"),
|
||||
stream("/Type /XObject /Subtype /Image /Width 1 /Height 1 /ColorSpace /DeviceGray /BitsPerComponent 8", b"o"),
|
||||
]);
|
||||
mark(&mut doc, 0, &[[9.0, 95.0, 16.0, 110.0]], "");
|
||||
let out = apply(&mut doc, None);
|
||||
assert!(refused_for(&out, "glyph stream syntax"), "{out:?}");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Memory regressions for reading the forms a redaction follows through resources: their
|
||||
//! operators are read one at a time, so a form of millions of them is never held whole. A test
|
||||
//! binary of its own, so that the one test is all the process runs while its peak is measured.
|
||||
//! Memory regressions for the streams a redaction reads besides its walk: the forms it follows
|
||||
//! through resources, and what the Type 3 cleanup scans and reads. Their operators are read one
|
||||
//! at a time, so a stream of millions of them is never held whole. A test binary of its own, so
|
||||
//! that the one test is all the process runs while its peak is measured.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -80,7 +81,7 @@ fn type3_page(form: &[u8], glyph: &[u8]) -> Document {
|
||||
/// glyphs it shows;
|
||||
/// - the Type 3 cleanup reads the program of a glyph it keeps, for the resources it uses.
|
||||
///
|
||||
/// Each parsed every operator first: the peak grew by 4,415 and 2,527 MiB in the first two. Now
|
||||
/// Each parsed every operator first: the peak grew by 4,415, 2,526, 2,526 and 2,494 MiB. Now
|
||||
/// the first holds at most one operator past the walk's limit, and the others none.
|
||||
#[test]
|
||||
fn the_forms_followed_through_resources_are_read_one_operator_at_a_time() {
|
||||
|
||||
Reference in New Issue
Block a user