fast_dav_rs/common/
compression.rs1use anyhow::Result;
7use async_compression::tokio::bufread::{BrotliDecoder, GzipDecoder, ZstdDecoder};
8use bytes::Bytes;
9use futures_util::TryStreamExt;
10use http_body_util::BodyStream;
11use hyper::body::Incoming;
12use hyper::{HeaderMap, header, http};
13use std::io::Cursor;
14use tokio::io::{AsyncBufRead, AsyncReadExt, BufReader};
15use tokio_util::io::StreamReader;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ContentEncoding {
23 Identity,
24 Br,
25 Gzip,
26 Zstd,
27}
28
29impl ContentEncoding {
30 pub fn as_str(&self) -> &'static str {
31 match self {
32 ContentEncoding::Identity => "identity",
33 ContentEncoding::Br => "br",
34 ContentEncoding::Gzip => "gzip",
35 ContentEncoding::Zstd => "zstd",
36 }
37 }
38}
39
40pub fn detect_encodings(headers: &HeaderMap) -> Vec<ContentEncoding> {
45 let Some(val) = headers.get(header::CONTENT_ENCODING) else {
46 return Vec::new();
47 };
48
49 let Ok(raw) = val.to_str() else {
50 return Vec::new();
51 };
52
53 raw.split(',')
54 .filter_map(|token| {
55 let enc = token.trim().to_ascii_lowercase();
56 Some(match enc.as_str() {
57 "br" => ContentEncoding::Br,
58 "gzip" => ContentEncoding::Gzip,
59 "zstd" | "zst" => ContentEncoding::Zstd,
60 "identity" => return None,
61 _ => return None,
62 })
63 })
64 .collect()
65}
66
67pub fn add_accept_encoding(h: &mut HeaderMap) {
71 if !h.contains_key(header::ACCEPT_ENCODING) {
72 h.insert(
73 header::ACCEPT_ENCODING,
74 http::HeaderValue::from_static("br, zstd, gzip"),
75 );
76 }
77}
78
79pub fn detect_request_compression_preference(headers: &HeaderMap) -> Option<ContentEncoding> {
86 let raw = headers.get(header::ACCEPT_ENCODING)?.to_str().ok()?;
87
88 let mut wildcard_q: Option<f32> = None;
89 let mut identity_q: f32 = 1.0;
90 let mut identity_explicit = false;
91 let mut entries: Vec<(String, f32)> = Vec::new();
92
93 for part in raw.split(',') {
94 let trimmed = part.trim();
95 if trimmed.is_empty() {
96 continue;
97 }
98
99 let mut segments = trimmed.split(';');
100 let token = segments.next().unwrap().trim().to_ascii_lowercase();
101 if token.is_empty() {
102 continue;
103 }
104
105 let mut weight = 1.0_f32;
106 for param in segments {
107 if let Some((key, value)) = param.split_once('=')
108 && key.trim().eq_ignore_ascii_case("q")
109 && let Ok(parsed) = value.trim().parse::<f32>()
110 {
111 weight = parsed.clamp(0.0, 1.0);
112 }
113 }
114
115 match token.as_str() {
116 "identity" => {
117 identity_q = weight;
118 identity_explicit = true;
119 }
120 "*" => {
121 wildcard_q = Some(weight);
122 }
123 other => entries.push((other.to_string(), weight)),
124 }
125 }
126
127 if !identity_explicit && let Some(q) = wildcard_q {
128 identity_q = q;
129 }
130
131 let mut best: Option<(ContentEncoding, f32)> = None;
132 for candidate in [
133 ContentEncoding::Br,
134 ContentEncoding::Zstd,
135 ContentEncoding::Gzip,
136 ] {
137 let direct_q = entries.iter().find_map(|(name, q)| {
138 if name == candidate.as_str() {
139 Some(*q)
140 } else {
141 None
142 }
143 });
144 let effective_q = direct_q.or(wildcard_q);
145
146 if let Some(q) = effective_q {
147 if q <= 0.0 {
148 continue;
149 }
150
151 let should_replace = best
152 .map(|(_, best_q)| q > best_q + f32::EPSILON)
153 .unwrap_or(true);
154 if should_replace {
155 best = Some((candidate, q));
156 }
157 }
158 }
159
160 if let Some((encoding, _)) = best {
161 return Some(encoding);
162 }
163
164 if identity_q > 0.0 {
165 return Some(ContentEncoding::Identity);
166 }
167
168 None
169}
170
171pub fn detect_encoding(headers: &HeaderMap) -> ContentEncoding {
173 detect_encodings(headers)
174 .into_iter()
175 .next()
176 .unwrap_or(ContentEncoding::Identity)
177}
178
179pub async fn decompress_body(body: Incoming, encodings: &[ContentEncoding]) -> Result<Bytes> {
184 let stream = BodyStream::new(body)
185 .map_ok(|frame| frame.into_data().unwrap_or_default())
186 .map_err(std::io::Error::other);
187 let reader = StreamReader::new(stream);
188 let reader = BufReader::new(reader);
189 let mut out = Vec::with_capacity(32 * 1024);
190 let mut current: Box<dyn AsyncBufRead + Unpin + Send> = Box::new(reader);
191
192 for encoding in encodings.iter().rev() {
193 current = match encoding {
194 ContentEncoding::Identity => current,
195 ContentEncoding::Br => Box::new(BufReader::new(BrotliDecoder::new(current))),
196 ContentEncoding::Gzip => Box::new(BufReader::new(GzipDecoder::new(current))),
197 ContentEncoding::Zstd => Box::new(BufReader::new(ZstdDecoder::new(current))),
198 };
199 }
200
201 let mut decoder = current;
202 decoder.read_to_end(&mut out).await?;
203
204 Ok(Bytes::from(out))
205}
206
207pub fn decompress_stream(
212 body: Incoming,
213 encodings: &[ContentEncoding],
214) -> Result<Box<dyn AsyncBufRead + Unpin + Send>> {
215 let stream = BodyStream::new(body)
216 .map_ok(|frame| frame.into_data().unwrap_or_default())
217 .map_err(std::io::Error::other);
218 let reader: Box<dyn AsyncBufRead + Unpin + Send> =
219 Box::new(BufReader::new(StreamReader::new(stream)));
220
221 let mut current = reader;
222 for encoding in encodings.iter().rev() {
223 current = match encoding {
224 ContentEncoding::Identity => current,
225 ContentEncoding::Br => Box::new(BufReader::new(BrotliDecoder::new(current))),
226 ContentEncoding::Gzip => Box::new(BufReader::new(GzipDecoder::new(current))),
227 ContentEncoding::Zstd => Box::new(BufReader::new(ZstdDecoder::new(current))),
228 };
229 }
230
231 Ok(current)
232}
233
234pub async fn compress_payload(data: Bytes, encoding: ContentEncoding) -> Result<Bytes> {
262 match encoding {
263 ContentEncoding::Identity => Ok(data),
264 ContentEncoding::Br => {
265 use async_compression::tokio::bufread::BrotliEncoder;
266
267 let mut encoder = BrotliEncoder::new(BufReader::new(Cursor::new(data)));
268 let mut compressed = Vec::new();
269 encoder.read_to_end(&mut compressed).await?;
270 Ok(Bytes::from(compressed))
271 }
272 ContentEncoding::Gzip => {
273 use async_compression::tokio::bufread::GzipEncoder;
274
275 let mut encoder = GzipEncoder::new(BufReader::new(Cursor::new(data)));
276 let mut compressed = Vec::new();
277 encoder.read_to_end(&mut compressed).await?;
278 Ok(Bytes::from(compressed))
279 }
280 ContentEncoding::Zstd => {
281 use async_compression::tokio::bufread::ZstdEncoder;
282
283 let mut encoder = ZstdEncoder::new(BufReader::new(Cursor::new(data)));
284 let mut compressed = Vec::new();
285 encoder.read_to_end(&mut compressed).await?;
286 Ok(Bytes::from(compressed))
287 }
288 }
289}
290
291pub fn add_content_encoding(headers: &mut HeaderMap, encoding: ContentEncoding) {
312 if encoding != ContentEncoding::Identity
313 && let Ok(value) = http::HeaderValue::from_str(encoding.as_str())
314 {
315 headers.insert("Content-Encoding", value);
316 }
317}