Compare commits

...

6 Commits

Author SHA1 Message Date
Alexander Krotov
2edd0b682a WIP vcard parsing 2020-11-25 00:02:54 +03:00
Alexander Krotov
0d2aaec742 Add ical dependency 2020-11-23 01:43:08 +03:00
Alexander Krotov
241a9c5990 Drop incoming vCards
Otherwise they show up as attachments in the chat.
2020-11-23 01:06:41 +03:00
Alexander Krotov
2dd90c2509 Add vcard.eml 2020-11-23 00:37:11 +03:00
Alexander Krotov
0c70624e21 Put display name into vCard 2020-11-22 22:35:44 +03:00
Alexander Krotov
5ad85c52bc Send avatar in vCard 2020-11-22 21:32:19 +03:00
5 changed files with 339 additions and 12 deletions

212
Cargo.lock generated
View File

@@ -452,6 +452,27 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64-stream"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6596cd4b981cb9e85a9bddf2d1c3a76ebffe64f9e6b0ea6f053d810aea7807c"
dependencies = [
"base64 0.13.0",
"educe",
"generic-array",
]
[[package]]
name = "bincode"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
dependencies = [
"byteorder",
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.2"
@@ -685,6 +706,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "chrono-tz"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120"
dependencies = [
"chrono",
"parse-zoneinfo",
]
[[package]]
name = "circular"
version = "0.3.0"
@@ -812,7 +843,7 @@ dependencies = [
"clap",
"criterion-plot",
"csv",
"itertools",
"itertools 0.9.0",
"lazy_static",
"num-traits",
"oorandom",
@@ -834,7 +865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
dependencies = [
"cast",
"itertools",
"itertools 0.9.0",
]
[[package]]
@@ -991,6 +1022,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d0e2d24e5ee3b23a01de38eefdcd978907890701f08ffffd4cb457ca4ee8d6"
[[package]]
name = "debug-helper"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a5bb894f24f42c247f19b25928a88e31867c0f84552c05df41a9dd527435e"
[[package]]
name = "deflate"
version = "0.8.6"
@@ -1029,9 +1066,10 @@ dependencies = [
"futures",
"futures-lite",
"hex",
"ical",
"image",
"indexmap",
"itertools",
"itertools 0.9.0",
"kamadak-exif",
"lettre_email",
"libc",
@@ -1046,7 +1084,7 @@ dependencies = [
"pretty_assertions",
"pretty_env_logger",
"proptest",
"quick-xml",
"quick-xml 0.18.1",
"r2d2",
"r2d2_sqlite",
"rand",
@@ -1067,6 +1105,7 @@ dependencies = [
"toml",
"url",
"uuid",
"vcard",
]
[[package]]
@@ -1209,6 +1248,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "educe"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7260c7e6e656fc7702a1aa8d5b498a1a69aa84ac4ffcd5501b7d26939f368a93"
dependencies = [
"enum-ordinalize",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.6.1"
@@ -1335,6 +1386,19 @@ dependencies = [
"syn",
]
[[package]]
name = "enum-ordinalize"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1676e1daadfd216bda88d3a6fedd1bf53b829a085f5cc4d81c6f3054f50ef983"
dependencies = [
"num-bigint 0.3.1",
"num-traits",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -1363,6 +1427,28 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
@@ -1754,6 +1840,15 @@ dependencies = [
"quick-error",
]
[[package]]
name = "ical"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9f7215ad0d77e69644570dee000c7678a47ba7441062c1b5f918adde0d73cf"
dependencies = [
"thiserror",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1833,6 +1928,15 @@ dependencies = [
"winreg",
]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.9.0"
@@ -2161,6 +2265,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.6.0"
@@ -2254,6 +2369,12 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
[[package]]
name = "oncemutex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "oorandom"
version = "11.1.2"
@@ -2374,6 +2495,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
dependencies = [
"regex",
]
[[package]]
name = "pem"
version = "0.8.1"
@@ -2440,6 +2570,26 @@ dependencies = [
"zeroize",
]
[[package]]
name = "phonenumber"
version = "0.2.4+8.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207b3a8b4b30da166f6d8175ad39b86aa84d190965be4533af3d5541754de358"
dependencies = [
"bincode",
"either",
"failure",
"fnv",
"itertools 0.8.2",
"lazy_static",
"nom 5.1.2",
"quick-xml 0.17.2",
"regex",
"regex-cache",
"serde",
"serde_derive",
]
[[package]]
name = "pin-project"
version = "0.4.25"
@@ -2600,6 +2750,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.18.1"
@@ -2759,6 +2918,18 @@ dependencies = [
"byteorder",
]
[[package]]
name = "regex-cache"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c86351f6af6bbf23b4c5f73ee4fdfe92d298fdf28572ea4f69494cabe38699"
dependencies = [
"lru-cache",
"oncemutex",
"regex",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
@@ -3137,7 +3308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint",
"num-bigint 0.2.6",
"num-traits",
]
@@ -3617,6 +3788,37 @@ dependencies = [
"serde",
]
[[package]]
name = "validators"
version = "0.20.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af4acf80e223db35334bfb3b09421c54ed259ca203b5189177e61e3b9e1c2a3"
dependencies = [
"debug-helper",
"lazy_static",
"num-traits",
"phonenumber",
"regex",
]
[[package]]
name = "vcard"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c988b5461430fd8d078237c8a9436c8e2f810f944cba3086e3ce99cd54652caf"
dependencies = [
"base64-stream",
"chrono",
"chrono-tz",
"idna",
"lazy_static",
"mime",
"mime_guess",
"percent-encoding",
"regex",
"validators",
]
[[package]]
name = "vcpkg"
version = "0.2.10"

View File

@@ -61,6 +61,8 @@ url = "2.1.1"
async-std-resolver = "0.19.5"
async-tar = "0.3.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
vcard = "0.4.6"
ical = { version = "0.7.0", default-features = false, features = ["vcard"] }
pretty_env_logger = { version = "0.4.0", optional = true }
log = {version = "0.4.8", optional = true }

View File

@@ -1,5 +1,9 @@
use chrono::TimeZone;
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
use std::collections::HashSet;
use vcard::properties::Photo;
use vcard::values::image_value::ImageValue;
use vcard::{Set, VCard};
use crate::blob::BlobObject;
use crate::chat::{self, Chat};
@@ -991,13 +995,24 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if self.attach_selfavatar {
match context.get_config(Config::Selfavatar).await {
Some(path) => match build_selfavatar_file(context, &path) {
Ok((part, filename)) => {
parts.push(part);
protected_headers.push(Header::new("Chat-User-Avatar".into(), filename))
}
Err(err) => warn!(context, "mimefactory: cannot attach selfavatar: {}", err),
},
Some(path) => {
match build_selfavatar_file(context, &path) {
Ok((part, filename)) => {
parts.push(part);
protected_headers.push(Header::new("Chat-User-Avatar".into(), filename))
}
Err(err) => {
warn!(context, "mimefactory: cannot attach selfavatar: {}", err)
}
};
match build_vcard_part(context, &path).await {
Ok(part) => {
parts.push(part);
}
Err(err) => warn!(context, "mimefactory: cannot build vCard: {}", err),
};
}
None => protected_headers.push(Header::new("Chat-User-Avatar".into(), "0".into())),
}
}
@@ -1199,6 +1214,40 @@ async fn build_body_file(
Ok((mail, filename_to_send))
}
async fn build_vcard_file(context: &Context, avatar_path: &str) -> Result<String, Error> {
let avatar_blob = BlobObject::from_path(context, avatar_path)?;
let displayname = context
.get_config(Config::Displayname)
.await
.unwrap_or_default();
let mut vcard = VCard::from_formatted_name_str(&displayname)?;
// TODO: add KIND:individual
let mut photos = HashSet::new();
if let Ok(image_value) = ImageValue::from_file(avatar_blob.to_abs_path()) {
let photo = Photo::from_image_value(image_value);
photos.insert(photo);
}
vcard.photos = Set::from_hash_set(photos).ok();
Ok(vcard.to_string())
}
async fn build_vcard_part(context: &Context, avatar_path: &str) -> Result<PartBuilder, Error> {
let body = build_vcard_file(context, avatar_path).await?;
let encoded_body = wrapped_base64_encode(&body.as_bytes());
let part = PartBuilder::new()
.content_type(&mime::TEXT_VCARD)
.header((
"Content-Disposition",
"attachment; filename=\"{avatar.vcf}\"",
))
.header(("Content-Transfer-Encoding", "base64"))
.body(encoded_body);
Ok(part)
}
fn build_selfavatar_file(context: &Context, path: &str) -> Result<(PartBuilder, String), Error> {
let blob = BlobObject::from_path(context, path)?;
let filename_to_send = match blob.suffix() {

View File

@@ -814,6 +814,26 @@ impl MimeMessage {
return;
}
}
if mime_type.type_() == "text/x-vcard"
|| mime_type.type_() == "text/vcard"
|| filename.ends_with(".vcf")
|| filename.ends_with(".vcard")
{
println!("Parsing vcard {:?}", String::from_utf8_lossy(decoded_data));
for contact in ical::VcardParser::new(decoded_data) {
println!("Parsing contact {:?}", contact);
if let Ok(contact) = contact {
for property in contact.properties {
println!("Parsed property {:?}", property);
if property.name == "email" {
}
}
}
}
return;
}
/* we have a regular file attachment,
write decoded data to new blob object */
@@ -1632,6 +1652,22 @@ mod tests {
assert_eq!(mimeparser.group_avatar, None);
}
#[async_std::test]
async fn test_mimeparser_with_vcard() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/vcard.eml");
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
assert_eq!(mimeparser.parts.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
assert_eq!(
mimeparser.parts[0].msg,
"vCard example An example of vCard sent from Thunderbird."
);
assert_eq!(mimeparser.user_avatar, None);
assert_eq!(mimeparser.group_avatar, None);
}
#[async_std::test]
async fn test_mimeparser_message_kml() {
let context = TestContext::new().await;

View File

@@ -0,0 +1,38 @@
Return-Path: <alice@example.org>
Delivered-To: bob@example.org
To: bob@example.org
From: Alice <alice@example.org>
Subject: vCard example
Message-ID: <foobar@example.org>
Date: Sun, 22 Nov 2020 22:44:00 +0000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101
Thunderbird/78.4.3
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="------------01973B3475205164D5F9F507"
Content-Language: en-US
This is a multi-part message in MIME format.
--------------01973B3475205164D5F9F507
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
An example of vCard sent from Thunderbird.
--------------01973B3475205164D5F9F507
Content-Type: text/x-vcard; charset=utf-8;
name="alice.vcf"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="alice.vcf"
begin:vcard
fn:Alice Foobar
n:Foobar;Alice
email;internet:alice@example.org
x-mozilla-html:FALSE
version:2.1
end:vcard
--------------01973B3475205164D5F9F507--