diff --git a/crates/caldav/src/calendar/service.rs b/crates/caldav/src/calendar/service.rs index 62244e6..9a41e10 100644 --- a/crates/caldav/src/calendar/service.rs +++ b/crates/caldav/src/calendar/service.rs @@ -50,6 +50,7 @@ impl ResourceService for CalendarResourc type PrincipalUri = CalDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; + const IS_COLLECTION: bool = true; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_object/service.rs b/crates/caldav/src/calendar_object/service.rs index bd8106b..bf15dd0 100644 --- a/crates/caldav/src/calendar_object/service.rs +++ b/crates/caldav/src/calendar_object/service.rs @@ -50,6 +50,7 @@ impl ResourceService for CalendarObjectResourceService { type PrincipalUri = CalDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; + const IS_COLLECTION: bool = false; async fn get_resource( &self, diff --git a/crates/caldav/src/calendar_set/service.rs b/crates/caldav/src/calendar_set/service.rs index 68d15da..bdd644d 100644 --- a/crates/caldav/src/calendar_set/service.rs +++ b/crates/caldav/src/calendar_set/service.rs @@ -45,6 +45,7 @@ impl ResourceService for CalendarSetReso type PrincipalUri = CalDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol, calendar-access"; + const IS_COLLECTION: bool = true; async fn get_resource( &self, diff --git a/crates/caldav/src/lib.rs b/crates/caldav/src/lib.rs index 72af678..37f8113 100644 --- a/crates/caldav/src/lib.rs +++ b/crates/caldav/src/lib.rs @@ -24,7 +24,7 @@ pub struct CalDavPrincipalUri(&'static str); impl PrincipalUri for CalDavPrincipalUri { fn principal_uri(&self, principal: &str) -> String { - format!("{}/principal/{}", self.0, principal) + format!("{}/principal/{}/", self.0, principal) } } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 87c340b..3723cf4 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -48,7 +48,7 @@ impl Resource for PrincipalResource { .map(|principal| puri.principal_uri(principal)) .flat_map(|principal_url| { self.home_set.iter().map(move |&home_name| { - HrefElement::new(format!("{}/{}", &principal_url, home_name)) + HrefElement::new(format!("{}{}/", &principal_url, home_name)) }) }) .collect(), diff --git a/crates/caldav/src/principal/service.rs b/crates/caldav/src/principal/service.rs index ee6099a..cf2e90f 100644 --- a/crates/caldav/src/principal/service.rs +++ b/crates/caldav/src/principal/service.rs @@ -46,6 +46,7 @@ impl ResourceService for AddressObjectResourceService type PrincipalUri = CardDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + const IS_COLLECTION: bool = false; async fn get_resource( &self, diff --git a/crates/carddav/src/addressbook/service.rs b/crates/carddav/src/addressbook/service.rs index 71daaec..4383dee 100644 --- a/crates/carddav/src/addressbook/service.rs +++ b/crates/carddav/src/addressbook/service.rs @@ -52,6 +52,7 @@ impl ResourceService type PrincipalUri = CardDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + const IS_COLLECTION: bool = true; async fn get_resource( &self, diff --git a/crates/carddav/src/lib.rs b/crates/carddav/src/lib.rs index 7e3ad3b..43d75b7 100644 --- a/crates/carddav/src/lib.rs +++ b/crates/carddav/src/lib.rs @@ -23,7 +23,7 @@ pub struct CardDavPrincipalUri(&'static str); impl PrincipalUri for CardDavPrincipalUri { fn principal_uri(&self, principal: &str) -> String { - format!("{}/principal/{}", self.0, principal) + format!("{}/principal/{}/", self.0, principal) } } diff --git a/crates/carddav/src/principal/service.rs b/crates/carddav/src/principal/service.rs index 5edd65f..4925c3a 100644 --- a/crates/carddav/src/principal/service.rs +++ b/crates/carddav/src/principal/service.rs @@ -55,6 +55,7 @@ impl Reso type PrincipalUri = CardDavPrincipalUri; const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + const IS_COLLECTION: bool = true; async fn get_resource( &self, diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 56fab1e..e2b176e 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -76,8 +76,13 @@ pub(crate) async fn route_propfind( let mut member_responses = Vec::new(); if depth != &Depth::Zero { for member in resource_service.get_members(path_components).await? { + // Collections should have a trailing slash + let mut name = member.get_name(); + if R::IS_COLLECTION { + name.push('/') + } member_responses.push(member.propfind_typed( - &format!("{}/{}", path.trim_end_matches('/'), member.get_name()), + &format!("{}/{}", path.trim_end_matches('/'), name), &propfind_member.prop, puri, principal, diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index 83c8ccb..49fcf0d 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -18,6 +18,7 @@ pub trait ResourceService: Clone + Sized + Send + Sync + AxumMethods + 'static { type PrincipalUri: PrincipalUri; const DAV_HEADER: &'static str; + const IS_COLLECTION: bool; async fn get_members( &self, diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index bb0341b..4f5ecb3 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -77,6 +77,7 @@ where type PrincipalUri = PURI; const DAV_HEADER: &str = "1, 3, access-control"; + const IS_COLLECTION: bool = true; async fn get_resource(&self, _: &()) -> Result { Ok(RootResource::::default())