From b36d2dbf783897d2a6483844826dc99b519b7990 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Sun, 31 Dec 2023 10:17:29 +0100 Subject: [PATCH] Fixed IMAP SEARCH BEFORE command --- CHANGELOG.md | 1 + crates/imap-proto/Cargo.toml | 2 +- crates/imap-proto/src/parser/search.rs | 17 ++++++++++++++++- crates/imap-proto/src/protocol/select.rs | 6 +++--- tests/resources/scripts/imap_import.py | 9 ++++++++- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd34acd6..e8fe4a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. This projec ### Fixed - Invalid DKIM signatures for empty message bodies. +- Parsing IMAP `SEARCH BEFORE` commands. ## [0.5.0] - 2023-12-27 diff --git a/crates/imap-proto/Cargo.toml b/crates/imap-proto/Cargo.toml index 0edeeee2..bac227dc 100644 --- a/crates/imap-proto/Cargo.toml +++ b/crates/imap-proto/Cargo.toml @@ -6,7 +6,7 @@ resolver = "2" [dependencies] jmap_proto = { path = "../jmap-proto" } -store = { path = "../store" } +store = { path = "../store", features = ["sqlite"] } mail-parser = { version = "0.9", features = ["full_encoding", "serde_support", "ludicrous_mode"] } ahash = { version = "0.8" } chrono = { version = "0.4"} diff --git a/crates/imap-proto/src/parser/search.rs b/crates/imap-proto/src/parser/search.rs index 1c48e5b4..87651554 100644 --- a/crates/imap-proto/src/parser/search.rs +++ b/crates/imap-proto/src/parser/search.rs @@ -128,7 +128,12 @@ pub fn parse_filters( } else if value.eq_ignore_ascii_case(b"BCC") { filters.push(Filter::Bcc(decode_argument(tokens, decoder)?)); } else if value.eq_ignore_ascii_case(b"BEFORE") { - filters.push(Filter::All); + filters.push(Filter::Before(parse_date( + &tokens + .next() + .ok_or_else(|| Cow::from("Expected date"))? + .unwrap_bytes(), + )?)); } else if value.eq_ignore_ascii_case(b"BODY") { filters.push(Filter::Body(decode_argument(tokens, decoder)?)); } else if value.eq_ignore_ascii_case(b"CC") { @@ -742,6 +747,16 @@ mod tests { sort: None, }, ), + ( + b"5 UID SEARCH BEFORE 1-Dec-2023\r\n".to_vec(), + search::Arguments { + tag: "5".to_string(), + result_options: vec![], + filter: vec![Filter::Before(1701388800)], + is_esearch: true, + sort: None, + }, + ), ] { let command_str = String::from_utf8_lossy(&command).into_owned(); assert_eq!( diff --git a/crates/imap-proto/src/protocol/select.rs b/crates/imap-proto/src/protocol/select.rs index 8bf98d0f..fbb7e393 100644 --- a/crates/imap-proto/src/protocol/select.rs +++ b/crates/imap-proto/src/protocol/select.rs @@ -70,7 +70,7 @@ impl ImapResponse for Response { } buf.extend_from_slice(b"* "); buf.extend_from_slice(self.total_messages.to_string().as_bytes()); - if self.recent_messages > 0 { + if !self.is_rev2 && self.recent_messages > 0 { buf.extend_from_slice( b" EXISTS\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", ); @@ -158,7 +158,7 @@ mod tests { ), concat!( "* 172 EXISTS\r\n", - "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n", + "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", "* 5 RECENT\r\n", "* OK [UNSEEN 3] Unseen messages\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", @@ -196,7 +196,7 @@ mod tests { concat!( "* OK [CLOSED] Closed previous mailbox\r\n", "* 172 EXISTS\r\n", - "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n", + "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\Recent)\r\n", "* 5 RECENT\r\n", "* OK [UNSEEN 3] Unseen messages\r\n", "* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered \\Flagged \\Draft \\*)] All allowed\r\n", diff --git a/tests/resources/scripts/imap_import.py b/tests/resources/scripts/imap_import.py index 481b152b..3798ec4f 100644 --- a/tests/resources/scripts/imap_import.py +++ b/tests/resources/scripts/imap_import.py @@ -3,6 +3,8 @@ import socket import time import threading from email.message import Message +from email.utils import formatdate +from datetime import datetime, timedelta def append_message(thread_id, start, end): conn = imaplib.IMAP4('localhost') @@ -10,15 +12,20 @@ def append_message(thread_id, start, end): conn.socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) start_time = time.time() + base_date = datetime(2000, 1, 1) + for n in range(start, end): + current_date = base_date + timedelta(hours=n) + msg = Message() msg['From'] = 'somebody@some.where' msg['To'] = 'john@example.org' msg['Message-Id'] = f'unique.message.id.{n}@nowhere' + msg['Date'] = formatdate(time.mktime(current_date.timetuple()), localtime=False, usegmt=True) msg['Subject'] = f"This is message #{n}" msg.set_payload('...nothing...') - response_code, response_details = conn.append('INBOX', '', None, str(msg).encode('utf-8')) + response_code, response_details = conn.append('INBOX', '', imaplib.Time2Internaldate(time.mktime(current_date.timetuple())), str(msg).encode('utf-8')) if response_code != 'OK': print(f'Thread {thread_id}: Error while appending message #{n}: {response_code} {response_details}') break