mime: rework mime_find_header_token

Make mime_find_header_token return a Vec directly as callers
turned it into a Vec.
This way, we do not need sections_values arg anymore.

Also do not build the complete hashmap of all tokens, when
we are just looking for one token. Instead, iterate the
token until we find the right name, and directly return its value.
This changes the behavior in case of repetitions as we are now
using the first token instead of the last one.

Fixes quoted unescaping as mime_parse_value_delimited does
the escaping itself the right way and returns a Vec

These API changes will be useful to support rfc2231 part 4 about
charset and language.

Ticket: 9157
pull/16501/head
Philippe Antoine 6 days ago
parent bea218e7b1
commit d280803e05

@ -25,32 +25,41 @@ use nom8::{Err, IResult, Parser};
use std;
use std::collections::HashMap;
#[derive(Clone)]
pub struct HeaderTokens<'a> {
pub tokens: HashMap<&'a [u8], &'a [u8]>,
fn mime_skip_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (input, _) = char('"').parse(input)?;
let mut escaping = false;
for i in 0..input.len() {
if escaping {
escaping = false;
} else if input[i] == b'\\' {
escaping = true;
continue;
} else if input[i] == b'"' {
return Ok((&input[i + 1..], &input[..i]));
}
}
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
let (input, _) = char('"').parse(input)?;
let mut escaping = false;
let mut r = Vec::new();
for i in 0..input.len() {
if input[i] == b'\\' {
escaping = true;
} else {
if input[i] == b'"' && !escaping {
return Ok((&input[i + 1..], &input[..i]));
}
// unescape can be processed later
if escaping {
escaping = false;
} else if input[i] == b'\\' {
escaping = true;
continue;
} else if input[i] == b'"' {
return Ok((&input[i + 1..], r));
}
r.push(input[i]);
}
// should fail
let (input, value) = take_until("\"").parse(input)?;
let (input, _) = char('"').parse(input)?;
return Ok((input, value));
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
fn mime_parse_value_until_semicolon(input: &[u8]) -> IResult<&[u8], &[u8]> {
fn mime_skip_value_until_semicolon(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (input, value) = alt((take_till(|ch: u8| ch == b';'), rest)).parse(input)?;
for i in 0..value.len() {
if !is_mime_space(value[value.len() - i - 1]) {
@ -60,93 +69,117 @@ fn mime_parse_value_until_semicolon(input: &[u8]) -> IResult<&[u8], &[u8]> {
return Ok((input, value));
}
fn mime_parse_value_until_semicolon(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
let (input, value) = mime_skip_value_until_semicolon(input)?;
return Ok((input, value.to_vec()));
}
#[inline]
fn is_mime_space(ch: u8) -> bool {
ch == 0x20 || ch == 0x09 || ch == 0x0a || ch == 0x0d
}
pub fn mime_parse_header_token(input: &[u8]) -> IResult<&[u8], (&'_ [u8], &'_ [u8])> {
pub fn mime_parse_header_token_name(input: &[u8]) -> IResult<&[u8], &[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) = take_until("=").parse(input)?;
let (input, _) = char('=').parse(input)?;
return Ok((input, name));
}
pub fn mime_skip_header_token_value(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) =
alt((mime_skip_value_delimited, mime_skip_value_until_semicolon)).parse(input)?;
let (input, _) = take_while(is_mime_space).parse(input)?;
let (input, _) = opt(complete(char(';'))).parse(input)?;
return Ok((input, ()));
}
pub fn mime_parse_header_token_value(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
let (input, value) =
alt((mime_parse_value_delimited, mime_parse_value_until_semicolon)).parse(input)?;
let (input, _) = take_while(is_mime_space).parse(input)?;
let (input, _) = opt(complete(char(';'))).parse(input)?;
return Ok((input, (name, value)));
return Ok((input, value));
}
fn mime_parse_header_tokens(input: &[u8]) -> IResult<&[u8], HeaderTokens<'_>> {
let (mut input, _) = take_until_and_consume(b";").parse(input)?;
let mut tokens = HashMap::new();
pub fn mime_find_header_token(header: &[u8], token: &[u8]) -> Option<Vec<u8>> {
let (mut input, _) = take_until_and_consume::<nom8::error::Error<&[u8]>>(b";")
.parse(header)
.ok()?;
// in case of multiple sections for the parameter cf RFC2231
let mut current_section_slice = Vec::with_capacity(token.len() + 2);
current_section_slice.extend_from_slice(token);
current_section_slice.extend_from_slice(b"*0");
let mut sections_values = Vec::new();
let mut current_section_seen = 0;
let mut sections = HashMap::new();
while !input.is_empty() {
match mime_parse_header_token(input) {
Ok((rem, t)) => {
tokens.insert(t.0, t.1);
match mime_parse_header_token_name(input) {
Ok((rem, name)) => {
let prev = input;
if name == token {
if let Ok((_, value)) = mime_parse_header_token_value(rem) {
return Some(value);
} else {
return None;
}
} else if name == current_section_slice {
if let Ok((rem2, value)) = mime_parse_header_token_value(rem) {
sections_values.extend_from_slice(&value);
input = rem2;
} else {
return None;
}
current_section_seen += 1;
let nbdigits = current_section_slice.len() - token.len() - 1;
current_section_slice.truncate(current_section_slice.len() - nbdigits);
current_section_slice
.extend_from_slice(current_section_seen.to_string().as_bytes());
} else if name.len() > token.len() && &name[..token.len()] == token {
// prefix
if let Ok((rem2, value)) = mime_parse_header_token_value(rem) {
sections.insert(name, value);
input = rem2;
} else {
return None;
}
} else if let Ok((rem2, _)) = mime_skip_header_token_value(rem) {
input = rem2;
} else {
break;
}
// should never happen
debug_validate_bug_on!(input.len() == rem.len());
if input.len() == rem.len() {
debug_validate_bug_on!(input.len() == prev.len());
if input.len() == prev.len() {
//infinite loop
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
return None;
}
input = rem;
}
Err(_) => {
// keep first tokens is error in remaining buffer
break;
}
}
}
return Ok((input, HeaderTokens { tokens }));
}
pub fn mime_find_header_token<'a>(
header: &'a [u8], token: &[u8], sections_values: &'a mut Vec<u8>,
) -> Option<&'a [u8]> {
match mime_parse_header_tokens(header) {
Ok((_rem, t)) => {
// in case of multiple sections for the parameter cf RFC2231
let mut current_section_slice = Vec::new();
// look for the specific token
match t.tokens.get(token) {
// easy nominal case
Some(value) => return Some(value),
None => {
// check for initial section of a parameter
current_section_slice.extend_from_slice(token);
current_section_slice.extend_from_slice(b"*0");
{
let value = t.tokens.get(&current_section_slice[..])?;
sections_values.extend_from_slice(value);
let l = current_section_slice.len();
current_section_slice[l - 1] = b'1';
}
}
}
let mut current_section_seen = 1;
// we have at least the initial section
// try looping until we do not find anymore a next section
loop {
match t.tokens.get(&current_section_slice[..]) {
Some(value) => {
sections_values.extend_from_slice(value);
current_section_seen += 1;
let nbdigits = current_section_slice.len() - token.len() - 1;
current_section_slice.truncate(current_section_slice.len() - nbdigits);
current_section_slice
.extend_from_slice(current_section_seen.to_string().as_bytes());
}
None => return Some(sections_values),
if current_section_seen > 0 {
loop {
match sections.get(&current_section_slice[..]) {
Some(value) => {
sections_values.extend_from_slice(value);
current_section_seen += 1;
let nbdigits = current_section_slice.len() - token.len() - 1;
current_section_slice.truncate(current_section_slice.len() - nbdigits);
current_section_slice
.extend_from_slice(current_section_seen.to_string().as_bytes());
}
None => return Some(sections_values),
}
}
Err(_) => {
return None;
}
}
return None;
}
pub(crate) const RS_MIME_MAX_TOKEN_LEN: usize = 255;
@ -257,19 +290,10 @@ fn mime_parse_headers<'a>(
{
if let Ok((value, name)) = mime_parse_header_line(line) {
if slice_equals_lowercase(name, "content-disposition".as_bytes()) {
let mut sections_values = Vec::new();
if let Some(filename) =
mime_find_header_token(value, "filename".as_bytes(), &mut sections_values)
{
if let Some(filename) = mime_find_header_token(value, "filename".as_bytes()) {
if !filename.is_empty() {
ctx.filename = Vec::with_capacity(filename.len());
ctx.filename = filename;
fileopen = true;
for c in filename {
// unescape
if *c != b'\\' {
ctx.filename.push(*c);
}
}
}
}
}
@ -418,8 +442,7 @@ fn mime_process(ctx: &mut MimeStateHTTP, i: &[u8]) -> (MimeParserResult, u32, u3
}
pub fn mime_state_init(i: &[u8]) -> Option<MimeStateHTTP> {
let mut sections_values = Vec::new();
if let Some(value) = mime_find_header_token(i, "boundary".as_bytes(), &mut sections_values) {
if let Some(value) = mime_find_header_token(i, "boundary".as_bytes()) {
if value.len() <= RS_MIME_MAX_TOKEN_LEN {
let mut r = MimeStateHTTP {
boundary: Vec::with_capacity(2 + value.len()),
@ -428,12 +451,7 @@ pub fn mime_state_init(i: &[u8]) -> Option<MimeStateHTTP> {
// start wih 2 additional hyphens
r.boundary.push(b'-');
r.boundary.push(b'-');
for c in value {
// unescape
if *c != b'\\' {
r.boundary.push(*c);
}
}
r.boundary.extend_from_slice(&value);
return Some(r);
}
}
@ -491,78 +509,74 @@ mod test {
#[test]
fn test_mime_find_header_token() {
let mut outvec = Vec::new();
let undelimok = mime_find_header_token(
"attachment; filename=test;".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(undelimok, Some("test".as_bytes()));
)
.unwrap();
assert_eq!(undelimok, b"test");
let delimok = mime_find_header_token(
"attachment; filename=\"test2\";".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(delimok, Some("test2".as_bytes()));
)
.unwrap();
assert_eq!(delimok, b"test2");
let escaped = mime_find_header_token(
"attachment; filename=\"test\\\"2\";".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(escaped, Some("test\\\"2".as_bytes()));
)
.unwrap();
assert_eq!(escaped, b"test\"2");
let evasion_othertoken = mime_find_header_token(
"attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(evasion_othertoken, Some("real".as_bytes()));
)
.unwrap();
assert_eq!(evasion_othertoken, b"real");
let evasion_suffixtoken = mime_find_header_token(
"attachment; notafilename=wrong; filename=good;".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(evasion_suffixtoken, Some("good".as_bytes()));
)
.unwrap();
assert_eq!(evasion_suffixtoken, b"good");
let badending = mime_find_header_token(
"attachment; filename=oksofar; badending".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(badending, Some("oksofar".as_bytes()));
)
.unwrap();
assert_eq!(badending, b"oksofar");
let missend = mime_find_header_token(
"attachment; filename=test".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(missend, Some("test".as_bytes()));
)
.unwrap();
assert_eq!(missend, b"test");
let spaces = mime_find_header_token(
"attachment; filename=test me wrong".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(spaces, Some("test me wrong".as_bytes()));
)
.unwrap();
assert_eq!(spaces, b"test me wrong");
assert_eq!(outvec.len(), 0);
let multi = mime_find_header_token(
"attachment; filename*0=abc; filename*1=\"def\";".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(multi, Some("abcdef".as_bytes()));
outvec.clear();
)
.unwrap();
assert_eq!(multi, b"abcdef");
let multi = mime_find_header_token(
"attachment; filename*1=456; filename*0=\"123\"".as_bytes(),
"filename".as_bytes(),
&mut outvec,
);
assert_eq!(multi, Some("123456".as_bytes()));
outvec.clear();
)
.unwrap();
assert_eq!(multi, b"123456");
}
}

@ -184,26 +184,19 @@ pub const MIME_ANOM_LONG_BOUNDARY: u32 = 0x80;
pub const MIME_ANOM_LONG_FILENAME: u32 = 0x100;
fn mime_smtp_process_headers(ctx: &mut MimeStateSMTP) -> (u32, bool) {
let mut sections_values = Vec::new();
let mut warnings = 0;
let mut encap = false;
for h in &ctx.headers[ctx.main_headers_nb..] {
if mime::slice_equals_lowercase(&h.name, b"content-disposition") {
if ctx.filename.is_empty() {
if let Some(value) =
mime::mime_find_header_token(&h.value, b"filename", &mut sections_values)
{
let value = if value.len() > mime::RS_MIME_MAX_TOKEN_LEN {
if let Some(mut value) = mime::mime_find_header_token(&h.value, b"filename") {
if value.len() > mime::RS_MIME_MAX_TOKEN_LEN {
warnings |= MIME_ANOM_LONG_FILENAME;
&value[..mime::RS_MIME_MAX_TOKEN_LEN]
} else {
value
};
ctx.filename.extend_from_slice(value);
let mut newname = Vec::new();
newname.extend_from_slice(value);
ctx.attachments.push(newname);
sections_values.clear();
value.truncate(mime::RS_MIME_MAX_TOKEN_LEN);
value.shrink_to_fit();
}
ctx.filename.extend_from_slice(&value);
ctx.attachments.push(value);
}
}
} else if mime::slice_equals_lowercase(&h.name, b"content-transfer-encoding") {
@ -215,35 +208,26 @@ fn mime_smtp_process_headers(ctx: &mut MimeStateSMTP) -> (u32, bool) {
}
} else if mime::slice_equals_lowercase(&h.name, b"content-type") {
if ctx.filename.is_empty() {
if let Some(value) =
mime::mime_find_header_token(&h.value, b"name", &mut sections_values)
{
let value = if value.len() > mime::RS_MIME_MAX_TOKEN_LEN {
if let Some(mut value) = mime::mime_find_header_token(&h.value, b"name") {
if value.len() > mime::RS_MIME_MAX_TOKEN_LEN {
warnings |= MIME_ANOM_LONG_FILENAME;
&value[..mime::RS_MIME_MAX_TOKEN_LEN]
} else {
value
};
ctx.filename.extend_from_slice(value);
let mut newname = Vec::new();
newname.extend_from_slice(value);
ctx.attachments.push(newname);
sections_values.clear();
value.truncate(mime::RS_MIME_MAX_TOKEN_LEN);
value.shrink_to_fit();
}
ctx.filename.extend_from_slice(&value);
ctx.attachments.push(value);
}
}
if let Some(value) =
mime::mime_find_header_token(&h.value, b"boundary", &mut sections_values)
{
if let Some(value) = mime::mime_find_header_token(&h.value, b"boundary") {
// start wih 2 additional hyphens
let mut boundary = Vec::new();
boundary.push(b'-');
boundary.push(b'-');
boundary.extend_from_slice(value);
boundary.extend_from_slice(&value);
ctx.boundaries.push(boundary);
if value.len() > MAX_BOUNDARY_LEN {
warnings |= MIME_ANOM_LONG_BOUNDARY;
}
sections_values.clear();
}
let ct = if let Some(x) = h.value.iter().position(|&x| x == b';') {
&h.value[..x]

Loading…
Cancel
Save