Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for APPENDUID response data #232

Merged
merged 4 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> {
///
/// Note: be sure to set flags and optional date before you
/// finish the command.
pub fn finish(&mut self) -> Result<()> {
pub fn finish(&mut self) -> Result<Appended> {
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
let flagstr = self
.flags
.clone()
Expand Down Expand Up @@ -246,7 +246,9 @@ impl<'a, T: Read + Write> AppendCmd<'a, T> {
self.session.stream.write_all(self.content)?;
self.session.stream.write_all(b"\r\n")?;
self.session.stream.flush()?;
self.session.read_response().map(|_| ())
self.session
.read_response()
.and_then(|(lines, _)| parse_append(&lines, &mut self.session.unsolicited_responses_tx))
}
}

Expand Down
78 changes: 78 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,40 @@ pub fn parse_expunge(
}
}

pub fn parse_append(
mut lines: &[u8],
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Appended> {
let mut appended = Appended::default();

loop {
match imap_proto::parser::parse_response(lines) {
Ok((rest, Response::Done { status, code, .. })) => {
lines = rest;
assert_eq!(status, imap_proto::Status::Ok);

if let Some(ResponseCode::AppendUid(validity, uids)) = code {
appended.uid_validity = Some(validity);
appended.uids = Some(uids);
}
}
Ok((rest, data)) => {
lines = rest;
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
break Err(resp.into());
}
}
_ => {
return Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
}
}

if lines.is_empty() {
break Ok(appended);
}
}
}

pub fn parse_noop(
lines: Vec<u8>,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
Expand Down Expand Up @@ -698,4 +732,48 @@ mod tests {
assert_eq!(del.next(), Some(12));
assert_eq!(del.next(), None);
}

#[test]
fn parse_append_uid() {
// If the user has enabled UIDPLUS (RFC 4315), the response contains an APPENDUID
// response code followed by the UIDVALIDITY of the destination mailbox and the
// UID assigned to the appended message in the destination mailbox.
// If the MULTIAPPEND extension is also used, there can be multiple UIDs.
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
let lines = b"A003 OK [APPENDUID 38505 3955] APPEND completed\r\n";
let (mut send, recv) = mpsc::channel();
let resp = parse_append(lines, &mut send).unwrap();

assert!(recv.try_recv().is_err());
assert_eq!(resp.uid_validity, Some(38505));
match resp.uids {
Some(uid_list) => {
let mut it = uid_list.iter();
assert_eq!(it.next(), Some(&UidSetMember::Uid(3955)));
assert_eq!(it.next(), None);
}
None => panic!("Missing UIDs in APPEND response"),
};
}

#[test]
fn parse_multiappend_uid() {
// If the user has enabled UIDPLUS (RFC 4315), the response contains an APPENDUID
// response code followed by the UIDVALIDITY of the destination mailbox and the
// UID assigned to the appended message in the destination mailbox.
// If the MULTIAPPEND extension is also used, there can be multiple UIDs.
let lines = b"A003 OK [APPENDUID 38505 3955:3957] APPEND completed\r\n";
let (mut send, recv) = mpsc::channel();
let resp = parse_append(lines, &mut send).unwrap();

assert!(recv.try_recv().is_err());
assert_eq!(resp.uid_validity, Some(38505));
match resp.uids {
Some(uid_list) => {
let mut it = uid_list.iter();
assert_eq!(it.next(), Some(&UidSetMember::UidRange(3955..=3957)));
assert_eq!(it.next(), None);
}
None => panic!("Missing UIDs in APPEND response"),
};
}
}
29 changes: 29 additions & 0 deletions src/types/appended.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use imap_proto::UidSetMember;

/// Meta-information about a message, as returned by
/// [`APPEND`](https://tools.ietf.org/html/rfc3501#section-6.3.11).
/// Note that `APPEND` only returns any data if certain extensions are enabled,
/// for example [`UIDPLUS`](https://tools.ietf.org/html/rfc4315).
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Appended {
/// The unique identifier validity value of the mailbox that the message was appended to.
/// See [`Uid`] for more details. Only present if server supports [`UIDPLUS`](https://tools.ietf.org/html/rfc4315).
pub uid_validity: Option<u32>,

/// The unique identifier value of the messages that were appended.
/// Only present if server supports [`UIDPLUS`](https://tools.ietf.org/html/rfc4315).
/// Contains only a single value unless the [`MULTIAPPEND`](https://tools.ietf.org/html/rfc3502) extension
/// was used to upload multiple messages.
pub uids: Option<Vec<UidSetMember>>,
}

#[allow(clippy::derivable_impls)]
impl Default for Appended {
fn default() -> Appended {
Appended {
uid_validity: None,
uids: None,
}
}
}
3 changes: 3 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,6 @@ pub use self::deleted::Deleted;

mod unsolicited_response;
pub use self::unsolicited_response::{AttributeValue, UnsolicitedResponse};

mod appended;
pub use self::appended::Appended;