Skip to main content

typst_pack/pack_archive/
read.rs

1use std::io::{self, Read};
2
3#[cfg(feature = "fs")]
4use std::path::{Path, PathBuf};
5
6use super::{DecodeError, DecodeLimits, decode};
7use crate::limits::{LimitError, Limits, ResourceKind};
8use crate::{Pack, PackArchiveBytes};
9
10/// A resource bounded during Pack Archive Read.
11pub type ReadResource = ResourceKind<7>;
12
13#[allow(non_upper_case_globals)]
14impl ResourceKind<7> {
15    pub const ArchiveBytes: Self = Self::new(0);
16}
17
18/// Pack Archive Read exceeded a mandatory ceiling.
19pub type ReadLimitError = LimitError<ReadResource>;
20
21/// Mandatory finite resource ceilings for Pack Archive Read.
22pub type ReadLimits = Limits<ReadResource>;
23
24impl Limits<ReadResource> {
25    /// Constructs a validated read ceiling.
26    #[track_caller]
27    pub fn new(archive_bytes: u64) -> Self {
28        Self::from_ceilings([archive_bytes, 0, 0, 0, 0, 0, 0])
29            .assert_probe_resources([ReadResource::ArchiveBytes])
30    }
31
32    /// The first-party read limit for version-1 Pack Archives.
33    pub const fn reference_v1() -> Self {
34        Self::from_ceilings([512 * 1024 * 1024, 0, 0, 0, 0, 0, 0])
35    }
36
37    pub const fn archive_bytes(&self) -> u64 {
38        self.ceilings[0]
39    }
40}
41
42/// A failure while reading exact Pack Archive bytes from a stream.
43#[derive(Debug, thiserror::Error)]
44#[non_exhaustive]
45pub enum ReadError {
46    #[error(transparent)]
47    Limit(#[from] ReadLimitError),
48    #[error("failed to read Pack Archive bytes: {0}")]
49    Read(#[source] io::Error),
50}
51
52/// Reads exact bytes from a stream under a mandatory finite ceiling.
53pub fn read(mut reader: impl Read, limits: ReadLimits) -> Result<PackArchiveBytes, ReadError> {
54    const BUFFER_BYTES: usize = 8 * 1024;
55
56    let mut bytes = Vec::new();
57    let mut buffer = [0; BUFFER_BYTES];
58    let mut observed = 0u64;
59    loop {
60        let probe_end =
61            limits
62                .archive_bytes()
63                .checked_add(1)
64                .ok_or(ReadLimitError::AccountingOverflow {
65                    resource: ReadResource::ArchiveBytes,
66                })?;
67        let remaining =
68            probe_end
69                .checked_sub(observed)
70                .ok_or(ReadLimitError::AccountingOverflow {
71                    resource: ReadResource::ArchiveBytes,
72                })?;
73        let read_length = usize::try_from(remaining.min(BUFFER_BYTES as u64)).map_err(|_| {
74            ReadLimitError::AccountingOverflow {
75                resource: ReadResource::ArchiveBytes,
76            }
77        })?;
78        let read = match reader.read(&mut buffer[..read_length]) {
79            Ok(read) => read,
80            Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
81            Err(error) => return Err(ReadError::Read(error)),
82        };
83        if read == 0 {
84            return Ok(PackArchiveBytes::from_vec(bytes));
85        }
86        observed = observed
87            .checked_add(
88                u64::try_from(read).map_err(|_| ReadLimitError::AccountingOverflow {
89                    resource: ReadResource::ArchiveBytes,
90                })?,
91            )
92            .ok_or(ReadLimitError::AccountingOverflow {
93                resource: ReadResource::ArchiveBytes,
94            })?;
95        if observed > limits.archive_bytes() {
96            return Err(ReadLimitError::exceeded(
97                ReadResource::ArchiveBytes,
98                limits.archive_bytes(),
99            )
100            .into());
101        }
102        bytes.extend_from_slice(&buffer[..read]);
103    }
104}
105
106/// A failure in bounded read followed by Pack Archive Decoding.
107#[derive(Debug, thiserror::Error)]
108#[non_exhaustive]
109pub enum ReadPackError {
110    #[error(transparent)]
111    Read(#[from] ReadError),
112    #[error("read bytes could not be decoded as a Pack: {source}")]
113    Decode {
114        archive: PackArchiveBytes,
115        #[source]
116        source: DecodeError,
117    },
118}
119
120/// Reads and decodes one Pack while preserving exact bytes on decode failure.
121pub fn read_pack(
122    reader: impl Read,
123    read_limits: ReadLimits,
124    decode_limits: DecodeLimits,
125) -> Result<Pack, ReadPackError> {
126    let archive = read(reader, read_limits)?;
127    match decode(&archive, decode_limits) {
128        Ok(pack) => Ok(pack),
129        Err(source) => Err(ReadPackError::Decode { archive, source }),
130    }
131}
132
133/// The filesystem phase in which Pack Archive Read failed.
134#[cfg(feature = "fs")]
135#[derive(Debug, Clone, Copy, Eq, PartialEq)]
136#[non_exhaustive]
137pub enum FileReadPhase {
138    Open,
139    Metadata,
140    Read,
141}
142
143/// A failure while reading exact Pack Archive bytes from a file.
144#[cfg(feature = "fs")]
145#[derive(Debug, thiserror::Error)]
146#[non_exhaustive]
147pub enum FileReadError {
148    #[error("failed to open Pack Archive {path:?}: {source}")]
149    Open {
150        path: PathBuf,
151        #[source]
152        source: io::Error,
153    },
154    #[error("failed to inspect Pack Archive {path:?}: {source}")]
155    Metadata {
156        path: PathBuf,
157        #[source]
158        source: io::Error,
159    },
160    #[error("Pack Archive {path:?} exceeds its read limit: {source}")]
161    Limit {
162        path: PathBuf,
163        phase: FileReadPhase,
164        #[source]
165        source: ReadLimitError,
166    },
167    #[error("failed to read Pack Archive {path:?}: {source}")]
168    Read {
169        path: PathBuf,
170        #[source]
171        source: io::Error,
172    },
173}
174
175#[cfg(feature = "fs")]
176impl FileReadError {
177    pub const fn phase(&self) -> FileReadPhase {
178        match self {
179            Self::Open { .. } => FileReadPhase::Open,
180            Self::Metadata { .. } => FileReadPhase::Metadata,
181            Self::Limit { phase, .. } => *phase,
182            Self::Read { .. } => FileReadPhase::Read,
183        }
184    }
185
186    pub fn path(&self) -> &Path {
187        match self {
188            Self::Open { path, .. }
189            | Self::Metadata { path, .. }
190            | Self::Limit { path, .. }
191            | Self::Read { path, .. } => path,
192        }
193    }
194}
195
196/// Reads exact bytes from one file using known-size preflight and metered reads.
197#[cfg(feature = "fs")]
198pub fn read_file(
199    path: impl AsRef<Path>,
200    limits: ReadLimits,
201) -> Result<PackArchiveBytes, FileReadError> {
202    let path = path.as_ref();
203    let file = std::fs::File::open(path).map_err(|source| FileReadError::Open {
204        path: path.to_owned(),
205        source,
206    })?;
207    let known_size = file
208        .metadata()
209        .map_err(|source| FileReadError::Metadata {
210            path: path.to_owned(),
211            source,
212        })?
213        .len();
214    if known_size > limits.archive_bytes() {
215        return Err(FileReadError::Limit {
216            path: path.to_owned(),
217            phase: FileReadPhase::Metadata,
218            source: ReadLimitError::exceeded(ReadResource::ArchiveBytes, limits.archive_bytes()),
219        });
220    }
221    read(file, limits).map_err(|error| match error {
222        ReadError::Limit(source) => FileReadError::Limit {
223            path: path.to_owned(),
224            phase: FileReadPhase::Read,
225            source,
226        },
227        ReadError::Read(source) => FileReadError::Read {
228            path: path.to_owned(),
229            source,
230        },
231    })
232}
233
234/// A failure in bounded file read followed by Pack Archive Decoding.
235#[cfg(feature = "fs")]
236#[derive(Debug, thiserror::Error)]
237#[non_exhaustive]
238pub enum OpenPackError {
239    #[error(transparent)]
240    Read(#[from] FileReadError),
241    #[error("read bytes from {path:?} could not be decoded as a Pack: {source}")]
242    Decode {
243        path: PathBuf,
244        archive: PackArchiveBytes,
245        #[source]
246        source: Box<DecodeError>,
247    },
248}
249
250/// Reads and decodes one Pack file while preserving exact bytes on decode failure.
251#[cfg(feature = "fs")]
252pub fn open_pack(
253    path: impl AsRef<Path>,
254    read_limits: ReadLimits,
255    decode_limits: DecodeLimits,
256) -> Result<Pack, OpenPackError> {
257    let path = path.as_ref();
258    let archive = read_file(path, read_limits)?;
259    match decode(&archive, decode_limits) {
260        Ok(pack) => Ok(pack),
261        Err(source) => Err(OpenPackError::Decode {
262            path: path.to_owned(),
263            archive,
264            source: Box::new(source),
265        }),
266    }
267}