Skip to main content

io_webdav/rfc4918/
put.rs

1//! Generic `PUT` coroutine (RFC 4918 §9.7).
2//!
3//! Sends a `PUT` against `path` with the caller-supplied body bytes
4//! and content type. Stays byte-oriented: callers parse iCal/vCard
5//! upstream.
6//!
7//! Supports the optional `If-Match` (RFC 9110 §13.1.1) and
8//! `If-None-Match` (RFC 9110 §13.1.2) preconditions so callers can
9//! gate the write on a known ETag.
10//!
11//! # Example
12//!
13//! ```rust,no_run
14//! use std::{
15//!     io::{Read, Write},
16//!     net::TcpStream,
17//! };
18//!
19//! use io_webdav::{
20//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
21//!     rfc4918::{
22//!         WebdavAuth,
23//!         put::{Put, PutArgs},
24//!     },
25//! };
26//! use url::Url;
27//!
28//! // Ready stream needed (TCP-connected, TLS-negociated)
29//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
30//! let mut buf = [0u8; 4096];
31//!
32//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
33//! let auth = WebdavAuth::None;
34//! let mut coroutine = Put::new(PutArgs {
35//!     base_url: &base_url,
36//!     auth: &auth,
37//!     user_agent: "io-webdav",
38//!     path: "/dav/calendars/personal/event-1.ics",
39//!     content_type: "text/calendar; charset=utf-8",
40//!     body: b"BEGIN:VCALENDAR\r\n...\r\nEND:VCALENDAR\r\n".to_vec(),
41//!     if_match: None,
42//!     if_none_match: Some("*"),
43//! });
44//! let mut arg = None;
45//!
46//! let ok = loop {
47//!     match coroutine.resume(arg.take()) {
48//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
49//!             stream.write_all(&bytes).unwrap();
50//!         }
51//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
52//!             let n = stream.read(&mut buf).unwrap();
53//!             arg = Some(&buf[..n]);
54//!         }
55//!         WebdavCoroutineState::Complete(Ok(ok)) => break ok,
56//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
57//!     }
58//! };
59//!
60//! println!("keep-alive: {}", ok.keep_alive);
61//! ```
62
63use alloc::vec::Vec;
64
65use log::trace;
66use url::Url;
67
68use crate::{
69    coroutine::*,
70    rfc4918::{
71        WebdavAuth,
72        request::WebdavRequest,
73        send::{SendError, SendOk, SendRaw},
74    },
75};
76
77/// Build inputs for a [`Put`] coroutine.
78///
79/// Uses a struct rather than positional arguments so callers can
80/// build the request literal-style and skip the two optional
81/// precondition fields without juggling positional `None`s.
82#[derive(Clone, Debug)]
83pub struct PutArgs<'a> {
84    /// Base URL the request path is resolved against.
85    pub base_url: &'a Url,
86    /// Authentication scheme for the `Authorization` header.
87    pub auth: &'a WebdavAuth,
88    /// Value emitted as the `User-Agent` header.
89    pub user_agent: &'a str,
90    /// Resource path to PUT to, relative to `base_url`.
91    pub path: &'a str,
92    /// MIME type emitted as the `Content-Type` header.
93    pub content_type: &'a str,
94    /// Raw request body bytes.
95    pub body: Vec<u8>,
96    /// Optional `If-Match` ETag (RFC 9110 §13.1.1).
97    pub if_match: Option<&'a str>,
98    /// Optional `If-None-Match` ETag (RFC 9110 §13.1.2).
99    pub if_none_match: Option<&'a str>,
100}
101
102/// Coroutine that runs a `PUT`.
103#[derive(Debug)]
104pub struct Put {
105    state: State,
106}
107
108impl Put {
109    /// Builds a new `PUT` coroutine.
110    pub fn new(args: PutArgs<'_>) -> Self {
111        let mut builder = WebdavRequest::put(args.base_url, args.auth, args.user_agent, args.path)
112            .content_type(args.content_type);
113
114        if let Some(etag) = args.if_match {
115            builder = builder.if_match(etag);
116        }
117
118        if let Some(etag) = args.if_none_match {
119            builder = builder.if_none_match(etag);
120        }
121
122        let request = builder.body(args.body);
123        Self {
124            state: State::Send(SendRaw::new(request)),
125        }
126    }
127}
128
129impl WebdavCoroutine for Put {
130    type Yield = WebdavYield;
131    type Return = Result<SendOk<Vec<u8>>, SendError>;
132
133    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
134        trace!("sending request");
135        match &mut self.state {
136            State::Send(send) => send.resume(arg),
137        }
138    }
139}
140
141#[derive(Debug)]
142enum State {
143    Send(SendRaw),
144}