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 22a6e85923b4b3137980bf9ac845a66be6d43ff6
Author: Benoit Tellier <[email protected]>
AuthorDate: Mon Sep 28 14:51:00 2020 +0700

    JAMES-3377 Email/query: search headers
---
 .../contract/EmailQueryMethodContract.scala        | 422 +++++++++++++++++++--
 .../doc/specs/spec/mail/message.mdown              |   1 -
 .../james/jmap/json/EmailQuerySerializer.scala     |  23 +-
 .../org/apache/james/jmap/mail/EmailQuery.scala    |   6 +-
 .../james/jmap/utils/search/MailboxFilter.scala    |   5 +-
 5 files changed, 408 insertions(+), 49 deletions(-)

diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index e39f014..e954bf5 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -47,6 +47,7 @@ import org.apache.james.mailbox.model.{MailboxACL, 
MailboxPath, MessageId}
 import org.apache.james.mime4j.dom.Message
 import org.apache.james.mime4j.field.address.DefaultAddressParser
 import org.apache.james.mime4j.message.DefaultMessageWriter
+import org.apache.james.mime4j.stream.RawField
 import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
 import org.apache.james.utils.DataProbeImpl
 import org.awaitility.Awaitility
@@ -592,6 +593,387 @@ trait EmailQueryMethodContract {
   }
 
   @Test
