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