mirror of
https://github.com/chatmail/core.git
synced 2026-09-22 04:58:47 +03:00
Largest change is in the FFI crate.
With 2024 (but not 2021) edition unsafe code
inside unsafe functions should be marked separately
so we can mark exactly the code that is unsafe.
Some CFFI functions even have no unsafe code inside.
Most interesting change is that .strdup()
functions are not marked as unsafe anymore.
They are allocating memory and return raw pointers,
but there is nothing unsafe about it.
Only using the returned raw pointers is unsafe.
This way calls to .strdup() don't have to be marked
with unsafe{} blocks.
302 lines
9.5 KiB
Rust
302 lines
9.5 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,\nI'm Alice; and this is a backslash: \\".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\\,\\nI'm Alice\\; and this is a backslash: \\\\\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"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vcard_value_escape_unescape() {
|
|
let original = "Text, with; chars and a \\ and a newline\nand a literal newline \\n";
|
|
let expected_escaped = r"Text\, with\; chars and a \\ and a newline\nand a literal newline \\n";
|
|
|
|
let escaped = escape(original);
|
|
assert_eq!(escaped, expected_escaped);
|
|
let unescaped = unescape(&escaped);
|
|
assert_eq!(original, unescaped);
|
|
}
|