2023-07-24 12:26:53 +02:00
use nom ::IResult ;
2023-07-14 19:12:34 +02:00
2023-07-24 19:19:49 +02:00
use crate ::header ::{ header , self } ;
use crate ::imf ;
2023-07-24 12:37:30 +02:00
use crate ::mime ;
2023-07-24 19:19:49 +02:00
use crate ::part ::{ self , AnyPart , field ::MixedField } ;
2023-07-23 16:37:47 +02:00
use crate ::text ::boundary ::{ boundary , Delimiter } ;
2023-07-23 09:46:57 +02:00
2023-07-24 12:26:53 +02:00
//--- Multipart
2023-07-23 13:25:33 +02:00
#[ derive(Debug, PartialEq) ]
2023-07-24 12:26:53 +02:00
pub struct Multipart < ' a > {
2023-07-25 14:00:01 +02:00
pub mime : mime ::MIME < ' a , mime ::r#type ::Multipart > ,
2023-07-24 12:37:30 +02:00
pub children : Vec < AnyPart < ' a > > ,
2023-07-24 18:32:26 +02:00
pub preamble : & ' a [ u8 ] ,
pub epilogue : & ' a [ u8 ] ,
}
impl < ' a > Multipart < ' a > {
pub fn with_epilogue ( mut self , e : & ' a [ u8 ] ) -> Self {
self . epilogue = e ;
self
}
2023-07-22 14:38:43 +02:00
}
2023-07-14 19:12:34 +02:00
2023-07-23 16:37:47 +02:00
pub fn multipart < ' a > (
2023-07-24 22:08:13 +02:00
m : mime ::MIME < ' a , mime ::r#type ::Multipart > ,
2023-07-23 16:37:47 +02:00
) -> impl Fn ( & ' a [ u8 ] ) -> IResult < & ' a [ u8 ] , Multipart < ' a > > {
2023-07-23 12:24:46 +02:00
let m = m . clone ( ) ;
move | input | {
2023-07-25 14:00:01 +02:00
let bound = m . interpreted_type . boundary . as_bytes ( ) ;
2023-07-24 18:32:26 +02:00
let ( mut input_loop , preamble ) = part ::part_raw ( bound ) ( input ) ? ;
2023-07-23 09:46:57 +02:00
let mut mparts : Vec < AnyPart > = vec! [ ] ;
2023-07-22 16:46:36 +02:00
loop {
2023-07-23 12:24:46 +02:00
let input = match boundary ( bound ) ( input_loop ) {
2023-07-24 12:37:30 +02:00
Err ( _ ) = > {
return Ok ( (
input_loop ,
Multipart {
2023-07-25 14:00:01 +02:00
mime : m . clone ( ) ,
2023-07-24 12:37:30 +02:00
children : mparts ,
2023-07-24 18:32:26 +02:00
preamble ,
epilogue : & [ ] ,
2023-07-24 12:37:30 +02:00
} ,
) )
}
Ok ( ( inp , Delimiter ::Last ) ) = > {
return Ok ( (
inp ,
Multipart {
2023-07-25 14:00:01 +02:00
mime : m . clone ( ) ,
2023-07-24 12:37:30 +02:00
children : mparts ,
2023-07-24 18:32:26 +02:00
preamble ,
epilogue : & [ ] ,
2023-07-24 12:37:30 +02:00
} ,
) )
}
2023-07-22 16:46:36 +02:00
Ok ( ( inp , Delimiter ::Next ) ) = > inp ,
} ;
2023-07-18 15:00:38 +02:00
2023-07-24 22:08:13 +02:00
// parse mime headers, otherwise pick default mime
let ( input , naive_mime ) = match header ( mime ::field ::content ) ( input ) {
Ok ( ( input , ( known , unknown , bad ) ) ) = > ( input , known . into_iter ( ) . collect ::< mime ::NaiveMIME > ( ) . with_opt ( unknown ) . with_bad ( bad ) ) ,
Err ( _ ) = > ( input , mime ::NaiveMIME ::default ( ) ) ,
} ;
// interpret mime according to context
2023-07-25 14:00:01 +02:00
let mime = match m . interpreted_type . subtype {
2023-07-24 22:08:13 +02:00
mime ::r#type ::MultipartSubtype ::Digest = > naive_mime . to_interpreted ::< mime ::WithDigestDefault > ( ) . into ( ) ,
_ = > naive_mime . to_interpreted ::< mime ::WithGenericDefault > ( ) . into ( ) ,
2023-07-24 18:01:37 +02:00
} ;
2023-07-22 20:52:35 +02:00
2023-07-23 09:46:57 +02:00
// parse raw part
2023-07-24 12:26:53 +02:00
let ( input , rpart ) = part ::part_raw ( bound ) ( input ) ? ;
2023-07-23 09:46:57 +02:00
2023-07-22 20:52:35 +02:00
// parse mime body
2023-07-24 12:26:53 +02:00
mparts . push ( part ::to_anypart ( mime , rpart ) ) ;
2023-07-14 19:12:34 +02:00
2023-07-24 22:08:13 +02:00
2023-07-22 16:46:36 +02:00
input_loop = input ;
}
}
2023-07-18 15:00:38 +02:00
}
2023-07-24 12:26:53 +02:00
//--- Message
#[ derive(Debug, PartialEq) ]
pub struct Message < ' a > {
2023-07-25 14:00:01 +02:00
pub mime : mime ::MIME < ' a , mime ::r#type ::DeductibleMessage > ,
2023-07-24 12:26:53 +02:00
pub imf : imf ::Imf < ' a > ,
pub child : Box < AnyPart < ' a > > ,
2023-07-24 18:32:26 +02:00
pub epilogue : & ' a [ u8 ] ,
2023-07-14 19:12:34 +02:00
}
2023-07-24 18:32:26 +02:00
impl < ' a > Message < ' a > {
pub fn with_epilogue ( mut self , e : & ' a [ u8 ] ) -> Self {
self . epilogue = e ;
self
}
}
2023-07-14 19:12:34 +02:00
2023-07-24 12:26:53 +02:00
pub fn message < ' a > (
2023-07-25 14:00:01 +02:00
m : mime ::MIME < ' a , mime ::r#type ::DeductibleMessage > ,
2023-07-24 12:26:53 +02:00
) -> impl Fn ( & ' a [ u8 ] ) -> IResult < & ' a [ u8 ] , Message < ' a > > {
move | input : & [ u8 ] | {
2023-07-24 22:08:13 +02:00
// parse header fields
2023-07-24 19:19:49 +02:00
let ( input , ( known , unknown , bad ) ) : ( _ , ( Vec ::< MixedField > , Vec < header ::Kv > , Vec < & [ u8 ] > ) ) =
2023-07-24 12:37:30 +02:00
header ( part ::field ::mixed_field ) ( input ) ? ;
2023-07-24 12:26:53 +02:00
2023-07-24 22:08:13 +02:00
// aggregate header fields
let ( naive_mime , imf ) = part ::field ::sections ( known ) ;
// attach bad headers to imf
let imf = imf . with_opt ( unknown ) . with_bad ( bad ) ;
// interpret headers to choose a mime type
let in_mime = naive_mime . to_interpreted ::< mime ::WithGenericDefault > ( ) . into ( ) ;
// parse this mimetype
2023-07-24 12:26:53 +02:00
let part = part ::to_anypart ( in_mime , input ) ;
2023-07-24 12:37:30 +02:00
Ok ( (
& [ ] ,
Message {
2023-07-25 14:00:01 +02:00
mime : m . clone ( ) ,
2023-07-24 12:37:30 +02:00
imf ,
child : Box ::new ( part ) ,
2023-07-24 18:32:26 +02:00
epilogue : & [ ] ,
2023-07-24 12:37:30 +02:00
} ,
) )
2023-07-17 17:14:08 +02:00
}
2023-07-17 11:44:55 +02:00
}
2023-07-14 19:12:34 +02:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2023-07-24 12:26:53 +02:00
use crate ::part ::discrete ::Text ;
use crate ::part ::AnyPart ;
2023-07-24 12:37:30 +02:00
use crate ::text ::encoding ::{ Base64Word , EncodedWord , QuotedChunk , QuotedWord } ;
2023-07-24 22:08:13 +02:00
use crate ::text ::misc_token ::{ Phrase , UnstrToken , Unstructured , Word , MIMEWord } ;
use crate ::text ::quoted ::QuotedString ;
2023-07-23 16:20:38 +02:00
use chrono ::{ FixedOffset , TimeZone } ;
2023-07-14 19:12:34 +02:00
2023-07-17 17:14:08 +02:00
#[ test ]
fn test_multipart ( ) {
2023-07-24 22:08:13 +02:00
let base_mime = mime ::MIME {
2023-07-25 14:00:01 +02:00
interpreted_type : mime ::r#type ::Multipart {
2023-07-23 13:25:33 +02:00
subtype : mime ::r#type ::MultipartSubtype ::Alternative ,
boundary : " simple boundary " . to_string ( ) ,
} ,
2023-07-25 14:00:01 +02:00
fields : mime ::NaiveMIME ::default ( ) ,
2023-07-24 22:08:13 +02:00
} ;
2023-07-23 13:25:33 +02:00
2023-07-17 17:14:08 +02:00
assert_eq! (
2023-07-23 13:25:33 +02:00
multipart ( base_mime . clone ( ) ) ( b " This is the preamble. It is to be ignored, though it
2023-07-17 17:14:08 +02:00
is a handy place for composition agents to include an
explanatory note to non - MIME conformant readers .
- - simple boundary
This is implicitly typed plain US - ASCII text .
It does NOT end with a linebreak .
- - simple boundary
Content - type : text / plain ; charset = us - ascii
This is explicitly typed plain US - ASCII text .
It DOES end with a linebreak .
- - simple boundary - -
This is the epilogue . It is also to be ignored .
" ),
Ok ( ( & b " \n This is the epilogue. It is also to be ignored. \n " [ .. ] ,
2023-07-24 12:26:53 +02:00
Multipart {
2023-07-25 14:00:01 +02:00
mime : base_mime ,
2023-07-24 18:32:26 +02:00
preamble : & b " This is the preamble. It is to be ignored, though it \n is a handy place for composition agents to include an \n explanatory note to non-MIME conformant readers. \n " [ .. ] ,
epilogue : & b " " [ .. ] ,
2023-07-24 12:26:53 +02:00
children : vec ! [
2023-07-24 12:37:30 +02:00
AnyPart ::Txt ( Text {
2023-07-25 14:00:01 +02:00
mime : mime ::MIME {
interpreted_type : mime ::r#type ::Deductible ::Inferred ( mime ::r#type ::Text {
2023-07-23 13:25:33 +02:00
subtype : mime ::r#type ::TextSubtype ::Plain ,
2023-07-25 14:00:01 +02:00
charset : mime ::r#type ::Deductible ::Inferred ( mime ::charset ::EmailCharset ::US_ASCII ) ,
} ) ,
fields : mime ::NaiveMIME ::default ( ) ,
2023-07-24 22:08:13 +02:00
} ,
2023-07-24 12:26:53 +02:00
body : & b " This is implicitly typed plain US-ASCII text. \n It does NOT end with a linebreak. " [ .. ] ,
} ) ,
AnyPart ::Txt ( Text {
2023-07-25 14:00:01 +02:00
mime : mime ::MIME {
interpreted_type : mime ::r#type ::Deductible ::Explicit ( mime ::r#type ::Text {
2023-07-23 13:25:33 +02:00
subtype : mime ::r#type ::TextSubtype ::Plain ,
2023-07-25 14:00:01 +02:00
charset : mime ::r#type ::Deductible ::Explicit ( mime ::charset ::EmailCharset ::US_ASCII ) ,
} ) ,
fields : mime ::NaiveMIME {
2023-07-24 22:08:13 +02:00
ctype : Some ( mime ::r#type ::NaiveType {
main : & b " text " [ .. ] ,
sub : & b " plain " [ .. ] ,
params : vec ! [
mime ::r#type ::Parameter {
name : & b " charset " [ .. ] ,
value : MIMEWord ::Atom ( & b " us-ascii " [ .. ] ) ,
}
]
} ) ,
.. mime ::NaiveMIME ::default ( )
} ,
} ,
2023-07-24 12:26:53 +02:00
body : & b " This is explicitly typed plain US-ASCII text. \n It DOES end with a linebreak. \n " [ .. ] ,
} ) ,
2023-07-23 13:25:33 +02:00
] ,
2023-07-24 12:26:53 +02:00
} ,
2023-07-23 13:25:33 +02:00
) )
2023-07-17 17:14:08 +02:00
) ;
}
2023-07-23 16:20:38 +02:00
#[ test ]
fn test_message ( ) {
let fullmail : & [ u8 ] = r #" Date: Sat, 8 Jul 2023 07:14:29 +0200
From : Grrrnd Zero < grrrndzero @ example . org >
To : John Doe < jdoe @ machine . example >
CC : = ? ISO - 8859 - 1 ? Q ? Andr = E9 ? = Pirard < PIRARD @ vm1 . ulg . ac . be >
Subject : = ? ISO - 8859 - 1 ? B ? SWYgeW91IGNhbiByZWFkIHRoaXMgeW8 = ? =
= ? ISO - 8859 - 2 ? B ? dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg = = ? =
X - Unknown : something something
Bad entry
on multiple lines
Message - ID : < NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5 @ www . grrrndzero . org >
MIME - Version : 1.0
Content - Type : multipart / alternative ;
boundary = " b1_e376dc71bafc953c0b0fdeb9983a9956 "
Content - Transfer - Encoding : 7 bit
This is a multi - part message in MIME format .
- - b1_e376dc71bafc953c0b0fdeb9983a9956
Content - Type : text / plain ; charset = utf - 8
Content - Transfer - Encoding : quoted - printable
GZ
OoOoO
oOoOoOoOo
oOoOoOoOoOoOoOoOo
oOoOoOoOoOoOoOoOoOoOoOo
oOoOoOoOoOoOoOoOoOoOoOoOoOoOo
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
- - b1_e376dc71bafc953c0b0fdeb9983a9956
Content - Type : text / html ; charset = us - ascii
< div style = " text-align: center; " > < strong > GZ < / strong > < br / >
OoOoO < br / >
oOoOoOoOo < br / >
oOoOoOoOoOoOoOoOo < br / >
oOoOoOoOoOoOoOoOoOoOoOo < br / >
oOoOoOoOoOoOoOoOoOoOoOoOoOoOo < br / >
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO < br / >
< / div >
- - b1_e376dc71bafc953c0b0fdeb9983a9956 - -
2023-07-23 16:37:47 +02:00
" #
. as_bytes ( ) ;
2023-07-23 16:20:38 +02:00
2023-07-25 14:00:01 +02:00
let base_mime = mime ::MIME ::< mime ::r#type ::DeductibleMessage > ::default ( ) ;
2023-07-23 16:20:38 +02:00
assert_eq! (
message ( base_mime . clone ( ) ) ( fullmail ) ,
Ok ( (
& [ ] [ .. ] ,
2023-07-24 12:26:53 +02:00
Message {
2023-07-25 14:00:01 +02:00
mime : base_mime ,
2023-07-24 18:32:26 +02:00
epilogue : & b " " [ .. ] ,
2023-07-24 12:26:53 +02:00
imf : imf ::Imf {
2023-07-23 16:20:38 +02:00
date : Some ( FixedOffset ::east_opt ( 2 * 3600 )
. unwrap ( )
. with_ymd_and_hms ( 2023 , 07 , 8 , 7 , 14 , 29 )
. unwrap ( ) ) ,
from : vec ! [
imf ::mailbox ::MailboxRef {
name : Some ( Phrase ( vec! [ Word ::Atom ( & b " Grrrnd " [ .. ] ) , Word ::Atom ( & b " Zero " [ .. ] ) ] ) ) ,
addrspec : imf ::mailbox ::AddrSpec {
local_part : imf ::mailbox ::LocalPart ( vec! [
imf ::mailbox ::LocalPartToken ::Word ( Word ::Atom ( & b " grrrndzero " [ .. ] ) )
] ) ,
domain : imf ::mailbox ::Domain ::Atoms ( vec! [ & b " example " [ .. ] , & b " org " [ .. ] ] ) ,
}
} ,
] ,
to : vec ! [ imf ::address ::AddressRef ::Single ( imf ::mailbox ::MailboxRef {
name : Some ( Phrase ( vec! [ Word ::Atom ( & b " John " [ .. ] ) , Word ::Atom ( & b " Doe " [ .. ] ) ] ) ) ,
addrspec : imf ::mailbox ::AddrSpec {
local_part : imf ::mailbox ::LocalPart ( vec! [
imf ::mailbox ::LocalPartToken ::Word ( Word ::Atom ( & b " jdoe " [ .. ] ) )
] ) ,
domain : imf ::mailbox ::Domain ::Atoms ( vec! [ & b " machine " [ .. ] , & b " example " [ .. ] ] ) ,
}
} ) ] ,
cc : vec ! [ imf ::address ::AddressRef ::Single ( imf ::mailbox ::MailboxRef {
name : Some ( Phrase ( vec! [
Word ::Encoded ( EncodedWord ::Quoted ( QuotedWord {
enc : encoding_rs ::WINDOWS_1252 ,
chunks : vec ! [
QuotedChunk ::Safe ( & b " Andr " [ .. ] ) ,
2023-07-23 18:27:03 +02:00
QuotedChunk ::Encoded ( vec! [ 0xE9 ] ) ,
2023-07-23 16:20:38 +02:00
] ,
} ) ) ,
Word ::Atom ( & b " Pirard " [ .. ] )
] ) ) ,
addrspec : imf ::mailbox ::AddrSpec {
local_part : imf ::mailbox ::LocalPart ( vec! [
imf ::mailbox ::LocalPartToken ::Word ( Word ::Atom ( & b " PIRARD " [ .. ] ) )
] ) ,
domain : imf ::mailbox ::Domain ::Atoms ( vec! [
& b " vm1 " [ .. ] , & b " ulg " [ .. ] , & b " ac " [ .. ] , & b " be " [ .. ] ,
] ) ,
}
} ) ] ,
subject : Some ( Unstructured ( vec! [
UnstrToken ::Encoded ( EncodedWord ::Base64 ( Base64Word {
enc : encoding_rs ::WINDOWS_1252 ,
content : & b " SWYgeW91IGNhbiByZWFkIHRoaXMgeW8 " [ .. ] ,
2023-07-23 16:37:47 +02:00
} ) ) ,
2023-07-23 16:20:38 +02:00
UnstrToken ::Encoded ( EncodedWord ::Base64 ( Base64Word {
enc : encoding_rs ::ISO_8859_2 ,
content : & b " dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg " [ .. ] ,
} ) ) ,
] ) ) ,
msg_id : Some ( imf ::identification ::MessageID {
left : & b " NTAxNzA2AC47634Y366BAMTY4ODc5MzQyODY0ODY5 " [ .. ] ,
right : & b " www.grrrndzero.org " [ .. ] ,
} ) ,
mime_version : Some ( imf ::mime ::Version { major : 1 , minor : 0 } ) ,
2023-07-24 22:08:13 +02:00
header_ext : vec ! [
header ::Kv ( & b " X-Unknown " [ .. ] , Unstructured ( vec! [
UnstrToken ::Plain ( & b " something " [ .. ] ) ,
UnstrToken ::Plain ( & b " something " [ .. ] ) ,
] ) )
] ,
header_bad : vec ! [
& b " Bad entry \n on multiple lines \n " [ .. ] ,
] ,
2023-07-24 11:09:21 +02:00
.. imf ::Imf ::default ( )
2023-07-23 16:20:38 +02:00
} ,
2023-07-24 12:26:53 +02:00
child : Box ::new ( AnyPart ::Mult ( Multipart {
2023-07-25 14:00:01 +02:00
mime : mime ::MIME {
interpreted_type : mime ::r#type ::Multipart {
2023-07-23 16:20:38 +02:00
subtype : mime ::r#type ::MultipartSubtype ::Alternative ,
boundary : " b1_e376dc71bafc953c0b0fdeb9983a9956 " . to_string ( ) ,
} ,
2023-07-25 14:00:01 +02:00
fields : mime ::NaiveMIME {
2023-07-24 22:08:13 +02:00
ctype : Some ( mime ::r#type ::NaiveType {
main : & b " multipart " [ .. ] ,
sub : & b " alternative " [ .. ] ,
params : vec ! [
mime ::r#type ::Parameter {
name : & b " boundary " [ .. ] ,
value : MIMEWord ::Quoted ( QuotedString ( vec! [ & b " b1_e376dc71bafc953c0b0fdeb9983a9956 " [ .. ] ] ) ) ,
}
]
} ) ,
.. mime ::NaiveMIME ::default ( )
} ,
} ,
2023-07-24 18:32:26 +02:00
preamble : & b " This is a multi-part message in MIME format. \n " [ .. ] ,
epilogue : & b " " [ .. ] ,
2023-07-24 12:26:53 +02:00
children : vec ! [
AnyPart ::Txt ( Text {
2023-07-25 14:00:01 +02:00
mime : mime ::MIME {
interpreted_type : mime ::r#type ::Deductible ::Explicit ( mime ::r#type ::Text {
2023-07-23 16:20:38 +02:00
subtype : mime ::r#type ::TextSubtype ::Plain ,
2023-07-25 14:00:01 +02:00
charset : mime ::r#type ::Deductible ::Explicit ( mime ::charset ::EmailCharset ::UTF_8 ) ,
} ) ,
fields : mime ::NaiveMIME {
2023-07-24 22:08:13 +02:00
ctype : Some ( mime ::r#type ::NaiveType {
main : & b " text " [ .. ] ,
sub : & b " plain " [ .. ] ,
params : vec ! [
mime ::r#type ::Parameter {
name : & b " charset " [ .. ] ,
value : MIMEWord ::Atom ( & b " utf-8 " [ .. ] ) ,
}
]
} ) ,
2023-07-23 16:20:38 +02:00
transfer_encoding : mime ::mechanism ::Mechanism ::QuotedPrintable ,
2023-07-24 22:08:13 +02:00
.. mime ::NaiveMIME ::default ( )
2023-07-23 16:20:38 +02:00
}
2023-07-24 22:08:13 +02:00
} ,
2023-07-24 12:26:53 +02:00
body : & b " GZ \n OoOoO \n oOoOoOoOo \n oOoOoOoOoOoOoOoOo \n oOoOoOoOoOoOoOoOoOoOoOo \n oOoOoOoOoOoOoOoOoOoOoOoOoOoOo \n OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO \n " [ .. ] ,
} ) ,
AnyPart ::Txt ( Text {
2023-07-25 14:00:01 +02:00
mime : mime ::MIME {
interpreted_type : mime ::r#type ::Deductible ::Explicit ( mime ::r#type ::Text {
2023-07-23 16:20:38 +02:00
subtype : mime ::r#type ::TextSubtype ::Html ,
2023-07-25 14:00:01 +02:00
charset : mime ::r#type ::Deductible ::Explicit ( mime ::charset ::EmailCharset ::US_ASCII ) ,
} ) ,
2023-07-24 22:08:13 +02:00
2023-07-25 14:00:01 +02:00
fields : mime ::NaiveMIME {
2023-07-24 22:08:13 +02:00
ctype : Some ( mime ::r#type ::NaiveType {
main : & b " text " [ .. ] ,
sub : & b " html " [ .. ] ,
params : vec ! [
mime ::r#type ::Parameter {
name : & b " charset " [ .. ] ,
value : MIMEWord ::Atom ( & b " us-ascii " [ .. ] ) ,
}
]
} ) ,
.. mime ::NaiveMIME ::default ( )
} ,
} ,
2023-07-24 12:26:53 +02:00
body : & br #" <div style= " text - align : center ; " ><strong>GZ</strong><br />
2023-07-23 16:20:38 +02:00
OoOoO < br / >
oOoOoOoOo < br / >
oOoOoOoOoOoOoOoOo < br / >
oOoOoOoOoOoOoOoOoOoOoOo < br / >
oOoOoOoOoOoOoOoOoOoOoOoOoOoOo < br / >
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO < br / >
< / div >
" #[..],
2023-07-24 12:26:53 +02:00
} ) ,
2023-07-23 16:20:38 +02:00
] ,
2023-07-24 12:26:53 +02:00
} ) ) ,
} ,
2023-07-23 16:20:38 +02:00
) )
) ;
}
2023-07-14 19:12:34 +02:00
}