1use std::{
13 fmt,
14 fs::File,
15 io::{self, Read},
16 path::{Path, PathBuf},
17};
18
19use crate::{Limits, RObject};
20
21const XDR_MARKER: &[u8; 2] = b"X\n";
22const ZSTD_MAGIC: &[u8; 4] = &[0x28, 0xb5, 0x2f, 0xfd];
23const DEFAULT_CAP: usize = 256 * 1024 * 1024;
24
25#[derive(Debug, Clone, Copy)]
27pub struct ReadOptions {
28 limits: Limits,
29 max_compressed_bytes: usize,
30 max_decompressed_bytes: usize,
31}
32
33impl Default for ReadOptions {
34 fn default() -> Self {
35 Self {
36 limits: Limits::default(),
37 max_compressed_bytes: DEFAULT_CAP,
38 max_decompressed_bytes: DEFAULT_CAP,
39 }
40 }
41}
42
43impl ReadOptions {
44 pub fn limits(mut self, limits: Limits) -> Self {
45 self.limits = limits;
46 self
47 }
48
49 pub fn max_compressed_bytes(mut self, limit: usize) -> Self {
50 self.max_compressed_bytes = limit;
51 self
52 }
53
54 pub fn max_decompressed_bytes(mut self, limit: usize) -> Self {
55 self.max_decompressed_bytes = limit;
56 self
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum Compression {
64 Gzip,
65 Xz,
66 Bzip2,
67 Zstd,
68}
69
70impl fmt::Display for Compression {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 formatter.write_str(match self {
73 Self::Gzip => "gzip",
74 Self::Xz => "xz",
75 Self::Bzip2 => "bzip2",
76 Self::Zstd => "zstd",
77 })
78 }
79}
80
81#[derive(Debug, thiserror::Error)]
83#[non_exhaustive]
84pub enum ReadError {
85 #[error("I/O error at {path}: {source}")]
86 Io {
87 path: PathBuf,
88 #[source]
89 source: io::Error,
90 },
91 #[error("unknown RDS envelope magic bytes {magic:02x?}")]
92 UnknownEnvelope { magic: Vec<u8> },
93 #[error("{format} support is disabled")]
94 CompressionDisabled { format: Compression },
95 #[error("{format} decompression failed: {source}")]
96 Decompression {
97 format: Compression,
98 #[source]
99 source: io::Error,
100 },
101 #[error("compressed input exceeds the {limit}-byte limit")]
102 CompressedSizeLimitExceeded { limit: usize },
103 #[error("decompressed input exceeds the {limit}-byte limit")]
104 DecompressedSizeLimitExceeded { limit: usize },
105 #[error(transparent)]
106 Decode(#[from] crate::Error),
107}
108
109pub fn read(path: impl AsRef<Path>) -> Result<RObject, ReadError> {
111 read_with_options(path, &ReadOptions::default())
112}
113
114pub fn read_with_options(
116 path: impl AsRef<Path>,
117 options: &ReadOptions,
118) -> Result<RObject, ReadError> {
119 let path = path.as_ref();
120 let file = File::open(path).map_err(|source| ReadError::Io {
121 path: path.to_path_buf(),
122 source,
123 })?;
124 let mut bytes = Vec::new();
125 file.take(options.max_compressed_bytes.saturating_add(1) as u64)
126 .read_to_end(&mut bytes)
127 .map_err(|source| ReadError::Io {
128 path: path.to_path_buf(),
129 source,
130 })?;
131 if bytes.len() > options.max_compressed_bytes {
132 return Err(ReadError::CompressedSizeLimitExceeded {
133 limit: options.max_compressed_bytes,
134 });
135 }
136 from_bytes_with_options(&bytes, options)
137}
138
139pub fn from_bytes(bytes: &[u8]) -> Result<RObject, ReadError> {
141 from_bytes_with_options(bytes, &ReadOptions::default())
142}
143
144pub fn from_bytes_with_options(bytes: &[u8], options: &ReadOptions) -> Result<RObject, ReadError> {
146 if bytes.len() > options.max_compressed_bytes {
147 return Err(ReadError::CompressedSizeLimitExceeded {
148 limit: options.max_compressed_bytes,
149 });
150 }
151
152 let format = if is_xdr_stream(bytes) {
153 None
154 } else if bytes.starts_with(&[0x1f, 0x8b]) {
155 Some(Compression::Gzip)
156 } else if bytes.starts_with(&[0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00]) {
157 Some(Compression::Xz)
158 } else if bytes.starts_with(b"BZh") {
159 Some(Compression::Bzip2)
160 } else if bytes.starts_with(ZSTD_MAGIC) {
161 Some(Compression::Zstd)
162 } else {
163 return Err(ReadError::UnknownEnvelope {
164 magic: bytes[..bytes.len().min(6)].to_vec(),
165 });
166 };
167
168 let decoded = match format {
169 None => {
170 if bytes.len() > options.max_decompressed_bytes {
171 return Err(ReadError::DecompressedSizeLimitExceeded {
172 limit: options.max_decompressed_bytes,
173 });
174 }
175 bytes.to_vec()
176 }
177 Some(format) => decompress(bytes, format, options.max_decompressed_bytes)?,
178 };
179 Ok(crate::parse_with_limits(&decoded, options.limits)?)
180}
181
182pub fn is_xdr_stream(bytes: &[u8]) -> bool {
184 bytes.starts_with(XDR_MARKER)
185}
186
187fn decompress(bytes: &[u8], format: Compression, limit: usize) -> Result<Vec<u8>, ReadError> {
188 let output = match format {
189 Compression::Gzip => decompress_gzip(bytes, limit)?,
190 Compression::Xz => decompress_xz(bytes, limit)?,
191 Compression::Bzip2 => decompress_bzip2(bytes, limit)?,
192 Compression::Zstd => decompress_zstd(bytes, limit)?,
193 };
194 if output.len() > limit {
195 return Err(ReadError::DecompressedSizeLimitExceeded { limit });
196 }
197 Ok(output)
198}
199
200#[cfg(any(feature = "gzip", feature = "xz", feature = "bzip2", feature = "zstd"))]
201fn read_limited<R: std::io::Read>(
202 reader: R,
203 limit: usize,
204 format: Compression,
205) -> Result<Vec<u8>, ReadError> {
206 use std::io::Read as _;
207
208 let mut output = Vec::new();
209 reader
210 .take(limit.saturating_add(1) as u64)
211 .read_to_end(&mut output)
212 .map_err(|source| ReadError::Decompression { format, source })?;
213 Ok(output)
214}
215
216#[cfg(feature = "gzip")]
217fn decompress_gzip(bytes: &[u8], limit: usize) -> Result<Vec<u8>, ReadError> {
218 read_limited(
219 flate2::read::GzDecoder::new(bytes),
220 limit,
221 Compression::Gzip,
222 )
223}
224
225#[cfg(not(feature = "gzip"))]
226fn decompress_gzip(_: &[u8], _: usize) -> Result<Vec<u8>, ReadError> {
227 Err(ReadError::CompressionDisabled {
228 format: Compression::Gzip,
229 })
230}
231
232#[cfg(feature = "xz")]
233fn decompress_xz(bytes: &[u8], limit: usize) -> Result<Vec<u8>, ReadError> {
234 read_limited(xz2::read::XzDecoder::new(bytes), limit, Compression::Xz)
235}
236
237#[cfg(not(feature = "xz"))]
238fn decompress_xz(_: &[u8], _: usize) -> Result<Vec<u8>, ReadError> {
239 Err(ReadError::CompressionDisabled {
240 format: Compression::Xz,
241 })
242}
243
244#[cfg(feature = "bzip2")]
245fn decompress_bzip2(bytes: &[u8], limit: usize) -> Result<Vec<u8>, ReadError> {
246 read_limited(
247 bzip2::read::BzDecoder::new(bytes),
248 limit,
249 Compression::Bzip2,
250 )
251}
252
253#[cfg(not(feature = "bzip2"))]
254fn decompress_bzip2(_: &[u8], _: usize) -> Result<Vec<u8>, ReadError> {
255 Err(ReadError::CompressionDisabled {
256 format: Compression::Bzip2,
257 })
258}
259
260#[cfg(feature = "zstd")]
261fn decompress_zstd(bytes: &[u8], limit: usize) -> Result<Vec<u8>, ReadError> {
262 let mut source = bytes;
263 let decoder = ruzstd::decoding::StreamingDecoder::new(&mut source).map_err(|source| {
264 ReadError::Decompression {
265 format: Compression::Zstd,
266 source: io::Error::new(io::ErrorKind::InvalidData, source.to_string()),
267 }
268 })?;
269 read_limited(decoder, limit, Compression::Zstd)
270}
271
272#[cfg(not(feature = "zstd"))]
273fn decompress_zstd(_: &[u8], _: usize) -> Result<Vec<u8>, ReadError> {
274 Err(ReadError::CompressionDisabled {
275 format: Compression::Zstd,
276 })
277}