use std::io::{self, Read};
pub(crate) struct LimitedReader<R> {
inner: R,
remaining: u64,
limit: u64,
}
impl<R: Read> LimitedReader<R> {
pub(crate) fn new(inner: R, limit: u64) -> Self {
Self {
inner,
remaining: limit,
limit,
}
}
}
impl<R: Read> Read for LimitedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.inner.read(buf)?;
if n as u64 > self.remaining {
return Err(io::Error::other(format!(
"decompressed output exceeded the {}-byte limit (possible decompression bomb); \
raise the limit via the relevant A3S_BOX_MAX_*_BYTES env var if this is a \
legitimately large image/archive",
self.limit
)));
}
self.remaining -= n as u64;
Ok(n)
}
}
pub(crate) fn cap_from_env(var: &str, default_bytes: u64) -> u64 {
std::env::var(var)
.ok()
.and_then(|v| v.trim().parse::<u64>().ok())
.filter(|&v| v > 0)
.unwrap_or(default_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn errors_once_total_read_exceeds_limit() {
let data = vec![0u8; 10 * 1024];
let mut r = LimitedReader::new(&data[..], 4 * 1024);
let mut sink = Vec::new();
let err = std::io::copy(&mut r, &mut sink).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::Other);
assert!(sink.len() as u64 <= 4 * 1024, "must not exceed the cap");
assert!(err.to_string().contains("decompression bomb"));
}
#[test]
fn passes_data_under_the_limit() {
let data = vec![7u8; 4096];
let mut r = LimitedReader::new(&data[..], 1024 * 1024);
let mut sink = Vec::new();
std::io::copy(&mut r, &mut sink).unwrap();
assert_eq!(sink, data);
}
}