You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/11/22 07:11:51 UTC

[james-project] 06/12: JAMES-3858 Add EMAILID search key in IMAP SEARCH

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0dcbe6976788309c4d8f2ea6e5255a2520a12280
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Nov 17 22:34:58 2022 +0700

    JAMES-3858 Add EMAILID search key in IMAP SEARCH
---
 .../james/imap/api/message/request/SearchKey.java  | 109 ++++++++++++---------
 .../imap/decode/parser/SearchCommandParser.java    |  14 ++-
 .../james/imap/processor/SearchProcessor.java      |   2 +
 .../james/imapserver/netty/IMAPServerTest.java     |  30 ++++++
 4 files changed, 107 insertions(+), 48 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/message/request/SearchKey.java b/protocols/imap/src/main/java/org/apache/james/imap/api/message/request/SearchKey.java
index 89655e0ce6..c052cca700 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/message/request/SearchKey.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/message/request/SearchKey.java
@@ -28,6 +28,7 @@ import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_BODY
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_CC;
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_DELETED;
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_DRAFT;
+import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_EMAILID;
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_FLAGGED;
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_FROM;
 import static org.apache.james.imap.api.message.request.SearchKey.Type.TYPE_HEADER;
@@ -119,44 +120,45 @@ public final class SearchKey {
         TYPE_YOUNGER,
         TYPE_OLDER,
         TYPE_MODSEQ,
-        TYPE_THREADID
+        TYPE_THREADID,
+        TYPE_EMAILID
     }
 
