From dd95efe3cef08e375af7b8383ca9e846ab60a7bf Mon Sep 17 00:00:00 2001 From: Maurus Decimus <11444311+mdecimus@users.noreply.github.com> Date: Sat, 18 Apr 2026 10:42:21 +0200 Subject: [PATCH] WebDAV and other minor fixes --- CHANGELOG.md | 5 +++- crates/common/src/network/mta.rs | 14 +++++++++++ crates/dav-proto/src/responses/property.rs | 2 +- crates/dav/src/calendar/freebusy.rs | 2 +- crates/dav/src/calendar/query.rs | 2 +- crates/dav/src/card/query.rs | 2 +- crates/http/src/request.rs | 4 +++- tests/src/webdav/acl.rs | 28 ++++++++++++++++++++++ 8 files changed, 53 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e55e0118..21b6e85a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,10 @@ This version includes **multiple breaking changes**. If you are upgrading from v - Increment argument max length to `8000` bytes - ACL: Add `RIGHTS` capability (#2762) - ACL: Fix `ACL SET` permission override. -- WebDAV: Return `304` `NOT_MODIFIED` on `If-None-Match` +- WebDAV: + - Return `304` `NOT_MODIFIED` on `If-None-Match` + - Use RFC 2616 instead of RFC 1123 for date formatting + - Fix ACL container/item mismatch in reports. - Configuration: Prefix parsing issues (#2495) - OIDC: JWKS Exposes Symmetric Signing Key - SQLite: Fix thread pool exhaustion. diff --git a/crates/common/src/network/mta.rs b/crates/common/src/network/mta.rs index d7de05f4..f0ed036e 100644 --- a/crates/common/src/network/mta.rs +++ b/crates/common/src/network/mta.rs @@ -375,6 +375,20 @@ impl Server { }) .caused_by(trc::location!())? { + let last_trained_at = match &model { + SpamClassifier::FhClassifier { + last_trained_at, .. + } => Some(*last_trained_at), + SpamClassifier::CcfhClassifier { + last_trained_at, .. + } => Some(*last_trained_at), + SpamClassifier::Disabled => None, + }; + + trc::event!( + Spam(SpamEvent::ModelLoaded), + Details = last_trained_at.map(trc::Value::Timestamp), + ); self.inner.data.spam_classifier.store(Arc::new(model)); } else { trc::event!(Spam(SpamEvent::ModelNotFound)); diff --git a/crates/dav-proto/src/responses/property.rs b/crates/dav-proto/src/responses/property.rs index 9be7e92d..d8a9a50c 100644 --- a/crates/dav-proto/src/responses/property.rs +++ b/crates/dav-proto/src/responses/property.rs @@ -59,7 +59,7 @@ impl Display for Rfc1123DateTime { let dt = DateTime::from_timestamp(self.0); write!( f, - "{}, {} {} {:04} {:02}:{:02}:{:02} GMT", + "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT", DOW[dt.day_of_week() as usize], dt.day, MONTH diff --git a/crates/dav/src/calendar/freebusy.rs b/crates/dav/src/calendar/freebusy.rs index 051bd02e..0a3927e6 100644 --- a/crates/dav/src/calendar/freebusy.rs +++ b/crates/dav/src/calendar/freebusy.rs @@ -107,7 +107,7 @@ impl CalendarFreebusyRequestHandler for Server { // Obtain shared ids let shared_ids = if !access_token.is_member(account_id) { resources - .shared_containers( + .shared_items( access_token, [Acl::ReadItems, Acl::SchedulingReadFreeBusy], false, diff --git a/crates/dav/src/calendar/query.rs b/crates/dav/src/calendar/query.rs index dbe05be7..d8a448e8 100644 --- a/crates/dav/src/calendar/query.rs +++ b/crates/dav/src/calendar/query.rs @@ -90,7 +90,7 @@ impl CalendarQueryRequestHandler for Server { // Obtain shared ids let shared_ids = if !access_token.is_member(account_id) { resources - .shared_containers(access_token, [Acl::ReadItems], false) + .shared_items(access_token, [Acl::ReadItems], false) .into() } else { None diff --git a/crates/dav/src/card/query.rs b/crates/dav/src/card/query.rs index 7025469d..231ebf86 100644 --- a/crates/dav/src/card/query.rs +++ b/crates/dav/src/card/query.rs @@ -77,7 +77,7 @@ impl CardQueryRequestHandler for Server { // Obtain shared ids let shared_ids = if !access_token.is_member(account_id) { resources - .shared_containers(access_token, [Acl::ReadItems], false) + .shared_items(access_token, [Acl::ReadItems], false) .into() } else { None diff --git a/crates/http/src/request.rs b/crates/http/src/request.rs index ae7c3443..ef8496ed 100644 --- a/crates/http/src/request.rs +++ b/crates/http/src/request.rs @@ -595,7 +595,9 @@ impl ParseHttp for Server { } external => { if path.next().is_none() { - return Ok(HttpResponse::redirect(format!("/{external}/"))); + if !external.is_empty() { + return Ok(HttpResponse::redirect(format!("/{external}/"))); + } } else if let Some(resource) = self .inner .data diff --git a/tests/src/webdav/acl.rs b/tests/src/webdav/acl.rs index b652b698..13498c1f 100644 --- a/tests/src/webdav/acl.rs +++ b/tests/src/webdav/acl.rs @@ -131,6 +131,11 @@ pub async fn test(test: &TestServer) { .properties(&owner_file) .with_status(StatusCode::OK) .is_defined(DavProperty::WebDav(WebDavProperty::GetETag)); + sharee_client + .request("REPORT", &owner_folder, CALENDAR_QUERY_ANY_VEVENT) + .await + .with_status(StatusCode::MULTI_STATUS) + .with_hrefs([owner_file.as_str()]); } DavResourceName::Card => { sharee_client @@ -139,6 +144,11 @@ pub async fn test(test: &TestServer) { .properties(&owner_file) .with_status(StatusCode::OK) .is_defined(DavProperty::WebDav(WebDavProperty::GetETag)); + sharee_client + .request("REPORT", &owner_folder, ADDRESSBOOK_QUERY_ANY_FN) + .await + .with_status(StatusCode::MULTI_STATUS) + .with_hrefs([owner_file.as_str()]); } _ => {} } @@ -408,3 +418,21 @@ const ACL_PRINCIPAL_QUERY: &str = r#" "#; + +const CALENDAR_QUERY_ANY_VEVENT: &str = r#" + + + + + + + + "#; + +const ADDRESSBOOK_QUERY_ANY_FN: &str = r#" + + + + + + "#;