Skip to main content

io_webdav/rfc5397/
current_user_principal.rs

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