Skip to main content

kftray_http_logs/
parser.rs

1use std::io::{
2    self,
3    Read,
4};
5use std::str;
6
7use anyhow::Result;
8use brotli::Decompressor;
9use flate2::bufread::GzDecoder;
10use httparse::{
11    EMPTY_HEADER,
12    Header,
13    Request as HttpRequest,
14    Response as HttpResponse,
15    Status as ParseStatus,
16};
17use tokio::task;
18use tracing::debug;
19
20const MAX_HEADERS: usize = 64;
21/// Maximum allowed chunk size (100MB) to prevent integer overflow and DoS
22/// attacks
23const MAX_CHUNK_SIZE: usize = 100 * 1024 * 1024;
24
25/// Decodes every gzip member in a body, like `cat a.gz b.gz | gunzip`.
26///
27/// Unlike `MultiGzDecoder`, it stops without an error at bytes after a
28/// member that do not start another one, such as the start of the next
29/// response in the same capture, and at a later member it cannot decode,
30/// such as one cut off at the end of the capture.
31pub(crate) struct GzipMembers<'a> {
32    member: GzDecoder<&'a [u8]>,
33    first_member: bool,
34}
35
36impl<'a> GzipMembers<'a> {
37    pub(crate) fn new(data: &'a [u8]) -> Self {
38        Self {
39            member: GzDecoder::new(data),
40            first_member: true,
41        }
42    }
43}
44
45impl Read for GzipMembers<'_> {
46    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
47        loop {
48            let read = match self.member.read(buf) {
49                Ok(read) => read,
50                Err(_) if !self.first_member => return Ok(0),
51                Err(e) => return Err(e),
52            };
53            let rest = *self.member.get_ref();
54            if read > 0 || buf.is_empty() || !rest.starts_with(&[0x1f, 0x8b]) {
55                return Ok(read);
56            }
57            self.member = GzDecoder::new(rest);
58            self.first_member = false;
59        }
60    }
61}
62
63#[derive(Debug, PartialEq)]
64enum ContentCategory {
65    Empty,
66    Binary,
67    Json,
68    JavaScript,
69    Css,
70    Html,
71    Xml,
72    Svg,
73    Text,
74    Font,
75}
76
77lazy_static::lazy_static! {
78    static ref HEADERS_CACHE: dashmap::DashMap<&'static str, &'static str> = {
79        let map = dashmap::DashMap::new();
80        map.insert("content-type", "content-type");
81        map.insert("content-length", "content-length");
82        map.insert("content-encoding", "content-encoding");
83        map.insert("transfer-encoding", "transfer-encoding");
84        map
85    };
86}
87
88#[derive(Debug)]
89pub enum ChunkParseResult<'a> {
90    Complete {
91        chunk_data: &'a [u8],
92        next_pos: usize,
93    },
94    EndOfChunks {
95        next_pos: usize,
96    },
97    Incomplete {
98        chunk_data: Option<&'a [u8]>,
99        consumed: usize,
100    },
101    Skip {
102        consumed: usize,
103    },
104}
105
106pub type RequestParseResult<'a> = (
107    Option<&'a str>,
108    Option<&'a str>,
109    Option<u16>,
110    Vec<Header<'a>>,
111);
112
113pub struct RequestParser;
114
115impl RequestParser {
116    pub fn parse(buffer: &[u8]) -> Result<RequestParseResult<'_>> {
117        let mut headers = [EMPTY_HEADER; MAX_HEADERS];
118        let mut req = HttpRequest::new(&mut headers);
119
120        match req.parse(buffer)? {
121            ParseStatus::Complete(_) => Ok((
122                req.method,
123                req.path,
124                req.version.map(|v| v as u16),
125                req.headers.to_vec(),
126            )),
127            ParseStatus::Partial => Ok((None, None, None, Vec::new())),
128        }
129    }
130
131    pub fn get_content_length(headers: &[Header<'_>]) -> usize {
132        if let Some(content_length) = HEADERS_CACHE.get("content-length") {
133            let header_name = content_length.value();
134            for h in headers {
135                if h.name.eq_ignore_ascii_case(header_name) {
136                    if let Ok(v) = str::from_utf8(h.value)
137                        && let Ok(len) = v.parse::<usize>()
138                    {
139                        return len;
140                    }
141                    break;
142                }
143            }
144        }
145        0
146    }
147
148    pub fn get_content_encoding<'a>(headers: &'a [Header<'_>]) -> Option<&'a str> {
149        for header in headers {
150            if header.name.eq_ignore_ascii_case("content-encoding")
151                && let Ok(value) = std::str::from_utf8(header.value)
152            {
153                return Some(value.trim());
154            }
155        }
156        None
157    }
158
159    pub fn is_gzip_encoded(headers: &[Header<'_>]) -> bool {
160        if let Some(encoding) = Self::get_content_encoding(headers) {
161            return encoding == "gzip" || encoding.eq_ignore_ascii_case("gzip");
162        }
163        false
164    }
165
166    pub fn is_brotli_encoded(headers: &[Header<'_>]) -> bool {
167        if let Some(encoding) = Self::get_content_encoding(headers) {
168            return encoding == "br" || encoding.eq_ignore_ascii_case("br");
169        }
170        false
171    }
172
173    pub fn is_chunked_transfer(headers: &[Header<'_>]) -> bool {
174        if let Some(transfer_encoding) = HEADERS_CACHE.get("transfer-encoding") {
175            let header_name = transfer_encoding.value();
176            for h in headers {
177                if h.name.eq_ignore_ascii_case(header_name)
178                    && (h.value == b"chunked" || h.value.eq_ignore_ascii_case(b"chunked"))
179                {
180                    return true;
181                }
182            }
183        }
184        false
185    }
186
187    pub fn extract_body(buffer: &[u8]) -> Option<&[u8]> {
188        buffer
189            .windows(4)
190            .position(|window| window == b"\r\n\r\n")
191            .map(|headers_end| {
192                let body_start = headers_end + 4;
193                &buffer[body_start..]
194            })
195    }
196
197    pub fn process_chunked_body(body: &[u8]) -> Vec<u8> {
198        if body.is_empty() {
199            return Vec::new();
200        }
201
202        let estimated_capacity = body.len() * 9 / 10;
203        let mut result = Vec::with_capacity(estimated_capacity);
204        let mut pos = 0;
205        let mut chunks_found = 0;
206        let mut incomplete_chunk = false;
207        let mut last_incomplete_data: Option<&[u8]> = None;
208        let mut last_chunk_size: Option<usize> = None;
209
210        while pos < body.len() {
211            match Self::parse_chunk(&body[pos..], &mut chunks_found) {
212                Ok(ChunkParseResult::Complete {
213                    chunk_data,
214                    next_pos,
215                }) => {
216                    result.extend_from_slice(chunk_data);
217                    pos += next_pos;
218                    last_incomplete_data = None;
219                    last_chunk_size = None;
220                }
221                Ok(ChunkParseResult::EndOfChunks { next_pos }) => {
222                    pos += next_pos;
223                    if pos < body.len() {
224                        debug!(
225                            "Found end marker with {} bytes of trailing data",
226                            body.len() - pos
227                        );
228                    }
229                    break;
230                }
231                Ok(ChunkParseResult::Incomplete {
232                    chunk_data,
233                    consumed,
234                }) => {
235                    if let Some(data) = chunk_data {
236                        last_incomplete_data = Some(data);
237                        if let Some(chunk_line_end) = data.windows(2).position(|w| w == b"\r\n")
238                            && let Ok(size_str) = std::str::from_utf8(&data[..chunk_line_end])
239                            && let Ok(size) = usize::from_str_radix(size_str.trim(), 16)
240                        {
241                            last_chunk_size = Some(size);
242                        }
243                        result.extend_from_slice(data);
244                    }
245                    pos += consumed;
246                    incomplete_chunk = true;
247
248                    if pos >= body.len() {
249                        break;
250                    }
251                }
252                Ok(ChunkParseResult::Skip { consumed }) => {
253                    pos += consumed;
254                }
255                Err(e) => {
256                    debug!("Chunk parsing error: {:?} at position {}", e, pos);
257
258                    if pos < body.len() && body.len() - pos > 4 {
259                        if incomplete_chunk {
260                            if let (Some(last_incomplete_data), Some(last_chunk_size)) =
261                                (last_incomplete_data, last_chunk_size)
262                            {
263                                let remaining = &body[pos..];
264                                let last_size = last_chunk_size;
265                                let last_data_len = last_incomplete_data.len();
266
267                                if last_data_len + remaining.len() >= last_size {
268                                    debug!("Adding remaining data to complete partial chunk");
269                                    result.extend_from_slice(remaining);
270                                }
271                            } else {
272                                result.extend_from_slice(&body[pos..]);
273                            }
274                        } else {
275                            result.extend_from_slice(&body[pos..]);
276                        }
277                    }
278                    break;
279                }
280            }
281        }
282
283        if chunks_found > 0 && !result.is_empty() && !incomplete_chunk {
284            return result;
285        }
286
287        Self::handle_chunked_edge_cases(body, result, chunks_found, incomplete_chunk)
288    }
289
290    fn parse_chunk<'a>(
291        chunk_data: &'a [u8], chunks_found: &mut usize,
292    ) -> Result<ChunkParseResult<'a>> {
293        let line_end = match chunk_data.windows(2).position(|w| w == b"\r\n") {
294            Some(pos) => pos,
295            None => return Err(anyhow::anyhow!("No CRLF found in chunk header")),
296        };
297
298        if line_end == 0 {
299            return Ok(ChunkParseResult::Skip { consumed: 2 });
300        }
301
302        let hex_str = match std::str::from_utf8(&chunk_data[..line_end]) {
303            Ok(s) => s.trim(),
304            Err(_) => {
305                return Ok(ChunkParseResult::Skip {
306                    consumed: line_end + 2,
307                });
308            }
309        };
310
311        if hex_str.is_empty() {
312            return Ok(ChunkParseResult::Skip {
313                consumed: line_end + 2,
314            });
315        }
316
317        let size_part = if let Some(semi_idx) = hex_str.find(';') {
318            &hex_str[..semi_idx]
319        } else {
320            hex_str
321        };
322
323        let chunk_size = match usize::from_str_radix(size_part, 16) {
324            Ok(size) if size <= MAX_CHUNK_SIZE => size,
325            Ok(_) => {
326                return Ok(ChunkParseResult::Skip {
327                    consumed: line_end + 2,
328                });
329            }
330            Err(_) => {
331                return Ok(ChunkParseResult::Skip {
332                    consumed: line_end + 2,
333                });
334            }
335        };
336
337        *chunks_found += 1;
338
339        if chunk_size == 0 {
340            return Ok(ChunkParseResult::EndOfChunks {
341                next_pos: line_end + 2 + 2,
342            });
343        }
344
345        let chunk_start = line_end + 2;
346
347        let Some(chunk_end) = chunk_start.checked_add(chunk_size) else {
348            return Ok(ChunkParseResult::Skip {
349                consumed: line_end + 2,
350            });
351        };
352
353        if chunk_end <= chunk_data.len() {
354            let Some(next_pos) = chunk_end.checked_add(2) else {
355                return Ok(ChunkParseResult::Incomplete {
356                    chunk_data: Some(&chunk_data[chunk_start..chunk_end]),
357                    consumed: chunk_data.len(),
358                });
359            };
360            if next_pos <= chunk_data.len() {
361                Ok(ChunkParseResult::Complete {
362                    chunk_data: &chunk_data[chunk_start..chunk_end],
363                    next_pos,
364                })
365            } else {
366                Ok(ChunkParseResult::Incomplete {
367                    chunk_data: Some(&chunk_data[chunk_start..chunk_end]),
368                    consumed: chunk_data.len(),
369                })
370            }
371        } else if chunk_start < chunk_data.len() {
372            Ok(ChunkParseResult::Incomplete {
373                chunk_data: Some(&chunk_data[chunk_start..]),
374                consumed: chunk_data.len(),
375            })
376        } else {
377            Ok(ChunkParseResult::Incomplete {
378                chunk_data: None,
379                consumed: chunk_data.len(),
380            })
381        }
382    }
383
384    fn handle_chunked_edge_cases(
385        body: &[u8], result: Vec<u8>, chunks_found: usize, incomplete_chunk: bool,
386    ) -> Vec<u8> {
387        if chunks_found == 0 && result.is_empty() && !body.is_empty() {
388            return body.to_vec();
389        }
390
391        if incomplete_chunk && body.len() > result.len() {
392            let mut combined = result;
393
394            if combined.len() < body.len() {
395                let unparsed_start = body
396                    .iter()
397                    .zip(combined.iter())
398                    .position(|(a, b)| a != b)
399                    .unwrap_or(combined.len().min(body.len()));
400
401                if unparsed_start < body.len() {
402                    combined.extend_from_slice(&body[unparsed_start..]);
403                }
404            }
405
406            return combined;
407        }
408
409        if result.is_empty() && !body.is_empty() {
410            body.to_vec()
411        } else {
412            result
413        }
414    }
415}
416
417pub struct ResponseParser;
418
419impl ResponseParser {
420    pub fn parse(buffer: &[u8]) -> Result<(Option<u16>, Vec<Header<'_>>)> {
421        let mut headers = [EMPTY_HEADER; MAX_HEADERS];
422        let mut res = HttpResponse::new(&mut headers);
423
424        match res.parse(buffer)? {
425            ParseStatus::Complete(_) => Ok((res.code, res.headers.to_vec())),
426            ParseStatus::Partial => Ok((None, Vec::new())),
427        }
428    }
429}
430
431pub struct BodyParser;
432
433impl BodyParser {
434    pub fn is_image(headers: &[Header<'_>]) -> bool {
435        headers
436            .iter()
437            .any(|h| h.name.eq_ignore_ascii_case("content-type") && h.value.starts_with(b"image/"))
438    }
439
440    pub fn get_content_encoding<'a>(headers: &'a [Header<'_>]) -> Option<&'a str> {
441        for header in headers {
442            if header.name.eq_ignore_ascii_case("content-encoding")
443                && let Ok(value) = std::str::from_utf8(header.value)
444            {
445                return Some(value.trim());
446            }
447        }
448        None
449    }
450
451    pub fn get_content_type<'a>(headers: &'a [Header<'_>]) -> Option<&'a str> {
452        for header in headers {
453            if header.name.eq_ignore_ascii_case("content-type")
454                && let Ok(value) = str::from_utf8(header.value)
455            {
456                return Some(value.trim());
457            }
458        }
459        None
460    }
461
462    pub async fn process_response_body(body: &[u8], headers: &[Header<'_>]) -> Result<Vec<u8>> {
463        let is_chunked = RequestParser::is_chunked_transfer(headers);
464        let dechunked_body = if is_chunked {
465            debug!(
466                "Processing chunked response body, size before dechunking: {}",
467                body.len()
468            );
469            let result = RequestParser::process_chunked_body(body);
470            debug!(
471                "Dechunked response body size: {} -> {}",
472                body.len(),
473                result.len()
474            );
475            result
476        } else {
477            body.to_vec()
478        };
479
480        let content_encoding = Self::get_content_encoding(headers);
481
482        if let Some(encoding) = content_encoding {
483            debug!("Detected content encoding: {}", encoding);
484        }
485
486        match content_encoding {
487            Some(encoding) if encoding == "gzip" || encoding.eq_ignore_ascii_case("gzip") => {
488                debug!(
489                    "Decompressing gzip encoded content, size: {}",
490                    dechunked_body.len()
491                );
492
493                let mut decompressed = None;
494
495                match Self::decompress_gzip(&dechunked_body).await {
496                    Ok(result) if !result.is_empty() && result.len() > dechunked_body.len() / 2 => {
497                        debug!(
498                            "Successfully decompressed gzip content: {} bytes -> {} bytes",
499                            dechunked_body.len(),
500                            result.len()
501                        );
502                        decompressed = Some(result);
503                    }
504                    Ok(result) if !result.is_empty() => {
505                        debug!(
506                            "Partial gzip decompression: {} bytes -> {} bytes, trying alternative approaches",
507                            dechunked_body.len(),
508                            result.len()
509                        );
510                        decompressed = Some(result);
511                    }
512                    _ => {
513                        debug!("Standard gzip decompression failed, trying alternative approaches");
514                    }
515                }
516
517                if (decompressed.is_none() || decompressed.as_ref().unwrap().is_empty())
518                    && let Some(gzip_start) = Self::find_gzip_header(&dechunked_body)
519                    && gzip_start > 0
520                {
521                    debug!(
522                        "Found gzip header at offset {}, attempting alternative decompression",
523                        gzip_start
524                    );
525                    match Self::decompress_gzip(&dechunked_body[gzip_start..]).await {
526                        Ok(result) if !result.is_empty() => {
527                            debug!(
528                                "Alternative gzip decompression succeeded: {} bytes",
529                                result.len()
530                            );
531                            decompressed = Some(result);
532                        }
533                        _ => {
534                            debug!(
535                                "Alternative gzip decompression failed or produced empty output"
536                            );
537                        }
538                    }
539                }
540
541                match decompressed {
542                    Some(result) if !result.is_empty() => {
543                        debug!("Final decompressed size: {} bytes", result.len());
544                        Ok(result)
545                    }
546                    _ => {
547                        debug!(
548                            "All gzip decompression attempts failed, returning original content"
549                        );
550                        Ok(dechunked_body)
551                    }
552                }
553            }
554            Some(encoding) if encoding == "br" || encoding.eq_ignore_ascii_case("br") => {
555                debug!(
556                    "Decompressing brotli encoded content, size: {}",
557                    dechunked_body.len()
558                );
559                match Self::decompress_brotli(&dechunked_body).await {
560                    Ok(decompressed) => {
561                        debug!(
562                            "Successfully decompressed brotli content: {} bytes -> {} bytes",
563                            dechunked_body.len(),
564                            decompressed.len()
565                        );
566                        Ok(decompressed)
567                    }
568                    Err(e) => {
569                        debug!(
570                            "Brotli decompression failed: {:?}, returning original content",
571                            e
572                        );
573                        Ok(dechunked_body)
574                    }
575                }
576            }
577            Some(encoding) => {
578                debug!("Unknown content encoding: {}, returning as-is", encoding);
579                Ok(dechunked_body)
580            }
581            None => Ok(dechunked_body),
582        }
583    }
584
585    fn find_gzip_header(data: &[u8]) -> Option<usize> {
586        data.windows(2)
587            .position(|window| window[0] == 0x1f && window[1] == 0x8b)
588    }
589
590    pub async fn decompress_gzip(data: &[u8]) -> Result<Vec<u8>> {
591        let data = data.to_vec();
592
593        if data.len() < 8 {
594            return Ok(data);
595        }
596
597        if data.len() >= 2 && data[0] == 0x1F && data[1] == 0x8B {
598            task::spawn_blocking(move || {
599                let mut decoder = GzipMembers::new(&data[..]);
600                let mut decompressed_data = Vec::new();
601                if decoder.read_to_end(&mut decompressed_data).is_err() {
602                    if decompressed_data.is_empty() {
603                        return Ok(data);
604                    } else {
605                        return Ok(decompressed_data);
606                    }
607                }
608                Ok(decompressed_data)
609            })
610            .await?
611        } else {
612            Ok(data)
613        }
614    }
615
616    pub async fn decompress_brotli(data: &[u8]) -> Result<Vec<u8>> {
617        if data.is_empty() || data.len() < 4 {
618            return Ok(data.to_vec());
619        }
620
621        let data = data.to_vec();
622        task::spawn_blocking(move || {
623            let mut reader = Decompressor::new(&data[..], data.len());
624            let mut decompressed_data = Vec::new();
625
626            match reader.read_to_end(&mut decompressed_data) {
627                Ok(_) => {
628                    if !decompressed_data.is_empty() {
629                        debug!(
630                            "Successfully decompressed brotli data: {} -> {} bytes",
631                            data.len(),
632                            decompressed_data.len()
633                        );
634                        Ok(decompressed_data)
635                    } else {
636                        debug!("Brotli decompression produced empty result, using original");
637                        Ok(data)
638                    }
639                }
640                Err(e) => {
641                    debug!("Brotli decompression error: {:?}", e);
642
643                    let mut reader = Decompressor::new(&data[..], 65536);
644                    let mut decompressed_data = Vec::new();
645
646                    match reader.read_to_end(&mut decompressed_data) {
647                        Ok(_) if !decompressed_data.is_empty() => {
648                            debug!(
649                                "Alternative brotli decompression succeeded: {} bytes",
650                                decompressed_data.len()
651                            );
652                            Ok(decompressed_data)
653                        }
654                        _ => {
655                            debug!("All brotli decompression attempts failed, returning original");
656                            Ok(data)
657                        }
658                    }
659                }
660            }
661        })
662        .await?
663    }
664
665    fn bytes_to_utf8_string(bytes: &[u8]) -> String {
666        if let Ok(s) = std::str::from_utf8(bytes) {
667            return s.to_string();
668        }
669
670        String::from_utf8_lossy(bytes).to_string()
671    }
672
673    fn identify_content_category(content_type: Option<&str>, body: &[u8]) -> ContentCategory {
674        if body.is_empty() {
675            return ContentCategory::Empty;
676        }
677
678        if let Some(ct) = content_type {
679            let ct_lower = ct.to_lowercase();
680
681            if ct_lower.contains("svg") {
682                return ContentCategory::Svg;
683            }
684
685            if ct_lower.starts_with("image/") {
686                return ContentCategory::Binary;
687            }
688            if ct_lower.starts_with("audio/") {
689                return ContentCategory::Binary;
690            }
691            if ct_lower.starts_with("video/") {
692                return ContentCategory::Binary;
693            }
694            if ct_lower.contains("font/") || ct_lower.contains("application/font") {
695                return ContentCategory::Font;
696            }
697            if ct_lower.contains("application/pdf")
698                || ct_lower.contains("application/msword")
699                || ct_lower.contains("octet-stream")
700            {
701                return ContentCategory::Binary;
702            }
703
704            if ct_lower.contains("json") {
705                return ContentCategory::Json;
706            }
707            if ct_lower.contains("javascript") {
708                return ContentCategory::JavaScript;
709            }
710            if ct_lower.contains("css") {
711                return ContentCategory::Css;
712            }
713            if ct_lower.contains("html") {
714                return ContentCategory::Html;
715            }
716            if ct_lower.contains("xml") {
717                return ContentCategory::Xml;
718            }
719            if ct_lower.starts_with("text/") {
720                return ContentCategory::Text;
721            }
722        }
723
724        if body.len() > 2 {
725            if body.windows(4).any(|w| w == b"<svg") {
726                return ContentCategory::Svg;
727            }
728
729            if (body[0] == b'{' && body.contains(&b'}'))
730                || (body[0] == b'[' && body.contains(&b']'))
731            {
732                return ContentCategory::Json;
733            }
734
735            if body.len() > 5
736                && (body.starts_with(b"<?xml")
737                    || body.starts_with(b"<!DOC")
738                    || body.windows(5).any(|w| w == b"<html")
739                    || body.windows(5).any(|w| w == b"<body")
740                    || body.windows(5).any(|w| w == b"<head"))
741            {
742                if body.windows(5).any(|w| w == b"<html") || body.windows(5).any(|w| w == b"<body")
743                {
744                    return ContentCategory::Html;
745                }
746
747                return ContentCategory::Xml;
748            }
749
750            if body.starts_with(&[0xFF, 0xD8])
751                || body.starts_with(&[0x89, 0x50, 0x4E, 0x47])
752                || body.starts_with(&[0x47, 0x49, 0x46])
753            {
754                return ContentCategory::Binary;
755            }
756
757            let likely_binary = body
758                .iter()
759                .take(32)
760                .filter(|&&b| b < 9 && b != b'\t' && b != b'\n' && b != b'\r')
761                .count()
762                > 8;
763            if likely_binary {
764                return ContentCategory::Binary;
765            }
766        }
767
768        ContentCategory::Text
769    }
770
771    pub async fn format_body_async(body: &[u8], headers: &[Header<'_>]) -> Result<String> {
772        debug!(
773            "Processing response body for formatting, size: {} bytes",
774            body.len()
775        );
776
777        let processed_body = match Self::process_response_body(body, headers).await {
778            Ok(processed) => {
779                debug!(
780                    "Successfully processed body: {} bytes -> {} bytes",
781                    body.len(),
782                    processed.len()
783                );
784                processed
785            }
786            Err(e) => {
787                debug!("Failed to process body: {:?}, using original", e);
788                body.to_vec()
789            }
790        };
791
792        let content_type = Self::get_content_type(headers);
793        if let Some(ct) = content_type {
794            debug!("Content-Type: {}", ct);
795        } else {
796            debug!("No Content-Type header found");
797        }
798
799        match Self::format_body(&processed_body, content_type) {
800            Ok(formatted) => {
801                debug!("Successfully formatted body: {} bytes", formatted.len());
802                Ok(formatted)
803            }
804            Err(e) => {
805                debug!(
806                    "Failed to format body: {:?}, trying direct string conversion",
807                    e
808                );
809
810                if let Ok(text) = std::str::from_utf8(&processed_body) {
811                    debug!("Direct string conversion succeeded");
812                    Ok(text.to_string())
813                } else {
814                    debug!("Direct string conversion failed, using lossy conversion");
815                    Ok(String::from_utf8_lossy(&processed_body).to_string())
816                }
817            }
818        }
819    }
820
821    pub fn format_body(body: &[u8], content_type: Option<&str>) -> Result<String> {
822        let category = Self::identify_content_category(content_type, body);
823        debug!("Content category identified as: {:?}", category);
824
825        let result = match category {
826            ContentCategory::Empty => "# <empty body>".to_string(),
827
828            ContentCategory::Binary => {
829                let content_desc = content_type.unwrap_or("binary");
830                let preview = if !body.is_empty() {
831                    let preview_size = std::cmp::min(body.len(), 64);
832                    let bytes: Vec<String> = body[..preview_size]
833                        .iter()
834                        .map(|b| format!("{b:02x}"))
835                        .collect();
836                    format!("\n# Preview: {} ", bytes.join(" "))
837                } else {
838                    "".to_string()
839                };
840                format!(
841                    "# <binary data: {} format, {} bytes>{}",
842                    content_desc,
843                    body.len(),
844                    preview
845                )
846            }
847
848            ContentCategory::Font => {
849                let font_type = content_type.unwrap_or("font");
850                format!(
851                    "# <binary data: {} format, {} bytes>",
852                    font_type,
853                    body.len()
854                )
855            }
856
857            ContentCategory::Json => {
858                let body_str = Self::bytes_to_utf8_string(body);
859                let trimmed = body_str.trim();
860                debug!(
861                    "Attempting to format JSON content, size: {} bytes",
862                    trimmed.len()
863                );
864
865                match serde_json::from_str::<serde_json::Value>(trimmed) {
866                    Ok(json_value) => {
867                        debug!("Successfully parsed JSON, pretty-printing");
868                        match serde_json::to_string_pretty(&json_value) {
869                            Ok(pretty_json) => {
870                                debug!(
871                                    "Successfully pretty-printed JSON: {} bytes",
872                                    pretty_json.len()
873                                );
874                                pretty_json
875                            }
876                            Err(e) => {
877                                debug!("Error pretty-printing JSON: {:?}", e);
878                                format!("# <JSON content - error formatting>\n{trimmed}")
879                            }
880                        }
881                    }
882                    Err(e) => {
883                        debug!("Error parsing JSON: {:?}", e);
884
885                        if trimmed.contains("}\n{") || trimmed.contains("}\r\n{") {
886                            debug!("Detected potential JSON stream, formatting line by line");
887                            let mut formatted = String::new();
888                            let mut line_count = 0;
889
890                            for line in trimmed.lines() {
891                                let line = line.trim();
892                                if line.is_empty() {
893                                    continue;
894                                }
895
896                                line_count += 1;
897                                if line_count > 100 {
898                                    formatted.push_str("\n# ... more JSON objects omitted ...");
899                                    break;
900                                }
901
902                                match serde_json::from_str::<serde_json::Value>(line) {
903                                    Ok(json_value) => {
904                                        if let Ok(pretty) =
905                                            serde_json::to_string_pretty(&json_value)
906                                        {
907                                            formatted.push_str(&pretty);
908                                        } else {
909                                            formatted.push_str(line);
910                                        }
911                                    }
912                                    Err(_) => {
913                                        formatted.push_str(line);
914                                    }
915                                }
916                                formatted.push_str("\n\n");
917                            }
918
919                            if !formatted.is_empty() {
920                                debug!("Formatted JSON stream: {} lines", line_count);
921                                formatted
922                            } else {
923                                format!("# <JSON content - invalid JSON>\n{trimmed}")
924                            }
925                        } else {
926                            format!("# <JSON content - invalid JSON>\n{trimmed}")
927                        }
928                    }
929                }
930            }
931
932            ContentCategory::JavaScript => {
933                let js_str = Self::bytes_to_utf8_string(body);
934                debug!(
935                    "Formatting JavaScript content, size: {} bytes",
936                    js_str.len()
937                );
938
939                let mut formatted = String::new();
940                let mut indent_level: usize = 0;
941                let mut in_string = false;
942                let mut prev_char = '\0';
943
944                for c in js_str.chars() {
945                    if (c == '"' || c == '\'') && prev_char != '\\' {
946                        in_string = !in_string;
947                    }
948
949                    if !in_string {
950                        if c == '{' || c == '[' {
951                            formatted.push(c);
952                            formatted.push('\n');
953                            indent_level += 1;
954                            for _ in 0..indent_level {
955                                formatted.push_str("  ");
956                            }
957                            continue;
958                        } else if c == '}' || c == ']' {
959                            formatted.push('\n');
960                            indent_level = indent_level.saturating_sub(1);
961                            for _ in 0..indent_level {
962                                formatted.push_str("  ");
963                            }
964                            formatted.push(c);
965                            continue;
966                        } else if c == ';' {
967                            formatted.push(c);
968                            formatted.push('\n');
969                            for _ in 0..indent_level {
970                                formatted.push_str("  ");
971                            }
972                            continue;
973                        }
974                    }
975
976                    formatted.push(c);
977                    prev_char = c;
978                }
979
980                if formatted.len() > js_str.len() * 2 || formatted.len() < js_str.len() / 2 {
981                    debug!("JavaScript formatting produced unusual result, using original");
982                    js_str
983                } else {
984                    debug!(
985                        "Successfully formatted JavaScript: {} bytes",
986                        formatted.len()
987                    );
988                    formatted
989                }
990            }
991
992            ContentCategory::Css => {
993                let css_str = Self::bytes_to_utf8_string(body);
994                format!("# <CSS content>\n{}", css_str.trim())
995            }
996
997            ContentCategory::Html => {
998                let html_str = Self::bytes_to_utf8_string(body);
999                format!(
1000                    "# <HTML content ({} bytes)>\n{}",
1001                    body.len(),
1002                    html_str.trim()
1003                )
1004            }
1005
1006            ContentCategory::Xml => {
1007                let xml_str = Self::bytes_to_utf8_string(body);
1008                format!("# <XML content>\n{}", xml_str.trim())
1009            }
1010
1011            ContentCategory::Svg => {
1012                let svg_str = Self::bytes_to_utf8_string(body);
1013                format!("# <SVG content>\n{}", svg_str.trim())
1014            }
1015
1016            ContentCategory::Text => {
1017                let text_str = Self::bytes_to_utf8_string(body);
1018
1019                let type_info = if let Some(ct) = content_type {
1020                    format!(" of type {ct}")
1021                } else {
1022                    "".to_string()
1023                };
1024
1025                if content_type.is_some()
1026                    && !content_type.unwrap().to_lowercase().starts_with("text/")
1027                {
1028                    format!("# <Unknown content{}>\n{}", type_info, text_str.trim())
1029                } else {
1030                    text_str.trim().to_string()
1031                }
1032            }
1033        };
1034
1035        Ok(result)
1036    }
1037
1038    pub fn is_content_too_large(content_length: usize) -> bool {
1039        content_length > 100 * 1024 * 1024
1040    }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045    use super::*;
1046
1047    #[test]
1048    fn test_request_parser() {
1049        let request =
1050            b"GET /api/users HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\nHello";
1051        let (method, path, version, headers) = RequestParser::parse(request).unwrap();
1052
1053        assert_eq!(method, Some("GET"));
1054        assert_eq!(path, Some("/api/users"));
1055        assert_eq!(version, Some(1));
1056        assert_eq!(headers.len(), 2);
1057        assert_eq!(headers[0].name, "Host");
1058        assert_eq!(
1059            std::str::from_utf8(headers[0].value).unwrap(),
1060            "example.com"
1061        );
1062    }
1063
1064    #[test]
1065    fn test_request_parser_partial() {
1066        let partial_request = b"GET /api/";
1067        let (method, path, version, headers) = RequestParser::parse(partial_request).unwrap();
1068
1069        assert_eq!(method, None);
1070        assert_eq!(path, None);
1071        assert_eq!(version, None);
1072        assert!(headers.is_empty());
1073    }
1074
1075    #[test]
1076    fn test_get_content_length() {
1077        let mut headers = [EMPTY_HEADER; 2];
1078        headers[0].name = "Content-Length";
1079        headers[0].value = b"1024";
1080        headers[1].name = "Host";
1081        headers[1].value = b"example.com";
1082
1083        let length = RequestParser::get_content_length(&headers);
1084        assert_eq!(length, 1024);
1085
1086        let empty_headers: [Header; 0] = [];
1087        assert_eq!(RequestParser::get_content_length(&empty_headers), 0);
1088
1089        headers[0].value = b"invalid";
1090        assert_eq!(RequestParser::get_content_length(&headers), 0);
1091    }
1092
1093    #[test]
1094    fn test_get_content_encoding() {
1095        let mut headers = [EMPTY_HEADER; 2];
1096        headers[0].name = "Content-Encoding";
1097        headers[0].value = b"gzip";
1098        headers[1].name = "Host";
1099        headers[1].value = b"example.com";
1100
1101        let encoding = RequestParser::get_content_encoding(&headers);
1102        assert_eq!(encoding, Some("gzip"));
1103
1104        headers[0].value = b"br";
1105        assert_eq!(RequestParser::get_content_encoding(&headers), Some("br"));
1106
1107        let empty_headers: [Header; 0] = [];
1108        assert_eq!(RequestParser::get_content_encoding(&empty_headers), None);
1109    }
1110
1111    #[test]
1112    fn test_is_gzip_encoded() {
1113        let mut headers = [EMPTY_HEADER; 1];
1114        headers[0].name = "Content-Encoding";
1115        headers[0].value = b"gzip";
1116
1117        assert!(RequestParser::is_gzip_encoded(&headers));
1118
1119        headers[0].value = b"br";
1120        assert!(!RequestParser::is_gzip_encoded(&headers));
1121
1122        let empty_headers: [Header; 0] = [];
1123        assert!(!RequestParser::is_gzip_encoded(&empty_headers));
1124    }
1125
1126    #[test]
1127    fn test_is_brotli_encoded() {
1128        let mut headers = [EMPTY_HEADER; 1];
1129        headers[0].name = "Content-Encoding";
1130        headers[0].value = b"br";
1131
1132        assert!(RequestParser::is_brotli_encoded(&headers));
1133
1134        headers[0].value = b"gzip";
1135        assert!(!RequestParser::is_brotli_encoded(&headers));
1136
1137        let empty_headers: [Header; 0] = [];
1138        assert!(!RequestParser::is_brotli_encoded(&empty_headers));
1139    }
1140
1141    #[test]
1142    fn test_is_chunked_transfer() {
1143        let mut headers = [EMPTY_HEADER; 1];
1144        headers[0].name = "Transfer-Encoding";
1145        headers[0].value = b"chunked";
1146
1147        assert!(RequestParser::is_chunked_transfer(&headers));
1148
1149        headers[0].value = b"compress";
1150        assert!(!RequestParser::is_chunked_transfer(&headers));
1151
1152        let empty_headers: [Header; 0] = [];
1153        assert!(!RequestParser::is_chunked_transfer(&empty_headers));
1154    }
1155
1156    #[test]
1157    fn test_extract_body() {
1158        let request = b"GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\nHello World";
1159        let body = RequestParser::extract_body(request);
1160        assert_eq!(body, Some(b"Hello World" as &[u8]));
1161
1162        let no_body = b"GET /api/users HTTP/1.1\r\nHost: example.com";
1163        assert_eq!(RequestParser::extract_body(no_body), None);
1164    }
1165
1166    #[test]
1167    fn test_process_chunked_body() {
1168        let chunked_body = b"7\r\nHello, \r\n5\r\nWorld\r\n0\r\n\r\n";
1169        let processed = RequestParser::process_chunked_body(chunked_body);
1170        assert_eq!(processed, b"Hello, World");
1171
1172        let empty_body = b"";
1173        let processed = RequestParser::process_chunked_body(empty_body);
1174        assert!(processed.is_empty());
1175
1176        let incomplete = b"7\r\nHello,";
1177        let processed = RequestParser::process_chunked_body(incomplete);
1178        assert!(processed.len() >= 6);
1179        assert!(processed.starts_with(b"Hello,"));
1180
1181        let invalid = b"XYZ\r\nHello";
1182        let processed = RequestParser::process_chunked_body(invalid);
1183        assert!(processed.len() >= 5);
1184        assert!(processed.ends_with(b"Hello"));
1185
1186        let complex = b"5\r\nHello\r\n5\r\nWorld\r\n0\r\n\r\nExtra";
1187        let processed = RequestParser::process_chunked_body(complex);
1188        assert_eq!(processed, b"HelloWorld");
1189    }
1190
1191    #[test]
1192    fn test_parse_chunk() {
1193        let mut chunks_found = 0;
1194
1195        let data = b"5\r\nHello\r\n";
1196        let result = RequestParser::parse_chunk(data, &mut chunks_found).unwrap();
1197        match result {
1198            ChunkParseResult::Complete {
1199                chunk_data,
1200                next_pos,
1201            } => {
1202                assert_eq!(chunk_data, b"Hello");
1203                assert_eq!(next_pos, data.len());
1204                assert_eq!(chunks_found, 1);
1205            }
1206            _ => panic!("Expected Complete result"),
1207        }
1208
1209        chunks_found = 0;
1210        let data = b"0\r\n\r\n";
1211        let result = RequestParser::parse_chunk(data, &mut chunks_found).unwrap();
1212        match result {
1213            ChunkParseResult::EndOfChunks { next_pos } => {
1214                assert_eq!(next_pos, 5);
1215                assert_eq!(chunks_found, 1);
1216            }
1217            _ => panic!("Expected EndOfChunks result"),
1218        }
1219
1220        chunks_found = 0;
1221        let data = b"5\r\nHel";
1222        let result = RequestParser::parse_chunk(data, &mut chunks_found).unwrap();
1223        match result {
1224            ChunkParseResult::Incomplete {
1225                chunk_data,
1226                consumed,
1227            } => {
1228                assert_eq!(chunk_data, Some(b"Hel" as &[u8]));
1229                assert_eq!(consumed, data.len());
1230                assert_eq!(chunks_found, 1);
1231            }
1232            _ => panic!("Expected Incomplete result"),
1233        }
1234
1235        chunks_found = 0;
1236        let data = b"\r\nignore";
1237        let result = RequestParser::parse_chunk(data, &mut chunks_found).unwrap();
1238        match result {
1239            ChunkParseResult::Skip { consumed } => {
1240                assert_eq!(consumed, 2);
1241                assert_eq!(chunks_found, 0);
1242            }
1243            _ => panic!("Expected Skip result"),
1244        }
1245
1246        chunks_found = 0;
1247        let data = b"A;extension=value\r\n1234567890\r\n";
1248        let result = RequestParser::parse_chunk(data, &mut chunks_found).unwrap();
1249        match result {
1250            ChunkParseResult::Complete {
1251                chunk_data,
1252                next_pos,
1253            } => {
1254                assert_eq!(chunk_data, b"1234567890");
1255                assert_eq!(next_pos, data.len());
1256                assert_eq!(chunks_found, 1);
1257            }
1258            _ => panic!("Expected Complete result"),
1259        }
1260    }
1261
1262    #[test]
1263    fn test_handle_chunked_edge_cases() {
1264        let body = b"";
1265        let result = Vec::new();
1266        let output = RequestParser::handle_chunked_edge_cases(body, result, 0, false);
1267        assert!(output.is_empty());
1268
1269        let body = b"Hello World";
1270        let result = Vec::new();
1271        let output = RequestParser::handle_chunked_edge_cases(body, result, 0, false);
1272        assert_eq!(output, body);
1273
1274        let body = b"5\r\nHello\r\n3\r\nWor";
1275        let mut result = Vec::new();
1276        result.extend_from_slice(b"Hello");
1277        let output = RequestParser::handle_chunked_edge_cases(body, result, 1, true);
1278        assert!(output.contains(&b'H'));
1279        assert!(output.contains(&b'e'));
1280        assert!(output.contains(&b'l'));
1281        assert!(output.contains(&b'o'));
1282        assert!(output.contains(&b'W'));
1283        assert!(output.contains(&b'o'));
1284        assert!(output.contains(&b'r'));
1285    }
1286
1287    #[test]
1288    fn test_response_parser() {
1289        let response =
1290            b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 2\r\n\r\n{}";
1291        let (status, headers) = ResponseParser::parse(response).unwrap();
1292
1293        assert_eq!(status, Some(200));
1294        assert_eq!(headers.len(), 2);
1295        assert_eq!(headers[0].name, "Content-Type");
1296        assert_eq!(
1297            std::str::from_utf8(headers[0].value).unwrap(),
1298            "application/json"
1299        );
1300    }
1301
1302    #[test]
1303    fn test_response_parser_partial() {
1304        let partial_response = b"HTTP/1.1 20";
1305        let (status, headers) = ResponseParser::parse(partial_response).unwrap();
1306
1307        assert_eq!(status, None);
1308        assert!(headers.is_empty());
1309    }
1310
1311    #[test]
1312    fn test_is_image() {
1313        let mut headers = [EMPTY_HEADER; 1];
1314        headers[0].name = "Content-Type";
1315        headers[0].value = b"image/jpeg";
1316
1317        assert!(BodyParser::is_image(&headers));
1318
1319        headers[0].value = b"text/plain";
1320        assert!(!BodyParser::is_image(&headers));
1321    }
1322
1323    #[test]
1324    fn test_get_content_type() {
1325        let mut headers = [EMPTY_HEADER; 1];
1326        headers[0].name = "Content-Type";
1327        headers[0].value = b"application/json";
1328
1329        let content_type = BodyParser::get_content_type(&headers);
1330        assert_eq!(content_type, Some("application/json"));
1331
1332        headers[0].value = b"text/html; charset=utf-8";
1333        let content_type = BodyParser::get_content_type(&headers);
1334        assert_eq!(content_type, Some("text/html; charset=utf-8"));
1335
1336        let empty_headers: [Header; 0] = [];
1337        assert_eq!(BodyParser::get_content_type(&empty_headers), None);
1338    }
1339
1340    #[tokio::test]
1341    async fn test_gzip_decompression() {
1342        let short_data = &[0x1F];
1343        let result = BodyParser::decompress_gzip(short_data).await.unwrap();
1344        assert_eq!(result, short_data);
1345
1346        let non_gzip_data = b"Hello World";
1347        let result = BodyParser::decompress_gzip(non_gzip_data).await.unwrap();
1348        assert_eq!(result, non_gzip_data);
1349
1350        let data_with_gzip_header = b"prefix\x1F\x8Bgzipped";
1351        let index = BodyParser::find_gzip_header(data_with_gzip_header);
1352        assert_eq!(index, Some(6));
1353    }
1354
1355    #[tokio::test]
1356    async fn test_multi_member_gzip_response_keeps_every_member() {
1357        let mut body = Vec::new();
1358        for line in ["first-member-line\n", "second-member-line\n"] {
1359            let mut gzip =
1360                flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
1361            std::io::Write::write_all(&mut gzip, line.as_bytes()).unwrap();
1362            body.extend(gzip.finish().unwrap());
1363        }
1364        let mut headers = [EMPTY_HEADER; 1];
1365        headers[0].name = "Content-Encoding";
1366        headers[0].value = b"gzip";
1367
1368        let result = BodyParser::process_response_body(&body, &headers)
1369            .await
1370            .unwrap();
1371
1372        assert_eq!(result, b"first-member-line\nsecond-member-line\n");
1373    }
1374
1375    #[tokio::test]
1376    async fn test_brotli_decompression() {
1377        let empty_data = &[];
1378        let result = BodyParser::decompress_brotli(empty_data).await.unwrap();
1379        assert_eq!(result, empty_data);
1380
1381        let short_data = &[0x01, 0x02, 0x03];
1382        let result = BodyParser::decompress_brotli(short_data).await.unwrap();
1383        assert_eq!(result, short_data);
1384
1385        let non_br_data = b"Hello World";
1386        let result = BodyParser::decompress_brotli(non_br_data).await.unwrap();
1387        assert_eq!(result, non_br_data);
1388    }
1389
1390    #[test]
1391    fn test_bytes_to_utf8_string() {
1392        let valid_utf8 = b"Hello World";
1393        let result = BodyParser::bytes_to_utf8_string(valid_utf8);
1394        assert_eq!(result, "Hello World");
1395
1396        let invalid_utf8 = &[0xFF, 0xFE, 0xFD];
1397        let result = BodyParser::bytes_to_utf8_string(invalid_utf8);
1398        assert!(!result.is_empty());
1399    }
1400
1401    #[test]
1402    fn test_identify_content_category() {
1403        // Empty body test
1404        assert_eq!(
1405            BodyParser::identify_content_category(None, b""),
1406            ContentCategory::Empty
1407        );
1408
1409        // Content-Type based tests
1410        assert_eq!(
1411            BodyParser::identify_content_category(Some("image/png"), b"data"),
1412            ContentCategory::Binary
1413        );
1414
1415        assert_eq!(
1416            BodyParser::identify_content_category(Some("application/json"), b"data"),
1417            ContentCategory::Json
1418        );
1419
1420        assert_eq!(
1421            BodyParser::identify_content_category(Some("text/javascript"), b"data"),
1422            ContentCategory::JavaScript
1423        );
1424
1425        assert_eq!(
1426            BodyParser::identify_content_category(Some("text/css"), b"data"),
1427            ContentCategory::Css
1428        );
1429
1430        assert_eq!(
1431            BodyParser::identify_content_category(Some("text/html"), b"data"),
1432            ContentCategory::Html
1433        );
1434
1435        assert_eq!(
1436            BodyParser::identify_content_category(Some("text/xml"), b"data"),
1437            ContentCategory::Xml
1438        );
1439
1440        assert_eq!(
1441            BodyParser::identify_content_category(Some("image/svg+xml"), b"data"),
1442            ContentCategory::Svg
1443        );
1444
1445        assert_eq!(
1446            BodyParser::identify_content_category(Some("text/plain"), b"data"),
1447            ContentCategory::Text
1448        );
1449
1450        assert_eq!(
1451            BodyParser::identify_content_category(Some("font/ttf"), b"data"),
1452            ContentCategory::Font
1453        );
1454
1455        // Content-based detection
1456        assert_eq!(
1457            BodyParser::identify_content_category(None, b"{\"key\":\"value\"}"),
1458            ContentCategory::Json
1459        );
1460
1461        assert_eq!(
1462            BodyParser::identify_content_category(None, b"<html><body>Hello</body></html>"),
1463            ContentCategory::Html
1464        );
1465
1466        assert_eq!(
1467            BodyParser::identify_content_category(None, b"<svg width=\"100\"></svg>"),
1468            ContentCategory::Svg
1469        );
1470
1471        // Binary detection based on magic numbers
1472        assert_eq!(
1473            BodyParser::identify_content_category(None, &[0xFF, 0xD8, 0xFF]),
1474            ContentCategory::Binary
1475        );
1476
1477        assert_eq!(
1478            BodyParser::identify_content_category(None, &[0x89, 0x50, 0x4E, 0x47]),
1479            ContentCategory::Binary
1480        );
1481
1482        assert_eq!(
1483            BodyParser::identify_content_category(None, &[0x47, 0x49, 0x46]),
1484            ContentCategory::Binary
1485        );
1486    }
1487
1488    #[tokio::test]
1489    async fn test_process_response_body() {
1490        let mut headers = [EMPTY_HEADER; 1];
1491        headers[0].name = "Transfer-Encoding";
1492        headers[0].value = b"chunked";
1493
1494        let chunked_body = b"5\r\nHello\r\n6\r\n World\r\n0\r\n\r\n";
1495        let result = BodyParser::process_response_body(chunked_body, &headers)
1496            .await
1497            .unwrap();
1498        assert_eq!(result, b"Hello World");
1499
1500        let headers: [Header; 0] = [];
1501        let body = b"Hello World";
1502        let result = BodyParser::process_response_body(body, &headers)
1503            .await
1504            .unwrap();
1505        assert_eq!(result, body);
1506    }
1507
1508    #[tokio::test]
1509    async fn test_format_body() {
1510        let json_body = br#"{"name":"test","value":123}"#;
1511        let formatted = BodyParser::format_body_async(json_body, &[]).await.unwrap();
1512        assert!(formatted.contains("{\n"));
1513
1514        let text_body = b"Hello world";
1515        let formatted = BodyParser::format_body_async(text_body, &[]).await.unwrap();
1516        assert_eq!(formatted, "Hello world");
1517
1518        let empty_body = b"";
1519        let formatted = BodyParser::format_body_async(empty_body, &[])
1520            .await
1521            .unwrap();
1522        assert_eq!(formatted, "# <empty body>");
1523
1524        let binary_data = &[0xFF, 0xD8, 0xFF, 0xE0];
1525        let formatted = BodyParser::format_body_async(binary_data, &[])
1526            .await
1527            .unwrap();
1528        assert!(
1529            formatted.contains("binary"),
1530            "Expected binary format message, got: {formatted}"
1531        );
1532
1533        let jpeg_binary_data = &[0xFF, 0xD8, 0xFF, 0xE0];
1534        let mut headers = [EMPTY_HEADER; 1];
1535        headers[0].name = "Content-Type";
1536        headers[0].value = b"image/jpeg";
1537
1538        let formatted = BodyParser::format_body_async(jpeg_binary_data, &headers)
1539            .await
1540            .unwrap();
1541        assert!(
1542            formatted.contains("image"),
1543            "Expected image format message, got: {formatted}"
1544        );
1545
1546        let css_body = b"body { color: red; }";
1547        let mut headers = [EMPTY_HEADER; 1];
1548        headers[0].name = "Content-Type";
1549        headers[0].value = b"text/css";
1550
1551        let formatted = BodyParser::format_body_async(css_body, &headers)
1552            .await
1553            .unwrap();
1554        assert!(formatted.contains("CSS content"));
1555
1556        let html_body = b"<html><body>Hello</body></html>";
1557        let mut headers = [EMPTY_HEADER; 1];
1558        headers[0].name = "Content-Type";
1559        headers[0].value = b"text/html";
1560
1561        let formatted = BodyParser::format_body_async(html_body, &headers)
1562            .await
1563            .unwrap();
1564        assert!(formatted.contains("HTML content"));
1565
1566        let xml_body = b"<?xml version=\"1.0\"?><root><item>test</item></root>";
1567        let mut headers = [EMPTY_HEADER; 1];
1568        headers[0].name = "Content-Type";
1569        headers[0].value = b"application/xml";
1570
1571        let formatted = BodyParser::format_body_async(xml_body, &headers)
1572            .await
1573            .unwrap();
1574        assert!(formatted.contains("XML content"));
1575
1576        let svg_body = b"<svg width=\"100\" height=\"100\"></svg>";
1577        let mut headers = [EMPTY_HEADER; 1];
1578        headers[0].name = "Content-Type";
1579        headers[0].value = b"image/svg+xml";
1580
1581        let formatted = BodyParser::format_body_async(svg_body, &headers)
1582            .await
1583            .unwrap();
1584        assert!(formatted.contains("SVG content"));
1585
1586        let js_body = b"function test() { return 1; }";
1587        let mut headers = [EMPTY_HEADER; 1];
1588        headers[0].name = "Content-Type";
1589        headers[0].value = b"application/javascript";
1590
1591        let formatted = BodyParser::format_body_async(js_body, &headers)
1592            .await
1593            .unwrap();
1594        assert!(formatted.contains("function"));
1595    }
1596
1597    #[test]
1598    fn test_is_content_too_large() {
1599        assert!(BodyParser::is_content_too_large(101 * 1024 * 1024));
1600
1601        assert!(!BodyParser::is_content_too_large(10 * 1024 * 1024));
1602    }
1603}