mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
M8: an image stored again past the allowance has a test of its own again
Sinceb1f221611the dictionary is read within the allowance too, so the test that gave unshare no allowance was refused reading the dictionary, before the image was decoded: turning the image's refusal into "can't be read" left every test green (Claims review of5bef9b0e7). The test now also gives exactly what reading the dictionary costs, so the image is what is refused. The same review: the README lists the other images that can't be stored again (more than one component, no size) and names ImageBudget::decode, not Document::decoded_image_within, which #5569 made private; SharedJbig2Budget's doc and remove_hidden's comment name the dictionaries read to know copies too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
1 parent
5bef9b0e71
commit
4826ca6ebc
4 files changed
+19
-11
No files matched your search
@@ -19,7 +19,7 @@ clear_marks(&mut doc, None)?; // remove marks without app
|
||||
inside;
|
||||
- **images**: fully covered → removed; partly covered → a copy with the covered pixels cleared
|
||||
(8/16-bit and 1/2/4-bit images, image masks; Flate/LZW/RL/A85/AHx, and CCITT fax or JBIG2 as
|
||||
the last filter, decoded through `Document::decoded_image_within`, a JBIG2 image with its
|
||||
the last filter, decoded through `ImageBudget::decode`, a JBIG2 image with its
|
||||
`/JBIG2Globals`; `/DecodeParms` given by reference, numbers inside included, count). The copy
|
||||
is Flate, or for a one-bit image of one component (a scan as a rule) CCITT Group 4, or JBIG2
|
||||
when it was JBIG2, whichever is smallest as stored, coded from the cleared samples; it refers to
|
||||
@@ -27,8 +27,9 @@ clear_marks(&mut doc, None)?; // remove marks without app
|
||||
those of marks under the area included, so whatever left the document, by any route, takes its
|
||||
globals with it on the full rewrite: anything else the save would keep that names them, or a
|
||||
copy of them (the same bytes once decoded), is stored again without them, as a copy is (Flate, Group 4 or JBIG2,
|
||||
whichever is smallest; `Report::images_restored`). When one can't be (it isn't JBIG2, it's past
|
||||
the size redaction clears, its data is in another file, or it can't be decoded), or something else refers to them, `apply`
|
||||
whichever is smallest; `Report::images_restored`). When one can't be (it isn't JBIG2, its colour
|
||||
space is known to have more than one component, it has no size or one past what redaction
|
||||
clears, its data is in another file, or it can't be decoded), or something else refers to them, `apply`
|
||||
refuses and changes nothing (`RedactError::SharedJbig2Globals`). `remove_hidden` takes them out
|
||||
the same way. Another dictionary holding the same shapes in other bytes isn't found, nor is a copy
|
||||
nothing names, and an image that hasn't left keeps its globals with its pixels: one another page
|
||||
|
||||
@@ -84,8 +84,9 @@ pub enum RedactError {
|
||||
/// Finding what shares JBIG2 symbol data with what is being removed would read more of the file
|
||||
/// than one run allows (`globals::RUN_BUDGET`): its images and parameters refer to the same
|
||||
/// objects in more ways than are checked, or hold more than is decoded. Removing hidden
|
||||
/// information says so too when storing those images again would decode more than its image
|
||||
/// allowance, which no choice of pages makes smaller.
|
||||
/// information says so too when the dictionaries it reads to know copies, or the images it
|
||||
/// stores again, would decode more than its image allowance, which no choice of pages makes
|
||||
/// smaller.
|
||||
#[error(
|
||||
"finding and storing again what shares JBIG2 symbol data with something being removed would read more of this file than one run allows, so it can't be shown that the data leaves with it; this was refused"
|
||||
)]
|
||||
|
||||
@@ -689,7 +689,8 @@ pub fn remove_hidden(doc: &mut Document, which: &[Hidden]) -> Result<Vec<(Hidden
|
||||
if done.is_empty() {
|
||||
return Err(RedactError::NothingToApply);
|
||||
}
|
||||
// Images stored again without a dictionary are decoded within an allowance of their own.
|
||||
// Images stored again without a dictionary, and the dictionaries read to know copies, are
|
||||
// decoded within an allowance of their own.
|
||||
unshare_unmarked(doc, &kept_before, &mut pdfcraft_cos::ImageBudget::new(doc))?;
|
||||
doc.require_full_save();
|
||||
Ok(done)
|
||||
|
||||
@@ -1329,11 +1329,10 @@ fn globals_a_redacted_jbig2_image_shares_with_another_page_leave_the_file() {
|
||||
let mut doc = build(&image.replace(&format!("/Height {h}"), &format!("/Height {}", h + 1)));
|
||||
mark(&mut doc, 0, &[[0.25, 113.75, 49.75, 120.0]], "");
|
||||
assert!(matches!(apply(&mut doc, None), Err(RedactError::SharedJbig2Globals(1))));
|
||||
// Storing the second page's image again decodes it, within what is left of the run's
|
||||
// allowance: with nothing left the redaction is refused as one past the allowance, not
|
||||
// reported as an image that can't be read.
|
||||
// The first page's image leaves (its page no longer lists it), and what is left of the
|
||||
// allowance is all that decodes the second's.
|
||||
// Reading the dictionary to know copies of it, and storing the second page's image again,
|
||||
// decode within what is left of the run's allowance: past it the redaction is refused as one
|
||||
// past the allowance, not reported as an image that can't be read. The first page's image
|
||||
// leaves (its page no longer lists it).
|
||||
let mut doc = build(&image);
|
||||
let before = crate::globals::kept(&doc);
|
||||
let first = pdfcraft_model::pages(&doc)[0].obj;
|
||||
@@ -1343,6 +1342,12 @@ fn globals_a_redacted_jbig2_image_shares_with_another_page_leave_the_file() {
|
||||
.unwrap();
|
||||
let refused = crate::globals::unshare(&mut doc.clone(), &before, &mut pdfcraft_cos::ImageBudget::with_work(0));
|
||||
assert!(matches!(refused, Err(RedactError::ImagesPastAllowance)), "{refused:?}");
|
||||
// Enough to read the dictionary, and nothing left for the image.
|
||||
let mut reading = pdfcraft_cos::ImageBudget::with_work(u64::MAX);
|
||||
crate::globals::content(&mut crate::globals::Run::new(crate::globals::RUN_BUDGET), &doc, 9, &mut reading).unwrap();
|
||||
let read = u64::MAX - reading.work_left();
|
||||
let refused = crate::globals::unshare(&mut doc.clone(), &before, &mut pdfcraft_cos::ImageBudget::with_work(read));
|
||||
assert!(matches!(refused, Err(RedactError::ImagesPastAllowance)), "the image: {refused:?}");
|
||||
// Removing hidden information chose no pages, so fewer of them is no way out: it is refused as
|
||||
// finding what shares the dictionary is.
|
||||
let refused = crate::sanitize::unshare_unmarked(&mut doc.clone(), &before, &mut pdfcraft_cos::ImageBudget::with_work(0));
|
||||
|
||||
Reference in new issue
Block a user