Return all shared resources in calendar-home-set and addressbook-home-set (closes #1796)

This commit is contained in:
mdecimus 2025-07-14 17:09:52 +02:00
parent 20bab962d4
commit a203ce732f
3 changed files with 109 additions and 71 deletions

View file

@ -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(())
}

View file

@ -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<Vec<Href>> {
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<DavProperty> {
if collection == Collection::Principal {
vec![

View file

@ -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