From: Philippe Antoine Date: Fri, 11 Apr 2025 19:56:19 +0000 (+0200) Subject: http1: brotli decompression X-Git-Tag: suricata-8.0.0-rc1~450 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a1ff7424e43ddcb1bf7d4387a4d9ffccbf5eb4c2;p=thirdparty%2Fsuricata.git http1: brotli decompression Ticket: 5692 http2 already used brotli crate for decompression --- diff --git a/rust/Cargo.lock.in b/rust/Cargo.lock.in index ad15091453..2cb1202ffe 100644 --- a/rust/Cargo.lock.in +++ b/rust/Cargo.lock.in @@ -1585,6 +1585,7 @@ name = "suricata-htp" version = "2.0.0" dependencies = [ "base64", + "brotli", "bstr", "cdylib-link-lines", "flate2", diff --git a/rust/htp/Cargo.toml b/rust/htp/Cargo.toml index 0b928eb340..46b4c77784 100644 --- a/rust/htp/Cargo.toml +++ b/rust/htp/Cargo.toml @@ -27,6 +27,7 @@ libc = "0.2" nom = "7.1.1" lzma-rs = { version = "0.2.0", features = ["stream"] } flate2 = { version = "~1.0.35", features = ["zlib-default"], default-features = false } +brotli = "~3.4.0" lazy_static = "1.4.0" time = "=0.3.36" diff --git a/rust/htp/src/decompressors.rs b/rust/htp/src/decompressors.rs index 6fe55ebce2..56ef36cfc2 100644 --- a/rust/htp/src/decompressors.rs +++ b/rust/htp/src/decompressors.rs @@ -1,3 +1,4 @@ +use brotli; use std::{ io::{Cursor, Write}, time::Instant, @@ -194,6 +195,8 @@ pub(crate) enum HtpContentEncoding { Zlib, /// LZMA compression. Lzma, + /// Brotli compression. + Brotli, } /// The outer decompressor tracks the number of callbacks and time spent @@ -240,6 +243,7 @@ impl Decompressor { HtpContentEncoding::Gzip | HtpContentEncoding::Deflate | HtpContentEncoding::Zlib + | HtpContentEncoding::Brotli | HtpContentEncoding::Lzma => Ok(Decompressor::new(Box::new(InnerDecompressor::new( encoding, self.inner, options, )?))), @@ -660,6 +664,35 @@ impl BufWriter for LzmaBufWriter { } } +/// Simple wrapper around an lzma implementation +struct BrotliBufWriter(brotli::DecompressorWriter>>); + +impl Write for BrotliBufWriter { + fn write(&mut self, data: &[u8]) -> std::io::Result { + self.0.write(data) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} + +impl BufWriter for BrotliBufWriter { + fn get_mut(&mut self) -> Option<&mut Cursor>> { + Some(self.0.get_mut()) + } + + fn finish(self: Box) -> std::io::Result>> { + self.0 + .into_inner() + .map_err(|_e| std::io::Error::new(std::io::ErrorKind::Other, "brotli")) + } + + fn try_finish(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + /// Structure that represents each decompressor in the chain. struct InnerDecompressor { /// Decoder implementation that will write to a temporary buffer. @@ -693,6 +726,13 @@ impl InnerDecompressor { Box::new(ZlibBufWriter(flate2::write::ZlibDecoder::new(buf))), false, )), + HtpContentEncoding::Brotli => Ok(( + Box::new(BrotliBufWriter(brotli::DecompressorWriter::new( + buf, + ENCODING_CHUNK_SIZE, + ))), + false, + )), HtpContentEncoding::Lzma => { if let Some(options) = options.lzma { Ok(( @@ -896,7 +936,9 @@ impl Decompress for InnerDecompressor { HtpContentEncoding::Gzip => HtpContentEncoding::Deflate, HtpContentEncoding::Deflate => HtpContentEncoding::Zlib, HtpContentEncoding::Zlib => HtpContentEncoding::Gzip, + // For other encodings, we retry with deflate, zlib and gzip HtpContentEncoding::Lzma => HtpContentEncoding::Deflate, + HtpContentEncoding::Brotli => HtpContentEncoding::Deflate, HtpContentEncoding::None => { return Err(std::io::Error::new( std::io::ErrorKind::Other, diff --git a/rust/htp/src/request.rs b/rust/htp/src/request.rs index 56fe02db11..0f518e77ac 100644 --- a/rust/htp/src/request.rs +++ b/rust/htp/src/request.rs @@ -1043,6 +1043,7 @@ impl ConnectionParser { HtpContentEncoding::Gzip | HtpContentEncoding::Deflate | HtpContentEncoding::Zlib + | HtpContentEncoding::Brotli | HtpContentEncoding::Lzma => { // Send data buffer to the decompressor if it exists if req.request_decompressor.is_none() && data.is_none() { @@ -1125,6 +1126,8 @@ impl ConnectionParser { HtpContentEncoding::Gzip } else if ce.cmp_nocase_nozero(b"deflate") || ce.cmp_nocase_nozero(b"x-deflate") { HtpContentEncoding::Deflate + } else if ce.cmp_nocase_nozero(b"br") { + HtpContentEncoding::Brotli } else if ce.cmp_nocase_nozero(b"lzma") { HtpContentEncoding::Lzma } else if ce.cmp_nocase_nozero(b"inflate") || ce.cmp_nocase_nozero(b"none") { @@ -1154,6 +1157,7 @@ impl ConnectionParser { HtpContentEncoding::Gzip | HtpContentEncoding::Deflate | HtpContentEncoding::Zlib + | HtpContentEncoding::Brotli | HtpContentEncoding::Lzma => { self.request_prepend_decompressor(request_content_encoding_processing)?; } diff --git a/rust/htp/src/response.rs b/rust/htp/src/response.rs index 820f878bcc..cf1ed409a5 100644 --- a/rust/htp/src/response.rs +++ b/rust/htp/src/response.rs @@ -1062,6 +1062,7 @@ impl ConnectionParser { HtpContentEncoding::Gzip | HtpContentEncoding::Deflate | HtpContentEncoding::Zlib + | HtpContentEncoding::Brotli | HtpContentEncoding::Lzma => { // Send data buffer to the decompressor if it exists if resp.response_decompressor.is_none() && data.is_none() { @@ -1141,6 +1142,8 @@ impl ConnectionParser { HtpContentEncoding::Deflate } else if ce.cmp_nocase_nozero(b"lzma") { HtpContentEncoding::Lzma + } else if ce.cmp_nocase_nozero(b"br") { + HtpContentEncoding::Brotli } else if ce.cmp_nocase_nozero(b"inflate") || ce.cmp_nocase_nozero(b"none") { HtpContentEncoding::None } else { @@ -1160,6 +1163,7 @@ impl ConnectionParser { HtpContentEncoding::Gzip | HtpContentEncoding::Deflate | HtpContentEncoding::Zlib + | HtpContentEncoding::Brotli | HtpContentEncoding::Lzma => { self.response_prepend_decompressor(response_content_encoding_processing)?; }