M libdav/src/dav.rs => libdav/src/dav.rs +48 -0
@@ 296,6 296,54 @@ where
parse_prop(body, property)
}
+ /// Fetch multiple properties for a single resource.
+ ///
+ /// # Quirks
+ ///
+ /// Same as [`WebDavClient::get_property`].
+ ///
+ /// # Errors
+ ///
+ /// - If there are any network errors or the response could not be parsed.
+ /// - If the requested property is missing in the response.
+ ///
+ /// # See also
+ ///
+ /// - [`WebDavClient::get_property`]
+ /// - [`WebDavClient::set_property`]
+ pub async fn get_properties<'p>(
+ &self,
+ href: &str,
+ properties: &[&PropertyName<'p, 'p>],
+ ) -> Result<Vec<(PropertyName<'p, 'p>, Option<String>)>, WebDavError> {
+ let url = self.relative_uri(href)?;
+
+ let (head, body) = self.propfind(&url, properties, 0).await?;
+ check_status(head.status)?;
+
+ let body = std::str::from_utf8(body.as_ref())?;
+ let doc = roxmltree::Document::parse(body)?;
+ let root = doc.root_element();
+
+ let mut results = Vec::with_capacity(properties.len());
+ for property in properties {
+ let prop = root
+ .descendants()
+ .find(|node| node.tag_name() == **property)
+ // Hack to work around: https://github.com/cyrusimap/cyrus-imapd/issues/4489
+ .or_else(|| {
+ root.descendants()
+ .find(|node| node.tag_name().name() == property.name())
+ })
+ // End hack
+ .and_then(|p| p.text())
+ .map(str::to_owned);
+
+ results.push((**property, prop));
+ }
+ Ok(results)
+ }
+
/// Sends a `PROPUPDATE` query to the server.
///
/// Setting the value to `None` will remove the property. Returns the new value as returned by
M live_tests/src/caldav.rs => live_tests/src/caldav.rs +55 -0
@@ 158,6 158,61 @@ pub(crate) async fn test_setting_and_getting_colour(test_data: &TestData) -> any
Ok(())
}
+pub(crate) async fn test_get_properties(test_data: &TestData) -> anyhow::Result<()> {
+ let new_collection = format!(
+ "{}{}/",
+ test_data.calendar_home_set.path(),
+ &random_string(16)
+ );
+ test_data.caldav.create_calendar(&new_collection).await?;
+
+ let colour = "#ff00ff";
+ let colour_alpha = "#FF00FFFF"; // Some servers normalise to this value.
+ test_data
+ .caldav
+ .set_property(&new_collection, &names::CALENDAR_COLOUR, Some(colour))
+ .await
+ .context("setting collection colour")?;
+
+ let name = "panda-events";
+ test_data
+ .caldav
+ .set_property(&new_collection, &names::DISPLAY_NAME, Some(name))
+ .await
+ .context("setting collection displayname")?;
+
+ let values = test_data
+ .caldav
+ .get_properties(
+ &new_collection,
+ &[
+ &names::CALENDAR_COLOUR,
+ &names::DISPLAY_NAME,
+ &names::CALENDAR_ORDER,
+ ],
+ )
+ .await
+ .context("getting collection properties")?;
+
+ for value in values {
+ match value.0 {
+ names::CALENDAR_COLOUR => match value.1 {
+ Some(c) => {
+ ensure!(c.eq_ignore_ascii_case(colour) || c.eq_ignore_ascii_case(colour_alpha));
+ }
+ None => bail!("Set a colour but then got colour None"),
+ },
+ names::DISPLAY_NAME => ensure!(value.1 == Some("panda-events".into())),
+ names::CALENDAR_ORDER => ensure!(value.1 == None),
+ _ => bail!("got unexpected property"),
+ }
+ }
+
+ test_data.caldav.force_delete(&new_collection).await?;
+
+ Ok(())
+}
+
fn minimal_icalendar() -> anyhow::Result<Vec<u8>> {
let mut entry = String::new();
let uid = random_string(12);
M live_tests/src/main.rs => live_tests/src/main.rs +1 -0
@@ 252,6 252,7 @@ async fn main() -> anyhow::Result<()> {
let (total, passed) = run_tests!(
&test_data,
+ caldav::test_get_properties,
caldav::test_create_and_delete_collection,
caldav::test_create_and_force_delete_collection,
caldav::test_setting_and_getting_displayname,