+  def headerExistsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "value"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"header": ["X-SpEcIfIc"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def headerShouldAllowToMatchMailWithSpecificHeaderSet(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "value"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"header": ["X-Specific"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def headerShouldAllowToMatchMailWithSpecificValueHeaderSet(server: 
GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "value"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "other"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"header": ["X-Specific", "value"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def headerContainsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "VaLuE"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "other"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"header": ["X-Specific", "value"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId1)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "limit": 256,
+           |                "ids": ["${messageId1.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def headerShouldRejectWhenMoreThanTwoItems(server: GuiceJamesServer): Unit = 
{
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    val messageId1: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "value"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId2: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+    val messageId3: MessageId = mailboxProbe
+      .appendMessage(BOB.asString, inbox(BOB),
+        AppendCommand.from(
+          Message.Builder
+            .of
+            .addField(new RawField("X-Specific", "other"))
+            .setSubject("test")
+            .setBody("testmail", StandardCharsets.UTF_8)
+            .build))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail",
+         |    "urn:apache:james:params:jmap:mail:shares"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {"header": ["X-Specific", "value", "invalid"]}
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(s"""{
+                      |    "sessionState": "75128aab4b1b",
+                      |    "methodResponses": [
+                      |        [
+                      |            "error",
+                      |            {
+                      |                "type": "invalidArguments",
+                      |                "description": 
"{\\"errors\\":[{\\"path\\":\\"obj.filter.header\\",\\"messages\\":[\\"header 
filter needs to be an array of one or two strings\\"]}]}"
+                      |            },
+                      |            "c1"
+                      |        ]
+                      |    ]
+                      |}""".stripMargin)
+  }
+
+  @Test
   def hasAttachmentShouldKeepMessageWithoutAttachmentWhenFalse(server: 
GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
@@ -1645,46 +2027,6 @@ trait EmailQueryMethodContract {
        """)
   }
 
-  @Test
-  def listMailsShouldReturnUnsupportedFilterWhenHeaderFilter(): Unit = {
-    val request =
-      s"""{
-         |  "using": [
-         |    "urn:ietf:params:jmap:core",
-         |    "urn:ietf:params:jmap:mail"],
-         |  "methodCalls": [[
-         |    "Email/query",
-         |    {
-         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "filter" : {
-         |        "header": ["header1", "header2"]
-         |      }
-         |    },
-         |    "c1"]]
-         |}""".stripMargin
-
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
-
-    assertThatJson(response)
-      .inPath("$.methodResponses[0][1]")
-      .isEqualTo(s"""
-       {
-          "type": "unsupportedFilter",
-          "description": "The filter header is syntactically valid, but the 
server cannot process it. If the filter was the result of a user’s search 
input, the client SHOULD suggest that the user simplify their search."
-       }
-       """)
-  }
-
   @ParameterizedTest
   @ValueSource(strings = Array(
     "true",
diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/message.mdown 
b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/message.mdown
index 175ccfd..28a62aa 100644
--- a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/message.mdown
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/message.mdown
@@ -741,7 +741,6 @@ These properties are not supported yet for filtering:
 - noneInThreadHaveKeyword
 - text
 - body
-- header
 </aside>
 
 The exact semantics for matching `String` fields is **deliberately not 
defined** to allow for flexibility in indexing implementation, subject to the 
following:
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
index 84b90af..f137959 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
@@ -20,9 +20,8 @@
 package org.apache.james.jmap.json
 
 import javax.inject.Inject
-import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Anchor, 
AnchorOffset, Bcc, Body, Cc, CollapseThreads, From, FromSortProperty, 
HasKeywordSortProperty, Header, SizeSortProperty, 
SomeInThreadHaveKeywordSortProperty, Subject, SubjectSortProperty, Text, To, 
ToSortProperty, HasAttachment, Collation, Comparator, EmailQueryRequest, 
EmailQueryResponse, FilterCondition, IsAscending, ReceivedAtSortProperty, 
SentAtSortProperty, SortProperty}
-import org.apache.james.jmap.model.{CanCalculateChanges, LimitUnparsed, 
PositionUnparsed, QueryState}
-import org.apache.james.jmap.model.{AccountId, Keyword}
+import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Anchor, 
AnchorOffset, Bcc, Body, Cc, CollapseThreads, Collation, Comparator, 
EmailQueryRequest, EmailQueryResponse, FilterCondition, From, FromSortProperty, 
HasAttachment, HasKeywordSortProperty, Header, HeaderContains, HeaderExist, 
IsAscending, ReceivedAtSortProperty, SentAtSortProperty, SizeSortProperty, 
SomeInThreadHaveKeywordSortProperty, SortProperty, Subject, 
SubjectSortProperty, Text, To, ToSortProperty}
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, PositionUnparsed, QueryState}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
 
@@ -56,7 +55,23 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: 
MailboxId.Factory) {
   private implicit val ccReads: Reads[Cc] = Json.valueReads[Cc]
   private implicit val bccReads: Reads[Bcc] = Json.valueReads[Bcc]
   private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject]
-  private implicit val headerReads: Reads[Header] = Json.valueReads[Header]
+  private implicit val headerReads: Reads[Header] = {
+    case array: JsArray if array.value.length == 1 =>
+      extractString(array.value.head)
+          .fold[JsResult[Header]](e => e, name => JsSuccess(HeaderExist(name)))
+    case array: JsArray if array.value.length == 2 =>
+      extractString(array.value.head)
+          .flatMap(name => extractString(array.value.last)
+            .map(value => (name, value)))
+          .fold(e => e, {
+            case (name, value) => JsSuccess(HeaderContains(name, value))
+          })
+    case _ => JsError("header filter needs to be an array of one or two 
strings")
+  }
+  private def extractString(jsValue: JsValue): Either[JsResult[Header], 
String] = jsValue match {
+    case JsString(value) => Right(value)
+    case _ => Left(JsError("header filter needs to be an array of one or two 
strings"))
+  }
   private implicit val bodyReads: Reads[Body] = Json.valueReads[Body]
   private implicit val filterConditionReads: Reads[FilterCondition] = 
Json.reads[FilterCondition]
   private implicit val limitUnparsedReads: Reads[LimitUnparsed] = 
Json.valueReads[LimitUnparsed]
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 8ca075a..bb27528 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
@@ -38,7 +38,9 @@ case class To(value: String) extends AnyVal
 case class Cc(value: String) extends AnyVal
 case class Bcc(value: String) extends AnyVal
 case class Body(value: String) extends AnyVal
-case class Header(value: String) extends AnyVal
+sealed trait Header
+case class HeaderExist(name: String) extends Header
+case class HeaderContains(name: String, value: String) extends Header
 
 case class FilterCondition(inMailbox: Option[MailboxId],
                            inMailboxOtherThan: Option[Seq[MailboxId]],
@@ -58,7 +60,7 @@ case class FilterCondition(inMailbox: Option[MailboxId],
                            cc: Option[Cc],
                            bcc: Option[Bcc],
                            subject: Option[Subject],
-                           header: Option[Set[Header]],
+                           header: Option[Header],
                            body: Option[Body])
 
 case class EmailQueryRequest(accountId: AccountId,
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
index 94d2344..30a1022 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
@@ -21,7 +21,7 @@ package org.apache.james.jmap.utils.search
 import java.util.Date
 
 import cats.implicits._
-import org.apache.james.jmap.mail.{EmailQueryRequest, 
UnsupportedFilterException}
+import org.apache.james.jmap.mail.{EmailQueryRequest, HeaderContains, 
HeaderExist, UnsupportedFilterException}
 import org.apache.james.jmap.model.CapabilityIdentifier
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.mailbox.MailboxSession
@@ -220,7 +220,8 @@ object MailboxFilter {
   case object Header extends QueryFilter {
     override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] =
       request.filter.flatMap(_.header) match {
-        case Some(_) => Left(UnsupportedFilterException("header"))
+        case Some(HeaderExist(name)) => 
Right(builder.andCriteria(SearchQuery.headerExists(name)))
+        case Some(HeaderContains(name, value)) => 
Right(builder.andCriteria(SearchQuery.headerContains(name, value)))
         case None => Right(builder)
       }
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to