use super::lexer::{Lexer, Token};
use super::object::{Dict, Object};
use super::parser::{expect_int, object_from_token, parse_object};
use crate::{FormatError, Result};
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
pub type DecipherFn = dyn Fn(u32, u16, &[u8]) -> Vec<u8>;
pub type Decipher = Box<DecipherFn>;
#[derive(Clone, Debug)]
enum XRefEntry {
Uncompressed { offset: usize },
InObjStm { stmid: u32, index: usize },
}
#[derive(Clone, Debug, Default)]
struct XRef {
offsets: HashMap<u32, XRefEntry>,
trailer: Dict,
}
pub struct PdfDocument {
data: Vec<u8>,
pub(super) version: Vec<u8>,
xrefs: Vec<XRef>,
pub(super) trailer: Dict,
cache: RefCell<HashMap<u32, Rc<Object>>>,
objstm_cache: RefCell<HashMap<u32, (usize, Vec<Object>)>>,
resolving: RefCell<HashSet<u32>>,
decipher: RefCell<Option<Decipher>>,
encrypt_skip: Cell<Option<u32>>,
}
impl std::fmt::Debug for PdfDocument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PdfDocument")
.field("bytes", &self.data.len())
.field("xref_sections", &self.xrefs.len())
.field("objects", &self.object_ids().len())
.finish()
}
}
impl PdfDocument {
pub fn parse(data: &[u8]) -> Result<Self> {
let version = data.get(0..8).unwrap_or(data).to_vec();
let mut doc = PdfDocument {
data: data.to_vec(),
version,
xrefs: Vec::new(),
trailer: Dict::new(),
cache: RefCell::new(HashMap::new()),
objstm_cache: RefCell::new(HashMap::new()),
resolving: RefCell::new(HashSet::new()),
decipher: RefCell::new(None),
encrypt_skip: Cell::new(None),
};
doc.read_xrefs()?;
Ok(doc)
}
pub fn set_decipher(&self, decipher: Decipher) {
self.cache.borrow_mut().clear();
self.objstm_cache.borrow_mut().clear();
self.encrypt_skip.set(match self.trailer.get("Encrypt") {
Some(Object::Ref(objid, _)) => Some(*objid),
_ => None,
});
*self.decipher.borrow_mut() = Some(decipher);
}
pub fn object_ids(&self) -> Vec<u32> {
let mut ids: Vec<u32> = Vec::new();
let mut seen = HashSet::new();
for xref in &self.xrefs {
for &id in xref.offsets.keys() {
if seen.insert(id) {
ids.push(id);
}
}
}
ids.sort_unstable();
ids
}
pub fn trailer(&self) -> &Dict {
&self.trailer
}
pub fn encrypt(&self) -> Option<&Object> {
self.trailer.get("Encrypt")
}
pub fn id(&self) -> Option<&Object> {
self.trailer.get("ID")
}
pub fn resolve(&self, obj: &Object) -> Result<Rc<Object>> {
let mut objid = match obj {
Object::Ref(id, _) => *id,
_ => return Ok(Rc::new(obj.clone())),
};
let mut guard = 0;
loop {
let cur = self.get_object(objid)?;
match cur.as_ref() {
Object::Ref(id, _) => {
objid = *id;
guard += 1;
if guard > 100 {
return Err(FormatError::Invalid("pdf: reference cycle".into()));
}
}
_ => return Ok(cur),
}
}
}
pub fn get_object(&self, objid: u32) -> Result<Rc<Object>> {
if let Some(obj) = self.cache.borrow().get(&objid) {
return Ok(Rc::clone(obj));
}
if !self.resolving.borrow_mut().insert(objid) {
return Err(FormatError::Invalid(format!(
"pdf: reference cycle resolving object {}",
objid
)));
}
let built = self.build_object(objid);
self.resolving.borrow_mut().remove(&objid);
let obj = Rc::new(built?);
self.cache.borrow_mut().insert(objid, Rc::clone(&obj));
Ok(obj)
}
fn build_object(&self, objid: u32) -> Result<Object> {
let entry = self
.locate(objid)
.ok_or_else(|| FormatError::Invalid(format!("pdf: object {} not found", objid)))?;
match entry {
XRefEntry::Uncompressed { offset } => self.parse_indirect_at(offset, objid),
XRefEntry::InObjStm { stmid, index } => self.parse_from_objstm(stmid, index),
}
}
fn locate(&self, objid: u32) -> Option<XRefEntry> {
for xref in &self.xrefs {
if let Some(e) = xref.offsets.get(&objid) {
return Some(e.clone());
}
}
None
}
fn parse_indirect_at(&self, offset: usize, objid: u32) -> Result<Object> {
if offset >= self.data.len() {
return Err(FormatError::Invalid(format!(
"pdf: object {} offset {} out of range",
objid, offset
)));
}
let mut lex = Lexer::at(&self.data, offset);
let _objid = expect_int(&mut lex)?;
let genno = expect_int(&mut lex)?;
match lex.next_token()? {
Some(Token::Keyword(ref k)) if k == b"obj" => {}
other => {
return Err(FormatError::Invalid(format!(
"pdf: expected 'obj' for object {}, got {:?}",
objid, other
)));
}
}
let genno = genno.max(0) as u16;
let mut obj = parse_object(&mut lex, Some(self))?;
if let Object::Stream(ref mut s) = obj {
s.objid = objid;
s.genno = genno;
}
if self.encrypt_skip.get() != Some(objid) {
if let Some(decipher) = self.decipher.borrow().as_ref() {
obj = decipher_all(decipher.as_ref(), objid, genno, obj);
}
}
Ok(obj)
}
fn parse_from_objstm(&self, stmid: u32, index: usize) -> Result<Object> {
if !self.objstm_cache.borrow().contains_key(&stmid) {
let container = self.get_object(stmid)?;
let stream = match container.as_ref() {
Object::Stream(s) => s,
_ => {
return Err(FormatError::Invalid(format!(
"pdf: object stream {} is not a stream",
stmid
)));
}
};
let n = stream
.dict
.get("N")
.and_then(Object::as_int)
.unwrap_or(0)
.max(0) as usize;
let data = stream.decoded()?;
let mut lex = Lexer::new(&data);
let mut objs = Vec::new();
while let Some(tok) = lex.next_token()? {
objs.push(object_from_token(tok, &mut lex, None)?);
}
self.objstm_cache.borrow_mut().insert(stmid, (n, objs));
}
let borrow = self.objstm_cache.borrow();
let (n, objs) = borrow.get(&stmid).ok_or_else(|| {
FormatError::Invalid(format!("pdf: object stream {} vanished", stmid))
})?;
let i = n * 2 + index;
objs.get(i).cloned().ok_or_else(|| {
FormatError::Invalid(format!(
"pdf: object-stream {} has no member at index {}",
stmid, index
))
})
}
fn read_xrefs(&mut self) -> Result<()> {
let start = self
.find_startxref()
.ok_or_else(|| FormatError::Invalid("pdf: 'startxref' not found".into()))?;
let mut visited = HashSet::new();
self.read_xref_from(start, &mut visited)?;
for xref in &self.xrefs {
if xref.trailer.contains_key("Root") {
self.trailer = xref.trailer.clone();
break;
}
}
if self.trailer.is_empty() {
if let Some(first) = self.xrefs.first() {
self.trailer = first.trailer.clone();
}
}
if !self.trailer.contains_key("Root") {
return Err(FormatError::Invalid("pdf: no /Root in trailer".into()));
}
Ok(())
}
fn find_startxref(&self) -> Option<usize> {
let needle = b"startxref";
let hay = &self.data;
let idx = hay.windows(needle.len()).rposition(|w| w == needle)?;
let mut lex = Lexer::at(hay, idx + needle.len());
match lex.next_token().ok()?? {
Token::Int(n) if n >= 0 => Some(n as usize),
_ => None,
}
}
fn read_xref_from(&mut self, start: usize, visited: &mut HashSet<usize>) -> Result<()> {
if !visited.insert(start) || start >= self.data.len() {
return Ok(());
}
let mut lex = Lexer::at(&self.data, start);
let save = lex.pos;
let first = lex.next_token()?;
let xref = match first {
Some(Token::Keyword(ref k)) if k == b"xref" => self.read_classic_xref(&mut lex)?,
Some(Token::Int(_)) => {
lex.pos = save;
self.read_xref_stream(&mut lex)?
}
other => {
return Err(FormatError::Invalid(format!(
"pdf: expected xref at offset {}, got {:?}",
start, other
)));
}
};
let trailer = xref.trailer.clone();
self.xrefs.push(xref);
if let Some(Object::Int(pos)) = trailer.get("XRefStm") {
if *pos >= 0 {
self.read_xref_from(*pos as usize, visited)?;
}
}
if let Some(Object::Int(pos)) = trailer.get("Prev") {
if *pos >= 0 {
self.read_xref_from(*pos as usize, visited)?;
}
}
Ok(())
}
fn read_classic_xref(&self, lex: &mut Lexer) -> Result<XRef> {
let mut xref = XRef::default();
loop {
match lex.next_token()? {
Some(Token::Keyword(ref k)) if k == b"trailer" => break,
Some(Token::Int(start)) => {
let count = expect_int(lex)?;
if start < 0 || count < 0 {
return Err(FormatError::Invalid("pdf: negative xref subsection".into()));
}
for i in 0..count as u32 {
let offset = expect_int(lex)?;
let _gen = expect_int(lex)?;
let kind = match lex.next_token()? {
Some(Token::Keyword(k)) => k,
other => {
return Err(FormatError::Invalid(format!(
"pdf: bad xref entry type {:?}",
other
)));
}
};
if kind == b"n" && offset >= 0 {
xref.offsets.insert(
start as u32 + i,
XRefEntry::Uncompressed {
offset: offset as usize,
},
);
}
}
}
other => {
return Err(FormatError::Invalid(format!(
"pdf: unexpected token in xref table: {:?}",
other
)));
}
}
}
let dict = parse_object(lex, Some(self))?;
xref.trailer = match dict {
Object::Dict(d) => d,
_ => return Err(FormatError::Invalid("pdf: trailer is not a dict".into())),
};
Ok(xref)
}
fn read_xref_stream(&self, lex: &mut Lexer) -> Result<XRef> {
let _objid = expect_int(lex)?;
let _genno = expect_int(lex)?;
match lex.next_token()? {
Some(Token::Keyword(ref k)) if k == b"obj" => {}
other => {
return Err(FormatError::Invalid(format!(
"pdf: expected 'obj' for xref stream, got {:?}",
other
)));
}
}
let obj = parse_object(lex, Some(self))?;
let stream = match obj {
Object::Stream(s) => s,
_ => {
return Err(FormatError::Invalid(
"pdf: xref object is not a stream".into(),
))
}
};
let dict = &stream.dict;
let size = dict
.get("Size")
.and_then(Object::as_int)
.ok_or_else(|| FormatError::Invalid("pdf: xref stream missing /Size".into()))?;
let widths: Vec<i64> = dict
.get("W")
.and_then(Object::as_array)
.map(|a| a.iter().filter_map(Object::as_int).collect())
.unwrap_or_default();
if widths.len() != 3 {
return Err(FormatError::Invalid(
"pdf: xref stream /W must have 3 ints".into(),
));
}
let (w1, w2, w3) = (widths[0] as usize, widths[1] as usize, widths[2] as usize);
let entlen = w1 + w2 + w3;
if entlen == 0 {
return Err(FormatError::Invalid("pdf: xref stream /W all zero".into()));
}
let index: Vec<i64> = match dict.get("Index").and_then(Object::as_array) {
Some(a) => a.iter().filter_map(Object::as_int).collect(),
None => vec![0, size],
};
let data = stream.decoded()?;
let mut xref = XRef::default();
let mut pos = 0usize;
let mut pair = index.chunks(2);
while let Some(&[first, count]) = pair.next() {
for k in 0..count.max(0) {
if pos + entlen > data.len() {
break;
}
let ent = &data[pos..pos + entlen];
pos += entlen;
let f1 = if w1 == 0 { 1 } else { nunpack(&ent[0..w1]) };
let f2 = nunpack(&ent[w1..w1 + w2]);
let f3 = nunpack(&ent[w1 + w2..]);
let objid = (first + k) as u32;
match f1 {
1 => {
xref.offsets.insert(
objid,
XRefEntry::Uncompressed {
offset: f2 as usize,
},
);
}
2 => {
xref.offsets.insert(
objid,
XRefEntry::InObjStm {
stmid: f2 as u32,
index: f3 as usize,
},
);
}
_ => { }
}
}
}
xref.trailer = stream.dict;
Ok(xref)
}
}
fn decipher_all(f: &DecipherFn, objid: u32, genno: u16, obj: Object) -> Object {
match obj {
Object::Str(bytes) => Object::Str(f(objid, genno, &bytes)),
Object::Array(items) => Object::Array(
items
.into_iter()
.map(|v| decipher_all(f, objid, genno, v))
.collect(),
),
Object::Dict(dict) => Object::Dict(
dict.into_iter()
.map(|(k, v)| (k, decipher_all(f, objid, genno, v)))
.collect(),
),
Object::Stream(mut s) => {
s.rawdata = f(objid, genno, &s.rawdata);
Object::Stream(s)
}
other => other,
}
}
fn nunpack(bytes: &[u8]) -> u64 {
let mut v = 0u64;
for &b in bytes {
v = v << 8 | b as u64;
}
v
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pdf::PdfSerializer;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::io::Write;
fn zlib(data: &[u8]) -> Vec<u8> {
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data).unwrap();
e.finish().unwrap()
}
fn build_classic_pdf() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"%PDF-1.4\n");
let mut offsets = Vec::new();
offsets.push((1u32, buf.len()));
buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
offsets.push((2u32, buf.len()));
buf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
offsets.push((3u32, buf.len()));
buf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n",
);
let xref_pos = buf.len();
buf.extend_from_slice(b"xref\n0 4\n");
buf.extend_from_slice(b"0000000000 65535 f \n");
for (_, off) in &offsets {
buf.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
}
buf.extend_from_slice(b"trailer\n<< /Size 4 /Root 1 0 R >>\n");
buf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_pos).as_bytes());
buf
}
#[test]
fn parse_classic_pdf() {
let pdf = build_classic_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
assert_eq!(doc.object_ids(), vec![1, 2, 3]);
let root = doc.trailer().get("Root").cloned().unwrap();
let catalog = doc.resolve(&root).unwrap();
assert_eq!(catalog.type_name(), Some("Catalog"));
let pages = doc
.resolve(catalog.as_dict().unwrap().get("Pages").unwrap())
.unwrap();
assert_eq!(pages.as_dict().unwrap().get("Count"), Some(&Object::Int(1)));
assert!(doc.encrypt().is_none());
}
#[test]
fn serialize_roundtrip_preserves_content() {
let pdf = build_classic_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
let out = PdfSerializer::new(&doc).serialize().unwrap();
let doc2 = PdfDocument::parse(&out).unwrap();
assert_eq!(doc.object_ids(), doc2.object_ids());
for id in doc.object_ids() {
assert_eq!(
doc.get_object(id).unwrap(),
doc2.get_object(id).unwrap(),
"object {} differs after round trip",
id
);
}
let page = doc2.get_object(3).unwrap();
assert_eq!(
page.as_dict().unwrap().get("MediaBox"),
Some(&Object::Array(vec![
Object::Int(0),
Object::Int(0),
Object::Int(612),
Object::Int(792),
]))
);
}
fn build_xref_stream_pdf() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"%PDF-1.5\n");
let member = b"<< /Type /Catalog /Pages 3 0 R >>";
let header = b"1 0 "; let (objstm_plain, first) = {
let mut d = Vec::new();
d.extend_from_slice(header);
let first = d.len();
d.extend_from_slice(member);
(d, first)
};
let objstm_comp = zlib(&objstm_plain);
let obj2_off = buf.len();
buf.extend_from_slice(
format!(
"2 0 obj\n<< /Type /ObjStm /N 1 /First {} /Length {} /Filter /FlateDecode >>\nstream\n",
first,
objstm_comp.len()
)
.as_bytes(),
);
buf.extend_from_slice(&objstm_comp);
buf.extend_from_slice(b"\nendstream\nendobj\n");
let obj3_off = buf.len();
buf.extend_from_slice(b"3 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
let obj4_off = buf.len();
let mut xref_data = Vec::new();
let push_entry = |v: &mut Vec<u8>, f1: u8, f2: u16, f3: u16| {
v.push(f1);
v.extend_from_slice(&f2.to_be_bytes());
v.extend_from_slice(&f3.to_be_bytes());
};
push_entry(&mut xref_data, 0, 0, 0);
push_entry(&mut xref_data, 2, 2, 0);
push_entry(&mut xref_data, 1, obj2_off as u16, 0);
push_entry(&mut xref_data, 1, obj3_off as u16, 0);
push_entry(&mut xref_data, 1, obj4_off as u16, 0);
let xref_comp = zlib(&xref_data);
buf.extend_from_slice(
format!(
"4 0 obj\n<< /Type /XRef /Size 5 /Root 1 0 R /W [1 2 2] /Filter /FlateDecode /Length {} >>\nstream\n",
xref_comp.len()
)
.as_bytes(),
);
buf.extend_from_slice(&xref_comp);
buf.extend_from_slice(b"\nendstream\nendobj\n");
buf.extend_from_slice(format!("startxref\n{}\n%%EOF", obj4_off).as_bytes());
buf
}
#[test]
fn parse_xref_stream_and_objstm() {
let pdf = build_xref_stream_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
let catalog = doc.get_object(1).unwrap();
assert_eq!(catalog.type_name(), Some("Catalog"));
let pages = doc
.resolve(catalog.as_dict().unwrap().get("Pages").unwrap())
.unwrap();
assert_eq!(pages.type_name(), Some("Pages"));
assert_eq!(doc.trailer().get("Root"), Some(&Object::Ref(1, 0)));
}
#[test]
fn xref_stream_serializes_to_classic() {
let pdf = build_xref_stream_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
let out = PdfSerializer::new(&doc).serialize().unwrap();
let doc2 = PdfDocument::parse(&out).unwrap();
let catalog = doc2.get_object(1).unwrap();
assert_eq!(catalog.type_name(), Some("Catalog"));
let pages = doc2
.resolve(catalog.as_dict().unwrap().get("Pages").unwrap())
.unwrap();
assert_eq!(pages.type_name(), Some("Pages"));
}
fn build_self_referential_objstm_pdf() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"%PDF-1.5\n");
let obj2_off = buf.len();
let mut xref_data = Vec::new();
let push_entry = |v: &mut Vec<u8>, f1: u8, f2: u16, f3: u16| {
v.push(f1);
v.extend_from_slice(&f2.to_be_bytes());
v.extend_from_slice(&f3.to_be_bytes());
};
push_entry(&mut xref_data, 0, 0, 0);
push_entry(&mut xref_data, 2, 1, 0);
push_entry(&mut xref_data, 1, obj2_off as u16, 0);
let xref_comp = zlib(&xref_data);
buf.extend_from_slice(
format!(
"2 0 obj\n<< /Type /XRef /Size 3 /Root 1 0 R /W [1 2 2] /Filter /FlateDecode /Length {} >>\nstream\n",
xref_comp.len()
)
.as_bytes(),
);
buf.extend_from_slice(&xref_comp);
buf.extend_from_slice(b"\nendstream\nendobj\n");
buf.extend_from_slice(format!("startxref\n{}\n%%EOF", obj2_off).as_bytes());
buf
}
fn build_large_stream_pdf() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"%PDF-1.4\n");
let mut offsets = Vec::new();
offsets.push((1u32, buf.len()));
buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
offsets.push((2u32, buf.len()));
buf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
offsets.push((3u32, buf.len()));
buf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /Contents 4 0 R >>\nendobj\n",
);
let body = vec![b'A'; 200_000];
offsets.push((4u32, buf.len()));
buf.extend_from_slice(
format!("4 0 obj\n<< /Length {} >>\nstream\n", body.len()).as_bytes(),
);
buf.extend_from_slice(&body);
buf.extend_from_slice(b"\nendstream\nendobj\n");
let xref_pos = buf.len();
buf.extend_from_slice(b"xref\n0 5\n");
buf.extend_from_slice(b"0000000000 65535 f \n");
for (_, off) in &offsets {
buf.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
}
buf.extend_from_slice(b"trailer\n<< /Size 5 /Root 1 0 R >>\n");
buf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_pos).as_bytes());
buf
}
#[test]
fn repeated_get_object_shares_one_allocation() {
let pdf = build_large_stream_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
let h1 = doc.get_object(4).unwrap();
let h2 = doc.get_object(4).unwrap();
assert!(
Rc::ptr_eq(&h1, &h2),
"repeated get_object should share one Rc<Object>, not re-clone rawdata"
);
assert_eq!(h1.as_stream().unwrap().rawdata.len(), 200_000);
let via_ref = doc.resolve(&Object::Ref(4, 0)).unwrap();
assert!(Rc::ptr_eq(&h1, &via_ref));
}
#[test]
fn self_referential_objstm_errors_without_overflow() {
let pdf = build_self_referential_objstm_pdf();
let doc = PdfDocument::parse(&pdf).unwrap();
let err = doc.get_object(1).unwrap_err();
assert!(
matches!(&err, FormatError::Invalid(m) if m.contains("cycle")),
"expected a cycle error, got {:?}",
err
);
}
}