mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-09-07 04:24:15 +08:00
Legacy vCard 2.1 and 3.0 serialization support
This commit is contained in:
parent
30907f6a00
commit
48cc823432
13 changed files with 104 additions and 53 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<VCardVersion>,
|
||||
pub overwrite_fail: bool,
|
||||
pub no_timezones: bool,
|
||||
pub ret: Return,
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
_ => {}
|
||||
);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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<VCardVersion>,
|
||||
) -> 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
|
||||
}
|
||||
|
|
|
@ -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<u32>,
|
||||
pub max_vcard_version: Option<VCardVersion>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()),
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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" }
|
||||
|
|
Loading…
Add table
Reference in a new issue