11use std:: fmt;
22
3- use serde:: Deserialize ;
3+ use serde:: { Deserialize , Serialize } ;
44
55/// The different types an attachment can have.
6- #[ derive( Debug , Clone , Eq , PartialEq , Deserialize , Default ) ]
6+ #[ derive( Debug , Clone , Eq , PartialEq , Deserialize , Serialize , Default ) ]
77pub enum AttachmentType {
88 #[ serde( rename = "event.attachment" ) ]
99 /// (default) A standard attachment without special meaning.
@@ -56,27 +56,51 @@ pub struct Attachment {
5656 pub ty : Option < AttachmentType > ,
5757}
5858
59+ struct AttachmentHeaderType ;
60+
61+ impl Serialize for AttachmentHeaderType {
62+ fn serialize < S > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error >
63+ where
64+ S : serde:: Serializer ,
65+ {
66+ "attachment" . serialize ( serializer)
67+ }
68+ }
69+
70+ #[ derive( Serialize ) ]
71+ struct AttachmentHeader < ' a > {
72+ r#type : AttachmentHeaderType ,
73+ length : usize ,
74+ filename : & ' a str ,
75+ attachment_type : & ' a AttachmentType ,
76+ content_type : & ' a str ,
77+ }
78+
5979impl Attachment {
6080 /// Writes the attachment and its headers to the provided `Writer`.
6181 pub fn to_writer < W > ( & self , writer : & mut W ) -> std:: io:: Result < ( ) >
6282 where
6383 W : std:: io:: Write ,
6484 {
65- writeln ! (
66- writer,
67- r#"{{"type":"attachment","length":{length},"filename":"{filename}","attachment_type":"{at}","content_type":"{ct}"}}"# ,
68- filename = self . filename,
69- length = self . buffer. len( ) ,
70- at = self
71- . ty
72- . as_ref( )
73- . unwrap_or( & AttachmentType :: default ( ) )
74- . as_str( ) ,
75- ct = self
76- . content_type
77- . as_ref( )
78- . unwrap_or( & "application/octet-stream" . to_string( ) )
79- ) ?;
85+ let attachment_type = match self . ty . as_ref ( ) {
86+ Some ( ty) => ty,
87+ None => & Default :: default ( ) ,
88+ } ;
89+
90+ let content_type = self
91+ . content_type
92+ . as_deref ( )
93+ . unwrap_or ( "application/octet-stream" ) ;
94+ let header = AttachmentHeader {
95+ r#type : AttachmentHeaderType ,
96+ length : self . buffer . len ( ) ,
97+ filename : & self . filename ,
98+ attachment_type,
99+ content_type,
100+ } ;
101+
102+ serde_json:: to_writer ( & mut * writer, & header) ?;
103+ writeln ! ( writer) ?;
80104
81105 writer. write_all ( & self . buffer ) ?;
82106 Ok ( ( ) )
@@ -99,7 +123,7 @@ impl fmt::Debug for Attachment {
99123#[ cfg( test) ]
100124mod tests {
101125 use super :: * ;
102- use serde_json;
126+ use serde_json:: { self , json } ;
103127
104128 #[ test]
105129 fn test_attachment_type_deserialize ( ) {
@@ -109,4 +133,33 @@ mod tests {
109133 let result: AttachmentType = serde_json:: from_str ( r#""my.custom.type""# ) . unwrap ( ) ;
110134 assert_eq ! ( result, AttachmentType :: Custom ( "my.custom.type" . to_string( ) ) ) ;
111135 }
136+
137+ #[ test]
138+ fn test_attachment_header_escapes_json_strings ( ) {
139+ let attachment = Attachment {
140+ buffer : b"payload" . to_vec ( ) ,
141+ filename : "file \" name\" \n part.txt" . to_string ( ) ,
142+ content_type : Some ( "text/\" plain\n next" . to_string ( ) ) ,
143+ ty : Some ( AttachmentType :: Custom ( "custom/\" type\n next" . to_string ( ) ) ) ,
144+ } ;
145+
146+ let mut buf = Vec :: new ( ) ;
147+ attachment. to_writer ( & mut buf) . unwrap ( ) ;
148+
149+ let mut parts = buf. splitn ( 2 , |& b| b == b'\n' ) ;
150+ let header: serde_json:: Value = serde_json:: from_slice ( parts. next ( ) . unwrap ( ) ) . unwrap ( ) ;
151+ let payload = parts. next ( ) . unwrap ( ) ;
152+
153+ assert_eq ! (
154+ header,
155+ json!( {
156+ "type" : "attachment" ,
157+ "length" : 7 ,
158+ "filename" : "file \" name\" \n part.txt" ,
159+ "content_type" : "text/\" plain\n next" ,
160+ "attachment_type" : "custom/\" type\n next" ,
161+ } )
162+ ) ;
163+ assert_eq ! ( payload, b"payload" ) ;
164+ }
112165}
0 commit comments