Fix bug with missing trailing slash in propfind response

This commit is contained in:
Lennart
2025-06-09 22:36:11 +02:00
parent 6d6f8f20df
commit 7628cdafbd
22 changed files with 32 additions and 22 deletions

View File

@@ -67,7 +67,7 @@ fn objects_response(
object, object,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind_typed(&path, prop, puri, user)?, .propfind(&path, prop, puri, user)?,
); );
} }

View File

@@ -39,7 +39,7 @@ pub async fn handle_sync_collection<C: CalendarStore>(
object, object,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind_typed(&path, &sync_collection.prop, puri, user)?, .propfind(&path, &sync_collection.prop, puri, user)?,
); );
} }

View File

@@ -100,6 +100,8 @@ impl Resource for CalendarResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
if self.cal.subscription_url.is_none() { if self.cal.subscription_url.is_none() {
Resourcetype(&[ Resourcetype(&[

View File

@@ -50,7 +50,6 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
type PrincipalUri = CalDavPrincipalUri; type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
const IS_COLLECTION: bool = true;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -27,6 +27,8 @@ impl Resource for CalendarObjectResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = false;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[]) Resourcetype(&[])
} }

View File

@@ -50,7 +50,6 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
type PrincipalUri = CalDavPrincipalUri; type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
const IS_COLLECTION: bool = false;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -28,6 +28,8 @@ impl Resource for CalendarSetResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner( Resourcetype(&[ResourcetypeInner(
Some(rustical_dav::namespace::NS_DAV), Some(rustical_dav::namespace::NS_DAV),

View File

@@ -45,7 +45,6 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetReso
type PrincipalUri = CalDavPrincipalUri; type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol, calendar-access"; const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol, calendar-access";
const IS_COLLECTION: bool = true;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -27,6 +27,8 @@ impl Resource for PrincipalResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),

View File

@@ -46,7 +46,6 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
type PrincipalUri = CalDavPrincipalUri; type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
const IS_COLLECTION: bool = true;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -32,6 +32,8 @@ impl Resource for AddressObjectResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = false;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[]) Resourcetype(&[])
} }

View File

@@ -41,7 +41,6 @@ impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS>
type PrincipalUri = CardDavPrincipalUri; type PrincipalUri = CardDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, addressbook"; const DAV_HEADER: &str = "1, 3, access-control, addressbook";
const IS_COLLECTION: bool = false;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -80,7 +80,7 @@ pub async fn handle_addressbook_multiget<AS: AddressbookStore>(
object, object,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind_typed(&path, prop, puri, user)?, .propfind(&path, prop, puri, user)?,
); );
} }

View File

@@ -39,7 +39,7 @@ pub async fn handle_sync_collection<AS: AddressbookStore>(
object, object,
principal: principal.to_owned(), principal: principal.to_owned(),
} }
.propfind_typed(&path, &sync_collection.prop, puri, user)?, .propfind(&path, &sync_collection.prop, puri, user)?,
); );
} }

View File

@@ -38,6 +38,8 @@ impl Resource for AddressbookResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),

View File

@@ -52,7 +52,6 @@ impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
type PrincipalUri = CardDavPrincipalUri; type PrincipalUri = CardDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, addressbook"; const DAV_HEADER: &str = "1, 3, access-control, addressbook";
const IS_COLLECTION: bool = true;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -26,6 +26,8 @@ impl Resource for PrincipalResource {
type Error = Error; type Error = Error;
type Principal = User; type Principal = User;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),

View File

