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, IDENTITY, LENGTH, NAME, 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::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            let crypt_filter = self.crypt_filter();
126            let target = match &crypt_filter {
127                Some(name) => DecryptionTarget::Named(name),
128                None => DecryptionTarget::Stream,
129            };
130
131            match self.dict.obj_id() {
132                Some(obj_id) => Cow::Owned(
133                    ctx.xref()
134                        .decrypt(obj_id, self.data, target)
135                        // TODO: MAybe an error would be better?
136                        .unwrap_or_default(),
137                ),
138                // Should not be reachable, as streams always have IDs.
139                None => Cow::Borrowed(self.data),
140            }
141        } else {
142            Cow::Borrowed(self.data)
143        }
144    }
145
146    /// Return the raw, underlying dictionary of the stream.
147    pub fn dict(&self) -> &Dict<'a> {
148        &self.dict
149    }
150
151    /// Return the object identifier of the stream.
152    pub fn obj_id(&self) -> ObjectIdentifier {
153        self.dict.obj_id().unwrap_or_else(|| {
154            // In theory shouldn't ever happen, but could theoretically be
155            // triggered in a crafted PDF.
156            ObjectIdentifier::new(0, 0)
157        })
158    }
159
160    /// Return the filters that are applied to the stream.
161    pub fn filters(&self) -> SmallVec<[Filter; 2]> {
162        self.filters_and_params().filters
163    }
164
165    /// Return the decoded data of the stream.
166    ///
167    /// Note that the result of this method will not be cached, so calling it multiple
168    /// times is expensive.
169    pub fn decoded(&self) -> Result<Cow<'a, [u8]>, DecodeFailure> {
170        self.decoded_image(&ImageDecodeParams::default())
171            .map(|r| r.data)
172    }
173
174    /// Return the decoded data of a stream that isn't an image.
175    ///
176    /// Fails with [`DecodeFailure::ImageDecode`] if the stream uses an image filter
177    /// (`DCTDecode`, `JPXDecode`, `JBIG2Decode` or `CCITTFaxDecode`). Use
178    /// [`Stream::decoded_image`] for those.
179    pub fn decoded_data(&self) -> Result<Cow<'a, [u8]>, DecodeFailure> {
180        if self.filters().iter().any(|filter| {
181            matches!(
182                filter,
183                Filter::DctDecode
184                    | Filter::JpxDecode
185                    | Filter::Jbig2Decode
186                    | Filter::CcittFaxDecode
187            )
188        }) {
189            return Err(DecodeFailure::ImageDecode);
190        }
191
192        self.decoded()
193    }
194
195    /// Return the decoded data of the stream, and return image metadata
196    /// if available.
197    pub fn decoded_image(
198        &self,
199        image_params: &ImageDecodeParams,
200    ) -> Result<FilterResult<'a>, DecodeFailure> {
201        if matches!(
202            self.dict.get::<Object<'_>>(F),
203            Some(Object::String(_) | Object::Dict(_))
204        ) {
205            return Err(DecodeFailure::StreamDecode);
206        }
207
208        let data = self.raw_data();
209        let filters_and_params = self.filters_and_params();
210
211        let mut current: Option<FilterResult<'a>> = None;
212
213        for (i, (filter, params)) in filters_and_params
214            .filters
215            .iter()
216            .zip(filters_and_params.params.iter())
217            .enumerate()
218        {
219            if i == 0 && *filter == Filter::Crypt {
220                continue;
221            }
222
223            let new = filter.apply(
224                current.as_ref().map(|c| c.data.as_ref()).unwrap_or(&data),
225                params,
226                image_params,
227            )?;
228            current = Some(new);
229        }
230
231        Ok(current.unwrap_or(FilterResult {
232            data,
233            image_data: None,
234        }))
235    }
236
237    fn crypt_filter(&self) -> Option<Name<'a>> {
238        let FiltersAndParams { filters, params } = self.filters_and_params();
239
240        (filters.first() == Some(&Filter::Crypt)).then(|| {
241            params
242                .first()
243                .and_then(|params| params.get::<Name<'a>>(NAME))
244                .unwrap_or(Name::new_unescaped(IDENTITY))
245        })
246    }
247}
248
249impl Debug for Stream<'_> {
250    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
251        write!(f, "Stream (len: {:?})", self.data.len())
252    }
253}
254
255impl Display for Stream<'_> {
256    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
257        write!(f, "Stream (len: {})", self.data.len())
258    }
259}
260
261impl Skippable for Stream<'_> {
262    fn skip(_: &mut Reader<'_>, _: bool) -> Option<()> {
263        // A stream can never appear in a dict/array, so it should never be skipped.
264
265        None
266    }
267}
268
269impl<'a> Readable<'a> for Stream<'a> {
270    fn read(r: &mut Reader<'a>, ctx: &ReaderContext<'a>) -> Option<Self> {
271        let dict = r.read_with_context::<Dict<'_>>(ctx)?;
272        let offset = r.offset();
273        parse_proper(r, &dict).or_else(|| {
274            r.jump(offset);
275            parse_fallback(r, &dict)
276        })
277    }
278}
279
280#[derive(Debug, Copy, Clone)]
281/// A failure that can occur during decoding a data stream.
282pub enum DecodeFailure {
283    /// An image stream failed to decode.
284    ImageDecode,
285    /// A data stream failed to decode.
286    StreamDecode,
287    /// A failure occurred while decrypting a file.
288    Decryption,
289    /// An unknown failure occurred.
290    Unknown,
291}
292
293/// An image color space.
294#[derive(Debug, Copy, Clone)]
295pub enum ImageColorSpace {
296    /// Grayscale color space.
297    Gray,
298    /// RGB color space.
299    Rgb,
300    /// CMYK color space.
301    Cmyk,
302    /// An unknown color space.
303    Unknown(u8),
304}
305
306/// Additional data that is extracted from some image streams.
307pub struct ImageData {
308    /// An optional alpha channel of the image.
309    pub alpha: Option<Vec<u8>>,
310    /// The color space of the image.
311    pub color_space: Option<ImageColorSpace>,
312    /// The bits per component of the image.
313    pub bits_per_component: u8,
314    /// The width of the image.
315    pub width: u32,
316    /// The height of the image.
317    pub height: u32,
318}
319
320/// The result of applying a filter.
321pub struct FilterResult<'a> {
322    /// The decoded data.
323    pub data: Cow<'a, [u8]>,
324    /// Additional data that is extracted from JPX image streams.
325    pub image_data: Option<ImageData>,
326}
327
328impl FilterResult<'_> {
329    pub(crate) fn from_data(data: Vec<u8>) -> Self {
330        Self {
331            data: Cow::Owned(data),
332            image_data: None,
333        }
334    }
335}
336
337fn parse_proper<'a>(r: &mut Reader<'a>, dict: &Dict<'a>) -> Option<Stream<'a>> {
338    let length = dict.get::<u32>(LENGTH)?;
339
340    r.skip_white_spaces_and_comments();
341    r.forward_tag(b"stream")?;
342    r.forward_tag(b"\n")
343        .or_else(|| r.forward_tag(b"\r\n"))
344        .or_else(|| r.forward_tag(b"\r"))?;
345    let data = r.read_bytes(length as usize)?;
346    r.skip_white_spaces();
347    r.forward_tag(b"endstream")?;
348
349    Some(Stream::new(data, dict.clone()))
350}
351
352fn parse_fallback<'a>(r: &mut Reader<'a>, dict: &Dict<'a>) -> Option<Stream<'a>> {
353    r.skip_white_spaces_and_comments();
354    r.forward_tag(b"stream")?;
355
356    r.forward_tag(b"\n")
357        .or_else(|| r.forward_tag(b"\r\n"))
358        // Technically not allowed, but no reason to not try it.
359        .or_else(|| r.forward_tag(b"\r"))?;
360
361    // TODO: This search can take quadratic time in total when many streams with a wrong length
362    // have no `endstream` after them, or only a distant one.
363    let tail = r.tail()?;
364    let endstream_offset = find_needle(tail, b"endstream")?;
365    let data_end = trim_trailing_ascii_whitespace(&tail[..endstream_offset]);
366    let data = tail.get(..data_end)?;
367
368    r.read_bytes(endstream_offset)?;
369    r.skip_white_spaces();
370    r.forward_tag(b"endstream")?;
371
372    Some(Stream::new(data, dict.clone()))
373}
374
375fn trim_trailing_ascii_whitespace(data: &[u8]) -> usize {
376    let mut end = data.len();
377
378    while data
379        .get(end.wrapping_sub(1))
380        .copied()
381        .is_some_and(is_white_space_character)
382    {
383        end -= 1;
384    }
385
386    end
387}
388
389impl<'a> TryFrom<Object<'a>> for Stream<'a> {
390    type Error = ();
391
392    fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
393        match value {
394            Object::Stream(s) => Ok(s),
395            _ => Err(()),
396        }
397    }
398}
399
400impl<'a> ObjectLike<'a> for Stream<'a> {}
401impl<'a> ObjectRefLike<'a> for Stream<'a> {
402    fn cast_ref<'b>(obj: &'b Object<'a>) -> Option<&'b Self> {
403        match obj {
404            Object::Stream(stream) => Some(stream),
405            _ => None,
406        }
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use crate::object::Stream;
413    use crate::reader::Reader;
414    use crate::reader::{ReaderContext, ReaderExt};
415
416    #[test]
417    fn display() {
418        let mut reader = Reader::new(b"<< /Length 3 >> stream\nabc\nendstream");
419        let stream = reader
420            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
421            .unwrap();
422
423        assert_eq!(format!("{stream}"), "Stream (len: 3)");
424    }
425
426    #[test]
427    fn stream() {
428        let data = b"<< /Length 10 >> stream\nabcdefghij\nendstream";
429        let mut r = Reader::new(data);
430        let stream = r
431            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
432            .unwrap();
433
434        assert_eq!(stream.data, b"abcdefghij");
435    }
436
437    #[test]
438    fn stream_fallback() {
439        let data = b"<< /Length 999 >> stream\nabcdefghij\nendstream";
440        let mut r = Reader::new(data);
441        let stream = r
442            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
443            .unwrap();
444
445        assert_eq!(stream.data, b"abcdefghij");
446    }
447
448    #[test]
449    fn stream_with_external_file() {
450        let data = b"<< /Length 3 /F (data.bin) >> stream\nabc\nendstream";
451        let mut r = Reader::new(data);
452        let stream = r
453            .read_with_context::<Stream<'_>>(&ReaderContext::dummy())
454            .unwrap();
455
456        assert_eq!(stream.data, b"abc");
457        assert!(stream.dict().contains_key(crate::object::dict::keys::F));
458        assert!(stream.decoded().is_err());
459    }
460
461    #[test]
462    fn inline_filter_abbreviation_still_decodes() {
463        let mut r = Reader::new(b"<< /F /AHx >>");
464        let dict = r
465            .read_with_context::<crate::object::Dict<'_>>(&ReaderContext::dummy())
466            .unwrap();
467        let stream = Stream::new(b"41>", dict);
468
469        assert_eq!(stream.decoded().unwrap().as_ref(), b"A");
470    }
471}