From ca51077f53d1fa706feefc95642768b6d3fa921d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 28 Feb 2020 09:09:43 +0200 Subject: [PATCH] imap: Add support for untagged FETCH (FLAG.. messages IDLE connection can get untagged "* FETCH (FLAGS ({flag_list))" messages if any client has changed flags. Support this refresh event. --- melib/src/backends.rs | 1 + melib/src/backends/imap/protocol_parser.rs | 24 +++++++++++-- melib/src/backends/imap/watch.rs | 41 ++++++++++++++++++++++ src/conf/accounts.rs | 28 +++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 47338484..6af6b9dc 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -210,6 +210,7 @@ pub enum RefreshEventKind { Rename(EnvelopeHash, EnvelopeHash), Create(Box), Remove(EnvelopeHash), + NewFlags(EnvelopeHash, (Flag, Vec)), Rescan, Failure(MeliError), } diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index 0457b8c6..b47ad5a4 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -648,6 +648,7 @@ pub enum UntaggedResponse { /// messages). /// ``` Recent(usize), + Fetch(usize, (Flag, Vec)), } named!( @@ -664,6 +665,8 @@ named!( b"EXPUNGE" => Some(Expunge(num)), b"EXISTS" => Some(Exists(num)), b"RECENT" => Some(Recent(num)), + _ if tag.starts_with(b"FETCH ") => flags( +unsafe { std::str::from_utf8_unchecked(&tag[b"FETCH (FLAGS (".len()..]) }).map(|flags| Fetch(num, flags)).to_full_result().map_err(|err| debug!("untagged_response malformed fetch: {}", unsafe { std::str::from_utf8_unchecked(tag) })).ok(), _ => { debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) }); None @@ -675,8 +678,8 @@ named!( named!( pub search_results>, - alt_complete!(do_parse!( tag!("* SEARCH") - >> list: separated_nonempty_list_complete!(tag!(" "), map_res!(is_not!("\r\n"), |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })) + alt_complete!(do_parse!( tag!("* SEARCH ") + >> list: separated_nonempty_list_complete!(tag!(b" "), map_res!(is_not!(" \r\n"), |s: &[u8]| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })) >> tag!("\r\n") >> ({ list })) | do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() }))) @@ -691,6 +694,23 @@ named!( do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] }))) ); +#[test] +fn test_imap_search() { + assert_eq!(search_results(b"* SEARCH\r\n").to_full_result(), Ok(vec![])); + assert_eq!( + search_results(b"* SEARCH 1\r\n").to_full_result(), + Ok(vec![1]) + ); + assert_eq!( + search_results(b"* SEARCH 1 2 3 4\r\n").to_full_result(), + Ok(vec![1, 2, 3, 4]) + ); + assert_eq!( + search_results_raw(b"* SEARCH 1 2 3 4\r\n").to_full_result(), + Ok(&b"1 2 3 4"[..]) + ); +} + #[derive(Debug, Default, Clone)] pub struct SelectResponse { pub exists: usize, diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index 2a43f146..56f09205 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -492,6 +492,47 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { *prev_exists = n; } } + Ok(Some(Fetch(msg_seq, flags))) => { + /* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq + * and send update + */ + let mut conn = main_conn.lock().unwrap(); + debug!("fetch {} {:?}", msg_seq, flags); + exit_on_error!( + sender, + mailbox_hash, + work_context, + thread_id, + conn.send_command(b"EXAMINE INBOX") + conn.read_response(&mut response) + conn.send_command( + &[ + b"UID SEARCH ", + format!("{}", msg_seq).as_bytes(), + ] + .join(&b' '), + ) + conn.read_response(&mut response) + ); + match search_results(response.split_rn().next().unwrap_or("").as_bytes()) + .to_full_result() + { + Ok(mut v) => { + if let Some(uid) = v.pop() { + if let Some(env_hash) = uid_store.uid_index.lock().unwrap().get(&uid) { + sender.send(RefreshEvent { + hash: mailbox_hash, + kind: NewFlags(*env_hash, flags), + }); + } + } + } + Err(e) => { + debug!(&response); + debug!(e); + } + } + } Ok(None) | Err(_) => {} } work_context diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index 01cd3c47..3e7f669c 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -453,6 +453,34 @@ impl Account { self.collection.update(old_hash, *envelope, mailbox_hash); return Some(EnvelopeUpdate(old_hash)); } + RefreshEventKind::NewFlags(env_hash, (flags, tags)) => { + let mut envelopes = self.collection.envelopes.write().unwrap(); + envelopes.entry(env_hash).and_modify(|entry| { + for f in tags { + let hash = tag_hash!(f); + if !entry.labels().contains(&hash) { + entry.labels_mut().push(hash); + } + } + entry.set_flags(flags); + }); + #[cfg(feature = "sqlite3")] + { + if let Err(err) = crate::sqlite3::remove(env_hash).and_then(|_| { + crate::sqlite3::insert(&envelopes[&env_hash], &self.backend, &self.name) + }) { + melib::log( + format!( + "Failed to update envelope {} in cache: {}", + envelopes[&env_hash].message_id_display(), + err.to_string() + ), + melib::ERROR, + ); + } + } + return Some(EnvelopeUpdate(env_hash)); + } RefreshEventKind::Rename(old_hash, new_hash) => { debug!("rename {} to {}", old_hash, new_hash); self.collection.rename(old_hash, new_hash, mailbox_hash);