Skip to main content

caj2pdf_core/
io.rs

1// SPDX-License-Identifier: MIT
2
3use crate::fallible::len_u64;
4use crate::{Error, Limits, Result};
5use std::io::Write;
6
7/// A source with a stable size snapshot and positioned reads.
8///
9/// A read may return fewer bytes than requested, including zero at end of
10/// input. Implementations must never write beyond `destination` or report
11/// more than its length. All callers bound each request to `MAX_IO_CHUNK`.
12/// The mutable receiver allows adapters over a seekable handle. Use
13/// [`crate::native::SeekableSource`] for a `Read + Seek` handle such as a
14/// `File` or a `Cursor<Vec<u8>>`; a byte slice is a source as it is.
15pub trait RangedSource {
16    fn size(&self) -> u64;
17    fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result<usize>;
18}
19
20/// A [`RangedSource`] adapter that adds each count its source returns to a
21/// caller-owned byte counter.
22///
23/// The counter is borrowed, so it keeps the bytes of the successful reads
24/// before a failing one and can accumulate across short-lived adapters. It
25/// saturates; reaching `u64::MAX` would take more reads than any input allows.
26///
27/// A count larger than the destination is passed on uncounted, for the
28/// caller's read helper to reject, unless [`Self::rejecting_overread`] makes
29/// the adapter reject it first.
30pub struct CountingSource<'a, S> {
31    source: &'a mut S,
32    bytes_read: &'a mut u64,
33    overread: Option<&'static str>,
34}
35
36impl<'a, S> CountingSource<'a, S> {
37    /// Count the bytes `source` returns into `bytes_read`.
38    pub fn new(source: &'a mut S, bytes_read: &'a mut u64) -> Self {
39        Self {
40            source,
41            bytes_read,
42            overread: None,
43        }
44    }
45
46    /// Reject a count larger than the destination as
47    /// an invalid-input [`Error`] with `reason`.
48    pub fn rejecting_overread(self, reason: &'static str) -> Self {
49        Self {
50            overread: Some(reason),
51            ..self
52        }
53    }
54}
55
56impl<S: RangedSource> RangedSource for CountingSource<'_, S> {
57    fn size(&self) -> u64 {
58        self.source.size()
59    }
60
61    fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result<usize> {
62        let read = self.source.read_at(offset, destination)?;
63        if read <= destination.len() {
64            *self.bytes_read = self.bytes_read.saturating_add(len_u64(read));
65        } else if let Some(reason) = self.overread {
66            return Err(Error::invalid(reason));
67        }
68        Ok(read)
69    }
70}
71
72/// A boxed source, such as a font resource in [`crate::Fonts`].
73impl<S: RangedSource + ?Sized> RangedSource for Box<S> {
74    fn size(&self) -> u64 {
75        (**self).size()
76    }
77
78    fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result<usize> {
79        (**self).read_at(offset, destination)
80    }
81}
82
83/// A byte slice is a source of its own length.
84impl RangedSource for &[u8] {
85    fn size(&self) -> u64 {
86        len_u64(self.len())
87    }
88
89    fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result<usize> {
90        let start = usize::try_from(offset)
91            .ok()
92            .filter(|&start| start <= self.len())
93            .ok_or(Error::invalid("read starts beyond source size"))?;
94        let count = destination.len().min(self.len() - start);
95        destination[..count].copy_from_slice(&self[start..start + count]);
96        Ok(count)
97    }
98}
99
100/// Bytes read into memory, with the source offset of their first byte.
101///
102/// The image decoders read a payload from memory but address it in source
103/// coordinates, so their errors keep naming source offsets. As a
104/// [`RangedSource`] it has the size of its end offset; bytes before its start
105/// are not readable.
106#[derive(Clone, Copy, Debug, Default)]
107pub struct Payload<'a> {
108    offset: u64,
109    bytes: &'a [u8],
110}
111
112impl<'a> Payload<'a> {
113    /// `bytes` start at source offset `offset`; their end must fit a `u64`.
114    pub fn new(offset: u64, bytes: &'a [u8]) -> Result<Self> {
115        offset
116            .checked_add(len_u64(bytes.len()))
117            .ok_or(Error::invalid("payload end overflows 64-bit offset"))?;
118        Ok(Self { offset, bytes })
119    }
120
121    /// The source offset of the first byte.
122    pub fn offset(&self) -> u64 {
123        self.offset
124    }
125
126    pub fn bytes(&self) -> &'a [u8] {
127        self.bytes
128    }
129
130    /// The source offset just past the last byte.
131    pub fn end(&self) -> u64 {
132        // Checked by `new`.
133        self.offset + len_u64(self.bytes.len())
134    }
135
136    /// The `length` bytes at source offset `start`, if all lie in this payload.
137    pub fn get(&self, start: u64, length: u64) -> Option<&'a [u8]> {
138        let first = usize::try_from(start.checked_sub(self.offset)?).ok()?;
139        let last = first.checked_add(usize::try_from(length).ok()?)?;
140        self.bytes.get(first..last)
141    }
142}
143
144/// A slice starting at source offset zero.
145impl<'a> From<&'a [u8]> for Payload<'a> {
146    fn from(bytes: &'a [u8]) -> Self {
147        Self { offset: 0, bytes }
148    }
149}
150
151impl RangedSource for Payload<'_> {
152    fn size(&self) -> u64 {
153        self.end()
154    }
155
156    fn read_at(&mut self, offset: u64, destination: &mut [u8]) -> Result<usize> {
157        let relative = offset
158            .checked_sub(self.offset)
159            .ok_or(Error::invalid("read starts before the payload"))?;
160        self.bytes.read_at(relative, destination)
161    }
162}
163
164/// Read `length` bytes at `offset` into `buffer`, replacing its contents, in
165/// requests of at most one I/O chunk. The length must fit
166/// `limits.max_allocation_bytes`; the buffer is reused between payloads.
167pub fn read_payload<'a, S: RangedSource, C: Cancellation>(
168    source: &mut S,
169    offset: u64,
170    length: u64,
171    buffer: &'a mut Vec<u8>,
172    limits: &Limits,
173    cancellation: &C,
174) -> Result<Payload<'a>> {
175    limits.check_allocation(length)?;
176    let size =
177        usize::try_from(length).map_err(|_| limits.allocation_refused("payload bytes", length))?;
178    buffer.clear();
179    buffer
180        .try_reserve_exact(size)
181        .map_err(|_| limits.allocation_refused("payload bytes", length))?;
182    buffer.resize(size, 0);
183    // A zero chunk size is refused by `Limits::validate` at the entry point;
184    // `read_exact_at` then refuses each nonempty chunk.
185    let chunk_bytes = limits.io_chunk_bytes.max(1);
186    for (index, chunk) in buffer.chunks_mut(chunk_bytes).enumerate() {
187        // Each chunk starts below `length`, which fits a `usize`.
188        let at = offset
189            .checked_add(len_u64(index * chunk_bytes))
190            .ok_or(Error::invalid("payload offset overflows 64-bit offset"))?;
191        read_exact_at(source, at, chunk, limits, cancellation)?;
192    }
193    Payload::new(offset, buffer)
194}
195
196/// A platform-provided cancellation signal, checked between rows, pages and
197/// I/O chunks.
198pub trait Cancellation {
199    fn is_cancelled(&self) -> bool;
200}
201
202/// A cancellation signal for operations that cannot be cancelled.
203#[derive(Clone, Copy, Debug, Default)]
204pub struct NeverCancel;
205
206impl Cancellation for NeverCancel {
207    fn is_cancelled(&self) -> bool {
208        false
209    }
210}
211
212fn check_cancelled<C: Cancellation>(cancellation: &C) -> Result<()> {
213    if cancellation.is_cancelled() {
214        Err(crate::ErrorKind::Cancelled.into())
215    } else {
216        Ok(())
217    }
218}
219
220fn check_range(source_size: u64, offset: u64, length: u64) -> Result<()> {
221    let end = offset
222        .checked_add(length)
223        .ok_or(Error::invalid("range end overflows 64-bit offset"))?;
224    if offset > source_size {
225        return Err(Error::invalid("range starts beyond source size"));
226    }
227    if end > source_size {
228        return Err(Error::truncated(offset, length, source_size - offset));
229    }
230    Ok(())
231}
232
233/// Fill one bounded buffer from a positioned source, tolerating short reads.
234///
235/// This function rejects a request larger than the configured I/O chunk. A
236/// format handler must process larger ranges in a loop.
237/// The caller checks the selected operation size; unrelated source bytes do
238/// not count against this individual read.
239pub fn read_exact_at<S: RangedSource, C: Cancellation>(
240    source: &mut S,
241    offset: u64,
242    destination: &mut [u8],
243    limits: &Limits,
244    cancellation: &C,
245) -> Result<()> {
246    let length = len_u64(destination.len());
247    limits.check_input_size(length)?;
248    if destination.len() > limits.io_chunk_bytes {
249        return Err(Error::limit(
250            "I/O request bytes",
251            len_u64(limits.io_chunk_bytes),
252            length,
253        ));
254    }
255    check_range(source.size(), offset, length)?;
256    check_cancelled(cancellation)?;
257
258    let mut done = 0;
259    while done < destination.len() {
260        let current = offset
261            .checked_add(len_u64(done))
262            .ok_or(Error::invalid("read offset overflows 64-bit offset"))?;
263        let remaining = &mut destination[done..];
264        let read = source.read_at(current, remaining)?;
265        if read > remaining.len() {
266            return Err(Error::invalid("source reported more bytes than requested"));
267        }
268        done += read;
269        check_cancelled(cancellation)?;
270        if read == 0 {
271            return Err(Error::truncated(offset, length, len_u64(done)));
272        }
273    }
274    Ok(())
275}
276
277/// Write all bytes in chunks of at most `limits.io_chunk_bytes`, checking
278/// the output limit before the first byte and cancellation between chunks.
279///
280/// `output_bytes_written` is an operation-wide counter. It counts every
281/// completed chunk, including when a later chunk or cancellation fails.
282pub(crate) fn write_counted<W: Write + ?Sized, C: Cancellation>(
283    sink: &mut W,
284    bytes: &[u8],
285    output_bytes_written: &mut u64,
286    limits: &Limits,
287    cancellation: &C,
288) -> Result<()> {
289    let attempted = output_bytes_written
290        .checked_add(len_u64(bytes.len()))
291        .ok_or(Error::invalid("output byte count overflows 64 bits"))?;
292    if attempted > limits.max_output_bytes {
293        return Err(Error::limit(
294            "output bytes",
295            limits.max_output_bytes,
296            attempted,
297        ));
298    }
299    check_cancelled(cancellation)?;
300    // A zero chunk size is refused by `Limits::validate` at the entry point.
301    for chunk in bytes.chunks(limits.io_chunk_bytes.max(1)) {
302        sink.write_all(chunk)?;
303        // Bounded by `attempted` above.
304        *output_bytes_written += len_u64(chunk.len());
305        check_cancelled(cancellation)?;
306    }
307    Ok(())
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use crate::ErrorKind;
314    use crate::test_support::CancelAfter;
315    use std::io;
316
317    /// Accepts at most `limit` bytes in total, then fails.
318    struct Bounded {
319        bytes: Vec<u8>,
320        limit: usize,
321    }
322
323    impl Write for Bounded {
324        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
325            let count = bytes.len().min(self.limit - self.bytes.len());
326            if count == 0 {
327                return Err(io::Error::other("sink full"));
328            }
329            self.bytes.extend_from_slice(&bytes[..count]);
330            Ok(count)
331        }
332
333        fn flush(&mut self) -> io::Result<()> {
334            Ok(())
335        }
336    }
337
338    fn chunked(chunk: usize) -> Limits {
339        Limits {
340            io_chunk_bytes: chunk,
341            ..Limits::default()
342        }
343    }
344
345    #[test]
346    fn counted_writes_check_the_output_limit_before_the_first_byte() {
347        let mut sink = Vec::new();
348        let limits = Limits {
349            max_output_bytes: 5,
350            ..Limits::default()
351        };
352        let mut count = 4;
353        assert!(matches!(
354            write_counted(&mut sink, b"xy", &mut count, &limits, &NeverCancel),
355            Err(Error {
356                kind: ErrorKind::LimitExceeded { attempted: 6, .. },
357                ..
358            })
359        ));
360        assert_eq!((count, sink.len()), (4, 0));
361        count = u64::MAX;
362        assert!(matches!(
363            write_counted(
364                &mut sink,
365                b"x",
366                &mut count,
367                &Limits::default(),
368                &NeverCancel
369            ),
370            Err(Error {
371                kind: ErrorKind::Malformed,
372                ..
373            })
374        ));
375    }
376
377    #[test]
378    fn counted_writes_count_completed_chunks_before_a_failure() {
379        let mut sink = Bounded {
380            bytes: Vec::new(),
381            limit: 3,
382        };
383        let mut count = 0;
384        let error =
385            write_counted(&mut sink, b"abcde", &mut count, &chunked(2), &NeverCancel).unwrap_err();
386        assert!(matches!(
387            error,
388            Error {
389                kind: ErrorKind::Io(_),
390                ..
391            }
392        ));
393        assert_eq!((count, sink.bytes.as_slice()), (2, &b"abc"[..]));
394    }
395
396    #[test]
397    fn counted_writes_observe_cancellation_between_chunks() {
398        let mut sink = Vec::new();
399        let mut count = 0;
400        // The check before the first chunk passes; the one after it trips.
401        let cancellation = CancelAfter::new(1);
402        assert!(matches!(
403            write_counted(&mut sink, b"abc", &mut count, &chunked(1), &cancellation),
404            Err(Error {
405                kind: ErrorKind::Cancelled,
406                ..
407            })
408        ));
409        assert_eq!((count, sink.as_slice()), (1, &b"a"[..]));
410    }
411
412    #[test]
413    fn core_errors_cross_an_io_write_adapter_unchanged() {
414        let carried = io::Error::from(Error::cancelled());
415        assert!(matches!(
416            Error::from(carried),
417            Error {
418                kind: ErrorKind::Cancelled,
419                ..
420            }
421        ));
422        let plain = io::Error::other("disk failed");
423        assert!(matches!(
424            Error::from(plain),
425            Error {
426                kind: ErrorKind::Io(_),
427                ..
428            }
429        ));
430        let io = io::Error::from(Error::from(ErrorKind::Io(io::Error::new(
431            io::ErrorKind::BrokenPipe,
432            "x",
433        ))));
434        assert_eq!(io.kind(), io::ErrorKind::BrokenPipe);
435    }
436}