Skip to main content

hayro_syntax/object/
stream.rs

1//! Streams.
2
3use crate::crypto::DecryptionTarget;
4use crate::filter::Filter;
5use crate::object;
6use crate::object::Dict;
7use crate::object::Name;
8use crate::object::dict::keys::{DECODE_PARMS, DP, F, FILTER, LENGTH, TYPE};
9use crate::object::{Array, ObjectIdentifier};
10use crate::object::{Object, ObjectLike, ObjectRefLike};
11use crate::reader::Reader;
12use crate::reader::{Readable, ReaderContext, ReaderExt, Skippable};
13use crate::trivia::is_white_space_character;
14use crate::util::{OptionLog, find_needle};
15use alloc::borrow::Cow;
16use alloc::vec::Vec;
17use core::fmt::{Debug, Display, Formatter};
18use smallvec::SmallVec;
19
20struct FiltersAndParams<'a> {
21    filters: SmallVec<[Filter; 2]>,
22    params: SmallVec<[Dict<'a>; 2]>,
23}
24
25/// A stream of arbitrary data.
26#[derive(Clone)]
27pub struct Stream<'a> {
28    dict: Dict<'a>,
29    data: &'a [u8],
30}
31
32impl PartialEq for Stream<'_> {
33    fn eq(&self, other: &Self) -> bool {
34        self.dict == other.dict && self.data == other.data
35    }
36}
37
38/// Additional parameters for decoding images.
39#[derive(Clone, PartialEq, Default)]
40pub struct ImageDecodeParams {
41    /// Whether the color space of the image is an indexed color space.
42    pub is_indexed: bool,
43    /// The bits per component of the image, if that information is available.
44    pub bpc: Option<u8>,
45    /// The components per channel of the image, if that information is available.
46    pub num_components: Option<u8>,
47    /// A target resolution for the image. Note that this is only a hint so that
48    /// in case it's possible, a version of the image will be extracted that
49    /// is as close as possible to the hinted dimension.
50    pub target_dimension: Option<(u32, u32)>,
51    /// The width of the image as indicated by the image dictionary.
52    pub width: u32,
53    /// The height of the image as indicated by the image dictionary.
54    pub height: u32,
55}
56
57impl<'a> Stream<'a> {
58    pub(crate) fn new(data: &'a [u8], dict: Dict<'a>) -> Self {
59        Self { dict, data }
60    }
61
62    fn filters_and_params(&self) -> FiltersAndParams<'a> {
63        let mut collected_filters = SmallVec::new();
64        let mut collected_params = SmallVec::new();
65
66        if let Some(filter) = self
67            .dict
68            .get::<Name<'_>>(F)
69            .or_else(|| self.dict.get::<Name<'_>>(FILTER))
70            .and_then(Filter::from_name)
71        {
72            let params = self
73                .dict
74                .get::<Dict<'_>>(DP)
75                .or_else(|| self.dict.get::<Dict<'_>>(DECODE_PARMS))
76                .unwrap_or_default();
77
78            collected_filters.push(filter);
79            collected_params.push(params);
80        } else if let Some(filters) = self
81            .dict
82            .get::<Array<'_>>(F)
83            .or_else(|| self.dict.get::<Array<'_>>(FILTER))
84        {
85            let filters = filters.iter::<Name<'_>>().map(Filter::from_name);
86            let mut params = self
87                .dict
88                .get::<Array<'_>>(DP)
89                .or_else(|| self.dict.get::<Array<'_>>(DECODE_PARMS))
90                .map(|a| a.iter::<Object<'_>>());
91
92            for filter in filters {
93                let params = params
94                    .as_mut()
95                    .and_then(|p| p.next())
96                    .and_then(|p| p.into_dict())
97                    .unwrap_or_default();
98
99                if let Some(filter) = filter {
100                    collected_filters.push(filter);
101                    collected_params.push(params);
102                }
103            }
104        }
105
106        FiltersAndParams {
107            filters: collected_filters,
108            params: collected_params,
109        }
110    }
111
112    /// Return the raw, decrypted data of the stream.
113    ///
114    /// Stream filters will not be applied.
115    pub fn raw_data(&self) -> Cow<'a, [u8]> {
116        let ctx = self.dict.ctx();
117
118        if ctx.xref().needs_decryption(ctx)
119            && self
120                .dict
121                .get::<object::String<'_>>(TYPE)
122                .map(|t| t.as_ref() != b"XRef")
123                .unwrap_or(true)
124        {
125            match self.dict.obj_id() {
126                Some(obj_id) => Cow::Owned(
127                    ctx.xref()
128                        .decrypt(obj_id, self.data, DecryptionTarget::Stream)
129                        // TODO: MAybe an error would be better?
130                        .unwrap_or_default(),
131                ),
132                // Should not be reachable, as streams always have IDs.
133                None => Cow::Borrowed(self.data),
134            }
135        } else {
136            Cow::Borrowed(self.data)
137        }
138    }
139
140    /// Return the raw, underlying dictionary of the stream.
141    pub fn dict(&self) -> &Dict<'a> {
142        &self.dict
143    }
144
145    /// Return the object identifier of the stream.
146    pub fn obj_id(&self) -> ObjectIdentifier {
147        self.dict.obj_id().unwrap_or_else(|| {
148            // In theory shouldn't ever happen, but could theoretically be
149            // triggered in a crafted PDF.
150            ObjectIdentifier::new(0, 0)
151        })
152    }
153
154    /// Return the filters that are applied to the stream.
155    pub fn filters(&self) -> SmallVec<[Filter; 2]> {
156        self.filters_and_params().filters
157    }
158
159    /// Return the decoded data of the stream.
160    ///
161    /// Note that the result of this method will not be cached, so calling it multiple
162    /// times is expensive.
163    pub fn decoded(&self) -> Result<Cow<'a, [u8]>, DecodeFailure> {
164        self.decoded_image(&ImageDecodeParams::default())
165            .map(|r| r.data)
166    }
167
168    /// Return the decoded data of the stream, and return image metadata
169    /// if available.
170    pub fn decoded_image(
171        &self,
172        image_params: &ImageDecodeParams,
173    ) -> Result<FilterResult<'a>, DecodeFailure> {
174        let data = self.raw_data();
175        let filters_and_params = self.filters_and_params();
176
177        let mut current: Option<FilterResult<'a>> = None;
178
179        for (filter, params) in filters_and_params
180            .filters
181            .iter()
182            .zip(filters_and_params.params.iter())
183        {
184            let new = filter.apply(
185                current.as_ref().map(|c| c.data.as_ref()).unwrap_or(&data),
186                params,
187                image_params,
188            )?;
189            current = Some(new);
190        }
191
192        Ok(current.unwrap_or(FilterResult {
193            data,
194            image_data: None,
195        }))
196    }
197}
198
199impl Debug for Stream<'_> {
200    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
201        write!(f, "Stream (len: {:?})", self.data.len())
202    }
203}
204
205impl Display for Stream<'_> {
206    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
207        write!(f, "Stream (len: {})", self.data.len())
208    }
209}
210
211impl Skippable for Stream<'_> {
212    fn skip(_: &mut Reader<'_>, _: bool) -> Option<()> {
213        // A stream can never appear in a dict/array, so it should never be skipped.
214        warn!("attempted to skip a stream object");
215
216        None
217    }
218}
219
220impl<'a> Readable<'a> for Stream<'a> {
221    fn read(r: &mut Reader<'a>, ctx: &ReaderContext<'a>) -> Option<Self> {
222        let dict = r.read_with_context::<Dict<'_>>(ctx)?;
223
224        if dict.contains_key(F) {
225            warn!("encountered stream referencing external file, which is unsupported");
226
227            return None;
228        }
229
230        let offset = r.offset();
231        parse_proper(r, &dict)
232            .or_else(|| {
233                warn!("failed to parse stream, trying to parse it manually");
234
235                r.jump(offset);
236                parse_fallback(r, &dict)
237            })
238            .error_none("was unable to manually parse the stream")
239    }
240}
241
242#[derive(Debug, Copy, Clone)]
243/// A failure that can occur during decoding a data stream.
244pub enum DecodeFailure {
245    /// An image stream failed to decode.
246    ImageDecode,
247    /// A data stream failed to decode.
248    StreamDecode,
249    /// A failure occurred while decrypting a file.
250    Decryption,
251    /// An unknown failure occurred.
252    Unknown,
253}
254
255/// An image color space.
256#[derive(Debug, Copy, Clone)]
257pub enum ImageColorSpace {
258    /// Grayscale color space.
259    Gray,
260    /// RGB color space.
261    Rgb,
262    /// CMYK color space.
263    Cmyk,
264    /// An unknown color space.
265    Unknown(u8),
266}
267
268/// Additional data that is extracted from some image streams.
269pub struct ImageData {
270    /// An optional alpha channel of the image.
271    pub alpha: Option<Vec<u8>>,
272    /// The color space of the image.
273    pub color_space: Option<ImageColorSpace>,
274    /// The bits per component of the image.
275    pub bits_per_component: u8,
276    /// The width of the image.
277    pub width: u32,
278    /// The height of the image.
279    pub height: u32,
280}
281
282/// The result of applying a filter.
283pub struct FilterResult<'a> {
284    /// The decoded data.
285    pub data: Cow<'a, [u8]>,
286    /// Additional data that is extracted from JPX image streams.
287    pub image_data: Option<ImageData>,
288}
289
290impl FilterResult<'_> {
291    pub(crate) fn from_data(data: Vec<u8>) -> Self {
292        Self {
293            data: Cow::Owned(data),
294            image_data: None,
295        }
296    }
297}
298
299fn parse_proper<'a>(r: &mut Reader<'a>, dict: &Dict<'a>) -> Option<Stream<'a>> {
300    let length = dict.get::<u32>(LENGTH)?;
301
302    r.skip_white_spaces_and_comments();
303    r.forward_tag(b"stream")?;
304    r.forward_tag(b"\n")
305        .or_else(|| r.forward_tag(b"\r\n"))
306        .or_else(|| r.forward_tag(b"\r"))?;
307    let data = r.read_bytes(length as usize)?;
308    r.skip_white_spaces();
309    r.forward_tag(b"endstream")?;
310
311    Some(Stream::new(data, dict.clone()))
312}
313
314fn parse_fallback<'a>(r: &mut Reader<'a>, dict: &Dict<'a>) -> Option<Stream<'a>> {
315    let stream_offset = find_needle(r.tail()?, b"stream")?;
316    r.read_bytes(stream_offset)?;
317    r.forward_tag(b"stream")?;
318
319    r.forward_tag(b"\n")
320        .or_else(|| r.forward_tag(b"\r\n"))
321        // Technically not allowed, but no reason to not try it.
322        .or_else(|| r.forward_tag(b"\r"))?;
323
324    let tail = r.tail()?;
325    let endstream_offset = find_needle(tail, b"endstream")?;
326    let data_end = trim_trailing_ascii_whitespace(&tail[..endstream_offset]);
327    let data = tail.get(..data_end)?;
328
329    r.read_bytes(endstream_offset)?;
330    r.skip_white_spaces();
331    r.forward_tag(b"endstream")?;
332
333    Some(Stream::new(data, dict.clone()))
334}
335
336fn trim_trailing_ascii_whitespace(data: &[u8]) -> usize {
337    let mut end = data.len();
338
339    while data
340        .get(end.wrapping_sub(1))
341        .copied()
342        .is_some_and(is_white_space_character)
343    {
344        end -= 1;
345    }
346
347    end
348}
349
350impl<'a> TryFrom<Object<'a>> for Stream<'a> {
351    type Error = ();
352
353    fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
354        match value {
355            Object::Stream(s) => Ok(s),
356            _ => Err(()),
357        }
358    }
359}
360
361impl<'a> ObjectLike<'a> for Stream<'a> {}
362impl<'a> ObjectRefLike<'a> for Stream<'a> {
363    fn cast_ref<'b>(obj: &'b Object<'a>) -> Option<&'b Self> {
364        match obj {
365            Object::Stream(stream) => Some(stream),
366            _ => None,
367        }
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use crate::object::Stream;
374    use crate::reader::Reader;
375    use crate::reader::{ReaderContext, ReaderExt};
376
377    #[test]
378    fn display() {
379        let mut reader = Reader::new(b"<< /Length 3 >> stream\nabc\nendstream");
380        let stream = reader
381            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
382            .unwrap();
383
384        assert_eq!(format!("{stream}"), "Stream (len: 3)");
385    }
386
387    #[test]
388    fn stream() {
389        let data = b"<< /Length 10 >> stream\nabcdefghij\nendstream";
390        let mut r = Reader::new(data);
391        let stream = r
392            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
393            .unwrap();
394
395        assert_eq!(stream.data, b"abcdefghij");
396    }
397
398    #[test]
399    fn stream_fallback() {
400        let data = b"<< /Length 999 >> stream\nabcdefghij\nendstream";
401        let mut r = Reader::new(data);
402        let stream = r
403            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
404            .unwrap();
405
406        assert_eq!(stream.data, b"abcdefghij");
407    }
408}