1use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
28use pdfrum_object::{Object, Resolve, Stream, names};
29
30use crate::lexer::{Lexer, Token, atoui};
31use crate::syntax::{Context, Strictness, body};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct ObjStmEntry {
36 pub num: u32,
38 pub offset: u32,
40}
41
42#[derive(Debug, Clone)]
48pub struct ObjStm {
49 data: Vec<u8>,
51 first: usize,
53 entries: Vec<ObjStmEntry>,
55}
56
57impl ObjStm {
58 pub(crate) fn build<R: Resolve + ?Sized>(
65 stream: &Stream,
66 limits: &Limits,
67 diags: &mut Diagnostics,
68 store: &R,
69 ) -> Option<Self> {
70 let dict = &stream.dict;
71 if dict.name(names::TYPE) != Some(names::OBJ_STM) {
72 return None;
73 }
74 let count = literal_int(dict, names::N)?;
75 if count < 0 || count > i64::from(limits.max_object_number) {
76 return None;
77 }
78 let first = literal_int(dict, names::FIRST)?;
79 if first < 0 {
80 return None;
81 }
82
83 let data = crate::decode::structural_bytes(stream, store, limits, diags)?;
84 let entries = read_header(&data, count.cast_unsigned(), limits, diags);
85 Some(Self {
86 data,
87 first: usize::try_from(first).unwrap_or(usize::MAX),
88 entries,
89 })
90 }
91
92 #[must_use]
94 pub fn entries(&self) -> &[ObjStmEntry] {
95 &self.entries
96 }
97
98 pub(crate) fn member<R: Resolve + ?Sized>(
105 &self,
106 num: u32,
107 index: u32,
108 limits: &Limits,
109 diags: &mut Diagnostics,
110 store: &R,
111 depth: u32,
112 ) -> Option<Object> {
113 let entry = self.entries.get(usize::try_from(index).ok()?)?;
114 if entry.num != num {
115 return None;
116 }
117 let at = self
118 .first
119 .checked_add(usize::try_from(entry.offset).ok()?)?;
120 if at >= self.data.len() {
121 return None;
122 }
123 let mut lx = Lexer::at(&self.data, at);
124 let mut ctx = Context {
125 limits,
126 diags,
127 file: None,
130 store: Some(store),
131 };
132 body(&mut lx, &mut ctx, Strictness::Loose, depth).ok()
133 }
134}
135
136fn literal_int(dict: &pdfrum_object::Dict, key: &pdfrum_object::Name) -> Option<i64> {
138 match dict.raw(key)? {
139 Object::Int(v) => Some(*v),
140 _ => None,
142 }
143}
144
145fn read_header(
147 data: &[u8],
148 count: u64,
149 limits: &Limits,
150 diags: &mut Diagnostics,
151) -> Vec<ObjStmEntry> {
152 let mut entries = Vec::new();
153 let mut lx = Lexer::new(data);
154 for _ in 0..count {
155 if lx.pos() >= data.len() {
156 break;
157 }
158 let num = direct_num(&mut lx, limits);
159 let offset = direct_num(&mut lx, limits);
160 if num == 0 {
164 diags.record(Severity::Suspicious, DiagKind::ObjStmEntryDropped, None);
165 continue;
166 }
167 entries.push(ObjStmEntry { num, offset });
168 }
169 entries
170}
171
172fn direct_num(lx: &mut Lexer<'_>, limits: &Limits) -> u32 {
177 match lx.next_word(limits) {
178 Token::Number(word) => atoui(word),
179 _ => 0,
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::{ObjStm, ObjStmEntry};
188 use pdfrum_common::{Diagnostics, Limits};
189 use pdfrum_object::{ByteSpan, Dict, Name, NoResolve, Object, Stream, names};
190
191 const NORMAL: &[u8] = b"10 0 11 14 12 21<</Name /Foo>>[1 2 3]4";
194
195 fn dict(pairs: impl IntoIterator<Item = (&'static str, Object)>) -> Dict {
196 Dict::from_pairs(pairs.into_iter().map(|(k, v)| (Name::from(k), v)))
197 }
198
199 fn objstm_dict(count: i64, first: i64) -> Dict {
200 dict([
201 ("Type", Object::Name(names::OBJ_STM.clone())),
202 ("N", Object::Int(count)),
203 ("First", Object::Int(first)),
204 ])
205 }
206
207 fn build(d: Dict, data: &[u8]) -> Option<ObjStm> {
208 let stream = Stream::new(d, ByteSpan::from(data.to_vec()));
209 ObjStm::build(
210 &stream,
211 &Limits::default(),
212 &mut Diagnostics::default(),
213 &NoResolve,
214 )
215 }
216
217 fn entries(o: &ObjStm) -> Vec<(u32, u32)> {
218 o.entries().iter().map(|e| (e.num, e.offset)).collect()
219 }
220
221 fn member(o: &ObjStm, num: u32, index: u32) -> Option<Object> {
222 o.member(
223 num,
224 index,
225 &Limits::default(),
226 &mut Diagnostics::default(),
227 &NoResolve,
228 0,
229 )
230 }
231
232 #[test]
233 fn a_normal_stream_indexes_its_members() {
234 let o = build(objstm_dict(3, 16), NORMAL).expect("object stream");
235 assert_eq!(entries(&o), vec![(10, 0), (11, 14), (12, 21)]);
236 assert!(member(&o, 10, 0).expect("member").as_dict().is_some());
237 assert!(member(&o, 11, 1).expect("member").as_array().is_some());
238 assert!(member(&o, 12, 2).expect("member").as_number().is_some());
239 }
240
241 #[test]
242 fn both_the_index_and_the_number_must_match() {
243 let o = build(objstm_dict(3, 16), NORMAL).expect("object stream");
244 for (num, index) in [
245 (10, 1),
246 (10, 2),
247 (10, 3),
248 (11, 0),
249 (11, 2),
250 (11, 3),
251 (12, 0),
252 (12, 1),
253 (12, 3),
254 ] {
255 assert!(member(&o, num, index).is_none(), "({num}, {index})");
256 }
257 }
258
259 #[test]
260 fn dictionaries_that_do_not_describe_an_object_stream() {
261 assert!(build(Dict::new(), NORMAL).is_none());
263 assert!(
264 build(
265 dict([("N", Object::Int(3)), ("First", Object::Int(5))]),
266 NORMAL
267 )
268 .is_none()
269 );
270 assert!(
272 build(
273 dict([
274 (
275 "Type",
276 Object::Str(pdfrum_object::PdfString::literal(b"ObjStm"))
277 ),
278 ("N", Object::Int(3)),
279 ("First", Object::Int(5)),
280 ]),
281 NORMAL,
282 )
283 .is_none()
284 );
285 assert!(
287 build(
288 dict([
289 ("Type", Object::Name(Name::from("ObjStmmmm"))),
290 ("N", Object::Int(3)),
291 ("First", Object::Int(5)),
292 ]),
293 NORMAL,
294 )
295 .is_none()
296 );
297 }
298
299 #[test]
300 fn the_count_must_be_a_literal_nonnegative_integer() {
301 let base = |n: Object| {
302 dict([
303 ("Type", Object::Name(names::OBJ_STM.clone())),
304 ("N", n),
305 ("First", Object::Int(5)),
306 ])
307 };
308 assert!(
310 build(
311 dict([
312 ("Type", Object::Name(names::OBJ_STM.clone())),
313 ("First", Object::Int(5)),
314 ]),
315 NORMAL,
316 )
317 .is_none()
318 );
319 assert!(build(base(Object::Real(2.2)), NORMAL).is_none());
320 assert!(build(base(Object::Int(-1)), NORMAL).is_none());
321 assert!(build(base(Object::Int(999_999_999)), NORMAL).is_none());
322 }
323
324 #[test]
325 fn the_first_offset_must_be_a_literal_nonnegative_integer() {
326 let base = |f: Object| {
327 dict([
328 ("Type", Object::Name(names::OBJ_STM.clone())),
329 ("N", Object::Int(3)),
330 ("First", f),
331 ])
332 };
333 assert!(
334 build(
335 dict([
336 ("Type", Object::Name(names::OBJ_STM.clone())),
337 ("N", Object::Int(3)),
338 ]),
339 NORMAL,
340 )
341 .is_none()
342 );
343 assert!(build(base(Object::Real(5.5)), NORMAL).is_none());
344 assert!(build(base(Object::Int(-5)), NORMAL).is_none());
345 }
346
347 #[test]
348 fn a_first_past_the_data_parses_the_table_but_no_members() {
349 let o = build(objstm_dict(3, 39), NORMAL).expect("object stream");
351 assert_eq!(entries(&o), vec![(10, 0), (11, 14), (12, 21)]);
352 assert!(member(&o, 10, 0).is_none());
353 assert!(member(&o, 11, 1).is_none());
354 assert!(member(&o, 12, 2).is_none());
355 }
356
357 #[test]
358 fn a_count_smaller_than_the_table_stops_early() {
359 let o = build(objstm_dict(2, 16), NORMAL).expect("object stream");
360 assert_eq!(entries(&o), vec![(10, 0), (11, 14)]);
361 assert!(member(&o, 10, 0).expect("member").as_dict().is_some());
362 assert!(member(&o, 11, 1).expect("member").as_array().is_some());
363 assert!(member(&o, 12, 2).is_none());
364 }
365
366 #[test]
367 fn a_count_larger_than_the_table_reads_into_the_bodies() {
368 let o = build(objstm_dict(9, 16), NORMAL).expect("object stream");
371 assert_eq!(entries(&o), vec![(10, 0), (11, 14), (12, 21), (2, 3)]);
372 for index in 0..5 {
373 assert!(member(&o, 2, index).is_none());
374 }
375 }
376
377 #[test]
378 fn a_garbage_object_number_drops_exactly_one_pair() {
379 let data = b"10 0 hi 14 12 21<</Name /Foo>>[1 2 3]4";
380 let o = build(objstm_dict(3, 19), data).expect("object stream");
381 assert_eq!(entries(&o), vec![(10, 0), (12, 21)]);
382 }
383
384 #[test]
385 fn a_garbage_offset_reads_as_zero_and_keeps_the_entry() {
386 let data = b"10 0 11 hi 12 21<</Name /Foo>>[1 2 3]4";
387 let o = build(objstm_dict(3, 16), data).expect("object stream");
388 assert_eq!(entries(&o), vec![(10, 0), (11, 0), (12, 21)]);
389 assert!(member(&o, 10, 0).expect("member").as_dict().is_some());
391 assert!(member(&o, 11, 1).expect("member").as_dict().is_some());
392 }
393
394 #[test]
395 fn a_negative_offset_wraps_into_an_unreachable_one() {
396 let data = b"10 0 11 -1 12 21<</Name /Foo>>[1 2 3]4";
397 let o = build(objstm_dict(3, 16), data).expect("object stream");
398 assert_eq!(
399 o.entries().get(1),
400 Some(&ObjStmEntry {
401 num: 11,
402 offset: 4_294_967_295,
403 })
404 );
405 assert!(member(&o, 11, 1).is_none());
406 }
407
408 #[test]
409 fn an_offset_past_the_data_yields_no_member() {
410 let data = b"10 0 11 999 12 21<</Name /Foo>>[1 2 3]4";
411 let o = build(objstm_dict(3, 17), data).expect("object stream");
412 assert_eq!(entries(&o), vec![(10, 0), (11, 999), (12, 21)]);
413 assert!(member(&o, 11, 1).is_none());
414 }
415
416 #[test]
417 fn duplicate_object_numbers_are_told_apart_by_index() {
418 let data = b"10 0 10 14 12 21<</Name /Foo>>[1 2 3]4";
419 let o = build(objstm_dict(3, 16), data).expect("object stream");
420 assert_eq!(entries(&o), vec![(10, 0), (10, 14), (12, 21)]);
421 assert!(member(&o, 10, 0).expect("member").as_dict().is_some());
422 assert!(member(&o, 10, 1).expect("member").as_array().is_some());
423 assert!(member(&o, 10, 2).is_none());
424 assert!(member(&o, 10, 3).is_none());
425 assert!(member(&o, 12, 2).expect("member").as_number().is_some());
426 }
427
428 #[test]
429 fn object_numbers_need_not_ascend() {
430 let data = b"11 0 12 14 10 21<</Name /Foo>>[1 2 3]4";
431 let o = build(objstm_dict(3, 16), data).expect("object stream");
432 assert_eq!(entries(&o), vec![(11, 0), (12, 14), (10, 21)]);
433 assert!(member(&o, 10, 2).expect("member").as_number().is_some());
434 assert!(member(&o, 11, 0).expect("member").as_dict().is_some());
435 assert!(member(&o, 12, 1).expect("member").as_array().is_some());
436 }
437
438 #[test]
439 fn offsets_need_not_ascend_either() {
440 let data = b"10 21 11 0 12 14<</Name /Foo>>[1 2 3]4";
441 let o = build(objstm_dict(3, 16), data).expect("object stream");
442 assert_eq!(entries(&o), vec![(10, 21), (11, 0), (12, 14)]);
443 assert!(member(&o, 10, 0).expect("member").as_number().is_some());
444 assert!(member(&o, 11, 1).expect("member").as_dict().is_some());
445 assert!(member(&o, 12, 2).expect("member").as_array().is_some());
446 }
447
448 #[test]
449 fn never_panics_on_arbitrary_bytes() {
450 for data in [
451 &b""[..],
452 b"0 0 0 0",
453 b"999999999999 999999999999",
454 b"\xff\x80\x00",
455 b"1",
456 ] {
457 for (n, first) in [(0, 0), (3, 0), (9, 100), (1, 1)] {
458 let _ = build(objstm_dict(n, first), data);
459 }
460 }
461 }
462}