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/16529/head
Philippe Antoine 7 days ago committed by Victor Julien
parent 1f12296c1e
commit 94d512cffe

@ -25,32 +25,41 @@ use nom8::{Err, IResult, Parser};
use std; use std;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone)] fn mime_skip_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
pub struct HeaderTokens<'a> {
pub tokens: HashMap<&'a [u8], &'a [u8]>,
}
fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (input, _) = char('"').parse(input)?; let (input, _) = char('"').parse(input)?;
let mut escaping = false; let mut escaping = false;
for i in 0..input.len() { for i in 0..input.len() {
if input[i] == b'\\' { if escaping {
escaping = false;
} else if input[i] == b'\\' {
escaping = true; escaping = true;
} else { continue;
if input[i] == b'"' && !escaping { } else if input[i] == b'"' {
return Ok((&input[i + 1..], &input[..i])); return Ok((&input[i + 1..], &input[..i]));
} }
// unescape can be processed later }
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
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 escaping {
escaping = false; 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 return Err(Err::Error(make_error(input, ErrorKind::Eof)));
let (input, value) = take_until("\"").parse(input)?;
let (input, _) = char('"').parse(input)?;
return Ok((input, value));
} }
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)?; let (input, value) = alt((take_till(|ch: u8| ch == b';'), rest)).parse(input)?;
for i in 0..value.len() { for i in 0..value.len() {
if !is_mime_space(value[value.len() - i - 1]) { if !is_mime_space(value[value.len() - i - 1]) {
@ -60,77 +69,104 @@ fn mime_parse_value_until_semicolon(input: &[u8]) -> IResult<&[u8], &[u8]> {
return Ok((input, value)); 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] #[inline]
fn is_mime_space(ch: u8) -> bool { 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(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 // 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) = take_until("=").parse(input)?;
let (input, _) = char('=').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) = 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)?;
return Ok((input, (name, value))); return Ok((input, value));
} }
fn mime_parse_header_tokens(input: &[u8]) -> IResult<&[u8], HeaderTokens<'_>> { pub fn mime_find_header_token(header: &[u8], token: &[u8]) -> Option<Vec<u8>> {
let (mut input, _) = take_until_and_consume(b";").parse(input)?; let (mut input, _) = take_until_and_consume::<nom8::error::Error<&[u8]>>(b";")
let mut tokens = HashMap::new(); .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() { while !input.is_empty() {
match mime_parse_header_token(input) { match mime_parse_header_token_name(input) {
Ok((rem, t)) => { Ok((rem, name)) => {
tokens.insert(t.0, t.1); let prev = input;
// should never happen if name == token {
debug_validate_bug_on!(input.len() == rem.len()); if let Ok((_, value)) = mime_parse_header_token_value(rem) {
if input.len() == rem.len() { return Some(value);
//infinite loop } else {
return Err(Err::Error(make_error(input, ErrorKind::Eof))); return None;
} }
input = rem; } 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;
} }
Err(_) => { current_section_seen += 1;
// keep first tokens is error in remaining buffer 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; break;
} }
// should never happen
debug_validate_bug_on!(input.len() == prev.len());
if input.len() == prev.len() {
//infinite loop
return None;
} }
} }
return Ok((input, HeaderTokens { tokens })); Err(_) => {
} break;
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';
} }
} }
} }
if current_section_seen > 0 {
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 { loop {
match t.tokens.get(&current_section_slice[..]) { match sections.get(&current_section_slice[..]) {
Some(value) => { Some(value) => {
sections_values.extend_from_slice(value); sections_values.extend_from_slice(value);
current_section_seen += 1; current_section_seen += 1;
@ -143,10 +179,7 @@ pub fn mime_find_header_token<'a>(
} }
} }
} }
Err(_) => {
return None; return None;
}
}
} }
pub(crate) const RS_MIME_MAX_TOKEN_LEN: usize = 255; 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 let Ok((value, name)) = mime_parse_header_line(line) {
if slice_equals_lowercase(name, "content-disposition".as_bytes()) { 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()) {
if let Some(filename) =
mime_find_header_token(value, "filename".as_bytes(), &mut sections_values)
{
if !filename.is_empty() { if !filename.is_empty() {
ctx.filename = Vec::with_capacity(filename.len()); ctx.filename = filename;
fileopen = true; 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> { 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()) {
if let Some(value) = mime_find_header_token(i, "boundary".as_bytes(), &mut sections_values) {
if value.len() <= RS_MIME_MAX_TOKEN_LEN { if value.len() <= RS_MIME_MAX_TOKEN_LEN {
let mut r = MimeStateHTTP { let mut r = MimeStateHTTP {
boundary: Vec::with_capacity(2 + value.len()), 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 // start wih 2 additional hyphens
r.boundary.push(b'-'); r.boundary.push(b'-');
r.boundary.push(b'-'); r.boundary.push(b'-');
for c in value { r.boundary.extend_from_slice(&value);
// unescape
if *c != b'\\' {
r.boundary.push(*c);
}
}
return Some(r); return Some(r);
} }
} }
@ -491,78 +509,74 @@ mod test {
#[test] #[test]
fn test_mime_find_header_token() { fn test_mime_find_header_token() {
let mut outvec = Vec::new();
let undelimok = mime_find_header_token( let undelimok = mime_find_header_token(
"attachment; filename=test;".as_bytes(), "attachment; filename=test;".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(undelimok, Some("test".as_bytes())); assert_eq!(undelimok, b"test");
let delimok = mime_find_header_token( let delimok = mime_find_header_token(
"attachment; filename=\"test2\";".as_bytes(), "attachment; filename=\"test2\";".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(delimok, Some("test2".as_bytes())); assert_eq!(delimok, b"test2");
let escaped = mime_find_header_token( let escaped = mime_find_header_token(
"attachment; filename=\"test\\\"2\";".as_bytes(), "attachment; filename=\"test\\\"2\";".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(escaped, Some("test\\\"2".as_bytes())); assert_eq!(escaped, b"test\"2");
let evasion_othertoken = mime_find_header_token( let evasion_othertoken = mime_find_header_token(
"attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(), "attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(evasion_othertoken, Some("real".as_bytes())); assert_eq!(evasion_othertoken, b"real");
let evasion_suffixtoken = mime_find_header_token( let evasion_suffixtoken = mime_find_header_token(
"attachment; notafilename=wrong; filename=good;".as_bytes(), "attachment; notafilename=wrong; filename=good;".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(evasion_suffixtoken, Some("good".as_bytes())); assert_eq!(evasion_suffixtoken, b"good");
let badending = mime_find_header_token( let badending = mime_find_header_token(
"attachment; filename=oksofar; badending".as_bytes(), "attachment; filename=oksofar; badending".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(badending, Some("oksofar".as_bytes())); assert_eq!(badending, b"oksofar");
let missend = mime_find_header_token( let missend = mime_find_header_token(
"attachment; filename=test".as_bytes(), "attachment; filename=test".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(missend, Some("test".as_bytes())); assert_eq!(missend, b"test");
let spaces = mime_find_header_token( let spaces = mime_find_header_token(
"attachment; filename=test me wrong".as_bytes(), "attachment; filename=test me wrong".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(spaces, Some("test me wrong".as_bytes())); assert_eq!(spaces, b"test me wrong");
assert_eq!(outvec.len(), 0);
let multi = mime_find_header_token( let multi = mime_find_header_token(
"attachment; filename*0=abc; filename*1=\"def\";".as_bytes(), "attachment; filename*0=abc; filename*1=\"def\";".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(multi, Some("abcdef".as_bytes())); assert_eq!(multi, b"abcdef");
outvec.clear();
let multi = mime_find_header_token( let multi = mime_find_header_token(
"attachment; filename*1=456; filename*0=\"123\"".as_bytes(), "attachment; filename*1=456; filename*0=\"123\"".as_bytes(),
"filename".as_bytes(), "filename".as_bytes(),
&mut outvec, )
); .unwrap();
assert_eq!(multi, Some("123456".as_bytes())); assert_eq!(multi, b"123456");
outvec.clear();
} }
} }

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

Loading…
Cancel
Save