diff --git a/src/client.rs b/src/client.rs index a3253b5..12c74fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use base64::Engine as _; use extensions::id::{format_identification, parse_id}; use extensions::quota::parse_get_quota_root; use futures::{io, Stream, StreamExt}; -use imap_proto::{RequestId, Response}; +use imap_proto::{Metadata, RequestId, Response}; #[cfg(feature = "runtime-tokio")] use tokio::io::{AsyncRead as Read, AsyncWrite as Write, AsyncWriteExt}; @@ -1250,6 +1250,36 @@ impl Session { Ok(c) } + /// The [`GETMETADATA` command](https://datatracker.ietf.org/doc/html/rfc5464.html#section-4.2) + pub async fn get_metadata( + &mut self, + mailbox_name: &str, + options: &str, + entry_specifier: &str, + ) -> Result> { + let options = if options.is_empty() { + String::new() + } else { + format!(" {options}") + }; + let id = self + .run_command(format!( + "GETMETADATA {} {}{}", + quote!(mailbox_name), + options, + entry_specifier + )) + .await?; + let metadata = parse_metadata( + &mut self.conn.stream, + mailbox_name, + self.unsolicited_responses_tx.clone(), + id, + ) + .await?; + Ok(metadata) + } + /// The [`ID` command](https://datatracker.ietf.org/doc/html/rfc2971) /// /// `identification` is an iterable sequence of pairs such as `("name", Some("MyMailClient"))`. @@ -2315,4 +2345,28 @@ mod tests { assert_eq!(status.exists, 231); } } + + #[cfg_attr(feature = "runtime-tokio", tokio::test)] + #[cfg_attr(feature = "runtime-async-std", async_std::test)] + async fn get_metadata() { + { + let response = b"* METADATA \"INBOX\" (/private/comment \"My own comment\")\r\n\ + A0001 OK GETMETADATA complete\r\n" + .to_vec(); + + let mock_stream = MockStream::new(response); + let mut session = mock_session!(mock_stream); + let metadata = session + .get_metadata("INBOX", "", "\"/private/comment\"") + .await + .unwrap(); + assert_eq!( + session.stream.inner.written_buf, + b"A0001 GETMETADATA \"INBOX\" \"/private/comment\"\r\n".to_vec() + ); + assert_eq!(metadata.len(), 1); + assert_eq!(metadata[0].entry, "/private/comment"); + assert_eq!(metadata[0].value.as_ref().unwrap(), "My own comment"); + } + } } diff --git a/src/parse.rs b/src/parse.rs index 307dc9e..dff34df 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -4,7 +4,7 @@ use async_channel as channel; use futures::io; use futures::prelude::*; use futures::stream::Stream; -use imap_proto::{self, MailboxDatum, RequestId, Response}; +use imap_proto::{self, MailboxDatum, Metadata, RequestId, Response}; use crate::error::{Error, Result}; use crate::types::ResponseData; @@ -394,6 +394,40 @@ pub(crate) async fn parse_ids> + Unpin Ok(ids) } +/// Parses [GETMETADATA](https://www.rfc-editor.org/info/rfc5464) response. +pub(crate) async fn parse_metadata> + Unpin>( + stream: &mut T, + mailbox_name: &str, + unsolicited: channel::Sender, + command_tag: RequestId, +) -> Result> { + let mut res_values = Vec::new(); + while let Some(resp) = stream + .take_while(|res| filter(res, &command_tag)) + .next() + .await + { + let resp = resp?; + match resp.parsed() { + // METADATA Response with Values + // + Response::MailboxData(MailboxDatum::MetadataSolicited { mailbox, values }) + if mailbox == mailbox_name => + { + res_values.extend_from_slice(values.as_slice()); + } + + // We are not interested in + // [Unsolicited METADATA Response without Values](https://datatracker.ietf.org/doc/html/rfc5464.html#section-4.4.2), + // they go to unsolicited channel with other unsolicited responses. + _ => { + handle_unilateral(resp, unsolicited.clone()).await; + } + } + } + Ok(res_values) +} + // check if this is simply a unilateral server response // (see Section 7 of RFC 3501): pub(crate) async fn handle_unilateral(