@@ -46,24 +46,29 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String {
46
46
Some ( datetime. format ( "%Y%m%dT%H%M%SZ" ) . to_string ( ) )
47
47
}
48
48
49
+ fn escape ( s : & str ) -> String {
50
+ s. replace ( ',' , "\\ ," )
51
+ }
52
+
49
53
let mut res = "" . to_string ( ) ;
50
54
for c in contacts {
51
- let addr = & c. addr ;
52
- let display_name = c. display_name ( ) ;
55
+ // Mustn't contain ',', but it's easier to escape than to error out.
56
+ let addr = escape ( & c. addr ) ;
57
+ let display_name = escape ( c. display_name ( ) ) ;
53
58
res += & format ! (
54
59
"BEGIN:VCARD\r \n \
55
60
VERSION:4.0\r \n \
56
61
EMAIL:{addr}\r \n \
57
62
FN:{display_name}\r \n "
58
63
) ;
59
64
if let Some ( key) = & c. key {
60
- res += & format ! ( "KEY:data:application/pgp-keys;base64,{key}\r \n " ) ;
65
+ res += & format ! ( "KEY:data:application/pgp-keys;base64\\ ,{key}\r \n " ) ;
61
66
}
62
67
if let Some ( profile_image) = & c. profile_image {
63
- res += & format ! ( "PHOTO:data:image/jpeg;base64,{profile_image}\r \n " ) ;
68
+ res += & format ! ( "PHOTO:data:image/jpeg;base64\\ ,{profile_image}\r \n " ) ;
64
69
}
65
70
if let Some ( biography) = & c. biography {
66
- res += & format ! ( "NOTE:{biography }\r \n " ) ;
71
+ res += & format ! ( "NOTE:{}\r \n " , escape ( biography ) ) ;
67
72
}
68
73
if let Some ( timestamp) = format_timestamp ( c) {
69
74
res += & format ! ( "REV:{timestamp}\r \n " ) ;
@@ -84,8 +89,8 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
84
89
None
85
90
}
86
91
}
87
- /// Returns (parameters, value) tuple.
88
- fn vcard_property < ' a > ( line : & ' a str , property : & str ) -> Option < ( & ' a str , & ' a str ) > {
92
+ /// Returns (parameters, raw value) tuple.
93
+ fn vcard_property_raw < ' a > ( line : & ' a str , property : & str ) -> Option < ( & ' a str , & ' a str ) > {
89
94
let remainder = remove_prefix ( line, property) ?;
90
95
// If `s` is `EMAIL;TYPE=work:alice@example.com` and `property` is `EMAIL`,
91
96
// then `remainder` is now `;TYPE=work:alice@example.com`
@@ -115,23 +120,25 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
115
120
}
116
121
Some ( ( params, value) )
117
122
}
123
+ /// Returns (parameters, unescaped value) tuple.
124
+ fn vcard_property < ' a > ( line : & ' a str , property : & str ) -> Option < ( & ' a str , String ) > {
125
+ let ( params, value) = vcard_property_raw ( line, property) ?;
126
+ // Some fields can't contain commas, but unescape them everywhere for safety.
127
+ Some ( ( params, value. replace ( "\\ ," , "," ) ) )
128
+ }
118
129
fn base64_key ( line : & str ) -> Option < & str > {
119
- let ( params, value) = vcard_property ( line, "key" ) ?;
130
+ let ( params, value) = vcard_property_raw ( line, "key" ) ?;
120
131
if params. eq_ignore_ascii_case ( "PGP;ENCODING=BASE64" )
121
132
|| params. eq_ignore_ascii_case ( "TYPE=PGP;ENCODING=b" )
122
133
{
123
134
return Some ( value) ;
124
135
}
125
- if let Some ( value) = remove_prefix ( value, "data:application/pgp-keys;base64," )
126
- . or_else ( || remove_prefix ( value, r"data:application/pgp-keys;base64\," ) )
127
- {
128
- return Some ( value) ;
129
- }
130
-
131
- None
136
+ remove_prefix ( value, "data:application/pgp-keys;base64\\ ," )
137
+ // Old Delta Chat format.
138
+ . or_else ( || remove_prefix ( value, "data:application/pgp-keys;base64," ) )
132
139
}
133
140
fn base64_photo ( line : & str ) -> Option < & str > {
134
- let ( params, value) = vcard_property ( line, "photo" ) ?;
141
+ let ( params, value) = vcard_property_raw ( line, "photo" ) ?;
135
142
if params. eq_ignore_ascii_case ( "JPEG;ENCODING=BASE64" )
136
143
|| params. eq_ignore_ascii_case ( "ENCODING=BASE64;JPEG" )
137
144
|| params. eq_ignore_ascii_case ( "TYPE=JPEG;ENCODING=b" )
@@ -141,13 +148,9 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
141
148
{
142
149
return Some ( value) ;
143
150
}
144
- if let Some ( value) = remove_prefix ( value, "data:image/jpeg;base64," )
145
- . or_else ( || remove_prefix ( value, r"data:image/jpeg;base64\," ) )
146
- {
147
- return Some ( value) ;
148
- }
149
-
150
- None
151
+ remove_prefix ( value, "data:image/jpeg;base64\\ ," )
152
+ // Old Delta Chat format.
153
+ . or_else ( || remove_prefix ( value, "data:image/jpeg;base64," ) )
151
154
}
152
155
fn parse_datetime ( datetime : & str ) -> Result < i64 > {
153
156
// According to https://www.rfc-editor.org/rfc/rfc6350#section-4.3.5, the timestamp
@@ -216,16 +219,19 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
216
219
} else if let Some ( ( _params, rev) ) = vcard_property ( line, "rev" ) {
217
220
datetime. get_or_insert ( rev) ;
218
221
} else if line. eq_ignore_ascii_case ( "END:VCARD" ) {
219
- let ( authname, addr) =
220
- sanitize_name_and_addr ( display_name. unwrap_or ( "" ) , addr. unwrap_or ( "" ) ) ;
222
+ let ( authname, addr) = sanitize_name_and_addr (
223
+ & display_name. unwrap_or_default ( ) ,
224
+ & addr. unwrap_or_default ( ) ,
225
+ ) ;
221
226
222
227
contacts. push ( VcardContact {
223
228
authname,
224
229
addr,
225
230
key : key. map ( |s| s. to_string ( ) ) ,
226
231
profile_image : photo. map ( |s| s. to_string ( ) ) ,
227
- biography : biography . map ( |b| b . to_owned ( ) ) ,
232
+ biography,
228
233
timestamp : datetime
234
+ . as_deref ( )
229
235
. context ( "No timestamp in vcard" )
230
236
. and_then ( parse_datetime) ,
231
237
} ) ;
0 commit comments