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}