Skip to main content

io_webdav/rfc4791/calendar/
home_set.rs

1//! `calendar-home-set` discovery (RFC 4791 ยง6.2.1).
2//!
3//! Runs a PROPFIND against the principal URL and surfaces the
4//! discovered calendar-home-set URL.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use std::{
10//!     io::{Read, Write},
11//!     net::TcpStream,
12//! };
13//!
14//! use io_webdav::{
15//!     coroutine::{WebdavCoroutine, WebdavCoroutineState},
16//!     rfc4791::calendar::home_set::CalendarHomeSet,
17//!     rfc4918::{WebdavAuth, coroutine::WebdavRedirectYield},
18//! };
19//! use url::Url;
20//!
21//! // Ready stream needed (TCP-connected, TLS-negociated)
22//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
23//! let mut buf = [0u8; 4096];
24//!
25//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
26//! let auth = WebdavAuth::None;
27//! let mut coroutine =
28//!     CalendarHomeSet::new(&base_url, &auth, "io-webdav", "/principals/alice/");
29//! let mut arg = None;
30//!
31//! let home_set = loop {
32//!     match coroutine.resume(arg.take()) {
33//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsWrite(bytes)) => {
34//!             stream.write_all(&bytes).unwrap();
35//!         }
36//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRead) => {
37//!             let n = stream.read(&mut buf).unwrap();
38//!             arg = Some(&buf[..n]);
39//!         }
40//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRedirect { url, .. }) => {
41//!             todo!("reconnect to {url}");
42//!         }
43//!         WebdavCoroutineState::Complete(Ok(home_set)) => break home_set,
44//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
45//!     }
46//! };
47//!
48//! println!("{home_set:?}");
49//! ```
50
51use alloc::string::String;
52
53use log::trace;
54use url::Url;
55
56use crate::{
57    coroutine::*,
58    rfc4791::calendar::CALENDAR_HOME_SET,
59    rfc4918::{
60        WebdavAuth,
61        coroutine::WebdavRedirectYield,
62        follow_redirects::{FollowRedirects, FollowRedirectsError},
63        parse_multistatus, propfind_body,
64        request::WebdavRequest,
65        resolve_href,
66    },
67    webdav_try,
68};
69
70/// I/O-free coroutine that discovers the calendar-home-set URL. Yields
71/// [`None`] when the server returned an empty multistatus.
72#[derive(Debug)]
73pub struct CalendarHomeSet {
74    base_url: Url,
75    state: State,
76}
77
78impl CalendarHomeSet {
79    /// Builds a new `calendar-home-set` discovery coroutine targeting
80    /// `principal_path` (relative to `base_url`).
81    pub fn new(base_url: &Url, auth: &WebdavAuth, user_agent: &str, principal_path: &str) -> Self {
82        let request = WebdavRequest::propfind(base_url, auth, user_agent, principal_path)
83            .depth(0)
84            .content_type_xml()
85            .body(propfind_body(&[CALENDAR_HOME_SET]));
86
87        Self {
88            base_url: base_url.clone(),
89            state: State::Send(FollowRedirects::new(request)),
90        }
91    }
92}
93
94impl WebdavCoroutine for CalendarHomeSet {
95    type Yield = WebdavRedirectYield;
96    type Return = Result<Option<Url>, FollowRedirectsError>;
97
98    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
99        trace!("sending request");
100        match &mut self.state {
101            State::Send(send) => {
102                let ok = webdav_try!(send, arg);
103                let xml = String::from_utf8_lossy(&ok.body);
104                let url = parse_multistatus(&xml)
105                    .responses
106                    .iter()
107                    .find_map(|entry| entry.text(CALENDAR_HOME_SET))
108                    .and_then(|href| resolve_href(&self.base_url, href));
109                WebdavCoroutineState::Complete(Ok(url))
110            }
111        }
112    }
113}
114
115#[derive(Debug)]
116enum State {
117    Send(FollowRedirects),
118}