Skip to main content

io_webdav/rfc6352/card/
create.rs

1//! `create-card` coroutine: PUT raw vCard bytes against
2//! `<addressbook>/<id>.vcf`.
3//!
4//! Uses `If-None-Match: *` so the server rejects the PUT when a
5//! resource with the same id already exists.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use std::{
11//!     io::{Read, Write},
12//!     net::TcpStream,
13//! };
14//!
15//! use io_webdav::{
16//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
17//!     rfc4918::WebdavAuth,
18//!     rfc6352::card::create::CreateCard,
19//! };
20//! use url::Url;
21//!
22//! // Ready stream needed (TCP-connected, TLS-negociated)
23//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
24//! let mut buf = [0u8; 4096];
25//!
26//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
27//! let auth = WebdavAuth::None;
28//! let vcard = b"BEGIN:VCARD\r\n...\r\nEND:VCARD\r\n".to_vec();
29//! let mut coroutine = CreateCard::new(
30//!     &base_url,
31//!     &auth,
32//!     "io-webdav",
33//!     "/dav/addressbooks/contacts/",
34//!     "alice",
35//!     vcard,
36//! );
37//! let mut arg = None;
38//!
39//! let created = loop {
40//!     match coroutine.resume(arg.take()) {
41//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
42//!             stream.write_all(&bytes).unwrap();
43//!         }
44//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
45//!             let n = stream.read(&mut buf).unwrap();
46//!             arg = Some(&buf[..n]);
47//!         }
48//!         WebdavCoroutineState::Complete(Ok(created)) => break created,
49//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
50//!     }
51//! };
52//!
53//! println!("created {} (etag {:?})", created.id, created.etag);
54//! ```
55
56use core::mem;
57
58use alloc::{
59    format,
60    string::{String, ToString},
61    vec::Vec,
62};
63
64use log::trace;
65use url::Url;
66
67use crate::{
68    coroutine::*,
69    rfc4918::{
70        WebdavAuth,
71        put::{Put, PutArgs},
72        read_etag,
73        send::{SendError, SendOk},
74    },
75    rfc6352::card::join_path,
76    webdav_try,
77};
78
79/// Coroutine that creates a card.
80#[derive(Debug)]
81pub struct CreateCard {
82    id: String,
83    state: State,
84}
85
86impl CreateCard {
87    /// Builds a new `create-card` coroutine.
88    pub fn new(
89        base_url: &Url,
90        auth: &WebdavAuth,
91        user_agent: &str,
92        addressbook_path: &str,
93        id: &str,
94        vcard: Vec<u8>,
95    ) -> Self {
96        let path = join_path(addressbook_path, &format!("{id}.vcf"));
97        let put = Put::new(PutArgs {
98            base_url,
99            auth,
100            user_agent,
101            path: &path,
102            content_type: "text/vcard; charset=utf-8",
103            body: vcard,
104            if_match: None,
105            if_none_match: Some("*"),
106        });
107        Self {
108            id: id.to_string(),
109            state: State::Put(put),
110        }
111    }
112}
113
114impl WebdavCoroutine for CreateCard {
115    type Yield = WebdavYield;
116    type Return = Result<CreateCardOk, SendError>;
117
118    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
119        trace!("sending request");
120        match &mut self.state {
121            State::Put(put) => {
122                let SendOk { response, .. } = webdav_try!(put, arg);
123                let etag = read_etag(&response);
124                let id = mem::take(&mut self.id);
125                WebdavCoroutineState::Complete(Ok(CreateCardOk { id, etag }))
126            }
127        }
128    }
129}
130
131#[derive(Debug)]
132enum State {
133    Put(Put),
134}
135
136/// Outcome of a successful
137/// [`CreateCard`] resume.
138#[derive(Clone, Debug)]
139pub struct CreateCardOk {
140    /// Card identifier (as supplied by the caller).
141    pub id: String,
142    /// Entity tag returned by the server, when present.
143    pub etag: Option<String>,
144}