diff --git a/Cargo.lock b/Cargo.lock index 788cf7d8..3123920c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -989,16 +989,16 @@ dependencies = [ [[package]] name = "calcard" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75eaecaebc21cf56381d401db71ed288d61f3fcbb880c777e08cab2f82dfc0ab" +checksum = "8e16f081c4da49b29187103679784b4dcf2fbda52e9b519388c397cb58414e5a" dependencies = [ "ahash", "chrono", "chrono-tz", "hashify", "mail-builder", - "mail-parser 0.10.2", + "mail-parser", "rkyv", "serde", ] @@ -1289,7 +1289,7 @@ dependencies = [ "libc", "mail-auth", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "md5", "nlp", @@ -1776,7 +1776,7 @@ dependencies = [ "compact_str", "hashify", "hyper 1.6.0", - "mail-parser 0.11.0", + "mail-parser", "quick-xml 0.37.5", "rkyv", "serde", @@ -1974,7 +1974,7 @@ dependencies = [ "jmap_proto", "ldap3", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "md5", "nlp", @@ -2252,7 +2252,7 @@ dependencies = [ "hashify", "jmap_proto", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "nlp", "rand 0.8.5", "rasn", @@ -3174,7 +3174,7 @@ dependencies = [ "jmap_proto", "mail-auth", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "mime", "pkcs8", @@ -3576,7 +3576,7 @@ dependencies = [ "imap_proto", "indexmap 2.9.0", "jmap_proto", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "md5", "nlp", @@ -3600,7 +3600,7 @@ dependencies = [ "compact_str", "hashify", "jmap_proto", - "mail-parser 0.11.0", + "mail-parser", "store", "tokio", "trc", @@ -3860,7 +3860,7 @@ dependencies = [ "lz4_flex", "mail-auth", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "nlp", "p256", @@ -3916,7 +3916,7 @@ dependencies = [ "compact_str", "fast-float", "hashify", - "mail-parser 0.11.0", + "mail-parser", "rkyv", "serde", "serde_json", @@ -4332,7 +4332,7 @@ dependencies = [ "flate2", "hickory-resolver", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "quick-xml 0.37.5", "quick_cache", "rand 0.8.5", @@ -4354,16 +4354,6 @@ dependencies = [ "gethostname", ] -[[package]] -name = "mail-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187a2b93c4c8c32f552ee06c2d99915e575de2fc7e04b07891c9edfee5b8edd6" -dependencies = [ - "encoding_rs", - "hashify", -] - [[package]] name = "mail-parser" version = "0.11.0" @@ -4405,7 +4395,7 @@ dependencies = [ "imap", "imap_proto", "jmap_proto", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "md5", "parking_lot", @@ -4508,7 +4498,7 @@ dependencies = [ "jmap_proto", "lz4_flex", "mail-auth", - "mail-parser 0.11.0", + "mail-parser", "nlp", "rkyv", "serde", @@ -5467,7 +5457,7 @@ dependencies = [ "email", "imap", "jmap_proto", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "rustls 0.23.27", "store", @@ -7296,7 +7286,7 @@ dependencies = [ "email", "hkdf", "jmap_proto", - "mail-parser 0.11.0", + "mail-parser", "memory-stats", "p256", "reqwest 0.12.15", @@ -7421,7 +7411,7 @@ dependencies = [ "fancy-regex", "hashify", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "rkyv", "serde", ] @@ -7508,7 +7498,7 @@ dependencies = [ "lru-cache", "mail-auth", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "md5", "nlp", @@ -7593,7 +7583,7 @@ dependencies = [ "infer 0.19.0", "mail-auth", "mail-builder", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "nlp", "psl", @@ -7684,7 +7674,7 @@ dependencies = [ "indicatif", "jmap-client", "mail-auth", - "mail-parser 0.11.0", + "mail-parser", "num_cpus", "prettytable-rs", "pwhash", @@ -7946,7 +7936,7 @@ dependencies = [ "jmap-client", "jmap_proto", "mail-auth", - "mail-parser 0.11.0", + "mail-parser", "mail-send", "managesieve", "migration", @@ -8467,7 +8457,7 @@ dependencies = [ "compact_str", "event_macro", "mail-auth", - "mail-parser 0.11.0", + "mail-parser", "parking_lot", "reqwest 0.12.15", "rkyv", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ba315e29..1780ba4e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -19,7 +19,7 @@ mail-auth = { version = "0.7" } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } smtp-proto = { version = "0.1", features = ["rkyv"] } dns-update = { version = "0.1" } -calcard = { version = "0.1", features = ["rkyv"] } +calcard = { version = "0.1.1", features = ["rkyv"] } ahash = { version = "0.8.2", features = ["serde"] } parking_lot = "0.12.1" regex = "1.7.0" diff --git a/crates/dav-proto/Cargo.toml b/crates/dav-proto/Cargo.toml index 537fb922..6dfc46cc 100644 --- a/crates/dav-proto/Cargo.toml +++ b/crates/dav-proto/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" trc = { path = "../trc" } hashify = "0.2.6" quick-xml = "0.37.2" -calcard = { version = "0.1", features = ["rkyv"] } +calcard = { version = "0.1.1", features = ["rkyv"] } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } hyper = "1.6.0" rkyv = { version = "0.8.10", features = ["little_endian"] } @@ -15,7 +15,7 @@ chrono = { version = "0.4.40", features = ["serde"], optional = true } compact_str = "0.9.0" [dev-dependencies] -calcard = { version = "0.1", features = ["serde", "rkyv"] } +calcard = { version = "0.1.1", features = ["serde", "rkyv"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" chrono = { version = "0.4.40", features = ["serde"] } diff --git a/crates/dav-proto/src/lib.rs b/crates/dav-proto/src/lib.rs index d5841c06..bfb92c7a 100644 --- a/crates/dav-proto/src/lib.rs +++ b/crates/dav-proto/src/lib.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use calcard::vcard::VCardVersion; use compact_str::{CompactString, ToCompactString}; use trc::Value; @@ -40,6 +41,7 @@ pub struct RequestHeaders<'x> { pub content_type: Option<&'x str>, pub destination: Option<&'x str>, pub lock_token: Option<&'x str>, + pub max_vcard_version: Option, pub overwrite_fail: bool, pub no_timezones: bool, pub ret: Return, diff --git a/crates/dav-proto/src/parser/header.rs b/crates/dav-proto/src/parser/header.rs index 08e19088..d6238376 100644 --- a/crates/dav-proto/src/parser/header.rs +++ b/crates/dav-proto/src/parser/header.rs @@ -4,6 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use calcard::vcard::VCardVersion; + use crate::{Condition, Depth, If, RequestHeaders, ResourceState, Return, Timeout}; impl<'x> RequestHeaders<'x> { @@ -82,6 +84,23 @@ impl<'x> RequestHeaders<'x> { } return true; }, + "Accept" => { + for value in value.split(',') { + if value.trim().starts_with("text/vcard") { + if let Some(version) = value.split_once("version=") + .and_then(|(_, version)| VCardVersion::try_parse(version.trim())) { + if let Some(max_vcard_version) = &mut self.max_vcard_version { + if version > *max_vcard_version { + *max_vcard_version = version; + } + } else { + self.max_vcard_version = Some(version); + } + } + } + } + return true; + }, _ => {} ); diff --git a/crates/dav/Cargo.toml b/crates/dav/Cargo.toml index 3eb4608f..64d5df0d 100644 --- a/crates/dav/Cargo.toml +++ b/crates/dav/Cargo.toml @@ -14,7 +14,7 @@ directory = { path = "../directory" } http_proto = { path = "../http-proto" } jmap_proto = { path = "../jmap-proto" } trc = { path = "../trc" } -calcard = { version = "0.1", features = ["rkyv"] } +calcard = { version = "0.1.1", features = ["rkyv"] } hashify = { version = "0.2" } hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } percent-encoding = "2.3.1" diff --git a/crates/dav/src/card/get.rs b/crates/dav/src/card/get.rs index c755a6b3..284ada98 100644 --- a/crates/dav/src/card/get.rs +++ b/crates/dav/src/card/get.rs @@ -105,7 +105,14 @@ impl CardGetRequestHandler for Server { .with_etag(etag) .with_last_modified(Rfc1123DateTime::new(i64::from(card.modified)).to_string()); - let vcard = card.card.to_string(); + let mut vcard = String::with_capacity(128); + let _ = card.card.write_to( + &mut vcard, + headers + .max_vcard_version + .or_else(|| card.card.version()) + .unwrap_or_default(), + ); if !is_head { Ok(response.with_binary_body(vcard)) diff --git a/crates/dav/src/card/query.rs b/crates/dav/src/card/query.rs index ac3c63fa..90e58a60 100644 --- a/crates/dav/src/card/query.rs +++ b/crates/dav/src/card/query.rs @@ -13,7 +13,8 @@ use crate::{ }, }; use calcard::vcard::{ - ArchivedVCard, ArchivedVCardEntry, ArchivedVCardParameter, VCardParameterName, + ArchivedVCard, ArchivedVCardEntry, ArchivedVCardParameter, VCardParameterName, VCardProperty, + VCardVersion, }; use common::{Server, auth::AccessToken}; use dav_proto::{ @@ -200,22 +201,30 @@ fn find_parameter<'x>( pub(crate) fn serialize_vcard_with_props( card: &ArchivedVCard, props: &[CardDavPropertyName], + version: Option, ) -> String { + let mut vcard = String::with_capacity(128); + let version = version.or_else(|| card.version()).unwrap_or_default(); if !props.is_empty() { - let mut vcard = String::with_capacity(128); let _ = write!(&mut vcard, "BEGIN:VCARD\r\n"); + let is_v4 = matches!(version, VCardVersion::V4_0); for entry in card.entries.iter() { for item in props { if entry.name == item.name && entry.group == item.group { - let _ = entry.write_to(&mut vcard, !item.no_value); + if item.name != VCardProperty::Version { + let _ = entry.write_to(&mut vcard, !item.no_value, is_v4); + } else { + let _ = write!(&mut vcard, "VERSION:{version}\r\n"); + } break; } } } let _ = write!(&mut vcard, "END:VCARD\r\n"); - vcard } else { - card.to_string() + let _ = card.write_to(&mut vcard, version); } + + vcard } diff --git a/crates/dav/src/common/mod.rs b/crates/dav/src/common/mod.rs index 4717cad0..dbfb9a08 100644 --- a/crates/dav/src/common/mod.rs +++ b/crates/dav/src/common/mod.rs @@ -6,7 +6,7 @@ use calcard::{ icalendar::{ICalendarComponentType, ICalendarParameterName, ICalendarProperty}, - vcard::VCardParameterName, + vcard::{VCardParameterName, VCardVersion}, }; use dav_proto::{ Depth, RequestHeaders, Return, @@ -35,7 +35,7 @@ pub mod lock; pub mod propfind; pub mod uri; -#[derive(Default, Debug)] +#[derive(Debug)] pub(crate) struct DavQuery<'x> { pub uri: &'x str, pub resource: DavQueryResource<'x>, @@ -43,6 +43,7 @@ pub(crate) struct DavQuery<'x> { pub sync_type: SyncType, pub depth: usize, pub limit: Option, + pub max_vcard_version: Option, pub ret: Return, pub depth_no_root: bool, pub expand: bool, @@ -158,7 +159,10 @@ impl<'x> DavQuery<'x> { ret: headers.ret, depth_no_root: headers.depth_no_root, uri: headers.uri, - ..Default::default() + max_vcard_version: headers.max_vcard_version, + sync_type: Default::default(), + limit: Default::default(), + expand: Default::default(), } } @@ -176,7 +180,11 @@ impl<'x> DavQuery<'x> { ret: headers.ret, depth_no_root: headers.depth_no_root, uri: headers.uri, - ..Default::default() + max_vcard_version: headers.max_vcard_version, + sync_type: Default::default(), + depth: Default::default(), + limit: Default::default(), + expand: Default::default(), } } @@ -196,7 +204,10 @@ impl<'x> DavQuery<'x> { ret: headers.ret, depth_no_root: headers.depth_no_root, uri: headers.uri, - ..Default::default() + max_vcard_version: headers.max_vcard_version, + sync_type: Default::default(), + depth: Default::default(), + expand: Default::default(), } } @@ -220,7 +231,11 @@ impl<'x> DavQuery<'x> { ret: headers.ret, depth_no_root: headers.depth_no_root, uri: headers.uri, - ..Default::default() + sync_type: Default::default(), + depth: Default::default(), + limit: Default::default(), + max_vcard_version: Default::default(), + expand: Default::default(), } } @@ -249,6 +264,7 @@ impl<'x> DavQuery<'x> { depth_no_root: headers.depth_no_root, expand: false, uri: headers.uri, + max_vcard_version: headers.max_vcard_version, } } @@ -277,7 +293,9 @@ impl<'x> DavQuery<'x> { depth_no_root: headers.depth_no_root, expand: true, uri: headers.uri, - ..Default::default() + sync_type: Default::default(), + limit: Default::default(), + max_vcard_version: headers.max_vcard_version, } } diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 69757954..47757f6a 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -1191,6 +1191,9 @@ impl PropFindRequestHandler for Server { DavValue::CData(serialize_vcard_with_props( &card.inner.card, items, + query + .max_vcard_version + .or_else(|| card.inner.card.version()), )), )); } diff --git a/crates/dav/src/principal/matching.rs b/crates/dav/src/principal/matching.rs index 66c34dab..905f3ff3 100644 --- a/crates/dav/src/principal/matching.rs +++ b/crates/dav/src/principal/matching.rs @@ -69,7 +69,10 @@ impl PrincipalMatching for Server { ret: headers.ret, depth_no_root: headers.depth_no_root, uri: headers.uri, - ..Default::default() + sync_type: Default::default(), + limit: Default::default(), + max_vcard_version: Default::default(), + expand: Default::default(), }, ) .await; diff --git a/crates/groupware/Cargo.toml b/crates/groupware/Cargo.toml index d1e2c136..54bc0e3c 100644 --- a/crates/groupware/Cargo.toml +++ b/crates/groupware/Cargo.toml @@ -12,7 +12,7 @@ jmap_proto = { path = "../jmap-proto" } trc = { path = "../trc" } directory = { path = "../directory" } dav-proto = { path = "../dav-proto" } -calcard = { version = "0.1", features = ["rkyv"] } +calcard = { version = "0.1.1", features = ["rkyv"] } hashify = "0.2" tokio = { version = "1.45", features = ["net", "macros"] } rkyv = { version = "0.8.10", features = ["little_endian"] } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index b65925ea..8bd7aee9 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -29,7 +29,7 @@ imap = { path = "../crates/imap", features = ["test_mode"] } imap_proto = { path = "../crates/imap-proto" } dav = { path = "../crates/dav", features = ["test_mode"] } dav-proto = { path = "../crates/dav-proto", features = ["test_mode"] } -calcard = { version = "0.1", features = ["rkyv"] } +calcard = { version = "0.1.1", features = ["rkyv"] } groupware = { path = "../crates/groupware", features = ["test_mode"] } http = { path = "../crates/http", features = ["test_mode", "enterprise"] } http_proto = { path = "../crates/http-proto" }