flare_core/common/compression/
algorithms.rs1use super::traits::Compressor;
6use crate::common::error::{FlareError, Result};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub enum CompressionAlgorithm {
13 None,
15 Gzip,
17 Zstd,
19 Custom(String),
39}
40
41impl CompressionAlgorithm {
42 #[allow(clippy::should_implement_trait)]
59 pub fn from_str(s: &str) -> Option<Self> {
60 match s.to_lowercase().as_str() {
61 "none" | "" => Some(CompressionAlgorithm::None),
62 "gzip" => Some(CompressionAlgorithm::Gzip),
63 "zstd" => Some(CompressionAlgorithm::Zstd),
64 custom => Some(CompressionAlgorithm::Custom(custom.to_string())),
65 }
66 }
67
68 pub fn as_str(&self) -> String {
81 match self {
82 CompressionAlgorithm::None => "none".to_string(),
83 CompressionAlgorithm::Gzip => "gzip".to_string(),
84 CompressionAlgorithm::Zstd => "zstd".to_string(),
85 CompressionAlgorithm::Custom(name) => name.clone(),
86 }
87 }
88
89 pub fn is_custom(&self) -> bool {
91 matches!(self, CompressionAlgorithm::Custom(_))
92 }
93
94 pub fn custom_name(&self) -> Option<&str> {
96 match self {
97 CompressionAlgorithm::Custom(name) => Some(name),
98 _ => None,
99 }
100 }
101}
102
103pub struct NoCompressor;
105
106impl Compressor for NoCompressor {
107 fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
108 Ok(data.to_vec())
109 }
110
111 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
112 Ok(data.to_vec())
113 }
114
115 fn algorithm(&self) -> CompressionAlgorithm {
116 CompressionAlgorithm::None
117 }
118
119 fn name(&self) -> &'static str {
120 "none"
121 }
122
123 fn can_detect(&self, _data: &[u8]) -> bool {
124 false
126 }
127}
128
129#[cfg(feature = "compression-gzip")]
131const MAX_GZIP_DECOMPRESSED_LEN: u64 = 16 * 1024 * 1024;
132
133pub struct GzipCompressor;
135
136impl Compressor for GzipCompressor {
137 fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
138 #[cfg(feature = "compression-gzip")]
139 {
140 use flate2::Compression;
141 use flate2::write::GzEncoder;
142 use std::io::Write;
143
144 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
145 encoder.write_all(data).map_err(|e| {
146 FlareError::encoding_error(format!("Gzip compression failed: {}", e))
147 })?;
148 encoder.finish().map_err(|e| {
149 FlareError::encoding_error(format!("Gzip compression finish failed: {}", e))
150 })
151 }
152
153 #[cfg(not(feature = "compression-gzip"))]
154 {
155 let _ = data;
156 Err(FlareError::operation_not_supported(
157 "gzip compression feature is disabled",
158 ))
159 }
160 }
161
162 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>> {
163 #[cfg(feature = "compression-gzip")]
164 {
165 use flate2::read::GzDecoder;
166 use std::io::Read;
167
168 let mut decoder = GzDecoder::new(data).take(MAX_GZIP_DECOMPRESSED_LEN + 1);
170 let mut decompressed = Vec::new();
171 decoder.read_to_end(&mut decompressed).map_err(|e| {
172 FlareError::encoding_error(format!("Gzip decompression failed: {}", e))
173 })?;
174 if decompressed.len() as u64 > MAX_GZIP_DECOMPRESSED_LEN {
175 return Err(FlareError::encoding_error(format!(
176 "Gzip decompressed payload exceeds limit ({MAX_GZIP_DECOMPRESSED_LEN} bytes)"
177 )));
178 }
179 Ok(decompressed)
180 }
181
182 #[cfg(not(feature = "compression-gzip"))]
183 {
184 let _ = data;
185 Err(FlareError::operation_not_supported(
186 "gzip compression feature is disabled",
187 ))
188 }
189 }
190
191 fn algorithm(&self) -> CompressionAlgorithm {
192 CompressionAlgorithm::Gzip
193 }
194
195 fn name(&self) -> &'static str {
196 "gzip"
197 }
198
199 fn can_detect(&self, data: &[u8]) -> bool {
200 data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b
202 }
203}
204
205#[cfg(all(test, feature = "compression-gzip"))]
206mod gzip_decompress_limit_tests {
207 use super::GzipCompressor;
208 use crate::common::compression::traits::Compressor;
209
210 #[test]
211 fn roundtrip_under_limit_is_unchanged() {
212 let c = GzipCompressor;
213 let data = vec![7u8; 1024 * 1024]; let compressed = c.compress(&data).unwrap();
215 let restored = c.decompress(&compressed).unwrap();
216 assert_eq!(restored, data);
217 }
218
219 #[test]
220 fn decompress_rejects_zip_bomb_over_limit() {
221 let c = GzipCompressor;
222 let data = vec![0u8; 17 * 1024 * 1024];
224 let compressed = c.compress(&data).unwrap();
225 assert!(
226 compressed.len() < 1024 * 1024,
227 "bomb payload should compress small"
228 );
229 assert!(
230 c.decompress(&compressed).is_err(),
231 "decompressed payload exceeding the limit must be rejected"
232 );
233 }
234}