You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/10/02 08:48:39 UTC
[james-project] 03/08: JAMES-3390 Reject nested inMailbox &
inMailboxOtherThan
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit b69ef78a19737af3f81d1538cb6008ae6e55b850
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Sep 30 09:57:01 2020 +0700
JAMES-3390 Reject nested inMailbox & inMailboxOtherThan
---
.../contract/EmailQueryMethodContract.scala | 126 +++++++++++++++++++++
.../doc/specs/spec/mail/message.mdown | 3 +
.../org/apache/james/jmap/mail/EmailQuery.scala | 28 ++++-
.../org/apache/james/jmap/method/Method.scala | 6 +-
.../james/jmap/utils/search/MailboxFilter.scala | 39 +++----
5 files changed, 181 insertions(+), 21 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 9ab093d..8cc4b2a 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
@@ -5039,6 +5039,132 @@ trait EmailQueryMethodContract {
}
@Test
+ def inMailboxShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
+ val message: Message = buildTestMessage
+ val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
+ .getMessageId
+
+ val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
+ .getMessageId
+
+ val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "filter" : {
+ | "operator": "AND",
+ | "conditions": [
+ | { "inMailbox": "${mailboxId.serialize()}" }, { "hasKeyword": "another_custom" }
+ | ]
+ | }
+ | },
+ | "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": "unsupportedFilter",
+ | "description": "Nested inMailbox filters are not supported"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def inMailboxOtherThanShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
+ val message: Message = buildTestMessage
+ val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
+ .getMessageId
+
+ val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
+ .getMessageId
+
+ val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "filter" : {
+ | "operator": "AND",
+ | "conditions": [
+ | { "inMailboxOtherThan": ["${mailboxId.serialize()}"] }, { "hasKeyword": "another_custom" }
+ | ]
+ | }
+ | },
+ | "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": "unsupportedFilter",
+ | "description": "Nested inMailboxOtherThan filter are not supported"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
def emailQueryFilterByTextShouldIgnoreAttachmentContent(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
server.getProbe(classOf[MailboxProbeImpl])
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 28a62aa..52306f6 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,6 +741,9 @@ These properties are not supported yet for filtering:
- noneInThreadHaveKeyword
- text
- body
+
+Also please note that inMailbox and inMailboxOtherThan filters are rejected
+when nested in operator. They need to be specified in a filter condition.
</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/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 7d678a8..2badc57 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
@@ -19,6 +19,7 @@
package org.apache.james.jmap.mail
+import cats.implicits._
import org.apache.james.jmap.mail.Email.Size
import org.apache.james.jmap.mail.IsAscending.ASCENDING
import org.apache.james.jmap.method.WithAccountId
@@ -31,6 +32,7 @@ import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
case class UnsupportedSortException(unsupportedSort: String) extends UnsupportedOperationException
case class UnsupportedFilterException(unsupportedFilter: String) extends UnsupportedOperationException
+case class UnsupportedNestingException(message: String) extends UnsupportedOperationException
case class UnsupportedRequestParameterException(unsupportedParam: String) extends UnsupportedOperationException
sealed trait FilterQuery
@@ -79,7 +81,31 @@ case class EmailQueryRequest(accountId: AccountId,
comparator: Option[Set[Comparator]],
collapseThreads: Option[CollapseThreads],
anchor: Option[Anchor],
- anchorOffset: Option[AnchorOffset]) extends WithAccountId
+ anchorOffset: Option[AnchorOffset]) extends WithAccountId {
+ val validatedFilter: Either[UnsupportedNestingException, Option[FilterQuery]] =
+ filter.map(validateFilter)
+ .sequence
+ .map(_.flatten)
+
+ private def validateFilter(filter: FilterQuery): Either[UnsupportedNestingException, Option[FilterQuery]] = filter match {
+ case filterCondition: FilterCondition => scala.Right(Some(filterCondition))
+ case filterOperator: FilterOperator => rejectMailboxFilters(filterOperator)
+ }
+
+ private def rejectMailboxFilters(filter: FilterQuery): Either[UnsupportedNestingException, Option[FilterQuery]] =
+ filter match {
+ case filterCondition: FilterCondition if filterCondition.inMailbox.isDefined =>
+ scala.Left(UnsupportedNestingException("Nested inMailbox filters are not supported"))
+ case filterCondition: FilterCondition if filterCondition.inMailboxOtherThan.isDefined =>
+ scala.Left(UnsupportedNestingException("Nested inMailboxOtherThan filter are not supported"))
+ case filterCondition: FilterCondition => scala.Right(Some(filterCondition))
+ case filterOperator: FilterOperator => filterOperator.conditions
+ .toList
+ .map(rejectMailboxFilters)
+ .sequence
+ .map(_ => Some(filterOperator))
+ }
+}
sealed trait SortProperty {
def toSortClause: Either[UnsupportedSortException, SortClause]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
index 5ee14cb..1322f1e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
@@ -20,7 +20,7 @@ package org.apache.james.jmap.method
import org.apache.james.jmap.http.SessionSupplier
-import org.apache.james.jmap.mail.{UnsupportedFilterException, UnsupportedRequestParameterException, UnsupportedSortException}
+import org.apache.james.jmap.mail.{UnsupportedFilterException, UnsupportedNestingException, UnsupportedRequestParameterException, UnsupportedSortException}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.Invocation.MethodName
import org.apache.james.jmap.model.{AccountId, Capabilities, ErrorCode, Invocation, Session}
@@ -71,6 +71,10 @@ trait MethodRequiringAccountId[REQUEST <: WithAccountId] extends Method {
ErrorCode.UnsupportedFilter,
s"The filter ${e.unsupportedFilter} 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.",
invocation.invocation.methodCallId), invocation.processingContext))
+ case e: UnsupportedNestingException => SMono.just(InvocationWithContext(Invocation.error(
+ ErrorCode.UnsupportedFilter,
+ description = e.message,
+ invocation.invocation.methodCallId), invocation.processingContext))
case e: IllegalArgumentException => SMono.just(InvocationWithContext(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.invocation.methodCallId), invocation.processingContext))
case e: MailboxNotFoundException => SMono.just(InvocationWithContext(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.invocation.methodCallId), invocation.processingContext))
case e: Throwable => SMono.raiseError(e)
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 1dcc2f4..754e590 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
@@ -100,9 +100,10 @@ object MailboxFilter {
}
object QueryFilter {
- def buildQuery(request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] =
- request.filter.map(toCriterion)
- .getOrElse(Right(Nil))
+ def buildQuery(request: EmailQueryRequest): Either[UnsupportedOperationException, SearchQuery.Builder] =
+ request.validatedFilter.flatMap(
+ _.map(toCriterion)
+ .getOrElse(Right(Nil)))
.map(criteria => new SearchQuery.Builder().andCriteria(criteria.asJava))
def toCriterion(filterQuery: FilterQuery): Either[UnsupportedFilterException, List[Criterion]] =
@@ -121,7 +122,7 @@ object MailboxFilter {
val strictlyBefore = SearchQuery.internalDateBefore(Date.from(before.asUTC.toInstant), Second)
val sameDate = SearchQuery.internalDateOn(Date.from(before.asUTC.toInstant), Second)
Right(List(SearchQuery.or(strictlyBefore, sameDate)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -131,7 +132,7 @@ object MailboxFilter {
case Some(after) =>
val strictlyAfter = new InternalDateCriterion(new DateOperator(DateComparator.AFTER, Date.from(after.asUTC.toInstant), DateResolution.Second))
Right(List(strictlyAfter))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -139,7 +140,7 @@ object MailboxFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.hasAttachment match {
case Some(hasAttachment) => Right(List(SearchQuery.hasAttachment(hasAttachment.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -150,7 +151,7 @@ object MailboxFilter {
Right(List(SearchQuery.or(
SearchQuery.sizeGreaterThan(minSize.value),
SearchQuery.sizeEquals(minSize.value))))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -159,7 +160,7 @@ object MailboxFilter {
filterCondition.maxSize match {
case Some(maxSize) =>
Right(List(SearchQuery.sizeLessThan(maxSize.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -171,7 +172,7 @@ object MailboxFilter {
case Some(systemFlag) => Right(List(SearchQuery.flagIsSet(systemFlag)))
case None => Right(List(SearchQuery.flagIsSet(keyword.flagName)))
}
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object NotKeyWord extends ConditionFilter {
@@ -182,28 +183,28 @@ object MailboxFilter {
case Some(systemFlag) => Right(List(SearchQuery.flagIsUnSet(systemFlag)))
case None => Right(List(SearchQuery.flagIsUnSet(keyword.flagName)))
}
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object AllInThreadHaveKeyword extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.allInThreadHaveKeyword match {
case Some(_) => Left(UnsupportedFilterException("allInThreadHaveKeyword"))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object NoneInThreadHaveKeyword extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.noneInThreadHaveKeyword match {
case Some(_) => Left(UnsupportedFilterException("noneInThreadHaveKeyword"))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object SomeInThreadHaveKeyword extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.someInThreadHaveKeyword match {
case Some(_) => Left(UnsupportedFilterException("someInThreadHaveKeyword"))
- case None => Right(List())
+ case None => Right(Nil)
}
}
@@ -227,35 +228,35 @@ object MailboxFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.from match {
case Some(from) => Right(List(SearchQuery.address(AddressType.From, from.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object To extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.to match {
case Some(to) => Right(List(SearchQuery.address(AddressType.To, to.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object Cc extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.cc match {
case Some(cc) => Right(List(SearchQuery.address(AddressType.Cc, cc.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object Bcc extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.bcc match {
case Some(bcc) => Right(List(SearchQuery.address(AddressType.Bcc, bcc.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object Subject extends ConditionFilter {
override def toQuery(filterCondition: FilterCondition): Either[UnsupportedFilterException, List[Criterion]] =
filterCondition.subject match {
case Some(subject) => Right(List(SearchQuery.headerContains("Subject", subject.value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
case object Header extends ConditionFilter {
@@ -263,7 +264,7 @@ object MailboxFilter {
filterCondition.header match {
case Some(HeaderExist(name)) => Right(List(SearchQuery.headerExists(name)))
case Some(HeaderContains(name, value)) => Right(List(SearchQuery.headerContains(name, value)))
- case None => Right(List())
+ case None => Right(Nil)
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org