2017-09-07 23:00:08 +03:00
/*
* meli - parser module
*
2020-06-06 19:38:20 +03:00
* Copyright 2017 - 2020 Manos Pitsidianakis
2017-09-07 23:00:08 +03:00
*
* This file is part of meli .
*
* meli is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* meli is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with meli . If not , see < http ://www.gnu.org/licenses/>.
* /
2018-08-17 13:21:24 +03:00
2020-06-06 19:38:20 +03:00
use crate ::error ::{ MeliError , Result , ResultIntoMeliError } ;
use nom ::{
branch ::alt ,
bytes ::complete ::{ is_a , is_not , tag , take_until , take_while } ,
character ::is_hex_digit ,
combinator ::peek ,
2020-09-03 12:09:29 +03:00
combinator ::{ map , opt } ,
error ::{ context , ErrorKind } ,
2020-09-03 14:44:03 +03:00
multi ::{ many0 , many1 , separated_nonempty_list } ,
2020-06-06 19:38:20 +03:00
number ::complete ::le_u8 ,
2020-08-09 09:50:20 +03:00
sequence ::{ delimited , pair , preceded , separated_pair , terminated } ,
2020-06-06 19:38:20 +03:00
} ;
2020-08-26 00:22:16 +03:00
use smallvec ::SmallVec ;
2020-06-20 11:10:27 +03:00
use std ::borrow ::Cow ;
2020-09-03 12:09:29 +03:00
macro_rules ! to_str {
( $l :expr ) = > { {
unsafe { std ::str ::from_utf8_unchecked ( $l ) }
} } ;
}
#[ derive(Eq, PartialEq) ]
2020-06-20 11:10:27 +03:00
pub struct ParsingError < I > {
2020-09-11 00:08:56 +03:00
pub input : I ,
pub error : Cow < 'static , str > ,
2020-06-20 11:10:27 +03:00
}
2020-09-03 12:09:29 +03:00
impl core ::fmt ::Debug for ParsingError < & '_ [ u8 ] > {
fn fmt ( & self , fmt : & mut core ::fmt ::Formatter ) -> core ::fmt ::Result {
fmt . debug_struct ( " ParsingError " )
. field ( " input " , & to_str! ( & self . input ) )
. field ( " error " , & self . error )
. finish ( )
}
}
struct DebugOkWrapper < ' r , I , R : AsRef < [ u8 ] > > ( & ' r IResult < I , R > ) ;
impl < R : AsRef < [ u8 ] > + core ::fmt ::Debug > core ::fmt ::Debug for DebugOkWrapper < '_ , & '_ [ u8 ] , R > {
fn fmt ( & self , fmt : & mut core ::fmt ::Formatter ) -> core ::fmt ::Result {
if let Ok ( ( a , b ) ) = self . 0 {
write! ( fmt , " Ok({}, {}) " , & to_str! ( a ) , & to_str! ( b . as_ref ( ) ) )
} else {
write! ( fmt , " {:?} " , self . 0 )
}
}
}
2020-06-20 11:10:27 +03:00
pub type IResult < I , O , E = ParsingError < I > > = std ::result ::Result < ( I , O ) , nom ::Err < E > > ;
impl < ' i > ParsingError < & ' i str > {
pub fn as_bytes ( self ) -> ParsingError < & ' i [ u8 ] > {
ParsingError {
input : self . input . as_bytes ( ) ,
error : self . error ,
}
}
}
2020-09-03 12:09:29 +03:00
impl < I > From < ( I , & 'static str ) > for ParsingError < I > {
fn from ( ( input , error ) : ( I , & 'static str ) ) -> Self {
2020-06-20 11:10:27 +03:00
Self {
input ,
error : error . into ( ) ,
}
}
}
2020-09-03 12:09:29 +03:00
impl < I > From < ( I , String ) > for ParsingError < I > {
fn from ( ( input , error ) : ( I , String ) ) -> Self {
2020-06-25 11:47:52 +03:00
Self {
input ,
error : error . into ( ) ,
}
}
}
2020-06-20 11:10:27 +03:00
impl < I > nom ::error ::ParseError < I > for ParsingError < I > {
fn from_error_kind ( input : I , kind : ErrorKind ) -> Self {
Self {
input ,
error : kind . description ( ) . to_string ( ) . into ( ) ,
}
}
fn append ( input : I , kind : ErrorKind , other : Self ) -> Self {
Self {
input ,
error : format ! ( " {}, {} " , kind . description ( ) , other . error ) . into ( ) ,
}
}
}
impl < ' i > From < ParsingError < & ' i [ u8 ] > > for MeliError {
fn from ( val : ParsingError < & ' i [ u8 ] > ) -> MeliError {
MeliError ::new ( " Parsing error " ) . set_summary ( format! (
r #" In input: " { } .. . " ,
Error : { } " #,
String ::from_utf8_lossy ( val . input )
. chars ( )
. take ( 30 )
. collect ::< String > ( ) ,
val . error
) )
}
}
impl < ' i > From < ParsingError < & ' i str > > for MeliError {
fn from ( val : ParsingError < & ' i str > ) -> MeliError {
MeliError ::new ( " Parsing error " ) . set_summary ( format! (
r #" In input: " { } .. . " ,
Error : { } " #,
val . input . chars ( ) . take ( 30 ) . collect ::< String > ( ) ,
val . error
) )
}
}
impl < ' i > From < nom ::Err < ParsingError < & ' i [ u8 ] > > > for MeliError {
fn from ( val : nom ::Err < ParsingError < & ' i [ u8 ] > > ) -> MeliError {
match val {
nom ::Err ::Incomplete ( _ ) = > MeliError ::new ( " Parsing Error: Incomplete " ) ,
nom ::Err ::Error ( err ) | nom ::Err ::Failure ( err ) = > err . into ( ) ,
}
}
}
impl < ' i > From < nom ::Err < ParsingError < & ' i str > > > for MeliError {
fn from ( val : nom ::Err < ParsingError < & ' i str > > ) -> MeliError {
match val {
nom ::Err ::Incomplete ( _ ) = > MeliError ::new ( " Parsing Error: Incomplete " ) ,
nom ::Err ::Error ( err ) | nom ::Err ::Failure ( err ) = > err . into ( ) ,
}
}
}
2018-07-27 18:01:52 +03:00
2019-09-25 22:00:30 +03:00
macro_rules ! is_ctl_or_space {
( $var :ident ) = > {
/* <any ASCII control character and DEL> */
$var < 33 | | $var = = 127
} ;
( $var :expr ) = > {
/* <any ASCII control character and DEL> */
$var < 33 | | $var = = 127
} ;
}
2018-08-05 12:14:26 +03:00
macro_rules ! is_whitespace {
( $var :ident ) = > {
2018-08-15 00:48:31 +03:00
$var = = b ' ' | | $var = = b '\t' | | $var = = b '\n' | | $var = = b '\r'
2018-08-05 12:14:26 +03:00
} ;
( $var :expr ) = > {
2018-08-15 00:48:31 +03:00
$var = = b ' ' | | $var = = b '\t' | | $var = = b '\n' | | $var = = b '\r'
2018-08-05 12:14:26 +03:00
} ;
}
2018-08-04 20:40:20 +03:00
pub trait BytesExt {
2018-08-14 19:19:01 +03:00
fn rtrim ( & self ) -> & Self ;
fn ltrim ( & self ) -> & Self ;
2018-08-04 20:40:20 +03:00
fn trim ( & self ) -> & Self ;
fn find ( & self , needle : & [ u8 ] ) -> Option < usize > ;
2019-11-18 13:00:43 +02:00
fn rfind ( & self , needle : & [ u8 ] ) -> Option < usize > ;
2018-08-04 20:40:20 +03:00
fn replace ( & self , from : & [ u8 ] , to : & [ u8 ] ) -> Vec < u8 > ;
2019-11-28 22:15:32 +02:00
fn is_quoted ( & self ) -> bool ;
2018-08-04 20:40:20 +03:00
}
impl BytesExt for [ u8 ] {
2018-08-14 19:19:01 +03:00
fn rtrim ( & self ) -> & Self {
if let Some ( last ) = self . iter ( ) . rposition ( | b | ! is_whitespace! ( * b ) ) {
2019-03-03 22:11:15 +02:00
& self [ ..= last ]
2018-08-14 19:19:01 +03:00
} else {
& [ ]
}
}
fn ltrim ( & self ) -> & Self {
2018-08-05 12:14:26 +03:00
if let Some ( first ) = self . iter ( ) . position ( | b | ! is_whitespace! ( * b ) ) {
2018-08-14 19:19:01 +03:00
& self [ first .. ]
2018-08-04 20:40:20 +03:00
} else {
& [ ]
}
}
2018-08-14 19:19:01 +03:00
fn trim ( & self ) -> & [ u8 ] {
self . rtrim ( ) . ltrim ( )
}
2018-08-04 20:40:20 +03:00
// https://stackoverflow.com/a/35907071
fn find ( & self , needle : & [ u8 ] ) -> Option < usize > {
2020-05-06 18:53:44 +03:00
if needle . is_empty ( ) {
return None ;
}
2018-08-07 15:01:15 +03:00
self . windows ( needle . len ( ) )
. position ( | window | window = = needle )
2018-08-04 20:40:20 +03:00
}
2019-11-18 13:00:43 +02:00
fn rfind ( & self , needle : & [ u8 ] ) -> Option < usize > {
2020-05-06 18:53:44 +03:00
if needle . is_empty ( ) {
return None ;
}
2019-11-18 13:00:43 +02:00
self . windows ( needle . len ( ) )
. rposition ( | window | window = = needle )
}
2018-08-04 20:40:20 +03:00
fn replace ( & self , from : & [ u8 ] , to : & [ u8 ] ) -> Vec < u8 > {
let mut ret = self . to_vec ( ) ;
if let Some ( idx ) = self . find ( from ) {
ret . splice ( idx .. ( idx + from . len ( ) ) , to . iter ( ) . cloned ( ) ) ;
}
ret
}
2019-11-28 22:15:32 +02:00
fn is_quoted ( & self ) -> bool {
self . starts_with ( b " \" " ) & & self . ends_with ( b " \" " ) & & self . len ( ) > 1
}
2018-08-04 20:40:20 +03:00
}
2020-05-30 14:40:47 +03:00
pub trait BytesIterExt {
fn join ( & mut self , sep : u8 ) -> Vec < u8 > ;
}
impl < ' a , P : for < ' r > FnMut ( & ' r u8 ) -> bool > BytesIterExt for std ::slice ::Split < ' a , u8 , P > {
fn join ( & mut self , sep : u8 ) -> Vec < u8 > {
self . fold ( vec! [ ] , | mut acc , el | {
if ! acc . is_empty ( ) {
acc . push ( sep ) ;
}
acc . extend ( el . iter ( ) ) ;
acc
} )
}
}
2020-06-06 19:38:20 +03:00
//fn parser(input: I) -> IResult<I, O, E>;
pub fn mail ( input : & [ u8 ] ) -> Result < ( Vec < ( & [ u8 ] , & [ u8 ] ) > , & [ u8 ] ) > {
2020-08-09 09:50:20 +03:00
let ( rest , result ) = alt ( (
separated_pair (
headers ::headers ,
alt ( ( tag ( b " \n " ) , tag ( b " \r \n " ) ) ) ,
take_while ( | _ | true ) ,
) ,
pair ( headers ::headers , generic ::eof ) ,
) ) ( input )
2020-06-06 19:38:20 +03:00
. chain_err_summary ( | | " Could not parse mail " ) ? ;
if ! rest . is_empty ( ) {
return Err ( MeliError ::new ( " Got leftover bytes after parsing mail " ) ) ;
2017-09-09 14:20:32 +03:00
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
Ok ( result )
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
pub mod generic {
use super ::* ;
pub fn date ( input : & [ u8 ] ) -> Result < crate ::datetime ::UnixTimestamp > {
let ( _ , mut parsed_result ) = encodings ::phrase ( & eat_comments ( input ) , false ) ? ;
if let Some ( pos ) = parsed_result . find ( b " -0000 " ) {
parsed_result [ pos ] = b '+' ;
2017-09-07 23:00:08 +03:00
}
2020-06-06 19:38:20 +03:00
crate ::datetime ::rfc822_to_timestamp ( parsed_result . trim ( ) )
2017-09-07 23:00:08 +03:00
}
2020-09-03 12:09:29 +03:00
///`%x21-7E`
fn vchar ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
if input . is_empty ( ) {
return Err ( nom ::Err ::Error ( ( input , " vchar(): empty input " ) . into ( ) ) ) ;
}
if input [ 0 ] > = 0x21 & & input [ 0 ] < = 0x7e {
Ok ( ( & input [ 1 .. ] , input [ 0 ] ) )
} else {
Err ( nom ::Err ::Error ( ( input , " vchar(): out of range " ) . into ( ) ) )
}
}
///`quoted-pair = ("\" (VCHAR / WSP)) / obs-qp`
fn quoted_pair ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let ( input , byte ) = preceded ( tag ( " \\ " ) , alt ( ( vchar , wsp ) ) ) ( input ) ? ;
Ok ( ( input , vec! [ byte ] . into ( ) ) )
}
///```text
///ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\"
/// obs-ctext
///```
fn ctext ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( ) > {
if input . is_empty ( ) {
return Err ( nom ::Err ::Error ( ( input , " ctext(): empty input " ) . into ( ) ) ) ;
}
if ( input [ 0 ] > = 33 & & input [ 0 ] < = 39 )
| | ( input [ 0 ] > = 42 & & input [ 0 ] < = 91 )
| | ( input [ 0 ] > = 93 & & input [ 0 ] < = 126 )
{
Ok ( ( & input [ 1 .. ] , ( ) ) )
} else {
Err ( nom ::Err ::Error ( ( input , " ctext(): out of range " ) . into ( ) ) )
}
}
///```text
///ctext = %d33-39 / ; Printable US-ASCII
/// %d42-91 / ; characters not including
/// %d93-126 / ; "(", ")", or "\"
/// obs-ctext
///ccontent = ctext / quoted-pair / comment
///comment = "(" *([FWS] ccontent) [FWS] ")"
///```
pub fn comment ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( ) > {
if ! input . starts_with ( b " ( " ) {
return Err ( nom ::Err ::Error (
( input , " comment(): not starting with '(' " ) . into ( ) ,
) ) ;
}
let mut input = & input [ 1 .. ] ;
let mut comment_level = 1 ;
while comment_level > 0 {
if input . is_empty ( ) {
return Err ( nom ::Err ::Error (
( input , " comment(): unclosed comment " ) . into ( ) ,
) ) ;
}
input = context ( " comment() " , opt ( fws ) ) ( input ) ? . 0 ;
while let Ok ( ( _input , _ ) ) =
context ( " comment() " , alt ( ( ctext , map ( quoted_pair , | _ | ( ) ) ) ) ) ( input )
{
input = _input ;
}
if input . starts_with ( b " ) " ) {
comment_level - = 1 ;
input = & input [ 1 .. ] ;
} else if input . starts_with ( b " ( " ) {
comment_level + = 1 ;
input = & input [ 1 .. ] ;
} else {
input = context ( " comment() " , opt ( fws ) ) ( input ) ? . 0 ;
}
}
Ok ( ( input , ( ) ) )
}
#[ test ]
fn test_parser_comment ( ) {
let s = b " (recursive (comment) block) " ;
assert_eq! ( comment ( s ) , Ok ( ( & b " " [ .. ] , ( ) ) ) ) ;
}
///`FWS = ([*WSP CRLF] 1*WSP) / obs-FWS`
pub fn fws ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
if let Ok ( ( rest , ws ) ) = terminated ( many0 ( wsp ) , crlf ) ( input ) {
let mut v : Vec < u8 > = ws . into_iter ( ) . fold ( vec! [ ] , | mut acc , x | {
acc . push ( x ) ;
acc
} ) ;
let mut width = 0 ;
let mut input = rest ;
while let Ok ( ( input_ , w ) ) = wsp ( input ) {
v . push ( w ) ;
width + = 1 ;
input = input_ ;
}
if width = = 0 {
Err ( nom ::Err ::Error ( ( input , " fws(): no WSP " ) . into ( ) ) )
} else {
Ok ( ( input , Cow ::Owned ( v ) ) )
}
} else {
let orig_input = input ;
let mut input = input ;
let mut width = 0 ;
while let Ok ( ( input_ , _ ) ) = wsp ( input ) {
width + = 1 ;
input = input_ ;
}
if width = = 0 {
Err ( nom ::Err ::Error ( ( input , " fws(): no WSP " ) . into ( ) ) )
} else {
Ok ( ( input , Cow ::Borrowed ( & orig_input [ .. width ] ) ) )
}
}
}
///`WSP = SP / HTAB ; white space`
pub fn wsp ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
if input . starts_with ( b " " ) | | input . starts_with ( b " \t " ) {
Ok ( ( & input [ 1 .. ] , input [ 0 ] ) )
} else {
Err ( nom ::Err ::Error ( ( input , " wsp(): not whitespace " ) . into ( ) ) )
}
}
pub fn crlf ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( ) > {
if input . starts_with ( b " \n " ) {
Ok ( ( & input [ 1 .. ] , ( ) ) )
} else if input . starts_with ( b " \r \n " ) {
Ok ( ( & input [ 2 .. ] , ( ) ) )
} else {
Err ( nom ::Err ::Error ( ( input , " crlf(): not whitespace " ) . into ( ) ) )
}
}
///`CFWS = (1*([FWS] comment) [FWS]) / FWS`
pub fn cfws ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
alt ( (
| input | {
let ( input , pr ) = many1 ( terminated ( opt ( fws ) , comment ) ) ( input ) ? ;
let ( input , end ) = opt ( fws ) ( input ) ? ;
let mut pr = pr . into_iter ( ) . filter_map ( | s | s ) . fold ( vec! [ ] , | mut acc , x | {
acc . extend_from_slice ( & x ) ;
acc
} ) ;
if pr . is_empty ( ) {
Ok ( ( input , end . unwrap_or ( ( & b " " [ .. ] ) . into ( ) ) ) )
} else {
if let Some ( end ) = end {
pr . extend_from_slice ( & end ) ;
}
Ok ( ( input , pr . into ( ) ) )
}
} ,
fws ,
) ) ( input )
}
2020-06-06 19:38:20 +03:00
fn eat_comments ( input : & [ u8 ] ) -> Vec < u8 > {
let mut in_comment = false ;
input
. iter ( )
. fold ( Vec ::with_capacity ( input . len ( ) ) , | mut acc , x | {
if * x = = b '(' & & ! in_comment {
in_comment = true ;
acc
} else if * x = = b ')' & & in_comment {
in_comment = false ;
acc
} else if in_comment {
acc
} else {
acc . push ( * x ) ;
acc
}
} )
2019-06-10 15:46:32 +03:00
}
2020-09-03 12:09:29 +03:00
///`unstructured = (*([FWS] VCHAR) *WSP) / obs-unstruct`
pub fn unstructured ( input : & [ u8 ] ) -> Result < String > {
let ( input , r ) : ( _ , Vec < ( Option < Cow < '_ , [ u8 ] > > , u8 ) > ) =
many0 ( pair ( opt ( fws ) , vchar ) ) ( input ) ? ;
let ( input , rest_wsp ) : ( _ , Vec < u8 > ) = many0 ( wsp ) ( input ) ? ;
let mut ret_s = Vec ::new ( ) ;
for ( opt_slice , b ) in r {
if let Some ( slice ) = opt_slice {
ret_s . extend_from_slice ( & slice ) ;
}
ret_s . push ( b ) ;
}
ret_s . extend_from_slice ( & rest_wsp ) ;
let ret_s = String ::from_utf8_lossy ( & ret_s ) . into_owned ( ) ;
if ! input . is_empty ( ) {
Err ( MeliError ::from ( format! (
" unstructured(): unmatched input: {} while result is {} " ,
to_str! ( input ) ,
ret_s
) ) )
} else {
Ok ( ret_s )
}
}
2020-06-06 19:38:20 +03:00
use crate ::email ::address ::Address ;
use crate ::email ::mailto ::Mailto ;
pub fn mailto ( mut input : & [ u8 ] ) -> IResult < & [ u8 ] , Mailto > {
if ! input . starts_with ( b " mailto: " ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " mailto(): input doesn't start with `mailto:` " ) . into ( ) ,
) ) ;
2019-06-10 15:46:32 +03:00
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
input = & input [ b " mailto: " . len ( ) .. ] ;
let end = input . iter ( ) . position ( | e | * e = = b '?' ) . unwrap_or ( input . len ( ) ) ;
let address : Address ;
if let Ok ( ( _ , addr ) ) = crate ::email ::parser ::address ::address ( & input [ .. end ] ) {
address = addr ;
input = if input [ end .. ] . is_empty ( ) {
& input [ end .. ]
} else {
& input [ end + 1 .. ]
} ;
} else {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " mailto(): address not found in input " ) . into ( ) ,
) ) ;
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
let mut subject = None ;
let mut cc = None ;
let mut bcc = None ;
let mut body = None ;
while ! input . is_empty ( ) {
let tag = if let Some ( tag_pos ) = input . iter ( ) . position ( | e | * e = = b '=' ) {
let ret = & input [ 0 .. tag_pos ] ;
input = & input [ tag_pos + 1 .. ] ;
ret
} else {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " mailto(): extra characters found in input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} ;
let value_end = input . iter ( ) . position ( | e | * e = = b '&' ) . unwrap_or ( input . len ( ) ) ;
let value = String ::from_utf8_lossy ( & input [ .. value_end ] ) . to_string ( ) ;
match tag {
b " subject " if subject . is_none ( ) = > {
subject = Some ( value ) ;
}
b " cc " if cc . is_none ( ) = > {
cc = Some ( value ) ;
}
b " bcc " if bcc . is_none ( ) = > {
bcc = Some ( value ) ;
}
b " body " if body . is_none ( ) = > {
/* FIXME:
* Parse escaped characters properly .
* /
body = Some ( value . replace ( " %20 " , " " ) . replace ( " %0A " , " \n " ) ) ;
}
_ = > {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " mailto(): unknown tag in input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
}
if input [ value_end .. ] . is_empty ( ) {
break ;
}
input = & input [ value_end + 1 .. ] ;
2019-06-28 19:34:40 +03:00
}
2020-06-06 19:38:20 +03:00
Ok ( (
input ,
Mailto {
address ,
subject ,
cc ,
bcc ,
body ,
} ,
) )
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
pub struct HeaderIterator < ' a > ( pub & ' a [ u8 ] ) ;
impl < ' a > Iterator for HeaderIterator < ' a > {
type Item = ( & ' a [ u8 ] , & ' a [ u8 ] ) ;
fn next ( & mut self ) -> Option < ( & ' a [ u8 ] , & ' a [ u8 ] ) > {
if self . 0. is_empty ( ) {
return None ;
}
match super ::headers ::header ( self . 0 ) {
Ok ( ( rest , value ) ) = > {
self . 0 = rest ;
Some ( value )
}
_ = > {
self . 0 = & [ ] ;
None
}
}
2019-06-10 15:46:32 +03:00
}
}
2020-08-09 09:50:20 +03:00
pub fn eof ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
if input . is_empty ( ) {
Ok ( ( input , input ) )
} else {
Err ( nom ::Err ::Error ( ( input , " expected EOF " ) . into ( ) ) )
}
}
2020-09-03 12:09:29 +03:00
#[ test ]
fn test_parser_cfws ( ) {
let s = r #" This
is a test " #;
assert_eq! ( & unstructured ( s . as_bytes ( ) ) . unwrap ( ) , " This is a test " , ) ;
assert_eq! ( & unstructured ( s . as_bytes ( ) ) . unwrap ( ) , " This is a test " , ) ;
let s = " this is \n \t a folded name " ;
assert_eq! (
& unstructured ( s . as_bytes ( ) ) . unwrap ( ) ,
2020-09-03 14:44:03 +03:00
" this is \t a folded name " ,
2020-09-03 12:09:29 +03:00
) ;
}
2020-09-03 14:44:03 +03:00
///`atom = [CFWS] 1*atext [CFWS]`
pub fn atom ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let ( input , opt_space ) = opt ( cfws ) ( input ) ? ;
let mut i = 0 ;
while i < input . len ( ) {
//&& !input[i].is_ascii_whitespace() {
match input [ i ] {
b '(' | b ')' | b '<' | b '>' | b '[' | b ']' | b ':' | b ';' | b '@' | b '\\' | b ','
| b '.' | b '\r' | b '\n' | b '"' = > break ,
_ = > { }
}
i + = 1 ;
}
if i = = 0 {
return Err ( nom ::Err ::Error (
( input , " atom(): starts with whitespace or empty " ) . into ( ) ,
) ) ;
}
while i + 1 > 0 {
if input [ i - 1 ] = = b ' ' | | input [ i - 1 ] = = b '\t' {
i - = 1 ;
} else {
break ;
}
}
let ( rest , opt_space2 ) = opt ( cfws ) ( & input [ i .. ] ) ? ;
let ret = if opt_space . is_some ( ) | | opt_space2 . is_some ( ) {
let mut ret = Vec ::with_capacity ( i + 2 ) ;
if let Some ( opt_space ) = opt_space {
ret . extend_from_slice ( & opt_space ) ;
}
ret . extend_from_slice ( & input [ .. i ] ) ;
if let Some ( opt_space ) = opt_space2 {
ret . extend_from_slice ( & opt_space ) ;
}
Cow ::Owned ( ret )
} else {
Cow ::Borrowed ( & input [ .. i ] )
} ;
Ok ( ( rest , ret ) )
}
///`quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]`
pub fn quoted_string ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let ( input , opt_space ) = opt ( cfws ) ( input ) ? ;
if ! input . starts_with ( b " \" " ) {
return Err ( nom ::Err ::Error (
( input , " quoted_string(): doesn't start with DQUOTE " ) . into ( ) ,
) ) ;
}
let input = & input [ 1 .. ] ;
let mut i = 0 ;
while i < input . len ( ) & & input [ i ] ! = b '"' {
if opt_space . is_some ( ) | | ( input [ i .. ] . starts_with ( b " \\ " ) & & i + 1 < input . len ( ) ) {
let mut ret = if let Some ( opt_space ) = opt_space {
let mut r = Vec ::with_capacity ( 2 * i ) ;
r . extend_from_slice ( & opt_space ) ;
r
} else {
Vec ::with_capacity ( 2 * i )
} ;
ret . extend_from_slice ( & input [ .. i ] ) ;
i + = 1 ;
ret . push ( input [ i ] ) ;
i + = 1 ;
while i < input . len ( ) & & input [ i ] ! = b '"' {
if input [ i .. ] . starts_with ( b " \\ " ) & & i + 1 < input . len ( ) {
i + = 1 ;
}
ret . push ( input [ i ] ) ;
i + = 1 ;
}
if i < input . len ( ) {
// skip DQUOTE
i + = 1 ;
} else {
return Err ( nom ::Err ::Error (
( input , " quoted_string(): unclosed DQUOTE " ) . into ( ) ,
) ) ;
}
let ( rest , opt_sp ) = opt ( cfws ) ( & input [ i .. ] ) ? ;
if let Some ( opt_sp ) = opt_sp {
ret . extend_from_slice ( & opt_sp ) ;
}
let ret = Cow ::Owned ( ret ) ;
return Ok ( ( rest , ret ) ) ;
}
i + = 1 ;
}
let ret = Cow ::Borrowed ( & input [ .. i ] ) ;
if i < input . len ( ) {
// skip DQUOTE
i + = 1 ;
} else {
return Err ( nom ::Err ::Error (
( input , " quoted_string(): unclosed DQUOTE " ) . into ( ) ,
) ) ;
}
let ( rest , opt_sp ) = opt ( cfws ) ( & input [ i .. ] ) ? ;
if let Some ( opt_sp ) = opt_sp {
let mut ret = ret . to_vec ( ) ;
ret . extend_from_slice ( & opt_sp ) ;
Ok ( ( rest , Cow ::Owned ( ret ) ) )
} else {
Ok ( ( rest , ret ) )
}
}
///`word = atom / quoted-string`
pub fn word ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
alt ( ( quoted_string , atom ) ) ( input )
}
///`phrase = 1*word / obs-phrase`
pub fn phrase2 ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < u8 > > {
let ( rest , words ) = many1 ( word ) ( input ) ? ;
let len = words . iter ( ) . map ( | v | v . len ( ) ) . sum ::< usize > ( ) ;
let mut ret = words
. into_iter ( )
. fold ( Vec ::with_capacity ( len ) , | mut acc , el | {
acc . extend_from_slice ( & el ) ;
acc
} ) ;
let right_wsp_padding = ret . len ( ) - ret . rtrim ( ) . len ( ) ;
for _ in 0 .. right_wsp_padding {
ret . pop ( ) ;
}
Ok ( ( rest , ret ) )
}
#[ test ]
fn test_phrase ( ) {
let s = b " \" Jeffrey \\ \" fejj \\ \" Stedfast \" " ; // <fejj@helixcode.com>"
assert_eq! ( to_str! ( & phrase2 ( s ) . unwrap ( ) . 1 ) , " Jeffrey \" fejj \" Stedfast " ) ;
}
///dot-atom-text = 1*atext *("." 1*atext)
pub fn dot_atom_text ( mut input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let mut ret = vec! [ ] ;
let mut at_least_one = false ;
while let Ok ( ( _input , atext_r ) ) = atext ( input ) {
at_least_one = true ;
ret . push ( atext_r ) ;
input = _input ;
}
if ! at_least_one {
return Err ( nom ::Err ::Error (
( input , " dot_atom(): starts with at least one atext " ) . into ( ) ,
) ) ;
}
loop {
if ! input . starts_with ( b " . " ) {
break ;
}
ret . push ( b '.' ) ;
input = & input [ 1 .. ] ;
let mut at_least_one = false ;
while let Ok ( ( _input , atext_r ) ) = atext ( input ) {
at_least_one = true ;
ret . push ( atext_r ) ;
input = _input ;
}
if ! at_least_one {
return Err ( nom ::Err ::Error (
( input , " dot_atom(): DOT followed with at least one atext " ) . into ( ) ,
) ) ;
}
}
Ok ( ( input , ret . into ( ) ) )
}
///`atext = ALPHA / DIGIT / ; Printable US-ASCII "!" / "#" / ; characters not including "$" / "%" / ; specials. Used for atoms. "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"`
pub fn atext ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
if input . is_empty ( ) {
return Err ( nom ::Err ::Error ( ( input , " atext(): empty input " ) . into ( ) ) ) ;
}
if input [ 0 ] . is_ascii_alphanumeric ( )
| | [
b '!' , b '#' , b '$' , b '%' , b '&' , b '\'' , b '*' , b '+' , b '-' , b '/' , b '=' , b '?' , b '^' ,
b '_' , b '`' , b '{' , b '|' , b '}' , b '~' ,
]
. contains ( & input [ 0 ] )
{
Ok ( ( & input [ 1 .. ] , input [ 0 ] ) )
} else {
return Err ( nom ::Err ::Error ( ( input , " atext(): invalid byte " ) . into ( ) ) ) ;
}
}
///dot-atom = [CFWS] dot-atom-text [CFWS]
pub fn dot_atom ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
let ( input , ret ) = dot_atom_text ( input ) ? ;
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
Ok ( ( input , ret . into ( ) ) )
}
///```text
///dtext = %d33-90 / ; Printable US-ASCII
/// %d94-126 / ; characters not including
/// obs-dtext ; "[", "]", or "\"
///```
pub fn dtext ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
if input . is_empty ( ) {
return Err ( nom ::Err ::Error ( ( input , " dtext(): empty input " ) . into ( ) ) ) ;
}
if ( input [ 0 ] > = 33 & & input [ 0 ] < = 90 ) | | ( input [ 0 ] > 94 & & input [ 0 ] < 126 ) {
Ok ( ( & input [ 1 .. ] , input [ 0 ] ) )
} else {
Err ( nom ::Err ::Error ( ( input , " dtext(): out of range " ) . into ( ) ) )
}
}
2019-06-10 15:46:32 +03:00
}
2019-02-15 19:21:58 +02:00
2020-09-11 00:08:56 +03:00
pub mod mailing_lists {
//! Mailing lists headers.
//!
//! Implemented RFCs:
//!
//! - [RFC2369 "The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields"](https://tools.ietf.org/html/rfc2369)
use super ::* ;
use generic ::cfws ;
///Parse the value of headers defined in RFC2369 "The Use of URLs as Meta-Syntax for Core
///Mail List Commands and their Transport through Message Header Fields"
pub fn rfc_2369_list_headers_action_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < & [ u8 ] > > {
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
let ( input , ret ) = alt ( (
separated_nonempty_list (
delimited (
map ( opt ( cfws ) , | _ | ( ) ) ,
map ( is_a ( " , " ) , | _ | ( ) ) ,
map ( opt ( cfws ) , | _ | ( ) ) ,
) ,
delimited ( tag ( " < " ) , take_until ( " > " ) , tag ( " > " ) ) ,
) ,
map ( delimited ( tag ( " < " ) , take_until ( " > " ) , tag ( " > " ) ) , | el | {
vec! [ el ]
} ) ,
map (
delimited (
map ( opt ( cfws ) , | _ | ( ) ) ,
map ( tag ( " NO " ) , | _ | ( ) ) ,
map ( opt ( cfws ) , | _ | ( ) ) ,
) ,
| _ | vec! [ ] ,
) ,
) ) ( input ) ? ;
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
Ok ( ( input , ret ) )
}
#[ test ]
fn test_parser_rfc_2369_list ( ) {
let s = r #" List-Help: <mailto:list@host.com?subject=help> (List Instructions)
List - Help : < mailto :list - manager @ host . com ? body = info >
List - Help : < mailto :list - info @ host . com > ( Info about the list )
List - Help : < http ://www.host.com/list/>, <mailto:list-info@host.com>
List - Help : < ftp ://ftp.host.com/list.txt> (FTP),
< mailto :list @ host . com ? subject = help >
List - Post : < mailto :list @ host . com >
List - Post : < mailto :moderator @ host . com > ( Postings are Moderated )
List - Post : < mailto :moderator @ host . com ? subject = list % 20 posting >
List - Post : NO ( posting not allowed on this list )
List - Archive : < mailto :archive @ host . com ? subject = index % 20 list >
List - Archive : < ftp ://ftp.host.com/pub/list/archive/>
List - Archive : < http ://www.host.com/list/archive/> (Web Archive)
" #;
let ( rest , headers ) = headers ::headers ( s . as_bytes ( ) ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
for ( h , v ) in headers {
let ( rest , action_list ) = rfc_2369_list_headers_action_list ( v ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
}
}
}
2020-06-06 19:38:20 +03:00
pub mod headers {
use super ::* ;
pub fn headers ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < ( & [ u8 ] , & [ u8 ] ) > > {
many1 ( header ) ( input )
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
pub fn header ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] ) > {
alt ( ( header_without_val , header_with_val ) ) ( input )
}
pub fn header_without_val ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] ) > {
if input . is_empty ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_without_val(): input is empty " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} else if input . starts_with ( b " \n " ) | | input . starts_with ( b " \r \n " ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
(
input ,
" header_without_val(): input starts with folding whitespace " ,
)
. into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
let mut ptr = 0 ;
2020-08-25 16:39:12 +03:00
let mut name : & [ u8 ] = & [ ] ;
2020-06-06 19:38:20 +03:00
let mut has_colon = false ;
/* field-name = 1 * <any CHAR, excluding CTLs, SPACE, and ":"> */
for ( i , x ) in input . iter ( ) . enumerate ( ) {
if input [ i .. ] . starts_with ( b " \r \n " ) {
name = & input [ 0 .. i ] ;
ptr = i + 2 ;
break ;
2020-06-20 11:13:50 +03:00
} else if * x = = b ':' {
2020-06-06 19:38:20 +03:00
name = & input [ 0 .. i ] ;
has_colon = true ;
ptr = i ;
break ;
2020-06-20 11:13:50 +03:00
} else if * x = = b '\n' {
name = & input [ 0 .. i ] ;
ptr = i ;
break ;
2020-06-06 19:38:20 +03:00
} else if is_ctl_or_space! ( * x ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error ( (
input ,
r # "header_without_val(): field-name should contain "any CHAR, excluding CTLs, SPACE, and ":""# ,
) . into ( ) ) ) ;
2020-06-06 19:38:20 +03:00
}
}
if name . is_empty ( ) | | input . len ( ) < = ptr {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_without_val(): not enough input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
if input [ ptr ] = = b ':' {
ptr + = 1 ;
2019-09-25 22:00:30 +03:00
has_colon = true ;
2020-06-06 19:38:20 +03:00
if ptr > = input . len ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_without_val(): EOF after colon " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
if ! has_colon {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_without_val(): no colon found " ) . into ( ) ,
) ) ;
2019-06-10 15:46:32 +03:00
}
2019-09-25 22:00:30 +03:00
2020-06-06 19:38:20 +03:00
while input [ ptr ] = = b ' ' {
ptr + = 1 ;
if ptr > = input . len ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
(
input ,
" header_without_val(): expected start of next field, found EOF " ,
)
. into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
}
if input [ ptr .. ] . starts_with ( b " \n " ) {
ptr + = 1 ;
if ptr > = input . len ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
(
input ,
" header_without_val(): expected folding whitespace, found EOF " ,
)
. into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
if input . len ( ) > ptr & & input [ ptr ] ! = b ' ' & & input [ ptr ] ! = b '\t' {
Ok ( ( & input [ ptr .. ] , ( name , b " " ) ) )
} else {
2020-06-20 11:10:27 +03:00
Err ( nom ::Err ::Error (
(
input ,
" header_without_val(): expected folding whitespace, found EOF " ,
)
. into ( ) ,
) )
2020-06-06 19:38:20 +03:00
}
} else if input [ ptr .. ] . starts_with ( b " \r \n " ) {
ptr + = 2 ;
if ptr > input . len ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
(
input ,
" header_without_val(): expected folding whitespace, found EOF " ,
)
. into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
if input . len ( ) > ptr & & input [ ptr ] ! = b ' ' & & input [ ptr ] ! = b '\t' {
Ok ( ( & input [ ptr .. ] , ( name , b " " ) ) )
} else {
2020-06-20 11:10:27 +03:00
Err ( nom ::Err ::Error (
(
& input [ ptr .. ] ,
" header_without_val(): expected folding whitespace, found EOF " ,
)
. into ( ) ,
) )
2020-06-06 19:38:20 +03:00
}
} else {
2020-06-20 11:10:27 +03:00
Err ( nom ::Err ::Error (
(
& input [ ptr .. ] ,
" header_without_val(): expected folding whitespace (newline) " ,
)
. into ( ) ,
) )
2020-06-06 19:38:20 +03:00
}
2019-09-25 22:00:30 +03:00
}
2020-06-06 19:38:20 +03:00
/* A header can span multiple lines, eg:
*
* Received : from - - - - - - - - - - - - - - - - - - - - ( - - - - - - - - - - - - - - - - - - - - - - - - - )
* by - - - - - - - - - - - - - - - - - - - - - ( - - - - - - - - - - - - - - - - - - - - - [ - - - - - - - - - - - - - - - - - - ] ) ( - - - - - - - - - - - - - - - - - - - - - - - )
* with ESMTP id - - - - - - - - - - - - for < - - - - - - - - - - - - - - - - - - ->;
* Tue , 5 Jan 2016 21 :30 :44 + 0100 ( CET )
* /
pub fn header_value ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
let input_len = input . len ( ) ;
for ( i , x ) in input . iter ( ) . enumerate ( ) {
if * x = = b '\n'
& & ( ( ( i + 1 ) < input_len & & input [ i + 1 ] ! = b ' ' & & input [ i + 1 ] ! = b '\t' )
| | i + 1 = = input_len )
{
return Ok ( ( & input [ ( i + 1 ) .. ] , & input [ 0 .. i ] ) ) ;
} else if input [ i .. ] . starts_with ( b " \r \n " )
& & ( ( ( i + 2 ) < input_len & & input [ i + 2 ] ! = b ' ' & & input [ i + 2 ] ! = b '\t' )
| | i + 2 = = input_len )
{
return Ok ( ( & input [ ( i + 2 ) .. ] , & input [ 0 .. i ] ) ) ;
}
2019-06-10 15:46:32 +03:00
}
2020-06-20 11:10:27 +03:00
Err ( nom ::Err ::Error (
(
input ,
" header_value(): expected new line after header value " ,
)
. into ( ) ,
) )
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
/* Parse a single header as a tuple */
pub fn header_with_val ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] ) > {
if input . is_empty ( ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): empty input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} else if input . starts_with ( b " \n " ) | | input . starts_with ( b " \r \n " ) {
2020-06-20 11:10:27 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): field name starts with new line " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
let mut ptr = 0 ;
2020-08-25 16:39:12 +03:00
let mut name : & [ u8 ] = & [ ] ;
2020-06-06 19:38:20 +03:00
/* field-name = 1 * <any CHAR, excluding CTLs, SPACE, and ":"> */
for ( i , x ) in input . iter ( ) . enumerate ( ) {
if * x = = b ':' {
name = & input [ 0 .. i ] ;
ptr = i + 1 ;
break ;
} else if is_ctl_or_space! ( * x ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
(
& input [ i .. ] ,
format! ( " header_with_val(): invalid character: {:?} " , * x as char ) ,
)
. into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
}
if name . is_empty ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found empty header name " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
2019-07-31 13:30:28 +03:00
if ptr > = input . len ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found EOF " ) . into ( ) ,
) ) ;
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
if input [ ptr ] = = b '\n' {
ptr + = 1 ;
if ptr > = input . len ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found EOF " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
} else if input [ ptr .. ] . starts_with ( b " \r \n " ) {
ptr + = 2 ;
if ptr > input . len ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found EOF " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
2019-06-28 19:34:40 +03:00
}
2020-06-06 19:38:20 +03:00
if ptr > = input . len ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found EOF " ) . into ( ) ,
) ) ;
2019-06-28 19:34:40 +03:00
}
2020-06-06 19:38:20 +03:00
while input [ ptr ] = = b ' ' | | input [ ptr ] = = b '\t' {
ptr + = 1 ;
if ptr > = input . len ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " header_with_val(): found EOF " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
2019-06-10 15:46:32 +03:00
}
2020-06-06 19:38:20 +03:00
header_value ( & input [ ptr .. ] ) . map ( | ( rest , value ) | ( rest , ( name , value ) ) )
2019-06-10 15:46:32 +03:00
}
2019-02-15 19:21:58 +02:00
2020-06-06 19:38:20 +03:00
pub fn headers_raw ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
if input . is_empty ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
2020-08-25 16:39:12 +03:00
( input , " headers_raw(): input is empty " ) . into ( ) ,
2020-06-25 11:47:52 +03:00
) ) ;
2020-06-06 19:38:20 +03:00
}
for i in 0 .. input . len ( ) {
if input [ i .. ] . starts_with ( b " \n \n " ) {
return Ok ( ( & input [ ( i + 1 ) .. ] , & input [ 0 ..= i ] ) ) ;
} else if input [ i .. ] . starts_with ( b " \r \n \r \n " ) {
return Ok ( ( & input [ ( i + 2 ) .. ] , & input [ 0 ..= i ] ) ) ;
}
2017-09-24 12:29:39 +03:00
}
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
( input , " headers_raw(): got EOF while looking for new line " ) . into ( ) ,
) )
2017-09-24 12:29:39 +03:00
}
}
2017-09-14 18:08:14 +03:00
2020-06-06 19:38:20 +03:00
pub mod attachments {
use super ::* ;
use crate ::email ::address ::* ;
2020-08-09 09:49:32 +03:00
use crate ::email ::attachment_types ::{ ContentDisposition , ContentDispositionKind } ;
2020-06-06 19:38:20 +03:00
pub fn attachment ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( std ::vec ::Vec < ( & [ u8 ] , & [ u8 ] ) > , & [ u8 ] ) > {
2020-08-09 09:50:20 +03:00
alt ( (
separated_pair (
many0 ( headers ::header ) ,
alt ( ( tag ( b " \n " ) , tag ( b " \r \n " ) ) ) ,
take_while ( | _ | true ) ,
) ,
pair ( headers ::headers , generic ::eof ) ,
) ) ( input )
2020-06-06 19:38:20 +03:00
}
2019-09-25 22:00:30 +03:00
2020-06-06 19:38:20 +03:00
pub fn multipart_parts < ' a > (
input : & ' a [ u8 ] ,
boundary : & [ u8 ] ,
) -> IResult < & ' a [ u8 ] , Vec < StrBuilder > > {
let mut ret : Vec < _ > = Vec ::new ( ) ;
let mut input = input ;
let mut offset = 0 ;
loop {
let b_start = if let Some ( v ) = input . find ( boundary ) {
v
} else {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " multipart_parts(): could not find starting boundary " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} ;
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
if b_start < 2 {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " multipart_parts(): malformed boundary " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
offset + = b_start - 2 ;
input = & input [ b_start - 2 .. ] ;
if & input [ 0 .. 2 ] = = b " -- " {
offset + = 2 + boundary . len ( ) ;
input = & input [ 2 + boundary . len ( ) .. ] ;
if input [ 0 ] = = b '\n' {
offset + = 1 ;
input = & input [ 1 .. ] ;
} else if input [ 0 .. ] . starts_with ( b " \r \n " ) {
offset + = 2 ;
input = & input [ 2 .. ] ;
} else {
continue ;
}
break ;
}
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
loop {
if input . len ( ) < boundary . len ( ) + 4 {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " multipart_parts(): found EOF " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
if let Some ( end ) = input . find ( boundary ) {
if & input [ end - 2 .. end ] ! = b " -- " {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " multipart_parts(): malformed boundary " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
2020-08-09 09:50:20 +03:00
if input [ .. end - 2 ] . ends_with ( b " \r \n " ) {
ret . push ( StrBuilder {
offset ,
length : end - 4 ,
} ) ;
} else {
ret . push ( StrBuilder {
offset ,
length : end - 3 ,
} ) ;
}
2020-06-06 19:38:20 +03:00
offset + = end + boundary . len ( ) ;
input = & input [ end + boundary . len ( ) .. ] ;
if input . len ( ) < 2 | | input [ 0 ] ! = b '\n' | | & input [ 0 .. 2 ] = = b " -- " {
break ;
}
if input [ 0 ] = = b '\n' {
offset + = 1 ;
input = & input [ 1 .. ] ;
} else if input [ 0 .. ] . starts_with ( b " \r \n " ) {
offset + = 2 ;
input = & input [ 2 .. ] ;
}
} else {
ret . push ( StrBuilder {
offset ,
length : input . len ( ) ,
} ) ;
break ;
}
2018-08-11 22:47:27 +03:00
}
2020-06-06 19:38:20 +03:00
Ok ( ( input , ret ) )
2018-08-11 22:47:27 +03:00
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
fn parts_f ( boundary : & [ u8 ] ) -> impl Fn ( & [ u8 ] ) -> IResult < & [ u8 ] , Vec < & [ u8 ] > > + '_ {
move | input : & [ u8 ] | -> IResult < & [ u8 ] , Vec < & [ u8 ] > > {
let mut ret : Vec < & [ u8 ] > = Vec ::new ( ) ;
let mut input = input ;
loop {
let b_start = if let Some ( v ) = input . find ( boundary ) {
v
} else {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " parts_f(): could not find starting boundary " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} ;
if b_start < 2 {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " parts_f(): malformed boundary " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
input = & input [ b_start - 2 .. ] ;
if & input [ 0 .. 2 ] = = b " -- " {
input = & input [ 2 + boundary . len ( ) .. ] ;
if input [ 0 ] = = b '\n' {
input = & input [ 1 .. ] ;
} else if input [ 0 .. ] . starts_with ( b " \r \n " ) {
input = & input [ 2 .. ] ;
} else {
continue ;
}
break ;
}
}
loop {
if input . len ( ) < boundary . len ( ) + 4 {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error ( ( input , " parts_f(): found EOF " ) . into ( ) ) ) ;
2020-06-06 19:38:20 +03:00
}
if let Some ( end ) = input . find ( boundary ) {
if & input [ end - 2 .. end ] ! = b " -- " {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error ( ( input , " parts_f(): found EOF " ) . into ( ) ) ) ;
2020-06-06 19:38:20 +03:00
}
2020-08-09 09:50:20 +03:00
if input [ .. end - 2 ] . ends_with ( b " \r \n " ) {
ret . push ( & input [ .. end - 4 ] ) ;
} else {
ret . push ( & input [ .. end - 3 ] ) ;
}
2020-06-06 19:38:20 +03:00
input = & input [ end + boundary . len ( ) .. ] ;
if input . len ( ) < 2
| | ( input [ 0 ] ! = b '\n' & & & input [ 0 .. 2 ] ! = b " \r \n " )
| | & input [ 0 .. 2 ] = = b " -- "
{
break ;
}
if input [ 0 ] = = b '\n' {
input = & input [ 1 .. ] ;
} else if input [ 0 .. ] . starts_with ( b " \r \n " ) {
input = & input [ 2 .. ] ;
}
} else {
ret . push ( input ) ;
break ;
}
}
Ok ( ( input , ret ) )
2018-08-11 22:47:27 +03:00
}
}
2020-06-06 19:38:20 +03:00
pub fn parts < ' a > ( input : & ' a [ u8 ] , boundary : & [ u8 ] ) -> IResult < & ' a [ u8 ] , Vec < & ' a [ u8 ] > > {
alt ( (
parts_f ( boundary ) ,
| input : & ' a [ u8 ] | -> IResult < & ' a [ u8 ] , Vec < & ' a [ u8 ] > > {
let ( input , _ ) = take_until ( & b " -- " [ .. ] ) ( input ) ? ;
let ( input , _ ) = take_until ( boundary ) ( input ) ? ;
Ok ( ( input , Vec ::< & [ u8 ] > ::new ( ) ) )
} ,
) ) ( input )
/*
alt_complete! ( call! ( parts_f , boundary ) | do_parse! (
take_until_and_consume! ( & b " -- " [ .. ] ) > >
take_until_and_consume! ( boundary ) > >
( { Vec ::< & [ u8 ] > ::new ( ) } ) )
) ) ;
* /
}
2018-08-11 22:47:27 +03:00
2020-06-06 19:38:20 +03:00
/* Caution: values should be passed through phrase() */
pub fn content_type_parameter ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] ) > {
2020-08-09 09:49:32 +03:00
let ( input , _ ) = tag ( " ; " ) ( input . ltrim ( ) ) ? ;
2020-06-06 19:38:20 +03:00
let ( input , name ) = terminated ( take_until ( " = " ) , tag ( " = " ) ) ( input . ltrim ( ) ) ? ;
let ( input , value ) = alt ( (
delimited ( tag ( " \" " ) , take_until ( " \" " ) , tag ( " \" " ) ) ,
is_not ( " ; " ) ,
) ) ( input . ltrim ( ) ) ? ;
2018-08-11 22:47:27 +03:00
2020-06-06 19:38:20 +03:00
Ok ( ( input , ( name , value ) ) )
2017-09-16 13:32:40 +03:00
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
pub fn content_type ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] , Vec < ( & [ u8 ] , & [ u8 ] ) > ) > {
2020-08-09 09:49:32 +03:00
let ( input , _type ) = take_until ( " / " ) ( input . ltrim ( ) ) ? ;
2020-06-06 19:38:20 +03:00
let ( input , _ ) = tag ( " / " ) ( input ) ? ;
let ( input , _subtype ) = is_not ( " ; " ) ( input ) ? ;
let ( input , parameters ) = many0 ( content_type_parameter ) ( input ) ? ;
Ok ( ( input , ( _type , _subtype , parameters ) ) )
/*
do_parse! (
_type : take_until ! ( " / " ) > >
tag! ( " / " ) > >
_subtype : is_not ! ( " ; " ) > >
parameters : many0 ! ( complete! ( content_type_parameter ) ) > >
( {
( _type , _subtype , parameters )
} )
) ) ;
* /
2018-08-08 10:41:25 +03:00
}
2020-08-09 09:49:32 +03:00
/* Caution: values should be passed through phrase() */
pub fn content_disposition_parameter ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( & [ u8 ] , & [ u8 ] ) > {
let ( input , _ ) = tag ( " ; " ) ( input . ltrim ( ) ) ? ;
let ( input , name ) = terminated ( take_until ( " = " ) , tag ( " = " ) ) ( input . ltrim ( ) ) ? ;
let ( input , value ) = alt ( (
delimited ( tag ( " \" " ) , take_until ( " \" " ) , tag ( " \" " ) ) ,
is_not ( " ; " ) ,
) ) ( input . ltrim ( ) ) ? ;
Ok ( ( input , ( name , value ) ) )
}
pub fn content_disposition ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ContentDisposition > {
let ( input , kind ) = alt ( ( take_until ( " ; " ) , take_while ( | _ | true ) ) ) ( input . trim ( ) ) ? ;
let mut ret = ContentDisposition {
kind : if kind . trim ( ) . eq_ignore_ascii_case ( b " attachment " ) {
ContentDispositionKind ::Attachment
} else {
ContentDispositionKind ::Inline
} ,
.. ContentDisposition ::default ( )
} ;
if input . is_empty ( ) {
return Ok ( ( input , ret ) ) ;
}
let ( input , parameters ) = many0 ( content_disposition_parameter ) ( input . ltrim ( ) ) ? ;
for ( k , v ) in parameters {
if k . eq_ignore_ascii_case ( b " filename " ) {
ret . filename =
Some ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
} else if k . eq_ignore_ascii_case ( b " size " ) {
ret . size =
Some ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
} else if k . eq_ignore_ascii_case ( b " creation-date " ) {
ret . creation_date =
Some ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
} else if k . eq_ignore_ascii_case ( b " modification-date " ) {
ret . modification_date =
Some ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
} else if k . eq_ignore_ascii_case ( b " read-date " ) {
ret . read_date =
Some ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
} else {
ret . parameter
. push ( String ::from_utf8_lossy ( & super ::encodings ::phrase ( v , false ) ? . 1 ) . into ( ) ) ;
}
}
Ok ( ( input , ret ) )
}
2018-08-08 10:41:25 +03:00
}
2020-06-06 19:38:20 +03:00
pub mod encodings {
use super ::* ;
use crate ::email ::attachment_types ::Charset ;
use data_encoding ::BASE64_MIME ;
use encoding ::all ::* ;
use encoding ::{ DecoderTrap , Encoding } ;
pub fn quoted_printable_byte ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
if input . len ( ) < 3 {
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
(
input ,
" quoted_printable_byte(): input too short to be quoted_printable " ,
)
. into ( ) ,
) )
2020-06-06 19:38:20 +03:00
} else if input [ 0 ] = = b '=' & & is_hex_digit ( input [ 1 ] ) & & is_hex_digit ( input [ 2 ] ) {
let a = if input [ 1 ] < b ':' {
input [ 1 ] - 48
} else if input [ 1 ] < b '[' {
input [ 1 ] - 55
} else {
input [ 1 ] - 87
} ;
let b = if input [ 2 ] < b ':' {
input [ 2 ] - 48
} else if input [ 2 ] < b '[' {
input [ 2 ] - 55
} else {
input [ 2 ] - 87
} ;
Ok ( ( & input [ 3 .. ] , a * 16 + b ) )
} else if input . starts_with ( b " \r \n " ) {
Ok ( ( & input [ 2 .. ] , b '\n' ) )
} else {
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
( input , " quoted_printable_byte(): invalid input " ) . into ( ) ,
) )
2020-06-06 19:38:20 +03:00
}
2018-08-08 19:06:00 +03:00
}
2020-06-06 19:38:20 +03:00
/* Encoded words
* " =?charset?encoding?encoded text?= " .
* /
fn encoded_word ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < u8 > > {
if input . is_empty ( ) {
return Ok ( ( & [ ] , Vec ::with_capacity ( 0 ) ) ) ;
}
if input . len ( ) < 5 {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " encoded_word(): input too short to be encoded_word " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
} else if input [ 0 ] ! = b '=' | | input [ 1 ] ! = b '?' {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " encoded_word(): invalid input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
/* find end of Charset tag:
* = ? charset ? encoding ? encoded text ? =
* - - - - - - - - - ^
* /
let mut tag_end_idx = None ;
for ( idx , b ) in input [ 2 .. ] . iter ( ) . enumerate ( ) {
if * b = = b '?' {
tag_end_idx = Some ( idx + 2 ) ;
2018-07-27 18:01:52 +03:00
break ;
}
}
2020-06-06 19:38:20 +03:00
if tag_end_idx . is_none ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " encoded_word(): expected end tag " ) . into ( ) ,
) ) ;
2018-07-27 18:01:52 +03:00
}
2020-06-06 19:38:20 +03:00
let tag_end_idx = tag_end_idx . unwrap ( ) ;
if tag_end_idx + 2 > = input . len ( ) | | input [ 2 + tag_end_idx ] ! = b '?' {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " encoded_word(): expected valid end tag " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
/* See if input ends with "?=" and get ending index
* = ? charset ? encoding ? encoded text ? =
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ^
* /
let mut encoded_end_idx = None ;
for i in ( 3 + tag_end_idx ) .. input . len ( ) {
if input [ i ] = = b '?' & & i + 1 < input . len ( ) & & input [ i + 1 ] = = b '=' {
encoded_end_idx = Some ( i ) ;
break ;
2018-07-27 18:01:52 +03:00
}
}
2020-06-06 19:38:20 +03:00
if encoded_end_idx . is_none ( ) {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " encoded_word(): expected input after end tag " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
let encoded_end_idx = encoded_end_idx . unwrap ( ) ;
let encoded_text = & input [ 3 + tag_end_idx .. encoded_end_idx ] ;
let s : Vec < u8 > = match input [ tag_end_idx + 1 ] {
b 'b' | b 'B' = > match BASE64_MIME . decode ( encoded_text ) {
Ok ( v ) = > v ,
Err ( _ ) = > encoded_text . to_vec ( ) ,
} ,
b 'q' | b 'Q' = > match quoted_printable_bytes_header ( encoded_text ) {
Ok ( ( b " " , s ) ) = > s ,
2020-06-25 11:47:52 +03:00
_ = > {
return Err ( nom ::Err ::Error (
( input , " encoded_word(): invalid quoted_printable " ) . into ( ) ,
) )
}
2020-06-06 19:38:20 +03:00
} ,
2020-06-25 11:47:52 +03:00
_ = > {
return Err ( nom ::Err ::Error (
( input , " encoded_word(): expected 'b|q' " ) . into ( ) ,
) )
}
2020-06-06 19:38:20 +03:00
} ;
2019-11-28 22:15:32 +02:00
2020-06-06 19:38:20 +03:00
let charset = Charset ::from ( & input [ 2 .. tag_end_idx ] ) ;
2019-11-28 22:15:32 +02:00
2020-06-06 19:38:20 +03:00
if let Charset ::UTF8 = charset {
Ok ( ( & input [ encoded_end_idx + 2 .. ] , s ) )
2018-07-27 18:01:52 +03:00
} else {
2020-06-06 19:38:20 +03:00
match decode_charset ( & s , charset ) {
Ok ( v ) = > Ok ( ( & input [ encoded_end_idx + 2 .. ] , v . into_bytes ( ) ) ) ,
2020-06-25 11:47:52 +03:00
_ = > Err ( nom ::Err ::Error (
(
input ,
format! ( " encoded_word(): unknown charset {:?} " , charset ) ,
)
. into ( ) ,
) ) ,
2020-06-06 19:38:20 +03:00
}
2018-07-27 18:01:52 +03:00
}
}
2020-06-06 19:38:20 +03:00
pub fn decode_charset ( s : & [ u8 ] , charset : Charset ) -> Result < String > {
match charset {
Charset ::UTF8 | Charset ::Ascii = > Ok ( String ::from_utf8_lossy ( s ) . to_string ( ) ) ,
Charset ::ISO8859_1 = > Ok ( ISO_8859_1 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::ISO8859_2 = > Ok ( ISO_8859_2 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::ISO8859_7 = > Ok ( ISO_8859_7 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::ISO8859_15 = > Ok ( ISO_8859_15 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::GBK = > Ok ( GBK . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::Windows1250 = > Ok ( WINDOWS_1250 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::Windows1251 = > Ok ( WINDOWS_1251 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::Windows1252 = > Ok ( WINDOWS_1252 . decode ( s , DecoderTrap ::Strict ) ? ) ,
Charset ::Windows1253 = > Ok ( WINDOWS_1253 . decode ( s , DecoderTrap ::Strict ) ? ) ,
// Unimplemented:
Charset ::GB2312 = > Ok ( String ::from_utf8_lossy ( s ) . to_string ( ) ) ,
Charset ::UTF16 = > Ok ( String ::from_utf8_lossy ( s ) . to_string ( ) ) ,
Charset ::BIG5 = > Ok ( String ::from_utf8_lossy ( s ) . to_string ( ) ) ,
Charset ::ISO2022JP = > Ok ( String ::from_utf8_lossy ( s ) . to_string ( ) ) ,
2018-07-27 18:01:52 +03:00
}
2020-06-06 19:38:20 +03:00
}
fn quoted_printable_soft_break ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
if input . len ( ) < 2 {
2020-07-05 15:28:55 +03:00
Err ( nom ::Err ::Error (
2020-06-25 11:47:52 +03:00
( input , " quoted_printable_soft_break(): found EOF " ) . into ( ) ,
2020-07-05 15:28:55 +03:00
) )
2020-06-06 19:38:20 +03:00
} else if input [ 0 ] = = b '=' & & input [ 1 ] = = b '\n' {
Ok ( ( & input [ 2 .. ] , & input [ 0 .. 2 ] ) ) // `=\n` is an escaped space character.
} else if input . len ( ) > 3 & & input . starts_with ( b " = \r \n " ) {
Ok ( ( & input [ 3 .. ] , & input [ 0 .. 3 ] ) ) // `=\r\n` is an escaped space character.
2018-07-27 18:01:52 +03:00
} else {
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
( input , " quoted_printable_soft_break(): invalid input " ) . into ( ) ,
) )
2018-07-27 18:01:52 +03:00
}
}
2020-06-06 19:38:20 +03:00
pub fn qp_underscore_header ( input : & [ u8 ] ) -> IResult < & [ u8 ] , u8 > {
let ( rest , _ ) = tag ( b " _ " ) ( input ) ? ;
Ok ( ( rest , 0x20 ) )
2018-07-27 18:01:52 +03:00
}
2020-06-06 19:38:20 +03:00
// With MIME, headers in quoted printable format can contain underscores that represent spaces.
// In non-header context, an underscore is just a plain underscore.
pub fn quoted_printable_bytes_header ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < u8 > > {
many0 ( alt ( ( quoted_printable_byte , qp_underscore_header , le_u8 ) ) ) ( input )
2018-07-27 18:01:52 +03:00
}
2020-06-06 19:38:20 +03:00
// For atoms in Header values.
pub fn quoted_printable_bytes ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < u8 > > {
many0 ( alt ( (
preceded ( quoted_printable_soft_break , quoted_printable_byte ) ,
preceded ( quoted_printable_soft_break , le_u8 ) ,
quoted_printable_byte ,
le_u8 ,
) ) ) ( input )
2018-07-27 18:01:52 +03:00
}
2020-06-06 19:38:20 +03:00
pub fn space ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( ) > {
let ( rest , _ ) =
take_while ( | c : u8 | c = = b ' ' | | c = = b '\t' | | c = = b '\r' | | c = = b '\n' ) ( input ) ? ;
Ok ( ( rest , ( ) ) )
//eat_separator!());
}
2017-09-01 15:24:32 +03:00
2020-08-26 00:22:16 +03:00
pub fn encoded_word_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , SmallVec < [ u8 ; 64 ] > > {
2020-06-06 19:38:20 +03:00
let ( input , list ) = separated_nonempty_list ( space , encoded_word ) ( input ) ? ;
let list_len = list . iter ( ) . fold ( 0 , | mut acc , x | {
acc + = x . len ( ) ;
acc
} ) ;
Ok ( (
input ,
list . iter ( )
2020-08-26 00:22:16 +03:00
. fold ( SmallVec ::with_capacity ( list_len ) , | mut acc , x | {
acc . extend ( x . into_iter ( ) . cloned ( ) ) ;
2020-06-06 19:38:20 +03:00
acc
} ) ,
) )
2019-12-12 00:44:47 +02:00
}
2020-08-26 00:22:16 +03:00
pub fn ascii_token ( input : & [ u8 ] ) -> IResult < & [ u8 ] , SmallVec < [ u8 ; 64 ] > > {
2020-06-06 19:38:20 +03:00
let ( input , word ) = alt ( (
terminated ( take_until ( " =? " ) , peek ( preceded ( tag ( b " " ) , encoded_word ) ) ) ,
take_while ( | _ | true ) ,
) ) ( input ) ? ;
2020-08-26 00:22:16 +03:00
Ok ( ( input , SmallVec ::from ( word ) ) )
2020-06-06 19:38:20 +03:00
}
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
pub fn phrase (
input : & [ u8 ] ,
multiline : /* preserve newlines */ bool ,
) -> IResult < & [ u8 ] , Vec < u8 > > {
if input . is_empty ( ) {
return Ok ( ( & [ ] , Vec ::with_capacity ( 0 ) ) ) ;
2017-09-14 18:08:14 +03:00
}
2020-06-06 19:38:20 +03:00
let mut input = input . ltrim ( ) ;
let mut acc : Vec < u8 > = Vec ::new ( ) ;
let mut ptr = 0 ;
2017-09-01 15:24:32 +03:00
2020-06-06 19:38:20 +03:00
while ptr < input . len ( ) {
let mut flag = false ;
// Check if word is encoded.
while let Ok ( ( rest , v ) ) = encoded_word ( & input [ ptr .. ] ) {
flag = true ;
input = rest ;
ptr = 0 ;
acc . extend ( v ) ;
2019-09-25 22:00:30 +03:00
2020-06-06 19:38:20 +03:00
// consume whitespace
while ptr < input . len ( ) & & ( is_whitespace! ( input [ ptr ] ) ) {
ptr + = 1 ;
}
if ptr > = input . len ( ) {
break ;
}
2019-09-25 22:00:30 +03:00
}
2020-06-06 19:38:20 +03:00
if flag & & ptr < input . len ( ) & & ptr ! = 0 {
acc . push ( b ' ' ) ;
}
let end = input [ ptr .. ] . find ( b " =? " ) ;
2019-09-25 22:00:30 +03:00
2020-06-06 19:38:20 +03:00
let end = end . unwrap_or_else ( | | input . len ( ) - ptr ) + ptr ;
let ascii_s = ptr ;
let mut ascii_e = 0 ;
while ptr < end & & ! ( is_whitespace! ( input [ ptr ] ) ) {
ptr + = 1 ;
}
if ! multiline {
ascii_e = ptr ;
2019-09-25 22:00:30 +03:00
}
2020-06-06 19:38:20 +03:00
while ptr < input . len ( ) & & ( is_whitespace! ( input [ ptr ] ) ) {
ptr + = 1 ;
}
if multiline {
ascii_e = ptr ;
}
if ptr > = input . len ( ) {
acc . extend ( ascii_token ( & input [ ascii_s .. ascii_e ] ) ? . 1 ) ;
2019-09-25 22:00:30 +03:00
break ;
}
2020-06-06 19:38:20 +03:00
if ascii_s > = ascii_e {
/* We have the start of an encoded word but not the end, so parse it as ascii */
ascii_e = input [ ascii_s .. ]
. find ( b " " )
. unwrap_or_else ( | | ascii_s + input [ ascii_s .. ] . len ( ) ) ;
ptr = ascii_e ;
}
if ascii_s > = ascii_e {
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " phrase(): start of an encoded word but no end " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
acc . extend ( ascii_token ( & input [ ascii_s .. ascii_e ] ) ? . 1 ) ;
if ptr ! = ascii_e {
acc . push ( b ' ' ) ;
2019-09-25 22:00:30 +03:00
}
}
2020-06-06 19:38:20 +03:00
Ok ( ( & input [ ptr .. ] , acc ) )
2019-09-25 22:00:30 +03:00
}
}
2020-06-06 19:38:20 +03:00
pub mod address {
2020-09-09 14:24:30 +03:00
//! Parsing of address values and address-related headers.
//!
//! Implemented RFCs:
//!
//! - [RFC5322 "Internet Message Format"](https://tools.ietf.org/html/rfc5322)
//! - [RFC6532 "Internationalized Email Headers"](https://tools.ietf.org/html/rfc6532)
//! - [RFC2047 "MIME Part Three: Message Header Extensions for Non-ASCII Text"](https://tools.ietf.org/html/rfc2047)
2020-06-06 19:38:20 +03:00
use super ::* ;
use crate ::email ::address ::* ;
2020-09-03 14:44:03 +03:00
use crate ::email ::parser ::generic ::{
atom , cfws , dot_atom , dot_atom_text , dtext , phrase2 , quoted_string ,
} ;
2020-06-06 19:38:20 +03:00
pub fn display_addr ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
if input . is_empty ( ) | | input . len ( ) < 3 {
2020-07-05 15:28:55 +03:00
Err ( nom ::Err ::Error ( ( input , " display_addr(): EOF " ) . into ( ) ) )
2020-06-06 19:38:20 +03:00
} else if ! is_whitespace! ( input [ 0 ] ) {
let mut display_name = StrBuilder {
offset : 0 ,
length : 0 ,
} ;
let mut flag = false ;
for ( i , b ) in input [ 0 .. ] . iter ( ) . enumerate ( ) {
if * b = = b '<' {
display_name . length = i . saturating_sub ( 1 ) ; // if i != 0 { i - 1 } else { 0 };
flag = true ;
break ;
}
}
if ! flag {
let ( rest , output ) = match super ::encodings ::phrase ( input , false ) {
Ok ( v ) = > v ,
2020-06-25 11:47:52 +03:00
_ = > {
return Err ( nom ::Err ::Error (
( input , " display_addr(): no '<' found " ) . into ( ) ,
) )
}
2020-06-06 19:38:20 +03:00
} ;
if output . contains ( & b '<' ) {
let ( _ , address ) = match display_addr ( & output ) {
Ok ( v ) = > v ,
2020-06-25 11:47:52 +03:00
_ = > {
return Err ( nom ::Err ::Error (
( input , " display_addr(): invalid input " ) . into ( ) ,
) )
}
2020-06-06 19:38:20 +03:00
} ;
return Ok ( ( rest , address ) ) ;
}
2020-06-25 11:47:52 +03:00
return Err ( nom ::Err ::Error (
( input , " display_addr(): invalid input " ) . into ( ) ,
) ) ;
2020-06-06 19:38:20 +03:00
}
let mut end = input . len ( ) ;
let mut at_flag = false ;
let mut flag = false ;
for ( i , b ) in input [ display_name . length + 2 .. ] . iter ( ) . enumerate ( ) {
match * b {
b '@' = > at_flag = true ,
b '>' = > {
end = i ;
flag = true ;
break ;
}
_ = > { }
}
}
if at_flag & & flag {
let ( _ , raw ) =
super ::encodings ::phrase ( & input [ 0 .. end + display_name . length + 3 ] , false ) ? ;
let display_name_end = raw . find ( b " < " ) . unwrap ( ) ;
display_name . length = raw [ 0 .. display_name_end ] . trim ( ) . len ( ) ;
let address_spec = if display_name_end = = 0 {
StrBuilder {
offset : 1 ,
length : end + 1 ,
}
} else {
StrBuilder {
offset : display_name_end + 1 ,
length : end ,
}
} ;
2018-08-17 23:07:29 +03:00
2020-06-06 19:38:20 +03:00
if display_name . display ( & raw ) . as_bytes ( ) . is_quoted ( ) {
display_name . offset + = 1 ;
display_name . length - = 2 ;
}
let rest_start = if input . len ( ) > end + display_name . length + 2 {
end + display_name . length + 3
} else {
end + display_name . length + 2
} ;
Ok ( (
input . get ( rest_start .. ) . unwrap_or_default ( ) ,
Address ::Mailbox ( MailboxAddress {
raw ,
display_name ,
address_spec ,
} ) ,
) )
2019-09-25 22:00:30 +03:00
} else {
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
( input , " display_addr(): did not find both '@' and '>' " ) . into ( ) ,
) )
2018-08-17 23:07:29 +03:00
}
2020-06-06 19:38:20 +03:00
} else {
2020-06-25 11:47:52 +03:00
Err ( nom ::Err ::Error (
( input , " display_addr(): unexpected whitespace " ) . into ( ) ,
) )
2018-08-17 23:07:29 +03:00
}
}
2020-06-06 19:38:20 +03:00
2020-09-03 12:09:29 +03:00
///`angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr`
2020-09-09 14:24:30 +03:00
pub fn angle_addr ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
2020-09-03 12:09:29 +03:00
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
let ( input , _ ) = tag ( " < " ) ( input ) ? ;
let ( input , addr_spec ) = addr_spec ( input ) ? ;
let ( input , _ ) = tag ( " > " ) ( input ) ? ;
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
Ok ( ( input , addr_spec ) )
}
2020-09-09 14:24:30 +03:00
///`obs-domain = atom *("." atom)`
pub fn obs_domain ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let ( mut input , atom_ ) = context ( " obs_domain " , atom ) ( input ) ? ;
let mut ret : Vec < u8 > = atom_ . into ( ) ;
loop {
if ! input . starts_with ( b " . " ) {
break ;
}
ret . push ( b '.' ) ;
input = & input [ 1 .. ] ;
if let Ok ( ( _input , atom_ ) ) = context ( " obs_domain " , atom ) ( input ) {
ret . extend_from_slice ( & atom_ ) ;
input = _input ;
} else {
return Err ( nom ::Err ::Error (
( input , " obs_domain(): expected <atom> after DOT " ) . into ( ) ,
) ) ;
2020-09-03 12:09:29 +03:00
}
}
2020-09-09 14:24:30 +03:00
Ok ( ( input , ret . into ( ) ) )
}
2020-09-03 12:09:29 +03:00
2020-09-09 14:24:30 +03:00
///`local-part = dot-atom / quoted-string / obs-local-part`
pub fn local_part ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
alt ( ( dot_atom , quoted_string ) ) ( input )
}
2020-09-03 12:09:29 +03:00
2020-09-09 14:24:30 +03:00
///`domain = dot-atom / domain-literal / obs-domain`
pub fn domain ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
alt ( ( dot_atom , domain_literal , obs_domain ) ) ( input )
}
2020-09-03 12:09:29 +03:00
2020-09-09 14:24:30 +03:00
///`domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]`
pub fn domain_literal ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
use crate ::email ::parser ::generic ::fws ;
let ( input , first_opt_space ) = context ( " domain_literal() " , opt ( cfws ) ) ( input ) ? ;
let ( input , _ ) = context ( " domain_literal() " , tag ( " [ " ) ) ( input ) ? ;
let ( input , dtexts ) = many0 ( pair ( opt ( fws ) , dtext ) ) ( input ) ? ;
let ( input , end_fws ) : ( _ , Option < _ > ) = context ( " domain_literal() " , opt ( fws ) ) ( input ) ? ;
let ( input , _ ) = context ( " domain_literal() " , tag ( " ] " ) ) ( input ) ? ;
let ( input , _ ) = context ( " domain_literal() " , opt ( cfws ) ) ( input ) ? ;
let mut ret_s = vec! [ b '[' ] ;
if let Some ( first_opt_space ) = first_opt_space {
ret_s . extend_from_slice ( & first_opt_space ) ;
}
for ( fws_opt , dtext ) in dtexts {
if let Some ( fws_opt ) = fws_opt {
ret_s . extend_from_slice ( & fws_opt ) ;
2020-09-03 12:09:29 +03:00
}
2020-09-09 14:24:30 +03:00
ret_s . push ( dtext ) ;
}
if let Some ( end_fws ) = end_fws {
ret_s . extend_from_slice ( & end_fws ) ;
2020-09-03 12:09:29 +03:00
}
2020-09-09 14:24:30 +03:00
ret_s . push ( b ']' ) ;
Ok ( ( input , ret_s . into ( ) ) )
}
2020-09-03 12:09:29 +03:00
2020-09-09 14:24:30 +03:00
///`addr-spec = local-part "@" domain`
pub fn addr_spec ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
2020-09-03 12:09:29 +03:00
let ( input , local_part ) = context ( " addr_spec() " , local_part ) ( input ) ? ;
let ( input , _ ) = context ( " addr_spec() " , tag ( " @ " ) ) ( input ) ? ;
let ( input , domain ) = context ( " addr_spec() " , domain ) ( input ) ? ;
Ok ( (
input ,
Address ::new (
None ,
format! ( " {} @ {} " , to_str! ( & local_part ) , to_str! ( & domain ) ) ,
) ,
) )
}
2020-09-09 14:24:30 +03:00
///Returns the raw `local_part` and `domain` parts.
///
///`addr-spec = local-part "@" domain`
pub fn addr_spec_raw ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ( Cow < '_ , [ u8 ] > , Cow < '_ , [ u8 ] > ) > {
let ( input , local_part ) = context ( " addr_spec() " , local_part ) ( input ) ? ;
let ( input , _ ) = context ( " addr_spec() " , tag ( " @ " ) ) ( input ) ? ;
let ( input , domain ) = context ( " addr_spec() " , domain ) ( input ) ? ;
Ok ( ( input , ( local_part , domain ) ) )
}
2020-09-03 12:09:29 +03:00
///`display-name = phrase`
pub fn display_name ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < u8 > > {
2020-09-03 14:44:03 +03:00
let ( rest , ret ) = phrase2 ( input ) ? ;
2020-09-03 12:09:29 +03:00
if let Ok ( ( _ , ret ) ) = crate ::email ::parser ::encodings ::phrase ( & ret , true ) {
Ok ( ( rest , ret ) )
2018-08-17 23:07:29 +03:00
} else {
2020-09-03 12:09:29 +03:00
Ok ( ( rest , ret ) )
2018-08-17 23:07:29 +03:00
}
}
2017-09-05 16:41:29 +03:00
2020-09-03 12:09:29 +03:00
///`name-addr = [display-name] angle-addr`
pub fn name_addr ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
let ( input , ( display_name , angle_addr ) ) = alt ( (
pair ( map ( display_name , | s | Some ( s ) ) , angle_addr ) ,
map ( angle_addr , | r | ( None , r ) ) ,
) ) ( input ) ? ;
Ok ( (
input ,
Address ::new (
display_name . map ( | v | to_str! ( & v ) . to_string ( ) ) ,
angle_addr . get_email ( ) ,
) ,
) )
2018-08-15 00:48:31 +03:00
}
2020-09-03 12:09:29 +03:00
///`mailbox = name-addr / addr-spec`
pub fn mailbox ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
alt ( ( addr_spec , name_addr ) ) ( input )
2020-06-06 19:38:20 +03:00
}
2018-08-15 00:48:31 +03:00
2020-09-03 12:09:29 +03:00
///`group-list = mailbox-list / CFWS / obs-group-list`
pub fn group_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < Address > > {
///`mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list`
fn mailbox_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < Address > > {
let ( mut input , first_m ) = mailbox ( input ) ? ;
let mut ret = vec! [ first_m ] ;
loop {
if ! input . starts_with ( b " , " ) {
break ;
}
input = & input [ 1 .. ] ;
let ( input_ , next_m ) = mailbox ( input ) ? ;
ret . push ( next_m ) ;
input = input_ ;
2018-08-15 00:48:31 +03:00
}
2020-09-03 12:09:29 +03:00
Ok ( ( input , ret ) )
2018-08-15 00:48:31 +03:00
}
2020-09-03 12:09:29 +03:00
if let Ok ( ( input , mailboxes ) ) = mailbox_list ( input ) {
Ok ( ( input , mailboxes ) )
} else {
let ( input , _ ) = cfws ( input ) ? ;
Ok ( ( input , vec! [ ] ) )
2020-05-06 18:47:37 +03:00
}
2020-09-03 12:09:29 +03:00
}
2018-09-15 20:05:34 +03:00
2020-09-03 12:09:29 +03:00
///`group = display-name ":" [group-list] ";" [CFWS]`
fn group ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
let ( input , display_name ) = context ( " group() " , display_name ) ( input ) ? ;
let ( input , _ ) = context ( " group() " , tag ( " : " ) ) ( input ) ? ;
let ( input , group_list ) : ( _ , Option < Vec < Address > > ) =
context ( " group() " , opt ( group_list ) ) ( input ) ? ;
let ( input , _ ) = context ( " group() " , tag ( " ; " ) ) ( input ) ? ;
let ( input , _ ) = context ( " group() " , opt ( cfws ) ) ( input ) ? ;
2020-06-06 19:38:20 +03:00
Ok ( (
2020-09-03 12:09:29 +03:00
input ,
Address ::new_group (
to_str! ( & display_name ) . to_string ( ) ,
group_list . unwrap_or_default ( ) ,
) ,
2020-06-06 19:38:20 +03:00
) )
2018-08-15 13:41:48 +03:00
}
2019-06-18 21:58:55 +03:00
2020-09-03 12:09:29 +03:00
///```text
///address = mailbox / group
///```
2020-06-06 19:38:20 +03:00
pub fn address ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Address > {
2020-09-03 12:09:29 +03:00
alt ( ( mailbox , group ) ) ( input )
2019-06-18 21:58:55 +03:00
}
2020-07-26 16:08:22 +03:00
pub fn rfc2822address_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , SmallVec < [ Address ; 1 ] > > {
2020-09-03 12:09:29 +03:00
separated_list_smallvec ( is_a ( " , " ) , address ) ( input . ltrim ( ) )
2020-06-06 19:38:20 +03:00
// ws!( separated_list!(is_a!(","), address))
}
2019-06-18 21:58:55 +03:00
2020-06-06 19:38:20 +03:00
pub fn address_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , String > {
let ( input , list ) = alt ( (
super ::encodings ::encoded_word_list ,
super ::encodings ::ascii_token ,
) ) ( input ) ? ;
let list : Vec < & [ u8 ] > = list . split ( | c | * c = = b ',' ) . collect ( ) ;
let string_len = list . iter ( ) . fold ( 0 , | mut acc , x | {
acc + = x . trim ( ) . len ( ) ;
acc
} ) + list . len ( )
- 1 ;
let list_len = list . len ( ) ;
let mut i = 0 ;
Ok ( (
input ,
list . iter ( )
. fold ( String ::with_capacity ( string_len ) , | acc , x | {
let mut acc = acc
+ & String ::from_utf8_lossy (
x . replace ( b " \n " , b " " )
. replace ( b " \r " , b " " )
. replace ( b " \t " , b " " )
. trim ( ) ,
) ;
if i ! = list_len - 1 {
acc . push_str ( " " ) ;
i + = 1 ;
}
acc
} ) ,
) )
}
2019-06-18 21:58:55 +03:00
2020-09-03 14:44:03 +03:00
///`msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS]`
pub fn msg_id ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MessageID > {
///`no-fold-literal = "[" *dtext "]"`
pub fn no_fold_literal ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
let orig_input = input ;
let ( input , _ ) = tag ( " [ " ) ( input ) ? ;
let ( input , ret ) = many0 ( dtext ) ( input ) ? ;
let ( input , _ ) = tag ( " ] " ) ( input ) ? ;
Ok ( ( input , Cow ::Borrowed ( & orig_input [ 0 .. ret . len ( ) + 1 ] ) ) )
}
2019-06-18 21:58:55 +03:00
2020-09-03 14:44:03 +03:00
///`id-left = dot-atom-text / obs-id-left`
pub fn id_left ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
dot_atom_text ( input )
}
///`id-right = dot-atom-text / no-fold-literal / obs-id-right`
pub fn id_right ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Cow < '_ , [ u8 ] > > {
alt ( ( dot_atom_text , no_fold_literal ) ) ( input )
2019-06-18 21:58:55 +03:00
}
2020-09-03 14:44:03 +03:00
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
let orig_input = input ;
let ( input , _ ) = tag ( " < " ) ( input ) ? ;
let ( input , id_left_ ) = id_left ( input ) ? ;
let ( input , _ ) = tag ( " @ " ) ( input ) ? ;
let ( input , id_right_ ) = id_right ( input ) ? ;
let ( input , _ ) = tag ( " > " ) ( input ) ? ;
let ( input , _ ) = opt ( cfws ) ( input ) ? ;
Ok ( (
input ,
MessageID ::new (
& orig_input [ .. 3 + id_left_ . len ( ) + id_right_ . len ( ) ] ,
& orig_input [ 1 .. 2 + id_left_ . len ( ) + id_right_ . len ( ) ] ,
) ,
) )
2019-06-18 21:58:55 +03:00
}
2019-09-25 22:00:30 +03:00
2020-09-03 14:44:03 +03:00
pub fn msg_id_list ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < MessageID > > {
many0 ( msg_id ) ( input )
2019-09-25 22:00:30 +03:00
}
2020-07-26 16:08:22 +03:00
use smallvec ::SmallVec ;
pub fn separated_list_smallvec < I , O , Sep , E , F , G > (
sep : G ,
f : F ,
) -> impl FnMut ( I ) -> IResult < I , SmallVec < [ O ; 1 ] > , E >
where
I : Clone + PartialEq ,
F : Fn ( I ) -> IResult < I , O , E > ,
G : Fn ( I ) -> IResult < I , Sep , E > ,
E : nom ::error ::ParseError < I > ,
{
move | i : I | {
let mut res = SmallVec ::new ( ) ;
2020-08-25 16:39:12 +03:00
let mut i = i ;
2020-07-26 16:08:22 +03:00
// Parse the first element
match f ( i . clone ( ) ) {
Err ( e ) = > return Err ( e ) ,
Ok ( ( i1 , o ) ) = > {
if i1 = = i {
return Err ( nom ::Err ::Error ( E ::from_error_kind (
i1 ,
ErrorKind ::SeparatedList ,
) ) ) ;
}
res . push ( o ) ;
i = i1 ;
}
}
loop {
match sep ( i . clone ( ) ) {
Err ( nom ::Err ::Error ( _ ) ) = > return Ok ( ( i , res ) ) ,
Err ( e ) = > return Err ( e ) ,
Ok ( ( i1 , _ ) ) = > {
if i1 = = i {
return Err ( nom ::Err ::Error ( E ::from_error_kind (
i1 ,
ErrorKind ::SeparatedList ,
) ) ) ;
}
match f ( i1 . clone ( ) ) {
Err ( nom ::Err ::Error ( _ ) ) = > return Ok ( ( i , res ) ) ,
Err ( e ) = > return Err ( e ) ,
Ok ( ( i2 , o ) ) = > {
if i2 = = i {
return Err ( nom ::Err ::Error ( E ::from_error_kind (
i2 ,
ErrorKind ::SeparatedList ,
) ) ) ;
}
res . push ( o ) ;
i = i2 ;
}
}
}
}
}
}
}
2019-09-25 22:00:30 +03:00
}
2018-08-14 00:13:08 +03:00
#[ cfg(test) ]
mod tests {
2020-06-06 19:38:20 +03:00
use super ::{ address ::* , encodings ::* , generic ::* , * } ;
use crate ::email ::address ::* ;
2019-12-17 14:24:45 +02:00
use crate ::make_address ;
2018-08-14 00:13:08 +03:00
2018-08-17 13:21:24 +03:00
#[ test ]
2020-02-08 13:44:53 +02:00
fn test_phrase ( ) {
2018-08-17 13:21:24 +03:00
let words = b " =?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
2018-08-15 13:41:48 +03:00
= ? iso - 8859 - 7 ? B ? INb18 + nq3l0gzd3hIMHt4erv3 + 358 + c6IMzF0c / TIMHQz9TFy8XTzMHU ? =
= ? iso - 8859 - 7 ? B ? 2 c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw = = ? = " ;
2020-06-06 19:38:20 +03:00
assert_eq! ( " [mycourses.ntua.gr - Κυματική και Κβαντική Φυσική] Νέα Ανακοίνωση: ΜΕΡΟΣ ΑΠΟΤΕΛΕΣΜΑΤΩΝ Β Τ Μ . Κ Α Ν . ΕΞΕΤΑΣΗΣ 2017-18: ΣΗΜΕΙΩΣΗ " , std ::str ::from_utf8 ( & phrase ( words . trim ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( ) ) ;
2018-08-17 13:21:24 +03:00
let words = b " =?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?= " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" Πρόσθετη εξεταστική " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . trim ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2018-08-17 13:21:24 +03:00
) ;
let words = b " [Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?= \n \t =?utf-8?b?dXNoIM67z4zOs8+JIG1pc3ByZWRpY3Rpb24gzrrOsc+Ezqwgz4TOt869?= \n \t =?utf-8?b?IM61zrrPhM6tzrvOtc+Dzrcgc3RvcmU=?= " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" [Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . trim ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2018-08-17 13:21:24 +03:00
) ;
let words = b " Re: [Advcomparch] =?utf-8?b?zqPPhc68z4DOtc+BzrnPhs6/z4HOrCDPg861IGZs?=
2018-08-14 19:19:01 +03:00
= ? utf - 8 ? b ? dXNoIM67z4zOs8 + JIG1pc3ByZWRpY3Rpb24gzrrOsc + Ezqwgz4TOt869 ? =
2018-08-15 00:48:31 +03:00
= ? utf - 8 ? b ? IM61zrrPhM6tzrvOtc + Dzrcgc3RvcmU = ? = " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" Re: [Advcomparch] Συμπεριφορά σε flush λόγω misprediction κατά την εκτέλεση store " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . trim ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2018-08-17 13:21:24 +03:00
) ;
let words = b " sdf " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" sdf " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words , false ) . unwrap ( ) . 1 ) . unwrap ( )
2019-12-12 00:44:47 +02:00
) ;
2018-08-17 13:21:24 +03:00
let words = b " =?iso-8859-7?b?U2VnIGZhdWx0IPP05+0g5er03evl8+cg9O/1?= =?iso-8859-7?q?_example_ru_n_=5Fsniper?= " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" Seg fault στην εκτέλεση του example ru n _sniper " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words , false ) . unwrap ( ) . 1 ) . unwrap ( )
2018-08-17 13:21:24 +03:00
) ;
let words = b " Re: [Advcomparch]
2018-08-14 00:13:08 +03:00
= ? iso - 8859 - 7 ? b ? U2VnIGZhdWx0IPP05 + 0 g5er03evl8 + cg9O / 1 ? =
= ? iso - 8859 - 7 ? q ? _example_ru_n_ = 5 Fsniper ? = " ;
2019-12-12 00:44:47 +02:00
assert_eq! (
" Re: [Advcomparch] Seg fault στην εκτέλεση του example ru n _sniper " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words , false ) . unwrap ( ) . 1 ) . unwrap ( )
2019-12-12 00:44:47 +02:00
) ;
let words = r #" [internal] =?UTF-8?B?zp3Orc6/z4Igzp/OtM63zrPPjM+CIM6jz4XOs86zz4E=?=
= ? UTF - 8 ? B ? zrHPhs6uz4I = ? = " #;
assert_eq! (
" [internal] Νέος Οδηγός Συγγραφής " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . as_bytes ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2018-08-17 13:21:24 +03:00
) ;
2020-01-11 13:10:41 +02:00
let words = r #" =?UTF-8?Q?Re=3a_Climate_crisis_reality_check_=e2=80=93=c2=a0EcoHust?=
= ? UTF - 8 ? Q ? ler ? = " #;
assert_eq! (
" Re: Climate crisis reality check – \u{a0} EcoHustler " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . as_bytes ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2020-01-11 13:10:41 +02:00
) ;
let words = r #" Re: Climate crisis reality check =?windows-1250?B?lqBFY29IdXN0?=
= ? windows - 1250 ? B ? bGVy ? = " #;
assert_eq! (
" Re: Climate crisis reality check – \u{a0} EcoHustler " ,
2020-06-06 19:38:20 +03:00
std ::str ::from_utf8 ( & phrase ( words . as_bytes ( ) , false ) . unwrap ( ) . 1 ) . unwrap ( )
2020-01-11 13:10:41 +02:00
) ;
2018-08-17 13:21:24 +03:00
}
2018-08-14 00:13:08 +03:00
2018-08-17 13:21:24 +03:00
#[ test ]
2019-05-08 18:00:35 +03:00
fn test_address_list ( ) {
2018-08-17 13:21:24 +03:00
let s = b " Obit Oppidum <user@domain>,
2018-08-14 00:13:08 +03:00
list < list @ domain . tld > , list2 < list2 @ domain . tld > ,
2019-05-08 18:00:35 +03:00
Bobit Boppidum < user @ otherdomain . com > , Cobit Coppidum < user2 @ otherdomain . com > , < user @ domain . tld > " ;
assert_eq! (
(
& s [ 0 .. 0 ] ,
2020-07-26 16:08:22 +03:00
smallvec ::smallvec! [
2019-05-08 18:00:35 +03:00
make_address! ( " Obit Oppidum " , " user@domain " ) ,
make_address! ( " list " , " list@domain.tld " ) ,
make_address! ( " list2 " , " list2@domain.tld " ) ,
make_address! ( " Bobit Boppidum " , " user@otherdomain.com " ) ,
make_address! ( " Cobit Coppidum " , " user2@otherdomain.com " ) ,
make_address! ( " " , " user@domain.tld " )
]
) ,
rfc2822address_list ( s ) . unwrap ( )
) ;
2018-08-17 13:21:24 +03:00
}
2018-08-14 00:13:08 +03:00
2018-08-17 13:21:24 +03:00
#[ test ]
fn test_date ( ) {
let s = b " Thu, 31 Aug 2017 13:43:37 +0000 (UTC) " ;
let _s = b " Thu, 31 Aug 2017 13:43:37 +0000 " ;
let __s = b " =?utf-8?q?Thu=2C_31_Aug_2017_13=3A43=3A37_-0000?= " ;
2019-05-01 19:20:33 +03:00
debug! ( " {:?}, {:?} " , date ( s ) , date ( _s ) ) ;
debug! ( " {:?} " , date ( __s ) ) ;
2018-08-17 13:21:24 +03:00
assert_eq! ( date ( s ) . unwrap ( ) , date ( _s ) . unwrap ( ) ) ;
assert_eq! ( date ( _s ) . unwrap ( ) , date ( __s ) . unwrap ( ) ) ;
2020-02-08 13:44:53 +02:00
let val = b " Fri, 23 Dec 0001 21:20:36 -0800 (PST) " ;
assert_eq! ( date ( val ) . unwrap ( ) , 0 ) ;
2018-08-17 13:21:24 +03:00
}
2020-02-08 13:44:53 +02:00
2018-08-17 13:21:24 +03:00
#[ test ]
fn test_attachments ( ) {
2019-05-08 18:00:35 +03:00
//FIXME: add file
return ;
2019-05-13 22:05:00 +03:00
/*
2020-09-03 12:09:29 +03:00
use std ::io ::Read ;
let mut buffer : Vec < u8 > = Vec ::new ( ) ;
let _ = std ::fs ::File ::open ( " " ) . unwrap ( ) . read_to_end ( & mut buffer ) ;
let boundary = b " b1_4382d284f0c601a737bb32aaeda53160 " ;
let ( _ , body ) = match mail ( & buffer ) {
Ok ( v ) = > v ,
Err ( _ ) = > panic! ( ) ,
} ;
let attachments = parts ( body , boundary ) . unwrap ( ) . 1 ;
assert_eq! ( attachments . len ( ) , 4 ) ;
let v : Vec < & str > = attachments
. iter ( )
. map ( | v | std ::str ::from_utf8 ( v ) . unwrap ( ) )
. collect ( ) ;
//println!("attachments {:?}", v);
* /
2018-08-15 13:41:48 +03:00
}
2018-08-17 13:21:24 +03:00
#[ test ]
fn test_addresses ( ) {
2020-09-03 12:09:29 +03:00
macro_rules ! assert_parse {
( $name :literal , $addr :literal , $raw :literal ) = > { {
let s = $raw . as_bytes ( ) ;
let r = address ( s ) . unwrap ( ) . 1 ;
match r {
Address ::Mailbox ( ref m ) = > {
assert_eq! ( to_str! ( m . display_name . display_bytes ( & m . raw ) ) , $name ) ;
assert_eq! ( to_str! ( m . address_spec . display_bytes ( & m . raw ) ) , $addr ) ;
}
_ = > assert! ( false ) ,
}
} } ;
2018-08-14 00:13:08 +03:00
}
2020-09-03 12:09:29 +03:00
assert_parse! (
" Σταύρος Μαλτέζος " ,
" maltezos@central.ntua.gr " ,
" =?iso-8859-7?B?0/Th/fHv8iDM4ev03ebv8g==?= <maltezos@central.ntua.gr> "
) ;
assert_parse! ( " " , " user@domain " , " user@domain " ) ;
assert_parse! ( " " , " user@domain " , " <user@domain> " ) ;
assert_parse! ( " " , " user@domain " , " <user@domain> " ) ;
assert_parse! ( " Name " , " user@domain " , " Name <user@domain> " ) ;
assert_parse! (
" " ,
" julia@ficdep.minitrue " ,
" julia(outer party)@ficdep.minitrue "
) ;
assert_parse! (
" Winston Smith " ,
" winston.smith@recdep.minitrue " ,
" \" Winston Smith \" <winston.smith@recdep.minitrue> (Records Department) "
) ;
assert_parse! (
" John Q. Public " ,
" JQB@bar.com " ,
" \" John Q. Public \" <JQB@bar.com> "
) ;
assert_parse! (
" John Q. Public " ,
" JQB@bar.com " ,
" John \" Q. \" Public <JQB@bar.com> "
) ;
assert_parse! (
" John Q. Public " ,
" JQB@bar.com " ,
" \" John Q. \" Public <JQB@bar.com> "
) ;
assert_parse! (
" John Q. Public " ,
" JQB@bar.com " ,
" John \" Q. Public \" <JQB@bar.com> "
) ;
assert_parse! (
" Jeffrey Stedfast " ,
" fejj@helixcode.com " ,
" Jeffrey Stedfast <fejj@helixcode.com> "
) ;
assert_parse! (
" this is \t a folded name " ,
" folded@name.com " ,
" this is \n \t a folded name <folded@name.com> "
) ;
assert_parse! (
" Jeffrey fejj Stedfast " ,
" fejj@helixcode.com " ,
" Jeffrey fejj Stedfast <fejj@helixcode.com> "
) ;
assert_parse! (
" Jeffrey fejj Stedfast " ,
" fejj@helixcode.com " ,
" Jeffrey \" fejj \" Stedfast <fejj@helixcode.com> "
) ;
assert_parse! (
" Jeffrey \" fejj \" Stedfast " ,
" fejj@helixcode.com " ,
" \" Jeffrey \\ \" fejj \\ \" Stedfast \" <fejj@helixcode.com> "
) ;
assert_parse! (
" Stedfast, Jeffrey " ,
" fejj@helixcode.com " ,
" \" Stedfast, Jeffrey \" <fejj@helixcode.com> "
) ;
assert_parse! (
" " ,
" fejj@helixcode.com " ,
" fejj@helixcode.com (Jeffrey Stedfast) "
) ;
assert_parse! (
" Jeffrey Stedfast " ,
" fejj@helixcode.com " ,
" Jeffrey Stedfast <fejj(nonrecursive block)@helixcode.(and a comment here)com> "
) ;
assert_parse! (
" Jeffrey Stedfast " ,
" fejj@helixcode.com " ,
" Jeffrey Stedfast <fejj(recursive (comment) block)@helixcode.(and a comment here)com> "
) ;
assert_parse! (
" Joe Q. Public " ,
" john.q.public@example.com " ,
" \" Joe Q. Public \" <john.q.public@example.com> "
) ;
assert_parse! ( " Mary Smith " , " mary@x.test " , " Mary Smith <mary@x.test> " ) ;
assert_parse! ( " Mary Smith " , " mary@x.test " , " Mary Smith <mary@x.test> " ) ;
assert_parse! ( " " , " jdoe@example.org " , " jdoe@example.org " ) ;
assert_parse! ( " Who? " , " one@y.test " , " Who? <one@y.test> " ) ;
assert_parse! ( " " , " boss@nil.test " , " <boss@nil.test> " ) ;
assert_parse! (
" Giant; \" Big \" Box " ,
" sysservices@example.net " ,
r # ""Giant; \"Big\" Box" <sysservices@example.net>"#
) ;
//assert_eq!(
// make_address!("Jeffrey Stedfast", "fejj@helixcode.com"),
// address(b"Jeffrey Stedfast <fejj@helixcode.com.>")
// .unwrap()
// .1
//);
assert_parse! (
" John <middle> Doe " ,
" jdoe@machine.example " ,
" \" John <middle> Doe \" <jdoe@machine.example> "
) ;
// RFC 2047 "Q"-encoded ISO-8859-1 address.
assert_parse! (
" Jörg Doe " ,
" joerg@example.com " ,
" =?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com> "
) ;
// RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal.
assert_parse! (
" Jorg Doe " ,
" joerg@example.com " ,
" =?us-ascii?q?J=6Frg_Doe?= <joerg@example.com> "
) ;
// RFC 2047 "Q"-encoded UTF-8 address.
assert_parse! (
" Jörg Doe " ,
" joerg@example.com " ,
" =?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com> "
) ;
// RFC 2047 "Q"-encoded UTF-8 address with multiple encoded-words.
assert_parse! (
" JörgDoe " ,
" joerg@example.com " ,
" =?utf-8?q?J=C3=B6rg?= =?utf-8?q?Doe?= <joerg@example.com> "
) ;
assert_parse! (
" André Pirard " ,
" PIRARD@vm1.ulg.ac.be " ,
" =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be> "
) ;
// Custom example of RFC 2047 "B"-encoded ISO-8859-1 address.
assert_parse! (
" Jörg " ,
" joerg@example.com " ,
" =?ISO-8859-1?B?SvZyZw==?= <joerg@example.com> "
) ;
// Custom example of RFC 2047 "B"-encoded UTF-8 address.
assert_parse! (
" Jörg " ,
" joerg@example.com " ,
" =?UTF-8?B?SsO2cmc=?= <joerg@example.com> "
) ;
// Custom example with "." in name. For issue 4938
//assert_parse!(
// "Asem H.",
// "noreply@example.com",
// "Asem H. <noreply@example.com>"
//);
assert_parse! (
// RFC 6532 3.2.3, qtext /= UTF8-non-ascii
" Gø Pher " ,
" gopher@example.com " ,
" \" Gø Pher \" <gopher@example.com> "
) ;
// RFC 6532 3.2, atext /= UTF8-non-ascii
assert_parse! ( " µ " , " micro@example.com " , " µ <micro@example.com> " ) ;
// RFC 6532 3.2.2, local address parts allow UTF-8
//assert_parse!("Micro", "µ@example.com", "Micro <µ@example.com>");
// RFC 6532 3.2.4, domains parts allow UTF-8
//assert_parse!(
// "Micro",
// "micro@µ.example.com",
// "Micro <micro@µ.example.com>"
//);
// Issue 14866
assert_parse! (
" " ,
" emptystring@example.com " ,
" \" \" <emptystring@example.com> "
) ;
// CFWS
assert_parse! (
" " ,
" cfws@example.com " ,
" <cfws@example.com> (CFWS (cfws)) (another comment) "
) ;
//"<cfws@example.com> () (another comment), <cfws2@example.com> (another)"
assert_parse! (
" Kristoffer Brånemyr " ,
" ztion@swipenet.se " ,
" =?iso-8859-1?q?Kristoffer_Br=E5nemyr?= <ztion@swipenet.se> "
) ;
assert_parse! (
" François Pons " ,
" fpons@mandrakesoft.com " ,
" =?iso-8859-1?q?Fran=E7ois?= Pons <fpons@mandrakesoft.com> "
) ;
assert_parse! (
" هل تتكلم اللغة الإنجليزية /العربية؟ " , " do.you.speak@arabic.com " ,
" =?utf-8?b?2YfZhCDYqtiq2YPZhNmFINin2YTZhNi62Kkg2KfZhNil2YbYrNmE2YrYstmK2Kk=?= \n =?utf-8?b?IC/Yp9mE2LnYsdio2YrYqdif?= <do.you.speak@arabic.com> "
) ;
assert_parse! (
" 狂ったこの世で狂うなら気は確かだ。 " , " famous@quotes.ja " ,
" =?utf-8?b?54uC44Gj44Gf44GT44Gu5LiW44Gn54uC44GG44Gq44KJ5rCX44Gv56K644GL44Gg?= \n =?utf-8?b?44CC?= <famous@quotes.ja> "
) ;
assert_eq! (
Address ::new_group (
" A Group " . to_string ( ) ,
vec! [
make_address! ( " Ed Jones " , " c@a.test " ) ,
make_address! ( " " , " joe@where.test " ) ,
make_address! ( " John " , " jdoe@one.test " )
]
) ,
address ( b " A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>; " )
. unwrap ( )
. 1
) ;
assert_eq! (
Address ::new_group ( " Undisclosed recipients " . to_string ( ) , vec! [ ] ) ,
address ( b " Undisclosed recipients:; " ) . unwrap ( ) . 1
) ;
assert_parse! (
" 狂ったこの世で狂うなら気は確かだ。 " ,
" famous@quotes.ja " ,
" 狂ったこの世で狂うなら気は確かだ。 <famous@quotes.ja> "
) ;
2018-08-14 00:13:08 +03:00
}
2019-12-11 15:10:59 +02:00
#[ test ]
fn test_quoted_printable ( ) {
let input = r #" <=21-- SEPARATOR -->
< tr >
< td style = 3 D = 22 padding - left : 10 px ; padding - right : 10 px ; background - color :=
= 23 f3f5fa ; = 22 >
< table width = 3 D = 22100 % = 22 cellspacing = 3 D = 220 = 22 cellpadding = 3 D = 220 = 22 =
border = 3 D = 220 = 22 >
< tr >
< td style = 3 D = 22 height :5 px ; background - color : = 23 f3f5fa ; = 22 > & nbsp ; < / td >
< / tr >
< / table >
< / td >
< / tr > " #;
assert_eq! (
quoted_printable_bytes ( input . as_bytes ( ) )
. as_ref ( )
2020-06-06 19:38:20 +03:00
. map ( | ( _ , b ) | unsafe { std ::str ::from_utf8_unchecked ( b ) } ) ,
2019-12-11 15:10:59 +02:00
Ok ( r #" <!-- SEPARATOR -->
< tr >
< td style = " padding-left: 10px;padding-right: 10px;background-color: #f3f5fa; " >
< table width = " 100% " cellspacing = " 0 " cellpadding = " 0 " border = " 0 " >
< tr >
< td style = " height:5px;background-color: #f3f5fa; " > & nbsp ; < / td >
< / tr >
< / table >
< / td >
< / tr > " #)
) ;
}
2020-09-03 14:44:03 +03:00
#[ test ]
fn test_msg_id ( ) {
let s = " Message-ID: <1234@local.machine.example> \r \n " ;
let ( rest , ( _header_name , value ) ) = headers ::header ( s . as_bytes ( ) ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
let a = msg_id ( value ) . unwrap ( ) . 1 ;
assert_eq! ( a . val ( ) , b " <1234@local.machine.example> " ) ;
let s = " Message-ID: <testabcd.1234@silly.test> \r \n " ;
let ( rest , ( _header_name , value ) ) = headers ::header ( s . as_bytes ( ) ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
let b = msg_id ( value ) . unwrap ( ) . 1 ;
assert_eq! ( b . val ( ) , b " <testabcd.1234@silly.test> " ) ;
let s = " References: <1234@local.machine.example> \r \n " ;
let ( rest , ( _header_name , value ) ) = headers ::header ( s . as_bytes ( ) ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
assert_eq! ( & msg_id_list ( value ) . unwrap ( ) . 1 , & [ a . clone ( ) ] ) ;
let s = " References: <1234@local.machine.example> <3456@example.net> \r \n " ;
let ( rest , ( _header_name , value ) ) = headers ::header ( s . as_bytes ( ) ) . unwrap ( ) ;
assert! ( rest . is_empty ( ) ) ;
let s = b " <3456@example.net> " ;
let c = msg_id ( s ) . unwrap ( ) . 1 ;
assert_eq! ( & msg_id_list ( value ) . unwrap ( ) . 1 , & [ a , c ] ) ;
}
2018-08-14 00:13:08 +03:00
}