rustical_dav/resource/
mod.rs1use crate::Principal;
2use crate::privileges::UserPrivilegeSet;
3use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
4use crate::xml::{PropElement, PropfindElement, PropfindType, Resourcetype};
5use crate::xml::{TagList, multistatus::ResponseElement};
6use headers::{ETag, IfMatch, IfNoneMatch};
7use http::{StatusCode, Uri};
8use itertools::Itertools;
9use quick_xml::name::Namespace;
10pub use resource_service::ResourceService;
11use rustical_xml::{
12 EnumVariants, NamespaceOwned, PropName, XmlDeserialize, XmlDocument, XmlSerialize,
13};
14use std::borrow::Cow;
15use std::str::FromStr;
16
17mod axum_methods;
18mod axum_service;
19mod methods;
20mod principal_uri;
21mod resource_service;
22
23pub use axum_methods::{AxumMethods, MethodFunction};
24pub use axum_service::AxumService;
25pub use principal_uri::PrincipalUri;
26
27pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
28impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
29
30pub trait ResourcePropName: FromStr {}
31impl<T: FromStr> ResourcePropName for T {}
32
33pub trait ResourceName {
34 fn get_name(&self) -> Cow<'_, str>;
37}
38
39pub trait Resource: Clone + Send + 'static {
40 type Prop: ResourceProp + PartialEq + Clone + EnumVariants + PropName + Send;
41 type Error: From<crate::Error>;
42 type Principal: Principal;
43
44 fn is_collection(&self) -> bool;
45
46 fn get_resourcetype(&self) -> Resourcetype;
47
48 #[must_use]
49 fn list_props() -> Vec<(Option<Namespace<'static>>, &'static str)> {
50 Self::Prop::variant_names()
51 }
52
53 fn get_prop(
54 &self,
55 principal_uri: &impl PrincipalUri,
56 principal: &Self::Principal,
57 prop: &<Self::Prop as PropName>::Names,
58 ) -> Result<Self::Prop, Self::Error>;
59
60 fn set_prop(&mut self, _prop: Self::Prop) -> Result<(), crate::Error> {
61 Err(crate::Error::PropReadOnly)
62 }
63
64 fn remove_prop(&mut self, _prop: &<Self::Prop as PropName>::Names) -> Result<(), crate::Error> {
65 Err(crate::Error::PropReadOnly)
66 }
67
68 fn get_displayname(&self) -> Option<&str>;
69 fn set_displayname(&mut self, _name: Option<String>) -> Result<(), crate::Error> {
70 Err(crate::Error::PropReadOnly)
71 }
72
73 fn get_owner(&self) -> Option<&str> {
74 None
75 }
76
77 fn get_etag(&self) -> Option<String> {
78 None
79 }
80
81 fn satisfies_if_match(&self, if_match: &IfMatch) -> bool {
82 self.get_etag().map_or_else(
83 || if_match.is_any(),
84 |etag| {
85 ETag::from_str(&etag).map_or_else(
86 |_| if_match.is_any(),
87 |etag| if_match.precondition_passes(&etag),
88 )
89 },
90 )
91 }
92
93 fn satisfies_if_none_match(&self, if_none_match: &IfNoneMatch) -> bool {
94 self.get_etag().map_or_else(
95 || if_none_match != &IfNoneMatch::any(),
96 |etag| {
97 ETag::from_str(&etag).map_or_else(
98 |_| if_none_match != &IfNoneMatch::any(),
99 |etag| if_none_match.precondition_passes(&etag),
100 )
101 },
102 )
103 }
104
105 fn get_user_privileges(
106 &self,
107 principal: &Self::Principal,
108 ) -> Result<UserPrivilegeSet, Self::Error>;
109
110 fn parse_propfind(
111 body: &str,
112 ) -> Result<PropfindElement<<Self::Prop as PropName>::Names>, rustical_xml::XmlError> {
113 if body.is_empty() {
114 Ok(PropfindElement {
115 prop: PropfindType::Allprop,
116 include: None,
117 })
118 } else {
119 PropfindElement::parse_str(body)
120 }
121 }
122
123 fn propfind(
124 &self,
125 path: &str,
126 prop: &PropfindType<<Self::Prop as PropName>::Names>,
127 include: Option<&PropElement<<Self::Prop as PropName>::Names>>,
128 principal_uri: &impl PrincipalUri,
129 principal: &Self::Principal,
130 ) -> Result<ResponseElement<Self::Prop>, Self::Error> {
131 let mut path = path.to_string();
133 if self.is_collection() && !path.ends_with('/') {
134 path.push('/');
135 }
136
137 let (mut props, mut invalid_props): (Vec<<Self::Prop as PropName>::Names>, Vec<_>) =
138 match prop {
139 PropfindType::Propname => {
140 let props = Self::list_props()
141 .into_iter()
142 .map(|(ns, tag)| (ns.map(NamespaceOwned::from), tag.to_string()))
143 .collect_vec();
144
145 return Ok(ResponseElement {
146 href: Uri::from_str(&path).unwrap(),
147 propstat: vec![PropstatWrapper::TagList(PropstatElement {
148 prop: TagList::from(props),
149 status: StatusCode::OK,
150 })],
151 status: None,
152 });
153 }
154 PropfindType::Allprop => (
155 Self::list_props()
156 .iter()
157 .map(|(_ns, name)| <Self::Prop as PropName>::Names::from_str(name).unwrap())
158 .collect(),
159 vec![],
160 ),
161 PropfindType::Prop(PropElement(valid_tags, invalid_tags)) => (
162 valid_tags.iter().unique().cloned().collect(),
163 invalid_tags.to_owned(),
164 ),
165 };
166
167 if let Some(PropElement(valid_tags, invalid_tags)) = include {
168 props.extend(valid_tags.clone());
169 invalid_props.extend(invalid_tags.to_owned());
170 }
171
172 let prop_responses = props
173 .into_iter()
174 .map(|prop| self.get_prop(principal_uri, principal, &prop))
175 .collect::<Result<Vec<_>, Self::Error>>()?;
176
177 let mut propstats = vec![PropstatWrapper::Normal(PropstatElement {
178 status: StatusCode::OK,
179 prop: PropTagWrapper(prop_responses),
180 })];
181 if !invalid_props.is_empty() {
182 propstats.push(PropstatWrapper::TagList(PropstatElement {
183 status: StatusCode::NOT_FOUND,
184 prop: invalid_props.into(),
185 }));
186 }
187 Ok(ResponseElement {
188 href: Uri::from_str(&path).unwrap(),
189 propstat: propstats,
190 status: None,
191 })
192 }
193}