mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Citing @link2xt: > RFC examples sometimes don't escape commas, but there is errata that fixes some of them. Also this unescapes commas in all fields. This can lead to, say, an email address with commas, but anyway the caller should check parsed `VcardContact`'s fields for correctness.
279 lines
9.0 KiB
Rust
279 lines
9.0 KiB
Rust
use chrono::TimeZone as _;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_vcard_thunderbird() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN:'Alice Mueller'
|
|
EMAIL;PREF=1:alice.mueller@posteo.de
|
|
UID:a8083264-ca47-4be7-98a8-8ec3db1447ca
|
|
END:VCARD
|
|
BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN:'bobzzz@freenet.de'
|
|
EMAIL;PREF=1:bobzzz@freenet.de
|
|
UID:cac4fef4-6351-4854-bbe4-9b6df857eaed
|
|
END:VCARD
|
|
",
|
|
);
|
|
|
|
assert_eq!(contacts[0].addr, "alice.mueller@posteo.de".to_string());
|
|
assert_eq!(contacts[0].authname, "Alice Mueller".to_string());
|
|
assert_eq!(contacts[0].key, None);
|
|
assert_eq!(contacts[0].profile_image, None);
|
|
assert!(contacts[0].timestamp.is_err());
|
|
|
|
assert_eq!(contacts[1].addr, "bobzzz@freenet.de".to_string());
|
|
assert_eq!(contacts[1].authname, "".to_string());
|
|
assert_eq!(contacts[1].key, None);
|
|
assert_eq!(contacts[1].profile_image, None);
|
|
assert!(contacts[1].timestamp.is_err());
|
|
|
|
assert_eq!(contacts.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_simple_example() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN:Alice Wonderland
|
|
N:Wonderland;Alice;;;Ms.
|
|
GENDER:W
|
|
EMAIL;TYPE=work:alice@example.com
|
|
KEY;TYPE=PGP;ENCODING=b:[base64-data]
|
|
REV:20240418T184242Z
|
|
|
|
END:VCARD",
|
|
);
|
|
|
|
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
|
|
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
|
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
|
|
assert_eq!(contacts[0].profile_image, None);
|
|
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
|
|
|
|
assert_eq!(contacts.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_with_trailing_newline() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD\r
|
|
VERSION:4.0\r
|
|
FN:Alice Wonderland\r
|
|
N:Wonderland;Alice;;;Ms.\r
|
|
GENDER:W\r
|
|
EMAIL;TYPE=work:alice@example.com\r
|
|
KEY;TYPE=PGP;ENCODING=b:[base64-data]\r
|
|
REV:20240418T184242Z\r
|
|
END:VCARD\r
|
|
\r",
|
|
);
|
|
|
|
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
|
|
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
|
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
|
|
assert_eq!(contacts[0].profile_image, None);
|
|
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
|
|
|
|
assert_eq!(contacts.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_make_and_parse_vcard() {
|
|
let contacts = [
|
|
VcardContact {
|
|
addr: "alice@example.org".to_string(),
|
|
authname: "Alice Wonderland".to_string(),
|
|
key: Some("[base64-data]".to_string()),
|
|
profile_image: Some("image in Base64".to_string()),
|
|
biography: Some("Hi, I'm Alice".to_string()),
|
|
timestamp: Ok(1713465762),
|
|
},
|
|
VcardContact {
|
|
addr: "bob@example.com".to_string(),
|
|
authname: "".to_string(),
|
|
key: None,
|
|
profile_image: None,
|
|
biography: None,
|
|
timestamp: Ok(0),
|
|
},
|
|
];
|
|
let items = [
|
|
"BEGIN:VCARD\r\n\
|
|
VERSION:4.0\r\n\
|
|
EMAIL:alice@example.org\r\n\
|
|
FN:Alice Wonderland\r\n\
|
|
KEY:data:application/pgp-keys;base64\\,[base64-data]\r\n\
|
|
PHOTO:data:image/jpeg;base64\\,image in Base64\r\n\
|
|
NOTE:Hi\\, I'm Alice\r\n\
|
|
REV:20240418T184242Z\r\n\
|
|
END:VCARD\r\n",
|
|
"BEGIN:VCARD\r\n\
|
|
VERSION:4.0\r\n\
|
|
EMAIL:bob@example.com\r\n\
|
|
FN:bob@example.com\r\n\
|
|
REV:19700101T000000Z\r\n\
|
|
END:VCARD\r\n",
|
|
];
|
|
let mut expected = "".to_string();
|
|
for len in 0..=contacts.len() {
|
|
let contacts = &contacts[0..len];
|
|
let vcard = make_vcard(contacts);
|
|
if len > 0 {
|
|
expected += items[len - 1];
|
|
}
|
|
assert_eq!(vcard, expected);
|
|
let parsed = parse_vcard(&vcard);
|
|
assert_eq!(parsed.len(), contacts.len());
|
|
for i in 0..parsed.len() {
|
|
assert_eq!(parsed[i].addr, contacts[i].addr);
|
|
assert_eq!(parsed[i].authname, contacts[i].authname);
|
|
assert_eq!(parsed[i].key, contacts[i].key);
|
|
assert_eq!(parsed[i].profile_image, contacts[i].profile_image);
|
|
assert_eq!(
|
|
parsed[i].timestamp.as_ref().unwrap(),
|
|
contacts[i].timestamp.as_ref().unwrap()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_android() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD
|
|
VERSION:2.1
|
|
N:;Bob;;;
|
|
FN:Bob
|
|
TEL;CELL:+1-234-567-890
|
|
EMAIL;HOME:bob@example.org
|
|
END:VCARD
|
|
BEGIN:VCARD
|
|
VERSION:2.1
|
|
N:;Alice;;;
|
|
FN:Alice
|
|
EMAIL;HOME:alice@example.org
|
|
END:VCARD
|
|
",
|
|
);
|
|
|
|
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
|
assert_eq!(contacts[0].authname, "Bob".to_string());
|
|
assert_eq!(contacts[0].key, None);
|
|
assert_eq!(contacts[0].profile_image, None);
|
|
|
|
assert_eq!(contacts[1].addr, "alice@example.org".to_string());
|
|
assert_eq!(contacts[1].authname, "Alice".to_string());
|
|
assert_eq!(contacts[1].key, None);
|
|
assert_eq!(contacts[1].profile_image, None);
|
|
|
|
assert_eq!(contacts.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_local_datetime() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD\n\
|
|
VERSION:4.0\n\
|
|
FN:Alice Wonderland\n\
|
|
EMAIL;TYPE=work:alice@example.org\n\
|
|
REV:20240418T184242\n\
|
|
END:VCARD",
|
|
);
|
|
assert_eq!(contacts.len(), 1);
|
|
assert_eq!(contacts[0].addr, "alice@example.org".to_string());
|
|
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
|
assert_eq!(
|
|
*contacts[0].timestamp.as_ref().unwrap(),
|
|
chrono::offset::Local
|
|
.with_ymd_and_hms(2024, 4, 18, 18, 42, 42)
|
|
.unwrap()
|
|
.timestamp()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_with_base64_avatar() {
|
|
// This is not an actual base64-encoded avatar, it's just to test the parsing.
|
|
// This one is Android-like.
|
|
let vcard0 = "BEGIN:VCARD
|
|
VERSION:2.1
|
|
N:;Bob;;;
|
|
FN:Bob
|
|
EMAIL;HOME:bob@example.org
|
|
PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU
|
|
AAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAA
|
|
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
|
|
|
|
END:VCARD
|
|
";
|
|
// This one is DOS-like.
|
|
let vcard1 = vcard0.replace('\n', "\r\n");
|
|
for vcard in [vcard0, vcard1.as_str()] {
|
|
let contacts = parse_vcard(vcard);
|
|
assert_eq!(contacts.len(), 1);
|
|
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
|
assert_eq!(contacts[0].authname, "Bob".to_string());
|
|
assert_eq!(contacts[0].key, None);
|
|
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_protonmail_vcard() {
|
|
let contacts = parse_vcard(
|
|
"BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN;PREF=1:Alice Wonderland
|
|
UID:proton-web-03747582-328d-38dc-5ddd-000000000000
|
|
ITEM1.EMAIL;PREF=1:alice@example.org
|
|
ITEM1.KEY;PREF=1:data:application/pgp-keys;base64,aaaaaaaaaaaaaaaaaaaaaaaaa
|
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
ITEM1.KEY;PREF=2:data:application/pgp-keys;base64,bbbbbbbbbbbbbbbbbbbbbbbbb
|
|
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
ITEM1.X-PM-ENCRYPT:true
|
|
ITEM1.X-PM-SIGN:true
|
|
END:VCARD",
|
|
);
|
|
|
|
assert_eq!(contacts.len(), 1);
|
|
assert_eq!(&contacts[0].addr, "alice@example.org");
|
|
assert_eq!(&contacts[0].authname, "Alice Wonderland");
|
|
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
assert!(contacts[0].timestamp.is_err());
|
|
assert_eq!(contacts[0].profile_image, None);
|
|
}
|
|
|
|
/// Proton at some point slightly changed the format of their vcards.
|
|
/// This also tests unescaped commas in PHOTO and KEY (old Delta Chat format).
|
|
#[test]
|
|
fn test_protonmail_vcard2() {
|
|
let contacts = parse_vcard(
|
|
r"BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN;PREF=1:Alice
|
|
PHOTO;PREF=1:data:image/jpeg;base64,/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z
|
|
REV:Invalid Date
|
|
ITEM1.EMAIL;PREF=1:alice@example.org
|
|
KEY;PREF=1:data:application/pgp-keys;base64,xsaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==
|
|
UID:proton-web-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
|
END:VCARD",
|
|
);
|
|
|
|
assert_eq!(contacts.len(), 1);
|
|
assert_eq!(&contacts[0].addr, "alice@example.org");
|
|
assert_eq!(&contacts[0].authname, "Alice");
|
|
assert_eq!(contacts[0].key.as_ref().unwrap(), "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==");
|
|
assert!(contacts[0].timestamp.is_err());
|
|
assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z");
|
|
}
|