Skip to main content

io_webdav/rfc6352/card/
read.rs

1//! `read-card` coroutine: GET a card by its resource name.
2//!
3//! Stays byte-oriented: returns raw vCard bytes plus the response's
4//! ETag so io-addressbook can run calcard upstream.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use std::{
10//!     io::{Read, Write},
11//!     net::TcpStream,
12//! };
13//!
14//! use io_webdav::{
15//!     coroutine::{WebdavCoroutine, WebdavCoroutineState, WebdavYield},
16//!     rfc4918::WebdavAuth,
17//!     rfc6352::card::read::ReadCard,
18//! };
19//! use url::Url;
20//!
21//! // Ready stream needed (TCP-connected, TLS-negociated)
22//! let mut stream = TcpStream::connect("dav.example.org:443").unwrap();
23//! let mut buf = [0u8; 4096];
24//!
25//! let base_url: Url = "https://dav.example.org/".parse().unwrap();
26//! let auth = WebdavAuth::None;
27//! let mut coroutine =
28//!     ReadCard::new(&base_url, &auth, "io-webdav", "/dav/addressbooks/contacts/", "alice");
29//! let mut arg = None;
30//!
31//! let card = loop {
32//!     match coroutine.resume(arg.take()) {
33//!         WebdavCoroutineState::Yielded(WebdavYield::WantsWrite(bytes)) => {
34//!             stream.write_all(&bytes).unwrap();
35//!         }
36//!         WebdavCoroutineState::Yielded(WebdavYield::WantsRead) => {
37//!             let n = stream.read(&mut buf).unwrap();
38//!             arg = Some(&buf[..n]);
39//!         }
40//!         WebdavCoroutineState::Complete(Ok(card)) => break card,
41//!         WebdavCoroutineState::Complete(Err(err)) => panic!("{err}"),
42//!     }
43//! };
44//!
45//! println!("{} bytes, etag {:?}", card.data.len(), card.etag);
46//! ```
47
48use alloc::{string::String, vec::Vec};
49
50use log::trace;
51use url::Url;
52
53use crate::{
54    coroutine::*,
55    rfc4918::{
56        WebdavAuth,
57        get::Get,
58        read_etag,
59        send::{SendError, SendOk},
60    },
61    rfc6352::card::join_path,
62    webdav_try,
63};
64
65/// Coroutine that reads a card.
66#[derive(Debug)]
67pub struct ReadCard {
68    state: State,
69}
70
71impl ReadCard {
72    /// Builds a new `read-card` coroutine. `card_uri` is the resource
73    /// name as the server returned it (`CardEntry::uri`).
74    pub fn new(
75        base_url: &Url,
76        auth: &WebdavAuth,
77        user_agent: &str,
78        addressbook_path: &str,
79        card_uri: &str,
80    ) -> Self {
81        let path = join_path(addressbook_path, card_uri);
82        Self {
83            state: State::Get(Get::new(base_url, auth, user_agent, &path)),
84        }
85    }
86}
87
88impl WebdavCoroutine for ReadCard {
89    type Yield = WebdavYield;
90    type Return = Result<CardBody, SendError>;
91
92    fn resume(&mut self, arg: Option<&[u8]>) -> WebdavCoroutineState<Self::Yield, Self::Return> {
93        trace!("sending request");
94        match &mut self.state {
95            State::Get(get) => {
96                let SendOk { response, body, .. } = webdav_try!(get, arg);
97                let etag = read_etag(&response);
98                WebdavCoroutineState::Complete(Ok(CardBody { data: body, etag }))
99            }
100        }
101    }
102}
103
104#[derive(Debug)]
105enum State {
106    Get(Get),
107}
108
109/// Card body plus optional ETag returned by
110/// [`ReadCard`].
111#[derive(Clone, Debug)]
112pub struct CardBody {
113    /// Raw vCard bytes.
114    pub data: Vec<u8>,
115    /// Entity tag (RFC 9110 ยง8.8.3), without surrounding quotes.
116    pub etag: Option<String>,
117}