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}