Skip to main content

pdfrum_object/
stream.rs

1//! Stream objects (ISO 32000-1 ยง7.3.8) and the zero-copy window their data
2//! lives in.
3
4use std::fmt;
5use std::ops::{Deref, Range};
6use std::sync::Arc;
7
8use bytes::Bytes;
9
10use crate::{Dict, Error};
11
12/// A window into a shared byte buffer.
13///
14/// The document's bytes are held once and every stream is a window into them,
15/// so opening a file costs one copy no matter how many streams it holds. Three
16/// buffers occur in practice: the file itself, a decrypted replacement for one
17/// stream's bytes, and a decoded object stream's payload.
18///
19/// Backed by [`bytes::Bytes`], whose owner pointer is separate from its data
20/// pointer. That is what lets [`ByteSpan::from`] adopt a `Vec<u8>`'s allocation
21/// instead of copying it โ€” `Arc<[u8]>` cannot, because its refcounts live
22/// inline with the payload. A window is a refcount bump, never an allocation,
23/// so [`subspan`](Self::subspan) is the cheap way to carve a file up.
24///
25/// Decoded (filtered) data is deliberately *not* cached here โ€” the page layer
26/// owns those caches, keyed by the reference that produced them.
27///
28/// ```
29/// use std::sync::Arc;
30/// use pdfrum_object::ByteSpan;
31///
32/// let file: Arc<[u8]> = Arc::from(&b"%PDF-1.7 stream-bytes"[..]);
33/// let span = ByteSpan::new(file, 9..21).unwrap();
34/// assert_eq!(&*span, b"stream-bytes");
35/// assert_eq!(span.len(), 12);
36/// ```
37#[derive(Clone)]
38pub struct ByteSpan {
39    buf: Bytes,
40}
41
42impl ByteSpan {
43    /// A window covering `range` of `file`.
44    ///
45    /// # Errors
46    ///
47    /// [`Error::SpanOutOfBounds`] when the range runs past the buffer or ends
48    /// before it starts. Ranges come from `/Length` values in untrusted
49    /// files, so this is checked rather than trusted.
50    pub fn new(file: Arc<[u8]>, range: Range<usize>) -> Result<Self, Error> {
51        // Checked before slicing, never delegated to `Bytes::slice`: that
52        // panics where this must return, and the ranges are untrusted.
53        if range.start > range.end || range.end > file.len() {
54            return Err(Error::SpanOutOfBounds {
55                start: range.start,
56                end: range.end,
57                len: file.len(),
58            });
59        }
60        Ok(Self {
61            buf: Bytes::from_owner(file).slice(range),
62        })
63    }
64
65    /// A window over a whole buffer.
66    #[must_use]
67    pub fn whole(file: Arc<[u8]>) -> Self {
68        Self {
69            buf: Bytes::from_owner(file),
70        }
71    }
72
73    /// An empty window.
74    #[must_use]
75    pub fn empty() -> Self {
76        Self { buf: Bytes::new() }
77    }
78
79    /// The bytes in the window.
80    #[must_use]
81    pub fn as_bytes(&self) -> &[u8] {
82        &self.buf
83    }
84
85    /// Number of bytes in the window.
86    #[must_use]
87    pub fn len(&self) -> usize {
88        self.buf.len()
89    }
90
91    /// Whether the window is empty.
92    #[must_use]
93    pub fn is_empty(&self) -> bool {
94        self.buf.is_empty()
95    }
96
97    /// A sub-window, with offsets relative to this window's start.
98    ///
99    /// # Errors
100    ///
101    /// [`Error::SpanOutOfBounds`] when `range` leaves this window.
102    pub fn subspan(&self, range: Range<usize>) -> Result<Self, Error> {
103        if range.start > range.end || range.end > self.len() {
104            return Err(Error::SpanOutOfBounds {
105                start: range.start,
106                end: range.end,
107                len: self.len(),
108            });
109        }
110        Ok(Self {
111            buf: self.buf.slice(range),
112        })
113    }
114}
115
116impl Deref for ByteSpan {
117    type Target = [u8];
118
119    fn deref(&self) -> &[u8] {
120        self.as_bytes()
121    }
122}
123
124impl AsRef<[u8]> for ByteSpan {
125    fn as_ref(&self) -> &[u8] {
126        self.as_bytes()
127    }
128}
129
130impl PartialEq for ByteSpan {
131    fn eq(&self, other: &Self) -> bool {
132        self.as_bytes() == other.as_bytes()
133    }
134}
135
136impl Eq for ByteSpan {}
137
138impl fmt::Debug for ByteSpan {
139    /// Prints the window's shape rather than its bytes: stream payloads run
140    /// to megabytes and a `Debug` dump of an object tree must stay readable.
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        f.debug_struct("ByteSpan")
143            .field("len", &self.len())
144            .finish_non_exhaustive()
145    }
146}
147
148impl From<Arc<[u8]>> for ByteSpan {
149    /// Shares the buffer; nothing is copied.
150    fn from(file: Arc<[u8]>) -> Self {
151        Self::whole(file)
152    }
153}
154
155impl From<Vec<u8>> for ByteSpan {
156    /// Adopts the vector's allocation; nothing is copied.
157    fn from(bytes: Vec<u8>) -> Self {
158        Self {
159            buf: Bytes::from(bytes),
160        }
161    }
162}
163
164/// A stream object: a dictionary describing bytes, plus the bytes.
165///
166/// The data is the *raw* payload as it sits in the file โ€” filters have not
167/// been applied and encryption has, if the document was encrypted. Its length
168/// is authoritative: a `/Length` in the dictionary that disagreed with the
169/// bytes found before `endstream` was already repaired by the reader, which
170/// leaves the dictionary untouched and records a diagnostic.
171///
172/// ```
173/// use pdfrum_object::{ByteSpan, Dict, Object, Stream, names};
174///
175/// let stream = Stream {
176///     dict: Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]),
177///     data: ByteSpan::from(b"abc".to_vec()),
178/// };
179/// assert_eq!(&*stream.data, b"abc");
180/// ```
181#[derive(Debug, Clone, PartialEq)]
182pub struct Stream {
183    /// The stream's dictionary: `/Length`, `/Filter`, `/DecodeParms`, and
184    /// whatever the stream's own type adds.
185    pub dict: Dict,
186    /// The raw stream data.
187    pub data: ByteSpan,
188}
189
190impl Stream {
191    /// A stream from a dictionary and its raw bytes.
192    #[must_use]
193    pub fn new(dict: Dict, data: ByteSpan) -> Self {
194        Self { dict, data }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use std::ops::Range;
201    use std::sync::Arc;
202
203    use super::ByteSpan;
204    use crate::Error;
205
206    #[test]
207    fn window_covers_only_its_range() {
208        let file: Arc<[u8]> = Arc::from(&b"0123456789"[..]);
209        let span = ByteSpan::new(Arc::clone(&file), 2..5).unwrap();
210        assert_eq!(&*span, b"234");
211        assert_eq!(span.len(), 3);
212        assert!(!span.is_empty());
213    }
214
215    #[test]
216    fn out_of_bounds_ranges_are_refused_not_clamped() {
217        let file: Arc<[u8]> = Arc::from(&b"0123"[..]);
218        assert_eq!(
219            ByteSpan::new(Arc::clone(&file), 0..5),
220            Err(Error::SpanOutOfBounds {
221                start: 0,
222                end: 5,
223                len: 4
224            })
225        );
226        let reversed = Range { start: 3, end: 1 };
227        assert!(ByteSpan::new(Arc::clone(&file), reversed).is_err());
228        assert!(ByteSpan::new(file, 4..4).is_ok());
229    }
230
231    #[test]
232    fn subspans_are_relative_and_bounded() {
233        let span = ByteSpan::from(b"0123456789".to_vec());
234        let inner = span.subspan(2..5).unwrap();
235        assert_eq!(&*inner, b"234");
236        assert_eq!(&*inner.subspan(1..2).unwrap(), b"3");
237        assert!(inner.subspan(0..4).is_err());
238    }
239
240    #[test]
241    fn spans_compare_by_content_not_by_backing_buffer() {
242        let a = ByteSpan::from(b"abc".to_vec());
243        let b = ByteSpan::new(Arc::from(&b"xxabcxx"[..]), 2..5).unwrap();
244        assert_eq!(a, b);
245        assert!(ByteSpan::empty().is_empty());
246    }
247}