From 2cb765ed37e01fe6f3d7fdba9374b28edde5f12f Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Mon, 15 Jun 2026 12:57:37 +0200 Subject: [PATCH] Fix CardDAV: Version requests included in `address-data` are ignored --- CHANGELOG.md | 3 +- Cargo.lock | 151 +++++++++++------- .../resources/requests/report-014.json | 68 ++++---- .../resources/requests/report-015.json | 68 ++++---- .../resources/requests/report-017.json | 68 ++++---- .../resources/requests/report-018.json | 4 +- crates/dav-proto/src/parser/header.rs | 2 +- crates/dav-proto/src/parser/property.rs | 26 ++- crates/dav-proto/src/requests/mod.rs | 35 +++- crates/dav-proto/src/responses/mod.rs | 5 +- crates/dav-proto/src/responses/property.rs | 2 +- crates/dav-proto/src/schema/property.rs | 8 +- crates/dav/src/card/mod.rs | 5 +- crates/dav/src/common/propfind.rs | 11 +- crates/smtp/src/queue/spool.rs | 4 +- tests/src/webdav/card_query.rs | 28 ++-- tests/src/webdav/multiget.rs | 7 +- 17 files changed, 299 insertions(+), 196 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f24316..8b947b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - JMAP: `*/changes` methods leak ids of non-shared objects (reported by @5ud0er). - Sieve: Do not allow invalid certs in `http_header` function. - FoundationDB: Fix read version cache expiration logic. -- MTA: Re-scheduling or editing a queued message reports success but persists nothing for recipients in a non-`default` virtual queue (reported by @DorianCoding). +- MTA: Re-scheduling or editing a queued message reports success but persists nothing for recipients in a non-`default` virtual queue. +- CardDAV: Version requests included in `address-data` are ignored. ## [0.16.8] - 2026-06-06 diff --git a/Cargo.lock b/Cargo.lock index 4f75b04f..4e972daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -985,9 +985,9 @@ dependencies = [ [[package]] name = "calcard" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d4f7ea9ab08310fba58c0010e3c203aaa2b6464f1d4886d36855d99b6b6036" +checksum = "4c253992730ab0e6e0994eaa50521b369789147f4dd70d077efc1e60c856da28" dependencies = [ "ahash", "chrono", @@ -1050,9 +1050,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -1926,7 +1926,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -3998,9 +3997,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.100" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", @@ -4428,6 +4427,29 @@ dependencies = [ "utils", ] +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + [[package]] name = "maplit" version = "1.0.2" @@ -4499,9 +4521,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memory-stats" @@ -4628,15 +4650,15 @@ dependencies = [ [[package]] name = "mysql-common-derive" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f62cad7623a9cb6f8f64037f0c4f69c8db8e82914334a83c9788201c2c1bfa" +checksum = "a4db8a44120571277accfaa3f3d91e7d3989d601d817c2fc01a9391b86135666" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "heck", + "manyhow", "num-bigint", "proc-macro-crate", - "proc-macro-error2", "proc-macro2", "quote", "syn", @@ -5018,9 +5040,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.80" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ "bitflags", "cfg-if", @@ -5049,9 +5071,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.116" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -5556,9 +5578,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "postgres-protocol" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc" +checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" dependencies = [ "base64 0.22.1", "byteorder", @@ -5574,9 +5596,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186" +checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" dependencies = [ "bytes", "fallible-iterator 0.2.0", @@ -5692,6 +5714,17 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -6235,9 +6268,9 @@ dependencies = [ [[package]] name = "redis" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6b5f4d8ef33944e833e2b1859ad478deab6e431d7337b30ee2efe21f7543" +checksum = "f9fd510128eda94d1d49b9f81487744d5c451422431cce41238fe2853d29f4cc" dependencies = [ "arcstr", "async-lock", @@ -6251,7 +6284,7 @@ dependencies = [ "num-bigint", "percent-encoding", "pin-project-lite", - "rand 0.9.4", + "rand 0.10.1", "rustls 0.23.40", "rustls-native-certs", "ryu", @@ -7501,9 +7534,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smtp" @@ -7677,9 +7710,9 @@ dependencies = [ [[package]] name = "stabby" -version = "72.1.4" +version = "72.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da596fe515a75300efdc17bea773f7304a7758e187a0a8013fca4364aa5edacd" +checksum = "a7b834ec7ced12095fea1e4b07dcb7e8cf2b59b18afa3eac52494d835965a5ec" dependencies = [ "rustversion", "stabby-abi", @@ -7687,9 +7720,9 @@ dependencies = [ [[package]] name = "stabby-abi" -version = "72.1.4" +version = "72.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080815c68b7e997f113fdbe75b726f4187817196a1b7d7f4c13843f40a8dd79c" +checksum = "ff1a4f477858a5bdf927c9fab7f579899de9b13e39f8b3b3b300c89fbab632f4" dependencies = [ "rustc_version", "rustversion", @@ -7699,14 +7732,13 @@ dependencies = [ [[package]] name = "stabby-macros" -version = "72.1.4" +version = "72.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18084b18318cf35f502f70c5157e86840d9d1b0964316f08d0b3ebf405e4127d" +checksum = "b31c4b2434980b67ad83f300a58088ba14d59454dcd79ba3d87419bbd924d31e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "rand 0.8.6", "syn", ] @@ -8117,12 +8149,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", - "itoa", "js-sys", "num-conv", "powerfmt", @@ -8133,15 +8164,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d" dependencies = [ "num-conv", "time-core", @@ -8235,9 +8266,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce" +checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" dependencies = [ "async-trait", "byteorder", @@ -9006,9 +9037,9 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen 0.57.1", ] @@ -9033,9 +9064,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -9046,9 +9077,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.73" +version = "0.4.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" dependencies = [ "js-sys", "wasm-bindgen", @@ -9056,9 +9087,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9066,9 +9097,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -9079,9 +9110,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] @@ -9148,9 +9179,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.100" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -10360,18 +10391,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/crates/dav-proto/resources/requests/report-014.json b/crates/dav-proto/resources/requests/report-014.json index 4733001e..967e5dc8 100644 --- a/crates/dav-proto/resources/requests/report-014.json +++ b/crates/dav-proto/resources/requests/report-014.json @@ -13,43 +13,45 @@ "type": "CardDav", "data": { "type": "AddressData", - "data": [ - { - "group": null, - "name": { - "type": "Version" + "data": { + "properties": [ + { + "group": null, + "name": { + "type": "Version" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Uid" + { + "group": null, + "name": { + "type": "Uid" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Nickname" + { + "group": null, + "name": { + "type": "Nickname" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Email" + { + "group": null, + "name": { + "type": "Email" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Fn" - }, - "no_value": false - } - ] + { + "group": null, + "name": { + "type": "Fn" + }, + "no_value": false + } + ] + } } } ] diff --git a/crates/dav-proto/resources/requests/report-015.json b/crates/dav-proto/resources/requests/report-015.json index 48356178..2fadae00 100644 --- a/crates/dav-proto/resources/requests/report-015.json +++ b/crates/dav-proto/resources/requests/report-015.json @@ -13,43 +13,45 @@ "type": "CardDav", "data": { "type": "AddressData", - "data": [ - { - "group": null, - "name": { - "type": "Version" + "data": { + "properties": [ + { + "group": null, + "name": { + "type": "Version" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Uid" + { + "group": null, + "name": { + "type": "Uid" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Nickname" + { + "group": null, + "name": { + "type": "Nickname" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Email" + { + "group": null, + "name": { + "type": "Email" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Fn" - }, - "no_value": false - } - ] + { + "group": null, + "name": { + "type": "Fn" + }, + "no_value": false + } + ] + } } } ] diff --git a/crates/dav-proto/resources/requests/report-017.json b/crates/dav-proto/resources/requests/report-017.json index d433d1ae..fa10e19e 100644 --- a/crates/dav-proto/resources/requests/report-017.json +++ b/crates/dav-proto/resources/requests/report-017.json @@ -13,43 +13,45 @@ "type": "CardDav", "data": { "type": "AddressData", - "data": [ - { - "group": null, - "name": { - "type": "Version" + "data": { + "properties": [ + { + "group": null, + "name": { + "type": "Version" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Uid" + { + "group": null, + "name": { + "type": "Uid" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Nickname" + { + "group": null, + "name": { + "type": "Nickname" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Email" + { + "group": null, + "name": { + "type": "Email" + }, + "no_value": false }, - "no_value": false - }, - { - "group": null, - "name": { - "type": "Fn" - }, - "no_value": false - } - ] + { + "group": null, + "name": { + "type": "Fn" + }, + "no_value": false + } + ] + } } } ] diff --git a/crates/dav-proto/resources/requests/report-018.json b/crates/dav-proto/resources/requests/report-018.json index 8a9cfd64..c468ee0c 100644 --- a/crates/dav-proto/resources/requests/report-018.json +++ b/crates/dav-proto/resources/requests/report-018.json @@ -13,7 +13,9 @@ "type": "CardDav", "data": { "type": "AddressData", - "data": [] + "data": { + "properties": [] + } } } ] diff --git a/crates/dav-proto/src/parser/header.rs b/crates/dav-proto/src/parser/header.rs index dd09a852..f15dd651 100644 --- a/crates/dav-proto/src/parser/header.rs +++ b/crates/dav-proto/src/parser/header.rs @@ -86,7 +86,7 @@ impl<'x> RequestHeaders<'x> { "Accept" => { for value in value.split(',') { if value.trim().starts_with("text/vcard") && let Some(version) = value.split_once("version=") - .and_then(|(_, version)| VCardVersion::try_parse(version.trim())) { + .and_then(|(_, version)| VCardVersion::try_parse(version.trim().trim_matches('"'))) { if let Some(max_vcard_version) = &mut self.max_vcard_version { if version > *max_vcard_version { *max_vcard_version = version; diff --git a/crates/dav-proto/src/parser/property.rs b/crates/dav-proto/src/parser/property.rs index cc77c6bc..59946467 100644 --- a/crates/dav-proto/src/parser/property.rs +++ b/crates/dav-proto/src/parser/property.rs @@ -18,7 +18,7 @@ use calcard::{ Entry, Parser, common::{IanaParse, PartialDateTime}, icalendar::{ICalendar, ICalendarComponentType, ICalendarParameterName, ICalendarProperty}, - vcard::{VCardParameterName, VCardProperty}, + vcard::{VCardParameterName, VCardProperty, VCardVersion}, }; use mail_parser::DateTime; use types::{TimeRange, dead_property::DeadProperty}; @@ -48,11 +48,18 @@ impl Tokenizer<'_> { ns: Namespace::CardDav, element: Element::AddressData, }, - .. + raw, } => { - elements.push(DavProperty::CardDav(CardDavProperty::AddressData( - self.collect_address_data()?, - ))); + let mut version = None; + for attribute in raw.attributes::() { + if let Attribute::Version(value) = attribute? { + version = VCardVersion::try_parse(value.trim().trim_matches('"')); + } + } + elements.push(DavProperty::CardDav(CardDavProperty::AddressData { + properties: self.collect_address_data()?, + version, + })); } Token::ElementStart { name, .. } => { if let Some(property) = DavProperty::from_element(name) { @@ -510,9 +517,12 @@ impl DavProperty { (Namespace::CardDav, Element::PrincipalAddress) => { Some(DavProperty::Principal(PrincipalProperty::PrincipalAddress)) } - (Namespace::CardDav, Element::AddressData) => Some(DavProperty::CardDav( - CardDavProperty::AddressData(Default::default()), - )), + (Namespace::CardDav, Element::AddressData) => { + Some(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) + } (Namespace::CardDav, Element::MaxResourceSize) => { Some(DavProperty::CardDav(CardDavProperty::MaxResourceSize)) } diff --git a/crates/dav-proto/src/requests/mod.rs b/crates/dav-proto/src/requests/mod.rs index 3b233a42..e00526c5 100644 --- a/crates/dav-proto/src/requests/mod.rs +++ b/crates/dav-proto/src/requests/mod.rs @@ -111,11 +111,44 @@ impl From<&RawElement<'_>> for DeadElementTag { #[cfg(test)] mod tests { + use calcard::vcard::VCardVersion; + use crate::{ parser::{DavParser, tokenizer::Tokenizer}, - schema::request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report}, + schema::{ + property::{CardDavProperty, DavProperty}, + request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report}, + }, }; + #[test] + fn parse_address_data_version() { + let xml = r#" + + + + + + + "#; + + let mut tokenizer = Tokenizer::new(xml.as_bytes()); + let report = Report::parse(&mut tokenizer).unwrap(); + let Report::AddressbookQuery(query) = report else { + panic!("expected addressbook-query, got {report:?}"); + }; + let PropFind::Prop(properties) = query.properties else { + panic!("expected prop, got {:?}", query.properties); + }; + + let version = properties.iter().find_map(|property| match property { + DavProperty::CardDav(CardDavProperty::AddressData { version, .. }) => Some(*version), + _ => None, + }); + + assert_eq!(version, Some(Some(VCardVersion::V3_0))); + } + #[test] fn parse_requests() { for entry in std::fs::read_dir("resources/requests").unwrap() { diff --git a/crates/dav-proto/src/responses/mod.rs b/crates/dav-proto/src/responses/mod.rs index 0d9ea5d2..1ce07a87 100644 --- a/crates/dav-proto/src/responses/mod.rs +++ b/crates/dav-proto/src/responses/mod.rs @@ -439,7 +439,10 @@ END:VCALENDAR vec![PropStat::new_list(vec![ DavPropertyValue::new(WebDavProperty::GetETag, "\"23ba4d-ff11fb\""), DavPropertyValue::new( - CardDavProperty::AddressData(Default::default()), + CardDavProperty::AddressData { + properties: Default::default(), + version: None, + }, DavValue::CData( r#"BEGIN:VCARD VERSION:3.0 diff --git a/crates/dav-proto/src/responses/property.rs b/crates/dav-proto/src/responses/property.rs index d8a9a50c..e27cb272 100644 --- a/crates/dav-proto/src/responses/property.rs +++ b/crates/dav-proto/src/responses/property.rs @@ -181,7 +181,7 @@ impl DavProperty { CardDavProperty::SupportedAddressData => "B:supported-address-data", CardDavProperty::SupportedCollationSet => "B:supported-collation-set", CardDavProperty::MaxResourceSize => "B:max-resource-size", - CardDavProperty::AddressData(_) => "B:address-data", + CardDavProperty::AddressData { .. } => "B:address-data", }, DavProperty::CalDav(prop) => match prop { CalDavProperty::CalendarDescription => "A:calendar-description", diff --git a/crates/dav-proto/src/schema/property.rs b/crates/dav-proto/src/schema/property.rs index 9709923c..d3d3a1ac 100644 --- a/crates/dav-proto/src/schema/property.rs +++ b/crates/dav-proto/src/schema/property.rs @@ -12,7 +12,7 @@ use super::{ use crate::{Depth, Timeout}; use calcard::{ icalendar::{ICalendar, ICalendarComponentType, ICalendarProperty}, - vcard::{VCard, VCardProperty}, + vcard::{VCard, VCardProperty, VCardVersion}, }; use types::{ TimeRange, @@ -72,7 +72,11 @@ pub enum CardDavProperty { SupportedAddressData, SupportedCollationSet, MaxResourceSize, - AddressData(Vec), + AddressData { + properties: Vec, + #[cfg_attr(test, serde(skip))] + version: Option, + }, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/dav/src/card/mod.rs b/crates/dav/src/card/mod.rs index 5ef2204a..6c14e1c0 100644 --- a/crates/dav/src/card/mod.rs +++ b/crates/dav/src/card/mod.rs @@ -68,7 +68,10 @@ pub(crate) static CARD_ITEM_PROPS: [DavProperty; 20] = [ DavProperty::WebDav(WebDavProperty::GetContentLanguage), DavProperty::WebDav(WebDavProperty::GetContentLength), DavProperty::WebDav(WebDavProperty::GetContentType), - DavProperty::CardDav(CardDavProperty::AddressData(vec![])), + DavProperty::CardDav(CardDavProperty::AddressData { + properties: Vec::new(), + version: None, + }), ]; pub(crate) async fn assert_is_unique_uid( diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 009cad40..dff11f37 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -839,16 +839,19 @@ impl PropFindRequestHandler for Server { )); } ( - CardDavProperty::AddressData(items), + CardDavProperty::AddressData { + properties, + version, + }, ArchivedResource::ContactCard(card), ) => { fields.push(DavPropertyValue::new( property.clone(), DavValue::CData(serialize_vcard_with_props( &card.inner.card, - items, - query - .max_vcard_version + properties, + (*version) + .or(query.max_vcard_version) .or_else(|| card.inner.card.version()), )), )); diff --git a/crates/smtp/src/queue/spool.rs b/crates/smtp/src/queue/spool.rs index fe1f798d..f0c0f19c 100644 --- a/crates/smtp/src/queue/spool.rs +++ b/crates/smtp/src/queue/spool.rs @@ -34,7 +34,9 @@ use store::write::{ AlignedBytes, Archive, Archiver, BatchBuilder, BlobLink, BlobOp, MergeResult, Params, QueueClass, RegistryClass, ValueClass, now, }; -use store::{Deserialize, IterateParams, Serialize, SerializeInfallible, U32_LEN, U64_LEN, ValueKey}; +use store::{ + Deserialize, IterateParams, Serialize, SerializeInfallible, U32_LEN, U64_LEN, ValueKey, +}; use trc::{AddContext, ServerEvent, SpamEvent}; use types::blob::BlobId; use types::blob_hash::BlobHash; diff --git a/tests/src/webdav/card_query.rs b/tests/src/webdav/card_query.rs index c8d01887..bfb8b6fa 100644 --- a/tests/src/webdav/card_query.rs +++ b/tests/src/webdav/card_query.rs @@ -44,9 +44,10 @@ pub async fn test(test: &TestServer) { .get(DavProperty::WebDav(WebDavProperty::GetETag)) .is_not_empty(); props - .get(DavProperty::CardDav(CardDavProperty::AddressData( - Default::default(), - ))) + .get(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) .with_values([r#"BEGIN:VCARD VERSION:4.0 FN:Carlos Rodriguez-Martinez @@ -71,9 +72,10 @@ END:VCARD .get(DavProperty::WebDav(WebDavProperty::GetETag)) .is_not_empty(); props - .get(DavProperty::CardDav(CardDavProperty::AddressData( - Default::default(), - ))) + .get(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) .with_values([r#"BEGIN:VCARD FN:Carlos Rodriguez-Martinez BDAY:--0623 @@ -90,9 +92,10 @@ END:VCARD .get(DavProperty::WebDav(WebDavProperty::GetETag)) .is_not_empty(); props - .get(DavProperty::CardDav(CardDavProperty::AddressData( - Default::default(), - ))) + .get(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) .with_values([r#"BEGIN:VCARD FN:Sarah Johnson BDAY:19850415 @@ -113,9 +116,10 @@ END:VCARD .into_propfind_response(None); let props = response.properties(uri_acme); props - .get(DavProperty::CardDav(CardDavProperty::AddressData( - Default::default(), - ))) + .get(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) .with_values([VCARD3.replace('\n', "\r\n").as_str()]); // Test 4: Search using limit diff --git a/tests/src/webdav/multiget.rs b/tests/src/webdav/multiget.rs index 34b0549b..d95ec7d9 100644 --- a/tests/src/webdav/multiget.rs +++ b/tests/src/webdav/multiget.rs @@ -62,9 +62,10 @@ pub async fn test(test: &TestServer) { .get(DavProperty::WebDav(WebDavProperty::GetETag)) .with_values([etag.as_str()]); props - .get(DavProperty::CardDav(CardDavProperty::AddressData( - Default::default(), - ))) + .get(DavProperty::CardDav(CardDavProperty::AddressData { + properties: Default::default(), + version: None, + })) .with_values([contents.as_str()]); } }