diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 7a7b6957..f8f8a731 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -22,7 +22,10 @@ use crate::{ }, common::{DavQueryResource, acl::current_user_privilege_set, uri::DavUriResource}, file::{FILE_CONTAINER_PROPS, FILE_ITEM_PROPS}, - principal::{CurrentUserPrincipal, propfind::PrincipalPropFind}, + principal::{ + CurrentUserPrincipal, + propfind::{PrincipalPropFind, build_home_set}, + }, }; use calcard::common::timezone::Tz; use common::{ @@ -47,10 +50,10 @@ use dav_proto::{ }, }; use directory::{Permission, Type, backend::internal::manage::ManageDirectory}; +use groupware::calendar::SCHEDULE_INBOX_ID; use groupware::{ DavCalendarResource, DavResourceName, cache::GroupwareCache, calendar::ArchivedTimezone, }; -use groupware::{RFC_3986, calendar::SCHEDULE_INBOX_ID}; use http_proto::HttpResponse; use hyper::StatusCode; use jmap_proto::types::{ @@ -246,11 +249,13 @@ impl PropFindRequestHandler for Server { // Add container info if !headers.depth_no_root { add_base_collection_response( + self, &request, resource.collection, access_token, &mut response, - ); + ) + .await?; } if return_children { @@ -386,11 +391,13 @@ impl PropFindRequestHandler for Server { // Add container info if !query.depth_no_root { add_base_collection_response( + self, &query.propfind, parent_collection, access_token, &mut response, - ); + ) + .await?; } discover_root_paths( @@ -1686,12 +1693,13 @@ impl SyncTokenUrn for DavResources { } } -fn add_base_collection_response( +async fn add_base_collection_response( + server: &Server, request: &PropFind, collection: Collection, access_token: &AccessToken, response: &mut MultiStatus, -) { +) -> trc::Result<()> { let properties = match request { PropFind::PropName => { response.add_response(Response::new_propstat( @@ -1706,7 +1714,7 @@ fn add_base_collection_response( )), ])], )); - return; + return Ok(()); } PropFind::AllProp(_) => [ DavProperty::WebDav(WebDavProperty::ResourceType), @@ -1735,25 +1743,31 @@ fn add_base_collection_response( )); } 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, RFC_3986), - ))], - )); + let hrefs = build_home_set( + server, + access_token, + &access_token.name, + access_token.primary_id, + true, + ) + .await + .caused_by(trc::location!())?; + + fields.push(DavPropertyValue::new(prop.clone(), hrefs)); response.set_namespace(Namespace::CalDav); } DavProperty::Principal(PrincipalProperty::AddressbookHomeSet) => { - fields.push(DavPropertyValue::new( - prop.clone(), - vec![Href(format!( - "{}/{}/", - DavResourceName::Card.base_path(), - percent_encoding::utf8_percent_encode(&access_token.name, RFC_3986), - ))], - )); + let hrefs = build_home_set( + server, + access_token, + &access_token.name, + access_token.primary_id, + false, + ) + .await + .caused_by(trc::location!())?; + + fields.push(DavPropertyValue::new(prop.clone(), hrefs)); response.set_namespace(Namespace::CardDav); } DavProperty::WebDav(WebDavProperty::SupportedReportSet) => { @@ -1787,4 +1801,6 @@ fn add_base_collection_response( DavResourceName::from(collection).collection_path(), prop_stat, )); + + Ok(()) } diff --git a/crates/dav/src/principal/propfind.rs b/crates/dav/src/principal/propfind.rs index 2e360881..d39c1529 100644 --- a/crates/dav/src/principal/propfind.rs +++ b/crates/dav/src/principal/propfind.rs @@ -270,54 +270,28 @@ impl PrincipalPropFind for Server { ))], )); } - PrincipalProperty::CalendarHomeSet - | PrincipalProperty::AddressbookHomeSet => { - let mut hrefs = Vec::new(); - let (collection, resource_name, namespace) = - if principal_property == &PrincipalProperty::CalendarHomeSet { - ( - Collection::Calendar, - DavResourceName::Cal, - Namespace::CalDav, - ) - } else { - ( - Collection::AddressBook, - DavResourceName::Card, - Namespace::CardDav, - ) - }; - - hrefs.push(Href(format!( - "{}/{}/", - resource_name.base_path(), - percent_encoding::utf8_percent_encode(&name, RFC_3986), - ))); - - if account_id == access_token.primary_id() { - for account_id in access_token.all_ids_by_collection(collection) { - if account_id != access_token.primary_id() { - let other_name = self - .store() - .get_principal_name(account_id) - .await - .caused_by(trc::location!())? - .unwrap_or_else(|| format!("_{account_id}")); - - hrefs.push(Href(format!( - "{}/{}/", - resource_name.base_path(), - percent_encoding::utf8_percent_encode( - &other_name, - RFC_3986 - ), - ))); - } - } - } + PrincipalProperty::CalendarHomeSet => { + let hrefs = + build_home_set(self, access_token, name.as_ref(), account_id, true) + .await + .caused_by(trc::location!())?; fields.push(DavPropertyValue::new(property.clone(), hrefs)); - response.set_namespace(namespace); + response.set_namespace(Namespace::CalDav); + } + PrincipalProperty::AddressbookHomeSet => { + let hrefs = build_home_set( + self, + access_token, + name.as_ref(), + account_id, + false, + ) + .await + .caused_by(trc::location!())?; + + fields.push(DavPropertyValue::new(property.clone(), hrefs)); + response.set_namespace(Namespace::CardDav); } PrincipalProperty::PrincipalAddress => { @@ -435,6 +409,48 @@ impl PrincipalPropFind for Server { } } +pub(crate) async fn build_home_set( + server: &Server, + access_token: &AccessToken, + name: &str, + account_id: u32, + is_calendar: bool, +) -> trc::Result> { + let (collection, resource_name) = if is_calendar { + (Collection::Calendar, DavResourceName::Cal) + } else { + (Collection::AddressBook, DavResourceName::Card) + }; + + let mut hrefs = Vec::new(); + hrefs.push(Href(format!( + "{}/{}/", + resource_name.base_path(), + percent_encoding::utf8_percent_encode(name, RFC_3986), + ))); + + if account_id == access_token.primary_id() { + for account_id in access_token.all_ids_by_collection(collection) { + if account_id != access_token.primary_id() { + let other_name = server + .store() + .get_principal_name(account_id) + .await + .caused_by(trc::location!())? + .unwrap_or_else(|| format!("_{account_id}")); + + hrefs.push(Href(format!( + "{}/{}/", + resource_name.base_path(), + percent_encoding::utf8_percent_encode(&other_name, RFC_3986), + ))); + } + } + } + + Ok(hrefs) +} + fn all_props(collection: Collection, all_props: Option<&[DavProperty]>) -> Vec { if collection == Collection::Principal { vec![ diff --git a/tests/src/webdav/principals.rs b/tests/src/webdav/principals.rs index 872d7c6c..af50affb 100644 --- a/tests/src/webdav/principals.rs +++ b/tests/src/webdav/principals.rs @@ -209,13 +209,19 @@ pub async fn test(test: &WebDavTest) { .with_status(StatusCode::OK); props .get(DavProperty::Principal(PrincipalProperty::CalendarHomeSet)) - .with_values([format!("D:href:{}/jane/", DavResourceName::Cal.base_path()).as_str()]) + .with_values([ + format!("D:href:{}/jane/", DavResourceName::Cal.base_path()).as_str(), + format!("D:href:{}/support/", DavResourceName::Cal.base_path()).as_str(), + ]) .with_status(StatusCode::OK); props .get(DavProperty::Principal( PrincipalProperty::AddressbookHomeSet, )) - .with_values([format!("D:href:{}/jane/", DavResourceName::Card.base_path()).as_str()]) + .with_values([ + format!("D:href:{}/jane/", DavResourceName::Card.base_path()).as_str(), + format!("D:href:{}/support/", DavResourceName::Card.base_path()).as_str(), + ]) .with_status(StatusCode::OK); for (account, _, name, _) in TEST_USERS