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}