diff --git a/Cargo.lock b/Cargo.lock index a4a87a6ca..6806b38aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -1031,7 +1068,7 @@ dependencies = [ "hex", "image", "indexmap", - "itertools", + "itertools 0.9.0", "kamadak-exif", "lettre_email", "libc", @@ -1046,7 +1083,7 @@ dependencies = [ "pretty_assertions", "pretty_env_logger", "proptest", - "quick-xml", + "quick-xml 0.18.1", "r2d2", "r2d2_sqlite", "rand", @@ -1067,6 +1104,7 @@ dependencies = [ "toml", "url", "uuid", + "vcard", ] [[package]] @@ -1209,6 +1247,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 +1385,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 +1426,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" @@ -1833,6 +1918,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 +2255,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 +2359,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 +2485,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 +2560,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 +2740,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 +2908,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 +3298,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 +3778,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" diff --git a/Cargo.toml b/Cargo.toml index d1e739b69..9308b8b45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ 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" pretty_env_logger = { version = "0.4.0", optional = true } log = {version = "0.4.8", optional = true } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 9e18696c7..1c747683a 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -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) { + 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,36 @@ async fn build_body_file( Ok((mail, filename_to_send)) } +fn build_vcard_file(context: &Context, avatar_path: &str) -> Result { + let avatar_blob = BlobObject::from_path(context, avatar_path)?; + + let mut vcard = VCard::from_formatted_name_str("Display name goes here")?; + // 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()) +} + +fn build_vcard_part(context: &Context, avatar_path: &str) -> Result { + let body = build_vcard_file(context, avatar_path)?; + 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() {