|
|
|
|
@ -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;
|
|
|
|
|
@ -25,32 +26,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 +70,171 @@ 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])> {
|
|
|
|
|
// 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
|
|
|
|
|
let (input, _) = take_while(is_mime_space).parse(input)?;
|
|
|
|
|
let (input, name) = take_until("=").parse(input)?;
|
|
|
|
|
let (input, _) = char('=').parse(input)?;
|
|
|
|
|
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], ()> {
|
|
|
|
|
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], star: bool) -> 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)));
|
|
|
|
|
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![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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.1 == token {
|
|
|
|
|
if let Ok((_, value)) = mime_parse_header_token_value(rem, name.0) {
|
|
|
|
|
return Some(value);
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
} else if name.1 == current_section_slice {
|
|
|
|
|
if let Ok((rem2, value)) = mime_parse_header_token_value(rem, name.0) {
|
|
|
|
|
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.1.len() > token.len() && &name.1[..token.len()] == token {
|
|
|
|
|
// prefix
|
|
|
|
|
if let Ok((rem2, value)) = mime_parse_header_token_value(rem, name.0) {
|
|
|
|
|
sections.insert(name.1, 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(¤t_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 {
|
|
|
|
|
loop {
|
|
|
|
|
match sections.get(¤t_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),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(¤t_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 +345,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 +497,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 +506,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 +564,138 @@ 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");
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|