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}