use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlateRecoveryKind {
Unverified,
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilterRecovery {
pub filter_index: usize,
pub kind: FlateRecoveryKind,
pub error: String,
}
#[derive(Debug)]
pub struct RecoveredStream {
pub data: Vec<u8>,
pub diagnostics: Vec<FilterRecovery>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StreamRecoveryErrorKind {
InvalidFlate,
ResourceLimit,
Other,
}
#[derive(Debug, thiserror::Error)]
#[error("{source}")]
pub struct StreamRecoveryError {
pub kind: StreamRecoveryErrorKind,
#[source]
pub source: ParseError,
}
impl StreamRecoveryError {
pub(crate) fn other(source: ParseError) -> Self {
Self {
kind: StreamRecoveryErrorKind::Other,
source,
}
}
}
pub fn decode_stream_with_recovery(
data: &[u8],
dict: &PdfDictionary,
options: &ParseOptions,
max_bytes: usize,
) -> Result<RecoveredStream, StreamRecoveryError> {
let max_bytes = max_bytes.min(MAX_DECOMPRESSED_SIZE);
let filters = match dict.get("Filter") {
Some(PdfObject::Name(n)) => vec![n.as_str()],
Some(PdfObject::Array(a)) => {
a.0.iter()
.map(|o| {
o.as_name().map(|n| n.as_str()).ok_or_else(|| {
StreamRecoveryError::other(bounded_decode_syntax("Invalid filter in array"))
})
})
.collect::<Result<Vec<_>, _>>()?
}
None => {
return copy_with_limit(data, max_bytes)
.map(|data| RecoveredStream {
data,
diagnostics: Vec::new(),
})
.map_err(StreamRecoveryError::other)
}
_ => {
return Err(StreamRecoveryError::other(bounded_decode_syntax(
"Invalid Filter type",
)))
}
};
let mut result: Option<Vec<u8>> = None;
let mut diagnostics = Vec::new();
for (index, name) in filters.iter().enumerate() {
let filter = Filter::from_name(name).ok_or_else(|| {
StreamRecoveryError::other(bounded_decode_syntax("Unknown stream filter"))
})?;
let input = result.as_deref().unwrap_or(data);
let params = get_filter_params(dict.get("DecodeParms"), index);
let decoded = if matches!(filter, Filter::FlateDecode) {
let (decoded, recovery) = recover_flate(input, max_bytes)?;
if let Some((kind, error)) = recovery {
diagnostics.push(FilterRecovery {
filter_index: index,
kind,
error,
});
}
apply_declared_predictor(decoded, params).map_err(StreamRecoveryError::other)?
} else {
let mut single = PdfDictionary::new();
single.insert(
"Filter".into(),
PdfObject::Name(super::super::PdfName::new((*name).into())),
);
if let Some(params) = params {
single.insert("DecodeParms".into(), PdfObject::Dictionary(params.clone()));
}
decode_stream_with_limit(input, &single, options, max_bytes)
.map_err(StreamRecoveryError::other)?
};
result = Some(decoded);
}
let data = match result {
Some(bytes) => bytes,
None => copy_with_limit(data, max_bytes).map_err(StreamRecoveryError::other)?,
};
Ok(RecoveredStream { data, diagnostics })
}
#[cfg(feature = "compression")]
fn recover_flate(
data: &[u8],
max_bytes: usize,
) -> Result<(Vec<u8>, Option<(FlateRecoveryKind, String)>), StreamRecoveryError> {
let first = match inflate_bounded(data, max_bytes, true) {
Ok(bytes) => return Ok((bytes, None)),
Err(e) => e,
};
if first.resource_limit {
return Err(StreamRecoveryError {
kind: StreamRecoveryErrorKind::ResourceLimit,
source: first.error,
});
}
let original = first.error;
drop(first.partial);
let has_header = data.len() >= 2
&& data[0] & 15 == 8
&& data[0] >> 4 <= 7
&& u16::from_be_bytes([data[0], data[1]]) % 31 == 0;
if has_header && data[1] & 32 != 0 {
return Err(StreamRecoveryError {
kind: StreamRecoveryErrorKind::InvalidFlate,
source: original,
});
}
let raw = if has_header { &data[2..] } else { data };
match inflate_bounded(raw, max_bytes, false) {
Ok(bytes) => Ok((
bytes,
Some((FlateRecoveryKind::Unverified, original.to_string())),
)),
Err(e) if e.resource_limit => Err(StreamRecoveryError {
kind: StreamRecoveryErrorKind::ResourceLimit,
source: e.error,
}),
Err(e) if !e.partial.is_empty() => Ok((
e.partial,
Some((FlateRecoveryKind::Incomplete, original.to_string())),
)),
Err(_) => Err(StreamRecoveryError {
kind: StreamRecoveryErrorKind::InvalidFlate,
source: original,
}),
}
}
#[cfg(not(feature = "compression"))]
fn recover_flate(
_data: &[u8],
_max_bytes: usize,
) -> Result<(Vec<u8>, Option<(FlateRecoveryKind, String)>), StreamRecoveryError> {
Err(StreamRecoveryError::other(ParseError::StreamDecodeError(
"FlateDecode requires the compression feature".into(),
)))
}