mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 08:24:18 +00:00
fix(optimize): bound each unused-resource decode by the remaining shared budget
This commit is contained in:
@@ -341,6 +341,12 @@ impl Stream {
|
||||
self.decode_chain(MAX_DECODED, decode).map(|(v, _)| v).map_err(|(e, _)| CosError::Filter(e.to_string()))
|
||||
}
|
||||
|
||||
/// [`Stream::decoded_strict`] for a caller with less than [`MAX_DECODED`] left to spend:
|
||||
/// decoding past `max` bytes (in any filter stage) is an error.
|
||||
pub fn decoded_strict_within(&self, max: usize) -> Result<Vec<u8>, CosError> {
|
||||
self.decoded_strict_within_counted(max).map(|(v, _)| v).map_err(|(e, _)| CosError::Filter(e.to_string()))
|
||||
}
|
||||
|
||||
/// [`Stream::decoded_strict`] bounded by `max` and counted like
|
||||
/// [`Stream::decoded_within_counted`].
|
||||
pub(crate) fn decoded_strict_within_counted(&self, max: usize) -> Result<(Vec<u8>, usize), (pdfcraft_filters::FilterError, usize)> {
|
||||
@@ -607,6 +613,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_decoding_is_bounded_by_the_callers_remaining_allowance() {
|
||||
let stream = Stream::flate(Dict::new(), &vec![0u8; 1 << 20]);
|
||||
assert!(matches!(stream.decoded_strict_within(1000), Err(CosError::Filter(_))));
|
||||
assert_eq!(stream.decoded_strict_within(1 << 20).map(|v| v.len()).ok(), Some(1 << 20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_filter_arrays_refuse_decoding_past_a_caller_limit() {
|
||||
let mut dict = Dict::new();
|
||||
|
||||
@@ -168,7 +168,9 @@ fn plain_metadata(doc: &Document) -> bool {
|
||||
.is_some_and(|e| matches!(e.as_dict().and_then(|e| e.get(b"EncryptMetadata")), Some(Object::Bool(false))))
|
||||
}
|
||||
|
||||
fn data(s: &Stream, num: u32, g: &Graph, encrypted: bool, plain_metadata: bool) -> Data {
|
||||
/// `left` is what the caller may still decode: a stream that would decode past it (in any filter
|
||||
/// stage) is `Unknown`, so no stage is allowed to grow beyond the shared budget first.
|
||||
fn data(s: &Stream, num: u32, g: &Graph, encrypted: bool, plain_metadata: bool, left: usize) -> Data {
|
||||
let d = &s.dict;
|
||||
// Streams the security handler leaves as stored, which a viewer may still decrypt.
|
||||
let ty = d.name(b"Type");
|
||||
@@ -219,7 +221,7 @@ fn data(s: &Stream, num: u32, g: &Graph, encrypted: bool, plain_metadata: bool)
|
||||
if !parms_line_up {
|
||||
return Data::Unknown;
|
||||
}
|
||||
s.decoded_strict().map_or(Data::Unknown, Data::Decoded)
|
||||
s.decoded_strict_within(left).map_or(Data::Unknown, Data::Decoded)
|
||||
}
|
||||
|
||||
/// Drop the image and form XObjects that `pages` list in their own resources but no stream
|
||||
@@ -254,7 +256,7 @@ pub(crate) fn remove_unused(doc: &mut Document, pages: &[ObjRef], progress: &mut
|
||||
}
|
||||
let obj = doc.get(ObjRef::new(*num, doc.generation(*num)));
|
||||
let Object::Stream(s) = &*obj else { continue };
|
||||
match data(s, *num, &g, encrypted, plain_metadata) {
|
||||
match data(s, *num, &g, encrypted, plain_metadata, DECODE_BUDGET.saturating_sub(decoded)) {
|
||||
Data::Picture => {}
|
||||
Data::Decoded(bytes) => {
|
||||
decoded = decoded.saturating_add(bytes.len());
|
||||
|
||||
Reference in New Issue
Block a user