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