From 26605b7a66afbddd92647b7cd9e26c6c0a795a52 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Thu, 8 Oct 2026 11:09:34 +0200 Subject: [PATCH] mime: url decode value with charset and lang Ticket: 9157 --- rust/src/detect/transforms/urldecode.rs | 11 ++++++----- rust/src/mime/mime.rs | 25 +++++++++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/rust/src/detect/transforms/urldecode.rs b/rust/src/detect/transforms/urldecode.rs index e693080d72..da89e62223 100644 --- a/rust/src/detect/transforms/urldecode.rs +++ b/rust/src/detect/transforms/urldecode.rs @@ -41,7 +41,8 @@ fn hex_value(i: u8) -> Option { _ => None, } } -fn url_decode_transform_do(input: &[u8], output: &mut [u8]) -> u32 { + +pub(crate) fn url_decode_transform_do(input: &[u8], output: &mut [u8], plus_space: bool) -> u32 { let mut state = (0u8, 0u8); let mut nb = 0; for &i in input.iter() { @@ -68,7 +69,7 @@ fn url_decode_transform_do(input: &[u8], output: &mut [u8]) -> u32 { } else if i == b'%' { state = (1u8, 0u8); } else { - if i == b'+' { + if i == b'+' && plus_space { output[nb] = b' '; } else { output[nb] = i; @@ -104,7 +105,7 @@ unsafe extern "C" fn url_decode_transform( } let output = std::slice::from_raw_parts_mut(output, input_len as usize); - let out_len = url_decode_transform_do(input, output); + let out_len = url_decode_transform_do(input, output, true); SCInspectionBufferTruncate(buffer, out_len); } @@ -138,11 +139,11 @@ mod tests { let mut buf = Vec::new(); buf.extend_from_slice(b"Suricata%20is+%27%61wesome%21%27%25%30%30%ZZ%4"); let mut out = vec![0; buf.len()]; - let nb = url_decode_transform_do(&buf, &mut out); + let nb = url_decode_transform_do(&buf, &mut out, true); assert_eq!(&out[..nb as usize], b"Suricata is 'awesome!'%00%ZZ%4"); // test in place let still_buf = unsafe { std::slice::from_raw_parts(buf.as_ptr(), buf.len()) }; - let nb = url_decode_transform_do(still_buf, &mut buf); + let nb = url_decode_transform_do(still_buf, &mut buf, true); assert_eq!(&still_buf[..nb as usize], b"Suricata is 'awesome!'%00%ZZ%4"); } } diff --git a/rust/src/mime/mime.rs b/rust/src/mime/mime.rs index 733d46768f..bd65bf3dbd 100644 --- a/rust/src/mime/mime.rs +++ b/rust/src/mime/mime.rs @@ -16,6 +16,7 @@ */ use crate::common::nom8::take_until_and_consume; +use crate::detect::transforms::urldecode::url_decode_transform_do; use nom8::branch::alt; use nom8::bytes::complete::{tag, take, take_till, take_until, take_while}; use nom8::character::complete::char; @@ -118,8 +119,8 @@ fn mime_parse_token_name(input: &[u8]) -> IResult<&[u8], (bool, &[u8])> { pub fn mime_parse_header_token_name(input: &[u8]) -> IResult<&[u8], (bool, &[u8])> { // from RFC2047 : like ch.is_ascii_whitespace but without 0x0c FORM-FEED let (input, _) = take_while(is_mime_space).parse(input)?; - let (input, name) = mime_parse_token_name(input)?; - return Ok((input, name)); + let (input, (star, name)) = mime_parse_token_name(input)?; + return Ok((input, (star, name))); } pub fn mime_skip_header_token_value(input: &[u8]) -> IResult<&[u8], ()> { @@ -149,8 +150,9 @@ pub fn mime_parse_header_token_value(input: &[u8], star: bool) -> IResult<&[u8], break; } } - let mut decoded = Vec::new(); - decoded.extend_from_slice(&value[start..]); + let mut decoded = vec![0; value.len() - start]; + let out_len = url_decode_transform_do(&value[start..], &mut decoded, false); + decoded.truncate(out_len as usize); return Ok((input, decoded)); } return Ok((input, value)); @@ -680,5 +682,20 @@ mod test { ) .unwrap(); assert_eq!(quote_after_lang, b"shell.php"); + + let encoded = mime_find_header_token( + "attachment; filename*=us-ascii'en-us'she%6c%6C.php".as_bytes(), + "filename".as_bytes(), + ) + .unwrap(); + assert_eq!(encoded, b"shell.php"); + + let multi_encoded = mime_find_header_token( + "attachment; filename*0*=us-ascii'en-us'sh%65; filename*1*=%6c%6C;filename*2=\".php\"" + .as_bytes(), + "filename".as_bytes(), + ) + .unwrap(); + assert_eq!(multi_encoded, b"shell.php"); } }