Skip to main content

io_webdav/rfc4918/
send.rs

1//! Base coroutine every higher-level WebDAV coroutine delegates to:
2//! runs an HTTP/1.1 exchange and returns the raw response body. Higher
3//! layers parse the multistatus with
4//! `parse_multistatus` or keep the
5//! bytes as-is (`GET` / `PUT` of an iCal/vCard resource).
6//!
7//! All I/O is hoisted: the coroutine yields [`WebdavYield`] and the
8//! caller owns the stream work. 3xx redirects surface as
9//! [`SendError::UnexpectedRedirect`]; redirect-aware coroutines use
10//! [`crate::rfc4918::follow_redirects`] instead.
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, WebdavYield},
22//!     rfc4918::{WebdavAuth, request::WebdavRequest, send::SendRaw},
23//! };
24//! use url::Url;
25//!
26//! // Ready stream needed (TCP-connected, TLS-negociated)
27//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
28//! let mut buf = [0u8; 4096];
29//!
30//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
31//! let auth = WebdavAuth::None;
32//! let request = WebdavRequest::get(&base_url, &auth, "io-webdav", "/dav/file.txt").body(Vec::new());
33//! let mut coroutine = SendRaw::new(request);
34//! let mut arg = None;
35//!
36//! let ok = loop {
37//!     match coroutine.resume(arg.take()) {
38//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
39//!             stream.write_all(&bytes).unwrap();
40//!         }
41//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
42//!             let n = stream.read(&mut buf).unwrap();
43//!             arg = Some(&buf[..n]);
44//!         }
45//!         WebdavCoroutineState::Complete(Ok(ok)) => break ok,
46//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
47//!     }
48//! };
49//!
50//! println!("{} bytes, keep-alive: {}", ok.body.len(), ok.keep_alive);
51//! ```
52
53use alloc::{string::String, vec::Vec};
54
55use io_http::{
56    coroutine::*,
57    rfc9110::{
58        request::HttpRequest,
59        response::HttpResponse,
60        send::{HttpSendOutput, HttpSendYield},
61    },
62    rfc9112::send::{Http11Send, Http11SendError},
63};
64use log::trace;
65use thiserror::Error;
66
67use crate::coroutine::*;
68
69/// Successful terminal output of a WebDAV send coroutine.
70#[derive(Debug)]
71pub struct SendOk<T> {
72    /// The HTTP response head (status line and headers).
73    pub response: HttpResponse,
74    /// Whether the server allows reusing the connection.
75    pub keep_alive: bool,
76    /// The coroutine-specific parsed body.
77    pub body: T,
78}
79
80/// Failure causes during a WebDAV send.
81#[derive(Debug, Error)]
82pub enum SendError {
83    /// The server returned a non-2xx HTTP status.
84    #[error("WebDAV server returned HTTP {0}: {1}")]
85    HttpStatus(u16, String),
86    /// The server returned a redirect where none was expected.
87    #[error("WebDAV server returned unexpected redirect")]
88    UnexpectedRedirect,
89
90    /// The underlying HTTP/1.1 send failed.
91    #[error(transparent)]
92    Send(#[from] Http11SendError),
93}
94
95/// I/O-free coroutine that sends a WebDAV request and returns the
96/// response body as raw bytes.
97#[derive(Debug)]
98pub struct SendRaw {
99    state: State,
100}
101
102impl SendRaw {
103    /// Builds a new `SendRaw` coroutine. `request` must already carry
104    /// its body bytes (via [`crate::rfc4918::request::WebdavRequest::body`]).
105    pub fn new(request: HttpRequest) -> Self {
106        Self {
107            state: State::Send(Http11Send::new(request)),
108        }
109    }
110}
111
112impl WebdavCoroutine for SendRaw {
113    type Yield = WebdavYield;
114    type Return = Result<SendOk<Vec<u8>>, SendError>;
115
116    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
117        trace!("sending request");
118        match &mut self.state {
119            State::Send(send) => {
120                let out = match send.resume(arg) {
121                    HttpCoroutineState::Yielded(HttpSendYield::WantsRead) => {
122                        return WebdavCoroutineState::Yielded(WebdavYield::WantsRead);
123                    }
124                    HttpCoroutineState::Yielded(HttpSendYield::WantsWrite(bytes)) => {
125                        return WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes));
126                    }
127                    HttpCoroutineState::Yielded(HttpSendYield::WantsRedirect { .. }) => {
128                        return WebdavCoroutineState::Complete(Err(SendError::UnexpectedRedirect));
129                    }
130                    HttpCoroutineState::Complete(Err(err)) => {
131                        return WebdavCoroutineState::Complete(Err(err.into()));
132                    }
133                    HttpCoroutineState::Complete(Ok(out)) => out,
134                };
135
136                let HttpSendOutput {
137                    response,
138                    keep_alive,
139                    ..
140                } = out;
141
142                if !response.status.is_success() {
143                    let body = String::from_utf8_lossy(&response.body).into_owned();
144                    let err = SendError::HttpStatus(*response.status, body);
145                    return WebdavCoroutineState::Complete(Err(err));
146                }
147
148                let body = response.body.clone();
149                WebdavCoroutineState::Complete(Ok(SendOk {
150                    response,
151                    keep_alive,
152                    body,
153                }))
154            }
155        }
156    }
157}
158
159#[derive(Debug)]
160enum State {
161    Send(Http11Send),
162}