halldyll_core/fetch/
compression.rs1use bytes::Bytes;
4use flate2::read::{DeflateDecoder, GzDecoder};
5use std::io::Read;
6
7use crate::types::error::{Error, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompressionType {
12 None,
14 Gzip,
16 Deflate,
18 Brotli,
20}
21
22impl CompressionType {
23 pub fn from_header(content_encoding: Option<&str>) -> Self {
25 match content_encoding.map(|s| s.to_lowercase()).as_deref() {
26 Some("gzip") | Some("x-gzip") => CompressionType::Gzip,
27 Some("deflate") => CompressionType::Deflate,
28 Some("br") => CompressionType::Brotli,
29 _ => CompressionType::None,
30 }
31 }
32}
33
34pub struct Decompressor {
36 max_size: u64,
38 max_ratio: f64,
40}
41
42impl Default for Decompressor {
43 fn default() -> Self {
44 Self {
45 max_size: 100 * 1024 * 1024, max_ratio: 100.0,
47 }
48 }
49}
50
51impl Decompressor {
52 pub fn new(max_size: u64, max_ratio: f64) -> Self {
54 Self { max_size, max_ratio }
55 }
56
57 pub fn decompress(&self, data: &[u8], compression: CompressionType) -> Result<Bytes> {
59 match compression {
60 CompressionType::None => Ok(Bytes::copy_from_slice(data)),
61 CompressionType::Gzip => self.decompress_gzip(data),
62 CompressionType::Deflate => self.decompress_deflate(data),
63 CompressionType::Brotli => self.decompress_brotli(data),
64 }
65 }
66
67 fn decompress_gzip(&self, data: &[u8]) -> Result<Bytes> {
69 let mut decoder = GzDecoder::new(data);
70 self.read_with_limits(&mut decoder, data.len())
71 }
72
73 fn decompress_deflate(&self, data: &[u8]) -> Result<Bytes> {
75 let mut decoder = DeflateDecoder::new(data);
76 self.read_with_limits(&mut decoder, data.len())
77 }
78
79 fn decompress_brotli(&self, data: &[u8]) -> Result<Bytes> {
81 let mut decoder = brotli::Decompressor::new(data, 4096);
82 self.read_with_limits(&mut decoder, data.len())
83 }
84
85 fn read_with_limits<R: Read>(&self, reader: &mut R, compressed_size: usize) -> Result<Bytes> {
87 let mut output = Vec::new();
88 let mut buffer = [0u8; 8192];
89 let mut total_read: u64 = 0;
90
91 loop {
92 let n = reader.read(&mut buffer).map_err(|e| {
93 Error::Decompression(format!("Read error: {}", e))
94 })?;
95
96 if n == 0 {
97 break;
98 }
99
100 total_read += n as u64;
101
102 if total_read > self.max_size {
104 return Err(Error::SizeExceeded {
105 max: self.max_size,
106 actual: total_read,
107 });
108 }
109
110 if compressed_size > 0 {
112 let ratio = total_read as f64 / compressed_size as f64;
113 if ratio > self.max_ratio {
114 return Err(Error::Decompression(format!(
115 "Compression ratio {} exceeds limit {}",
116 ratio, self.max_ratio
117 )));
118 }
119 }
120
121 output.extend_from_slice(&buffer[..n]);
122 }
123
124 Ok(Bytes::from(output))
125 }
126}
127
128#[derive(Debug)]
130pub struct DecompressionResult {
131 pub data: Bytes,
133 pub compressed_size: u64,
135 pub decompressed_size: u64,
137 pub ratio: f64,
139}
140
141impl DecompressionResult {
142 pub fn new(data: Bytes, compressed_size: u64) -> Self {
144 let decompressed_size = data.len() as u64;
145 let ratio = if compressed_size > 0 {
146 decompressed_size as f64 / compressed_size as f64
147 } else {
148 1.0
149 };
150 Self {
151 data,
152 compressed_size,
153 decompressed_size,
154 ratio,
155 }
156 }
157}