diff --git a/Cargo.lock b/Cargo.lock index 9d5ae61a..dd155cdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -291,7 +291,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure 0.13.1", ] @@ -314,7 +314,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -330,9 +330,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310c9bcae737a48ef5cdee3174184e6d548b292739ede61a1f955ef76a738861" +checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" dependencies = [ "flate2", "futures-core", @@ -360,7 +360,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -382,18 +382,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -408,9 +408,9 @@ version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07a9b245ba0739fc90935094c29adbaee3f977218b5fb95e822e261cda7f56a3" dependencies = [ - "http 1.2.0", + "http 1.3.1", "log", - "rustls 0.23.23", + "rustls 0.23.26", "serde", "serde_json", "url", @@ -459,7 +459,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "itoa", @@ -485,7 +485,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -515,7 +515,7 @@ dependencies = [ "pin-project", "quick-xml 0.31.0", "rand 0.8.5", - "reqwest 0.12.12", + "reqwest 0.12.15", "rustc_version 0.4.1", "serde", "serde_json", @@ -605,9 +605,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base32" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" [[package]] name = "base64" @@ -635,15 +635,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bigdecimal" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", @@ -678,7 +678,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -698,7 +698,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -716,7 +716,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -730,7 +730,7 @@ dependencies = [ "num-bigint", "num-traits", "once_cell", - "ring 0.17.11", + "ring 0.17.14", "serde", "serde_json", ] @@ -819,15 +819,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675f87afced0413c9bb02843499dbbd3882a237645883f71a2b59644a6d2f753" +checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", - "constant_time_eq 0.3.1", + "constant_time_eq", ] [[package]] @@ -880,25 +880,25 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", - "cfg_aliases", + "cfg_aliases 0.2.1", ] [[package]] name = "borsh-derive" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -967,14 +967,14 @@ checksum = "efb7846e0cb180355c2dec69e721edafa36919850f1a9f52ffba4ebc0393cb71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder" @@ -984,9 +984,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" @@ -1057,9 +1057,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -1110,6 +1110,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -1164,9 +1170,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", "clap_derive", @@ -1174,9 +1180,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", @@ -1186,14 +1192,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1259,12 +1265,12 @@ dependencies = [ "dns-update", "futures", "hashify", - "hostname 0.4.0", + "hostname", "hyper 1.6.0", "idna", "imagesize", "imap_proto", - "indexmap 2.7.1", + "indexmap 2.9.0", "infer 0.16.0", "jmap_proto", "libc", @@ -1290,11 +1296,11 @@ dependencies = [ "pwhash", "rcgen 0.12.1", "regex", - "reqwest 0.12.12", - "ring 0.17.11", + "reqwest 0.12.15", + "ring 0.17.14", "rkyv 0.8.10", "rsa", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -1343,9 +1349,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", @@ -1386,12 +1392,6 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1408,6 +1408,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1477,9 +1487,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1611,7 +1621,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1626,12 +1636,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -1650,16 +1660,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1675,13 +1685,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.10", + "darling_core 0.20.11", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1692,9 +1702,9 @@ checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "dav" @@ -1725,7 +1735,7 @@ dependencies = [ "hashify", "hyper 1.6.0", "mail-parser", - "quick-xml 0.37.2", + "quick-xml 0.37.4", "rkyv 0.8.10", "serde", "serde_json", @@ -1844,9 +1854,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -1860,7 +1870,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1916,9 +1926,9 @@ dependencies = [ "proc_macros", "pwhash", "regex", - "reqwest 0.12.12", + "reqwest 0.12.15", "rkyv 0.8.10", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pki-types", "scrypt", "serde", @@ -1963,7 +1973,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1982,7 +1992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b0a9ca156a0285b2768b7fcb368b231e7736d3c13eef604c5c4203057bed33" dependencies = [ "hickory-client", - "reqwest 0.12.12", + "reqwest 0.12.15", "serde", "serde_json", "serde_urlencoded", @@ -2019,9 +2029,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "eax" @@ -2104,9 +2114,9 @@ dependencies = [ [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elasticsearch" @@ -2223,7 +2233,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2234,9 +2244,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" dependencies = [ "serde", "typeid", @@ -2244,9 +2254,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -2271,9 +2281,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.0", "pin-project-lite", @@ -2285,7 +2295,7 @@ version = "0.11.8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2340,9 +2350,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", "subtle", @@ -2362,9 +2372,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "libz-sys", @@ -2379,9 +2389,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -2405,12 +2415,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3757d8523a60f4d3516a018e767cd3bea0829a43045d290ace16dcc92ac4f4cd" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", "httparse", "memchr", "mime", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -2462,7 +2472,7 @@ checksum = "9be610412e5a92d89855fb15b099a57792b7dbdcf8ac74c5a0e24d9b7b1b6f7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "try_map", ] @@ -2515,7 +2525,7 @@ checksum = "e99b8b3c28ae0e84b604c75f721c21dc77afb3706076af5e8216d15fd1deaae3" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2527,7 +2537,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2539,7 +2549,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2619,7 +2629,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2687,17 +2697,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" dependencies = [ - "rustix", + "rustix 0.38.44", "windows-targets 0.52.6", ] [[package]] name = "gethostname" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd4b8790c0792e3b11895efdf5f289ebe8b59107a6624f1cce68f24ff8c7035" +checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e" dependencies = [ - "rustix", + "rustix 1.0.5", "windows-targets 0.52.6", ] @@ -2727,14 +2737,16 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -2791,9 +2803,9 @@ dependencies = [ [[package]] name = "gxhash" -version = "3.4.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a197c9b654827513cf53842c5c6d3da2b4b35a785f8e0eff78bdf8e445aba1bb" +checksum = "f3ce1bab7aa741d4e7042b2aae415b78741f267a98a7271ea226cd5ba6c43d7d" dependencies = [ "rustversion", ] @@ -2810,7 +2822,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -2819,17 +2831,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", - "indexmap 2.7.1", + "http 1.3.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -2874,7 +2886,7 @@ checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2906,9 +2918,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "hex" @@ -2956,7 +2968,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.8.5", - "ring 0.17.11", + "ring 0.17.14", "rustls 0.21.12", "rustls-pemfile 1.0.4", "thiserror 1.0.69", @@ -3029,24 +3041,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "hostname" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows", + "windows-link", ] [[package]] @@ -3086,7 +3087,7 @@ dependencies = [ "mail-send", "mime", "pkcs8", - "quick-xml 0.37.2", + "quick-xml 0.37.4", "rev_lines", "rkyv 0.8.10", "rsa", @@ -3107,9 +3108,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -3134,18 +3135,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] @@ -3188,9 +3189,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -3237,8 +3238,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.9", + "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", @@ -3270,10 +3271,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.2.0", + "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -3296,16 +3297,17 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "libc", "pin-project-lite", "socket2", "tokio", @@ -3315,14 +3317,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -3377,9 +3380,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -3401,9 +3404,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -3422,9 +3425,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -3451,7 +3454,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3506,15 +3509,15 @@ dependencies = [ "directory", "email", "imap_proto", - "indexmap 2.7.1", + "indexmap 2.9.0", "jmap_proto", "mail-parser", "mail-send", "md5", "nlp", "parking_lot", - "rand 0.9.0", - "rustls 0.23.23", + "rand 0.9.1", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "store", "tokio", @@ -3558,7 +3561,7 @@ dependencies = [ "libflate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3573,9 +3576,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -3648,11 +3651,11 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.0", "libc", "windows-sys 0.59.0", ] @@ -3710,9 +3713,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jemalloc-sys" @@ -3787,8 +3790,8 @@ dependencies = [ "nlp", "p256", "pkcs8", - "rand 0.9.0", - "reqwest 0.12.12", + "rand 0.9.1", + "reqwest 0.12.15", "rkyv 0.8.10", "rsa", "serde", @@ -3850,10 +3853,11 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -3873,7 +3877,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", ] [[package]] @@ -3980,9 +3984,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libflate" @@ -4051,9 +4055,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4" +checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" dependencies = [ "cc", "pkg-config", @@ -4062,9 +4066,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "pkg-config", @@ -4083,6 +4087,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litemap" version = "0.7.5" @@ -4107,9 +4117,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -4155,6 +4165,17 @@ dependencies = [ "crc", ] +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mail-auth" version = "0.6.1" @@ -4166,10 +4187,10 @@ dependencies = [ "hickory-resolver", "mail-builder", "mail-parser", - "quick-xml 0.37.2", + "quick-xml 0.37.4", "quick_cache", "rand 0.8.5", - "ring 0.17.11", + "ring 0.17.14", "rsa", "rustls-pemfile 2.2.0", "serde", @@ -4179,11 +4200,11 @@ dependencies = [ [[package]] name = "mail-builder" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d2c992d0b4d0acedb466c5524bb09327f4d0bb5bcfacea13005ec01c09f2ac" +checksum = "5666db60ccc742381c715c8daf303e60d266f1d1c8b123ab3fe78b590edaf564" dependencies = [ - "gethostname 1.0.0", + "gethostname 1.0.1", ] [[package]] @@ -4206,7 +4227,7 @@ dependencies = [ "base64 0.22.1", "gethostname 0.5.0", "md5", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pki-types", "smtp-proto", "tokio", @@ -4256,7 +4277,7 @@ dependencies = [ "mail-send", "md5", "parking_lot", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "sieve-rs", "store", @@ -4272,12 +4293,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchit" version = "0.7.3" @@ -4292,7 +4307,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4328,15 +4343,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memory-stats" version = "1.2.0" @@ -4377,9 +4383,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -4413,7 +4419,7 @@ checksum = "734799cf91479720b2f970c61a22850940dd91e27d4f02b1c6fc792778df2459" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4422,14 +4428,14 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63c3512cf11487168e0e9db7157801bf5273be13055a9cc95356dc9e0035e49c" dependencies = [ - "darling 0.20.10", + "darling 0.20.11", "heck 0.5.0", "num-bigint", "proc-macro-crate", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "termcolor", "thiserror 1.0.69", ] @@ -4527,15 +4533,14 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "cfg-if", + "cfg_aliases 0.1.1", "libc", - "memoffset", - "pin-utils", ] [[package]] @@ -4686,9 +4691,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" @@ -4698,9 +4703,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -4719,7 +4724,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4730,9 +4735,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -4762,9 +4767,9 @@ checksum = "88d8c2b76e5f7848a289aa9666dbe56b16f8a22a4c5246ef37a14941818d2913" dependencies = [ "async-trait", "bytes", - "http 1.2.0", + "http 1.3.1", "opentelemetry", - "reqwest 0.12.12", + "reqwest 0.12.15", ] [[package]] @@ -4775,13 +4780,13 @@ checksum = "596b1719b3cab83addb20bcbffdf21575279d9436d9ccccfe651a3bf0ab5ab06" dependencies = [ "async-trait", "futures-core", - "http 1.2.0", + "http 1.3.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.12.12", + "reqwest 0.12.15", "thiserror 1.0.69", "tokio", "tonic", @@ -4962,7 +4967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.9.0", ] [[package]] @@ -5005,22 +5010,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5058,9 +5063,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" @@ -5085,7 +5090,7 @@ dependencies = [ "jmap_proto", "mail-parser", "mail-send", - "rustls 0.23.23", + "rustls 0.23.26", "store", "tokio", "tokio-rustls 0.26.2", @@ -5112,7 +5117,7 @@ dependencies = [ "hmac 0.12.1", "md-5 0.10.6", "memchr", - "rand 0.9.0", + "rand 0.9.1", "sha2 0.10.8", "stringprep", ] @@ -5136,11 +5141,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy 0.8.24", ] [[package]] @@ -5151,12 +5156,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5184,9 +5189,9 @@ dependencies = [ [[package]] name = "privdrop" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc12de3935536ed9b69488faea4450a298dac44179b54f71806e63f55034bf9" +checksum = "879d008129b086c1c067a3b7ce406bb9766c29f20387e7883724d8dddbce7064" dependencies = [ "libc", "nix", @@ -5194,9 +5199,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] @@ -5220,14 +5225,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -5238,7 +5243,7 @@ version = "0.11.8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5275,7 +5280,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5290,9 +5295,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.89" +version = "2.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69a99af0dfed3b5279a24891a8128d536f075bef423b5974d70edd17e1f6c91" +checksum = "34355eb8747f514f2e0a607c8806226de08cfac767c3e1adc777021e07991e44" dependencies = [ "psl-types", ] @@ -5340,7 +5345,7 @@ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5358,12 +5363,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-xml" version = "0.31.0" @@ -5386,58 +5385,60 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" dependencies = [ "memchr", ] [[package]] name = "quick_cache" -version = "0.6.10" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f67cfc9c723c39f3615eb0840b00c4cb9e2b068d2fa761a30d845ec91730a59" +checksum = "287e56aac5a2b4fb25a6fb050961d157635924c8696305a5c937a76f29841a0f" dependencies = [ "ahash 0.8.11", "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "parking_lot", ] [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", + "cfg_aliases 0.2.1", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.26", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring 0.17.11", + "getrandom 0.3.2", + "rand 0.9.1", + "ring 0.17.14", "rustc-hash 2.1.1", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pki-types", "slab", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -5445,11 +5446,11 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2", @@ -5459,13 +5460,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "r2d2" version = "0.8.10" @@ -5528,13 +5535,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.21", ] [[package]] @@ -5591,7 +5597,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -5686,7 +5692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" dependencies = [ "pem", - "ring 0.17.11", + "ring 0.17.14", "time", "yasna", ] @@ -5698,7 +5704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ "pem", - "ring 0.17.11", + "ring 0.17.14", "rustls-pki-types", "time", "yasna", @@ -5706,9 +5712,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.29.0" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9568894e8bdefd16512bca9e286a9d2abc27773609aa4eb7f428497d64df4373" +checksum = "1bc42f3a12fd4408ce64d8efef67048a924e543bd35c6591c0447fda9054695f" dependencies = [ "arc-swap", "bytes", @@ -5721,9 +5727,9 @@ dependencies = [ "num-bigint", "percent-encoding", "pin-project-lite", - "rand 0.9.0", - "rustls 0.23.23", - "rustls-native-certs 0.7.3", + "rand 0.9.1", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", "ryu", "sha1_smol", "socket2", @@ -5736,9 +5742,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] @@ -5847,17 +5853,17 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.9", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -5872,7 +5878,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -5895,12 +5901,11 @@ dependencies = [ [[package]] name = "resolv-conf" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +checksum = "48375394603e3dd4b2d64371f7148fd8c7baa2680e28741f2cb8d23b59e3d4c4" dependencies = [ - "hostname 0.3.1", - "quick-error", + "hostname", ] [[package]] @@ -5939,9 +5944,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -5987,7 +5992,7 @@ dependencies = [ "bytecheck 0.8.1", "bytes", "hashbrown 0.15.2", - "indexmap 2.7.1", + "indexmap 2.9.0", "munge", "ptr_meta 0.3.0", "rancor", @@ -6016,7 +6021,7 @@ checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6027,9 +6032,9 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "roaring" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652edd001c53df0b3f96a36a8dc93fce6866988efc16808235653c6bcac8bf2" +checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" dependencies = [ "bytemuck", "byteorder", @@ -6058,9 +6063,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest 0.10.7", @@ -6094,9 +6099,9 @@ checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" [[package]] name = "rusqlite" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6d5e5acb6f6129fe3f7ba0a7fc77bca1942cb568535e18e7bc40262baf3110" +checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" dependencies = [ "bitflags 2.9.0", "fallible-iterator 0.3.0", @@ -6166,9 +6171,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.36.0" +version = "1.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" dependencies = [ "arrayvec", "borsh", @@ -6213,7 +6218,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.25", + "semver 1.0.26", ] [[package]] @@ -6234,7 +6239,20 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -6245,7 +6263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.11", + "ring 0.17.14", "rustls-webpki 0.101.7", "sct", ] @@ -6257,7 +6275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.11", + "ring 0.17.14", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -6266,14 +6284,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "once_cell", - "ring 0.17.11", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] @@ -6287,20 +6305,19 @@ dependencies = [ "openssl-probe", "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -6336,7 +6353,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.11", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -6346,22 +6363,33 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.11", + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa20" @@ -6438,15 +6466,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.11", + "ring 0.17.14", "untrusted 0.9.0", ] [[package]] name = "sdd" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" +checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" [[package]] name = "seahash" @@ -6475,7 +6503,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -6502,9 +6543,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "semver-parser" @@ -6570,40 +6611,40 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "itoa", "memchr", "ryu", @@ -6677,7 +6718,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6696,7 +6737,7 @@ dependencies = [ "mail-parser", "memory-stats", "p256", - "reqwest 0.12.12", + "reqwest 0.12.15", "rsa", "serde", "serde_json", @@ -6842,9 +6883,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smtp" @@ -6871,12 +6912,12 @@ dependencies = [ "nlp", "num_cpus", "parking_lot", - "rand 0.9.0", + "rand 0.9.1", "rayon", "regex", - "reqwest 0.12.12", + "reqwest 0.12.15", "rkyv 0.8.10", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -6929,9 +6970,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -6953,7 +6994,7 @@ dependencies = [ "mail-send", "nlp", "psl", - "reqwest 0.12.12", + "reqwest 0.12.15", "sha1", "sha2 0.10.8", "smtp-proto", @@ -7009,8 +7050,8 @@ dependencies = [ "num_cpus", "prettytable-rs", "pwhash", - "rand 0.9.0", - "reqwest 0.12.12", + "rand 0.9.1", + "reqwest 0.12.15", "rpassword", "serde", "serde_json", @@ -7054,18 +7095,18 @@ dependencies = [ "num_cpus", "parking_lot", "r2d2", - "rand 0.9.0", + "rand 0.9.1", "rayon", "redis", "regex", - "reqwest 0.12.12", - "ring 0.17.11", + "reqwest 0.12.15", + "ring 0.17.14", "rkyv 0.8.10", "roaring", "rocksdb", "rusqlite", "rust-s3", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pki-types", "serde", "serde_json", @@ -7079,9 +7120,9 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", @@ -7141,9 +7182,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -7185,7 +7226,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7195,7 +7236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7273,10 +7314,10 @@ dependencies = [ "num_cpus", "pop3", "rayon", - "reqwest 0.12.12", - "ring 0.17.11", + "reqwest 0.12.15", + "ring 0.17.14", "rkyv 0.8.10", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -7305,11 +7346,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -7320,25 +7361,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -7352,15 +7393,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -7402,9 +7443,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d" dependencies = [ "backtrace", "bytes", @@ -7427,7 +7468,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7449,7 +7490,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.0", + "rand 0.9.1", "socket2", "tokio", "tokio-util", @@ -7483,7 +7524,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.23", + "rustls 0.23.26", "tokio", ] @@ -7528,9 +7569,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -7551,7 +7592,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "toml_datetime", "winnow", ] @@ -7567,8 +7608,8 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.8", - "http 1.2.0", + "h2 0.4.9", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", @@ -7588,12 +7629,12 @@ dependencies = [ [[package]] name = "totp-rs" -version = "5.6.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b2f27dad992486c26b4e7455f38aa487e838d6d61b57e72906ee2b8c287a90" +checksum = "f124352108f58ef88299e909f6e9470f1cdc8d2a1397963901b4a6366206bf72" dependencies = [ "base32", - "constant_time_eq 0.2.6", + "constant_time_eq", "hmac 0.12.1", "sha1", "sha2 0.10.8", @@ -7667,7 +7708,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7691,7 +7732,7 @@ dependencies = [ "mail-auth", "mail-parser", "parking_lot", - "reqwest 0.12.12", + "reqwest 0.12.15", "rkyv 0.8.10", "rtrb", "serde", @@ -7726,7 +7767,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", "rand 0.8.5", @@ -7746,12 +7787,12 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", - "rand 0.9.0", + "rand 0.9.1", "sha1", - "thiserror 2.0.11", + "thiserror 2.0.12", "utf-8", ] @@ -7777,9 +7818,9 @@ dependencies = [ [[package]] name = "typeid" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" @@ -7816,9 +7857,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -7955,13 +7996,13 @@ dependencies = [ "privdrop", "psl", "quick_cache", - "rand 0.9.0", + "rand 0.9.1", "rcgen 0.13.2", "regex", - "reqwest 0.12.12", - "ring 0.17.11", + "reqwest 0.12.15", + "ring 0.17.14", "rkyv 0.8.10", - "rustls 0.23.23", + "rustls 0.23.26", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -7976,11 +8017,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", "serde", ] @@ -8041,9 +8082,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -8076,7 +8117,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -8111,7 +8152,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8164,7 +8205,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.11", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -8195,9 +8236,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -8206,9 +8247,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -8242,58 +8283,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.52.0" +name = "windows-core" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-core", - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-implement" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ - "windows-targets 0.52.6", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "windows-strings 0.3.1", + "windows-targets 0.53.0", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", ] [[package]] @@ -8347,13 +8412,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -8366,6 +8447,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -8378,6 +8465,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -8390,12 +8483,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -8408,6 +8513,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -8420,6 +8531,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -8432,6 +8549,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -8445,10 +8568,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.3" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -8465,9 +8594,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -8540,9 +8669,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xxhash-rust" @@ -8550,6 +8679,15 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yasna" version = "0.5.2" @@ -8579,7 +8717,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure 0.13.1", ] @@ -8589,17 +8727,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.21", + "zerocopy-derive 0.8.24", ] [[package]] @@ -8610,18 +8747,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerocopy-derive" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8641,7 +8778,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure 0.13.1", ] @@ -8662,7 +8799,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8684,33 +8821,32 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zip" -version = "2.2.3" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b280484c454e74e5fff658bbf7df8fdbe7a07c6b2de4a53def232c15ef138f3a" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" dependencies = [ "aes", "arbitrary", "bzip2", - "constant_time_eq 0.3.1", + "constant_time_eq", "crc32fast", "crossbeam-utils", "deflate64", - "displaydoc", "flate2", + "getrandom 0.3.2", "hmac 0.12.1", - "indexmap 2.7.1", + "indexmap 2.9.0", "lzma-rs", "memchr", "pbkdf2", - "rand 0.8.5", "sha1", - "thiserror 2.0.11", "time", + "xz2", "zeroize", "zopfli", "zstd", @@ -8741,19 +8877,20 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.3" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.14+zstd.1.5.7" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ + "bindgen 0.71.1", "cc", "pkg-config", ] diff --git a/crates/common/src/config/dav.rs b/crates/common/src/config/dav.rs index 5f4720e1..82b96ebd 100644 --- a/crates/common/src/config/dav.rs +++ b/crates/common/src/config/dav.rs @@ -16,6 +16,9 @@ pub struct DavConfig { pub max_changes: usize, pub max_match_results: usize, pub max_vcard_size: usize, + pub max_ical_size: usize, + pub max_ical_instances: usize, + pub max_ical_attendees_per_instance: usize, pub default_calendar_name: Option, pub default_addressbook_name: Option, pub default_calendar_display_name: Option, @@ -45,6 +48,9 @@ impl DavConfig { max_vcard_size: config .property("dav.limits.size.vcard") .unwrap_or(512 * 1024), + max_ical_size: config + .property("dav.limits.size.ical") + .unwrap_or(512 * 1024), default_calendar_name: config .property_or_default::>("dav.default.calendar.name", "default") .unwrap_or_default(), @@ -63,6 +69,12 @@ impl DavConfig { "Default Address Book", ) .unwrap_or_default(), + max_ical_instances: config + .property("dav.limits.ical.max-instances") + .unwrap_or(1000), + max_ical_attendees_per_instance: config + .property("dav.limits.ical.max-attendees-per-instance") + .unwrap_or(1000), } } } diff --git a/crates/dav-proto/src/parser/property.rs b/crates/dav-proto/src/parser/property.rs index b3521fe0..06110094 100644 --- a/crates/dav-proto/src/parser/property.rs +++ b/crates/dav-proto/src/parser/property.rs @@ -531,7 +531,7 @@ impl DavProperty { Some(DavProperty::CalDav(CalDavProperty::MaxAttendeesPerInstance)) } (Namespace::CalDav, Element::CalendarHomeSet) => { - Some(DavProperty::CalDav(CalDavProperty::CalendarHomeSet)) + Some(DavProperty::Principal(PrincipalProperty::CalendarHomeSet)) } (Namespace::CalDav, Element::CalendarData) => Some(DavProperty::CalDav( CalDavProperty::CalendarData(Default::default()), diff --git a/crates/dav-proto/src/responses/error.rs b/crates/dav-proto/src/responses/error.rs index c5a3477a..aaa818fe 100644 --- a/crates/dav-proto/src/responses/error.rs +++ b/crates/dav-proto/src/responses/error.rs @@ -92,6 +92,7 @@ impl Display for CalCondition { } CalCondition::ValidCalendarData => write!(f, ""), CalCondition::ValidFilter => write!(f, ""), + CalCondition::ValidTimezone => write!(f, ""), CalCondition::ValidCalendarObjectResource => { write!(f, "") } diff --git a/crates/dav-proto/src/responses/mkcol.rs b/crates/dav-proto/src/responses/mkcol.rs index f59fa787..d05478a2 100644 --- a/crates/dav-proto/src/responses/mkcol.rs +++ b/crates/dav-proto/src/responses/mkcol.rs @@ -13,11 +13,19 @@ use crate::schema::{ impl Display for MkColResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - self.namespaces, self.propstat - ) + if !self.mkcalendar { + write!( + f, + "{}", + self.namespaces, self.propstat + ) + } else { + write!( + f, + "{}", + self.namespaces, self.propstat + ) + } } } @@ -26,9 +34,18 @@ impl MkColResponse { Self { namespaces: Namespaces::default(), propstat: List(propstat), + mkcalendar: false, } } + pub fn with_mkcalendar(mut self, mkcalendar: bool) -> Self { + self.mkcalendar = mkcalendar; + if mkcalendar { + self.namespaces.set(Namespace::CalDav); + } + self + } + pub fn with_namespace(mut self, namespace: Namespace) -> Self { self.namespaces.set(namespace); self diff --git a/crates/dav-proto/src/responses/property.rs b/crates/dav-proto/src/responses/property.rs index 911e82d4..426b22f7 100644 --- a/crates/dav-proto/src/responses/property.rs +++ b/crates/dav-proto/src/responses/property.rs @@ -4,14 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::fmt::Display; - -use calcard::{icalendar::ICalendar, vcard::VCard}; -use mail_parser::{ - parsers::fields::date::{DOW, MONTH}, - DateTime, -}; - +use super::XmlEscape; use crate::schema::{ property::{ ActiveLock, CalDavProperty, CardDavProperty, Comp, DavProperty, DavValue, LockDiscovery, @@ -22,8 +15,12 @@ use crate::schema::{ response::{Ace, AclRestrictions, Href, List, PropResponse, SupportedPrivilege}, Namespace, Namespaces, }; - -use super::XmlEscape; +use calcard::{icalendar::ICalendar, vcard::VCard}; +use mail_parser::{ + parsers::fields::date::{DOW, MONTH}, + DateTime, +}; +use std::fmt::Display; impl Display for PropResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -90,6 +87,9 @@ impl Display for DavValue { DavValue::ICalendar(v) => { write!(f, "") } + DavValue::CData(v) => { + write!(f, "") + } DavValue::Components(v) => v.fmt(f), DavValue::Collations(v) => v.fmt(f), DavValue::Href(v) => v.fmt(f), @@ -105,7 +105,7 @@ impl Display for DavValue { "", "", "", - "", + "", "" ) ) @@ -121,6 +121,28 @@ impl Display for DavValue { ) ) } + DavValue::SupportedCalendarComponentSet => { + write!( + f, + concat!( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ) + ) + } DavValue::Null => Ok(()), } } @@ -176,7 +198,6 @@ impl DavProperty { CalDavProperty::MaxDateTime => "A:max-date-time", CalDavProperty::MaxInstances => "A:max-instances", CalDavProperty::MaxAttendeesPerInstance => "A:max-attendees-per-instance", - CalDavProperty::CalendarHomeSet => "A:calendar-home-set", CalDavProperty::CalendarData(_) => "A:calendar-data", CalDavProperty::TimezoneServiceSet => "A:timezone-service-set", CalDavProperty::TimezoneId => "A:calendar-timezone-id", @@ -186,6 +207,7 @@ impl DavProperty { PrincipalProperty::PrincipalURL => "D:principal-URL", PrincipalProperty::GroupMemberSet => "D:group-member-set", PrincipalProperty::GroupMembership => "D:group-membership", + PrincipalProperty::CalendarHomeSet => "A:calendar-home-set", PrincipalProperty::AddressbookHomeSet => "B:addressbook-home-set", PrincipalProperty::PrincipalAddress => "B:principal-address", }, diff --git a/crates/dav-proto/src/schema/property.rs b/crates/dav-proto/src/schema/property.rs index 28daf89c..a3c5799d 100644 --- a/crates/dav-proto/src/schema/property.rs +++ b/crates/dav-proto/src/schema/property.rs @@ -95,7 +95,6 @@ pub enum CalDavProperty { MaxDateTime, MaxInstances, MaxAttendeesPerInstance, - CalendarHomeSet, CalendarData(CalendarData), TimezoneServiceSet, TimezoneId, @@ -109,6 +108,7 @@ pub enum PrincipalProperty { PrincipalURL, GroupMemberSet, GroupMembership, + CalendarHomeSet, AddressbookHomeSet, PrincipalAddress, } @@ -149,6 +149,7 @@ pub enum DavValue { Rfc1123Date(Rfc1123DateTime), Uint64(u64), String(String), + CData(String), ResourceTypes(List), ActiveLocks(List), LockEntries(List), @@ -165,6 +166,7 @@ pub enum DavValue { DeadProperty(DeadProperty), SupportedAddressData, SupportedCalendarData, + SupportedCalendarComponentSet, Null, } @@ -313,8 +315,27 @@ impl Rfc1123DateTime { } impl DavProperty { + pub const ALL_PROPS: [DavProperty; 17] = [ + DavProperty::WebDav(WebDavProperty::CreationDate), + DavProperty::WebDav(WebDavProperty::DisplayName), + DavProperty::WebDav(WebDavProperty::GetETag), + DavProperty::WebDav(WebDavProperty::GetLastModified), + DavProperty::WebDav(WebDavProperty::ResourceType), + DavProperty::WebDav(WebDavProperty::LockDiscovery), + DavProperty::WebDav(WebDavProperty::SupportedLock), + DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal), + DavProperty::WebDav(WebDavProperty::SyncToken), + DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet), + DavProperty::WebDav(WebDavProperty::AclRestrictions), + DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet), + DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet), + DavProperty::WebDav(WebDavProperty::GetContentLanguage), + DavProperty::WebDav(WebDavProperty::GetContentLength), + DavProperty::WebDav(WebDavProperty::GetContentType), + DavProperty::WebDav(WebDavProperty::SupportedReportSet), + ]; + pub fn is_all_prop(&self) -> bool { - let todo = "add cal, card"; matches!( self, DavProperty::WebDav(WebDavProperty::CreationDate) @@ -335,11 +356,6 @@ impl DavProperty { | DavProperty::WebDav(WebDavProperty::GetContentType) | DavProperty::WebDav(WebDavProperty::SupportedReportSet) | DavProperty::DeadProperty(_) - | DavProperty::CardDav(CardDavProperty::AddressData(_)) - | DavProperty::CardDav(CardDavProperty::AddressbookDescription) - | DavProperty::CardDav(CardDavProperty::SupportedAddressData) - | DavProperty::CardDav(CardDavProperty::SupportedCollationSet) - | DavProperty::CardDav(CardDavProperty::MaxResourceSize), ) } } diff --git a/crates/dav-proto/src/schema/response.rs b/crates/dav-proto/src/schema/response.rs index 0ca82947..84450ea8 100644 --- a/crates/dav-proto/src/schema/response.rs +++ b/crates/dav-proto/src/schema/response.rs @@ -63,6 +63,7 @@ pub struct List(pub Vec); pub struct MkColResponse { pub namespaces: Namespaces, pub propstat: List, + pub mkcalendar: bool, } pub struct PropStat { @@ -201,6 +202,7 @@ pub enum CalCondition { ValidCalendarData, ValidFilter, ValidCalendarObjectResource, + ValidTimezone, NoUidConflict(Href), InitializeCalendarCollection, SupportedCalendarData, diff --git a/crates/dav/src/calendar/copy_move.rs b/crates/dav/src/calendar/copy_move.rs new file mode 100644 index 00000000..f8182924 --- /dev/null +++ b/crates/dav/src/calendar/copy_move.rs @@ -0,0 +1,937 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::{Server, auth::AccessToken}; +use dav_proto::{Depth, RequestHeaders, schema::response::CalCondition}; +use groupware::{ + DavName, DestroyArchive, + calendar::{Calendar, CalendarEvent, CalendarPreferences, Timezone}, + hierarchy::DavHierarchy, +}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use store::write::BatchBuilder; +use trc::AddContext; + +use crate::{DavError, DavErrorCondition, common::uri::DavUriResource, file::DavFileResource}; + +use super::assert_is_unique_uid; + +pub(crate) trait CalendarCopyMoveRequestHandler: Sync + Send { + fn handle_calendar_copy_move_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + is_move: bool, + ) -> impl Future> + Send; +} + +impl CalendarCopyMoveRequestHandler for Server { + async fn handle_calendar_copy_move_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + is_move: bool, + ) -> crate::Result { + // Validate source + let from_resource_ = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let from_account_id = from_resource_.account_id; + let from_resources = self + .fetch_dav_resources(access_token, from_account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + let from_resource_name = from_resource_ + .resource + .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; + let from_resource = from_resources + .paths + .by_name(from_resource_name) + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + + // Validate ACL + if !access_token.is_member(from_account_id) + && !self + .has_access_to_document( + access_token, + from_account_id, + Collection::Calendar, + if from_resource.is_container { + from_resource.document_id + } else { + from_resource.parent_id.unwrap() + }, + Acl::ReadItems, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Validate destination + let destination = self + .validate_uri( + access_token, + headers + .destination + .ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?, + ) + .await?; + if destination.collection != Collection::Calendar { + return Err(DavError::Code(StatusCode::BAD_GATEWAY)); + } + let to_account_id = destination + .account_id + .ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?; + let to_resources = if to_account_id == from_account_id { + from_resources.clone() + } else { + self.fetch_dav_resources(access_token, to_account_id, Collection::Calendar) + .await + .caused_by(trc::location!())? + }; + + // Map destination + let destination_resource_name = destination + .resource + .ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?; + if let Some(to_resource) = to_resources.paths.by_name(destination_resource_name) { + if from_resource.name == to_resource.name { + // Same resource + return Err(DavError::Code(StatusCode::BAD_GATEWAY)); + } + let new_name = destination_resource_name + .rsplit_once('/') + .map(|(_, name)| name) + .unwrap_or(destination_resource_name); + + match (from_resource.is_container, to_resource.is_container) { + (true, true) => { + let from_children_ids = from_resources + .subtree(from_resource_name) + .filter(|r| !r.is_container) + .map(|r| r.document_id) + .collect::>(); + let to_document_ids = to_resources + .subtree(destination_resource_name) + .filter(|r| !r.is_container) + .map(|r| r.document_id) + .collect::>(); + + // Validate ACLs + if !access_token.is_member(to_account_id) + || (!access_token.is_member(from_account_id) + && !self + .has_access_to_document( + access_token, + from_account_id, + Collection::Calendar, + from_resource.document_id, + if is_move { + Acl::RemoveItems + } else { + Acl::ReadItems + }, + ) + .await + .caused_by(trc::location!())?) + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Overwrite container + copy_container( + self, + access_token, + from_account_id, + from_resource.document_id, + from_children_ids, + to_account_id, + to_resource.document_id.into(), + to_document_ids, + new_name, + is_move, + ) + .await + } + (false, false) => { + // Overwrite event + let from_calendar_id = from_resource.parent_id.unwrap(); + let to_calendar_id = to_resource.parent_id.unwrap(); + + // Validate ACL + if (!access_token.is_member(from_account_id) + && !self + .has_access_to_document( + access_token, + from_account_id, + Collection::Calendar, + from_calendar_id, + if is_move { + Acl::RemoveItems + } else { + Acl::ReadItems + }, + ) + .await + .caused_by(trc::location!())?) + || (!access_token.is_member(to_account_id) + && !self + .has_access_to_document( + access_token, + to_account_id, + Collection::Calendar, + to_calendar_id, + Acl::RemoveItems, + ) + .await + .caused_by(trc::location!())?) + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + let to_base_path = to_resources + .format_resource(to_resource) + .rsplit_once('/') + .unwrap() + .0 + .to_string(); + if is_move { + move_event( + self, + access_token, + from_account_id, + from_resource.document_id, + from_calendar_id, + to_account_id, + to_resource.document_id.into(), + to_calendar_id, + to_base_path, + new_name, + ) + .await + } else { + copy_event( + self, + access_token, + from_account_id, + from_resource.document_id, + to_account_id, + to_resource.document_id.into(), + to_calendar_id, + to_base_path, + new_name, + ) + .await + } + } + _ => Err(DavError::Code(StatusCode::BAD_GATEWAY)), + } + } else if let Some((parent_resource, new_name)) = + to_resources.map_parent(destination_resource_name) + { + if let Some(parent_resource) = parent_resource { + // Creating items under a event is not allowed + // Copying/moving containers under a container is not allowed + if !parent_resource.is_container || from_resource.is_container { + return Err(DavError::Code(StatusCode::BAD_GATEWAY)); + } + + // Validate ACL + let from_calendar_id = from_resource.parent_id.unwrap(); + let to_calendar_id = parent_resource.document_id; + if (!access_token.is_member(from_account_id) + && !self + .has_access_to_document( + access_token, + from_account_id, + Collection::Calendar, + from_calendar_id, + if is_move { + Acl::RemoveItems + } else { + Acl::ReadItems + }, + ) + .await + .caused_by(trc::location!())?) + || (!access_token.is_member(to_account_id) + && !self + .has_access_to_document( + access_token, + to_account_id, + Collection::Calendar, + to_calendar_id, + Acl::AddItems, + ) + .await + .caused_by(trc::location!())?) + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Copy/move event + if is_move { + if from_account_id != to_account_id + || parent_resource.document_id != from_calendar_id + { + move_event( + self, + access_token, + from_account_id, + from_resource.document_id, + from_calendar_id, + to_account_id, + None, + to_calendar_id, + to_resources.format_resource(parent_resource), + new_name, + ) + .await + } else { + rename_event( + self, + access_token, + from_account_id, + from_resource.document_id, + from_calendar_id, + new_name, + ) + .await + } + } else { + copy_event( + self, + access_token, + from_account_id, + from_resource.document_id, + from_calendar_id, + None, + to_calendar_id, + to_resources.format_resource(parent_resource), + new_name, + ) + .await + } + } else { + // Copying/moving events to the root is not allowed + if !from_resource.is_container { + return Err(DavError::Code(StatusCode::BAD_GATEWAY)); + } + + // Shared users cannot create containers + if !access_token.is_member(to_account_id) { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Validate ACLs + if !access_token.is_member(from_account_id) + && !self + .has_access_to_document( + access_token, + from_account_id, + Collection::Calendar, + from_resource.document_id, + if is_move { + Acl::RemoveItems + } else { + Acl::ReadItems + }, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Copy/move container + let from_children_ids = from_resources + .subtree(from_resource_name) + .filter(|r| !r.is_container) + .map(|r| r.document_id) + .collect::>(); + if is_move { + if from_account_id != to_account_id { + copy_container( + self, + access_token, + from_account_id, + from_resource.document_id, + if headers.depth != Depth::Zero { + from_children_ids + } else { + return Err(DavError::Code(StatusCode::BAD_GATEWAY)); + }, + to_account_id, + None, + vec![], + new_name, + true, + ) + .await + } else { + rename_container( + self, + access_token, + from_account_id, + from_resource.document_id, + new_name, + ) + .await + } + } else { + copy_container( + self, + access_token, + from_account_id, + from_resource.document_id, + if headers.depth != Depth::Zero { + from_children_ids + } else { + vec![] + }, + to_account_id, + None, + vec![], + new_name, + false, + ) + .await + } + } + } else { + Err(DavError::Code(StatusCode::CONFLICT)) + } + } +} + +#[allow(clippy::too_many_arguments)] +async fn copy_event( + server: &Server, + access_token: &AccessToken, + from_account_id: u32, + from_document_id: u32, + to_account_id: u32, + to_document_id: Option, + to_calendar_id: u32, + to_base_path: String, + new_name: &str, +) -> crate::Result { + // Fetch event + let event_ = server + .get_archive(from_account_id, Collection::CalendarEvent, from_document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + let mut batch = BatchBuilder::new(); + + if from_account_id == to_account_id { + if let Some(name) = event + .inner + .names + .iter() + .find(|n| n.parent_id == to_calendar_id) + { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::NoUidConflict(format!("{}{}", to_base_path, name.name).into()), + ))); + } + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names.push(DavName { + name: new_name.to_string(), + parent_id: to_calendar_id, + }); + new_event + .update( + access_token, + event, + from_account_id, + from_document_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } else { + // Validate UID + assert_is_unique_uid( + server, + server + .fetch_dav_resources(access_token, to_account_id, Collection::Calendar) + .await + .caused_by(trc::location!())? + .as_ref(), + to_account_id, + to_calendar_id, + event.inner.event.uids().next().unwrap_or_default(), + ) + .await?; + + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names = vec![DavName { + name: new_name.to_string(), + parent_id: to_calendar_id, + }]; + let to_document_id = server + .store() + .assign_document_ids(to_account_id, Collection::CalendarEvent, 1) + .await + .caused_by(trc::location!())?; + new_event + .insert(access_token, to_account_id, to_document_id, &mut batch) + .caused_by(trc::location!())?; + } + + let response = if let Some(to_document_id) = to_document_id { + // Overwrite event on destination + let event_ = server + .get_archive(to_account_id, Collection::CalendarEvent, to_document_id) + .await + .caused_by(trc::location!())?; + if let Some(event_) = event_ { + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + + DestroyArchive(event) + .delete( + access_token, + to_account_id, + to_document_id, + to_calendar_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } + + Ok(HttpResponse::new(StatusCode::NO_CONTENT)) + } else { + Ok(HttpResponse::new(StatusCode::CREATED)) + }; + + server + .commit_batch(batch) + .await + .caused_by(trc::location!())?; + + response +} + +#[allow(clippy::too_many_arguments)] +async fn move_event( + server: &Server, + access_token: &AccessToken, + from_account_id: u32, + from_document_id: u32, + from_calendar_id: u32, + to_account_id: u32, + to_document_id: Option, + to_calendar_id: u32, + to_base_path: String, + new_name: &str, +) -> crate::Result { + // Fetch event + let event_ = server + .get_archive(from_account_id, Collection::CalendarEvent, from_document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + + let mut batch = BatchBuilder::new(); + if from_account_id == to_account_id { + let mut name_idx = None; + for (idx, name) in event.inner.names.iter().enumerate() { + if name.parent_id == to_calendar_id { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::NoUidConflict(format!("{}{}", to_base_path, name.name).into()), + ))); + } else if name.parent_id == from_calendar_id { + name_idx = Some(idx); + break; + } + } + + let name_idx = if let Some(name_idx) = name_idx { + name_idx + } else { + return Err(DavError::Code(StatusCode::NOT_FOUND)); + }; + + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names.swap_remove(name_idx); + new_event.names.push(DavName { + name: new_name.to_string(), + parent_id: to_calendar_id, + }); + new_event + .update( + access_token, + event.clone(), + from_account_id, + from_document_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } else { + // Validate UID + assert_is_unique_uid( + server, + server + .fetch_dav_resources(access_token, to_account_id, Collection::Calendar) + .await + .caused_by(trc::location!())? + .as_ref(), + to_account_id, + to_calendar_id, + event.inner.event.uids().next().unwrap_or_default(), + ) + .await?; + + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names = vec![DavName { + name: new_name.to_string(), + parent_id: to_calendar_id, + }]; + + DestroyArchive(event) + .delete( + access_token, + from_account_id, + from_document_id, + from_calendar_id, + &mut batch, + ) + .caused_by(trc::location!())?; + + let to_document_id = server + .store() + .assign_document_ids(to_account_id, Collection::CalendarEvent, 1) + .await + .caused_by(trc::location!())?; + new_event + .insert(access_token, to_account_id, to_document_id, &mut batch) + .caused_by(trc::location!())?; + } + + let response = if let Some(to_document_id) = to_document_id { + // Overwrite event on destination + let event_ = server + .get_archive(to_account_id, Collection::CalendarEvent, to_document_id) + .await + .caused_by(trc::location!())?; + if let Some(event_) = event_ { + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + + DestroyArchive(event) + .delete( + access_token, + to_account_id, + to_document_id, + to_calendar_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } + + Ok(HttpResponse::new(StatusCode::NO_CONTENT)) + } else { + Ok(HttpResponse::new(StatusCode::CREATED)) + }; + + server + .commit_batch(batch) + .await + .caused_by(trc::location!())?; + + response +} + +#[allow(clippy::too_many_arguments)] +async fn rename_event( + server: &Server, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + calendar_id: u32, + new_name: &str, +) -> crate::Result { + // Fetch event + let event_ = server + .get_archive(account_id, Collection::CalendarEvent, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + + let name_idx = event + .inner + .names + .iter() + .position(|n| n.parent_id == calendar_id) + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names[name_idx].name = new_name.to_string(); + + let mut batch = BatchBuilder::new(); + new_event + .update(access_token, event, account_id, document_id, &mut batch) + .caused_by(trc::location!())?; + server + .commit_batch(batch) + .await + .caused_by(trc::location!())?; + + Ok(HttpResponse::new(StatusCode::CREATED)) +} + +#[allow(clippy::too_many_arguments)] +async fn copy_container( + server: &Server, + access_token: &AccessToken, + from_account_id: u32, + from_document_id: u32, + from_children_ids: Vec, + to_account_id: u32, + to_document_id: Option, + to_children_ids: Vec, + new_name: &str, + remove_source: bool, +) -> crate::Result { + // Fetch calendar + let calendar_ = server + .get_archive(from_account_id, Collection::Calendar, from_document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let old_calendar = calendar_ + .to_unarchived::() + .caused_by(trc::location!())?; + let mut calendar = old_calendar + .deserialize::() + .caused_by(trc::location!())?; + + // Prepare write batch + let mut batch = BatchBuilder::new(); + + if remove_source { + DestroyArchive(old_calendar) + .delete(access_token, from_account_id, from_document_id, &mut batch) + .caused_by(trc::location!())?; + } + + let preference = calendar.preferences.into_iter().next().unwrap(); + calendar.name = new_name.to_string(); + calendar.default_alerts.clear(); + calendar.acls.clear(); + calendar.preferences = vec![CalendarPreferences { + account_id: to_account_id, + name: preference.name, + description: preference.description, + sort_order: 0, + color: preference.color, + flags: 0, + time_zone: Timezone::Default, + }]; + + let is_overwrite = to_document_id.is_some(); + let to_document_id = if let Some(to_document_id) = to_document_id { + // Overwrite destination + let calendar_ = server + .get_archive(to_account_id, Collection::Calendar, to_document_id) + .await + .caused_by(trc::location!())?; + if let Some(calendar_) = calendar_ { + let calendar = calendar_ + .to_unarchived::() + .caused_by(trc::location!())?; + + DestroyArchive(calendar) + .delete_with_events( + server, + access_token, + to_account_id, + to_document_id, + to_children_ids, + &mut batch, + ) + .await + .caused_by(trc::location!())?; + } + + to_document_id + } else { + server + .store() + .assign_document_ids(to_account_id, Collection::Calendar, 1) + .await + .caused_by(trc::location!())? + }; + calendar + .insert(access_token, to_account_id, to_document_id, &mut batch) + .caused_by(trc::location!())?; + + // Copy children + let mut required_space = 0; + for from_child_document_id in from_children_ids { + if let Some(event_) = server + .get_archive( + from_account_id, + Collection::CalendarEvent, + from_child_document_id, + ) + .await? + { + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + let mut new_name = None; + + for name in event.inner.names.iter() { + if name.parent_id == to_document_id { + continue; + } else if name.parent_id == from_document_id { + new_name = Some(name.name.to_string()); + } + } + let new_name = if let Some(new_name) = new_name { + DavName { + name: new_name, + parent_id: to_document_id, + } + } else { + continue; + }; + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + + if from_account_id == to_account_id { + if remove_source { + new_event + .names + .retain(|name| name.parent_id != from_document_id); + } + + new_event.names.push(new_name); + new_event + .update( + access_token, + event, + from_account_id, + from_document_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } else { + if remove_source { + DestroyArchive(event) + .delete( + access_token, + from_account_id, + from_child_document_id, + from_document_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } + + let to_document_id = server + .store() + .assign_document_ids(to_account_id, Collection::CalendarEvent, 1) + .await + .caused_by(trc::location!())?; + new_event.names = vec![new_name]; + required_space += new_event.size as u64; + new_event + .insert(access_token, to_account_id, to_document_id, &mut batch) + .caused_by(trc::location!())?; + } + } + } + + if from_account_id != to_account_id && required_space > 0 { + server + .has_available_quota( + &server + .get_resource_token(access_token, to_account_id) + .await?, + required_space, + ) + .await?; + } + + server + .commit_batch(batch) + .await + .caused_by(trc::location!())?; + + if !is_overwrite { + Ok(HttpResponse::new(StatusCode::CREATED)) + } else { + Ok(HttpResponse::new(StatusCode::NO_CONTENT)) + } +} + +#[allow(clippy::too_many_arguments)] +async fn rename_container( + server: &Server, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + new_name: &str, +) -> crate::Result { + // Fetch calendar + let calendar_ = server + .get_archive(account_id, Collection::Calendar, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let calendar = calendar_ + .to_unarchived::() + .caused_by(trc::location!())?; + let mut new_calendar = calendar + .deserialize::() + .caused_by(trc::location!())?; + new_calendar.name = new_name.to_string(); + + let mut batch = BatchBuilder::new(); + new_calendar + .update(access_token, calendar, account_id, document_id, &mut batch) + .caused_by(trc::location!())?; + server + .commit_batch(batch) + .await + .caused_by(trc::location!())?; + + Ok(HttpResponse::new(StatusCode::CREATED)) +} diff --git a/crates/dav/src/calendar/delete.rs b/crates/dav/src/calendar/delete.rs new file mode 100644 index 00000000..e02c4afe --- /dev/null +++ b/crates/dav/src/calendar/delete.rs @@ -0,0 +1,183 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::{Server, auth::AccessToken, sharing::EffectiveAcl}; +use dav_proto::RequestHeaders; +use groupware::{ + DestroyArchive, + calendar::{Calendar, CalendarEvent}, + hierarchy::DavHierarchy, +}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use store::write::BatchBuilder; +use trc::AddContext; + +use crate::{ + DavError, DavMethod, + common::{ + ETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; + +pub(crate) trait CalendarDeleteRequestHandler: Sync + Send { + fn handle_calendar_delete_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + ) -> impl Future> + Send; +} + +impl CalendarDeleteRequestHandler for Server { + async fn handle_calendar_delete_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + ) -> crate::Result { + // Validate URI + let resource = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let account_id = resource.account_id; + let delete_path = resource + .resource + .filter(|r| !r.is_empty()) + .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; + let resources = self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + + // Check resource type + let delete_resource = resources + .paths + .by_name(delete_path) + .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; + let document_id = delete_resource.document_id; + + // Fetch entry + let mut batch = BatchBuilder::new(); + if delete_resource.is_container { + let calendar_ = self + .get_archive(account_id, Collection::Calendar, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + + let calendar = calendar_ + .to_unarchived::() + .caused_by(trc::location!())?; + + // Validate ACL + if !access_token.is_member(account_id) + && !calendar + .inner + .acls + .effective_acl(access_token) + .contains_all([Acl::Delete, Acl::RemoveItems].into_iter()) + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Validate headers + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: Collection::Calendar, + document_id: document_id.into(), + etag: calendar.etag().into(), + path: delete_path, + ..Default::default() + }], + Default::default(), + DavMethod::DELETE, + ) + .await?; + + // Delete addresscalendar and events + DestroyArchive(calendar) + .delete_with_events( + self, + access_token, + account_id, + document_id, + resources + .subtree(delete_path) + .filter(|r| !r.is_container) + .map(|r| r.document_id) + .collect::>(), + &mut batch, + ) + .await + .caused_by(trc::location!())?; + } else { + // Validate ACL + let addresscalendar_id = delete_resource.parent_id.unwrap(); + if !access_token.is_member(account_id) + && !self + .has_access_to_document( + access_token, + account_id, + Collection::Calendar, + addresscalendar_id, + Acl::RemoveItems, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + let event_ = self + .get_archive(account_id, Collection::CalendarEvent, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + + // Validate headers + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: Collection::CalendarEvent, + document_id: document_id.into(), + etag: event_.etag().into(), + path: delete_path, + ..Default::default() + }], + Default::default(), + DavMethod::DELETE, + ) + .await?; + + // Delete event + DestroyArchive( + event_ + .to_unarchived::() + .caused_by(trc::location!())?, + ) + .delete( + access_token, + account_id, + document_id, + addresscalendar_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } + + self.commit_batch(batch).await.caused_by(trc::location!())?; + + Ok(HttpResponse::new(StatusCode::NO_CONTENT)) + } +} diff --git a/crates/dav/src/calendar/get.rs b/crates/dav/src/calendar/get.rs new file mode 100644 index 00000000..0fc06c93 --- /dev/null +++ b/crates/dav/src/calendar/get.rs @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::{Server, auth::AccessToken}; +use dav_proto::{RequestHeaders, schema::property::Rfc1123DateTime}; +use groupware::{calendar::CalendarEvent, hierarchy::DavHierarchy}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use trc::AddContext; + +use crate::{ + DavError, DavMethod, + common::{ + ETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; + +pub(crate) trait CalendarGetRequestHandler: Sync + Send { + fn handle_calendar_get_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + is_head: bool, + ) -> impl Future> + Send; +} + +impl CalendarGetRequestHandler for Server { + async fn handle_calendar_get_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + is_head: bool, + ) -> crate::Result { + // Validate URI + let resource_ = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let account_id = resource_.account_id; + let resources = self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + let resource = resources + .paths + .by_name( + resource_ + .resource + .ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?, + ) + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + if resource.is_container { + return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)); + } + + // Validate ACL + if !access_token.is_member(account_id) + && !self + .has_access_to_document( + access_token, + account_id, + Collection::Calendar, + resource.parent_id.unwrap(), + Acl::ReadItems, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Fetch event + let event_ = self + .get_archive(account_id, Collection::CalendarEvent, resource.document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let event = event_ + .unarchive::() + .caused_by(trc::location!())?; + + // Validate headers + let etag = event_.etag(); + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: Collection::CalendarEvent, + document_id: resource.document_id.into(), + etag: etag.clone().into(), + path: resource_.resource.unwrap(), + ..Default::default() + }], + Default::default(), + DavMethod::GET, + ) + .await?; + + let response = HttpResponse::new(StatusCode::OK) + .with_content_type("text/calendar; charset=utf-8") + .with_etag(etag) + .with_last_modified(Rfc1123DateTime::new(i64::from(event.modified)).to_string()); + + let ical = event.event.to_string(); + + if !is_head { + Ok(response.with_binary_body(ical)) + } else { + Ok(response.with_content_length(ical.len())) + } + } +} diff --git a/crates/dav/src/calendar/mkcol.rs b/crates/dav/src/calendar/mkcol.rs new file mode 100644 index 00000000..492dc24e --- /dev/null +++ b/crates/dav/src/calendar/mkcol.rs @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::{Server, auth::AccessToken}; +use dav_proto::{ + RequestHeaders, Return, + schema::{Namespace, request::MkCol, response::MkColResponse}, +}; +use groupware::{ + calendar::{Calendar, CalendarPreferences}, + hierarchy::DavHierarchy, +}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::collection::Collection; +use store::write::BatchBuilder; +use trc::AddContext; + +use crate::{ + DavError, DavMethod, + common::{ + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; + +use super::proppatch::CalendarPropPatchRequestHandler; + +pub(crate) trait CalendarMkColRequestHandler: Sync + Send { + fn handle_calendar_mkcol_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: Option, + ) -> impl Future> + Send; +} + +impl CalendarMkColRequestHandler for Server { + async fn handle_calendar_mkcol_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: Option, + ) -> crate::Result { + // Validate URI + let resource = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let account_id = resource.account_id; + let name = resource + .resource + .ok_or(DavError::Code(StatusCode::FORBIDDEN))?; + if name.contains('/') || !access_token.is_member(account_id) { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } else if self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())? + .paths + .by_name(name) + .is_some() + { + return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)); + } + + // Validate headers + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: resource.collection, + document_id: Some(u32::MAX), + path: name, + ..Default::default() + }], + Default::default(), + DavMethod::MKCOL, + ) + .await?; + + // Build file container + let mut calendar = Calendar { + name: name.to_string(), + preferences: vec![CalendarPreferences { + account_id, + name: name.to_string(), + ..Default::default() + }], + ..Default::default() + }; + + // Apply MKCOL properties + let mut return_prop_stat = None; + let mut is_mkcalendar = false; + if let Some(mkcol) = request { + let mut prop_stat = Vec::new(); + is_mkcalendar = mkcol.is_mkcalendar; + if !self.apply_calendar_properties( + account_id, + &mut calendar, + false, + mkcol.props, + &mut prop_stat, + ) { + return Ok(HttpResponse::new(StatusCode::FORBIDDEN).with_xml_body( + MkColResponse::new(prop_stat) + .with_namespace(Namespace::CalDav) + .with_mkcalendar(is_mkcalendar) + .to_string(), + )); + } + if headers.ret != Return::Minimal { + return_prop_stat = Some(prop_stat); + } + } + + // Prepare write batch + let mut batch = BatchBuilder::new(); + let document_id = self + .store() + .assign_document_ids(account_id, Collection::Calendar, 1) + .await + .caused_by(trc::location!())?; + calendar + .insert(access_token, account_id, document_id, &mut batch) + .caused_by(trc::location!())?; + self.commit_batch(batch).await.caused_by(trc::location!())?; + + if let Some(prop_stat) = return_prop_stat { + Ok(HttpResponse::new(StatusCode::CREATED).with_xml_body( + MkColResponse::new(prop_stat) + .with_namespace(Namespace::CalDav) + .with_mkcalendar(is_mkcalendar) + .to_string(), + )) + } else { + Ok(HttpResponse::new(StatusCode::CREATED)) + } + } +} diff --git a/crates/dav/src/calendar/mod.rs b/crates/dav/src/calendar/mod.rs index c8a832f8..b66a975f 100644 --- a/crates/dav/src/calendar/mod.rs +++ b/crates/dav/src/calendar/mod.rs @@ -3,3 +3,118 @@ * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ + +pub mod copy_move; +pub mod delete; +pub mod get; +pub mod mkcol; +pub mod proppatch; +pub mod query; +pub mod update; + +use crate::{DavError, DavErrorCondition}; +use common::{DavResources, Server}; +use dav_proto::schema::{ + property::{CalDavProperty, CalendarData, DavProperty, WebDavProperty}, + response::CalCondition, +}; +use groupware::IDX_UID; +use hyper::StatusCode; +use jmap_proto::types::collection::Collection; +use store::query::Filter; +use trc::AddContext; + +pub(crate) static CALENDAR_CONTAINER_PROPS: [DavProperty; 31] = [ + DavProperty::WebDav(WebDavProperty::CreationDate), + DavProperty::WebDav(WebDavProperty::DisplayName), + DavProperty::WebDav(WebDavProperty::GetETag), + DavProperty::WebDav(WebDavProperty::GetLastModified), + DavProperty::WebDav(WebDavProperty::ResourceType), + DavProperty::WebDav(WebDavProperty::LockDiscovery), + DavProperty::WebDav(WebDavProperty::SupportedLock), + DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal), + DavProperty::WebDav(WebDavProperty::SyncToken), + DavProperty::WebDav(WebDavProperty::Owner), + DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet), + DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet), + DavProperty::WebDav(WebDavProperty::Acl), + DavProperty::WebDav(WebDavProperty::AclRestrictions), + DavProperty::WebDav(WebDavProperty::InheritedAclSet), + DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet), + DavProperty::WebDav(WebDavProperty::SupportedReportSet), + DavProperty::WebDav(WebDavProperty::QuotaAvailableBytes), + DavProperty::WebDav(WebDavProperty::QuotaUsedBytes), + DavProperty::CalDav(CalDavProperty::CalendarDescription), + DavProperty::CalDav(CalDavProperty::SupportedCalendarData), + DavProperty::CalDav(CalDavProperty::SupportedCollationSet), + DavProperty::CalDav(CalDavProperty::SupportedCalendarComponentSet), + DavProperty::CalDav(CalDavProperty::CalendarTimezone), + DavProperty::CalDav(CalDavProperty::MaxResourceSize), + DavProperty::CalDav(CalDavProperty::MinDateTime), + DavProperty::CalDav(CalDavProperty::MaxDateTime), + DavProperty::CalDav(CalDavProperty::MaxInstances), + DavProperty::CalDav(CalDavProperty::MaxAttendeesPerInstance), + DavProperty::CalDav(CalDavProperty::TimezoneServiceSet), + DavProperty::CalDav(CalDavProperty::TimezoneId), +]; + +pub(crate) static CALENDAR_ITEM_PROPS: [DavProperty; 20] = [ + DavProperty::WebDav(WebDavProperty::CreationDate), + DavProperty::WebDav(WebDavProperty::DisplayName), + DavProperty::WebDav(WebDavProperty::GetETag), + DavProperty::WebDav(WebDavProperty::GetLastModified), + DavProperty::WebDav(WebDavProperty::ResourceType), + DavProperty::WebDav(WebDavProperty::LockDiscovery), + DavProperty::WebDav(WebDavProperty::SupportedLock), + DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal), + DavProperty::WebDav(WebDavProperty::SyncToken), + DavProperty::WebDav(WebDavProperty::Owner), + DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet), + DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet), + DavProperty::WebDav(WebDavProperty::Acl), + DavProperty::WebDav(WebDavProperty::AclRestrictions), + DavProperty::WebDav(WebDavProperty::InheritedAclSet), + DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet), + DavProperty::WebDav(WebDavProperty::GetContentLanguage), + DavProperty::WebDav(WebDavProperty::GetContentLength), + DavProperty::WebDav(WebDavProperty::GetContentType), + DavProperty::CalDav(CalDavProperty::CalendarData(CalendarData { + properties: vec![], + expand: None, + limit_recurrence: None, + limit_freebusy: None, + })), +]; + +pub(crate) async fn assert_is_unique_uid( + server: &Server, + resources: &DavResources, + account_id: u32, + calendar_id: u32, + uid: &str, +) -> crate::Result<()> { + let hits = server + .store() + .filter( + account_id, + Collection::CalendarEvent, + vec![Filter::eq(IDX_UID, uid.as_bytes().to_vec())], + ) + .await + .caused_by(trc::location!())?; + if !hits.results.is_empty() { + for path in resources.paths.iter() { + if !path.is_container + && hits.results.contains(path.document_id) + && path.parent_id.unwrap() == calendar_id + { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::NoUidConflict(resources.format_resource(path).into()), + ))); + } + } + } + + Ok(()) +} diff --git a/crates/dav/src/calendar/proppatch.rs b/crates/dav/src/calendar/proppatch.rs new file mode 100644 index 00000000..d8838556 --- /dev/null +++ b/crates/dav/src/calendar/proppatch.rs @@ -0,0 +1,533 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::{ + DavError, DavMethod, + common::{ + ETag, ExtractETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, +}; +use common::{Server, auth::AccessToken}; +use dav_proto::{ + RequestHeaders, Return, + schema::{ + Namespace, + property::{CalDavProperty, DavProperty, DavValue, ResourceType, WebDavProperty}, + request::{DavPropertyValue, PropertyUpdate}, + response::{BaseCondition, CalCondition, MultiStatus, PropStat, Response}, + }, +}; +use groupware::{ + calendar::{Calendar, CalendarEvent, Timezone}, + hierarchy::DavHierarchy, +}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use store::write::BatchBuilder; +use trc::AddContext; + +pub(crate) trait CalendarPropPatchRequestHandler: Sync + Send { + fn handle_calendar_proppatch_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: PropertyUpdate, + ) -> impl Future> + Send; + + fn apply_calendar_properties( + &self, + account_id: u32, + calendar: &mut Calendar, + is_update: bool, + properties: Vec, + items: &mut Vec, + ) -> bool; + + fn apply_event_properties( + &self, + event: &mut CalendarEvent, + is_update: bool, + properties: Vec, + items: &mut Vec, + ) -> bool; +} + +impl CalendarPropPatchRequestHandler for Server { + async fn handle_calendar_proppatch_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + mut request: PropertyUpdate, + ) -> crate::Result { + // Validate URI + let resource_ = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let uri = headers.uri; + let account_id = resource_.account_id; + let resources = self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + let resource = resource_ + .resource + .and_then(|r| resources.paths.by_name(r)) + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let document_id = resource.document_id; + let collection = if resource.is_container { + Collection::Calendar + } else { + Collection::CalendarEvent + }; + + if !request.has_changes() { + return Ok(HttpResponse::new(StatusCode::NO_CONTENT)); + } + + // Verify ACL + if !access_token.is_member(account_id) { + let (acl, document_id) = if resource.is_container { + (Acl::Read, resource.document_id) + } else { + (Acl::ReadItems, resource.parent_id.unwrap()) + }; + + if !self + .has_access_to_document( + access_token, + account_id, + Collection::Calendar, + document_id, + acl, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + } + + // Fetch archive + let archive = self + .get_archive(account_id, collection, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + + // Validate headers + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection, + document_id: document_id.into(), + etag: archive.etag().into(), + path: resource_.resource.unwrap(), + ..Default::default() + }], + Default::default(), + DavMethod::PROPPATCH, + ) + .await?; + + let is_success; + let mut batch = BatchBuilder::new(); + let mut items = Vec::with_capacity(request.remove.len() + request.set.len()); + + let etag = if resource.is_container { + // Deserialize + let calendar = archive + .to_unarchived::() + .caused_by(trc::location!())?; + let mut new_calendar = archive + .deserialize::() + .caused_by(trc::location!())?; + + // Remove properties + if !request.set_first && !request.remove.is_empty() { + remove_calendar_properties( + account_id, + &mut new_calendar, + std::mem::take(&mut request.remove), + &mut items, + ); + } + + // Set properties + is_success = self.apply_calendar_properties( + account_id, + &mut new_calendar, + true, + request.set, + &mut items, + ); + + // Remove properties + if is_success && !request.remove.is_empty() { + remove_calendar_properties( + account_id, + &mut new_calendar, + request.remove, + &mut items, + ); + } + + if is_success { + new_calendar + .update(access_token, calendar, account_id, document_id, &mut batch) + .caused_by(trc::location!())? + .etag() + } else { + calendar.etag().into() + } + } else { + // Deserialize + let event = archive + .to_unarchived::() + .caused_by(trc::location!())?; + let mut new_event = archive + .deserialize::() + .caused_by(trc::location!())?; + + // Remove properties + if !request.set_first && !request.remove.is_empty() { + remove_event_properties( + &mut new_event, + std::mem::take(&mut request.remove), + &mut items, + ); + } + + // Set properties + is_success = self.apply_event_properties(&mut new_event, true, request.set, &mut items); + + // Remove properties + if is_success && !request.remove.is_empty() { + remove_event_properties(&mut new_event, request.remove, &mut items); + } + + if is_success { + new_event + .update(access_token, event, account_id, document_id, &mut batch) + .caused_by(trc::location!())? + .etag() + } else { + event.etag().into() + } + }; + + if is_success { + self.commit_batch(batch).await.caused_by(trc::location!())?; + } + + if headers.ret != Return::Minimal || !is_success { + Ok(HttpResponse::new(StatusCode::MULTI_STATUS) + .with_xml_body( + MultiStatus::new(vec![Response::new_propstat(uri, items)]) + .with_namespace(Namespace::CalDav) + .to_string(), + ) + .with_etag_opt(etag)) + } else { + Ok(HttpResponse::new(StatusCode::NO_CONTENT).with_etag_opt(etag)) + } + } + + fn apply_calendar_properties( + &self, + account_id: u32, + calendar: &mut Calendar, + is_update: bool, + properties: Vec, + items: &mut Vec, + ) -> bool { + let mut has_errors = false; + + for property in properties { + match (property.property, property.value) { + (DavProperty::WebDav(WebDavProperty::DisplayName), DavValue::String(name)) => { + if name.len() <= self.core.dav.live_property_size { + calendar.preferences_mut(account_id).name = name; + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::DisplayName)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::DisplayName)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Display name too long"), + ); + has_errors = true; + } + } + ( + DavProperty::CalDav(CalDavProperty::CalendarDescription), + DavValue::String(name), + ) => { + if name.len() <= self.core.dav.live_property_size { + calendar.preferences_mut(account_id).description = Some(name); + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarDescription)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarDescription)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Calendar description too long"), + ); + has_errors = true; + } + } + ( + DavProperty::CalDav(CalDavProperty::CalendarTimezone), + DavValue::ICalendar(ical), + ) => { + if ical.size() > self.core.dav.max_ical_size { + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarTimezone)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Calendar timezone too large"), + ); + has_errors = true; + } else if !ical.is_timezone() { + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarTimezone)) + .with_status(StatusCode::PRECONDITION_FAILED) + .with_error(CalCondition::ValidCalendarData) + .with_response_description("Invalid calendar timezone"), + ); + has_errors = true; + } else { + calendar.preferences_mut(account_id).time_zone = Timezone::Custom(ical); + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarTimezone)) + .with_status(StatusCode::OK), + ); + } + } + (DavProperty::CalDav(CalDavProperty::TimezoneId), DavValue::String(tz_id)) => { + if !tz_id.is_empty() { + calendar.preferences_mut(account_id).time_zone = Timezone::IANA(tz_id); + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::TimezoneId)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::TimezoneId)) + .with_status(StatusCode::PRECONDITION_FAILED) + .with_error(CalCondition::ValidTimezone) + .with_response_description("Invalid timezone ID"), + ); + has_errors = true; + } + } + (DavProperty::WebDav(WebDavProperty::CreationDate), DavValue::Timestamp(dt)) => { + calendar.created = dt; + } + ( + DavProperty::WebDav(WebDavProperty::ResourceType), + DavValue::ResourceTypes(types), + ) => { + if types + .0 + .iter() + .all(|rt| matches!(rt, ResourceType::Collection | ResourceType::Calendar)) + { + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::ResourceType)) + .with_status(StatusCode::FORBIDDEN) + .with_error(BaseCondition::ValidResourceType), + ); + has_errors = true; + } else { + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::ResourceType)) + .with_status(StatusCode::OK), + ); + } + } + (DavProperty::DeadProperty(dead), DavValue::DeadProperty(values)) + if self.core.dav.dead_property_size.is_some() => + { + if is_update { + calendar.dead_properties.remove_element(&dead); + } + + if calendar.dead_properties.size() + values.size() + dead.size() + < self.core.dav.dead_property_size.unwrap() + { + calendar.dead_properties.add_element(dead.clone(), values.0); + items.push( + PropStat::new(DavProperty::DeadProperty(dead)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::DeadProperty(dead)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Dead property is too large."), + ); + has_errors = true; + } + } + (property, _) => { + items.push( + PropStat::new(property) + .with_status(StatusCode::CONFLICT) + .with_response_description("Property cannot be modified"), + ); + has_errors = true; + } + } + } + + !has_errors + } + + fn apply_event_properties( + &self, + event: &mut CalendarEvent, + is_update: bool, + properties: Vec, + items: &mut Vec, + ) -> bool { + let mut has_errors = false; + + for property in properties { + match (property.property, property.value) { + (DavProperty::WebDav(WebDavProperty::DisplayName), DavValue::String(name)) => { + if name.len() <= self.core.dav.live_property_size { + event.display_name = Some(name); + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::DisplayName)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::DisplayName)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Display name too long"), + ); + has_errors = true; + } + } + (DavProperty::WebDav(WebDavProperty::CreationDate), DavValue::Timestamp(dt)) => { + event.created = dt; + } + (DavProperty::DeadProperty(dead), DavValue::DeadProperty(values)) + if self.core.dav.dead_property_size.is_some() => + { + if is_update { + event.dead_properties.remove_element(&dead); + } + + if event.dead_properties.size() + values.size() + dead.size() + < self.core.dav.dead_property_size.unwrap() + { + event.dead_properties.add_element(dead.clone(), values.0); + items.push( + PropStat::new(DavProperty::DeadProperty(dead)) + .with_status(StatusCode::OK), + ); + } else { + items.push( + PropStat::new(DavProperty::DeadProperty(dead)) + .with_status(StatusCode::INSUFFICIENT_STORAGE) + .with_response_description("Dead property is too large."), + ); + has_errors = true; + } + } + (property, _) => { + items.push( + PropStat::new(property) + .with_status(StatusCode::CONFLICT) + .with_response_description("Property cannot be modified"), + ); + has_errors = true; + } + } + } + + !has_errors + } +} + +fn remove_event_properties( + event: &mut CalendarEvent, + properties: Vec, + items: &mut Vec, +) { + for property in properties { + match property { + DavProperty::WebDav(WebDavProperty::DisplayName) => { + event.display_name = None; + items.push( + PropStat::new(DavProperty::WebDav(WebDavProperty::DisplayName)) + .with_status(StatusCode::OK), + ); + } + DavProperty::DeadProperty(dead) => { + event.dead_properties.remove_element(&dead); + items.push( + PropStat::new(DavProperty::DeadProperty(dead)).with_status(StatusCode::OK), + ); + } + property => { + items.push( + PropStat::new(property) + .with_status(StatusCode::CONFLICT) + .with_response_description("Property cannot be deleted"), + ); + } + } + } +} + +fn remove_calendar_properties( + account_id: u32, + calendar: &mut Calendar, + properties: Vec, + items: &mut Vec, +) { + for property in properties { + match property { + DavProperty::CalDav(CalDavProperty::CalendarDescription) => { + calendar.preferences_mut(account_id).description = None; + items.push( + PropStat::new(DavProperty::CalDav(CalDavProperty::CalendarDescription)) + .with_status(StatusCode::OK), + ); + } + property @ (DavProperty::CalDav(CalDavProperty::CalendarTimezone) + | DavProperty::CalDav(CalDavProperty::TimezoneId)) => { + calendar.preferences_mut(account_id).time_zone = Timezone::Default; + items.push(PropStat::new(property).with_status(StatusCode::OK)); + } + DavProperty::DeadProperty(dead) => { + calendar.dead_properties.remove_element(&dead); + items.push( + PropStat::new(DavProperty::DeadProperty(dead)).with_status(StatusCode::OK), + ); + } + property => { + items.push( + PropStat::new(property) + .with_status(StatusCode::CONFLICT) + .with_response_description("Property cannot be deleted"), + ); + } + } + } +} diff --git a/crates/dav/src/calendar/query.rs b/crates/dav/src/calendar/query.rs new file mode 100644 index 00000000..ae7f888c --- /dev/null +++ b/crates/dav/src/calendar/query.rs @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::{Server, auth::AccessToken}; +use dav_proto::{ + RequestHeaders, + schema::request::{CalendarQuery, FreeBusyQuery}, +}; +use groupware::hierarchy::DavHierarchy; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use trc::AddContext; + +use crate::{ + DavError, + common::{ + DavQuery, + propfind::{PropFindItem, PropFindRequestHandler}, + uri::DavUriResource, + }, +}; + +pub(crate) trait CalendarQueryRequestHandler: Sync + Send { + fn handle_calendar_query_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: CalendarQuery, + ) -> impl Future> + Send; + + fn handle_calendar_freebusy_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: FreeBusyQuery, + ) -> impl Future> + Send; +} + +impl CalendarQueryRequestHandler for Server { + async fn handle_calendar_query_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: CalendarQuery, + ) -> crate::Result { + // Validate URI + let resource_ = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let account_id = resource_.account_id; + let resources = self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + let resource = resources + .paths + .by_name( + resource_ + .resource + .ok_or(DavError::Code(StatusCode::METHOD_NOT_ALLOWED))?, + ) + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + if !resource.is_container { + return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)); + } + + // Obtain shared ids + let shared_ids = if !access_token.is_member(account_id) { + self.shared_containers( + access_token, + account_id, + Collection::Calendar, + Acl::ReadItems, + ) + .await + .caused_by(trc::location!())? + .into() + } else { + None + }; + + // Obtain document ids in folder + let mut items = Vec::with_capacity(16); + for resource in resources.children(resource.document_id) { + if shared_ids + .as_ref() + .is_none_or(|ids| ids.contains(resource.document_id)) + { + items.push(PropFindItem::new( + resources.format_resource(resource), + account_id, + resource, + )); + } + } + + self.handle_dav_query( + access_token, + DavQuery::calendar_query(request, items, headers), + ) + .await + } + + async fn handle_calendar_freebusy_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + request: FreeBusyQuery, + ) -> crate::Result { + todo!() + } +} + +/* +pub(crate) fn vcard_query(card: &ArchivedVCard, filters: &AddressbookFilter) -> bool { + let mut is_all = true; + let mut matches_one = false; + + for filter in filters { + match filter { + Filter::AnyOf => { + is_all = false; + } + Filter::AllOf => { + is_all = true; + } + Filter::Property { prop, op, .. } => { + let result = if let Some(entry) = find_property(card, prop) { + match op { + FilterOp::Exists => true, + FilterOp::Undefined => false, + FilterOp::TextMatch(text_match) => { + let mut matched_any = false; + + for value in entry.values.iter() { + if let Some(text) = value.as_text() { + if text_match.matches(&text.to_lowercase()) { + matched_any = true; + break; + } + } + } + + matched_any + } + FilterOp::TimeRange(_) => false, + } + } else { + matches!(op, FilterOp::Undefined) + }; + + if result { + matches_one = true; + } else if is_all { + return false; + } + } + Filter::Parameter { + prop, param, op, .. + } => { + let result = if let Some(entry) = + find_property(card, prop).and_then(|entry| find_parameter(entry, param)) + { + match op { + FilterOp::Exists => true, + FilterOp::Undefined => false, + FilterOp::TextMatch(text_match) => { + if let Some(text) = entry.as_text() { + text_match.matches(&text.to_lowercase()) + } else { + false + } + } + FilterOp::TimeRange(_) => false, + } + } else { + matches!(op, FilterOp::Undefined) + }; + + if result { + matches_one = true; + } else if is_all { + return false; + } + } + Filter::Component { .. } => {} + } + } + + is_all || matches_one +} + +#[inline(always)] +fn find_property<'x>( + card: &'x ArchivedVCard, + prop: &VCardPropertyWithGroup, +) -> Option<&'x ArchivedVCardEntry> { + card.entries + .iter() + .find(|entry| entry.name == prop.name && entry.group == prop.group) +} + +#[inline(always)] +fn find_parameter<'x>( + entry: &'x ArchivedVCardEntry, + name: &VCardParameterName, +) -> Option<&'x ArchivedVCardParameter> { + entry.params.iter().find(|param| param.matches_name(name)) +} +*/ diff --git a/crates/dav/src/calendar/update.rs b/crates/dav/src/calendar/update.rs new file mode 100644 index 00000000..8a0a8333 --- /dev/null +++ b/crates/dav/src/calendar/update.rs @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::collections::HashSet; + +use calcard::{ + Entry, Parser, + icalendar::{ICalendar, ICalendarComponentType}, +}; +use common::{Server, auth::AccessToken}; +use dav_proto::{ + RequestHeaders, Return, + schema::{property::Rfc1123DateTime, response::CalCondition}, +}; +use groupware::{DavName, calendar::CalendarEvent, hierarchy::DavHierarchy}; +use http_proto::HttpResponse; +use hyper::StatusCode; +use jmap_proto::types::{acl::Acl, collection::Collection}; +use store::write::BatchBuilder; +use trc::AddContext; + +use crate::{ + DavError, DavErrorCondition, DavMethod, + common::{ + ETag, ExtractETag, + lock::{LockRequestHandler, ResourceState}, + uri::DavUriResource, + }, + file::DavFileResource, +}; + +use super::assert_is_unique_uid; + +pub(crate) trait CalendarUpdateRequestHandler: Sync + Send { + fn handle_calendar_update_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + bytes: Vec, + is_patch: bool, + ) -> impl Future> + Send; +} + +impl CalendarUpdateRequestHandler for Server { + async fn handle_calendar_update_request( + &self, + access_token: &AccessToken, + headers: RequestHeaders<'_>, + bytes: Vec, + _is_patch: bool, + ) -> crate::Result { + // Validate URI + let resource = self + .validate_uri(access_token, headers.uri) + .await? + .into_owned_uri()?; + let account_id = resource.account_id; + let resources = self + .fetch_dav_resources(access_token, account_id, Collection::Calendar) + .await + .caused_by(trc::location!())?; + let resource_name = resource + .resource + .ok_or(DavError::Code(StatusCode::CONFLICT))?; + + if bytes.len() > self.core.dav.max_ical_size { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::MaxResourceSize(self.core.dav.max_ical_size as u32), + ))); + } + let ical_raw = std::str::from_utf8(&bytes).map_err(|_| { + DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::SupportedCalendarData, + )) + })?; + + let ical = match Parser::new(ical_raw).entry() { + Entry::ICalendar(ical) => ical, + _ => { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::SupportedCalendarData, + ))); + } + }; + + if let Some(resource) = resources.paths.by_name(resource_name) { + if resource.is_container { + return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)); + } + + // Validate ACL + let parent_id = resource.parent_id.unwrap(); + let document_id = resource.document_id; + if !access_token.is_member(account_id) + && !self + .has_access_to_document( + access_token, + account_id, + Collection::Calendar, + parent_id, + Acl::ModifyItems, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Update + let event_ = self + .get_archive(account_id, Collection::CalendarEvent, document_id) + .await + .caused_by(trc::location!())? + .ok_or(DavError::Code(StatusCode::NOT_FOUND))?; + let event = event_ + .to_unarchived::() + .caused_by(trc::location!())?; + + // Validate headers + match self + .validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: Collection::CalendarEvent, + document_id: Some(document_id), + etag: event.etag().into(), + path: resource_name, + ..Default::default() + }], + Default::default(), + DavMethod::PUT, + ) + .await + { + Ok(_) => {} + Err(DavError::Code(StatusCode::PRECONDITION_FAILED)) + if headers.ret == Return::Representation => + { + return Ok(HttpResponse::new(StatusCode::PRECONDITION_FAILED) + .with_content_type("text/calendar; charset=utf-8") + .with_etag(event.etag()) + .with_last_modified( + Rfc1123DateTime::new(i64::from(event.inner.modified)).to_string(), + ) + .with_header("Preference-Applied", "return=representation") + .with_binary_body(event.inner.event.to_string())); + } + Err(e) => return Err(e), + } + + // Validate quota + let extra_bytes = + (bytes.len() as u64).saturating_sub(u32::from(event.inner.size) as u64); + if extra_bytes > 0 { + self.has_available_quota( + &self.get_resource_token(access_token, account_id).await?, + extra_bytes, + ) + .await?; + } + + // Validate iCal + if event.inner.event.uids().next().unwrap_or_default() != validate_ical(&ical)? { + return Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::NoUidConflict(resources.format_resource(resource).into()), + ))); + } + + // Build node + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.size = bytes.len() as u32; + new_event.event = ical; + + // Prepare write batch + let mut batch = BatchBuilder::new(); + let etag = new_event + .update(access_token, event, account_id, document_id, &mut batch) + .caused_by(trc::location!())? + .etag(); + self.commit_batch(batch).await.caused_by(trc::location!())?; + + Ok(HttpResponse::new(StatusCode::NO_CONTENT).with_etag_opt(etag)) + } else if let Some((Some(parent), name)) = resources.map_parent(resource_name) { + if !parent.is_container { + return Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)); + } + + // Validate ACL + if !access_token.is_member(account_id) + && !self + .has_access_to_document( + access_token, + account_id, + Collection::Calendar, + parent.document_id, + Acl::AddItems, + ) + .await + .caused_by(trc::location!())? + { + return Err(DavError::Code(StatusCode::FORBIDDEN)); + } + + // Validate headers + self.validate_headers( + access_token, + &headers, + vec![ResourceState { + account_id, + collection: resource.collection, + document_id: Some(u32::MAX), + path: resource_name, + ..Default::default() + }], + Default::default(), + DavMethod::PUT, + ) + .await?; + + // Validate quota + if !bytes.is_empty() { + self.has_available_quota( + &self.get_resource_token(access_token, account_id).await?, + bytes.len() as u64, + ) + .await?; + } + + // Validate ical object + assert_is_unique_uid( + self, + &resources, + account_id, + parent.document_id, + validate_ical(&ical)?, + ) + .await?; + + // Build node + let event = CalendarEvent { + names: vec![DavName { + name: name.to_string(), + parent_id: parent.document_id, + }], + event: ical, + size: bytes.len() as u32, + ..Default::default() + }; + + // Prepare write batch + let mut batch = BatchBuilder::new(); + let document_id = self + .store() + .assign_document_ids(account_id, Collection::CalendarEvent, 1) + .await + .caused_by(trc::location!())?; + let etag = event + .insert(access_token, account_id, document_id, &mut batch) + .caused_by(trc::location!())? + .etag(); + self.commit_batch(batch).await.caused_by(trc::location!())?; + + Ok(HttpResponse::new(StatusCode::CREATED).with_etag_opt(etag)) + } else { + Err(DavError::Code(StatusCode::CONFLICT))? + } + } +} + +fn validate_ical(ical: &ICalendar) -> crate::Result<&str> { + // Validate UIDs + let uids = ical.uids().collect::>(); + + // Validate component types + let mut types: [u8; 4] = [0; 4]; + for comp in &ical.components { + match comp.component_type { + ICalendarComponentType::VEvent => { + types[0] += 1; + } + ICalendarComponentType::VTodo => { + types[1] += 1; + } + ICalendarComponentType::VJournal => { + types[2] += 1; + } + ICalendarComponentType::VFreebusy => { + types[3] += 1; + } + _ => {} + } + } + + if uids.len() == 1 && types.iter().filter(|&&v| v == 0).count() == 3 { + Ok(uids.iter().next().unwrap()) + } else { + Err(DavError::Condition(DavErrorCondition::new( + StatusCode::PRECONDITION_FAILED, + CalCondition::ValidCalendarObjectResource, + ))) + } +} diff --git a/crates/dav/src/card/mod.rs b/crates/dav/src/card/mod.rs index 73019354..7dbd0447 100644 --- a/crates/dav/src/card/mod.rs +++ b/crates/dav/src/card/mod.rs @@ -9,7 +9,7 @@ use dav_proto::schema::{ property::{CardDavProperty, DavProperty, WebDavProperty}, response::CardCondition, }; -use groupware::IDX_CARD_UID; +use groupware::IDX_UID; use hyper::StatusCode; use jmap_proto::types::collection::Collection; use store::query::Filter; @@ -74,31 +74,6 @@ pub(crate) static CARD_ITEM_PROPS: [DavProperty; 20] = [ DavProperty::CardDav(CardDavProperty::AddressData(vec![])), ]; -pub(crate) static CARD_ALL_PROPS: [DavProperty; 22] = [ - DavProperty::WebDav(WebDavProperty::CreationDate), - DavProperty::WebDav(WebDavProperty::DisplayName), - DavProperty::WebDav(WebDavProperty::GetETag), - DavProperty::WebDav(WebDavProperty::GetLastModified), - DavProperty::WebDav(WebDavProperty::ResourceType), - DavProperty::WebDav(WebDavProperty::LockDiscovery), - DavProperty::WebDav(WebDavProperty::SupportedLock), - DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal), - DavProperty::WebDav(WebDavProperty::SyncToken), - DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet), - DavProperty::WebDav(WebDavProperty::AclRestrictions), - DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet), - DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet), - DavProperty::WebDav(WebDavProperty::GetContentLanguage), - DavProperty::WebDav(WebDavProperty::GetContentLength), - DavProperty::WebDav(WebDavProperty::GetContentType), - DavProperty::WebDav(WebDavProperty::SupportedReportSet), - DavProperty::CardDav(CardDavProperty::AddressData(vec![])), - DavProperty::CardDav(CardDavProperty::AddressbookDescription), - DavProperty::CardDav(CardDavProperty::SupportedAddressData), - DavProperty::CardDav(CardDavProperty::SupportedCollationSet), - DavProperty::CardDav(CardDavProperty::MaxResourceSize), -]; - pub(crate) async fn assert_is_unique_uid( server: &Server, resources: &DavResources, @@ -112,7 +87,7 @@ pub(crate) async fn assert_is_unique_uid( .filter( account_id, Collection::ContactCard, - vec![Filter::eq(IDX_CARD_UID, uid.as_bytes().to_vec())], + vec![Filter::eq(IDX_UID, uid.as_bytes().to_vec())], ) .await .caused_by(trc::location!())?; diff --git a/crates/dav/src/common/acl.rs b/crates/dav/src/common/acl.rs index 7b56f845..0dba62e8 100644 --- a/crates/dav/src/common/acl.rs +++ b/crates/dav/src/common/acl.rs @@ -146,7 +146,21 @@ impl DavAclHandler for Server { let mut batch = BatchBuilder::new(); match container { - ArchivedResource::Calendar(calendar) => todo!(), + ArchivedResource::Calendar(calendar) => { + let mut new_calendar = calendar + .deserialize::() + .caused_by(trc::location!())?; + new_calendar.acls = grants; + new_calendar + .update( + access_token, + calendar, + account_id, + resource.document_id, + &mut batch, + ) + .caused_by(trc::location!())?; + } ArchivedResource::AddressBook(book) => { let mut new_book = book .deserialize::() diff --git a/crates/dav/src/common/mod.rs b/crates/dav/src/common/mod.rs index c5f897b3..f661202d 100644 --- a/crates/dav/src/common/mod.rs +++ b/crates/dav/src/common/mod.rs @@ -330,10 +330,9 @@ impl<'x> ArchivedResource<'x> { pub fn display_name(&self, account_id: u32) -> Option<&str> { match self { - ArchivedResource::Calendar(archive) => archive - .inner - .preferences(account_id) - .map(|p| p.name.as_str()), + ArchivedResource::Calendar(archive) => { + Some(archive.inner.preferences(account_id).name.as_str()) + } ArchivedResource::CalendarEvent(archive) => archive.inner.display_name.as_deref(), ArchivedResource::AddressBook(archive) => archive.inner.display_name.as_deref(), ArchivedResource::ContactCard(archive) => archive.inner.display_name.as_deref(), diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 9b99502a..a12d0d5e 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -4,9 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::sync::Arc; - -use calcard::vcard::{VCard, VCardEntry}; +use crate::{ + DavError, DavErrorCondition, + calendar::{CALENDAR_CONTAINER_PROPS, CALENDAR_ITEM_PROPS}, + card::{CARD_CONTAINER_PROPS, CARD_ITEM_PROPS, query::vcard_query}, + common::{DavQueryResource, uri::DavUriResource}, + file::{FILE_CONTAINER_PROPS, FILE_ITEM_PROPS}, + principal::{CurrentUserPrincipal, propfind::PrincipalPropFind}, +}; use common::{ DavResource, DavResources, Server, auth::{AccessToken, AsTenantId}, @@ -17,8 +22,8 @@ use dav_proto::{ schema::{ Collation, Namespace, property::{ - ActiveLock, CardDavProperty, DavProperty, DavValue, PrincipalProperty, Privilege, - ReportSet, ResourceType, Rfc1123DateTime, SupportedCollation, SupportedLock, + ActiveLock, CalDavProperty, CardDavProperty, DavProperty, DavValue, PrincipalProperty, + Privilege, ReportSet, ResourceType, Rfc1123DateTime, SupportedCollation, SupportedLock, WebDavProperty, }, request::{DavPropertyValue, PropFind}, @@ -29,27 +34,21 @@ use dav_proto::{ }, }; use directory::{Type, backend::internal::manage::ManageDirectory}; -use groupware::{DavResourceName, hierarchy::DavHierarchy}; +use groupware::{DavResourceName, calendar::ArchivedTimezone, hierarchy::DavHierarchy}; use http_proto::HttpResponse; use hyper::StatusCode; use jmap_proto::types::{acl::Acl, collection::Collection}; use percent_encoding::NON_ALPHANUMERIC; +use std::fmt::Write; +use std::sync::Arc; use store::{ ahash::AHashMap, query::log::Query, roaring::RoaringBitmap, - write::{AlignedBytes, Archive, serialize::rkyv_deserialize}, + write::{AlignedBytes, Archive}, }; use trc::AddContext; -use crate::{ - DavError, DavErrorCondition, - card::{CARD_ALL_PROPS, CARD_CONTAINER_PROPS, CARD_ITEM_PROPS, query::vcard_query}, - common::{DavQueryResource, uri::DavUriResource}, - file::{FILE_ALL_PROPS, FILE_CONTAINER_PROPS, FILE_ITEM_PROPS}, - principal::{CurrentUserPrincipal, propfind::PrincipalPropFind}, -}; - use super::{ ArchivedResource, DavCollection, DavQuery, DavQueryFilter, ETag, acl::{DavAclHandler, Privileges}, @@ -229,6 +228,20 @@ impl PropFindRequestHandler for Server { vec![access_token.current_user_principal()], )); } + DavProperty::Principal(PrincipalProperty::CalendarHomeSet) => { + fields.push(DavPropertyValue::new( + prop.clone(), + vec![Href(format!( + "{}/{}/", + DavResourceName::Cal.base_path(), + percent_encoding::utf8_percent_encode( + &access_token.name, + NON_ALPHANUMERIC + ), + ))], + )); + response.set_namespace(Namespace::CalDav); + } DavProperty::Principal(PrincipalProperty::AddressbookHomeSet) => { fields.push(DavPropertyValue::new( prop.clone(), @@ -508,14 +521,14 @@ impl PropFindRequestHandler for Server { (resources, document_ids) }; - let c = println!( + /*let c = println!( "resources: {:?} resource: {resource:?}", resources .paths .iter() .map(|r| r.name.to_string()) .collect::>() - ); + );*/ if let Some(resource) = resource .resource @@ -575,16 +588,16 @@ impl PropFindRequestHandler for Server { } let mut is_all_prop = false; - let todo = "prop lists for calendar"; let properties = match &query.propfind { PropFind::PropName => { let (container_props, children_props) = match collection_container { Collection::FileNode => { (FILE_CONTAINER_PROPS.as_slice(), FILE_ITEM_PROPS.as_slice()) } - Collection::Calendar => { - (FILE_CONTAINER_PROPS.as_slice(), FILE_ITEM_PROPS.as_slice()) - } + Collection::Calendar => ( + CALENDAR_CONTAINER_PROPS.as_slice(), + CALENDAR_ITEM_PROPS.as_slice(), + ), Collection::AddressBook => { (CARD_CONTAINER_PROPS.as_slice(), CARD_ITEM_PROPS.as_slice()) } @@ -618,15 +631,8 @@ impl PropFindRequestHandler for Server { } PropFind::AllProp(items) => { is_all_prop = true; - let all_props = match collection_container { - Collection::FileNode => FILE_ALL_PROPS.as_slice(), - Collection::Calendar => FILE_ALL_PROPS.as_slice(), - Collection::AddressBook => CARD_ALL_PROPS.as_slice(), - _ => unreachable!(), - }; - - let mut result = Vec::with_capacity(items.len() + all_props.len()); - result.extend_from_slice(all_props); + let mut result = Vec::with_capacity(items.len() + DavProperty::ALL_PROPS.len()); + result.extend(DavProperty::ALL_PROPS); result.extend(items.iter().filter(|field| !field.is_all_prop()).cloned()); result } @@ -666,9 +672,7 @@ impl PropFindRequestHandler for Server { ( DavQueryFilter::Calendar { filter, timezone }, ArchivedResource::CalendarEvent(event), - ) => { - todo!() - } + ) => {} _ => (), } } @@ -999,42 +1003,26 @@ impl PropFindRequestHandler for Server { CardDavProperty::AddressData(items), ArchivedResource::ContactCard(card), ) => { - let mut vcard; - if !items.is_empty() { - vcard = VCard { - entries: Vec::with_capacity(items.len()), - }; + let vcard = if !items.is_empty() { + let mut vcard = String::with_capacity(128); + let _ = write!(&mut vcard, "BEGIN:VCARD\r\n"); for item in items { for entry in card.inner.card.entries.iter() { if entry.name == item.name && entry.group == item.group { - if !item.no_value { - vcard.entries.push( - rkyv_deserialize(entry) - .caused_by(trc::location!())?, - ); - } else { - vcard.entries.push(VCardEntry { - group: item.group.clone(), - name: item.name.clone(), - params: vec![], - values: vec![], - }); - } + let _ = entry.write_to(&mut vcard, !item.no_value); break; } } } - fields.push(DavPropertyValue::new( - property.clone(), - rkyv_deserialize(&card.inner.card) - .caused_by(trc::location!())?, - )); + let _ = write!(&mut vcard, "END:VCARD\r\n"); + vcard } else { - vcard = rkyv_deserialize(&card.inner.card) - .caused_by(trc::location!())? - } - - fields.push(DavPropertyValue::new(property.clone(), vcard)); + card.inner.card.to_string() + }; + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::CData(vcard), + )); } _ => { if !is_all_prop { @@ -1042,9 +1030,124 @@ impl PropFindRequestHandler for Server { } } }, - DavProperty::CalDav(cal_property) => { - todo!() - } + DavProperty::CalDav(cal_property) => match (cal_property, &archive) { + ( + CalDavProperty::CalendarDescription, + ArchivedResource::Calendar(calendar), + ) => { + if let Some(desc) = calendar + .inner + .preferences(account_id) + .description + .as_deref() + { + fields.push(DavPropertyValue::new( + property.clone(), + desc.to_string(), + )); + } else { + fields_not_found.push(DavPropertyValue::empty(property.clone())); + } + } + ( + CalDavProperty::CalendarTimezone, + ArchivedResource::Calendar(calendar), + ) => { + if let ArchivedTimezone::Custom(tz) = + &calendar.inner.preferences(account_id).time_zone + { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::CData(tz.to_string()), + )); + } else { + fields_not_found.push(DavPropertyValue::empty(property.clone())); + } + } + (CalDavProperty::TimezoneId, ArchivedResource::Calendar(calendar)) => { + if let ArchivedTimezone::IANA(tz) = + &calendar.inner.preferences(account_id).time_zone + { + fields + .push(DavPropertyValue::new(property.clone(), tz.to_string())); + } else { + fields_not_found.push(DavPropertyValue::empty(property.clone())); + } + } + ( + CalDavProperty::SupportedCalendarComponentSet, + ArchivedResource::Calendar(_), + ) => { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::SupportedCalendarComponentSet, + )); + } + (CalDavProperty::SupportedCalendarData, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::SupportedCalendarData, + )); + } + (CalDavProperty::SupportedCollationSet, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::Collations(List(vec![ + SupportedCollation { + collation: Collation::AsciiCasemap, + namespace: Namespace::CalDav, + }, + SupportedCollation { + collation: Collation::UnicodeCasemap, + namespace: Namespace::CalDav, + }, + ])), + )); + } + (CalDavProperty::MaxResourceSize, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + self.core.dav.max_ical_size as u64, + )); + } + (CalDavProperty::MinDateTime, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::Timestamp(-2201212800), + )); + } + (CalDavProperty::MaxDateTime, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + DavValue::Timestamp(32531605200), + )); + } + (CalDavProperty::MaxInstances, ArchivedResource::Calendar(_)) => { + fields.push(DavPropertyValue::new( + property.clone(), + self.core.dav.max_ical_instances as u64, + )); + } + ( + CalDavProperty::MaxAttendeesPerInstance, + ArchivedResource::Calendar(_), + ) => { + fields.push(DavPropertyValue::new( + property.clone(), + self.core.dav.max_ical_attendees_per_instance as u64, + )); + } + ( + CalDavProperty::CalendarData(calendar_data), + ArchivedResource::CalendarEvent(calendar), + ) => {} + + _ => { + if !is_all_prop { + fields_not_found.push(DavPropertyValue::empty(property.clone())); + } + } + }, property => { if !is_all_prop { diff --git a/crates/dav/src/file/mod.rs b/crates/dav/src/file/mod.rs index 6ca7a725..ece847fa 100644 --- a/crates/dav/src/file/mod.rs +++ b/crates/dav/src/file/mod.rs @@ -64,26 +64,6 @@ pub(crate) static FILE_ITEM_PROPS: [DavProperty; 19] = [ DavProperty::WebDav(WebDavProperty::GetContentType), ]; -pub(crate) static FILE_ALL_PROPS: [DavProperty; 17] = [ - DavProperty::WebDav(WebDavProperty::CreationDate), - DavProperty::WebDav(WebDavProperty::DisplayName), - DavProperty::WebDav(WebDavProperty::GetETag), - DavProperty::WebDav(WebDavProperty::GetLastModified), - DavProperty::WebDav(WebDavProperty::ResourceType), - DavProperty::WebDav(WebDavProperty::LockDiscovery), - DavProperty::WebDav(WebDavProperty::SupportedLock), - DavProperty::WebDav(WebDavProperty::CurrentUserPrincipal), - DavProperty::WebDav(WebDavProperty::SyncToken), - DavProperty::WebDav(WebDavProperty::SupportedPrivilegeSet), - DavProperty::WebDav(WebDavProperty::AclRestrictions), - DavProperty::WebDav(WebDavProperty::CurrentUserPrivilegeSet), - DavProperty::WebDav(WebDavProperty::PrincipalCollectionSet), - DavProperty::WebDav(WebDavProperty::GetContentLanguage), - DavProperty::WebDav(WebDavProperty::GetContentLength), - DavProperty::WebDav(WebDavProperty::GetContentType), - DavProperty::WebDav(WebDavProperty::SupportedReportSet), -]; - pub(crate) trait FromDavResource { fn from_dav_resource(item: &DavResource) -> Self; } diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 431eb60a..41a74e92 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -29,6 +29,7 @@ pub enum DavMethod { PROPPATCH, REPORT, MKCOL, + MKCALENDAR, COPY, MOVE, LOCK, @@ -89,6 +90,7 @@ impl DavMethod { "PROPPATCH" => DavMethod::PROPPATCH, "REPORT" => DavMethod::REPORT, "MKCOL" => DavMethod::MKCOL, + "MKCALENDAR" => DavMethod::MKCALENDAR, "COPY" => DavMethod::COPY, "MOVE" => DavMethod::MOVE, "LOCK" => DavMethod::LOCK, @@ -111,6 +113,7 @@ impl DavMethod { | DavMethod::REPORT | DavMethod::LOCK | DavMethod::ACL + | DavMethod::MKCALENDAR ) } } diff --git a/crates/dav/src/principal/propfind.rs b/crates/dav/src/principal/propfind.rs index baece680..26e9c2f8 100644 --- a/crates/dav/src/principal/propfind.rs +++ b/crates/dav/src/principal/propfind.rs @@ -246,6 +246,17 @@ impl PrincipalPropFind for Server { ))], )); } + PrincipalProperty::CalendarHomeSet => { + fields.push(DavPropertyValue::new( + property.clone(), + vec![Href(format!( + "{}/{}/", + DavResourceName::Cal.base_path(), + percent_encoding::utf8_percent_encode(&name, NON_ALPHANUMERIC), + ))], + )); + response.set_namespace(Namespace::CalDav); + } PrincipalProperty::AddressbookHomeSet => { fields.push(DavPropertyValue::new( property.clone(), diff --git a/crates/dav/src/request.rs b/crates/dav/src/request.rs index 1b0cb86f..32bf52f3 100644 --- a/crates/dav/src/request.rs +++ b/crates/dav/src/request.rs @@ -27,6 +27,12 @@ use jmap_proto::types::collection::Collection; use crate::{ DavError, DavMethod, DavResourceName, + calendar::{ + copy_move::CalendarCopyMoveRequestHandler, delete::CalendarDeleteRequestHandler, + get::CalendarGetRequestHandler, mkcol::CalendarMkColRequestHandler, + proppatch::CalendarPropPatchRequestHandler, query::CalendarQueryRequestHandler, + update::CalendarUpdateRequestHandler, + }, card::{ copy_move::CardCopyMoveRequestHandler, delete::CardDeleteRequestHandler, get::CardGetRequestHandler, mkcol::CardMkColRequestHandler, @@ -95,63 +101,23 @@ impl DavRequestDispatcher for Server { ) .await } - DavMethod::PROPPATCH => { - let request = PropertyUpdate::parse(&mut Tokenizer::new(&body))?; - match resource { - DavResourceName::Card => { - self.handle_card_proppatch_request(&access_token, headers, request) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_proppatch_request(&access_token, headers, request) - .await - } - DavResourceName::Principal => { - Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) - } - } - } - DavMethod::MKCOL => { - let request = if !body.is_empty() { - Some(MkCol::parse(&mut Tokenizer::new(&body))?) - } else { - None - }; - - match resource { - DavResourceName::Card => { - self.handle_card_mkcol_request(&access_token, headers, request) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_mkcol_request(&access_token, headers, request) - .await - } - DavResourceName::Principal => { - Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) - } - } - } - DavMethod::GET => match resource { + DavMethod::GET | DavMethod::HEAD => match resource { DavResourceName::Card => { - self.handle_card_get_request(&access_token, headers, false) - .await + self.handle_card_get_request( + &access_token, + headers, + matches!(method, DavMethod::HEAD), + ) + .await } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_get_request(&access_token, headers, false) - .await + DavResourceName::Cal => { + self.handle_calendar_get_request( + &access_token, + headers, + matches!(method, DavMethod::HEAD), + ) + .await } - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - }, - DavMethod::HEAD => match resource { - DavResourceName::Card => { - self.handle_card_get_request(&access_token, headers, true) - .await - } - DavResourceName::Cal => todo!(), DavResourceName::File => { #[cfg(debug_assertions)] { @@ -166,113 +132,16 @@ impl DavRequestDispatcher for Server { #[cfg(not(debug_assertions))] { - self.handle_file_get_request(&access_token, headers, true) - .await + self.handle_file_get_request( + &access_token, + headers, + matches!(method, DavMethod::HEAD), + ) + .await } } DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), }, - DavMethod::DELETE => { - // Include any fragments in the URI - if let Some(p) = request.uri().path_and_query() { - // TODO: Access to the fragment part is pending, see https://github.com/hyperium/http/issues/127 - headers.uri = p.as_str(); - } - - match resource { - DavResourceName::Card => { - self.handle_card_delete_request(&access_token, headers) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_delete_request(&access_token, headers) - .await - } - DavResourceName::Principal => { - Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) - } - } - } - DavMethod::PUT | DavMethod::POST => match resource { - DavResourceName::Card => { - self.handle_card_update_request(&access_token, headers, body, false) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_update_request(&access_token, headers, body, false) - .await - } - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - }, - DavMethod::PATCH => match resource { - DavResourceName::Card => { - self.handle_card_update_request(&access_token, headers, body, true) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_update_request(&access_token, headers, body, true) - .await - } - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - }, - DavMethod::COPY => match resource { - DavResourceName::Card => { - self.handle_card_copy_move_request(&access_token, headers, false) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_copy_move_request(&access_token, headers, false) - .await - } - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - }, - DavMethod::MOVE => match resource { - DavResourceName::Card => { - self.handle_card_copy_move_request(&access_token, headers, false) - .await - } - DavResourceName::Cal => todo!(), - DavResourceName::File => { - self.handle_file_copy_move_request(&access_token, headers, true) - .await - } - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - }, - DavMethod::LOCK => match resource { - DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), - _ => { - self.handle_lock_request( - &access_token, - headers, - if !body.is_empty() { - LockRequest::Lock(LockInfo::parse(&mut Tokenizer::new(&body))?) - } else { - LockRequest::Refresh - }, - ) - .await - } - }, - DavMethod::UNLOCK => { - self.handle_lock_request(&access_token, headers, LockRequest::Unlock) - .await - } - DavMethod::ACL => { - let request = Acl::parse(&mut Tokenizer::new(&body))?; - match resource { - DavResourceName::Card | DavResourceName::Cal | DavResourceName::File => { - self.handle_acl_request(&access_token, headers, request) - .await - } - DavResourceName::Principal => { - Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) - } - } - } DavMethod::REPORT => match Report::parse(&mut Tokenizer::new(&body))? { Report::SyncCollection(sync_collection) => { let uri = self @@ -332,11 +201,192 @@ impl DavRequestDispatcher for Server { ) .await } - Report::CalendarQuery(report) => todo!(), - Report::CalendarMultiGet(report) => todo!(), - Report::FreeBusyQuery(report) => todo!(), + Report::CalendarQuery(report) => { + self.handle_calendar_query_request(&access_token, headers, report) + .await + } + Report::CalendarMultiGet(report) => { + self.handle_dav_query( + &access_token, + DavQuery::multiget(report, Collection::Calendar, headers), + ) + .await + } + Report::FreeBusyQuery(report) => { + self.handle_calendar_freebusy_request(&access_token, headers, report) + .await + } Report::ExpandProperty(report) => todo!(), }, + DavMethod::PROPPATCH => { + let request = PropertyUpdate::parse(&mut Tokenizer::new(&body))?; + match resource { + DavResourceName::Card => { + self.handle_card_proppatch_request(&access_token, headers, request) + .await + } + DavResourceName::Cal => { + self.handle_calendar_proppatch_request(&access_token, headers, request) + .await + } + DavResourceName::File => { + self.handle_file_proppatch_request(&access_token, headers, request) + .await + } + DavResourceName::Principal => { + Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) + } + } + } + DavMethod::MKCOL => { + let request = if !body.is_empty() { + Some(MkCol::parse(&mut Tokenizer::new(&body))?) + } else { + None + }; + + match resource { + DavResourceName::Card => { + self.handle_card_mkcol_request(&access_token, headers, request) + .await + } + DavResourceName::Cal => { + self.handle_calendar_mkcol_request(&access_token, headers, request) + .await + } + DavResourceName::File => { + self.handle_file_mkcol_request(&access_token, headers, request) + .await + } + DavResourceName::Principal => { + Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) + } + } + } + DavMethod::DELETE => { + // Include any fragments in the URI + if let Some(p) = request.uri().path_and_query() { + // TODO: Access to the fragment part is pending, see https://github.com/hyperium/http/issues/127 + headers.uri = p.as_str(); + } + + match resource { + DavResourceName::Card => { + self.handle_card_delete_request(&access_token, headers) + .await + } + DavResourceName::Cal => { + self.handle_calendar_delete_request(&access_token, headers) + .await + } + DavResourceName::File => { + self.handle_file_delete_request(&access_token, headers) + .await + } + DavResourceName::Principal => { + Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) + } + } + } + DavMethod::PUT | DavMethod::POST | DavMethod::PATCH => match resource { + DavResourceName::Card => { + self.handle_card_update_request( + &access_token, + headers, + body, + matches!(method, DavMethod::PATCH), + ) + .await + } + DavResourceName::Cal => { + self.handle_calendar_update_request( + &access_token, + headers, + body, + matches!(method, DavMethod::PATCH), + ) + .await + } + DavResourceName::File => { + self.handle_file_update_request( + &access_token, + headers, + body, + matches!(method, DavMethod::PATCH), + ) + .await + } + DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), + }, + DavMethod::COPY | DavMethod::MOVE => match resource { + DavResourceName::Card => { + self.handle_card_copy_move_request( + &access_token, + headers, + matches!(method, DavMethod::MOVE), + ) + .await + } + DavResourceName::Cal => { + self.handle_calendar_copy_move_request( + &access_token, + headers, + matches!(method, DavMethod::MOVE), + ) + .await + } + DavResourceName::File => { + self.handle_file_copy_move_request( + &access_token, + headers, + matches!(method, DavMethod::MOVE), + ) + .await + } + DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), + }, + DavMethod::MKCALENDAR => match resource { + DavResourceName::Cal => { + self.handle_calendar_mkcol_request( + &access_token, + headers, + Some(MkCol::parse(&mut Tokenizer::new(&body))?), + ) + .await + } + _ => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), + }, + DavMethod::LOCK => match resource { + DavResourceName::Principal => Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)), + _ => { + self.handle_lock_request( + &access_token, + headers, + if !body.is_empty() { + LockRequest::Lock(LockInfo::parse(&mut Tokenizer::new(&body))?) + } else { + LockRequest::Refresh + }, + ) + .await + } + }, + DavMethod::UNLOCK => { + self.handle_lock_request(&access_token, headers, LockRequest::Unlock) + .await + } + DavMethod::ACL => { + let request = Acl::parse(&mut Tokenizer::new(&body))?; + match resource { + DavResourceName::Card | DavResourceName::Cal | DavResourceName::File => { + self.handle_acl_request(&access_token, headers, request) + .await + } + DavResourceName::Principal => { + Err(DavError::Code(StatusCode::METHOD_NOT_ALLOWED)) + } + } + } DavMethod::OPTIONS => unreachable!(), } } diff --git a/crates/groupware/src/calendar/index.rs b/crates/groupware/src/calendar/index.rs new file mode 100644 index 00000000..3b3c301d --- /dev/null +++ b/crates/groupware/src/calendar/index.rs @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use common::storage::index::{ + IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject, +}; +use jmap_proto::types::{collection::Collection, value::AclGrant}; +use store::SerializeInfallible; + +use crate::{IDX_NAME, IDX_UID}; + +use super::{ + ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert, + ArchivedTimezone, Calendar, CalendarEvent, CalendarPreferences, DefaultAlert, Timezone, +}; + +impl IndexableObject for Calendar { + fn index_values(&self) -> impl Iterator> { + // Note: When adding a new value with index id above 0u8, tune `build_hierarchy`` to skip + // this value during iteration. + [ + IndexValue::Index { + field: IDX_NAME, + value: self.name.as_str().into(), + }, + IndexValue::Acl { + value: (&self.acls).into(), + }, + IndexValue::Quota { + used: self.dead_properties.size() as u32 + + self.preferences.iter().map(|p| p.size()).sum::() as u32 + + self.default_alerts.iter().map(|a| a.size()).sum::() as u32 + + self.name.len() as u32, + }, + IndexValue::LogChild { prefix: None }, + ] + .into_iter() + } +} + +impl IndexableObject for &ArchivedCalendar { + fn index_values(&self) -> impl Iterator> { + [ + IndexValue::Index { + field: IDX_NAME, + value: self.name.as_str().into(), + }, + IndexValue::Acl { + value: self + .acls + .iter() + .map(AclGrant::from) + .collect::>() + .into(), + }, + IndexValue::Quota { + used: self.dead_properties.size() as u32 + + self.preferences.iter().map(|p| p.size()).sum::() as u32 + + self.default_alerts.iter().map(|a| a.size()).sum::() as u32 + + self.name.len() as u32, + }, + IndexValue::LogChild { prefix: None }, + ] + .into_iter() + } +} + +impl IndexableAndSerializableObject for Calendar {} + +impl IndexableObject for CalendarEvent { + fn index_values(&self) -> impl Iterator> { + [ + IndexValue::IndexList { + field: IDX_NAME, + value: self + .names + .iter() + .map(|v| IndexItem::Vec(v.serialize())) + .collect::>(), + }, + IndexValue::Index { + field: IDX_UID, + value: self.event.uids().next().into(), + }, + IndexValue::Quota { + used: self.dead_properties.size() as u32 + + self.display_name.as_ref().map_or(0, |n| n.len() as u32) + + self.names.iter().map(|n| n.name.len() as u32).sum::() + + self.size, + }, + IndexValue::LogChild { prefix: None }, + IndexValue::LogParent { + collection: Collection::Calendar.into(), + ids: self.names.iter().map(|n| n.parent_id).collect(), + }, + ] + .into_iter() + } +} + +impl IndexableObject for &ArchivedCalendarEvent { + fn index_values(&self) -> impl Iterator> { + [ + IndexValue::IndexList { + field: IDX_NAME, + value: self + .names + .iter() + .map(|v| IndexItem::Vec(v.serialize())) + .collect::>(), + }, + IndexValue::Index { + field: IDX_UID, + value: self.event.uids().next().into(), + }, + IndexValue::Quota { + used: self.dead_properties.size() as u32 + + self.display_name.as_ref().map_or(0, |n| n.len() as u32) + + self.names.iter().map(|n| n.name.len() as u32).sum::() + + self.size, + }, + IndexValue::LogChild { prefix: None }, + IndexValue::LogParent { + collection: Collection::Calendar.into(), + ids: self.names.iter().map(|n| n.parent_id.to_native()).collect(), + }, + ] + .into_iter() + } +} + +impl IndexableAndSerializableObject for CalendarEvent {} + +impl CalendarPreferences { + pub fn size(&self) -> usize { + self.name.len() + + self.description.as_ref().map_or(0, |n| n.len()) + + self.color.as_ref().map_or(0, |n| n.len()) + + self.time_zone.size() + } +} + +impl ArchivedCalendarPreferences { + pub fn size(&self) -> usize { + self.name.len() + + self.description.as_ref().map_or(0, |n| n.len()) + + self.color.as_ref().map_or(0, |n| n.len()) + + self.time_zone.size() + } +} + +impl Timezone { + pub fn size(&self) -> usize { + match self { + Timezone::IANA(s) => s.len(), + Timezone::Custom(c) => c.size(), + Timezone::Default => 0, + } + } +} + +impl ArchivedTimezone { + pub fn size(&self) -> usize { + match self { + ArchivedTimezone::IANA(s) => s.len(), + ArchivedTimezone::Custom(c) => c.size(), + ArchivedTimezone::Default => 0, + } + } +} + +impl DefaultAlert { + pub fn size(&self) -> usize { + self.alert.size() + self.id.len() + } +} + +impl ArchivedDefaultAlert { + pub fn size(&self) -> usize { + self.alert.size() + self.id.len() + } +} diff --git a/crates/groupware/src/calendar/mod.rs b/crates/groupware/src/calendar/mod.rs index 68ada96a..0e9a2725 100644 --- a/crates/groupware/src/calendar/mod.rs +++ b/crates/groupware/src/calendar/mod.rs @@ -4,46 +4,63 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::collections::HashMap; - -use calcard::icalendar::ICalendar; - -use dav_proto::schema::request::DeadProperty; -use jmap_proto::types::{acl::Acl, value::AclGrant}; -use store::{SERIALIZE_CALENDAR_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion, ahash}; -use utils::map::vec_map::VecMap; +pub mod index; +pub mod storage; use crate::DavName; +use calcard::icalendar::ICalendar; +use dav_proto::schema::request::DeadProperty; +use jmap_proto::types::{acl::Acl, value::AclGrant}; +use store::{SERIALIZE_CALENDAR_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion}; #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, )] pub struct Calendar { pub name: String, - pub preferences: HashMap, + pub preferences: Vec, + pub default_alerts: Vec, pub acls: Vec, pub dead_properties: DeadProperty, pub created: i64, pub modified: i64, } +pub const CALENDAR_SUBSCRIBED: u16 = 1; +pub const CALENDAR_DEFAULT: u16 = 1 << 1; +pub const CALENDAR_VISIBLE: u16 = 1 << 2; +pub const CALENDAR_AVAILABILITY_ALL: u16 = 1 << 3; +pub const CALENDAR_AVAILABILITY_ATTENDING: u16 = 1 << 4; + #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, )] pub struct CalendarPreferences { + pub account_id: u32, pub name: String, pub description: Option, pub sort_order: u32, pub color: Option, - pub is_subscribed: bool, - pub is_default: bool, - pub is_visible: bool, - pub include_in_availability: IncludeInAvailability, - pub default_alerts_with_time: HashMap, - pub default_alerts_without_time: HashMap, + pub flags: u16, pub time_zone: Timezone, } +#[derive( + rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, +)] +pub struct DefaultAlert { + pub account_id: u32, + pub id: String, + pub alert: ICalendar, + pub with_time: bool, +} + +pub const EVENT_INVITE_SELF: u16 = 1; +pub const EVENT_INVITE_OTHERS: u16 = 1 << 1; +pub const EVENT_HIDE_ATTENDEES: u16 = 1 << 2; +pub const EVENT_DRAFT: u16 = 1 << 3; +pub const EVENT_ORIGIN: u16 = 1 << 4; + #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, )] @@ -51,17 +68,22 @@ pub struct CalendarEvent { pub names: Vec, pub display_name: Option, pub event: ICalendar, - pub user_properties: VecMap, - pub may_invite_self: bool, - pub may_invite_others: bool, - pub hide_attendees: bool, - pub is_draft: bool, + pub user_properties: Vec, + pub flags: u16, pub dead_properties: DeadProperty, pub size: u32, pub created: i64, pub modified: i64, } +#[derive( + rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, +)] +pub struct UserProperties { + pub account_id: u32, + pub properties: ICalendar, +} + #[derive( rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, )] @@ -72,17 +94,6 @@ pub enum Timezone { Default, } -#[derive( - rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq, -)] -#[rkyv(derive(Debug))] -pub enum IncludeInAvailability { - All, - Attending, - #[default] - None, -} - pub enum CalendarRight { ReadFreeBusy, ReadItems, @@ -139,14 +150,43 @@ impl SerializedVersion for CalendarEvent { } } -impl ArchivedCalendar { - pub fn preferences(&self, account_id: u32) -> Option<&ArchivedCalendarPreferences> { +impl Calendar { + pub fn preferences(&self, account_id: u32) -> &CalendarPreferences { if self.preferences.len() == 1 { - self.preferences.values().next() + &self.preferences[0] } else { self.preferences - .get(&rkyv::rend::u32_le::from_native(account_id)) - .or_else(|| self.preferences.values().next()) + .iter() + .find(|p| p.account_id == account_id) + .or_else(|| self.preferences.first()) + .unwrap() + } + } + + pub fn preferences_mut(&mut self, account_id: u32) -> &mut CalendarPreferences { + if self.preferences.len() == 1 { + &mut self.preferences[0] + } else { + let idx = self + .preferences + .iter() + .position(|p| p.account_id == account_id) + .unwrap_or(0); + &mut self.preferences[idx] + } + } +} + +impl ArchivedCalendar { + pub fn preferences(&self, account_id: u32) -> &ArchivedCalendarPreferences { + if self.preferences.len() == 1 { + &self.preferences[0] + } else { + self.preferences + .iter() + .find(|p| p.account_id == account_id) + .or_else(|| self.preferences.first()) + .unwrap() } } } diff --git a/crates/groupware/src/calendar/storage.rs b/crates/groupware/src/calendar/storage.rs new file mode 100644 index 00000000..b03d47ee --- /dev/null +++ b/crates/groupware/src/calendar/storage.rs @@ -0,0 +1,246 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::DestroyArchive; +use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; +use jmap_proto::types::collection::Collection; +use store::write::{Archive, BatchBuilder, now}; +use trc::AddContext; + +use super::{ + ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, CalendarPreferences, +}; + +impl CalendarEvent { + pub fn update<'x>( + self, + access_token: &AccessToken, + event: Archive<&ArchivedCalendarEvent>, + account_id: u32, + document_id: u32, + batch: &'x mut BatchBuilder, + ) -> trc::Result<&'x mut BatchBuilder> { + let mut new_event = self; + + // Build event + new_event.modified = now() as i64; + + // Prepare write batch + batch + .with_account_id(account_id) + .with_collection(Collection::CalendarEvent) + .update_document(document_id) + .custom( + ObjectIndexBuilder::new() + .with_current(event) + .with_changes(new_event) + .with_tenant_id(access_token), + ) + .map(|b| b.commit_point()) + } + + pub fn insert<'x>( + self, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + batch: &'x mut BatchBuilder, + ) -> trc::Result<&'x mut BatchBuilder> { + // Build event + let mut event = self; + let now = now() as i64; + event.modified = now; + event.created = now; + + // Prepare write batch + batch + .with_account_id(account_id) + .with_collection(Collection::CalendarEvent) + .create_document(document_id) + .custom( + ObjectIndexBuilder::<(), _>::new() + .with_changes(event) + .with_tenant_id(access_token), + ) + .map(|b| b.commit_point()) + } +} + +impl Calendar { + pub fn insert<'x>( + self, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + batch: &'x mut BatchBuilder, + ) -> trc::Result<&'x mut BatchBuilder> { + // Build address calendar + let mut calendar = self; + let now = now() as i64; + calendar.modified = now; + calendar.created = now; + + if calendar.preferences.is_empty() { + calendar.preferences.push(CalendarPreferences { + account_id, + name: "default".to_string(), + ..Default::default() + }); + } + + // Prepare write batch + batch + .with_account_id(account_id) + .with_collection(Collection::Calendar) + .create_document(document_id) + .custom( + ObjectIndexBuilder::<(), _>::new() + .with_changes(calendar) + .with_tenant_id(access_token), + ) + .map(|b| b.commit_point()) + } + + pub fn update<'x>( + self, + access_token: &AccessToken, + calendar: Archive<&ArchivedCalendar>, + account_id: u32, + document_id: u32, + batch: &'x mut BatchBuilder, + ) -> trc::Result<&'x mut BatchBuilder> { + // Build address calendar + let mut new_calendar = self; + new_calendar.modified = now() as i64; + + // Prepare write batch + batch + .with_account_id(account_id) + .with_collection(Collection::Calendar) + .update_document(document_id) + .custom( + ObjectIndexBuilder::new() + .with_current(calendar) + .with_changes(new_calendar) + .with_tenant_id(access_token), + ) + .map(|b| b.commit_point()) + } +} + +impl DestroyArchive> { + pub async fn delete_with_events( + self, + server: &Server, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + children_ids: Vec, + batch: &mut BatchBuilder, + ) -> trc::Result<()> { + // Process deletions + let calendar_id = document_id; + for document_id in children_ids { + if let Some(event_) = server + .get_archive(account_id, Collection::CalendarEvent, document_id) + .await? + { + DestroyArchive( + event_ + .to_unarchived::() + .caused_by(trc::location!())?, + ) + .delete( + access_token, + account_id, + document_id, + calendar_id, + batch, + )?; + } + } + + self.delete(access_token, account_id, document_id, batch) + } + + pub fn delete( + self, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + batch: &mut BatchBuilder, + ) -> trc::Result<()> { + let calendar = self.0; + // Delete calendar + batch + .with_account_id(account_id) + .with_collection(Collection::Calendar) + .delete_document(document_id) + .custom( + ObjectIndexBuilder::<_, ()>::new() + .with_tenant_id(access_token) + .with_current(calendar), + ) + .caused_by(trc::location!())? + .commit_point(); + + Ok(()) + } +} + +impl DestroyArchive> { + pub fn delete( + self, + access_token: &AccessToken, + account_id: u32, + document_id: u32, + calendar_id: u32, + batch: &mut BatchBuilder, + ) -> trc::Result<()> { + let event = self.0; + if let Some(delete_idx) = event + .inner + .names + .iter() + .position(|name| name.parent_id == calendar_id) + { + batch + .with_account_id(account_id) + .with_collection(Collection::CalendarEvent); + + if event.inner.names.len() > 1 { + // Unlink calendar id from event + let mut new_event = event + .deserialize::() + .caused_by(trc::location!())?; + new_event.names.swap_remove(delete_idx); + batch + .update_document(document_id) + .custom( + ObjectIndexBuilder::new() + .with_tenant_id(access_token) + .with_current(event) + .with_changes(new_event), + ) + .caused_by(trc::location!())?; + } else { + // Delete event + batch + .delete_document(document_id) + .custom( + ObjectIndexBuilder::<_, ()>::new() + .with_tenant_id(access_token) + .with_current(event), + ) + .caused_by(trc::location!())?; + } + + batch.commit_point(); + } + + Ok(()) + } +} diff --git a/crates/groupware/src/contact/index.rs b/crates/groupware/src/contact/index.rs index d97e724f..3b7066b8 100644 --- a/crates/groupware/src/contact/index.rs +++ b/crates/groupware/src/contact/index.rs @@ -10,7 +10,7 @@ use common::storage::index::{ use jmap_proto::types::{collection::Collection, value::AclGrant}; use store::SerializeInfallible; -use crate::{IDX_CARD_UID, IDX_NAME}; +use crate::{IDX_NAME, IDX_UID}; use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard}; @@ -79,7 +79,7 @@ impl IndexableObject for ContactCard { .collect::>(), }, IndexValue::Index { - field: IDX_CARD_UID, + field: IDX_UID, value: self.card.uid().into(), }, IndexValue::Quota { @@ -110,7 +110,7 @@ impl IndexableObject for &ArchivedContactCard { .collect::>(), }, IndexValue::Index { - field: IDX_CARD_UID, + field: IDX_UID, value: self.card.uid().into(), }, IndexValue::Quota { diff --git a/crates/groupware/src/contact/storage.rs b/crates/groupware/src/contact/storage.rs index da1032e2..1efb638e 100644 --- a/crates/groupware/src/contact/storage.rs +++ b/crates/groupware/src/contact/storage.rs @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder}; use jmap_proto::types::collection::Collection; use store::write::{Archive, BatchBuilder, now}; diff --git a/crates/groupware/src/hierarchy.rs b/crates/groupware/src/hierarchy.rs index 5ef21aef..612598fc 100644 --- a/crates/groupware/src/hierarchy.rs +++ b/crates/groupware/src/hierarchy.rs @@ -18,7 +18,12 @@ use store::{ use trc::AddContext; use utils::bimap::IdBimap; -use crate::{DavName, DavResourceName, IDX_NAME, contact::AddressBook, file::FileNode}; +use crate::{ + DavName, DavResourceName, IDX_NAME, + calendar::{Calendar, CalendarPreferences}, + contact::AddressBook, + file::FileNode, +}; pub trait DavHierarchy: Sync + Send { fn fetch_dav_resources( @@ -127,7 +132,27 @@ impl DavHierarchy for Server { access_token: &AccessToken, account_id: u32, ) -> trc::Result<()> { - todo!() + if let Some(name) = &self.core.dav.default_calendar_name { + let mut batch = BatchBuilder::new(); + let document_id = self + .store() + .assign_document_ids(account_id, Collection::Calendar, 1) + .await?; + Calendar { + name: name.clone(), + preferences: vec![CalendarPreferences { + account_id, + name: name.clone(), + description: self.core.dav.default_calendar_display_name.clone(), + ..Default::default() + }], + ..Default::default() + } + .insert(access_token, account_id, document_id, &mut batch)?; + self.commit_batch(batch).await?; + } + + Ok(()) } } diff --git a/crates/groupware/src/lib.rs b/crates/groupware/src/lib.rs index 7256591f..5474def6 100644 --- a/crates/groupware/src/lib.rs +++ b/crates/groupware/src/lib.rs @@ -14,7 +14,7 @@ pub mod file; pub mod hierarchy; pub const IDX_NAME: u8 = 0; -pub const IDX_CARD_UID: u8 = 1; +pub const IDX_UID: u8 = 1; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DavResourceName { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index df9337ec..ec481fae 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,8 +5,8 @@ edition = "2024" resolver = "2" [features] -#default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "azure", "foundationdb"] -default = ["sqlite", "postgres", "mysql", "rocks", "s3", "redis"] +default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "azure", "foundationdb"] +#default = ["sqlite", "postgres", "mysql", "rocks", "s3", "redis"] #default = ["rocks", "redis", "s3"] sqlite = ["store/sqlite"] foundationdb = ["store/foundation", "common/foundation"]