1use crate::error::{Error, Result};
25use crate::limits::Limits;
26
27use super::cos::{
28 FilterClass, LengthValue, body_as_u64, dict_filter, dict_has_length, dict_int_or_ref,
29 dict_length, dict_name_value,
30};
31use super::lexer::lex;
32use super::span::{Span, SpanKind};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum PhysicalKind {
37 Header,
39 Comment,
41 Whitespace,
43 ObjHeader,
45 ObjBody,
47 EndObj,
49 StreamData,
51 XrefSection,
53 Trailer,
55 StartXref,
57 Eof,
59 Unclassified,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct PhysicalSpan {
66 pub start: u64,
68 pub len: u64,
70 pub kind: PhysicalKind,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum ObjRole {
82 Generic,
84 XRefStream,
87 ObjectStream,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct PdfObjectSpan {
94 pub number: u64,
96 pub generation: u64,
98 pub start: u64,
100 pub end: u64,
102 pub role: ObjRole,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum LengthSource {
109 Direct,
111 Indirect,
113 Fallback,
115 Missing,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct PdfStreamSpan {
122 pub object: u64,
124 pub generation: u64,
126 pub data_start: u64,
128 pub data_len: u64,
130 pub length_source: LengthSource,
132 pub filter: FilterClass,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct RevisionInfo {
139 pub index: u32,
141 pub start: u64,
143 pub end: u64,
145 pub startxref: Option<u64>,
147 pub prev: Option<u64>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Default)]
154pub struct PdfPhysical {
155 pub spans: Vec<PhysicalSpan>,
157 pub objects: Vec<PdfObjectSpan>,
159 pub streams: Vec<PdfStreamSpan>,
161 pub revisions: Vec<RevisionInfo>,
163 pub startxref: Vec<u64>,
165 pub eofs: Vec<u64>,
167 pub header: Option<(u64, u64)>,
169}
170
171impl PdfPhysical {
172 pub fn total_len(&self) -> u64 {
174 self.spans
175 .iter()
176 .fold(0u64, |acc, s| acc.saturating_add(s.len))
177 }
178
179 pub fn validate(&self, declared_len: u64) -> Result<()> {
185 let mut cursor: u64 = 0;
186 for (i, span) in self.spans.iter().enumerate() {
187 if span.len == 0 {
188 return Err(Error::invalid_pdf_structure(format!(
189 "physical span {i} has zero length at offset {}",
190 span.start
191 )));
192 }
193 if span.start != cursor {
194 let why = if span.start < cursor {
195 "overlap"
196 } else {
197 "gap"
198 };
199 return Err(Error::coverage_violation(format!(
200 "physical span {i} {why}: expected start {cursor}, found {}",
201 span.start
202 )));
203 }
204 cursor = cursor.checked_add(span.len).ok_or_else(|| {
205 Error::coverage_violation("physical span lengths overflow the address space")
206 })?;
207 }
208 if cursor != declared_len {
209 return Err(Error::coverage_violation(format!(
210 "physical cover ends at {cursor}, declared length is {declared_len}"
211 )));
212 }
213 Ok(())
214 }
215}
216
217pub fn scan(input: &[u8], limits: Limits) -> Result<PdfPhysical> {
219 let declared_len = input.len() as u64;
220 let lexed = lex(input, limits)?;
221 let spans = lexed.spans.spans;
222 let dm = DictMatch::build(&spans);
223
224 let obj_bodies = collect_bodies(input, &spans);
227 let body_index = build_body_index(&obj_bodies);
228 let mut state = ScanState::new(limits, body_index);
229 let t_loop = std::time::Instant::now();
230 let mut niter: u64 = 0;
231 let mut i = 0usize;
232 while i < spans.len() {
233 niter += 1;
234 let sp = spans[i];
235 let bytes = bytes_of(input, sp);
236
237 if state.header.is_none()
239 && sp.start == 0
240 && sp.kind == SpanKind::Comment
241 && bytes.starts_with(b"%PDF-")
242 {
243 state.push(sp.start, sp.len, PhysicalKind::Header)?;
244 state.header = Some((sp.start, sp.len));
245 i += 1;
246 continue;
247 }
248
249 if sp.kind == SpanKind::Comment && bytes.starts_with(b"%%EOF") {
251 state.push(sp.start, sp.len, PhysicalKind::Eof)?;
252 state.eofs.push(sp.start);
253 i += 1;
254 continue;
255 }
256
257 if sp.kind == SpanKind::Regular {
259 if bytes == b"startxref" {
260 if let Some(next) = state.try_startxref(input, &spans, i)? {
261 i = next;
262 continue;
263 }
264 } else if bytes == b"xref" {
265 i = state.emit_xref(input, &spans, i, &dm)?;
266 continue;
267 } else if bytes == b"trailer" {
268 i = state.emit_trailer(&spans, i, &dm)?;
269 continue;
270 } else if let Some((number, generation)) = obj_header_at(input, &spans, i) {
271 i = state.emit_object(input, &spans, i, number, generation, &dm)?;
272 continue;
273 }
274 }
275
276 let kind = match sp.kind {
278 SpanKind::Comment => PhysicalKind::Comment,
279 SpanKind::Whitespace => PhysicalKind::Whitespace,
280 _ => PhysicalKind::Unclassified,
281 };
282 state.push(sp.start, sp.len, kind)?;
283 i += 1;
284 }
285
286 if std::env::var_os("VOLE_PROPOSE_TRACE").is_some() {
287 eprintln!(
288 "[scan] loop={}ms iters={} nspans={}",
289 t_loop.elapsed().as_millis(),
290 niter,
291 spans.len()
292 );
293 }
294 let physical = state.finish(input, &spans, &dm);
295 physical.validate(declared_len)?;
296 Ok(physical)
297}
298
299struct ScanState {
301 builder: Builder,
302 objects: Vec<PdfObjectSpan>,
303 streams: Vec<PdfStreamSpan>,
304 body_index: BodyIndex,
305 startxref: Vec<(u64, u64)>,
306 eofs: Vec<u64>,
307 trailer_dicts: Vec<(u64, u64)>,
308 header: Option<(u64, u64)>,
309}
310
311impl ScanState {
312 fn new(limits: Limits, body_index: BodyIndex) -> Self {
313 ScanState {
314 builder: Builder::new(limits),
315 objects: Vec::new(),
316 streams: Vec::new(),
317 body_index,
318 startxref: Vec::new(),
319 eofs: Vec::new(),
320 trailer_dicts: Vec::new(),
321 header: None,
322 }
323 }
324
325 fn push(&mut self, start: u64, len: u64, kind: PhysicalKind) -> Result<()> {
326 self.builder.push(start, len, kind)
327 }
328
329 fn finish(self, input: &[u8], spans: &[Span], dm: &DictMatch) -> PdfPhysical {
330 let revisions = build_revisions(
331 input,
332 spans,
333 &self.eofs,
334 &self.objects,
335 &self.startxref,
336 &self.trailer_dicts,
337 dm,
338 );
339 PdfPhysical {
340 spans: self.builder.spans,
341 objects: self.objects,
342 streams: self.streams,
343 revisions,
344 startxref: self.startxref.iter().map(|&(_, value)| value).collect(),
345 eofs: self.eofs,
346 header: self.header,
347 }
348 }
349
350 fn emit_object(
353 &mut self,
354 input: &[u8],
355 spans: &[Span],
356 i: usize,
357 number: u64,
358 generation: u64,
359 dm: &DictMatch,
360 ) -> Result<usize> {
361 let obj_header_start = spans[i].start;
362 let obj_kw_end = spans[i + 4].start + spans[i + 4].len;
363 let role = leading_dict_role(dm, input, spans, i + 5);
364 self.push(
365 obj_header_start,
366 obj_kw_end - obj_header_start,
367 PhysicalKind::ObjHeader,
368 )?;
369
370 let mut stream: Option<ResolvedStream> = None;
374 let mut endobj: Option<usize> = None;
375 let mut j = i + 5;
376 while j < spans.len() {
377 if regular_eq(input, spans[j], b"endobj") {
378 endobj = Some(j);
379 break;
380 }
381 if stream.is_none()
382 && regular_eq(input, spans[j], b"stream")
383 && let Some(rs) = resolve_stream(input, spans, j, &self.body_index, i + 5, dm)
384 {
385 j = rs.endstream + 1;
386 stream = Some(rs);
387 continue;
388 }
389 j += 1;
390 }
391
392 match endobj {
393 Some(m) => {
394 if let Some(rs) = stream {
395 if rs.data_start > obj_kw_end {
396 self.push(
397 obj_kw_end,
398 rs.data_start - obj_kw_end,
399 PhysicalKind::ObjBody,
400 )?;
401 }
402 if rs.data_len > 0 {
403 self.push(rs.data_start, rs.data_len, PhysicalKind::StreamData)?;
404 }
405 let data_end = rs.data_start + rs.data_len;
408 let body_end = spans[m].start;
409 if body_end > data_end {
410 self.push(data_end, body_end - data_end, PhysicalKind::ObjBody)?;
411 }
412 self.streams.push(PdfStreamSpan {
413 object: number,
414 generation,
415 data_start: rs.data_start,
416 data_len: rs.data_len,
417 length_source: rs.source,
418 filter: rs.filter,
419 });
420 } else {
421 let body_end = spans[m].start;
422 if body_end > obj_kw_end {
423 self.push(obj_kw_end, body_end - obj_kw_end, PhysicalKind::ObjBody)?;
424 }
425 }
426
427 self.push(spans[m].start, spans[m].len, PhysicalKind::EndObj)?;
428 self.objects.push(PdfObjectSpan {
429 number,
430 generation,
431 start: obj_header_start,
432 end: spans[m].start + spans[m].len,
433 role,
434 });
435 Ok(m + 1)
436 }
437 None => {
438 let end = input.len() as u64;
440 if end > obj_kw_end {
441 self.push(obj_kw_end, end - obj_kw_end, PhysicalKind::ObjBody)?;
442 }
443 Ok(spans.len())
444 }
445 }
446 }
447
448 fn emit_xref(
451 &mut self,
452 input: &[u8],
453 spans: &[Span],
454 i: usize,
455 dm: &DictMatch,
456 ) -> Result<usize> {
457 let start = spans[i].start;
458 let mut end_idx = spans.len();
459 for (k, sp) in spans.iter().enumerate().skip(i + 1) {
460 let sp = *sp;
461 if regular_eq(input, sp, b"trailer") || regular_eq(input, sp, b"startxref") {
462 end_idx = k;
463 break;
464 }
465 if sp.kind == SpanKind::Comment && bytes_of(input, sp).starts_with(b"%%EOF") {
466 end_idx = k;
467 break;
468 }
469 }
470
471 let end = if end_idx < spans.len() {
472 spans[end_idx].start
473 } else {
474 input.len() as u64
475 };
476 if end > start {
477 self.push(start, end - start, PhysicalKind::XrefSection)?;
478 }
479
480 if end_idx < spans.len() && regular_eq(input, spans[end_idx], b"trailer") {
481 self.emit_trailer(spans, end_idx, dm)
482 } else {
483 Ok(end_idx)
484 }
485 }
486
487 fn emit_trailer(&mut self, spans: &[Span], t: usize, dm: &DictMatch) -> Result<usize> {
490 let start = spans[t].start;
491 let mut end = spans[t].start + spans[t].len;
492 let mut next = t + 1;
493 let dict_idx = if next < spans.len() && spans[next].kind == SpanKind::Whitespace {
494 next + 1
495 } else {
496 next
497 };
498 if dict_idx < spans.len()
499 && spans[dict_idx].kind == SpanKind::DictOpen
500 && let Some(close) = dm.matching(dict_idx)
501 {
502 let lo = spans[dict_idx].start;
503 let hi = spans[close].start + spans[close].len;
504 self.trailer_dicts.push((lo, hi));
505 end = hi;
506 next = close + 1;
507 }
508 if end > start {
509 self.push(start, end - start, PhysicalKind::Trailer)?;
510 }
511 Ok(next)
512 }
513
514 fn try_startxref(&mut self, input: &[u8], spans: &[Span], i: usize) -> Result<Option<usize>> {
516 if i + 2 < spans.len()
517 && spans[i + 1].kind == SpanKind::Whitespace
518 && spans[i + 2].kind == SpanKind::Regular
519 && let Some(value) = parse_uint(bytes_of(input, spans[i + 2]), 19)
520 {
521 let start = spans[i].start;
522 let end = spans[i + 2].start + spans[i + 2].len;
523 self.push(start, end - start, PhysicalKind::StartXref)?;
524 self.startxref.push((start, value));
525 return Ok(Some(i + 3));
526 }
527 Ok(None)
528 }
529}
530
531struct Builder {
534 max: u32,
535 spans: Vec<PhysicalSpan>,
536}
537
538impl Builder {
539 fn new(limits: Limits) -> Self {
540 Builder {
541 max: limits.max_pdf_spans,
542 spans: Vec::new(),
543 }
544 }
545
546 fn push(&mut self, start: u64, len: u64, kind: PhysicalKind) -> Result<()> {
547 if len == 0 {
548 return Ok(());
549 }
550 if let Some(last) = self.spans.last_mut()
551 && last.kind == kind
552 && last.start.checked_add(last.len) == Some(start)
553 {
554 last.len = last
555 .len
556 .checked_add(len)
557 .ok_or_else(|| Error::coverage_violation("physical span length overflow"))?;
558 return Ok(());
559 }
560 if self.spans.len() as u64 >= self.max as u64 {
561 return Err(Error::resource_limit(format!(
562 "pdf physical span count exceeds limit {}",
563 self.max
564 )));
565 }
566 self.spans.push(PhysicalSpan { start, len, kind });
567 Ok(())
568 }
569}
570
571fn bytes_of(input: &[u8], sp: Span) -> &[u8] {
573 let Ok(start) = usize::try_from(sp.start) else {
574 return &[];
575 };
576 let Some(end) = sp
577 .start
578 .checked_add(sp.len)
579 .and_then(|e| usize::try_from(e).ok())
580 else {
581 return &[];
582 };
583 if start > end || end > input.len() {
584 return &[];
585 }
586 &input[start..end]
587}
588
589fn regular_eq(input: &[u8], sp: Span, keyword: &[u8]) -> bool {
591 sp.kind == SpanKind::Regular && bytes_of(input, sp) == keyword
592}
593
594fn parse_uint(bytes: &[u8], max_digits: usize) -> Option<u64> {
596 if bytes.is_empty() || bytes.len() > max_digits {
597 return None;
598 }
599 let mut value: u64 = 0;
600 for &b in bytes {
601 if !b.is_ascii_digit() {
602 return None;
603 }
604 value = value.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
605 }
606 Some(value)
607}
608
609fn eol_start_after(input: &[u8], offset: u64) -> Option<u64> {
611 let start = usize::try_from(offset).ok()?;
612 if start > input.len() {
613 return None;
614 }
615 (start..input.len())
616 .find(|&i| input[i] == b'\n' || input[i] == b'\r')
617 .map(|i| i as u64)
618}
619
620fn find_regular(input: &[u8], spans: &[Span], from: usize, keyword: &[u8]) -> Option<usize> {
622 if from >= spans.len() {
623 return None;
624 }
625 spans[from..]
626 .iter()
627 .position(|sp| regular_eq(input, *sp, keyword))
628 .map(|off| from + off)
629}
630
631struct DictMatch {
641 close: Vec<u32>,
642}
643
644impl DictMatch {
645 fn build(spans: &[Span]) -> Self {
646 let mut close = vec![u32::MAX; spans.len()];
647 let mut stack: Vec<u32> = Vec::new();
648 for (k, sp) in spans.iter().enumerate() {
649 match sp.kind {
650 SpanKind::DictOpen => stack.push(k as u32),
651 SpanKind::DictClose => {
652 if let Some(o) = stack.pop() {
653 close[o as usize] = k as u32;
654 }
655 }
656 _ => {}
657 }
658 }
659 DictMatch { close }
660 }
661
662 fn matching(&self, open: usize) -> Option<usize> {
664 match self.close.get(open).copied() {
665 Some(c) if c != u32::MAX => Some(c as usize),
666 _ => None,
667 }
668 }
669}
670
671fn obj_header_at(input: &[u8], spans: &[Span], i: usize) -> Option<(u64, u64)> {
673 if i + 4 >= spans.len() {
674 return None;
675 }
676 if spans[i].kind != SpanKind::Regular {
677 return None;
678 }
679 let number = parse_uint(bytes_of(input, spans[i]), 10)?;
680 if spans[i + 1].kind != SpanKind::Whitespace {
681 return None;
682 }
683 if spans[i + 2].kind != SpanKind::Regular {
684 return None;
685 }
686 let generation = parse_uint(bytes_of(input, spans[i + 2]), 10)?;
687 if spans[i + 3].kind != SpanKind::Whitespace {
688 return None;
689 }
690 if !regular_eq(input, spans[i + 4], b"obj") {
691 return None;
692 }
693 Some((number, generation))
694}
695
696#[derive(Debug, Clone, Copy)]
702struct ObjBody {
703 number: u64,
704 generation: u64,
705 body_lo: u64,
706 body_hi: u64,
707}
708
709#[derive(Debug, Clone, Copy)]
711struct ResolvedStream {
712 data_start: u64,
713 data_len: u64,
714 endstream: usize,
715 source: LengthSource,
716 filter: FilterClass,
717}
718
719fn collect_bodies(input: &[u8], spans: &[Span]) -> Vec<ObjBody> {
723 let mut out = Vec::new();
724 let mut i = 0usize;
725 while i < spans.len() {
726 if let Some((number, generation)) = obj_header_at(input, spans, i) {
727 let body_lo = spans[i + 4].start + spans[i + 4].len;
728 match find_endobj(input, spans, i + 5) {
729 Some(m) => {
730 out.push(ObjBody {
731 number,
732 generation,
733 body_lo,
734 body_hi: spans[m].start,
735 });
736 i = m + 1;
737 }
738 None => {
739 out.push(ObjBody {
740 number,
741 generation,
742 body_lo,
743 body_hi: input.len() as u64,
744 });
745 i = spans.len();
746 }
747 }
748 } else {
749 i += 1;
750 }
751 }
752 out
753}
754
755fn find_endobj(input: &[u8], spans: &[Span], from: usize) -> Option<usize> {
758 let mut j = from;
759 while j < spans.len() {
760 if regular_eq(input, spans[j], b"endobj") {
761 return Some(j);
762 }
763 if regular_eq(input, spans[j], b"stream") {
764 let kw_end = spans[j].start + spans[j].len;
765 if let Some(data_start) = eol_start_after(input, kw_end)
766 && let Some(k) = find_regular(input, spans, j + 1, b"endstream")
767 && spans[k].start >= data_start
768 {
769 j = k + 1;
770 continue;
771 }
772 }
773 j += 1;
774 }
775 None
776}
777
778type BodyIndex = std::collections::HashMap<(u64, u64), (u64, u64)>;
785
786fn build_body_index(bodies: &[ObjBody]) -> BodyIndex {
787 let mut index = BodyIndex::with_capacity(bodies.len());
788 for b in bodies {
789 index
790 .entry((b.number, b.generation))
791 .or_insert((b.body_lo, b.body_hi));
792 }
793 index
794}
795
796fn resolve_stream(
802 input: &[u8],
803 spans: &[Span],
804 s: usize,
805 body_index: &BodyIndex,
806 lower: usize,
807 dm: &DictMatch,
808) -> Option<ResolvedStream> {
809 let kw_end = spans[s].start + spans[s].len;
810
811 let mut resolved: Option<ResolvedStream> = None;
812 let mut source = LengthSource::Fallback;
813 let mut filter = FilterClass::Absent;
814
815 if let Some((open, close)) = preceding_dict(spans, s, lower, dm) {
816 let dict_lo = spans[open].start;
817 let dict_hi = spans[close].start + spans[close].len;
818 filter = dict_filter(input, spans, dict_lo, dict_hi);
819 match dict_length(input, spans, dict_lo, dict_hi) {
820 Some(LengthValue::Direct(n)) => {
821 if let Some(rs) = verify_direct(input, spans, kw_end, n, filter) {
822 resolved = Some(rs);
823 source = LengthSource::Direct;
824 }
825 }
826 Some(LengthValue::Indirect { number, generation }) => {
827 if let Some(&(lo, hi)) = body_index.get(&(number, generation))
828 && let Some(n) = body_as_u64(input, spans, lo, hi)
829 && let Some(rs) = verify_direct(input, spans, kw_end, n, filter)
830 {
831 resolved = Some(rs);
832 source = LengthSource::Indirect;
833 }
834 }
835 None => {
836 if !dict_has_length(input, spans, dict_lo, dict_hi) {
837 source = LengthSource::Missing;
838 }
839 }
840 }
841 } else {
842 source = LengthSource::Missing;
843 }
844
845 if let Some(mut rs) = resolved {
846 rs.source = source;
847 return Some(rs);
848 }
849
850 let data_start = eol_start_after(input, kw_end)?;
853 let k = find_regular(input, spans, s + 1, b"endstream")?;
854 if spans[k].start < data_start {
855 return None;
856 }
857 Some(ResolvedStream {
858 data_start,
859 data_len: spans[k].start - data_start,
860 endstream: k,
861 source,
862 filter,
863 })
864}
865
866fn preceding_dict(
870 spans: &[Span],
871 s: usize,
872 lower: usize,
873 dm: &DictMatch,
874) -> Option<(usize, usize)> {
875 let mut j = s;
876 while j > lower {
877 j -= 1;
878 if spans[j].kind == SpanKind::DictOpen
879 && let Some(close) = dm.matching(j)
880 && close < s
881 {
882 return Some((j, close));
883 }
884 }
885 None
886}
887
888fn verify_direct(
892 input: &[u8],
893 spans: &[Span],
894 kw_end: u64,
895 n: u64,
896 filter: FilterClass,
897) -> Option<ResolvedStream> {
898 let eol = post_stream_eol_len(input, kw_end)?;
899 let data_start = kw_end + eol;
900 let data_end = data_start.checked_add(n)?;
901 let k = first_regular_at_or_after(input, spans, data_end, b"endstream")?;
902 let gap = spans[k].start.checked_sub(data_end)?;
903 let ok = gap == 0
904 || (gap == 1 && byte_at(input, data_end) == Some(b'\n'))
905 || (gap == 2
906 && byte_at(input, data_end) == Some(b'\r')
907 && byte_at(input, data_end + 1) == Some(b'\n'));
908 if !ok {
909 return None;
910 }
911 Some(ResolvedStream {
912 data_start,
913 data_len: n,
914 endstream: k,
915 source: LengthSource::Direct,
916 filter,
917 })
918}
919
920fn post_stream_eol_len(input: &[u8], kw_end: u64) -> Option<u64> {
923 match byte_at(input, kw_end) {
924 Some(b'\n') => Some(1),
925 Some(b'\r') if byte_at(input, kw_end + 1) == Some(b'\n') => Some(2),
926 _ => None,
927 }
928}
929
930fn first_regular_at_or_after(
933 input: &[u8],
934 spans: &[Span],
935 offset: u64,
936 keyword: &[u8],
937) -> Option<usize> {
938 let idx = spans.partition_point(|sp| sp.start < offset);
939 spans[idx..]
940 .iter()
941 .position(|sp| regular_eq(input, *sp, keyword))
942 .map(|off| idx + off)
943}
944
945fn byte_at(input: &[u8], offset: u64) -> Option<u8> {
947 usize::try_from(offset)
948 .ok()
949 .and_then(|i| input.get(i).copied())
950}
951
952fn build_revisions(
956 input: &[u8],
957 spans: &[Span],
958 eofs: &[u64],
959 objects: &[PdfObjectSpan],
960 startxref: &[(u64, u64)],
961 trailers: &[(u64, u64)],
962 dm: &DictMatch,
963) -> Vec<RevisionInfo> {
964 let mut out = Vec::with_capacity(eofs.len());
965 let mut start = 0u64;
966 for (index, &e) in eofs.iter().enumerate() {
967 let end = span_end_at(spans, e).unwrap_or(e);
968 let startxref = startxref
969 .iter()
970 .find(|&&(keyword, _)| keyword >= start && keyword < end)
971 .map(|&(_, value)| value);
972 let prev = resolve_prev(input, spans, start, end, objects, trailers, dm);
973 out.push(RevisionInfo {
974 index: index as u32,
975 start,
976 end,
977 startxref,
978 prev,
979 });
980 start = end + eol_len_after(input, end);
981 }
982 out
983}
984
985fn resolve_prev(
991 input: &[u8],
992 spans: &[Span],
993 rev_start: u64,
994 rev_end: u64,
995 objects: &[PdfObjectSpan],
996 trailers: &[(u64, u64)],
997 dm: &DictMatch,
998) -> Option<u64> {
999 let trailer = trailers
1000 .iter()
1001 .rev()
1002 .find(|&&(lo, _)| lo >= rev_start && lo < rev_end);
1003 let (dict_lo, dict_hi) = match trailer {
1004 Some(&(lo, hi)) => (lo, hi),
1005 None => {
1006 let object = objects.iter().find(|o| {
1007 o.role == ObjRole::XRefStream && o.start >= rev_start && o.start < rev_end
1008 })?;
1009 leading_dict_range_at(dm, input, spans, object.start)?
1010 }
1011 };
1012 match dict_int_or_ref(input, spans, dict_lo, dict_hi, b"Prev") {
1013 Some(LengthValue::Direct(n)) => Some(n),
1014 Some(LengthValue::Indirect { number, generation }) => objects
1015 .iter()
1016 .find(|o| o.number == number && o.generation == generation)
1017 .map(|o| o.start),
1018 None => None,
1019 }
1020}
1021
1022fn leading_dict_range(spans: &[Span], after: usize, dm: &DictMatch) -> Option<(u64, u64)> {
1026 let mut j = after;
1027 while j < spans.len() && matches!(spans[j].kind, SpanKind::Whitespace | SpanKind::Comment) {
1028 j += 1;
1029 }
1030 if j >= spans.len() || spans[j].kind != SpanKind::DictOpen {
1031 return None;
1032 }
1033 let close = dm.matching(j)?;
1034 Some((spans[j].start, spans[close].start + spans[close].len))
1035}
1036
1037fn leading_dict_role(dm: &DictMatch, input: &[u8], spans: &[Span], after: usize) -> ObjRole {
1040 let Some((lo, hi)) = leading_dict_range(spans, after, dm) else {
1041 return ObjRole::Generic;
1042 };
1043 match dict_name_value(input, spans, lo, hi, b"Type") {
1044 Some(name) if name == b"XRef".as_slice() => ObjRole::XRefStream,
1045 Some(name) if name == b"ObjStm".as_slice() => ObjRole::ObjectStream,
1046 _ => ObjRole::Generic,
1047 }
1048}
1049
1050fn leading_dict_range_at(
1053 dm: &DictMatch,
1054 input: &[u8],
1055 spans: &[Span],
1056 start: u64,
1057) -> Option<(u64, u64)> {
1058 let idx = spans.partition_point(|sp| sp.start < start);
1059 if idx >= spans.len() || spans[idx].start != start {
1060 return None;
1061 }
1062 obj_header_at(input, spans, idx)?;
1063 leading_dict_range(spans, idx + 5, dm)
1064}
1065
1066fn span_end_at(spans: &[Span], offset: u64) -> Option<u64> {
1068 let idx = spans.partition_point(|sp| sp.start <= offset);
1069 if idx == 0 {
1070 return None;
1071 }
1072 let sp = spans[idx - 1];
1073 if offset < sp.start.saturating_add(sp.len) {
1074 Some(sp.start.saturating_add(sp.len))
1075 } else {
1076 None
1077 }
1078}
1079
1080fn eol_len_after(input: &[u8], offset: u64) -> u64 {
1083 match byte_at(input, offset) {
1084 Some(b'\n') => 1,
1085 Some(b'\r') if byte_at(input, offset + 1) == Some(b'\n') => 2,
1086 Some(b'\r') => 1,
1087 _ => 0,
1088 }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094 use crate::error::ErrorClass;
1095
1096 fn count(p: &PdfPhysical, kind: PhysicalKind) -> usize {
1097 p.spans.iter().filter(|s| s.kind == kind).count()
1098 }
1099
1100 fn canonical_pdf() -> Vec<u8> {
1101 let mut s = String::new();
1102 s.push_str("%PDF-1.7\n");
1103 s.push_str("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1104 s.push_str("2 0 obj\n<< /Length 6 >>\nstream\nhello\nendstream\nendobj\n");
1105 s.push_str("3 0 obj\n<< /Length 7 >>\nstream\nworld\nendstream\nendobj\n");
1106 s.push_str("4 0 obj\n<< /Length 4 >>\nstream\nxyz\nendstream\nendobj\n");
1107 s.push_str("xref\n0 5\n0000000000 65535 f \n0000000010 00000 n \n");
1108 s.push_str("trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n321\n%%EOF");
1109 s.into_bytes()
1110 }
1111
1112 #[test]
1113 fn canonical_pdf_is_fully_classified() {
1114 let pdf = canonical_pdf();
1115 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1116
1117 assert_eq!(p.header, Some((0, 8)));
1118 assert_eq!(p.objects.len(), 4);
1119 let nums: Vec<(u64, u64)> = p.objects.iter().map(|o| (o.number, o.generation)).collect();
1120 assert_eq!(nums, [(1, 0), (2, 0), (3, 0), (4, 0)]);
1121 assert_eq!(p.startxref, [321]);
1122 assert_eq!(p.eofs.len(), 1);
1123 assert_eq!(count(&p, PhysicalKind::EndObj), 4);
1124 assert_eq!(count(&p, PhysicalKind::StreamData), 3);
1125 assert_eq!(count(&p, PhysicalKind::XrefSection), 1);
1126 assert_eq!(count(&p, PhysicalKind::Trailer), 1);
1127 assert_eq!(count(&p, PhysicalKind::ObjHeader), 4);
1128 assert_eq!(p.total_len(), pdf.len() as u64);
1129 p.validate(pdf.len() as u64).unwrap();
1130 }
1131
1132 #[test]
1133 fn endobj_inside_literal_string_does_not_split_object() {
1134 let pdf = b"%PDF-1.4\n1 0 obj\n(endobj)\nendobj".to_vec();
1135 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1136
1137 assert_eq!(p.objects.len(), 1);
1138 assert_eq!(p.objects[0].number, 1);
1139 assert_eq!(p.objects[0].generation, 0);
1140 assert_eq!(p.objects[0].end, pdf.len() as u64);
1141 assert_eq!(count(&p, PhysicalKind::EndObj), 1);
1142 assert_eq!(count(&p, PhysicalKind::StreamData), 0);
1143 p.validate(pdf.len() as u64).unwrap();
1144 }
1145
1146 #[test]
1147 fn stream_data_with_keyword_spellings_stays_opaque() {
1148 let pdf =
1149 b"%PDF-1.4\n1 0 obj\n<< /Length 30 >>\nstream\nendobj stream bytes here\nendstream\nendobj"
1150 .to_vec();
1151 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1152
1153 assert_eq!(p.objects.len(), 1);
1154 assert_eq!(p.objects[0].number, 1);
1155 assert_eq!(p.objects[0].end, pdf.len() as u64);
1156 assert_eq!(count(&p, PhysicalKind::EndObj), 1);
1157 assert_eq!(count(&p, PhysicalKind::StreamData), 1);
1158
1159 let data = p
1161 .spans
1162 .iter()
1163 .find(|s| s.kind == PhysicalKind::StreamData)
1164 .unwrap();
1165 let slice = &pdf[data.start as usize..(data.start + data.len) as usize];
1166 assert!(slice.windows(6).any(|w| w == b"endobj"));
1167 assert!(slice.windows(6).any(|w| w == b"stream"));
1168 p.validate(pdf.len() as u64).unwrap();
1169 }
1170
1171 #[test]
1172 fn two_objects_with_xref_trailer_and_startxref() {
1173 let pdf = b"%PDF-1.4\n1 0 obj\n<< >>\nendobj\n2 0 obj\n<< >>\nendobj\nxref\n0 3\n0000000000 65535 f \n0000000009 00000 n \ntrailer\n<< /Size 3 >>\nstartxref\n99\n%%EOF".to_vec();
1174 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1175
1176 assert_eq!(p.objects.len(), 2);
1177 assert_eq!(p.objects[0].number, 1);
1178 assert_eq!(p.objects[1].number, 2);
1179 assert!(p.objects[0].end <= p.objects[1].start);
1180 assert_eq!(count(&p, PhysicalKind::XrefSection), 1);
1181 assert_eq!(count(&p, PhysicalKind::Trailer), 1);
1182 assert_eq!(p.startxref, [99]);
1183 assert_eq!(p.eofs.len(), 1);
1184 p.validate(pdf.len() as u64).unwrap();
1185 }
1186
1187 #[test]
1188 fn malformed_object_without_endobj_is_conservative() {
1189 let pdf = b"1 0 obj".to_vec();
1190 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1191
1192 assert!(p.objects.is_empty());
1193 assert_eq!(count(&p, PhysicalKind::ObjHeader), 1);
1194 assert_eq!(count(&p, PhysicalKind::EndObj), 0);
1195 assert_eq!(p.total_len(), pdf.len() as u64);
1196 p.validate(pdf.len() as u64).unwrap();
1197 }
1198
1199 #[test]
1200 fn missing_header_leaves_cover_total() {
1201 let pdf = b"1 0 obj\nendobj\n".to_vec();
1202 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1203
1204 assert_eq!(p.header, None);
1205 assert_eq!(count(&p, PhysicalKind::EndObj), 1);
1206 p.validate(pdf.len() as u64).unwrap();
1207 }
1208
1209 fn xorshift64(state: &mut u64) -> u64 {
1210 let mut x = *state;
1211 x ^= x << 13;
1212 x ^= x >> 7;
1213 x ^= x << 17;
1214 *state = x;
1215 x
1216 }
1217
1218 #[test]
1219 fn random_bytes_never_panic_and_keep_cover() {
1220 let mut state: u64 = 0x1234_5678_9abc_def0;
1221 for _ in 0..500 {
1222 let len = (xorshift64(&mut state) % 96) as usize;
1223 let mut buf = Vec::with_capacity(len);
1224 for _ in 0..len {
1225 buf.push((xorshift64(&mut state) & 0xff) as u8);
1226 }
1227 match scan(&buf, Limits::DEFAULT) {
1228 Ok(p) => {
1229 p.validate(buf.len() as u64).unwrap();
1230 assert_eq!(p.total_len(), buf.len() as u64);
1231 }
1232 Err(e) => {
1233 let _ = e.class();
1235 }
1236 }
1237 }
1238 }
1239
1240 #[test]
1241 fn tiny_span_limit_is_resource_limit() {
1242 let pdf = canonical_pdf();
1243 let limits = Limits {
1244 max_pdf_spans: 1,
1245 ..Limits::DEFAULT
1246 };
1247 let err = scan(&pdf, limits).unwrap_err();
1248 assert_eq!(err.class(), ErrorClass::ResourceLimit);
1249 }
1250
1251 fn offset_of(hay: &[u8], needle: &[u8]) -> u64 {
1252 hay.windows(needle.len())
1253 .position(|w| w == needle)
1254 .expect("needle present") as u64
1255 }
1256
1257 fn slice_of(pdf: &[u8], s: PdfStreamSpan) -> &[u8] {
1258 &pdf[s.data_start as usize..(s.data_start + s.data_len) as usize]
1259 }
1260
1261 fn only_stream(p: &PdfPhysical) -> PdfStreamSpan {
1262 assert_eq!(p.streams.len(), 1, "expected exactly one stream");
1263 p.streams[0]
1264 }
1265
1266 #[test]
1267 fn direct_length_yields_exact_span() {
1268 let pdf =
1269 b"%PDF-1.5\n1 0 obj\n<< /Length 5 >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1270 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1271
1272 let s = only_stream(&p);
1273 assert_eq!(s.object, 1);
1274 assert_eq!(s.generation, 0);
1275 assert_eq!(s.length_source, LengthSource::Direct);
1276 assert_eq!(s.data_start, offset_of(&pdf, b"hello"));
1277 assert_eq!(s.data_len, 5);
1278 assert_eq!(slice_of(&pdf, s), b"hello");
1279
1280 let ds = p
1281 .spans
1282 .iter()
1283 .find(|sp| sp.kind == PhysicalKind::StreamData)
1284 .unwrap();
1285 assert_eq!(ds.start, s.data_start);
1286 assert_eq!(ds.len, s.data_len);
1287 p.validate(pdf.len() as u64).unwrap();
1288 }
1289
1290 #[test]
1291 fn length_including_trailing_eol_is_accepted() {
1292 let pdf =
1293 b"%PDF-1.5\n1 0 obj\n<< /Length 6 >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1294 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1295 let s = only_stream(&p);
1296 assert_eq!(s.length_source, LengthSource::Direct);
1297 assert_eq!(slice_of(&pdf, s), b"hello\n");
1298 p.validate(pdf.len() as u64).unwrap();
1299 }
1300
1301 #[test]
1302 fn crlf_and_lf_after_stream_are_both_handled() {
1303 let crlf =
1304 b"%PDF-1.5\n1 0 obj\n<< /Length 5 >>\r\nstream\r\nhello\r\nendstream\r\nendobj\n"
1305 .to_vec();
1306 let p = scan(&crlf, Limits::DEFAULT).unwrap();
1307 let s = only_stream(&p);
1308 assert_eq!(s.length_source, LengthSource::Direct);
1309 assert_eq!(slice_of(&crlf, s), b"hello");
1310 assert_eq!(s.data_start, offset_of(&crlf, b"hello"));
1311 p.validate(crlf.len() as u64).unwrap();
1312
1313 let lf = b"%PDF-1.5\n1 0 obj\n<< /Length 5 >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1314 let p = scan(&lf, Limits::DEFAULT).unwrap();
1315 let s = only_stream(&p);
1316 assert_eq!(s.length_source, LengthSource::Direct);
1317 assert_eq!(slice_of(&lf, s), b"hello");
1318 p.validate(lf.len() as u64).unwrap();
1319 }
1320
1321 #[test]
1322 fn lone_cr_after_stream_falls_back() {
1323 let pdf =
1324 b"%PDF-1.5\n1 0 obj\n<< /Length 5 >>\nstream\rhello\r\nendstream\nendobj\n".to_vec();
1325 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1326 let s = only_stream(&p);
1327 assert_eq!(s.length_source, LengthSource::Fallback);
1328 assert!(slice_of(&pdf, s).windows(5).any(|w| w == b"hello"));
1329 p.validate(pdf.len() as u64).unwrap();
1330 }
1331
1332 #[test]
1333 fn indirect_length_is_resolved_forward_reference() {
1334 let pdf =
1336 b"%PDF-1.5\n1 0 obj\n<< /Length 5 0 R >>\nstream\nhello\nendstream\nendobj\n5 0 obj\n5\nendobj\n"
1337 .to_vec();
1338 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1339 let s = only_stream(&p);
1340 assert_eq!(s.length_source, LengthSource::Indirect);
1341 assert_eq!(s.data_start, offset_of(&pdf, b"hello"));
1342 assert_eq!(s.data_len, 5);
1343 assert_eq!(slice_of(&pdf, s), b"hello");
1344 p.validate(pdf.len() as u64).unwrap();
1345 }
1346
1347 #[test]
1348 fn stream_filter_classification_reads_flate_and_absent() {
1349 let flate = b"%PDF-1.5\n1 0 obj\n<< /Length 5 /Filter /FlateDecode >>\nstream\nhello\nendstream\nendobj\n"
1350 .to_vec();
1351 let p = scan(&flate, Limits::DEFAULT).unwrap();
1352 let s = only_stream(&p);
1353 assert_eq!(s.filter, FilterClass::FlateDecode);
1354 assert_eq!(slice_of(&flate, s), b"hello");
1355 p.validate(flate.len() as u64).unwrap();
1356
1357 let absent =
1358 b"%PDF-1.5\n1 0 obj\n<< /Length 5 >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1359 let p = scan(&absent, Limits::DEFAULT).unwrap();
1360 let s = only_stream(&p);
1361 assert_eq!(s.filter, FilterClass::Absent);
1362 assert_eq!(slice_of(&absent, s), b"hello");
1363 p.validate(absent.len() as u64).unwrap();
1364 }
1365
1366 #[test]
1367 fn missing_length_uses_keyword_fallback() {
1368 let pdf = b"%PDF-1.5\n1 0 obj\n<< /Type /X >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1369 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1370 let s = only_stream(&p);
1371 assert_eq!(s.length_source, LengthSource::Missing);
1372 assert!(slice_of(&pdf, s).windows(5).any(|w| w == b"hello"));
1373 p.validate(pdf.len() as u64).unwrap();
1374 }
1375
1376 #[test]
1377 fn wrong_length_past_endstream_falls_back() {
1378 let pdf =
1379 b"%PDF-1.5\n1 0 obj\n<< /Length 100 >>\nstream\nhello\nendstream\nendobj\n".to_vec();
1380 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1381 let s = only_stream(&p);
1382 assert_eq!(s.length_source, LengthSource::Fallback);
1383 assert!(slice_of(&pdf, s).windows(5).any(|w| w == b"hello"));
1384 p.validate(pdf.len() as u64).unwrap();
1385 }
1386
1387 #[test]
1388 fn correct_length_beats_endstream_bytes_in_payload() {
1389 let payload = b"endstream\nfoo";
1392 let pdf =
1393 b"%PDF-1.5\n1 0 obj\n<< /Length 13 >>\nstream\nendstream\nfoo\nendstream\nendobj\n"
1394 .to_vec();
1395 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1396 let s = only_stream(&p);
1397 assert_eq!(s.length_source, LengthSource::Direct);
1398 assert_eq!(s.data_start, offset_of(&pdf, b"endstream\nfoo"));
1399 assert_eq!(s.data_len, payload.len() as u64);
1400 assert_eq!(slice_of(&pdf, s), payload);
1401 p.validate(pdf.len() as u64).unwrap();
1402 }
1403
1404 #[test]
1405 fn two_revisions_have_correct_boundaries() {
1406 let r1 = b"%PDF-1.4\n1 0 obj\n<< >>\nendobj\n%%EOF\n";
1407 let r2 = b"2 0 obj\n<< >>\nendobj\n%%EOF";
1408 let mut pdf = Vec::new();
1409 pdf.extend_from_slice(r1);
1410 pdf.extend_from_slice(r2);
1411
1412 let eof_positions: Vec<u64> = pdf
1413 .windows(5)
1414 .enumerate()
1415 .filter(|(_, w)| *w == b"%%EOF")
1416 .map(|(i, _)| i as u64)
1417 .collect();
1418 assert_eq!(eof_positions.len(), 2);
1419
1420 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1421 assert_eq!(p.eofs.len(), 2);
1422 assert_eq!(p.revisions.len(), 2);
1423
1424 let rev1_end = eof_positions[0] + 5;
1425 let rev2_end = eof_positions[1] + 5;
1426 assert_eq!(
1427 p.revisions,
1428 vec![
1429 RevisionInfo {
1430 index: 0,
1431 start: 0,
1432 end: rev1_end,
1433 startxref: None,
1434 prev: None,
1435 },
1436 RevisionInfo {
1437 index: 1,
1438 start: rev1_end + 1,
1439 end: rev2_end,
1440 startxref: None,
1441 prev: None,
1442 },
1443 ]
1444 );
1445 assert_eq!(rev2_end, pdf.len() as u64);
1446 assert_eq!(p.objects.len(), 2);
1447 p.validate(pdf.len() as u64).unwrap();
1448 }
1449
1450 #[test]
1451 fn classic_xref_revision_records_startxref_and_no_prev() {
1452 let pdf = canonical_pdf();
1453 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1454 assert_eq!(p.revisions.len(), 1);
1455 let r = p.revisions[0];
1456 assert_eq!(r.index, 0);
1457 assert_eq!(r.start, 0);
1458 assert_eq!(r.end, pdf.len() as u64);
1459 assert_eq!(r.startxref, Some(321));
1460 assert_eq!(r.prev, None);
1461 p.validate(pdf.len() as u64).unwrap();
1462 }
1463
1464 #[test]
1465 fn incremental_classic_trailer_prev_resolves_to_first_xref() {
1466 let rev1 = b"%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\nxref\n0 2\n0000000000 65535 f \n0000000009 00000 n \ntrailer\n<< /Size 2 /Root 1 0 R >>\nstartxref\n9\n%%EOF\n";
1467 let x1 = rev1
1468 .windows(4)
1469 .position(|w| w == b"xref")
1470 .expect("xref present") as u64;
1471 let rev2 = format!(
1472 "2 0 obj\n<< /Type /Pages >>\nendobj\nxref\n0 3\n0000000000 65535 f \n0000000009 00000 n \n0000000042 00000 n \ntrailer\n<< /Size 3 /Prev {x1} /Root 1 0 R >>\nstartxref\n777\n%%EOF"
1473 );
1474 let mut pdf = Vec::new();
1475 pdf.extend_from_slice(rev1);
1476 pdf.extend_from_slice(rev2.as_bytes());
1477
1478 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1479 assert_eq!(p.revisions.len(), 2);
1480 assert_eq!(p.revisions[0].index, 0);
1481 assert_eq!(p.revisions[0].startxref, Some(9));
1482 assert_eq!(p.revisions[0].prev, None);
1483 assert_eq!(p.revisions[1].index, 1);
1484 assert_eq!(p.revisions[1].startxref, Some(777));
1485 assert_eq!(p.revisions[1].prev, Some(x1));
1486 p.validate(pdf.len() as u64).unwrap();
1487 }
1488
1489 #[test]
1490 fn xref_stream_anchor_prev_reference_is_resolved() {
1491 let rev1 = b"%PDF-1.5\n1 0 obj\n<< >>\nendobj\nstartxref\n0\n%%EOF\n";
1492 let rev2 = b"2 0 obj\n<< /Type /XRef /Prev 3 0 R >>\nendobj\n3 0 obj\n<< >>\nendobj\nstartxref\n0\n%%EOF";
1493 let mut pdf = Vec::new();
1494 pdf.extend_from_slice(rev1);
1495 pdf.extend_from_slice(rev2);
1496 let obj3_start = pdf
1497 .windows(8)
1498 .position(|w| w == b"3 0 obj\n")
1499 .expect("object 3 present") as u64;
1500
1501 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1502 assert_eq!(p.revisions.len(), 2);
1503 assert_eq!(p.revisions[0].prev, None);
1504 assert_eq!(p.revisions[1].prev, Some(obj3_start));
1505 assert_eq!(
1506 p.objects.iter().find(|o| o.number == 2).unwrap().role,
1507 ObjRole::XRefStream
1508 );
1509 p.validate(pdf.len() as u64).unwrap();
1510 }
1511
1512 #[test]
1513 fn object_roles_are_classified_from_leading_dict() {
1514 let pdf = b"%PDF-1.5\n1 0 obj\n<< /Type /Catalog >>\nendobj\n2 0 obj\n<< /Type /XRef >>\nendobj\n3 0 obj\n<< /Type /ObjStm /N 0 >>\nendobj\n4 0 obj\n<< /Foo /Bar >>\nendobj\n5 0 obj\n<< /Type 5 >>\nendobj\n6 0 obj\n42\nendobj\n7 0 obj\n<< /Foo ( /Type /XRef ) >>\nendobj\n".to_vec();
1515 let p = scan(&pdf, Limits::DEFAULT).unwrap();
1516 let role = |n: u64| p.objects.iter().find(|o| o.number == n).unwrap().role;
1517 assert_eq!(role(1), ObjRole::Generic);
1518 assert_eq!(role(2), ObjRole::XRefStream);
1519 assert_eq!(role(3), ObjRole::ObjectStream);
1520 assert_eq!(role(4), ObjRole::Generic);
1521 assert_eq!(role(5), ObjRole::Generic);
1522 assert_eq!(role(6), ObjRole::Generic);
1523 assert_eq!(role(7), ObjRole::Generic);
1524 p.validate(pdf.len() as u64).unwrap();
1525 }
1526
1527 #[test]
1528 fn random_inputs_keep_streams_and_revisions_consistent() {
1529 let mut state: u64 = 0xdead_beef_cafe_f00d;
1530 for _ in 0..500 {
1531 let len = (xorshift64(&mut state) % 128) as usize;
1532 let mut buf = Vec::with_capacity(len);
1533 for _ in 0..len {
1534 buf.push((xorshift64(&mut state) & 0xff) as u8);
1535 }
1536 let p = scan(&buf, Limits::DEFAULT).unwrap();
1537 p.validate(buf.len() as u64).unwrap();
1538 for s in &p.streams {
1539 assert!(s.data_start + s.data_len <= buf.len() as u64);
1540 }
1541 for r in &p.revisions {
1542 assert!(r.start <= r.end && r.end <= buf.len() as u64);
1543 }
1544 }
1545 }
1546}