1use super::span::{Span, SpanKind};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LengthValue {
20 Direct(u64),
22 Indirect { number: u64, generation: u64 },
24}
25
26pub fn dict_length(input: &[u8], lex: &[Span], dict_lo: u64, dict_hi: u64) -> Option<LengthValue> {
33 let name = find_length_name(input, lex, dict_lo, dict_hi)?;
34 let t0 = next_significant(lex, name + 1, dict_hi)?;
35 let number = integer_of(input, lex[t0])?;
36
37 if let Some(t1) = next_significant(lex, t0 + 1, dict_hi)
39 && let Some(generation) = integer_of(input, lex[t1])
40 && let Some(t2) = next_significant(lex, t1 + 1, dict_hi)
41 && is_regular_r(input, lex[t2])
42 {
43 return Some(LengthValue::Indirect { number, generation });
44 }
45
46 Some(LengthValue::Direct(number))
47}
48
49pub fn dict_has_length(input: &[u8], lex: &[Span], dict_lo: u64, dict_hi: u64) -> bool {
53 find_length_name(input, lex, dict_lo, dict_hi).is_some()
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum FilterClass {
64 Absent,
66 FlateDecode,
68 Other,
70}
71
72pub fn dict_filter(input: &[u8], lex: &[Span], dict_lo: u64, dict_hi: u64) -> FilterClass {
79 let Some(name) = find_name_key(input, lex, dict_lo, dict_hi, b"Filter") else {
80 return FilterClass::Absent;
81 };
82
83 let is_flate = |sp: Span| {
84 sp.kind == SpanKind::Name
85 && span_bytes(input, sp).is_some_and(|b| b == b"/FlateDecode".as_slice())
86 };
87
88 let Some(t0) = next_significant(lex, name + 1, dict_hi) else {
89 return FilterClass::Other;
90 };
91 if is_flate(lex[t0]) {
92 return FilterClass::FlateDecode;
93 }
94 if lex[t0].kind != SpanKind::ArrayOpen {
95 return FilterClass::Other;
96 }
97
98 let Some(t1) = next_significant(lex, t0 + 1, dict_hi) else {
99 return FilterClass::Other;
100 };
101 if !is_flate(lex[t1]) {
102 return FilterClass::Other;
103 }
104 let Some(t2) = next_significant(lex, t1 + 1, dict_hi) else {
105 return FilterClass::Other;
106 };
107 if lex[t2].kind == SpanKind::ArrayClose {
108 FilterClass::FlateDecode
109 } else {
110 FilterClass::Other
111 }
112}
113
114pub fn dict_name_value<'a>(
122 input: &'a [u8],
123 lex: &[Span],
124 dict_lo: u64,
125 dict_hi: u64,
126 key: &[u8],
127) -> Option<&'a [u8]> {
128 let name = find_name_key(input, lex, dict_lo, dict_hi, key)?;
129 let t = next_significant(lex, name + 1, dict_hi)?;
130 let sp = lex[t];
131 if sp.kind != SpanKind::Name {
132 return None;
133 }
134 span_bytes(input, sp)?.strip_prefix(b"/")
135}
136
137pub fn dict_int_or_ref(
145 input: &[u8],
146 lex: &[Span],
147 dict_lo: u64,
148 dict_hi: u64,
149 key: &[u8],
150) -> Option<LengthValue> {
151 let name = find_name_key(input, lex, dict_lo, dict_hi, key)?;
152 let t0 = next_significant(lex, name + 1, dict_hi)?;
153 let number = integer_of(input, lex[t0])?;
154
155 if let Some(t1) = next_significant(lex, t0 + 1, dict_hi)
157 && let Some(generation) = integer_of(input, lex[t1])
158 && let Some(t2) = next_significant(lex, t1 + 1, dict_hi)
159 && is_regular_r(input, lex[t2])
160 {
161 return Some(LengthValue::Indirect { number, generation });
162 }
163
164 Some(LengthValue::Direct(number))
165}
166
167pub fn body_as_u64(input: &[u8], lex: &[Span], body_lo: u64, body_hi: u64) -> Option<u64> {
174 let mut only: Option<usize> = None;
175 for (idx, sp) in lex.iter().enumerate() {
176 if sp.start < body_lo {
177 continue;
178 }
179 if sp.start >= body_hi {
180 break;
181 }
182 if !sp
184 .start
185 .checked_add(sp.len)
186 .is_some_and(|end| end <= body_hi)
187 {
188 return None;
189 }
190 if matches!(sp.kind, SpanKind::Whitespace | SpanKind::Comment) {
191 continue;
192 }
193 if only.is_some() {
194 return None;
195 }
196 only = Some(idx);
197 }
198 integer_of(input, lex[only?])
199}
200
201fn find_length_name(input: &[u8], lex: &[Span], lo: u64, hi: u64) -> Option<usize> {
203 find_name_key(input, lex, lo, hi, b"Length")
204}
205
206fn find_name_key(input: &[u8], lex: &[Span], lo: u64, hi: u64, key: &[u8]) -> Option<usize> {
208 lex.iter().position(|sp| {
209 sp.kind == SpanKind::Name
210 && sp.start >= lo
211 && sp.start.checked_add(sp.len).is_some_and(|end| end <= hi)
212 && span_bytes(input, *sp)
213 .is_some_and(|b| b.len() == key.len() + 1 && b[0] == b'/' && &b[1..] == key)
214 })
215}
216
217fn next_significant(lex: &[Span], from: usize, hi: u64) -> Option<usize> {
220 let mut j = from;
221 while j < lex.len() {
222 let sp = lex[j];
223 if sp.start >= hi {
224 return None;
225 }
226 match sp.kind {
227 SpanKind::Whitespace | SpanKind::Comment => j += 1,
228 _ => return Some(j),
229 }
230 }
231 None
232}
233
234fn integer_of(input: &[u8], sp: Span) -> Option<u64> {
236 if sp.kind != SpanKind::Regular {
237 return None;
238 }
239 let bytes = span_bytes(input, sp)?;
240 if bytes.is_empty() {
241 return None;
242 }
243 let mut value: u64 = 0;
244 for &b in bytes {
245 if !b.is_ascii_digit() {
246 return None;
247 }
248 value = value.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
249 }
250 Some(value)
251}
252
253fn is_regular_r(input: &[u8], sp: Span) -> bool {
255 sp.kind == SpanKind::Regular && span_bytes(input, sp) == Some(b"R".as_slice())
256}
257
258fn span_bytes(input: &[u8], sp: Span) -> Option<&[u8]> {
260 let start = usize::try_from(sp.start).ok()?;
261 let end = usize::try_from(sp.start.checked_add(sp.len)?).ok()?;
262 if start > end || end > input.len() {
263 return None;
264 }
265 Some(&input[start..end])
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::adapter::pdf::lexer::lex;
272 use crate::limits::Limits;
273
274 fn lexed(input: &[u8]) -> Vec<Span> {
275 lex(input, Limits::DEFAULT)
276 .expect("lex must succeed")
277 .spans
278 .spans
279 }
280
281 #[test]
282 fn direct_length_is_read() {
283 let input = b"<< /Length 42 >>";
284 let spans = lexed(input);
285 assert_eq!(
286 dict_length(input, &spans, 0, input.len() as u64),
287 Some(LengthValue::Direct(42))
288 );
289 assert!(dict_has_length(input, &spans, 0, input.len() as u64));
290 }
291
292 #[test]
293 fn indirect_length_is_read() {
294 let input = b"<< /Length 5 0 R >>";
295 let spans = lexed(input);
296 assert_eq!(
297 dict_length(input, &spans, 0, input.len() as u64),
298 Some(LengthValue::Indirect {
299 number: 5,
300 generation: 0
301 })
302 );
303 }
304
305 #[test]
306 fn absent_key_returns_none() {
307 let input = b"<< /Type /X /N 3 >>";
308 let spans = lexed(input);
309 assert_eq!(dict_length(input, &spans, 0, input.len() as u64), None);
310 assert!(!dict_has_length(input, &spans, 0, input.len() as u64));
311 }
312
313 #[test]
314 fn non_integer_value_returns_none_but_key_is_present() {
315 let input = b"<< /Length /Foo >>";
316 let spans = lexed(input);
317 assert_eq!(dict_length(input, &spans, 0, input.len() as u64), None);
318 assert!(dict_has_length(input, &spans, 0, input.len() as u64));
319 }
320
321 #[test]
322 fn length_name_inside_string_is_not_a_key() {
323 let input = b"<< /X ( /Length 9 ) >>";
324 let spans = lexed(input);
325 assert_eq!(dict_length(input, &spans, 0, input.len() as u64), None);
326 assert!(!dict_has_length(input, &spans, 0, input.len() as u64));
327 }
328
329 #[test]
330 fn dict_name_value_reads_xref_and_objstm_types() {
331 let xref = b"<< /Type /XRef /Length 4 >>";
332 let spans = lexed(xref);
333 assert_eq!(
334 dict_name_value(xref, &spans, 0, xref.len() as u64, b"Type"),
335 Some(b"XRef".as_slice())
336 );
337
338 let objstm = b"<< /Type /ObjStm /N 3 >>";
339 let spans = lexed(objstm);
340 assert_eq!(
341 dict_name_value(objstm, &spans, 0, objstm.len() as u64, b"Type"),
342 Some(b"ObjStm".as_slice())
343 );
344 }
345
346 #[test]
347 fn dict_name_value_absent_key_and_non_name_value_return_none() {
348 let absent = b"<< /Foo /XRef >>";
349 let spans = lexed(absent);
350 assert_eq!(
351 dict_name_value(absent, &spans, 0, absent.len() as u64, b"Type"),
352 None
353 );
354
355 let non_name = b"<< /Type 5 >>";
356 let spans = lexed(non_name);
357 assert_eq!(
358 dict_name_value(non_name, &spans, 0, non_name.len() as u64, b"Type"),
359 None
360 );
361 }
362
363 #[test]
364 fn dict_name_value_inside_string_is_not_a_key() {
365 let input = b"<< /X ( /Type /XRef ) >>";
366 let spans = lexed(input);
367 assert_eq!(
368 dict_name_value(input, &spans, 0, input.len() as u64, b"Type"),
369 None
370 );
371 }
372
373 #[test]
374 fn dict_int_or_ref_reads_direct_and_reference() {
375 let direct = b"<< /Prev 1234 >>";
376 let spans = lexed(direct);
377 assert_eq!(
378 dict_int_or_ref(direct, &spans, 0, direct.len() as u64, b"Prev"),
379 Some(LengthValue::Direct(1234))
380 );
381
382 let reference = b"<< /Prev 7 0 R >>";
383 let spans = lexed(reference);
384 assert_eq!(
385 dict_int_or_ref(reference, &spans, 0, reference.len() as u64, b"Prev"),
386 Some(LengthValue::Indirect {
387 number: 7,
388 generation: 0,
389 })
390 );
391 }
392
393 #[test]
394 fn dict_int_or_ref_rejects_non_integer_and_absent() {
395 let name_value = b"<< /Prev /X >>";
396 let spans = lexed(name_value);
397 assert_eq!(
398 dict_int_or_ref(name_value, &spans, 0, name_value.len() as u64, b"Prev"),
399 None
400 );
401
402 let absent = b"<< /Size 4 >>";
403 let spans = lexed(absent);
404 assert_eq!(
405 dict_int_or_ref(absent, &spans, 0, absent.len() as u64, b"Prev"),
406 None
407 );
408 }
409
410 #[test]
411 fn dict_filter_absent_without_key() {
412 let input = b"<< /Length 1 >>";
413 let spans = lexed(input);
414 assert_eq!(
415 dict_filter(input, &spans, 0, input.len() as u64),
416 FilterClass::Absent
417 );
418 }
419
420 #[test]
421 fn dict_filter_reads_bare_and_single_element_array() {
422 let bare = b"<< /Filter /FlateDecode >>";
423 let spans = lexed(bare);
424 assert_eq!(
425 dict_filter(bare, &spans, 0, bare.len() as u64),
426 FilterClass::FlateDecode
427 );
428
429 let array = b"<< /Filter [ /FlateDecode ] >>";
430 let spans = lexed(array);
431 assert_eq!(
432 dict_filter(array, &spans, 0, array.len() as u64),
433 FilterClass::FlateDecode
434 );
435 }
436
437 #[test]
438 fn dict_filter_rejects_chain_other_filter_and_empty_array() {
439 let chain = b"<< /Filter [ /FlateDecode /ASCIIHexDecode ] >>";
440 let spans = lexed(chain);
441 assert_eq!(
442 dict_filter(chain, &spans, 0, chain.len() as u64),
443 FilterClass::Other
444 );
445
446 let other = b"<< /Filter /LZWDecode >>";
447 let spans = lexed(other);
448 assert_eq!(
449 dict_filter(other, &spans, 0, other.len() as u64),
450 FilterClass::Other
451 );
452
453 let empty = b"<< /Filter [] >>";
454 let spans = lexed(empty);
455 assert_eq!(
456 dict_filter(empty, &spans, 0, empty.len() as u64),
457 FilterClass::Other
458 );
459 }
460
461 #[test]
462 fn dict_filter_ignores_string_spelling_and_non_name_value() {
463 let inside_string = b"<< /X ( /Filter /FlateDecode ) >>";
464 let spans = lexed(inside_string);
465 assert_eq!(
466 dict_filter(inside_string, &spans, 0, inside_string.len() as u64),
467 FilterClass::Absent
468 );
469
470 let non_name = b"<< /Filter 5 >>";
471 let spans = lexed(non_name);
472 assert_eq!(
473 dict_filter(non_name, &spans, 0, non_name.len() as u64),
474 FilterClass::Other
475 );
476 }
477
478 #[test]
479 fn body_as_u64_accepts_single_integer_with_padding() {
480 let input = b"12";
481 let spans = lexed(input);
482 assert_eq!(body_as_u64(input, &spans, 0, 2), Some(12));
483
484 let padded = b"\n 12 \n";
485 let spans = lexed(padded);
486 assert_eq!(
487 body_as_u64(padded, &spans, 0, padded.len() as u64),
488 Some(12)
489 );
490 }
491
492 #[test]
493 fn body_as_u64_rejects_multi_token_body() {
494 let input = b"12 0";
495 let spans = lexed(input);
496 assert_eq!(body_as_u64(input, &spans, 0, input.len() as u64), None);
497
498 let reference = b"12 0 R";
499 let spans = lexed(reference);
500 assert_eq!(
501 body_as_u64(reference, &spans, 0, reference.len() as u64),
502 None
503 );
504 }
505
506 #[test]
507 fn body_as_u64_rejects_non_integer() {
508 let input = b"1.5";
509 let spans = lexed(input);
510 assert_eq!(body_as_u64(input, &spans, 0, input.len() as u64), None);
511 }
512}