-    private static final SearchKey UNSEEN = new SearchKey(TYPE_UNSEEN, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey UNSEEN = new SearchKey(TYPE_UNSEEN, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey UNFLAGGED = new SearchKey(TYPE_UNFLAGGED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey UNFLAGGED = new SearchKey(TYPE_UNFLAGGED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey UNDRAFT = new SearchKey(TYPE_UNDRAFT, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey UNDRAFT = new SearchKey(TYPE_UNDRAFT, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey UNDELETED = new SearchKey(TYPE_UNDELETED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey UNDELETED = new SearchKey(TYPE_UNDELETED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey UNANSWERED = new SearchKey(TYPE_UNANSWERED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey UNANSWERED = new SearchKey(TYPE_UNANSWERED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey SEEN = new SearchKey(TYPE_SEEN, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey SEEN = new SearchKey(TYPE_SEEN, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey RECENT = new SearchKey(TYPE_RECENT, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey RECENT = new SearchKey(TYPE_RECENT, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey OLD = new SearchKey(TYPE_OLD, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey OLD = new SearchKey(TYPE_OLD, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey NEW = new SearchKey(TYPE_NEW, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey NEW = new SearchKey(TYPE_NEW, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey FLAGGED = new SearchKey(TYPE_FLAGGED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey FLAGGED = new SearchKey(TYPE_FLAGGED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey DRAFT = new SearchKey(TYPE_DRAFT, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey DRAFT = new SearchKey(TYPE_DRAFT, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey DELETED = new SearchKey(TYPE_DELETED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey DELETED = new SearchKey(TYPE_DELETED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey ANSWERED = new SearchKey(TYPE_ANSWERED, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey ANSWERED = new SearchKey(TYPE_ANSWERED, null, null, 0, null, null, null, null, -1, -1, null, null);
 
-    private static final SearchKey ALL = new SearchKey(TYPE_ALL, null, null, 0, null, null, null, null, -1, -1, null);
+    private static final SearchKey ALL = new SearchKey(TYPE_ALL, null, null, 0, null, null, null, null, -1, -1, null, null);
 
     // NUMBERS
     public static SearchKey buildSequenceSet(IdRange[] ids) {
-        return new SearchKey(TYPE_SEQUENCE_SET, null, null, 0, null, null, null, ids, -1, -1, null);
+        return new SearchKey(TYPE_SEQUENCE_SET, null, null, 0, null, null, null, ids, -1, -1, null, null);
     }
 
     public static SearchKey buildUidSet(UidRange[] ids) {
-        return new SearchKey(TYPE_UID, null, null, 0, null, null, ids, null, -1, -1, null);
+        return new SearchKey(TYPE_UID, null, null, 0, null, null, ids, null, -1, -1, null, null);
     }
 
     // NO PARAMETERS
@@ -218,99 +220,103 @@ public final class SearchKey {
 
     // ONE VALUE
     public static SearchKey buildBcc(String value) {
-        return new SearchKey(TYPE_BCC, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_BCC, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildBody(String value) {
-        return new SearchKey(TYPE_BODY, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_BODY, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildCc(String value) {
-        return new SearchKey(TYPE_CC, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_CC, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildFrom(String value) {
-        return new SearchKey(TYPE_FROM, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_FROM, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildKeyword(String value) {
-        return new SearchKey(TYPE_KEYWORD, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_KEYWORD, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSubject(String value) {
-        return new SearchKey(TYPE_SUBJECT, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SUBJECT, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildText(String value) {
-        return new SearchKey(TYPE_TEXT, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_TEXT, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildTo(String value) {
-        return new SearchKey(TYPE_TO, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_TO, null, null, 0, null, value, null, null, -1, -1, null, null);
     }  
     
     public static SearchKey buildThreadId(String value) {
-        return new SearchKey(TYPE_THREADID, null, null, 0, null, null, null, null, -1, -1, value);
+        return new SearchKey(TYPE_THREADID, null, null, 0, null, null, null, null, -1, -1, value, null);
+    }
+
+    public static SearchKey buildMessageId(String value) {
+        return new SearchKey(TYPE_EMAILID, null, null, 0, null, null, null, null, -1, -1, null, value);
     }
 
     public static SearchKey buildUnkeyword(String value) {
-        return new SearchKey(TYPE_UNKEYWORD, null, null, 0, null, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_UNKEYWORD, null, null, 0, null, value, null, null, -1, -1, null, null);
     }
     
     // ONE DATE
     public static SearchKey buildYounger(long seconds) {
-        return new SearchKey(TYPE_YOUNGER, null, null, 0, null, null, null, null, seconds, -1, null);
+        return new SearchKey(TYPE_YOUNGER, null, null, 0, null, null, null, null, seconds, -1, null, null);
     }
 
     public static SearchKey buildOlder(long seconds) {
-        return new SearchKey(TYPE_OLDER, null, null, 0, null, null, null, null, seconds, -1, null);
+        return new SearchKey(TYPE_OLDER, null, null, 0, null, null, null, null, seconds, -1, null, null);
     }
 
     
     // ONE DATE
     public static SearchKey buildBefore(DayMonthYear date) {
-        return new SearchKey(TYPE_BEFORE, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_BEFORE, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildOn(DayMonthYear date) {
-        return new SearchKey(TYPE_ON, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_ON, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSentBefore(DayMonthYear date) {
-        return new SearchKey(TYPE_SENTBEFORE, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SENTBEFORE, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSentOn(DayMonthYear date) {
-        return new SearchKey(TYPE_SENTON, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SENTON, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSentSince(DayMonthYear date) {
-        return new SearchKey(TYPE_SENTSINCE, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SENTSINCE, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSince(DayMonthYear date) {
-        return new SearchKey(TYPE_SINCE, date, null, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SINCE, date, null, 0, null, null, null, null, -1, -1, null, null);
     }
 
     // FIELD VALUE
     public static SearchKey buildHeader(String name, String value) {
-        return new SearchKey(TYPE_HEADER, null, null, 0, name, value, null, null, -1, -1, null);
+        return new SearchKey(TYPE_HEADER, null, null, 0, name, value, null, null, -1, -1, null, null);
     }
 
     // ONE NUMBER
     public static SearchKey buildLarger(long size) {
-        return new SearchKey(TYPE_LARGER, null, null, size, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_LARGER, null, null, size, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildSmaller(long size) {
-        return new SearchKey(TYPE_SMALLER, null, null, size, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_SMALLER, null, null, size, null, null, null, null, -1, -1, null, null);
     }
 
     // NOT
     public static SearchKey buildNot(SearchKey key) {
         final List<SearchKey> keys = new ArrayList<>();
         keys.add(key);
-        return new SearchKey(TYPE_NOT, null, keys, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_NOT, null, keys, 0, null, null, null, null, -1, -1, null, null);
     }
 
     // OR
@@ -318,7 +324,7 @@ public final class SearchKey {
         final List<SearchKey> keys = new ArrayList<>();
         keys.add(keyOne);
         keys.add(keyTwo);
-        return new SearchKey(TYPE_OR, null, keys, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_OR, null, keys, 0, null, null, null, null, -1, -1, null, null);
     }
 
     /**
@@ -329,11 +335,11 @@ public final class SearchKey {
      * @return <code>SearchKey</code>, not null
      */
     public static SearchKey buildAnd(List<SearchKey> keys) {
-        return new SearchKey(TYPE_AND, null, keys, 0, null, null, null, null, -1, -1, null);
+        return new SearchKey(TYPE_AND, null, keys, 0, null, null, null, null, -1, -1, null, null);
     }
 
     public static SearchKey buildModSeq(long modSeq) {
-        return new SearchKey(TYPE_MODSEQ, null, null, 0, null, null, null, null, -1, modSeq, null);
+        return new SearchKey(TYPE_MODSEQ, null, null, 0, null, null, null, null, -1, modSeq, null, null);
     }
     
     private final Type type;
@@ -357,8 +363,10 @@ public final class SearchKey {
     private final long modSeq;
 
     private final String threadId;
-    
-    private SearchKey(Type type, DayMonthYear date, List<SearchKey> keys, long number, String name, String value, UidRange[] uids, IdRange[] sequence, long seconds, long modSeq, String threadId) {
+
+    private final String messageId;
+
+    private SearchKey(Type type, DayMonthYear date, List<SearchKey> keys, long number, String name, String value, UidRange[] uids, IdRange[] sequence, long seconds, long modSeq, String threadId, String messageId) {
         this.type = type;
         this.date = date;
         this.keys = keys;
@@ -370,12 +378,17 @@ public final class SearchKey {
         this.uids = uids;
         this.sequence = sequence;
         this.threadId = threadId;
+        this.messageId = messageId;
     }
 
     public String getThreadId() {
         return threadId;
     }
-    
+
+    public String getMessageId() {
+        return messageId;
+    }
+
     /**
      * Gets a date value to be search upon.
      * 
@@ -482,7 +495,8 @@ public final class SearchKey {
                 && Objects.equals(this.value, searchKey.value)
                 && Arrays.equals(this.sequence, searchKey.sequence)
                 && Arrays.equals(this.uids, searchKey.uids)
-                && Objects.equals(this.threadId, searchKey.threadId);
+                && Objects.equals(this.threadId, searchKey.threadId)
+                && Objects.equals(this.messageId, searchKey.messageId);
         }
         return false;
     }
@@ -490,7 +504,7 @@ public final class SearchKey {
     @Override
     public final int hashCode() {
         return Objects.hash(type, date, keys, size, name, value,
-            Arrays.hashCode(sequence), Arrays.hashCode(uids), seconds, modSeq, threadId);
+            Arrays.hashCode(sequence), Arrays.hashCode(uids), seconds, modSeq, threadId, messageId);
     }
 
     @Override
@@ -514,6 +528,7 @@ public final class SearchKey {
             .add("sequences", Optional.ofNullable(sequence).map(ImmutableList::copyOf).orElse(null))
             .add("keys", Optional.ofNullable(keys).map(ImmutableList::copyOf).orElse(null))
             .add("threadId", threadId)
+            .add("messageId", messageId)
             .toString();
     }
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/SearchCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/SearchCommandParser.java
index 3283b754b5..2ade773bde 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/SearchCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/SearchCommandParser.java
@@ -99,7 +99,7 @@ public class SearchCommandParser extends AbstractUidCommandParser {
             case 'D':
                 return d(request);
             case 'E':
-                throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unknown search key");
+                return emailId(request);
             case 'F':
                 return f(request, context.getCharset());
             case 'G':
@@ -729,6 +729,18 @@ public class SearchCommandParser extends AbstractUidCommandParser {
         return result;
     }
 
+    private SearchKey emailId(ImapRequestLineReader request) throws DecodingException {
+        nextIsM(request);
+        nextIsA(request);
+        nextIsI(request);
+        nextIsL(request);
+        nextIsI(request);
+        nextIsD(request);
+        nextIsSpace(request);
+
+        return SearchKey.buildMessageId(request.astring());
+    }
+
     private SearchKey uid(ImapRequestLineReader request) throws DecodingException {
         final SearchKey result;
         nextIsD(request);
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
index 0805f7f65b..786a8915ed 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
@@ -375,6 +375,8 @@ public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> imp
             return SearchQuery.or(SearchQuery.modSeqEquals(modSeq), SearchQuery.modSeqGreaterThan(modSeq));
         case TYPE_THREADID:
             return SearchQuery.threadId(ThreadId.fromBaseMessageId(getMailboxManager().getMessageIdFactory().fromString(key.getThreadId())));
+        case TYPE_EMAILID:
+            return SearchQuery.hasMessageId(getMailboxManager().getMessageIdFactory().fromString(key.getMessageId()));
         default:
             LOGGER.warn("Ignoring unknown search key {}", type);
             return SearchQuery.all();
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index c7d20cf0f5..5973e676a9 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1438,6 +1438,36 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains(("* SEARCH " + appendResult.getId().getUid().asLong())));
         }
 
+        // Not an MPT test as ThreadId is a variable server-set and implementation specific
+        @Test
+        void imapSearchShouldSupportEmailId() throws Exception {
+            MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
+            MessageManager.AppendResult appendResult = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .appendMessage(MessageManager.AppendCommand.builder().build("MIME-Version: 1.0\r\n" +
+                    "Content-Type: text/html; charset=UTF-8\r\n" +
+                    "Content-Transfer-Encoding: quoted-printable\r\n" +
+                    "From: =?ISO-8859-1?Q?Beno=EEt_TELLIER?= <b...@linagora.com>\r\n" +
+                    "Sender: =?ISO-8859-1?Q?Beno=EEt_TELLIER?= <b...@linagora.com>\r\n" +
+                    "Reply-To: b@linagora.com\r\n" +
+                    "To: =?ISO-8859-1?Q?Beno=EEt_TELLIER?= <b...@linagora.com>\r\n" +
+                    "Subject: Test utf-8 charset\r\n" +
+                    "Message-ID: <Mi...@linagora.com>\r\n" +
+                    "Date: Sun, 28 Mar 2021 03:58:06 +0000\r\n" +
+                    "\r\n" +
+                    "<p>=E5=A4=A9=E5=A4=A9=E5=90=91=E4=B8=8A<br></p>\r\n"), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a0 OK"));
+            clientConnection.write(ByteBuffer.wrap("a1 SELECT INBOX\r\n".getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK"));
+            clientConnection.write(ByteBuffer.wrap(String.format("a2 UID SEARCH EMAILID %s\r\n", appendResult.getId().getMessageId().serialize()).getBytes(StandardCharsets.UTF_8)));
+
+            readStringUntil(clientConnection, s -> s.contains(("* SEARCH " + appendResult.getId().getUid().asLong())));
+        }
+
         @Test
         void searchingShouldSupportMultipleUTF8Criteria() throws Exception {
             String host = "127.0.0.1";


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org