@@ -55,7 +55,6 @@ impl<A: AddressbookStore, AP: AuthenticationProvider, S: SubscriptionStore> Reso
type PrincipalUri = CardDavPrincipalUri; type PrincipalUri = CardDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, addressbook"; const DAV_HEADER: &str = "1, 3, access-control, addressbook";
const IS_COLLECTION: bool = true;
async fn get_resource( async fn get_resource(
&self, &self,

View File

@@ -76,13 +76,8 @@ pub(crate) async fn route_propfind<R: ResourceService>(
let mut member_responses = Vec::new(); let mut member_responses = Vec::new();
if depth != &Depth::Zero { if depth != &Depth::Zero {
for member in resource_service.get_members(path_components).await? { for member in resource_service.get_members(path_components).await? {
// Collections should have a trailing slash member_responses.push(member.propfind(
let mut name = member.get_name(); &format!("{}/{}", path.trim_end_matches('/'), member.get_name()),
if R::IS_COLLECTION {
name.push('/')
}
member_responses.push(member.propfind_typed(
&format!("{}/{}", path.trim_end_matches('/'), name),
&propfind_member.prop, &propfind_member.prop,
puri, puri,
principal, principal,
@@ -90,7 +85,7 @@ pub(crate) async fn route_propfind<R: ResourceService>(
} }
} }
let response = resource.propfind_typed(path, &propfind_self.prop, puri, principal)?; let response = resource.propfind(path, &propfind_self.prop, puri, principal)?;
Ok(MultistatusElement { Ok(MultistatusElement {
responses: vec![response], responses: vec![response],

View File

@@ -37,6 +37,8 @@ pub trait Resource: Clone + Send + 'static {
type Error: From<crate::Error>; type Error: From<crate::Error>;
type Principal: Principal; type Principal: Principal;
const IS_COLLECTION: bool;
fn get_resourcetype(&self) -> Resourcetype; fn get_resourcetype(&self) -> Resourcetype;
fn list_props() -> Vec<(Option<Namespace<'static>>, &'static str)> { fn list_props() -> Vec<(Option<Namespace<'static>>, &'static str)> {
@@ -95,13 +97,19 @@ pub trait Resource: Clone + Send + 'static {
principal: &Self::Principal, principal: &Self::Principal,
) -> Result<UserPrivilegeSet, Self::Error>; ) -> Result<UserPrivilegeSet, Self::Error>;
fn propfind_typed( fn propfind(
&self, &self,
path: &str, path: &str,
prop: &PropfindType<<Self::Prop as PropName>::Names>, prop: &PropfindType<<Self::Prop as PropName>::Names>,
principal_uri: &impl PrincipalUri, principal_uri: &impl PrincipalUri,
principal: &Self::Principal, principal: &Self::Principal,
) -> Result<ResponseElement<Self::Prop>, Self::Error> { ) -> Result<ResponseElement<Self::Prop>, Self::Error> {
// Collections have a trailing slash
let mut path = path.to_string();
if Self::IS_COLLECTION && !path.ends_with('/') {
path.push('/');
}
// TODO: Support include element // TODO: Support include element
let (props, invalid_props): (HashSet<<Self::Prop as PropName>::Names>, Vec<_>) = match prop let (props, invalid_props): (HashSet<<Self::Prop as PropName>::Names>, Vec<_>) = match prop
{ {

View File

@@ -18,7 +18,6 @@ pub trait ResourceService: Clone + Sized + Send + Sync + AxumMethods + 'static {
type PrincipalUri: PrincipalUri; type PrincipalUri: PrincipalUri;
const DAV_HEADER: &'static str; const DAV_HEADER: &'static str;
const IS_COLLECTION: bool;
async fn get_members( async fn get_members(
&self, &self,

View File

@@ -24,6 +24,8 @@ impl<PR: Resource, P: Principal> Resource for RootResource<PR, P> {
type Error = PR::Error; type Error = PR::Error;
type Principal = P; type Principal = P;
const IS_COLLECTION: bool = true;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner( Resourcetype(&[ResourcetypeInner(
Some(crate::namespace::NS_DAV), Some(crate::namespace::NS_DAV),
@@ -77,7 +79,6 @@ where
type PrincipalUri = PURI; type PrincipalUri = PURI;
const DAV_HEADER: &str = "1, 3, access-control"; const DAV_HEADER: &str = "1, 3, access-control";
const IS_COLLECTION: bool = true;
async fn get_resource(&self, _: &()) -> Result<Self::Resource, Self::Error> { async fn get_resource(&self, _: &()) -> Result<Self::Resource, Self::Error> {
Ok(RootResource::<PRS::Resource, P>::default()) Ok(RootResource::<PRS::Resource, P>::default())