Skip to main content

structured_pades/
bytes.rs

1//! Paid PDF name/string decoding without canonicalizing signed source bytes.
2
3use crate::byte_machine::{Decoder, Step};
4use crate::error::{Error, ErrorKind, Result, Syntax};
5use crate::source::Location;
6use core::cmp::Ordering;
7use structured_asn1::memory::{OwnedVec, Work};
8
9/// PDF spelling of a byte value, excluding its outer delimiters.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Form {
12    /// Name bytes after the leading slash; valid #xx escapes denote one byte.
13    Name,
14    /// Literal string bytes between parentheses, with PDF escapes and EOL rules.
15    Literal,
16    /// Hexadecimal string bytes between angle brackets, including whitespace.
17    Hexadecimal,
18}
19
20/// Original encoded body and its exact source location. This is an inspection
21/// input, not an assertion that its enclosing PDF object or signature is valid.
22#[derive(Debug, Clone, Copy)]
23pub struct Bytes<'a> {
24    raw: &'a [u8],
25    form: Form,
26    location: Location,
27}
28
29impl<'a> Bytes<'a> {
30    /// Associates an original body with an equal-length source range without
31    /// traversal or allocation. Syntax is checked by the paid decoding methods.
32    pub fn new(raw: &'a [u8], form: Form, location: Location) -> Result<Self> {
33        if location.span().length != raw.len() as u64 {
34            return Err(Error {
35                kind: ErrorKind::Foreign,
36                location,
37            });
38        }
39        Ok(Self {
40            raw,
41            form,
42            location,
43        })
44    }
45
46    /// Unmodified original body, not a decoded or re-encoded signature input.
47    #[must_use]
48    pub fn as_bytes(self) -> &'a [u8] {
49        self.raw
50    }
51
52    /// Original byte range and its coordinate domain.
53    #[must_use]
54    pub fn location(self) -> Location {
55        self.location
56    }
57
58    /// Decodes under the current work/account. Actual allocated capacity stays
59    /// charged for the returned owner's lifetime; refusal drops partial output.
60    pub fn decode_in<W: Work>(self, work: &W) -> Result<OwnedVec<u8, W>> {
61        let mut cursor = Cursor::new(self);
62        let mut out = work.vector();
63        while let Some(byte) = cursor.next(work)? {
64            out.try_push(byte).map_err(|cause| Error {
65                kind: ErrorKind::Resource(cause),
66                location: cursor.point(),
67            })?;
68        }
69        Ok(out)
70    }
71
72    /// Streaming decoded equality, without a scratch allocation or an
73    /// infallible `PartialEq` traversal. Each original byte is paid before read.
74    pub fn matches_in(self, other: Self, work: &impl Work) -> Result<bool> {
75        Ok(self.compare_in(other, work)? == Ordering::Equal)
76    }
77
78    /// Lexicographic decoded byte order, using the same streaming kernel as
79    /// equality. Neither case folding nor text normalization is performed.
80    pub fn compare_in(self, other: Self, work: &impl Work) -> Result<Ordering> {
81        let mut left = Cursor::new(self);
82        let mut right = Cursor::new(other);
83        loop {
84            match (left.next(work)?, right.next(work)?) {
85                (None, None) => return Ok(Ordering::Equal),
86                (Some(a), Some(b)) if a == b => {}
87                (None, Some(_)) => return Ok(Ordering::Less),
88                (Some(_), None) => return Ok(Ordering::Greater),
89                (Some(a), Some(b)) => return Ok(a.cmp(&b)),
90            }
91        }
92    }
93
94    /// Compares a PDF name/string with already decoded caller bytes. Caller
95    /// bytes are admitted before comparison as well; mismatches stop the scan.
96    pub fn equals_in(self, expected: &[u8], work: &impl Work) -> Result<bool> {
97        let mut cursor = Cursor::new(self);
98        let mut index = 0;
99        while let Some(byte) = cursor.next(work)? {
100            work.charge_work(1).map_err(|cause| Error {
101                kind: ErrorKind::Resource(cause),
102                location: cursor.point(),
103            })?;
104            if expected.get(index) != Some(&byte) {
105                return Ok(false);
106            }
107            index += 1;
108        }
109        Ok(index == expected.len())
110    }
111}
112
113struct Cursor<'a> {
114    value: Bytes<'a>,
115    index: usize,
116    decoder: Decoder,
117}
118
119impl<'a> Cursor<'a> {
120    fn new(value: Bytes<'a>) -> Self {
121        Self {
122            value,
123            index: 0,
124            decoder: Decoder::new(value.form),
125        }
126    }
127
128    fn point(&self) -> Location {
129        // Both the body range and this bounded cursor were checked at entry.
130        Location::new(
131            self.value.location.source(),
132            self.value.location.span().start + self.index as u64,
133            0,
134        )
135        .expect("a point inside the checked original body")
136    }
137
138    fn malformed(&self) -> Error {
139        Error {
140            kind: ErrorKind::Syntax(match self.value.form {
141                Form::Name => Syntax::Name,
142                Form::Literal | Form::Hexadecimal => Syntax::String,
143            }),
144            location: self.point(),
145        }
146    }
147
148    fn next(&mut self, work: &impl Work) -> Result<Option<u8>> {
149        loop {
150            let input = if self.index < self.value.raw.len() {
151                work.charge_work(1).map_err(|cause| Error {
152                    kind: ErrorKind::Resource(cause),
153                    location: self.point(),
154                })?;
155                Some(self.value.raw[self.index])
156            } else {
157                None
158            };
159            match self.decoder.step(input) {
160                Step::More => {
161                    debug_assert!(input.is_some());
162                    self.index += 1;
163                }
164                Step::Byte { value, consumed } => {
165                    if consumed {
166                        self.index += 1;
167                    }
168                    return Ok(Some(value));
169                }
170                Step::End => return Ok(None),
171                Step::Malformed => {
172                    if input.is_some() {
173                        self.index += 1;
174                    }
175                    return Err(self.malformed());
176                }
177            }
178        }
179    }
180}
181
182pub(crate) const fn is_space(byte: u8) -> bool {
183    matches!(byte, 0 | 9 | 10 | 12 | 13 | 32)
184}