@@ -108,15 +108,16 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
108
108
None
109
109
}
110
110
}
111
- fn vcard_property < ' a > ( s : & ' a str , property : & str ) -> Option < & ' a str > {
112
- let remainder = remove_prefix ( s, property) ?;
111
+ /// Returns (parameters, value) tuple.
112
+ fn vcard_property < ' a > ( line : & ' a str , property : & str ) -> Option < ( & ' a str , & ' a str ) > {
113
+ let remainder = remove_prefix ( line, property) ?;
113
114
// If `s` is `EMAIL;TYPE=work:[email protected] ` and `property` is `EMAIL`,
114
115
// then `remainder` is now `;TYPE=work:[email protected] `
115
116
116
117
// Note: This doesn't handle the case where there are quotes around a colon,
117
118
// like `NAME;Foo="Some quoted text: that contains a colon":value`.
118
119
// This could be improved in the future, but for now, the parsing is good enough.
119
- let ( params, value) = remainder. split_once ( ':' ) ?;
120
+ let ( mut params, value) = remainder. split_once ( ':' ) ?;
120
121
// In the example from above, `params` is now `;TYPE=work`
121
122
// and `value` is now `[email protected] `
122
123
@@ -130,7 +131,47 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
130
131
// so this line's property is actually something else
131
132
return None ;
132
133
}
133
- Some ( value)
134
+ if let Some ( p) = remove_prefix ( params, ";" ) {
135
+ params = p;
136
+ }
137
+ if let Some ( p) = remove_prefix ( params, "PREF=1" ) {
138
+ params = p;
139
+ }
140
+ Some ( ( params, value) )
141
+ }
142
+ fn base64_key ( line : & str ) -> Option < & str > {
143
+ let ( params, value) = vcard_property ( line, "key" ) ?;
144
+ if params. eq_ignore_ascii_case ( "PGP;ENCODING=BASE64" )
145
+ || params. eq_ignore_ascii_case ( "TYPE=PGP;ENCODING=b" )
146
+ {
147
+ return Some ( value) ;
148
+ }
149
+ if let Some ( value) = remove_prefix ( value, "data:application/pgp-keys;base64," )
150
+ . or_else ( || remove_prefix ( value, r"data:application/pgp-keys;base64\," ) )
151
+ {
152
+ return Some ( value) ;
153
+ }
154
+
155
+ None
156
+ }
157
+ fn base64_photo ( line : & str ) -> Option < & str > {
158
+ let ( params, value) = vcard_property ( line, "photo" ) ?;
159
+ if params. eq_ignore_ascii_case ( "JPEG;ENCODING=BASE64" )
160
+ || params. eq_ignore_ascii_case ( "ENCODING=BASE64;JPEG" )
161
+ || params. eq_ignore_ascii_case ( "TYPE=JPEG;ENCODING=b" )
162
+ || params. eq_ignore_ascii_case ( "ENCODING=b;TYPE=JPEG" )
163
+ || params. eq_ignore_ascii_case ( "ENCODING=BASE64;TYPE=JPEG" )
164
+ || params. eq_ignore_ascii_case ( "TYPE=JPEG;ENCODING=BASE64" )
165
+ {
166
+ return Some ( value) ;
167
+ }
168
+ if let Some ( value) = remove_prefix ( value, "data:image/jpeg;base64," )
169
+ . or_else ( || remove_prefix ( value, r"data:image/jpeg;base64\," ) )
170
+ {
171
+ return Some ( value) ;
172
+ }
173
+
174
+ None
134
175
}
135
176
fn parse_datetime ( datetime : & str ) -> Result < i64 > {
136
177
// According to https://www.rfc-editor.org/rfc/rfc6350#section-4.3.5, the timestamp
@@ -185,26 +226,15 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
185
226
line = remainder;
186
227
}
187
228
188
- if let Some ( email) = vcard_property ( line, "email" ) {
229
+ if let Some ( ( _params , email) ) = vcard_property ( line, "email" ) {
189
230
addr. get_or_insert ( email) ;
190
- } else if let Some ( name) = vcard_property ( line, "fn" ) {
231
+ } else if let Some ( ( _params , name) ) = vcard_property ( line, "fn" ) {
191
232
display_name. get_or_insert ( name) ;
192
- } else if let Some ( k) = remove_prefix ( line, "KEY;PGP;ENCODING=BASE64:" )
193
- . or_else ( || remove_prefix ( line, "KEY;TYPE=PGP;ENCODING=b:" ) )
194
- . or_else ( || remove_prefix ( line, "KEY:data:application/pgp-keys;base64," ) )
195
- . or_else ( || remove_prefix ( line, "KEY;PREF=1:data:application/pgp-keys;base64," ) )
196
- {
233
+ } else if let Some ( k) = base64_key ( line) {
197
234
key. get_or_insert ( k) ;
198
- } else if let Some ( p) = remove_prefix ( line, "PHOTO;JPEG;ENCODING=BASE64:" )
199
- . or_else ( || remove_prefix ( line, "PHOTO;ENCODING=BASE64;JPEG:" ) )
200
- . or_else ( || remove_prefix ( line, "PHOTO;TYPE=JPEG;ENCODING=b:" ) )
201
- . or_else ( || remove_prefix ( line, "PHOTO;ENCODING=b;TYPE=JPEG:" ) )
202
- . or_else ( || remove_prefix ( line, "PHOTO;ENCODING=BASE64;TYPE=JPEG:" ) )
203
- . or_else ( || remove_prefix ( line, "PHOTO;TYPE=JPEG;ENCODING=BASE64:" ) )
204
- . or_else ( || remove_prefix ( line, "PHOTO:data:image/jpeg;base64," ) )
205
- {
235
+ } else if let Some ( p) = base64_photo ( line) {
206
236
photo. get_or_insert ( p) ;
207
- } else if let Some ( rev) = vcard_property ( line, "rev" ) {
237
+ } else if let Some ( ( _params , rev) ) = vcard_property ( line, "rev" ) {
208
238
datetime. get_or_insert ( rev) ;
209
239
} else if line. eq_ignore_ascii_case ( "END:VCARD" ) {
210
240
let ( authname, addr) =
@@ -774,6 +804,33 @@ END:VCARD",
774
804
assert_eq ! ( contacts[ 0 ] . profile_image, None ) ;
775
805
}
776
806
807
+ /// Proton at some point slightly changed the format of their vcards
808
+ #[ test]
809
+ fn test_protonmail_vcard2 ( ) {
810
+ let contacts = parse_vcard (
811
+ r"BEGIN:VCARD
812
+ VERSION:4.0
813
+ FN;PREF=1:Alice
814
+ PHOTO;PREF=1:data:image/jpeg;base64\,/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
815
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
816
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z
817
+ REV:Invalid Date
818
+ ITEM1.EMAIL;PREF=1:[email protected]
819
+ KEY;PREF=1:data:application/pgp-keys;base64,xsaaaaaaaaaaaaaaaaaaaaaaaaaaaa
820
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
821
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==
822
+ UID:proton-web-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
823
+ END:VCARD" ,
824
+ ) ;
825
+
826
+ assert_eq ! ( contacts. len( ) , 1 ) ;
827
+ assert_eq ! ( & contacts
[ 0 ] . addr
, "[email protected] " ) ;
828
+ assert_eq ! ( & contacts[ 0 ] . authname, "Alice" ) ;
829
+ assert_eq ! ( contacts[ 0 ] . key. as_ref( ) . unwrap( ) , "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==" ) ;
830
+ assert ! ( contacts[ 0 ] . timestamp. is_err( ) ) ;
831
+ assert_eq ! ( contacts[ 0 ] . profile_image. as_ref( ) . unwrap( ) , "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z" ) ;
832
+ }
833
+
777
834
#[ test]
778
835
fn test_sanitize_name ( ) {
779
836
assert_eq ! ( & sanitize_name( " hello world " ) , "hello world" ) ;
0 commit comments