use super::*;
pub(super) struct CompressedObject {
reference: PdfRef,
container: ObjectLocation,
bytes: Vec<u8>,
}
pub(super) struct ObjectStreamLink {
reference: PdfRef,
offset: u64,
extends: Option<PdfRef>,
walk: usize,
}
impl PdfIndex {
pub(super) fn compressed_object(&self, reference: PdfRef) -> Option<&CompressedObject> {
self.compressed_objects
.binary_search_by_key(&reference.number, |object| object.reference.number)
.ok()
.map(|position| &self.compressed_objects[position])
.filter(|object| object.reference == reference)
}
pub(super) fn metadata_location(&self, reference: PdfRef) -> Result<ObjectLocation> {
match self.compressed_object(reference) {
Some(object) => Ok(object.container),
None => self.object_location(reference),
}
}
}
impl<S: RangedSource, C: Cancellation> Reader<'_, S, C> {
pub(super) fn record_object_stream_link(
&self,
head: &ObjectHead,
reference: PdfRef,
offset: u64,
links: &mut Vec<ObjectStreamLink>,
) -> Result<()> {
let Some(dictionary) = head.dictionary.as_ref() else {
return Ok(());
};
if dictionary.value(b"Type").and_then(exact_name).as_deref() != Some(b"ObjStm") {
return Ok(());
}
if reference.generation != 0 || !matches!(head.tail, ObjectTail::Stream { .. }) {
return Err(self.malformed(
offset,
Some(reference),
"ObjStm must be a generation-zero stream",
));
}
let extends = dictionary
.value(b"Extends")
.map(|value| {
exact_reference(value)
.filter(|target| target.generation == 0)
.ok_or(self.malformed(
offset,
Some(reference),
"object stream Extends must be a generation-zero reference",
))
})
.transpose()?;
push_bounded(
links,
ObjectStreamLink {
reference,
offset,
extends,
walk: 0,
},
self.limits.max_allocation_bytes / 8,
"PDF object stream collections",
)
.map_err(self.locator(offset, Some(reference)))
}
pub(super) fn validate_object_stream_collections(
&self,
links: &mut [ObjectStreamLink],
) -> Result<()> {
for start in 0..links.len() {
let marker = start + 1;
let mut current = start;
loop {
if self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
let link = &mut links[current];
if link.walk == marker {
return Err(self.malformed(
link.offset,
Some(link.reference),
"object stream Extends contains a cycle",
));
}
if link.walk != 0 {
break;
}
link.walk = marker;
let Some(target) = link.extends else { break };
let failure = self.malformed(
link.offset,
Some(link.reference),
"object stream Extends does not resolve to an ObjStm",
);
current = links
.binary_search_by_key(&target.number, |link| link.reference.number)
.map_err(|_| failure)?;
}
}
Ok(())
}
pub(super) fn load_indexed_object(
&mut self,
at: u64,
reference: PdfRef,
slots: &[Option<XrefSlot>],
index: &PdfIndex,
) -> Result<(ObjectHead, ObjectLocation)> {
if let Some(object) = index.compressed_object(reference) {
if self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
let mut bytes = Vec::new();
reserve_exact(
&mut bytes,
object.bytes.len(),
self.allocation_limit(
at,
Some(reference),
"PDF compressed metadata copy",
object.bytes.len() as u64,
),
)?;
bytes.extend_from_slice(&object.bytes);
let head = parse_object_head(bytes).map_err(|issue| {
self.parse_issue(at, Some(reference), issue)
.at(self.absolute(at))
})?;
Ok((head, object.container))
} else {
self.load_object(at, reference, slots)
}
}
pub(super) fn read_compressed_objects(
&mut self,
slots: &[Option<XrefSlot>],
index: &mut PdfIndex,
) -> Result<()> {
let cap = self.limits.max_allocation_bytes / 8;
let mut streams = Vec::new();
for (position, slot) in slots.iter().enumerate() {
if position % 1024 == 0 && self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
if let Some(XrefSlot {
kind: XrefKind::Compressed { stream, .. },
..
}) = slot
{
push_bounded(&mut streams, *stream, cap, "PDF object stream index")
.map_err(self.locator(index.xref_offset, None))?;
}
}
streams.sort_unstable();
streams.dedup();
let mut retained = 0_u64;
for number in streams {
let reference = PdfRef {
number,
generation: 0,
};
let at = match slots.get(number as usize).and_then(|slot| *slot) {
Some(XrefSlot {
generation: 0,
kind: XrefKind::InUse(at),
}) => at,
_ => {
return Err(self.malformed(
index.xref_offset,
Some(reference),
"object stream must be a standalone generation-zero object",
));
}
};
let (head, location) = self.load_object(at, reference, slots)?;
check_live_object_end(self.range, at, reference, location, index.logical_end)?;
let unsupported = || {
self.problem(
at,
Some(reference),
ErrorKind::UnsupportedFormat,
"object stream is outside the direct-length Flate profile",
)
};
let dictionary = head.dictionary.as_ref().ok_or_else(unsupported)?;
self.reject_duplicate_names(dictionary, at, Some(reference))?;
let ObjectTail::Stream { data_start } = head.tail else {
return Err(unsupported());
};
if dictionary.value(b"Type").and_then(exact_name).as_deref() != Some(b"ObjStm")
|| dictionary.value(b"Filter").and_then(exact_name).as_deref()
!= Some(b"FlateDecode")
|| [b"DecodeParms".as_slice(), b"F"]
.iter()
.any(|key| dictionary.value(key).is_some())
{
return Err(unsupported());
}
let length = dictionary
.value(b"Length")
.and_then(exact_unsigned)
.ok_or_else(unsupported)?;
let n = dictionary
.value(b"N")
.and_then(exact_unsigned)
.ok_or(self.malformed(at, Some(reference), "object stream has invalid N"))?;
let first = dictionary
.value(b"First")
.and_then(exact_unsigned)
.ok_or(self.malformed(at, Some(reference), "object stream has invalid First"))?;
let stream_cap = self.syntax_limit();
if n == 0 {
return Err(self.malformed(
at,
Some(reference),
"live object stream has no members",
));
}
for (resource, limit, attempted) in [
("PDF object stream members", u64::from(MAX_PDF_OBJECTS), n),
("PDF object stream header bytes", stream_cap, first),
("PDF object stream encoded bytes", stream_cap, length),
] {
if attempted > limit {
return Err(self.locate_limit(
at,
Some(reference),
Error::limit(resource, limit, attempted),
));
}
}
let data_at = at + data_start as u64;
let encoded = self.bytes(data_at, length as usize)?;
let decoded = inflate_pdf_stream(
&encoded,
stream_cap as usize,
false,
self.limits.io_chunk_bytes,
self.cancellation,
)
.map_err(self.locator(at, Some(reference)))?;
let header = decoded.get(..first as usize).ok_or(self.malformed(
at,
Some(reference),
"object stream First exceeds decoded data",
))?;
let mut syntax = Syntax::new(header);
let mut entries = Vec::new();
for position in 0..n {
if position % 1024 == 0 && self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
let member = syntax.unsigned().map_err(|issue| {
self.parse_issue(at, Some(reference), issue)
.at(self.absolute(at))
})?;
let offset = syntax.unsigned().map_err(|issue| {
self.parse_issue(at, Some(reference), issue)
.at(self.absolute(at))
})?;
if member == 0
|| member > u64::from(MAX_PDF_OBJECTS)
|| offset >= (decoded.len() - header.len()) as u64
|| (position == 0 && offset != 0)
|| entries
.last()
.is_some_and(|(_, previous)| offset <= *previous)
{
return Err(self.malformed(
at,
Some(reference),
"object stream member index is invalid",
));
}
push_bounded(
&mut entries,
(member as u32, offset),
cap,
"PDF object stream members",
)
.map_err(self.locator(at, Some(reference)))?;
}
if !syntax.at_end() {
return Err(self.malformed(
at,
Some(reference),
"object stream header has trailing data",
));
}
let mut numbers = Vec::new();
for &(member, _) in &entries {
push_bounded(
&mut numbers,
member,
cap,
"PDF object stream member numbers",
)
.map_err(self.locator(at, Some(reference)))?;
}
numbers.sort_unstable();
if numbers.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(self.malformed(
at,
Some(reference),
"object stream repeats a member number",
));
}
for (position, &(member, offset)) in entries.iter().enumerate() {
if self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
let end = entries
.get(position + 1)
.map_or(decoded.len(), |(_, offset)| {
first as usize + *offset as usize
});
let body = &decoded[first as usize + offset as usize..end];
let mut syntax = Syntax::new(body);
syntax.skip_value(0).map_err(|issue| {
self.parse_issue(at, Some(reference), issue)
.at(self.absolute(at))
})?;
if !syntax.at_end() {
return Err(self.malformed(
at,
Some(reference),
"object stream member is not one direct value",
));
}
let Some(XrefSlot {
kind:
XrefKind::Compressed {
stream,
index: ordinal,
},
..
}) = slots.get(member as usize).and_then(|slot| *slot)
else {
continue;
};
if stream != number {
continue;
}
if ordinal as usize != position {
return Err(self.malformed(
at,
Some(reference),
"xref compressed member index disagrees with object stream",
));
}
let prefix = format!("{member} 0 obj\n");
let needed = prefix.len().saturating_add(body.len()).saturating_add(8);
if needed as u64 > stream_cap {
return Err(self.locate_limit(
at,
Some(reference),
Error::limit("PDF object syntax bytes", stream_cap, needed as u64),
));
}
retained = retained.saturating_add(needed as u64);
if retained > cap {
return Err(self.locate_limit(
at,
Some(reference),
Error::limit("PDF compressed metadata bytes", cap, retained),
));
}
let mut bytes = Vec::new();
let refused = self.allocation_limit(
at,
Some(reference),
"PDF compressed metadata allocation",
needed as u64,
);
reserve_exact(&mut bytes, needed, refused)?;
bytes.extend_from_slice(prefix.as_bytes());
bytes.extend_from_slice(body);
bytes.extend_from_slice(b"\nendobj\n");
push_bounded(
&mut index.compressed_objects,
CompressedObject {
reference: PdfRef {
number: member,
generation: 0,
},
container: location,
bytes,
},
cap,
"PDF compressed metadata index",
)
.map_err(self.locator(at, Some(reference)))?;
}
}
index
.compressed_objects
.sort_unstable_by_key(|object| object.reference.number);
for (number, slot) in slots.iter().enumerate() {
if number % 1024 == 0 && self.cancellation.is_cancelled() {
return Err(ErrorKind::Cancelled.into());
}
if let Some(XrefSlot {
kind: XrefKind::Compressed { .. },
..
}) = slot
&& index
.compressed_object(PdfRef {
number: number as u32,
generation: 0,
})
.is_none()
{
return Err(self.malformed(
index.xref_offset,
Some(PdfRef {
number: number as u32,
generation: 0,
}),
"xref compressed object has no matching stream member",
));
}
}
Ok(())
}
}