Skip to main content

zpdf_parser/
header.rs

1use zpdf_core::{Error, Result};
2
3#[derive(Debug, Clone, Copy)]
4pub struct PdfHeader {
5    pub major: u8,
6    pub minor: u8,
7}
8
9pub fn parse_header(data: &[u8]) -> Result<PdfHeader> {
10    let prefix = b"%PDF-";
11    let pos = data
12        .windows(prefix.len())
13        .position(|w| w == prefix)
14        .ok_or(Error::NotAPdf)?;
15
16    let rest = &data[pos + prefix.len()..];
17    if rest.len() < 3 {
18        return Err(Error::NotAPdf);
19    }
20
21    let major = rest[0]
22        .checked_sub(b'0')
23        .filter(|&v| v <= 9)
24        .ok_or(Error::NotAPdf)?;
25
26    if rest[1] != b'.' {
27        return Err(Error::NotAPdf);
28    }
29
30    let minor = rest[2]
31        .checked_sub(b'0')
32        .filter(|&v| v <= 9)
33        .ok_or(Error::NotAPdf)?;
34
35    Ok(PdfHeader { major, minor })
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn valid_header() {
44        let data = b"%PDF-1.7\n";
45        let h = parse_header(data).unwrap();
46        assert_eq!(h.major, 1);
47        assert_eq!(h.minor, 7);
48    }
49
50    #[test]
51    fn pdf_2_0() {
52        let data = b"%PDF-2.0\n";
53        let h = parse_header(data).unwrap();
54        assert_eq!(h.major, 2);
55        assert_eq!(h.minor, 0);
56    }
57
58    #[test]
59    fn garbage_before_header() {
60        let data = b"\xef\xbb\xbf%PDF-1.4\n";
61        let h = parse_header(data).unwrap();
62        assert_eq!(h.major, 1);
63        assert_eq!(h.minor, 4);
64    }
65
66    #[test]
67    fn not_a_pdf() {
68        assert!(parse_header(b"not a pdf").is_err());
69    }
70}