http1: brotli decompression

Ticket: 5692

http2 already used brotli crate for decompression
This commit is contained in:
Philippe Antoine 2025-04-11 21:56:19 +02:00 committed by Victor Julien
parent 128ee9ba46
commit a1ff7424e4
5 changed files with 52 additions and 0 deletions

View file

@ -1585,6 +1585,7 @@ name = "suricata-htp"
version = "2.0.0"
dependencies = [
"base64",
"brotli",
"bstr",
"cdylib-link-lines",
"flate2",

View file

@ -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"

View file

@ -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<Cursor<Box<[u8]>>>);
impl Write for BrotliBufWriter {
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
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<Box<[u8]>>> {
Some(self.0.get_mut())
}
fn finish(self: Box<Self>) -> std::io::Result<Cursor<Box<[u8]>>> {
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,

View file

@ -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)?;
}

View file

@ -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)?;
}