mime: handle RFC 2231 part 4

Ticket: 9157

Parameter Value Character Set and Language Information
describes how we can have a parameter such as
title*=us-ascii'en-us'This

Fixes both SMTP and HTTP1
pull/16529/head
Philippe Antoine 1 week ago committed by Victor Julien
parent 94d512cffe
commit 164b57effc

@ -79,11 +79,46 @@ fn is_mime_space(ch: u8) -> bool {
ch == 0x20 || ch == 0x09 || ch == 0x0a || ch == 0x0d ch == 0x20 || ch == 0x09 || ch == 0x0a || ch == 0x0d
} }
pub fn mime_parse_header_token_name(input: &[u8]) -> IResult<&[u8], &[u8]> { // arbitrary value to avoid quadratic complexity
const MIME_MAX_CHARSETLANG_LEN: usize = 64;
fn is_attribute_char(c: u8) -> bool {
match c {
0..0x20 => false,
0x80.. => false,
b'(' | b')' | b'<' | b'>' | b'@' | b',' | b';' | b':' | b'\\' | b'"' | b'/' | b'['
| b']' | b'?' | b'=' | b'*' | b'\'' | b'%' => false,
_ => true,
}
}
fn mime_parse_token_name(input: &[u8]) -> IResult<&[u8], (bool, &[u8])> {
// handle RFC2331 part 4 "Parameter Value Character Set and Language Information"
let mut star = false;
for i in 0..input.len() {
match input[i] {
b'=' => {
if star {
return Ok((&input[i + 1..], (star, &input[..i - 1])));
} else {
return Ok((&input[i + 1..], (star, &input[..i])));
}
}
b'*' => {
star = true;
}
_ => {
star = false;
}
}
}
Err(Err::Error(make_error(input, ErrorKind::Tag)))
}
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 // from RFC2047 : like ch.is_ascii_whitespace but without 0x0c FORM-FEED
let (input, _) = take_while(is_mime_space).parse(input)?; let (input, _) = take_while(is_mime_space).parse(input)?;
let (input, name) = take_until("=").parse(input)?; let (input, name) = mime_parse_token_name(input)?;
let (input, _) = char('=').parse(input)?;
return Ok((input, name)); return Ok((input, name));
} }
@ -95,11 +130,29 @@ pub fn mime_skip_header_token_value(input: &[u8]) -> IResult<&[u8], ()> {
return Ok((input, ())); return Ok((input, ()));
} }
pub fn mime_parse_header_token_value(input: &[u8]) -> IResult<&[u8], Vec<u8>> { pub fn mime_parse_header_token_value(input: &[u8], star: bool) -> IResult<&[u8], Vec<u8>> {
let (input, value) = let (input, value) =
alt((mime_parse_value_delimited, mime_parse_value_until_semicolon)).parse(input)?; alt((mime_parse_value_delimited, mime_parse_value_until_semicolon)).parse(input)?;
let (input, _) = take_while(is_mime_space).parse(input)?; let (input, _) = take_while(is_mime_space).parse(input)?;
let (input, _) = opt(complete(char(';'))).parse(input)?; let (input, _) = opt(complete(char(';'))).parse(input)?;
if star {
let mut single_quotes = false;
let mut start = 0;
for (j, v) in value.iter().enumerate() {
if *v == b'\'' {
if single_quotes {
start = j + 1;
break;
}
single_quotes = true;
} else if j > MIME_MAX_CHARSETLANG_LEN || !is_attribute_char(*v) {
break;
}
}
let mut decoded = Vec::new();
decoded.extend_from_slice(&value[start..]);
return Ok((input, decoded));
}
return Ok((input, value)); return Ok((input, value));
} }
@ -120,14 +173,14 @@ pub fn mime_find_header_token(header: &[u8], token: &[u8]) -> Option<Vec<u8>> {
match mime_parse_header_token_name(input) { match mime_parse_header_token_name(input) {
Ok((rem, name)) => { Ok((rem, name)) => {
let prev = input; let prev = input;
if name == token { if name.1 == token {
if let Ok((_, value)) = mime_parse_header_token_value(rem) { if let Ok((_, value)) = mime_parse_header_token_value(rem, name.0) {
return Some(value); return Some(value);
} else { } else {
return None; return None;
} }
} else if name == current_section_slice { } else if name.1 == current_section_slice {
if let Ok((rem2, value)) = mime_parse_header_token_value(rem) { if let Ok((rem2, value)) = mime_parse_header_token_value(rem, name.0) {
sections_values.extend_from_slice(&value); sections_values.extend_from_slice(&value);
input = rem2; input = rem2;
} else { } else {
@ -138,10 +191,10 @@ pub fn mime_find_header_token(header: &[u8], token: &[u8]) -> Option<Vec<u8>> {
current_section_slice.truncate(current_section_slice.len() - nbdigits); current_section_slice.truncate(current_section_slice.len() - nbdigits);
current_section_slice current_section_slice
.extend_from_slice(current_section_seen.to_string().as_bytes()); .extend_from_slice(current_section_seen.to_string().as_bytes());
} else if name.len() > token.len() && &name[..token.len()] == token { } else if name.1.len() > token.len() && &name.1[..token.len()] == token {
// prefix // prefix
if let Ok((rem2, value)) = mime_parse_header_token_value(rem) { if let Ok((rem2, value)) = mime_parse_header_token_value(rem, name.0) {
sections.insert(name, value); sections.insert(name.1, value);
input = rem2; input = rem2;
} else { } else {
return None; return None;
@ -578,5 +631,54 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(multi, b"123456"); assert_eq!(multi, b"123456");
let multi = mime_find_header_token(
"attachment; filename*=us-ascii'en-us'shell.php".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"shell.php");
let multi = mime_find_header_token(
"attachment; filename*='en-us'shell.php".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"shell.php");
let multi = mime_find_header_token(
"attachment; filename*=us-ascii''shell.php".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"shell.php");
let multi = mime_find_header_token(
"attachment; filename=first; filename*=us-ascii''second;".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"first");
let multi = mime_find_header_token(
"attachment; filename*=us-ascii''first; filename=second;".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"first");
let multi = mime_find_header_token(
"attachment; filename*0*=us-ascii'en-us'shell; filename*1=\".php\"".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(multi, b"shell.php");
let quote_after_lang = mime_find_header_token(
"attachment; name=\"f\"; x*=a''\"; filename=\"shell.php\"".as_bytes(),
"filename".as_bytes(),
)
.unwrap();
assert_eq!(quote_after_lang, b"shell.php");
} }
} }

Loading…
Cancel
Save