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}