Skip to main content

io_webdav/rfc6352/addressbook/
home_set.rs

1//! `addressbook-home-set` discovery (RFC 6352 ยง7.1.1).
2//!
3//! Runs a PROPFIND against the principal URL and surfaces the
4//! discovered addressbook-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//!     rfc4918::{WebdavAuth, coroutine::WebdavRedirectYield},
17//!     rfc6352::addressbook::home_set::AddressbookHomeSet,
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//!     AddressbookHomeSet::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    rfc4918::{
59        WebdavAuth,
60        coroutine::WebdavRedirectYield,
61        follow_redirects::{FollowRedirects, FollowRedirectsError},
62        parse_multistatus, propfind_body,
63        request::WebdavRequest,
64        resolve_href,
65    },
66    rfc6352::addressbook::ADDRESSBOOK_HOME_SET,
67    webdav_try,
68};
69
70/// I/O-free coroutine that discovers the addressbook-home-set URL.
71/// Yields [`None`] when the server returned an empty multistatus.
72#[derive(Debug)]
73pub struct AddressbookHomeSet {
74    base_url: Url,
75    state: State,
76}
77
78impl AddressbookHomeSet {
79    /// Builds a new `addressbook-home-set` discovery coroutine.
80    pub fn new(base_url: &Url, auth: &WebdavAuth, user_agent: &str, principal_path: &str) -> Self {
81        let request = WebdavRequest::propfind(base_url, auth, user_agent, principal_path)
82            .depth(0)
83            .content_type_xml()
84            .body(propfind_body(&[ADDRESSBOOK_HOME_SET]));
85
86        Self {
87            base_url: base_url.clone(),
88            state: State::Send(FollowRedirects::new(request)),
89        }
90    }
91}
92
93impl WebdavCoroutine for AddressbookHomeSet {
94    type Yield = WebdavRedirectYield;
95    type Return = Result<Option<Url>, FollowRedirectsError>;
96
97    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
98        trace!("sending request");
99        match &mut self.state {
100            State::Send(send) => {
101                let ok = webdav_try!(send, arg);
102                let xml = String::from_utf8_lossy(&ok.body);
103                let url = parse_multistatus(&xml)
104                    .responses
105                    .iter()
106                    .find_map(|entry| entry.text(ADDRESSBOOK_HOME_SET))
107                    .and_then(|href| resolve_href(&self.base_url, href));
108                WebdavCoroutineState::Complete(Ok(url))
109            }
110        }
111    }
112}
113
114#[derive(Debug)]
115enum State {
116    Send(FollowRedirects),
117}