Skip to main content

io_webdav/rfc4918/
follow_redirects.rs

1//! Send coroutine that surfaces 3xx redirects to the caller.
2//!
3//! Runs an HTTP/1.1 exchange and turns the underlying
4//! `HttpSendYield::WantsRedirect` into a
5//! [`WebdavRedirectYield::WantsRedirect`] so the client can rebuild its
6//! connection and restart the operation against the new target URL. The
7//! success body is returned raw; callers parse it with
8//! `parse_multistatus`.
9//!
10//! [`WebdavRedirectYield::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::{
23//!         WebdavAuth,
24//!         coroutine::WebdavRedirectYield,
25//!         follow_redirects::FollowRedirects,
26//!         request::WebdavRequest,
27//!     },
28//! };
29//! use url::Url;
30//!
31//! // Ready stream needed (TCP-connected, TLS-negociated)
32//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
33//! let mut buf = [0u8; 4096];
34//!
35//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
36//! let auth = WebdavAuth::None;
37//! let request = WebdavRequest::propfind(&base_url, &auth, "io-webdav", "/")
38//!     .content_type_xml()
39//!     .body(Vec::new());
40//! let mut coroutine = FollowRedirects::new(request);
41//! let mut arg = None;
42//!
43//! let ok = loop {
44//!     match coroutine.resume(arg.take()) {
45//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsWrite(bytes)) => {
46//!             stream.write_all(&bytes).unwrap();
47//!         }
48//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRead) => {
49//!             let n = stream.read(&mut buf).unwrap();
50//!             arg = Some(&buf[..n]);
51//!         }
52//!         WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRedirect { url, .. }) => {
53//!             todo!("reconnect to {url}");
54//!         }
55//!         WebdavCoroutineState::Complete(Ok(ok)) => break ok,
56//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
57//!     }
58//! };
59//!
60//! println!("{} bytes", ok.body.len());
61//! ```
62
63use alloc::{string::String, vec::Vec};
64
65use io_http::{
66    coroutine::*,
67    rfc9110::{request::HttpRequest, send::HttpSendOutput},
68    rfc9112::send::{Http11Send, Http11SendError},
69};
70use log::trace;
71use thiserror::Error;
72
73use crate::{
74    coroutine::*,
75    rfc4918::{coroutine::WebdavRedirectYield, send::SendOk},
76};
77
78/// Failure causes during a redirect-aware WebDAV send.
79#[derive(Debug, Error)]
80pub enum FollowRedirectsError {
81    /// The server returned a non-2xx, non-redirect HTTP status.
82    #[error("WebDAV server returned HTTP {0}: {1}")]
83    HttpStatus(u16, String),
84
85    /// The underlying HTTP/1.1 send failed.
86    #[error(transparent)]
87    Send(#[from] Http11SendError),
88}
89
90/// I/O-free coroutine that sends a WebDAV request, surfaces 3xx
91/// redirects via [`WebdavRedirectYield::WantsRedirect`] and returns the
92/// success body as raw bytes.
93#[derive(Debug)]
94pub struct FollowRedirects {
95    state: State,
96}
97
98impl FollowRedirects {
99    /// Builds a new redirect-aware send coroutine. `request` must
100    /// already carry its body bytes.
101    pub fn new(request: HttpRequest) -> Self {
102        Self {
103            state: State::Send(Http11Send::new(request)),
104        }
105    }
106}
107
108impl WebdavCoroutine for FollowRedirects {
109    type Yield = WebdavRedirectYield;
110    type Return = Result<SendOk<Vec<u8>>, FollowRedirectsError>;
111
112    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
113        trace!("sending request");
114        match &mut self.state {
115            State::Send(send) => {
116                let out = match send.resume(arg) {
117                    HttpCoroutineState::Yielded(y) => {
118                        return WebdavCoroutineState::Yielded(y.into());
119                    }
120                    HttpCoroutineState::Complete(Err(err)) => {
121                        return WebdavCoroutineState::Complete(Err(err.into()));
122                    }
123                    HttpCoroutineState::Complete(Ok(out)) => out,
124                };
125
126                let HttpSendOutput {
127                    response,
128                    keep_alive,
129                    ..
130                } = out;
131
132                if !response.status.is_success() {
133                    let body = String::from_utf8_lossy(&response.body).into_owned();
134                    let err = FollowRedirectsError::HttpStatus(*response.status, body);
135                    return WebdavCoroutineState::Complete(Err(err));
136                }
137
138                let body = response.body.clone();
139                WebdavCoroutineState::Complete(Ok(SendOk {
140                    response,
141                    keep_alive,
142                    body,
143                }))
144            }
145        }
146    }
147}
148
149#[derive(Debug)]
150enum State {
151    Send(Http11Send),
152}