Skip to content

Commit

Permalink
feat: add METADATA extension support
Browse files Browse the repository at this point in the history
  • Loading branch information
link2xt committed Jan 26, 2024
1 parent 3d6509d commit ae57513
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
56 changes: 55 additions & 1 deletion src/client.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -1250,6 +1250,36 @@ impl<T: Read + Write + Unpin + fmt::Debug + Send> Session<T> {
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<Vec<Metadata>> {
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"))`.
Expand Down Expand Up @@ -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");
}
}
}
36 changes: 35 additions & 1 deletion src/parse.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -394,6 +394,40 @@ pub(crate) async fn parse_ids<T: Stream<Item = io::Result<ResponseData>> + Unpin
Ok(ids)
}

/// Parses [GETMETADATA](https://www.rfc-editor.org/info/rfc5464) response.
pub(crate) async fn parse_metadata<T: Stream<Item = io::Result<ResponseData>> + Unpin>(
stream: &mut T,
mailbox_name: &str,
unsolicited: channel::Sender<UnsolicitedResponse>,
command_tag: RequestId,
) -> Result<Vec<Metadata>> {
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
// <https://datatracker.ietf.org/doc/html/rfc5464.html#section-4.4.1>
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(
Expand Down

0 comments on commit ae57513

Please sign in to comment.