Skip to main content

io_webdav/rfc4918/
propfind.rs

1//! Generic `PROPFIND` coroutine (RFC 4918 ยง9.1).
2//!
3//! Requests `props` against `path` at the given `Depth`; the request
4//! body is generated from the selector and the response is parsed into
5//! a [`Multistatus`].
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use std::{
11//!     io::{Read, Write},
12//!     net::TcpStream,
13//! };
14//!
15//! use io_webdav::{
16//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
17//!     rfc4918::{DISPLAYNAME, RESOURCETYPE, WebdavAuth, propfind::Propfind},
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//!     Propfind::new(&base_url, &auth, "io-webdav", "/dav/", 1, &[RESOURCETYPE, DISPLAYNAME]);
29//! let mut arg = None;
30//!
31//! let multistatus = loop {
32//!     match coroutine.resume(arg.take()) {
33//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
34//!             stream.write_all(&bytes).unwrap();
35//!         }
36//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
37//!             let n = stream.read(&mut buf).unwrap();
38//!             arg = Some(&buf[..n]);
39//!         }
40//!         WebdavCoroutineState::Complete(Ok(multistatus)) => break multistatus,
41//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
42//!     }
43//! };
44//!
45//! for entry in &multistatus.responses {
46//!     println!("{}: {:?}", entry.href, entry.text(DISPLAYNAME));
47//! }
48//! ```
49
50use alloc::string::String;
51
52use log::trace;
53use url::Url;
54
55use crate::{
56    coroutine::*,
57    rfc4918::{
58        Multistatus, Property, WebdavAuth, parse_multistatus, propfind_body,
59        request::WebdavRequest,
60        send::{SendError, SendRaw},
61    },
62    webdav_try,
63};
64
65/// Coroutine that runs a `PROPFIND` and parses the multistatus body.
66#[derive(Debug)]
67pub struct Propfind {
68    state: State,
69}
70
71impl Propfind {
72    /// Builds a new `PROPFIND` coroutine requesting `props` against
73    /// `path` (relative to `base_url`) with the given `depth`.
74    pub fn new(
75        base_url: &Url,
76        auth: &WebdavAuth,
77        user_agent: &str,
78        path: &str,
79        depth: u8,
80        props: &[Property],
81    ) -> Self {
82        let request = WebdavRequest::propfind(base_url, auth, user_agent, path)
83            .depth(depth)
84            .content_type_xml()
85            .body(propfind_body(props));
86        Self {
87            state: State::Send(SendRaw::new(request)),
88        }
89    }
90}
91
92impl WebdavCoroutine for Propfind {
93    type Yield = WebdavYield;
94    type Return = Result<Multistatus, SendError>;
95
96    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
97        trace!("sending request");
98        match &mut self.state {
99            State::Send(send) => {
100                let ok = webdav_try!(send, arg);
101                let xml = String::from_utf8_lossy(&ok.body);
102                WebdavCoroutineState::Complete(Ok(parse_multistatus(&xml)))
103            }
104        }
105    }
106}
107
108#[derive(Debug)]
109enum State {
110    Send(SendRaw),
111}