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
The following commit(s) were added to refs/heads/master by this push: new d413f8fcc4 [FIX] JMAP: Set configurable limits for /get /set objects (#2096) d413f8fcc4 is described below commit d413f8fcc4030af6952a90b47f2041eebfd0c94b Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Tue Mar 12 06:57:58 2024 +0100 [FIX] JMAP: Set configurable limits for /get /set objects (#2096) --- .../docs/modules/ROOT/pages/configure/jmap.adoc | 6 ++++ .../org/apache/james/jmap/draft/JMAPModule.java | 2 +- .../org/apache/james/jmap/core/Capabilities.scala | 2 +- .../org/apache/james/jmap/core/Capability.scala | 8 ++--- .../james/jmap/core/JmapRfc8621Configuration.scala | 14 ++++++++- .../james/jmap/core/PushSubscriptionGet.scala | 6 ++-- .../james/jmap/core/PushSubscriptionSet.scala | 6 ++-- .../apache/james/jmap/delegation/DelegateGet.scala | 6 ++-- .../apache/james/jmap/delegation/DelegateSet.scala | 6 ++-- .../jmap/delegation/DelegatedAccountGet.scala | 6 ++-- .../jmap/delegation/DelegatedAccountSet.scala | 6 ++-- .../scala/org/apache/james/jmap/mail/Email.scala | 11 ++----- .../org/apache/james/jmap/mail/EmailGet.scala | 20 +++++++++++-- .../org/apache/james/jmap/mail/EmailImport.scala | 14 +++++++-- .../org/apache/james/jmap/mail/EmailParse.scala | 14 +++++++-- .../org/apache/james/jmap/mail/EmailSet.scala | 6 ++-- .../james/jmap/mail/EmailSubmissionSet.scala | 27 +++++++++++++---- .../org/apache/james/jmap/mail/IdentityGet.scala | 6 ++-- .../org/apache/james/jmap/mail/IdentitySet.scala | 6 ++-- .../org/apache/james/jmap/mail/MDNParse.scala | 10 +++---- .../scala/org/apache/james/jmap/mail/MDNSend.scala | 12 +++++--- .../org/apache/james/jmap/mail/MailboxGet.scala | 6 ++-- .../org/apache/james/jmap/mail/MailboxSet.scala | 6 ++-- .../scala/org/apache/james/jmap/mail/Quotas.scala | 6 ++-- .../scala/org/apache/james/jmap/mail/Thread.scala | 6 ++-- .../james/jmap/method/DelegateGetMethod.scala | 4 ++- .../james/jmap/method/DelegateSetMethod.scala | 4 ++- .../jmap/method/DelegatedAccountGetMethod.scala | 4 ++- .../jmap/method/DelegatedAccountSetMethod.scala | 4 ++- .../apache/james/jmap/method/EmailGetMethod.scala | 29 +++++++++--------- .../james/jmap/method/EmailImportMethod.scala | 8 +++-- .../james/jmap/method/EmailParseMethod.scala | 7 +++-- .../apache/james/jmap/method/EmailSetMethod.scala | 6 ++-- .../jmap/method/EmailSubmissionSetMethod.scala | 7 +++-- .../james/jmap/method/IdentityGetMethod.scala | 4 ++- .../james/jmap/method/IdentitySetMethod.scala | 5 +++- .../apache/james/jmap/method/MDNParseMethod.scala | 8 +++-- .../apache/james/jmap/method/MDNSendMethod.scala | 5 ++-- .../james/jmap/method/MailboxGetMethod.scala | 6 ++-- .../james/jmap/method/MailboxSetMethod.scala | 6 ++-- .../org/apache/james/jmap/method/Method.scala | 35 +++++++++++++++++++++- .../jmap/method/PushSubscriptionGetMethod.scala | 6 ++-- .../jmap/method/PushSubscriptionSetMethod.scala | 4 ++- .../apache/james/jmap/method/QuotaGetMethod.scala | 4 ++- .../apache/james/jmap/method/ThreadGetMethod.scala | 6 ++-- .../jmap/method/VacationResponseGetMethod.scala | 6 ++-- .../jmap/method/VacationResponseSetMethod.scala | 6 ++-- .../james/jmap/vacation/VacationResponseGet.scala | 6 ++-- .../james/jmap/vacation/VacationResponseSet.scala | 7 +++-- src/site/xdoc/server/config-jmap.xml | 6 ++++ 50 files changed, 291 insertions(+), 120 deletions(-) diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc index 4297eb1ad3..a1775ae4e5 100644 --- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc +++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmap.adoc @@ -105,6 +105,12 @@ This allows to prevent users from using some specific JMAP extensions. | email.get.full.max.size | Optional, default value is 5. The max number of items for EmailGet full reads. + +| get.max.size +| Optional, default value is 500. The max number of items for /get methods. + +| set.max.size +| Optional, default value is 500. The max number of items for /set methods. |=== == Wire tapping diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java index 151c86e751..b4339445f7 100644 --- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java +++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java @@ -173,7 +173,7 @@ public class JMAPModule extends AbstractModule { @ProvidesIntoSet CapabilityFactory coreCapability(JmapRfc8621Configuration configuration) { - return new CoreCapabilityFactory(configuration.maxUploadSize()); + return new CoreCapabilityFactory(configuration); } @ProvidesIntoSet diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala index 50144a6622..ef7939b733 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala @@ -26,7 +26,7 @@ import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier object DefaultCapabilities { @VisibleForTesting def supported(configuration: JmapRfc8621Configuration): Set[CapabilityFactory] = Set( - CoreCapabilityFactory(configuration.maxUploadSize), + CoreCapabilityFactory(configuration), MailCapabilityFactory(configuration), QuotaCapabilityFactory, JmapQuotaCapabilityFactory, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala index 94b2beefa5..658d5bcff9 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala @@ -106,17 +106,17 @@ trait CapabilityFactory { final case class CoreCapability(properties: CoreCapabilityProperties, identifier: CapabilityIdentifier = JMAP_CORE) extends Capability -final case class CoreCapabilityFactory(maxUploadSize: MaxSizeUpload) extends CapabilityFactory { +final case class CoreCapabilityFactory(configration: JmapRfc8621Configuration) extends CapabilityFactory { override def id(): CapabilityIdentifier = JMAP_CORE override def create(urlPrefixes: UrlPrefixes): Capability = CoreCapability(CoreCapabilityProperties( - maxUploadSize, + configration.maxUploadSize, MaxConcurrentUpload(4L), MaxSizeRequest(10_000_000L), MaxConcurrentRequests(4L), MaxCallsInRequest(16L), - MaxObjectsInGet(500L), - MaxObjectsInSet(500L), + configration.maxObjectsInGet, + configration.maxObjectsInSet, collationAlgorithms = List("i;unicode-casemap"))) } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala index 330b11ecaf..0c66fb16e7 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala @@ -25,7 +25,7 @@ import java.util.Optional import com.google.common.collect.ImmutableList import org.apache.commons.configuration2.Configuration import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier -import org.apache.james.jmap.core.JmapRfc8621Configuration.{JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT, JMAP_UPLOAD_QUOTA_LIMIT_DEFAULT, MAX_SIZE_ATTACHMENTS_PER_MAIL_DEFAULT, UPLOAD_LIMIT_DEFAULT} +import org.apache.james.jmap.core.JmapRfc8621Configuration.{JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT, JMAP_MAX_OBJECT_IN_GET, JMAP_MAX_OBJECT_IN_SET, JMAP_UPLOAD_QUOTA_LIMIT_DEFAULT, MAX_SIZE_ATTACHMENTS_PER_MAIL_DEFAULT, UPLOAD_LIMIT_DEFAULT} import org.apache.james.jmap.pushsubscription.PushClientConfiguration import org.apache.james.util.Size @@ -46,6 +46,8 @@ object JmapConfigProperties { val DISABLED_CAPABILITIES: String = "disabled.capabilities" val JMAP_UPLOAD_QUOTA_LIMIT_PROPERTY: String = "upload.quota.limit" val JMAP_EMAIL_GET_FULL_MAX_SIZE_PROPERTY: String = "email.get.full.max.size" + val JMAP_GET_MAX_SIZE_PROPERTY: String = "get.max.size" + val JMAP_SET_MAX_SIZE_PROPERTY: String = "set.max.size" } object JmapRfc8621Configuration { @@ -56,6 +58,8 @@ object JmapRfc8621Configuration { val MAX_SIZE_ATTACHMENTS_PER_MAIL_DEFAULT: MaxSizeAttachmentsPerEmail = MaxSizeAttachmentsPerEmail.of(Size.of(20_000_000L, Size.Unit.B)).get val JMAP_UPLOAD_QUOTA_LIMIT_DEFAULT: JmapUploadQuotaLimit = JmapUploadQuotaLimit.of(Size.of(200L, Size.Unit.M)).get val JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT: JmapEmailGetFullMaxSize = JmapEmailGetFullMaxSize(UnsignedInt.liftOrThrow(5)) + val JMAP_MAX_OBJECT_IN_GET: MaxObjectsInGet = MaxObjectsInGet(UnsignedInt.liftOrThrow(500)) + val JMAP_MAX_OBJECT_IN_SET: MaxObjectsInSet = MaxObjectsInSet(UnsignedInt.liftOrThrow(500)) val LOCALHOST_CONFIGURATION: JmapRfc8621Configuration = JmapRfc8621Configuration( urlPrefixString = URL_PREFIX_DEFAULT, @@ -83,6 +87,12 @@ object JmapRfc8621Configuration { jmapEmailGetFullMaxSize = Option(configuration.getLong(JMAP_EMAIL_GET_FULL_MAX_SIZE_PROPERTY, null)) .map(value => JmapEmailGetFullMaxSize(UnsignedInt.liftOrThrow(value))) .getOrElse(JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT), + maxObjectsInGet = Option(configuration.getLong(JMAP_GET_MAX_SIZE_PROPERTY, null)) + .map(value => MaxObjectsInGet(UnsignedInt.liftOrThrow(value))) + .getOrElse(JMAP_MAX_OBJECT_IN_GET), + maxObjectsInSet = Option(configuration.getLong(JMAP_SET_MAX_SIZE_PROPERTY, null)) + .map(value => MaxObjectsInSet(UnsignedInt.liftOrThrow(value))) + .getOrElse(JMAP_MAX_OBJECT_IN_SET), maxTimeoutSeconds = Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_TIMEOUT_SECONDS_PROPERTY, null)).map(Integer2int).toScala, maxConnections = Optional.ofNullable(configuration.getInteger(WEB_PUSH_MAX_CONNECTIONS_PROPERTY, null)).map(Integer2int).toScala, preventServerSideRequestForgery = Optional.ofNullable(configuration.getBoolean(WEB_PUSH_PREVENT_SERVER_SIDE_REQUEST_FORGERY, null)).orElse(true), @@ -103,6 +113,8 @@ case class JmapRfc8621Configuration(urlPrefixString: String, maxSizeAttachmentsPerEmail: MaxSizeAttachmentsPerEmail = MAX_SIZE_ATTACHMENTS_PER_MAIL_DEFAULT, jmapUploadQuotaLimit: JmapUploadQuotaLimit = JMAP_UPLOAD_QUOTA_LIMIT_DEFAULT, jmapEmailGetFullMaxSize: JmapEmailGetFullMaxSize = JMAP_EMAIL_GET_FULL_MAX_SIZE_DEFAULT, + maxObjectsInGet: MaxObjectsInGet = JMAP_MAX_OBJECT_IN_GET, + maxObjectsInSet: MaxObjectsInSet = JMAP_MAX_OBJECT_IN_SET, maxTimeoutSeconds: Option[Int] = None, maxConnections: Option[Int] = None, authenticationStrategies: Option[java.util.List[String]] = None, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionGet.scala index 6cf7ed6ad2..4c87aa6160 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionGet.scala @@ -23,7 +23,7 @@ import cats.implicits._ import eu.timepit.refined.auto._ import eu.timepit.refined.types.string.NonEmptyString import org.apache.james.jmap.api.model.{DeviceClientId, PushSubscription, PushSubscriptionId, TypeName, VerificationCode} -import org.apache.james.jmap.method.WithoutAccountId +import org.apache.james.jmap.method.{GetRequest, ValidableRequest, WithoutAccountId} case class Ids(value: List[UnparsedPushSubscriptionId]) { def validate: Either[IllegalArgumentException, List[PushSubscriptionId]] = @@ -36,7 +36,9 @@ object PushSubscriptionGet { } case class PushSubscriptionGetRequest(ids: Option[Ids], - properties: Option[Properties]) extends WithoutAccountId { + properties: Option[Properties]) extends WithoutAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) + def validateProperties: Either[IllegalArgumentException, Properties] = properties match { case None => Right(PushSubscriptionGet.allowedProperties) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala index d34a1add56..9e16c178b6 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala @@ -34,14 +34,16 @@ import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.Properties.toProperties import org.apache.james.jmap.core.SetError.SetErrorDescription import org.apache.james.jmap.mail.{InvalidPropertyException, InvalidUpdateException, PatchUpdateValidationException, UnsupportedPropertyUpdatedException} -import org.apache.james.jmap.method.WithoutAccountId +import org.apache.james.jmap.method.{SetRequest, WithoutAccountId} import play.api.libs.json.{JsArray, JsObject, JsString, JsValue} import scala.util.{Failure, Success, Try} case class PushSubscriptionSetRequest(create: Option[Map[PushSubscriptionCreationId, JsObject]], update: Option[Map[UnparsedPushSubscriptionId, PushSubscriptionPatchObject]], - destroy: Option[Seq[UnparsedPushSubscriptionId]]) extends WithoutAccountId + destroy: Option[Seq[UnparsedPushSubscriptionId]]) extends WithoutAccountId with SetRequest { + override def idCount: Int = create.map(_.size).getOrElse(0) + update.map(_.size).getOrElse(0) + destroy.map(_.size).getOrElse(0) +} case class PushSubscriptionCreationId(id: Id) { def serialise: String = id.value diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateGet.scala index e31ff8c4c4..dd266ec453 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateGet.scala @@ -25,7 +25,7 @@ import eu.timepit.refined.auto._ import org.apache.james.core.Username import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.{AccountId, Id, Properties} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import scala.util.Try @@ -50,7 +50,9 @@ case class DelegateIds(value: List[UnparsedDelegateId]) case class DelegateGetRequest(accountId: AccountId, ids: Option[DelegateIds], - properties: Option[Properties]) extends WithAccountId + properties: Option[Properties]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) +} case class Delegate(id: DelegationId, username: Username) { def delegationIdAsId(): Id = Id.validate(id.serialize) match { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala index 899ba8cbea..45c6b2edfb 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegateSet.scala @@ -22,7 +22,7 @@ package org.apache.james.jmap.delegation import org.apache.james.core.Username import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.{AccountId, Id, SetError, UuidState} -import org.apache.james.jmap.method.{WithAccountId, standardError} +import org.apache.james.jmap.method.{SetRequest, WithAccountId, standardError} import play.api.libs.json.{JsObject, JsPath, JsonValidationError} case class DelegateCreationId(id: Id) { @@ -31,7 +31,9 @@ case class DelegateCreationId(id: Id) { case class DelegateSetRequest(accountId: AccountId, create: Option[Map[DelegateCreationId, JsObject]], - destroy: Option[Seq[UnparsedDelegateId]]) extends WithAccountId + destroy: Option[Seq[UnparsedDelegateId]]) extends WithAccountId with SetRequest { + override def idCount: Int = create.map(_.size).getOrElse(0) + destroy.map(_.size).getOrElse(0) +} case class DelegateCreationRequest(username: Username) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountGet.scala index b387e572af..e72d93e108 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountGet.scala @@ -22,7 +22,7 @@ package org.apache.james.jmap.delegation import eu.timepit.refined.auto._ import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.{AccountId, Properties} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} object DelegatedAccountGet { val allProperties: Properties = Properties("id", "username") @@ -33,7 +33,9 @@ object DelegatedAccountGet { case class DelegatedAccountGetRequest(accountId: AccountId, ids: Option[DelegateIds], - properties: Option[Properties]) extends WithAccountId + properties: Option[Properties]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) +} case class DelegatedAccountNotFound(value: Set[UnparsedDelegateId]) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountSet.scala index 330162aac1..5085ffc367 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/delegation/DelegatedAccountSet.scala @@ -1,10 +1,12 @@ package org.apache.james.jmap.delegation import org.apache.james.jmap.core.{AccountId, SetError, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{SetRequest, WithAccountId} case class DelegatedAccountSetRequest(accountId: AccountId, - destroy: Option[Seq[UnparsedDelegateId]]) extends WithAccountId + destroy: Option[Seq[UnparsedDelegateId]]) extends WithAccountId with SetRequest { + override def idCount: Int = destroy.map(_.size).getOrElse(0) +} case class DelegatedAccountSetResponse(accountId: AccountId, oldState: Option[UuidState], diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala index 50f2c7c096..b7de62a699 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala @@ -36,7 +36,7 @@ import org.apache.james.jmap.api.model.Size.{Size, sanitizeSize} import org.apache.james.jmap.api.model.{EmailAddress, Preview} import org.apache.james.jmap.api.projections.{MessageFastViewPrecomputedProperties, MessageFastViewProjection} import org.apache.james.jmap.core.Id.{Id, IdConstraint} -import org.apache.james.jmap.core.{Properties, UTCDate} +import org.apache.james.jmap.core.{JmapRfc8621Configuration, Properties, UTCDate} import org.apache.james.jmap.mail.BracketHeader.sanitize import org.apache.james.jmap.mail.EmailFullViewFactory.extractBodyValues import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes @@ -44,7 +44,7 @@ import org.apache.james.jmap.mail.EmailHeaderName.{ADDRESSES_NAMES, DATE, MESSAG import org.apache.james.jmap.mail.FastViewWithAttachmentsMetadataReadLevel.supportedByFastViewWithAttachments import org.apache.james.jmap.mail.KeywordsFactory.LENIENT_KEYWORDS_FACTORY import org.apache.james.jmap.method.ZoneIdProvider -import org.apache.james.jmap.mime4j.{JamesBodyDescriptorBuilder, AvoidBinaryBodyBufferingBodyFactory} +import org.apache.james.jmap.mime4j.{AvoidBinaryBodyBufferingBodyFactory, JamesBodyDescriptorBuilder} import org.apache.james.mailbox.model.FetchGroup.{FULL_CONTENT, HEADERS, HEADERS_WITH_ATTACHMENTS_METADATA, MINIMAL} import org.apache.james.mailbox.model.{FetchGroup, MailboxId, MessageId, MessageResult, ThreadId => JavaThreadId} import org.apache.james.mailbox.{MailboxSession, MessageIdManager} @@ -118,13 +118,6 @@ object Email { } } - def validateIdsSize(request: EmailGetRequest, maxSize: Long, chain: Properties): Either[Exception, Properties] = - if (EmailGetRequest.readLevel(request).equals(FullReadLevel) && request.ids.exists(_.value.size > maxSize)) { - Left(RequestTooLargeException(s"Too many items in an email read at level FULL. Got ${request.ids.get.value.size} items instead of maximum ${maxSize}.")) - } else { - scala.Right(chain) - } - def asUnparsed(messageId: MessageId): Try[UnparsedEmailId] = refined.refineV[IdConstraint](messageId.serialize()) match { case Left(e) => Failure(new IllegalArgumentException(e)) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala index db9753c497..74a5e97e20 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala @@ -26,10 +26,10 @@ import eu.timepit.refined.auto._ import eu.timepit.refined.numeric.NonNegative import eu.timepit.refined.types.string.NonEmptyString import org.apache.james.jmap.api.change.Limit -import org.apache.james.jmap.core.{AccountId, Properties, UuidState} +import org.apache.james.jmap.core.{AccountId, JmapRfc8621Configuration, Properties, UuidState} import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes import org.apache.james.jmap.mail.EmailHeaders.SPECIFIC_HEADER_PREFIX -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import org.apache.james.mailbox.model.MessageId import org.apache.james.mime4j.stream.Field @@ -88,7 +88,21 @@ case class EmailGetRequest(accountId: AccountId, fetchHTMLBodyValues: Option[FetchHTMLBodyValues], maxBodyValueBytes: Option[MaxBodyValueBytes], properties: Option[Properties], - bodyProperties: Option[Properties]) extends WithAccountId + bodyProperties: Option[Properties]) extends WithAccountId with GetRequest { + + override def idCount: Option[Int] = ids.map(_.value).map(_.size) + + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, EmailGetRequest] = + if (EmailGetRequest.readLevel(this).equals(FullReadLevel) && ids.exists(_.value.size > configuration.jmapEmailGetFullMaxSize.asLong())) { + Left(RequestTooLargeException(s"Too many items in an email read at level FULL. " + + s"Got ${ids.get.value.size} items instead of maximum ${configuration.jmapEmailGetFullMaxSize.asLong()}.")) + } else if (ids.exists(_.value.size > configuration.maxObjectsInGet.value.value)) { + Left(RequestTooLargeException(s"Too many items in an email read at level ${EmailGetRequest.readLevel(this)}. " + + s"Got ${ids.get.value.size} items instead of maximum ${configuration.maxObjectsInGet.value}.")) + } else { + scala.Right(this) + } +} case class EmailNotFound(value: Set[UnparsedEmailId]) { def merge(other: EmailNotFound): EmailNotFound = EmailNotFound(this.value ++ other.value) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailImport.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailImport.scala index 44d644f588..b21f489882 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailImport.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailImport.scala @@ -21,13 +21,21 @@ package org.apache.james.jmap.mail import java.time.ZonedDateTime -import org.apache.james.jmap.core.{AccountId, SetError, UTCDate, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.core.{AccountId, JmapRfc8621Configuration, SetError, UTCDate, UuidState} +import org.apache.james.jmap.method.{ValidableRequest, WithAccountId} import org.apache.james.mailbox.model.MailboxId import reactor.core.scala.publisher.SMono case class EmailImportRequest(accountId: AccountId, - emails: Map[EmailCreationId, EmailImport]) extends WithAccountId + emails: Map[EmailCreationId, EmailImport]) extends WithAccountId with ValidableRequest { + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, ValidableRequest] = + if (emails.size > configuration.jmapEmailGetFullMaxSize.asLong()) { + Left(RequestTooLargeException(s"Too many items in an email parse request. " + + s"Got ${emails.size} items instead of maximum ${configuration.jmapEmailGetFullMaxSize.asLong()}.")) + } else { + scala.Right(this) + } +} case class EmailImport(blobId: BlobId, mailboxIds: MailboxIds, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailParse.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailParse.scala index 6c57d26039..035013bb9e 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailParse.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailParse.scala @@ -19,10 +19,10 @@ package org.apache.james.jmap.mail -import org.apache.james.jmap.core.{AccountId, Properties} +import org.apache.james.jmap.core.{AccountId, JmapRfc8621Configuration, Properties} import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes import org.apache.james.jmap.mail.MDNParse.UnparsedBlobId -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{ValidableRequest, WithAccountId} case class EmailParseRequest(accountId: AccountId, @@ -32,7 +32,15 @@ case class EmailParseRequest(accountId: AccountId, fetchHTMLBodyValues: Option[FetchHTMLBodyValues], maxBodyValueBytes: Option[MaxBodyValueBytes], properties: Option[Properties], - bodyProperties: Option[Properties]) extends WithAccountId + bodyProperties: Option[Properties]) extends WithAccountId with ValidableRequest { + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, EmailParseRequest] = + if (blobIds.value.size > configuration.jmapEmailGetFullMaxSize.asLong()) { + Left(RequestTooLargeException(s"Too many items in an email parse request. " + + s"Got ${blobIds.value.size} items instead of maximum ${configuration.jmapEmailGetFullMaxSize.asLong()}.")) + } else { + scala.Right(this) + } +} object EmailParseResults { def notFound(blobId: UnparsedBlobId): EmailParseResults = EmailParseResults(None, Some(EmailParseNotFound(Set(blobId))), None) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala index 5a018ceccf..2509fb00db 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala @@ -32,7 +32,7 @@ import org.apache.james.jmap.core.Id.{Id, IdConstraint} import org.apache.james.jmap.core.{AccountId, SetError, UTCDate, UuidState} import org.apache.james.jmap.mail.Disposition.INLINE import org.apache.james.jmap.mail.EmailCreationRequest.KEYWORD_DRAFT -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{SetRequest, WithAccountId} import org.apache.james.jmap.routes.{Blob, BlobResolvers} import org.apache.james.mailbox.MailboxSession import org.apache.james.mailbox.model.{Cid, MessageId} @@ -421,7 +421,9 @@ case class DestroyIds(value: Seq[UnparsedMessageId]) case class EmailSetRequest(accountId: AccountId, create: Option[Map[EmailCreationId, JsObject]], update: Option[Map[UnparsedMessageId, JsObject]], - destroy: Option[DestroyIds]) extends WithAccountId + destroy: Option[DestroyIds]) extends WithAccountId with SetRequest { + override def idCount: Int = create.map(_.size).getOrElse(0) + update.map(_.size).getOrElse(0) + destroy.map(_.value).map(_.size).getOrElse(0) +} case class EmailSetResponse(accountId: AccountId, oldState: Option[UuidState], diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala index 95b67e2983..26e34cbe7e 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala @@ -28,8 +28,8 @@ import org.apache.james.core.MailAddress import org.apache.james.jmap.core.Id.{Id, IdConstraint} import org.apache.james.jmap.core.Properties.toProperties import org.apache.james.jmap.core.SetError.SetErrorDescription -import org.apache.james.jmap.core.{AccountId, Id, SetError, UTCDate, UuidState} -import org.apache.james.jmap.method.{EmailSubmissionCreationParseException, WithAccountId} +import org.apache.james.jmap.core.{AccountId, Id, JmapRfc8621Configuration, SetError, UTCDate, UuidState} +import org.apache.james.jmap.method.{EmailSubmissionCreationParseException, ValidableRequest, WithAccountId} import org.apache.james.mailbox.model.MessageId import play.api.libs.json.JsObject @@ -46,7 +46,7 @@ case class EmailSubmissionCreationId(id: Id) case class EmailSubmissionSetRequest(accountId: AccountId, create: Option[Map[EmailSubmissionCreationId, JsObject]], onSuccessUpdateEmail: Option[Map[EmailSubmissionCreationId, JsObject]], - onSuccessDestroyEmail: Option[List[EmailSubmissionCreationId]]) extends WithAccountId { + onSuccessDestroyEmail: Option[List[EmailSubmissionCreationId]]) extends WithAccountId with ValidableRequest { def implicitEmailSetRequest(messageIdResolver: EmailSubmissionCreationId => Either[IllegalArgumentException, Option[MessageId]]): Either[IllegalArgumentException, Option[EmailSetRequest]] = for { update <- resolveOnSuccessUpdateEmail(messageIdResolver) @@ -82,11 +82,26 @@ case class EmailSubmissionSetRequest(accountId: AccountId, .sequence) .sequence - def validate: Either[IllegalArgumentException, EmailSubmissionSetRequest] = { + def validate(configuration: JmapRfc8621Configuration): Either[Exception, EmailSubmissionSetRequest] = { val supportedCreationIds: List[EmailSubmissionCreationId] = create.getOrElse(Map()).keys.toList - validateOnSuccessUpdateEmail(supportedCreationIds) - .flatMap(_ => validateOnSuccessDestroyEmail(supportedCreationIds)) + for { + _ <- validateOnSuccessUpdateEmail(supportedCreationIds) + _ <- validateOnSuccessDestroyEmail(supportedCreationIds) + _ <- validateIdCount(configuration) + } yield { + this + } + } + + private def validateIdCount(configuration: JmapRfc8621Configuration): Either[Exception, EmailSubmissionSetRequest] = { + val idCount = create.map(_.size).getOrElse(0) + if (idCount > configuration.maxObjectsInSet.value.value) { + Left(RequestTooLargeException(s"Too many items in a set request ${this.getClass}. " + + s"Got $idCount items instead of maximum ${configuration.maxObjectsInSet.value}.")) + } else { + scala.Right(this) + } } private def validateOnSuccessDestroyEmail(supportedCreationIds: List[EmailSubmissionCreationId]) : Either[IllegalArgumentException, EmailSubmissionSetRequest] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentityGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentityGet.scala index 6f9927d8d4..e894c7cdf9 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentityGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentityGet.scala @@ -26,7 +26,7 @@ import org.apache.james.jmap.api.model.{Identity, IdentityId} import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.UuidState.INSTANCE import org.apache.james.jmap.core.{AccountId, Id, Properties, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import scala.util.Try @@ -57,7 +57,7 @@ case class IdentityIds(ids: List[UnparsedIdentityId]) { case class IdentityGetRequest(accountId: AccountId, ids: Option[IdentityIds], - properties: Option[Properties]) extends WithAccountId { + properties: Option[Properties]) extends WithAccountId with GetRequest { def computeResponse(identities: List[Identity]): IdentityGetResponse = { val list: List[Identity] = identities.filter(identity => isRequested(identity.id)) val notFound: Option[IdentityIds] = ids @@ -72,6 +72,8 @@ case class IdentityGetRequest(accountId: AccountId, } private def isRequested(id: IdentityId): Boolean = ids.forall(_.validIds.contains(id)) + + override def idCount: Option[Int] = ids.map(_.ids).map(_.size) } case class IdentityGetResponse(accountId: AccountId, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala index 6b44f773e7..0491fbe919 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/IdentitySet.scala @@ -24,7 +24,7 @@ import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.SetError.SetErrorDescription import org.apache.james.jmap.core.{AccountId, Properties, SetError, UuidState} import org.apache.james.jmap.method.IdentitySetUpdatePerformer.IdentitySetUpdateResponse -import org.apache.james.jmap.method.{WithAccountId, standardError} +import org.apache.james.jmap.method.{SetRequest, WithAccountId, standardError} import play.api.libs.json.{JsObject, JsPath, JsonValidationError} object IdentitySet { @@ -51,7 +51,9 @@ object IdentityCreation { case class IdentitySetRequest(accountId: AccountId, create: Option[Map[IdentityCreationId, JsObject]], update: Option[Map[UnparsedIdentityId, JsObject]], - destroy: Option[Seq[UnparsedIdentityId]]) extends WithAccountId + destroy: Option[Seq[UnparsedIdentityId]]) extends WithAccountId with SetRequest { + override def idCount: Int = create.map(_.size).getOrElse(0) + update.map(_.size).getOrElse(0) + destroy.map(_.size).getOrElse(0) +} case class IdentityCreationId(id: Id) { def serialise: String = id.value diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala index 98db1b84a2..276abc4525 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala @@ -20,9 +20,9 @@ package org.apache.james.jmap.mail import eu.timepit.refined.api.Refined -import org.apache.james.jmap.core.{AccountId, Id} +import org.apache.james.jmap.core.{AccountId, Id, JmapRfc8621Configuration} import org.apache.james.jmap.mail.MDNParse._ -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{ValidableRequest, WithAccountId} import org.apache.james.mailbox.model.MessageId import org.apache.james.mdn.MDN import org.apache.james.mime4j.dom.Message @@ -45,12 +45,12 @@ case object MDNParseRequest { } case class MDNParseRequest(accountId: AccountId, - blobIds: BlobIds) extends WithAccountId { + blobIds: BlobIds) extends WithAccountId with ValidableRequest { import MDNParseRequest._ - def validate: Either[RequestTooLargeException, MDNParseRequest] = { - if (blobIds.value.length > MAXIMUM_NUMBER_OF_BLOB_IDS) { + override def validate(configuration: JmapRfc8621Configuration): Either[RequestTooLargeException, MDNParseRequest] = { + if (blobIds.value.length > configuration.jmapEmailGetFullMaxSize.asLong()) { Left(RequestTooLargeException("The number of ids requested by the client exceeds the maximum number the server is willing to process in a single method call")) } else { scala.Right(this) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNSend.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNSend.scala index 743252ef37..dbaad2c8f7 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNSend.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNSend.scala @@ -24,8 +24,8 @@ import java.util.UUID import cats.implicits.toTraverseOps import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.SetError.SetErrorDescription -import org.apache.james.jmap.core.{AccountId, Id, Properties, SetError} -import org.apache.james.jmap.method.{WithAccountId, standardError} +import org.apache.james.jmap.core.{AccountId, Id, JmapRfc8621Configuration, Properties, SetError} +import org.apache.james.jmap.method.{SetRequest, ValidableRequest, WithAccountId, standardError} import org.apache.james.mailbox.model.MessageId import play.api.libs.json.{JsObject, JsPath, JsonValidationError} @@ -122,15 +122,19 @@ case class MDNSendCreateResponse(subject: Option[SubjectField], case class MDNSendRequest(accountId: AccountId, identityId: UnparsedIdentityId, send: Map[MDNSendCreationId, JsObject], - onSuccessUpdateEmail: Option[Map[MDNSendCreationId, JsObject]]) extends WithAccountId { + onSuccessUpdateEmail: Option[Map[MDNSendCreationId, JsObject]]) extends WithAccountId with SetRequest { - def validate: Either[IllegalArgumentException, MDNSendRequest] = { + + override def idCount: Int = send.size + + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, MDNSendRequest] = { val supportedCreationIds: List[MDNSendCreationId] = send.keys.toList onSuccessUpdateEmail.getOrElse(Map()) .keys .toList .map(id => validateOnSuccessUpdateEmail(id, supportedCreationIds)) .sequence + .flatMap(_ => validateIdCounts(configuration)) .map(_ => this) } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala index 2666989b5b..f60cd79d40 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala @@ -23,7 +23,7 @@ import eu.timepit.refined import org.apache.james.jmap.api.change.{EmailChanges, Limit, MailboxChanges} import org.apache.james.jmap.core.Id.{Id, IdConstraint} import org.apache.james.jmap.core.{AccountId, Properties, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import org.apache.james.mailbox.model.MailboxId import scala.util.{Failure, Try} @@ -55,7 +55,9 @@ case class Ids(value: List[UnparsedMailboxId]) case class MailboxGetRequest(accountId: AccountId, ids: Option[Ids], - properties: Option[Properties]) extends WithAccountId + properties: Option[Properties]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) +} case class NotFound(value: Set[UnparsedMailboxId]) { def merge(other: NotFound): NotFound = NotFound(this.value ++ other.value) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala index a6e2afdffc..6a37cc850c 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala @@ -33,7 +33,7 @@ import org.apache.james.jmap.core.{AccountId, CapabilityIdentifier, Properties, import org.apache.james.jmap.json.MailboxSerializer import org.apache.james.jmap.mail.MailboxName.MailboxName import org.apache.james.jmap.mail.MailboxPatchObject.MailboxPatchObjectKey -import org.apache.james.jmap.method.{MailboxCreationParseException, WithAccountId} +import org.apache.james.jmap.method.{MailboxCreationParseException, SetRequest, WithAccountId} import org.apache.james.mailbox.model.{MailboxId, MailboxACL => JavaMailboxACL} import org.apache.james.mailbox.{MailboxSession, Role} import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsString, JsSuccess, JsValue} @@ -43,7 +43,9 @@ case class MailboxSetRequest(accountId: AccountId, create: Option[Map[MailboxCreationId, JsObject]], update: Option[Map[UnparsedMailboxId, MailboxPatchObject]], destroy: Option[Seq[UnparsedMailboxId]], - onDestroyRemoveEmails: Option[RemoveEmailsOnDestroy]) extends WithAccountId + onDestroyRemoveEmails: Option[RemoveEmailsOnDestroy]) extends WithAccountId with SetRequest { + override def idCount: Int = create.map(_.size).getOrElse(0) + update.map(_.size).getOrElse(0) + destroy.map(_.size).getOrElse(0) +} case class MailboxCreationId(id: Id) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala index 63d60cb641..af1d198715 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Quotas.scala @@ -31,7 +31,7 @@ import org.apache.james.jmap.core.Limit.Limit import org.apache.james.jmap.core.Position.Position import org.apache.james.jmap.core.UnsignedInt.UnsignedInt import org.apache.james.jmap.core.{AccountId, CanCalculateChanges, Id, Properties, QueryState, UnsignedInt, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import org.apache.james.mailbox.model.{Quota => ModelQuota, QuotaRoot => ModelQuotaRoot} import scala.compat.java8.OptionConverters._ @@ -80,7 +80,9 @@ case class QuotaIds(value: List[UnparsedQuotaId]) case class QuotaGetRequest(accountId: AccountId, ids: Option[QuotaIds], - properties: Option[Properties]) extends WithAccountId + properties: Option[Properties]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) +} object JmapQuota { private val WARN_LIMIT_PERCENTAGE = 0.9 diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala index e6619a502c..c4c2851190 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala @@ -22,13 +22,15 @@ package org.apache.james.jmap.mail import org.apache.james.jmap.core.Id.Id import org.apache.james.jmap.core.UnsignedInt.UnsignedInt import org.apache.james.jmap.core.{AccountId, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} import org.apache.james.mailbox.model.MessageId case class Thread(id: Id, emailIds: List[MessageId]) case class ThreadGetRequest(accountId: AccountId, - ids: List[UnparsedThreadId]) extends WithAccountId + ids: List[UnparsedThreadId]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = Some(ids.size) +} case class ThreadGetResponse(accountId: AccountId, state: UuidState, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateGetMethod.scala index db8eefbdbd..c8c3b02d51 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateGetMethod.scala @@ -24,7 +24,7 @@ import javax.inject.Inject import org.apache.james.core.Username import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_DELEGATION, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ErrorCode, Invocation, Properties, SessionTranslator} +import org.apache.james.jmap.core.{ErrorCode, Invocation, JmapRfc8621Configuration, Properties, SessionTranslator} import org.apache.james.jmap.delegation.{Delegate, DelegateGet, DelegateGetRequest, DelegateGetResult, DelegationId, ForbiddenAccountManagementException} import org.apache.james.jmap.json.DelegationSerializer import org.apache.james.jmap.routes.SessionSupplier @@ -39,6 +39,7 @@ import scala.jdk.OptionConverters._ class DelegateGetMethod @Inject()(val metricFactory: MetricFactory, + val configuration: JmapRfc8621Configuration, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, val delegationStore: DelegationStore) extends MethodRequiringAccountId[DelegateGetRequest] { @@ -48,6 +49,7 @@ class DelegateGetMethod @Inject()(val metricFactory: MetricFactory, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, DelegateGetRequest] = DelegationSerializer.deserializeDelegateGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: DelegateGetRequest): Publisher[InvocationWithContext] = { val requestedProperties: Properties = request.properties.getOrElse(DelegateGet.allProperties) (requestedProperties -- DelegateGet.allProperties match { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala index 7b63954cb9..62923e86a5 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegateSetMethod.scala @@ -23,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_DELEGATION, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{ClientId, Id, Invocation, JmapRfc8621Configuration, ServerId, SessionTranslator, UuidState} import org.apache.james.jmap.delegation.{DelegateSetRequest, DelegateSetResponse, ForbiddenAccountManagementException} import org.apache.james.jmap.json.DelegationSerializer import org.apache.james.jmap.routes.SessionSupplier @@ -34,6 +34,7 @@ import reactor.core.scala.publisher.SMono class DelegateSetMethod @Inject()(createPerformer: DelegateSetCreatePerformer, deletePerformer: DelegateSetDeletePerformer, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[DelegateSetRequest] { @@ -42,6 +43,7 @@ class DelegateSetMethod @Inject()(createPerformer: DelegateSetCreatePerformer, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, DelegateSetRequest] = DelegationSerializer.deserializeDelegateSetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: DelegateSetRequest): SMono[InvocationWithContext] = if (isPrimaryAccount(mailboxSession)) { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountGetMethod.scala index a3181bb8fc..1698671a31 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountGetMethod.scala @@ -24,7 +24,7 @@ import javax.inject.Inject import org.apache.james.core.Username import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_DELEGATION, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ErrorCode, Invocation, Properties, SessionTranslator} +import org.apache.james.jmap.core.{ErrorCode, Invocation, JmapRfc8621Configuration, Properties, SessionTranslator} import org.apache.james.jmap.delegation.{Delegate, DelegatedAccountGet, DelegatedAccountGetRequest, DelegatedAccountGetResult, DelegationId, ForbiddenAccountManagementException} import org.apache.james.jmap.json.DelegationSerializer import org.apache.james.jmap.routes.SessionSupplier @@ -38,6 +38,7 @@ import reactor.core.scala.publisher.{SFlux, SMono} import scala.jdk.OptionConverters._ class DelegatedAccountGetMethod @Inject()(val metricFactory: MetricFactory, + val configuration: JmapRfc8621Configuration, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, val delegationStore: DelegationStore) extends MethodRequiringAccountId[DelegatedAccountGetRequest] { @@ -47,6 +48,7 @@ class DelegatedAccountGetMethod @Inject()(val metricFactory: MetricFactory, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, DelegatedAccountGetRequest] = DelegationSerializer.deserializeDelegatedAccountGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: DelegatedAccountGetRequest): Publisher[InvocationWithContext] = { val requestedProperties: Properties = request.properties.getOrElse(DelegatedAccountGet.allProperties) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountSetMethod.scala index a342000c6d..8099217385 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/DelegatedAccountSetMethod.scala @@ -23,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_DELEGATION, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{Invocation, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator, UuidState} import org.apache.james.jmap.delegation.{DelegatedAccountSetRequest, DelegatedAccountSetResponse, ForbiddenAccountManagementException} import org.apache.james.jmap.json.DelegationSerializer import org.apache.james.jmap.routes.SessionSupplier @@ -33,6 +33,7 @@ import org.apache.james.metrics.api.MetricFactory import reactor.core.scala.publisher.SMono class DelegatedAccountSetMethod @Inject()(deletePerformer: DelegatedAccountDeletePerformer, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[DelegatedAccountSetRequest] { @@ -41,6 +42,7 @@ class DelegatedAccountSetMethod @Inject()(deletePerformer: DelegatedAccountDelet override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, DelegatedAccountSetRequest] = DelegationSerializer.deserializeDelegatedAccountSetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: DelegatedAccountSetRequest): SMono[InvocationWithContext] = if (isPrimaryAccount(mailboxSession)) { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala index 89b21dc111..b86439f661 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala @@ -29,7 +29,7 @@ import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.UuidState.INSTANCE import org.apache.james.jmap.core.{AccountId, ErrorCode, Invocation, JmapRfc8621Configuration, SessionTranslator, UuidState} import org.apache.james.jmap.json.EmailGetSerializer -import org.apache.james.jmap.mail.{Email, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound, EmailView, EmailViewReaderFactory, FullReadLevel, MetadataReadLevel, ReadLevel, UnparsedEmailId} +import org.apache.james.jmap.mail.{Email, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound, EmailView, EmailViewReaderFactory, UnparsedEmailId} import org.apache.james.jmap.routes.SessionSupplier import org.apache.james.mailbox.MailboxSession import org.apache.james.mailbox.model.MessageId @@ -95,19 +95,20 @@ class EmailGetMethod @Inject() (readerFactory: EmailViewReaderFactory, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, EmailGetRequest] = EmailGetSerializer.deserializeEmailGetRequest(invocation.arguments.value).asEitherRequest - private def computeResponseInvocation(capabilities: Set[CapabilityIdentifier], request: EmailGetRequest, invocation: Invocation, mailboxSession: MailboxSession): SMono[Invocation] = - Email.validateProperties(request.properties) - .flatMap(properties => Email.validateBodyProperties(request.bodyProperties) - .flatMap(validatedBodyProperties => Email.validateIdsSize(request, configuration.jmapEmailGetFullMaxSize.asLong(), validatedBodyProperties)) - .map((properties, _))) - .fold( - e => SMono.error(e), { - case (properties, bodyProperties) => getEmails(capabilities, request, mailboxSession) - .map(response => Invocation( - methodName = methodName, - arguments = Arguments(EmailGetSerializer.serialize(response, properties, bodyProperties).as[JsObject]), - methodCallId = invocation.methodCallId)) - }) + private def computeResponseInvocation(capabilities: Set[CapabilityIdentifier], request: EmailGetRequest, invocation: Invocation, mailboxSession: MailboxSession): SMono[Invocation] = { + val either: Either[Exception, SMono[Invocation]] = for { + properties <- Email.validateProperties(request.properties) + bodyProperties <- Email.validateBodyProperties(request.bodyProperties) + _ <- request.validate(configuration) + } yield { + getEmails(capabilities, request, mailboxSession) + .map(response => Invocation( + methodName = methodName, + arguments = Arguments(EmailGetSerializer.serialize(response, properties, bodyProperties).as[JsObject]), + methodCallId = invocation.methodCallId)) + } + either.fold(SMono.error, v => v) + } private def getEmails(capabilities: Set[CapabilityIdentifier], request: EmailGetRequest, mailboxSession: MailboxSession): SMono[EmailGetResponse] = request.ids match { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala index 7e00fc383d..cabd276601 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala @@ -29,7 +29,7 @@ import org.apache.james.jmap.api.model.{AccountId => JavaAccountId} import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.SetError.SetErrorDescription -import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, SessionTranslator, SetError, UuidState} +import org.apache.james.jmap.core.{ClientId, Id, Invocation, JmapRfc8621Configuration, ServerId, SessionTranslator, SetError, UuidState} import org.apache.james.jmap.json.EmailSetSerializer import org.apache.james.jmap.mail.{BlobId, EmailCreationId, EmailCreationResponse, EmailImport, EmailImportRequest, EmailImportResponse, ThreadId, ValidatedEmailImport} import org.apache.james.jmap.method.EmailImportMethod.{ImportFailure, ImportResult, ImportResults, ImportSuccess, ImportWithBlob} @@ -94,6 +94,7 @@ object EmailImportMethod { } class EmailImportMethod @Inject() (val metricFactory: MetricFactory, + val configuration: JmapRfc8621Configuration, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, val blobResolvers: BlobResolvers, @@ -105,6 +106,7 @@ class EmailImportMethod @Inject() (val metricFactory: MetricFactory, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, EmailImportRequest] = serializer.deserializeEmailImportRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailImportRequest): Publisher[InvocationWithContext] = for { @@ -139,10 +141,10 @@ class EmailImportMethod @Inject() (val metricFactory: MetricFactory, private def importEmails(request: EmailImportRequest, mailboxSession: MailboxSession): SMono[ImportResults] = SFlux.fromIterable(request.emails.toList) - .flatMap { + .concatMap { case creationId -> emailImport => resolveBlob(mailboxSession, creationId, emailImport) } - .flatMap { + .concatMap { case Right(emailImport) => importEmail(mailboxSession, emailImport) case Left(e) => SMono.just(e) }.collectSeq() diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailParseMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailParseMethod.scala index 291f0a844b..e0c663c527 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailParseMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailParseMethod.scala @@ -27,7 +27,7 @@ import org.apache.james.jmap.api.model.Preview import org.apache.james.jmap.api.model.Size.Size import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation._ -import org.apache.james.jmap.core.{Invocation, SessionTranslator} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator} import org.apache.james.jmap.json.EmailGetSerializer import org.apache.james.jmap.mail.{BlobId, BlobUnParsableException, Email, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailFullViewFactory, EmailHeaders, EmailParseMetadata, EmailParseRequest, EmailParseResponse, EmailParseResults, EmailParseView, HasAttachment} import org.apache.james.jmap.routes.{BlobNotFoundException, BlobResolvers, SessionSupplier} @@ -40,6 +40,7 @@ import reactor.core.scala.publisher.{SFlux, SMono} import scala.util.Try class EmailParseMethod @Inject()(val blobResolvers: BlobResolvers, + val configuration: JmapRfc8621Configuration, val zoneIdProvider: ZoneIdProvider, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, @@ -58,6 +59,7 @@ class EmailParseMethod @Inject()(val blobResolvers: BlobResolvers, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, EmailParseRequest] = EmailGetSerializer.deserializeEmailParseRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) def computeResponseInvocation(request: EmailParseRequest, invocation: Invocation, @@ -83,8 +85,9 @@ class EmailParseMethod @Inject()(val blobResolvers: BlobResolvers, val parsedIds: Seq[BlobId] = validations.flatMap(_.toOption) val invalid: Seq[EmailParseResults] = validations.map(_.left).flatMap(_.toOption) + val concurrency = 2 val parsed: SFlux[EmailParseResults] = SFlux.fromIterable(parsedIds) - .flatMap(blobId => toParseResults(request, blobId, mailboxSession)) + .flatMap(blobId => toParseResults(request, blobId, mailboxSession), concurrency, concurrency) SFlux.merge(Seq(parsed, SFlux.fromIterable(invalid))) .reduce(EmailParseResults.empty())(EmailParseResults.merge) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala index c6861b923a..ac565d19a2 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala @@ -24,7 +24,7 @@ import org.apache.james.jmap.api.change.EmailChangeRepository import org.apache.james.jmap.api.model.{AccountId => JavaAccountId} import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{ClientId, Id, Invocation, JmapRfc8621Configuration, ServerId, SessionTranslator, UuidState} import org.apache.james.jmap.json.EmailSetSerializer import org.apache.james.jmap.mail.{EmailSetRequest, EmailSetResponse} import org.apache.james.jmap.routes.SessionSupplier @@ -39,6 +39,7 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, + val configuration: JmapRfc8621Configuration, createPerformer: EmailSetCreatePerformer, deletePerformer: EmailSetDeletePerformer, updatePerformer: EmailSetUpdatePerformer, @@ -76,8 +77,9 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer, })) } - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, EmailSetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, EmailSetRequest] = serializer.deserialize(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def retrieveState(capabilities: Set[CapabilityIdentifier], mailboxSession: MailboxSession): SMono[UuidState] = if (capabilities.contains(JAMES_SHARES)) { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala index b5c2b212fe..e06e4ea912 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala @@ -38,7 +38,7 @@ import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EM import org.apache.james.jmap.core.Id.{Id, IdConstraint} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.SetError.{SetErrorDescription, SetErrorType} -import org.apache.james.jmap.core.{ClientId, Invocation, Properties, ServerId, SessionTranslator, SetError, SubmissionCapabilityFactory, UTCDate, UuidState} +import org.apache.james.jmap.core.{ClientId, Invocation, JmapRfc8621Configuration, Properties, ServerId, SessionTranslator, SetError, SubmissionCapabilityFactory, UTCDate, UuidState} import org.apache.james.jmap.json.EmailSubmissionSetSerializer import org.apache.james.jmap.mail.{EmailSubmissionAddress, EmailSubmissionCreationId, EmailSubmissionCreationRequest, EmailSubmissionCreationResponse, EmailSubmissionId, EmailSubmissionSetRequest, EmailSubmissionSetResponse, Envelope, ParameterName, ParameterValue} import org.apache.james.jmap.method.EmailSubmissionSetMethod.{CreationFailure, CreationResult, CreationResults, CreationSuccess, LOGGER, MAIL_METADATA_USERNAME_ATTRIBUTE, NO_DELAY, VALID_PARAMETER_NAME_SET, formatter} @@ -167,6 +167,7 @@ case class MessageMimeMessageSource(id: String, message: MessageResult) extends } class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerializer, + configuration: JmapRfc8621Configuration, messageIdManager: MessageIdManager, mailQueueFactory: MailQueueFactory[_ <: MailQueue], canSendFrom: CanSendFrom, @@ -211,10 +212,10 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize SFlux.concat(SMono.just(explicitInvocation), emailSetCall) }) - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, EmailSubmissionSetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, EmailSubmissionSetRequest] = serializer.deserializeEmailSubmissionSetRequest(invocation.arguments.value) .asEitherRequest - .flatMap(_.validate) + .flatMap(_.validate(configuration)) private def create(request: EmailSubmissionSetRequest, session: MailboxSession, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentityGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentityGetMethod.scala index 382ab49c6a..5aaf71ddaa 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentityGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentityGetMethod.scala @@ -36,6 +36,7 @@ import play.api.libs.json.JsObject import reactor.core.scala.publisher.{SFlux, SMono} class IdentityGetMethod @Inject() (identityRepository: IdentityRepository, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[IdentityGetRequest] { @@ -58,8 +59,9 @@ class IdentityGetMethod @Inject() (identityRepository: IdentityRepository, }).map(InvocationWithContext(_, invocation.processingContext)) } - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, IdentityGetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, IdentityGetRequest] = IdentitySerializer.deserialize(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def getIdentities(request: IdentityGetRequest, mailboxSession: MailboxSession): SMono[IdentityGetResponse] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala index 08ccf16212..9c1b6e06d7 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/IdentitySetMethod.scala @@ -23,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ClientId, Id, Invocation, ServerId, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{ClientId, Id, Invocation, JmapRfc8621Configuration, ServerId, SessionTranslator, UuidState} import org.apache.james.jmap.json.IdentitySerializer import org.apache.james.jmap.mail.{IdentitySetRequest, IdentitySetResponse} import org.apache.james.jmap.routes.SessionSupplier @@ -34,6 +34,7 @@ import reactor.core.scala.publisher.SMono class IdentitySetMethod @Inject()(createPerformer: IdentitySetCreatePerformer, updatePerformer: IdentitySetUpdatePerformer, deletePerformer: IdentitySetDeletePerformer, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[IdentitySetRequest] { @@ -42,6 +43,8 @@ class IdentitySetMethod @Inject()(createPerformer: IdentitySetCreatePerformer, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, IdentitySetRequest] = IdentitySerializer.deserializeIdentitySetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) + override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: IdentitySetRequest): SMono[InvocationWithContext] = for { creationResults <- createPerformer.create(request, mailboxSession) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNParseMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNParseMethod.scala index baff8d92da..c658b33849 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNParseMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNParseMethod.scala @@ -25,7 +25,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_MDN} import org.apache.james.jmap.core.Invocation._ -import org.apache.james.jmap.core.{Invocation, SessionTranslator} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator} import org.apache.james.jmap.json.MDNSerializer import org.apache.james.jmap.mail.{BlobId, BlobUnParsableException, MDNParseRequest, MDNParseResponse, MDNParseResults, MDNParsed} import org.apache.james.jmap.routes.{BlobNotFoundException, BlobResolvers, SessionSupplier} @@ -43,6 +43,7 @@ import scala.jdk.OptionConverters._ import scala.util.{Try, Using} class MDNParseMethod @Inject()(serializer: MDNSerializer, + val configuration: JmapRfc8621Configuration, val blobResolvers: BlobResolvers, val metricFactory: MetricFactory, val mdnEmailIdResolver: MDNEmailIdResolver, @@ -61,7 +62,7 @@ class MDNParseMethod @Inject()(serializer: MDNSerializer, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, MDNParseRequest] = serializer.deserializeMDNParseRequest(invocation.arguments.value) .asEitherRequest - .flatMap(_.validate) + .flatMap(request => request.validate(configuration).map(_ => request)) def computeResponseInvocation(request: MDNParseRequest, invocation: Invocation, @@ -82,8 +83,9 @@ class MDNParseMethod @Inject()(serializer: MDNSerializer, val parsedIds: Seq[BlobId] = validations.flatMap(_.toOption) val invalid: Seq[MDNParseResults] = validations.map(_.left).flatMap(_.toOption) + val concurrency = 2 val parsed: SFlux[MDNParseResults] = SFlux.fromIterable(parsedIds) - .flatMap(blobId => toParseResults(blobId, mailboxSession)) + .flatMap(blobId => toParseResults(blobId, mailboxSession), concurrency, concurrency) SFlux.merge(Seq(parsed, SFlux.fromIterable(invalid))) .reduce(MDNParseResults.empty())(MDNParseResults.merge) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala index 25adba64e9..9e06c0a835 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala @@ -26,7 +26,7 @@ import javax.inject.Inject import org.apache.james.jmap.api.model.Identity import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_MDN} import org.apache.james.jmap.core.Invocation._ -import org.apache.james.jmap.core.{Invocation, SessionTranslator} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator} import org.apache.james.jmap.json.MDNSerializer import org.apache.james.jmap.mail.MDN._ import org.apache.james.jmap.mail.MDNSend.MDN_ALREADY_SENT_FLAG @@ -60,6 +60,7 @@ class MDNSendMethod @Inject()(serializer: MDNSerializer, mailQueueFactory: MailQueueFactory[_ <: MailQueue], messageIdManager: MessageIdManager, emailSetMethod: EmailSetMethod, + val configuration: JmapRfc8621Configuration, val identityResolver: IdentityResolver, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, @@ -108,7 +109,7 @@ class MDNSendMethod @Inject()(serializer: MDNSerializer, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, MDNSendRequest] = serializer.deserializeMDNSendRequest(invocation.arguments.value) .asEitherRequest - .flatMap(_.validate) + .flatMap(_.validate(configuration)) private def create(identity: Identity, request: MDNSendRequest, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala index d78c5ace6a..fd177d6908 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala @@ -25,7 +25,7 @@ import org.apache.james.jmap.api.change.MailboxChangeRepository import org.apache.james.jmap.api.model.{AccountId => JavaAccountId} import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{AccountId, CapabilityIdentifier, ErrorCode, Invocation, Properties, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{AccountId, CapabilityIdentifier, ErrorCode, Invocation, JmapRfc8621Configuration, Properties, SessionTranslator, UuidState} import org.apache.james.jmap.http.MailboxesProvisioner import org.apache.james.jmap.json.MailboxSerializer import org.apache.james.jmap.mail.{Ids, Mailbox, MailboxFactory, MailboxGet, MailboxGetRequest, MailboxGetResponse, NotFound, PersonalNamespace, Subscriptions, UnparsedMailboxId} @@ -67,6 +67,7 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer, mailboxFactory: MailboxFactory, provisioner: MailboxesProvisioner, mailboxChangeRepository: MailboxChangeRepository, + configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[MailboxGetRequest] { @@ -101,8 +102,9 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer, .map(UuidState.fromJava) } - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, MailboxGetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, MailboxGetRequest] = serializer.deserializeMailboxGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def getMailboxes(capabilities: Set[CapabilityIdentifier], mailboxGetRequest: MailboxGetRequest, mailboxSession: MailboxSession): SFlux[MailboxGetResults] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala index 3f5d6d52ee..396e31875e 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala @@ -25,7 +25,7 @@ import org.apache.james.jmap.api.change.MailboxChangeRepository import org.apache.james.jmap.api.model.{AccountId => JavaAccountId} import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{Invocation, SessionTranslator, SetError, UuidState} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator, SetError, UuidState} import org.apache.james.jmap.json.MailboxSerializer import org.apache.james.jmap.mail.{MailboxSetRequest, MailboxSetResponse} import org.apache.james.jmap.method.MailboxSetCreatePerformer.MailboxCreationResults @@ -48,6 +48,7 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer, createPerformer: MailboxSetCreatePerformer, deletePerformer: MailboxSetDeletePerformer, updatePerformer: MailboxSetUpdatePerformer, + configuration: JmapRfc8621Configuration, mailboxChangeRepository: MailboxChangeRepository, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, @@ -64,8 +65,9 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer, response = createResponse(capabilities, invocation.invocation, request, creationResults._1, deletionResults, updateResults, oldState, newState) } yield InvocationWithContext(response, creationResults._2) - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, MailboxSetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, MailboxSetRequest] = serializer.deserializeMailboxSetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def createResponse(capabilities: Set[CapabilityIdentifier], invocation: Invocation, 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 ff02533f92..3816b8099a 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 @@ -21,7 +21,7 @@ package org.apache.james.jmap.method import org.apache.james.jmap.api.exception.ChangeNotFoundException import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier import org.apache.james.jmap.core.Invocation.MethodName -import org.apache.james.jmap.core.{AccountId, ErrorCode, Invocation, SessionTranslator} +import org.apache.james.jmap.core.{AccountId, ErrorCode, Invocation, JmapRfc8621Configuration, SessionTranslator} import org.apache.james.jmap.delegation.ForbiddenAccountManagementException import org.apache.james.jmap.mail.{IdentityIdNotFoundException, RequestTooLargeException, UnsupportedFilterException, UnsupportedNestingException, UnsupportedRequestParameterException, UnsupportedSortException} import org.apache.james.jmap.routes.{ProcessingContext, SessionSupplier} @@ -56,6 +56,39 @@ trait Method { trait WithAccountId { def accountId: AccountId } + +trait GetRequest extends ValidableRequest { + def idCount: Option[Int] + + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, GetRequest] = + if (idCount.exists(value => value > configuration.maxObjectsInGet.value.value)) { + Left(RequestTooLargeException(s"Too many items in a get request ${this.getClass}. " + + s"Got $idCount items instead of maximum ${configuration.maxObjectsInGet.value}.")) + } else { + scala.Right(this) + } +} + +trait SetRequest extends ValidableRequest { + def idCount: Int + + override def validate(configuration: JmapRfc8621Configuration): Either[Exception, SetRequest] = + validateIdCounts(configuration) + + def validateIdCounts(configuration: JmapRfc8621Configuration) = { + if (idCount > configuration.maxObjectsInGet.value.value) { + Left(RequestTooLargeException(s"Too many items in a set request ${this.getClass}. " + + s"Got $idCount items instead of maximum ${configuration.maxObjectsInSet.value}.")) + } else { + scala.Right(this) + } + } +} + +trait ValidableRequest { + def validate(configuration: JmapRfc8621Configuration): Either[Exception, ValidableRequest] +} + trait MethodRequiringAccountId[REQUEST <: WithAccountId] extends Method { def metricFactory: MetricFactory def sessionSupplier: SessionSupplier diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala index 8fe02c250f..1eb97aef2b 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionGetMethod.scala @@ -25,7 +25,7 @@ import org.apache.james.jmap.api.model.PushSubscriptionId import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{Ids, Invocation, PushSubscriptionDTO, PushSubscriptionGetRequest, PushSubscriptionGetResponse, SessionTranslator, UnparsedPushSubscriptionId} +import org.apache.james.jmap.core.{Ids, Invocation, JmapRfc8621Configuration, PushSubscriptionDTO, PushSubscriptionGetRequest, PushSubscriptionGetResponse, SessionTranslator, UnparsedPushSubscriptionId} import org.apache.james.jmap.json.PushSubscriptionSerializer import org.apache.james.jmap.routes.SessionSupplier import org.apache.james.lifecycle.api.Startable @@ -46,7 +46,8 @@ case class PushSubscriptionGetResults(results: Seq[PushSubscriptionDTO], notFoun } class PushSubscriptionGetMethod @Inject()(pushSubscriptionSerializer: PushSubscriptionSerializer, - pushSubscriptionRepository: PushSubscriptionRepository, + val configuration: JmapRfc8621Configuration, + val pushSubscriptionRepository: PushSubscriptionRepository, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodWithoutAccountId[PushSubscriptionGetRequest] with Startable { @@ -55,6 +56,7 @@ class PushSubscriptionGetMethod @Inject()(pushSubscriptionSerializer: PushSubscr override def getRequest(invocation: Invocation): Either[Exception, PushSubscriptionGetRequest] = pushSubscriptionSerializer.deserializePushSubscriptionGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(invocation: InvocationWithContext, session: MailboxSession, request: PushSubscriptionGetRequest): SMono[InvocationWithContext] = request.validateProperties diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala index b5157e1e06..b19bc238f0 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetMethod.scala @@ -23,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{Invocation, PushSubscriptionSetRequest, PushSubscriptionSetResponse, SessionTranslator} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, PushSubscriptionSetRequest, PushSubscriptionSetResponse, SessionTranslator} import org.apache.james.jmap.json.PushSubscriptionSerializer import org.apache.james.jmap.routes.SessionSupplier import org.apache.james.lifecycle.api.Startable @@ -34,6 +34,7 @@ import reactor.core.scala.publisher.SMono class PushSubscriptionSetMethod @Inject()(createPerformer: PushSubscriptionSetCreatePerformer, updatePerformer: PushSubscriptionUpdatePerformer, deletePerformer: PushSubscriptionSetDeletePerformer, + configuration: JmapRfc8621Configuration, pushSubscriptionSerializer: PushSubscriptionSerializer, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, @@ -43,6 +44,7 @@ class PushSubscriptionSetMethod @Inject()(createPerformer: PushSubscriptionSetCr override def getRequest(invocation: Invocation): Either[Exception, PushSubscriptionSetRequest] = pushSubscriptionSerializer.deserializePushSubscriptionSetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) override def doProcess(invocation: InvocationWithContext, mailboxSession: MailboxSession, request: PushSubscriptionSetRequest): SMono[InvocationWithContext] = for { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala index 09d8848a6d..78f27c12a7 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/QuotaGetMethod.scala @@ -24,7 +24,7 @@ import javax.inject.Inject import org.apache.james.core.Username import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JAMES_SHARES, JMAP_CORE, JMAP_QUOTA} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{ErrorCode, Invocation, Properties, SessionTranslator} +import org.apache.james.jmap.core.{ErrorCode, Invocation, JmapRfc8621Configuration, Properties, SessionTranslator} import org.apache.james.jmap.json.QuotaSerializer import org.apache.james.jmap.mail.{CountResourceType, JmapQuota, OctetsResourceType, QuotaGetRequest, QuotaIdFactory, QuotaResponseGetResult} import org.apache.james.jmap.routes.SessionSupplier @@ -39,6 +39,7 @@ import play.api.libs.json.JsObject import reactor.core.scala.publisher.{SFlux, SMono} class QuotaGetMethod @Inject()(val metricFactory: MetricFactory, + val configuration: JmapRfc8621Configuration, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, val quotaManager: QuotaManager, @@ -71,6 +72,7 @@ class QuotaGetMethod @Inject()(val metricFactory: MetricFactory, override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, QuotaGetRequest] = QuotaSerializer.deserializeQuotaGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def getQuotaGetResponse(request: QuotaGetRequest, username: Username, capabilities: Set[CapabilityIdentifier]): SMono[QuotaResponseGetResult] = jmapQuotaManagerWrapper.list(username, capabilities) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala index 07869d1bda..ffc775da8b 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala @@ -23,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} -import org.apache.james.jmap.core.{AccountId, Invocation, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{AccountId, Invocation, JmapRfc8621Configuration, SessionTranslator, UuidState} import org.apache.james.jmap.json.ThreadSerializer import org.apache.james.jmap.mail.{Thread, ThreadGetRequest, ThreadGetResponse, ThreadNotFound, UnparsedThreadId} import org.apache.james.jmap.routes.SessionSupplier @@ -60,6 +60,7 @@ case class ThreadGetResult(threads: Set[Thread], notFound: ThreadNotFound) { } class ThreadGetMethod @Inject()(val metricFactory: MetricFactory, + val configuration: JmapRfc8621Configuration, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator, val threadIdFactory: JavaThreadId.Factory, @@ -77,8 +78,9 @@ class ThreadGetMethod @Inject()(val metricFactory: MetricFactory, methodCallId = invocation.invocation.methodCallId)) .map(InvocationWithContext(_, invocation.processingContext)) - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, ThreadGetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, ThreadGetRequest] = ThreadSerializer.deserialize(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def getThreadResponse(threadGetRequest: ThreadGetRequest, mailboxSession: MailboxSession): SFlux[ThreadGetResult] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala index c7e2006750..189bfdbd22 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala @@ -24,7 +24,7 @@ import javax.inject.Inject import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_VACATION_RESPONSE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.UuidState.INSTANCE -import org.apache.james.jmap.core.{AccountId, ErrorCode, Invocation, Properties, SessionTranslator} +import org.apache.james.jmap.core.{AccountId, ErrorCode, Invocation, JmapRfc8621Configuration, Properties, SessionTranslator} import org.apache.james.jmap.json.VacationSerializer import org.apache.james.jmap.routes.SessionSupplier import org.apache.james.jmap.vacation.VacationResponse.UNPARSED_SINGLETON @@ -56,6 +56,7 @@ case class VacationResponseGetResult(vacationResponses: Set[VacationResponse], n } class VacationResponseGetMethod @Inject()(vacationService: VacationService, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[VacationResponseGetRequest] { @@ -81,8 +82,9 @@ class VacationResponseGetMethod @Inject()(vacationService: VacationService, } } - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, VacationResponseGetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, VacationResponseGetRequest] = VacationSerializer.deserializeVacationResponseGetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def getVacationResponse(vacationResponseGetRequest: VacationResponseGetRequest, mailboxSession: MailboxSession): SFlux[VacationResponseGetResult] = vacationResponseGetRequest.ids match { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala index 0158b60f1b..c061828cc2 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala @@ -30,7 +30,7 @@ import org.apache.james.jmap.change.{AccountIdRegistrationKey, StateChangeEvent, import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_VACATION_RESPONSE} import org.apache.james.jmap.core.Invocation.{Arguments, MethodName} import org.apache.james.jmap.core.SetError.SetErrorDescription -import org.apache.james.jmap.core.{Invocation, SessionTranslator, UuidState} +import org.apache.james.jmap.core.{Invocation, JmapRfc8621Configuration, SessionTranslator, UuidState} import org.apache.james.jmap.json.VacationSerializer import org.apache.james.jmap.method.VacationResponseSetMethod.VACATION_RESPONSE_PATCH_OBJECT_KEY import org.apache.james.jmap.routes.SessionSupplier @@ -83,6 +83,7 @@ object VacationResponseSetMethod { class VacationResponseSetMethod @Inject()(@Named(InjectionKeys.JMAP) eventBus: EventBus, vacationService: VacationService, + val configuration: JmapRfc8621Configuration, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier, val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[VacationResponseSetRequest] { @@ -94,8 +95,9 @@ class VacationResponseSetMethod @Inject()(@Named(InjectionKeys.JMAP) eventBus: E .flatMap(updateResults => dispatchVacationResponseChangeEvent(mailboxSession.getUser, updateResults) .`then`(SMono.just(InvocationWithContext(createResponse(invocation.invocation, request, updateResults), invocation.processingContext)))) - override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, VacationResponseSetRequest] = + override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[Exception, VacationResponseSetRequest] = VacationSerializer.deserializeVacationResponseSetRequest(invocation.arguments.value).asEitherRequest + .flatMap(request => request.validate(configuration).map(_ => request)) private def dispatchVacationResponseChangeEvent(username: Username, updateResults: VacationResponseUpdateResults): SMono[Void] = { def noVacationResponseChange: Boolean = updateResults.updateSuccess.isEmpty diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseGet.scala index ff01a75b1c..94bf6b39d3 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseGet.scala @@ -20,13 +20,15 @@ package org.apache.james.jmap.vacation import org.apache.james.jmap.core.{AccountId, Properties, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{GetRequest, WithAccountId} case class VacationResponseIds(value: List[UnparsedVacationResponseId]) case class VacationResponseGetRequest(accountId: AccountId, ids: Option[VacationResponseIds], - properties: Option[Properties]) extends WithAccountId + properties: Option[Properties]) extends WithAccountId with GetRequest { + override def idCount: Option[Int] = ids.map(_.value).map(_.size) +} case class VacationResponseNotFound(value: Set[UnparsedVacationResponseId]) { def merge(other: VacationResponseNotFound): VacationResponseNotFound = VacationResponseNotFound(this.value ++ other.value) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseSet.scala index 7470d8aac2..f780e76b9b 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/vacation/VacationResponseSet.scala @@ -24,7 +24,7 @@ import java.time.format.DateTimeFormatter import org.apache.james.jmap.core.SetError.{SetErrorDescription, SetErrorType, invalidArgumentValue, serverFailValue} import org.apache.james.jmap.core.{AccountId, UuidState} -import org.apache.james.jmap.method.WithAccountId +import org.apache.james.jmap.method.{SetRequest, WithAccountId} import org.apache.james.util.ValuePatch import org.apache.james.vacation.api.Vacation.ID import org.apache.james.vacation.api.VacationPatch @@ -35,7 +35,10 @@ import scala.util.{Failure, Success, Try} case class VacationResponseSetRequest(accountId: AccountId, update: Option[Map[String, VacationResponsePatchObject]], create: Option[Map[String, JsObject]], - destroy: Option[Seq[String]]) extends WithAccountId{ + destroy: Option[Seq[String]]) extends WithAccountId with SetRequest { + + override def idCount: Int = create.map(_.size).getOrElse(0) + update.map(_.size).getOrElse(0) + destroy.map(_.size).getOrElse(0) + def parsePatch(): Map[String, Either[IllegalArgumentException, VacationResponsePatchObject]] = update.getOrElse(Map()) .map({ diff --git a/src/site/xdoc/server/config-jmap.xml b/src/site/xdoc/server/config-jmap.xml index 9bea1131a1..7043f0200e 100644 --- a/src/site/xdoc/server/config-jmap.xml +++ b/src/site/xdoc/server/config-jmap.xml @@ -145,6 +145,12 @@ <dt><strong>email.get.full.max.size</strong></dt> <dd>Optional, default value is 5. The max number of items for EmailGet full reads</dd> + + <dt><strong>get.max.size</strong></dt> + <dd>Optional, default value is 500. The max number of items for /get methods.</dd> + + <dt><strong>set.max.size</strong></dt> + <dd>Optional, default value is 500. The max number of items for /set methods.</dd> </dl> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org