You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/10/30 01:27:13 UTC

[james-project] branch master updated (85a9af8 -> d81a9e6)

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

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


    from 85a9af8  Update RabbitMQConnectionFactory.java
     new bb1fc01  JAMES-3432 Limit size attachment
     new d1912b9  JAMES-3432 Advertise max upload size in the session
     new 640cfb7  JAMES-2891 use rfc6570 for JMAP downloadUrl
     new 5ff0c87  JAMES-2891 JMAP Session: Add {accountId} as a compulsory parameter of upload URL
     new d81a9e6  [WEBSITE] Community post: Performance testing for James with JMeter

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../servers/pages/distributed/configure/jmap.adoc  |  8 ++++
 .../src/main/java/org/apache/james/util/Size.java  |  8 +++-
 .../rfc8621/contract/SessionRoutesContract.scala   |  8 ++--
 .../jmap/rfc8621/contract/UploadContract.scala     | 16 +++----
 .../src/test/resources/jmap.properties             |  3 +-
 server/protocols/jmap-rfc-8621/pom.xml             |  5 ++
 .../org/apache/james/jmap/http/SessionRoutes.scala |  3 +-
 .../apache/james/jmap/http/SessionSupplier.scala   |  8 ++--
 .../apache/james/jmap/method/CoreEchoMethod.scala  |  6 +--
 .../apache/james/jmap/method/EmailGetMethod.scala  | 16 +++----
 .../james/jmap/method/EmailQueryMethod.scala       |  7 ++-
 .../apache/james/jmap/method/EmailSetMethod.scala  |  7 ++-
 .../james/jmap/method/MailboxGetMethod.scala       |  7 ++-
 .../james/jmap/method/MailboxQueryMethod.scala     |  5 +-
 .../james/jmap/method/MailboxSetMethod.scala       |  7 ++-
 .../org/apache/james/jmap/method/Method.scala      |  4 +-
 .../jmap/method/VacationResponseGetMethod.scala    |  7 ++-
 .../jmap/method/VacationResponseSetMethod.scala    |  7 ++-
 .../org/apache/james/jmap/model/Capabilities.scala | 14 +++---
 .../org/apache/james/jmap/model/Capability.scala   | 13 +++++-
 .../jmap/model/JmapRfc8621Configuration.scala      | 23 ++++++---
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  6 +--
 .../apache/james/jmap/routes/UploadRoutes.scala    | 54 ++++++++++++++--------
 .../apache/james/jmap/http/SessionRoutesTest.scala | 15 +++---
 .../jmap/model/JmapRfc8621ConfigurationTest.scala  |  8 ++--
 .../james/jmap/routes/JMAPApiRoutesTest.scala      |  7 ++-
 .../2020-10-29-testing-james-with-jmeter.markdown  | 20 ++++++++
 src/site/xdoc/server/config-jmap.xml               |  8 ++++
 28 files changed, 185 insertions(+), 115 deletions(-)
 create mode 100644 src/homepage/_posts/2020-10-29-testing-james-with-jmeter.markdown


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


[james-project] 04/05: JAMES-2891 JMAP Session: Add {accountId} as a compulsory parameter of upload URL

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5ff0c873291bddd3ea7def5b15a11bcb6dcdb4df
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 29 08:50:47 2020 +0700

    JAMES-2891 JMAP Session: Add {accountId} as a compulsory parameter of upload URL
---
 .../james/jmap/rfc8621/contract/SessionRoutesContract.scala      | 2 +-
 .../main/scala/org/apache/james/jmap/http/SessionRoutes.scala    | 3 +--
 .../org/apache/james/jmap/model/JmapRfc8621Configuration.scala   | 2 +-
 .../scala/org/apache/james/jmap/http/SessionRoutesTest.scala     | 9 +++++----
 .../apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala   | 4 ++--
 5 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index aac2416..7fc769e 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -103,7 +103,7 @@ object SessionRoutesContract {
                          |  "username" : "bob@domain.tld",
                          |  "apiUrl" : "http://domain.com/jmap",
                          |  "downloadUrl" : "http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}",
-                         |  "uploadUrl" : "http://domain.com/upload",
+                         |  "uploadUrl" : "http://domain.com/upload/{accountId}",
                          |  "eventSourceUrl" : "http://domain.com/eventSource",
                          |  "state" : "000001"
                          |}""".stripMargin
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index 10f1565..33e9598 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -27,9 +27,8 @@ import io.netty.handler.codec.http.HttpResponseStatus.OK
 import javax.inject.{Inject, Named}
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8
 import org.apache.james.jmap.JMAPRoutes.CORS_CONTROL
-import org.apache.james.jmap.JMAPUrls.AUTHENTICATION
 import org.apache.james.jmap.exceptions.UnauthorizedException
-import org.apache.james.jmap.http.SessionRoutes.{JMAP_SESSION, WELL_KNOWN_JMAP, LOGGER}
+import org.apache.james.jmap.http.SessionRoutes.{JMAP_SESSION, LOGGER, WELL_KNOWN_JMAP}
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
 import org.apache.james.jmap.json.ResponseSerializer
 import org.apache.james.jmap.model.Session
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
index aea142d..5897623 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
@@ -46,6 +46,6 @@ case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: MaxS
   val urlPrefix: URL = new URL(urlPrefixString)
   val apiUrl: URL = new URL(s"$urlPrefixString/jmap")
   val downloadUrl: URL = new URL(urlPrefixString + "/download/{accountId}/{blobId}/?type={type}&name={name}")
-  val uploadUrl: URL = new URL(s"$urlPrefixString/upload")
+  val uploadUrl: URL = new URL(s"$urlPrefixString/upload/{accountId}")
   val eventSourceUrl: URL = new URL(s"$urlPrefixString/eventSource")
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index b466742..1f3083c 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -33,6 +33,7 @@ import org.apache.james.core.Username
 import org.apache.james.jmap._
 import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
 import org.apache.james.jmap.model.JmapRfc8621Configuration
+import org.apache.james.jmap.model.JmapRfc8621Configuration.LOCALHOST_URL_PREFIX
 import org.apache.james.mailbox.MailboxSession
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito._
@@ -177,10 +178,10 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |    "urn:ietf:params:jmap:vacationresponse": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401"
                          |  },
                          |  "username" : "bob@james.org",
-                         |  "apiUrl" : "${JmapRfc8621Configuration.LOCALHOST_URL_PREFIX}/jmap",
-                         |  "downloadUrl" : "${JmapRfc8621Configuration.LOCALHOST_URL_PREFIX}/$downloadPath",
-                         |  "uploadUrl" : "${JmapRfc8621Configuration.LOCALHOST_URL_PREFIX}/upload",
-                         |  "eventSourceUrl" : "${JmapRfc8621Configuration.LOCALHOST_URL_PREFIX}/eventSource",
+                         |  "apiUrl" : "$LOCALHOST_URL_PREFIX/jmap",
+                         |  "downloadUrl" : "$LOCALHOST_URL_PREFIX/$downloadPath",
+                         |  "uploadUrl" : "$LOCALHOST_URL_PREFIX/upload/{accountId}",
+                         |  "eventSourceUrl" : "$LOCALHOST_URL_PREFIX/eventSource",
                          |  "state" : "000001"
                          |}""".stripMargin
 
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
index 1c84bae..5d82b91 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
@@ -43,7 +43,7 @@ class JmapRfc8621ConfigurationTest extends AnyWordSpec with Matchers {
 
       jmapRfc8621Configuration.apiUrl must be(new URL("http://random-domain.com/jmap"))
       jmapRfc8621Configuration.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}/?type={type}&name={name}"))
-      jmapRfc8621Configuration.uploadUrl must be(new URL("http://random-domain.com/upload"))
+      jmapRfc8621Configuration.uploadUrl must be(new URL("http://random-domain.com/upload/{accountId}"))
       jmapRfc8621Configuration.eventSourceUrl must be(new URL("http://random-domain.com/eventSource"))
     }
 
@@ -52,7 +52,7 @@ class JmapRfc8621ConfigurationTest extends AnyWordSpec with Matchers {
 
       jmapRfc8621Configuration.apiUrl must be(new URL("http://localhost/jmap"))
       jmapRfc8621Configuration.downloadUrl must be(new URL("http://localhost/download/{accountId}/{blobId}/?type={type}&name={name}"))
-      jmapRfc8621Configuration.uploadUrl must be(new URL("http://localhost/upload"))
+      jmapRfc8621Configuration.uploadUrl must be(new URL("http://localhost/upload/{accountId}"))
       jmapRfc8621Configuration.eventSourceUrl must be(new URL("http://localhost/eventSource"))
     }
   }


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


[james-project] 02/05: JAMES-3432 Advertise max upload size in the session

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d1912b97dd66c5e381d2d4160f439de14deb08c6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 29 12:42:05 2020 +0700

    JAMES-3432 Advertise max upload size in the session
    
    Note that methods only need to specify capability identifier (refactoring)
---
 .../rfc8621/contract/SessionRoutesContract.scala   |  4 ++--
 .../apache/james/jmap/http/SessionSupplier.scala   |  8 ++++---
 .../apache/james/jmap/method/CoreEchoMethod.scala  |  6 ++---
 .../apache/james/jmap/method/EmailGetMethod.scala  | 16 +++++--------
 .../james/jmap/method/EmailQueryMethod.scala       |  7 +++---
 .../apache/james/jmap/method/EmailSetMethod.scala  |  7 +++---
 .../james/jmap/method/MailboxGetMethod.scala       |  7 +++---
 .../james/jmap/method/MailboxQueryMethod.scala     |  5 ++---
 .../james/jmap/method/MailboxSetMethod.scala       |  7 +++---
 .../org/apache/james/jmap/method/Method.scala      |  4 ++--
 .../jmap/method/VacationResponseGetMethod.scala    |  7 +++---
 .../jmap/method/VacationResponseSetMethod.scala    |  7 +++---
 .../org/apache/james/jmap/model/Capabilities.scala | 14 ++++++------
 .../org/apache/james/jmap/model/Capability.scala   | 13 ++++++++++-
 .../jmap/model/JmapRfc8621Configuration.scala      |  9 +++++---
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  6 ++---
 .../apache/james/jmap/routes/UploadRoutes.scala    | 26 +++++++++++-----------
 .../apache/james/jmap/http/SessionRoutesTest.scala |  4 ++--
 .../james/jmap/routes/JMAPApiRoutesTest.scala      |  7 +++---
 19 files changed, 83 insertions(+), 81 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index 7232f15..1355d78 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -42,7 +42,7 @@ object SessionRoutesContract {
   private val expected_session_object: String = """{
                          |  "capabilities" : {
                          |    "urn:ietf:params:jmap:core" : {
-                         |      "maxSizeUpload" : 10000000,
+                         |      "maxSizeUpload" : 31457280,
                          |      "maxConcurrentUpload" : 4,
                          |      "maxSizeRequest" : 10000000,
                          |      "maxConcurrentRequests" : 4,
@@ -70,7 +70,7 @@ object SessionRoutesContract {
                          |      "isReadOnly" : false,
                          |      "accountCapabilities" : {
                          |        "urn:ietf:params:jmap:core" : {
-                         |          "maxSizeUpload" : 10000000,
+                         |          "maxSizeUpload" : 31457280,
                          |          "maxConcurrentUpload" : 4,
                          |          "maxSizeRequest" : 10000000,
                          |          "maxConcurrentRequests" : 4,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
index 3e625b5..0aa6bbc 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
@@ -26,10 +26,12 @@ import org.apache.james.jmap.model._
 import reactor.core.scala.publisher.SMono
 
 class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration){
+  private val maxSizeUpload = configuration.maxUploadSize
+
   def generate(username: Username): SMono[Session] = {
     accounts(username)
       .map(account => Session(
-        DefaultCapabilities.SUPPORTED,
+        DefaultCapabilities.supported(maxSizeUpload),
         List(account),
         primaryAccounts(account.accountId),
         username,
@@ -40,13 +42,13 @@ class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration){
   }
 
   private def accounts(username: Username): SMono[Account] = SMono.defer(() =>
-    Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.SUPPORTED.toSet) match {
+    Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.supported(maxSizeUpload).toSet) match {
       case Left(ex: IllegalArgumentException) => SMono.raiseError(ex)
       case Right(account: Account) => SMono.just(account)
     })
 
   private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
-    DefaultCapabilities.SUPPORTED.toSet
+    DefaultCapabilities.supported(maxSizeUpload).toSet
       .map(capability => (capability.identifier(), accountId))
       .toMap
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEchoMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEchoMethod.scala
index 7bba42e..c93e05c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEchoMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEchoMethod.scala
@@ -20,10 +20,8 @@ package org.apache.james.jmap.method
 
 
 import eu.timepit.refined.auto._
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.CORE_CAPABILITY
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE}
 import org.apache.james.jmap.model.Invocation.MethodName
-import org.apache.james.jmap.model.{Capabilities}
 import org.apache.james.mailbox.MailboxSession
 import org.reactivestreams.Publisher
 import reactor.core.scala.publisher.SMono
@@ -33,5 +31,5 @@ class CoreEchoMethod extends Method {
 
   override def process(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession): Publisher[InvocationWithContext] = SMono.just(invocation)
 
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE)
 }
\ No newline at end of file
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 296b836..5201c5a 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
@@ -23,16 +23,14 @@ import java.time.ZoneId
 import eu.timepit.refined.auto._
 import eu.timepit.refined.types.string.NonEmptyString
 import javax.inject.Inject
-import org.apache.james.jmap.api.model.Preview
 import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{EmailGetSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.Email.UnparsedEmailId
 import org.apache.james.jmap.mail.{Email, EmailBodyPart, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound, EmailView, EmailViewReaderFactory, SpecificHeaderRequest}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.State.INSTANCE
-import org.apache.james.jmap.model.{AccountId, Capabilities, ErrorCode, Invocation, Properties}
+import org.apache.james.jmap.model.{AccountId, ErrorCode, Invocation, Properties}
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.mailbox.model.MessageId
 import org.apache.james.metrics.api.MetricFactory
@@ -79,12 +77,10 @@ class SystemZoneIdProvider extends ZoneIdProvider {
 
 class EmailGetMethod @Inject() (readerFactory: EmailViewReaderFactory,
                                 messageIdFactory: MessageId.Factory,
-                                zoneIdProvider: ZoneIdProvider,
-                                previewFactory: Preview.Factory,
                                 val metricFactory: MetricFactory,
                                 val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[EmailGetRequest] {
-  override val methodName = MethodName("Email/get")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val methodName: MethodName = MethodName("Email/get")
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailGetRequest): SMono[InvocationWithContext] = {
     computeResponseInvocation(request, invocation.invocation, mailboxSession).onErrorResume({
@@ -100,7 +96,7 @@ class EmailGetMethod @Inject() (readerFactory: EmailViewReaderFactory,
       .flatMap(properties => validateBodyProperties(request).map((properties, _)))
       .fold(
         e => SMono.raiseError(e), {
-          case (properties, bodyProperties) => getEmails(request, properties, mailboxSession)
+          case (properties, bodyProperties) => getEmails(request, mailboxSession)
             .map(response => Invocation(
               methodName = methodName,
               arguments = Arguments(EmailGetSerializer.serialize(response, properties, bodyProperties).as[JsObject]),
@@ -143,7 +139,7 @@ class EmailGetMethod @Inject() (readerFactory: EmailViewReaderFactory,
       case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
     }
 
-  private def getEmails(request: EmailGetRequest, properties: Properties, mailboxSession: MailboxSession): SMono[EmailGetResponse] =
+  private def getEmails(request: EmailGetRequest, mailboxSession: MailboxSession): SMono[EmailGetResponse] =
     request.ids match {
       case None => SMono.raiseError(new IllegalArgumentException("ids can not be ommited for email/get"))
       case Some(ids) => getEmails(ids, mailboxSession, request)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index 86005c1..06fa529 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -24,12 +24,11 @@ import javax.inject.Inject
 import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse, UnsupportedRequestParameterException}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.jmap.model.Position.Position
-import org.apache.james.jmap.model.{CanCalculateChanges, Capabilities, Invocation, Limit, Position, QueryState}
+import org.apache.james.jmap.model.{CanCalculateChanges, Invocation, Limit, Position, QueryState}
 import org.apache.james.jmap.utils.search.MailboxFilter
 import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery
@@ -45,7 +44,7 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
                                   val metricFactory: MetricFactory,
                                   val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[EmailQueryRequest] {
   override val methodName: MethodName = MethodName("Email/query")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailQueryRequest): SMono[InvocationWithContext] = {
     processRequest(mailboxSession, invocation.invocation, request, capabilities)
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 5cf9af6..8686751 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
@@ -30,12 +30,11 @@ import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{EmailSetSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
 import org.apache.james.jmap.mail.{DestroyIds, EmailCreationRequest, EmailCreationResponse, EmailSet, EmailSetRequest, EmailSetResponse, MailboxIds, ValidatedEmailSetUpdate}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY
 import org.apache.james.jmap.model.SetError.SetErrorDescription
-import org.apache.james.jmap.model.{Capabilities, ClientId, Id, Invocation, ServerId, SetError, State, UTCDate}
+import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId, SetError, State, UTCDate}
 import org.apache.james.mailbox.MessageManager.{AppendCommand, FlagsUpdateMode}
 import org.apache.james.mailbox.exception.MailboxNotFoundException
 import org.apache.james.mailbox.model.{ComposedMessageIdWithMetaData, DeleteResult, MailboxId, MessageId, MessageRange}
@@ -149,7 +148,7 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
   }
 
   override val methodName: MethodName = MethodName("Email/set")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailSetRequest): SMono[InvocationWithContext] = {
     for {
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 154da66..0b41593 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,11 +25,10 @@ import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
 import org.apache.james.jmap.mail._
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.State.INSTANCE
-import org.apache.james.jmap.model.{AccountId, Capabilities, CapabilityIdentifier, ErrorCode, Invocation, MailboxFactory, Properties, Subscriptions}
+import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier, ErrorCode, Invocation, MailboxFactory, Properties, Subscriptions}
 import org.apache.james.jmap.utils.quotas.{QuotaLoader, QuotaLoaderWithPreloadedDefaultFactory}
 import org.apache.james.mailbox.exception.MailboxNotFoundException
 import org.apache.james.mailbox.model.search.MailboxQuery
@@ -52,7 +51,7 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
                                   val metricFactory: MetricFactory,
                                   val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[MailboxGetRequest] {
   override val methodName: MethodName = MethodName("Mailbox/get")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   object MailboxGetResults {
     def merge(result1: MailboxGetResults, result2: MailboxGetResults): MailboxGetResults = result1.merge(result2)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
index 8070ab7..d390d0f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
@@ -23,8 +23,7 @@ import javax.inject.Inject
 import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{MailboxQuerySerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.{MailboxQueryRequest, MailboxQueryResponse}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model._
 import org.apache.james.mailbox.{MailboxSession, SystemMailboxesProvider}
@@ -37,7 +36,7 @@ class MailboxQueryMethod @Inject()(systemMailboxesProvider: SystemMailboxesProvi
                                    val metricFactory: MetricFactory,
                                    val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[MailboxQueryRequest] {
   override val methodName = MethodName("Mailbox/query")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: MailboxQueryRequest): SMono[InvocationWithContext] = {
     processRequest(mailboxSession, invocation.invocation, request)
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 6a40581..ff59c84 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
@@ -26,11 +26,10 @@ import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
 import org.apache.james.jmap.mail.MailboxSetRequest.MailboxCreationId
 import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxGet, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, RemoveEmailsOnDestroy, ServerSetPropertyException, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException, ValidatedMai [...]
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.SetError.SetErrorDescription
-import org.apache.james.jmap.model.{Capabilities, ClientId, Id, Invocation, Properties, ServerId, SetError, State}
+import org.apache.james.jmap.model.{ClientId, Id, Invocation, Properties, ServerId, SetError, State}
 import org.apache.james.jmap.routes.ProcessingContext
 import org.apache.james.jmap.utils.quotas.QuotaLoaderWithPreloadedDefaultFactory
 import org.apache.james.mailbox.MailboxManager.RenameOption
@@ -153,7 +152,7 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
                                  val metricFactory: MetricFactory,
                                  val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[MailboxSetRequest] {
   override val methodName: MethodName = MethodName("Mailbox/set")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: MailboxSetRequest): SMono[InvocationWithContext] = for {
     creationResultsWithUpdatedProcessingContext <- createMailboxes(mailboxSession, request, invocation.processingContext)
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 824e70b..c2e52d9 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
@@ -23,7 +23,7 @@ import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.mail.{UnsupportedFilterException, UnsupportedNestingException, UnsupportedRequestParameterException, UnsupportedSortException}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Invocation.MethodName
-import org.apache.james.jmap.model.{AccountId, Capabilities, ErrorCode, Invocation, Session}
+import org.apache.james.jmap.model.{AccountId, ErrorCode, Invocation, Session}
 import org.apache.james.jmap.routes.ProcessingContext
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.mailbox.exception.MailboxNotFoundException
@@ -38,7 +38,7 @@ trait Method {
 
   val methodName: MethodName
 
-  val requiredCapabilities: Capabilities
+  val requiredCapabilities: Set[CapabilityIdentifier]
 
   def process(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession): Publisher[InvocationWithContext]
 }
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 ebbc939..05cbfdb 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
@@ -26,11 +26,10 @@ import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{ResponseSerializer, VacationSerializer}
 import org.apache.james.jmap.mail.VacationResponse.{UNPARSED_SINGLETON, UnparsedVacationResponseId}
 import org.apache.james.jmap.mail.{VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseNotFound}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
 import org.apache.james.jmap.model.State.INSTANCE
-import org.apache.james.jmap.model.{AccountId, Capabilities, ErrorCode, Invocation, MissingCapabilityException, Properties}
+import org.apache.james.jmap.model.{AccountId, ErrorCode, Invocation, MissingCapabilityException, Properties}
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.metrics.api.MetricFactory
 import play.api.libs.json.{JsError, JsObject, JsSuccess}
@@ -60,7 +59,7 @@ class VacationResponseGetMethod @Inject()(vacationRepository: VacationRepository
                                           val metricFactory: MetricFactory,
                                           val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[VacationResponseGetRequest] {
   override val methodName: MethodName = MethodName("VacationResponse/get")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: VacationResponseGetRequest): SMono[InvocationWithContext] = {
     {
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 66e52e0..c43978b 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
@@ -25,11 +25,10 @@ import org.apache.james.jmap.api.vacation.{AccountId, VacationPatch, VacationRep
 import org.apache.james.jmap.http.SessionSupplier
 import org.apache.james.jmap.json.{ResponseSerializer, VacationSerializer}
 import org.apache.james.jmap.method.VacationResponseSetMethod.VACATION_RESPONSE_PATCH_OBJECT_KEY
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.SetError.SetErrorDescription
-import org.apache.james.jmap.model.{Capabilities, Invocation, State}
+import org.apache.james.jmap.model.{Invocation, State}
 import org.apache.james.jmap.vacation.{VacationResponseSetError, VacationResponseSetRequest, VacationResponseSetResponse, VacationResponseUpdateResponse}
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.metrics.api.MetricFactory
@@ -74,7 +73,7 @@ class VacationResponseSetMethod @Inject()(vacationRepository: VacationRepository
                                           val metricFactory: MetricFactory,
                                           val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[VacationResponseSetRequest] {
   override val methodName: MethodName = MethodName("VacationResponse/set")
-  override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY)
+  override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: VacationResponseSetRequest): SMono[InvocationWithContext] = {
     update(mailboxSession, request)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
index 48f9954..fc96632 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
@@ -19,12 +19,12 @@
 package org.apache.james.jmap.model
 
 import eu.timepit.refined.auto._
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 
 object DefaultCapabilities {
-  val CORE_CAPABILITY = CoreCapability(
+  def coreCapability(maxUploadSize: MaxSizeUpload) = CoreCapability(
     properties = CoreCapabilityProperties(
-      MaxSizeUpload(10_000_000L),
+      maxUploadSize,
       MaxConcurrentUpload(4L),
       MaxSizeRequest(10_000_000L),
       MaxConcurrentRequests(4L),
@@ -40,9 +40,7 @@ object DefaultCapabilities {
       MaxSizeMailboxName(200L),
       MaxSizeAttachmentsPerEmail(20_000_000L),
       emailQuerySortOptions = List("receivedAt", "sentAt"),
-      MayCreateTopLevelMailbox(true)
-    )
-  )
+      MayCreateTopLevelMailbox(true)))
 
   val QUOTA_CAPABILITY = QuotaCapability()
 
@@ -50,7 +48,9 @@ object DefaultCapabilities {
 
   val VACATION_RESPONSE_CAPABILITY = VacationResponseCapability()
 
-  val SUPPORTED = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, QUOTA_CAPABILITY, SHARES_CAPABILITY, VACATION_RESPONSE_CAPABILITY)
+  val SUPPORTED_CAPABILITY_IDENTIFIERS = Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JAMES_SHARES, JAMES_QUOTA)
+
+  def supported(maxUploadSize: MaxSizeUpload) = Capabilities(coreCapability(maxUploadSize), MAIL_CAPABILITY, QUOTA_CAPABILITY, SHARES_CAPABILITY, VACATION_RESPONSE_CAPABILITY)
 }
 
 case class Capabilities(capabilities: Capability*) {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
index fbabaf9..4f1202d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.model
 
+import eu.timepit.refined
 import eu.timepit.refined.api.Refined
 import eu.timepit.refined.auto._
 import eu.timepit.refined.collection.NonEmpty
@@ -26,7 +27,10 @@ import eu.timepit.refined.string.Uri
 import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.model.CoreCapabilityProperties.CollationAlgorithm
 import org.apache.james.jmap.model.MailCapability.EmailQuerySortOption
-import org.apache.james.jmap.model.UnsignedInt.UnsignedInt
+import org.apache.james.jmap.model.UnsignedInt.{UnsignedInt, UnsignedIntConstraint}
+import org.apache.james.util.Size
+
+import scala.util.{Failure, Success, Try}
 
 sealed trait CapabilityValidationException extends IllegalArgumentException
 case class MissingCapabilityException(description: String) extends CapabilityValidationException
@@ -50,6 +54,13 @@ trait Capability {
 final case class CoreCapability(properties: CoreCapabilityProperties,
                                 identifier: CapabilityIdentifier = JMAP_CORE) extends Capability
 
+object MaxSizeUpload {
+  def of(size: Size): Try[MaxSizeUpload] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match {
+    case Right(value) => Success(MaxSizeUpload(value))
+    case Left(error) => Failure(new NumberFormatException(error))
+  }
+}
+
 case class MaxSizeUpload(value: UnsignedInt)
 case class MaxConcurrentUpload(value: UnsignedInt)
 case class MaxSizeRequest(value: UnsignedInt)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
index f54636b..994507e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
@@ -27,7 +27,7 @@ import org.apache.james.util.Size
 
 object JmapRfc8621Configuration {
   val LOCALHOST_URL_PREFIX: String = "http://localhost"
-  val UPLOAD_LIMIT_30_MB: Size = Size.of(30, Size.Unit.M)
+  val UPLOAD_LIMIT_30_MB: MaxSizeUpload = MaxSizeUpload.of(Size.of(30L, Size.Unit.M)).get
   val LOCALHOST_CONFIGURATION: JmapRfc8621Configuration = JmapRfc8621Configuration(LOCALHOST_URL_PREFIX, UPLOAD_LIMIT_30_MB)
   val URL_PREFIX_PROPERTIES: String = "url.prefix"
   val UPLOAD_LIMIT_PROPERTIES: String = "upload.max.size"
@@ -35,11 +35,14 @@ object JmapRfc8621Configuration {
   def from(configuration: Configuration): JmapRfc8621Configuration = {
     JmapRfc8621Configuration(
       urlPrefixString = Option(configuration.getString(URL_PREFIX_PROPERTIES)).getOrElse(LOCALHOST_URL_PREFIX),
-      maxUploadSize = Option(configuration.getString(UPLOAD_LIMIT_PROPERTIES, null)).map(Size.parse).getOrElse(UPLOAD_LIMIT_30_MB))
+      maxUploadSize = Option(configuration.getString(UPLOAD_LIMIT_PROPERTIES, null))
+        .map(Size.parse)
+        .map(MaxSizeUpload.of(_).get)
+        .getOrElse(UPLOAD_LIMIT_30_MB))
   }
 }
 
-case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: Size = UPLOAD_LIMIT_30_MB) {
+case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: MaxSizeUpload = UPLOAD_LIMIT_30_MB) {
   val urlPrefix: URL = new URL(urlPrefixString)
   val apiUrl: URL = new URL(s"$urlPrefixString/jmap")
   val downloadUrl: URL = new URL(urlPrefixString + "/download/$accountId/$blobId/?type=$type&name=$name")
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index 790cecb..d58912b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -113,7 +113,7 @@ class JMAPApiRoutes (val authenticator: Authenticator,
                       httpServerResponse: HttpServerResponse,
                       mailboxSession: MailboxSession): SMono[Void] = {
     val processingContext: ProcessingContext = ProcessingContext(Map.empty, Map.empty)
-    val unsupportedCapabilities = requestObject.using.toSet -- DefaultCapabilities.SUPPORTED.ids
+    val unsupportedCapabilities = requestObject.using.toSet -- DefaultCapabilities.SUPPORTED_CAPABILITY_IDENTIFIERS
     val capabilities: Set[CapabilityIdentifier] = requestObject.using.toSet
 
     if (unsupportedCapabilities.nonEmpty) {
@@ -168,8 +168,8 @@ class JMAPApiRoutes (val authenticator: Authenticator,
       .onErrorResume(throwable => SMono.just(InvocationWithContext(Invocation.error(ErrorCode.ServerFail, throwable.getMessage, invocation.invocation.methodCallId), invocation.processingContext)))
       .switchIfEmpty(SMono.just(InvocationWithContext(Invocation.error(ErrorCode.UnknownMethod, invocation.invocation.methodCallId), invocation.processingContext)))
 
-  private def validateCapabilities(capabilities: Set[CapabilityIdentifier], requiredCapabilities: Capabilities): Either[MissingCapabilityException, Unit] = {
-    val missingCapabilities = requiredCapabilities.ids -- capabilities
+  private def validateCapabilities(capabilities: Set[CapabilityIdentifier], requiredCapabilities: Set[CapabilityIdentifier]): Either[MissingCapabilityException, Unit] = {
+    val missingCapabilities = requiredCapabilities -- capabilities
     if (missingCapabilities.nonEmpty) {
       Left(MissingCapabilityException(s"Missing capability(ies): ${missingCapabilities.mkString(", ")}"))
     } else {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
index 452543c..d8aa79c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
@@ -24,32 +24,32 @@ import java.util.stream
 import java.util.stream.Stream
 
 import eu.timepit.refined.api.Refined
+import eu.timepit.refined.auto._
+import eu.timepit.refined.numeric.NonNegative
+import eu.timepit.refined.refineV
 import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
-import io.netty.handler.codec.http.HttpResponseStatus.{BAD_REQUEST, CREATED}
 import io.netty.handler.codec.http.HttpMethod
+import io.netty.handler.codec.http.HttpResponseStatus.{BAD_REQUEST, CREATED}
 import javax.inject.{Inject, Named}
-import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
+import org.apache.commons.fileupload.util.LimitedInputStream
+import org.apache.james.jmap.exceptions.UnauthorizedException
 import org.apache.james.jmap.http.Authenticator
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
+import org.apache.james.jmap.json.UploadSerializer
+import org.apache.james.jmap.mail.BlobId
 import org.apache.james.jmap.mail.Email.Size
+import org.apache.james.jmap.model.Id.Id
+import org.apache.james.jmap.model.{AccountId, Id, JmapRfc8621Configuration}
 import org.apache.james.jmap.routes.UploadRoutes.{LOGGER, sanitizeSize}
-import org.apache.james.mailbox.{AttachmentManager, MailboxSession}
+import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
 import org.apache.james.mailbox.model.{AttachmentMetadata, ContentType}
+import org.apache.james.mailbox.{AttachmentManager, MailboxSession}
 import org.apache.james.util.ReactorUtils
 import org.slf4j.{Logger, LoggerFactory}
 import reactor.core.publisher.Mono
 import reactor.core.scala.publisher.SMono
 import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
-import eu.timepit.refined.auto._
-import eu.timepit.refined.numeric.NonNegative
-import eu.timepit.refined.refineV
-import org.apache.commons.fileupload.util.LimitedInputStream
-import org.apache.james.jmap.exceptions.UnauthorizedException
-import org.apache.james.jmap.json.UploadSerializer
-import org.apache.james.jmap.mail.BlobId
-import org.apache.james.jmap.model.{AccountId, Id, JmapRfc8621Configuration}
-import org.apache.james.jmap.model.Id.Id
 
 object UploadRoutes {
   val LOGGER: Logger = LoggerFactory.getLogger(classOf[DownloadRoutes])
@@ -131,7 +131,7 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
   }
 
   def handle(accountId: AccountId, contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] = {
-    val maxSize: Long = configuration.maxUploadSize.asBytes()
+    val maxSize: Long = configuration.maxUploadSize.value.value
 
     SMono.fromCallable(() => new LimitedInputStream(content, maxSize) {
       override def raiseError(max: Long, count: Long): Unit = if (count > max) {
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index a1a9e15..46c1dca 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -118,7 +118,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
     val expectedJson = s"""{
                          |  "capabilities" : {
                          |    "urn:ietf:params:jmap:core" : {
-                         |      "maxSizeUpload" : 10000000,
+                         |      "maxSizeUpload" : 31457280,
                          |      "maxConcurrentUpload" : 4,
                          |      "maxSizeRequest" : 10000000,
                          |      "maxConcurrentRequests" : 4,
@@ -146,7 +146,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |      "isReadOnly" : false,
                          |      "accountCapabilities" : {
                          |        "urn:ietf:params:jmap:core" : {
-                         |          "maxSizeUpload" : 10000000,
+                         |          "maxSizeUpload" : 31457280,
                          |          "maxConcurrentUpload" : 4,
                          |          "maxSizeRequest" : 10000000,
                          |          "maxConcurrentRequests" : 4,
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index b9f23bc..379a71f 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -38,10 +38,9 @@ import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap._
 import org.apache.james.jmap.http.{Authenticator, BasicAuthenticationStrategy, MailboxesProvisioner, SessionSupplier, UserProvisioning}
 import org.apache.james.jmap.method.{CoreEchoMethod, Method}
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.DefaultCapabilities.CORE_CAPABILITY
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE}
 import org.apache.james.jmap.model.Invocation.MethodName
-import org.apache.james.jmap.model.{Capabilities, JmapRfc8621Configuration, RequestLevelErrorType}
+import org.apache.james.jmap.model.{JmapRfc8621Configuration, RequestLevelErrorType}
 import org.apache.james.jmap.routes.JMAPApiRoutesTest._
 import org.apache.james.mailbox.extension.PreDeletionHook
 import org.apache.james.mailbox.inmemory.{InMemoryMailboxManager, MemoryMailboxManagerProvider}
@@ -440,7 +439,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
       .process(any[Set[CapabilityIdentifier]], any(), any())
 
     when(mockCoreEchoMethod.methodName).thenReturn(MethodName("Core/echo"))
-    when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Capabilities(CORE_CAPABILITY))
+    when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Set(JMAP_CORE))
 
     val methods: Set[Method] = Set(mockCoreEchoMethod)
     val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, mailboxesProvisioner, methods, sessionSupplier)


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


[james-project] 05/05: [WEBSITE] Community post: Performance testing for James with JMeter

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d81a9e6159473ee4aa7cbfc393cb01d6a83112dd
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 29 08:38:09 2020 +0700

    [WEBSITE] Community post: Performance testing for James with JMeter
---
 .../2020-10-29-testing-james-with-jmeter.markdown    | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/src/homepage/_posts/2020-10-29-testing-james-with-jmeter.markdown b/src/homepage/_posts/2020-10-29-testing-james-with-jmeter.markdown
new file mode 100644
index 0000000..9afd3ae
--- /dev/null
+++ b/src/homepage/_posts/2020-10-29-testing-james-with-jmeter.markdown
@@ -0,0 +1,20 @@
+---
+layout: post
+title:  "Performance testing for James with JMeter"
+date:   2020-10-29  15:16:30 +0200
+categories: community
+---
+
+Ever wanted to figure out what Apache James gets in its guts?
+
+Xian Long detailed in this [blog post][post] how to be using JMeter in order to run some
+IMAP performance tests on top of the distributed server, using [JMeter][jmeter]
+
+[Other tools][james-gatling], based on [Gatling][gatling] had been developed within the community, addressing SMTP,
+IMAP and [JMAP][JMAP] protocols.
+
+[post]: https://www.cnblogs.com/hanxianlong/p/13894595.html
+[jmeter]: https://jmeter.apache.org/
+[gatling]: https://gatling.io/
+[JMAP]: https://jmap.io/
+[james-gatling]: https://github.com/linagora/james-gatling


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


[james-project] 01/05: JAMES-3432 Limit size attachment

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit bb1fc014db9f0c29bb5d41b3db7edcef26cabebf
Author: duc91 <vd...@linagora.com>
AuthorDate: Mon Oct 26 18:03:02 2020 +0700

    JAMES-3432 Limit size attachment
---
 .../servers/pages/distributed/configure/jmap.adoc  |  8 +++++
 .../src/main/java/org/apache/james/util/Size.java  |  8 ++++-
 .../jmap/rfc8621/contract/UploadContract.scala     | 16 +++++-----
 .../src/test/resources/jmap.properties             |  3 +-
 server/protocols/jmap-rfc-8621/pom.xml             |  5 ++++
 .../jmap/model/JmapRfc8621Configuration.scala      | 16 ++++++----
 .../apache/james/jmap/routes/UploadRoutes.scala    | 34 ++++++++++++++++------
 src/site/xdoc/server/config-jmap.xml               |  8 +++++
 8 files changed, 73 insertions(+), 25 deletions(-)

diff --git a/docs/modules/servers/pages/distributed/configure/jmap.adoc b/docs/modules/servers/pages/distributed/configure/jmap.adoc
index 0b5cce7..f2b55d0 100644
--- a/docs/modules/servers/pages/distributed/configure/jmap.adoc
+++ b/docs/modules/servers/pages/distributed/configure/jmap.adoc
@@ -29,6 +29,14 @@ This should not be the same keystore than the ones used by TLS based protocols.
 | jwt.publickeypem.url
 | Optional. JWT tokens allow request to bypass authentication
 
+| url.prefix
+| Optional. Configuration urlPrefix for JMAP routes.
+| Default value: http://localhost.
+
+| upload.max.size
+| Optional. Configuration max size Upload in new JMAP-RFC-8621.
+| Default value: 30M. Supported units are B (bytes) K (KB) M (MB) G (GB).
+
 |===
 
 == Wire tapping
diff --git a/server/container/util/src/main/java/org/apache/james/util/Size.java b/server/container/util/src/main/java/org/apache/james/util/Size.java
index 341a0e7..263fb54 100644
--- a/server/container/util/src/main/java/org/apache/james/util/Size.java
+++ b/server/container/util/src/main/java/org/apache/james/util/Size.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.util;
 
+import com.google.common.base.Preconditions;
 import com.google.common.math.LongMath;
 
 /**
@@ -35,7 +36,7 @@ public class Size {
      * supported units : B ( 2^0 ), K ( 2^10 ), M ( 2^20 ), G ( 2^30 )
      * See  RFC822.SIZE
      */
-    private enum Unit {
+    public enum Unit {
         NoUnit,
         B,
         K,
@@ -66,6 +67,11 @@ public class Size {
         return new Size(unit, Long.parseLong(argWithoutUnit));
     }
 
+    public static Size of(Long value, Unit unit) {
+        Preconditions.checkArgument(value >= 0, "Maxsize must be positive");
+        return new Size(unit, value);
+    }
+
     public Unit getUnit() {
         return unit;
     }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
index 770efcd..fe35d0f 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/UploadContract.scala
@@ -24,21 +24,20 @@ import java.nio.charset.StandardCharsets
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, requestSpecification}
 import io.restassured.http.ContentType
-import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.commons.io.IOUtils
-import org.apache.http.HttpStatus.{SC_CREATED, SC_NOT_FOUND, SC_OK, SC_UNAUTHORIZED}
+import org.apache.http.HttpStatus.{SC_BAD_REQUEST, SC_CREATED, SC_OK, SC_UNAUTHORIZED}
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ALICE, ALICE_ACCOUNT_ID, ALICE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, RFC8621_VERSION_HEADER, _2_DOT_DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.jmap.rfc8621.contract.UploadContract.{BIG_INPUT_STREAM, VALID_INPUT_STREAM}
 import org.apache.james.utils.DataProbeImpl
 import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.{BeforeEach, Disabled, Test}
+import org.junit.jupiter.api.{BeforeEach, Test}
 import play.api.libs.json.{JsString, Json}
 
 object UploadContract {
-  private val BIG_INPUT_STREAM: InputStream = new ByteArrayInputStream("123456789\r\n".repeat(10025).getBytes)
-  private val VALID_INPUT_STREAM: InputStream = new ByteArrayInputStream("123456789\r\n".repeat(1).getBytes)
+  private val BIG_INPUT_STREAM: InputStream = new ByteArrayInputStream("123456789\r\n".repeat(1024 * 1024 * 4).getBytes)
+  private val VALID_INPUT_STREAM: InputStream = new ByteArrayInputStream("123456789\r\n".repeat(1024 * 1024 * 3).getBytes)
 }
 
 trait UploadContract {
@@ -128,7 +127,6 @@ trait UploadContract {
   }
 
   @Test
-  @Disabled("JAMES-1788 Upload size limitation needs to be contributed")
   def shouldRejectWhenUploadFileTooBig(): Unit = {
     val response: String = `given`
       .basePath("")
@@ -138,13 +136,13 @@ trait UploadContract {
     .when
       .post(s"/upload/$ACCOUNT_ID/")
     .`then`
-      .statusCode(SC_OK)
+      .statusCode(SC_BAD_REQUEST)
       .extract
       .body
       .asString
 
-    assertThatJson(response)
-      .isEqualTo("Should be error")
+    assertThat(response)
+      .contains("Attempt to upload exceed max size")
   }
 
   @Test
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/jmap.properties b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/jmap.properties
index a0da434..0128031 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/jmap.properties
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/jmap.properties
@@ -1,2 +1,3 @@
 # Configuration urlPrefix for JMAP routes.
-url.prefix=http://domain.com
\ No newline at end of file
+url.prefix=http://domain.com
+upload.max.size=30M
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 520910a..048b51e 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -96,6 +96,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
             <groupId>com.github.dpaukov</groupId>
             <artifactId>combinatoricslib3</artifactId>
             <scope>test</scope>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
index 8865d05..f54636b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
@@ -22,18 +22,24 @@ package org.apache.james.jmap.model
 import java.net.URL
 
 import org.apache.commons.configuration2.Configuration
+import org.apache.james.jmap.model.JmapRfc8621Configuration.UPLOAD_LIMIT_30_MB
+import org.apache.james.util.Size
 
 object JmapRfc8621Configuration {
   val LOCALHOST_URL_PREFIX: String = "http://localhost"
-  private def from(urlPrefixString: String): JmapRfc8621Configuration = JmapRfc8621Configuration(urlPrefixString)
-  var LOCALHOST_CONFIGURATION: JmapRfc8621Configuration = from(LOCALHOST_URL_PREFIX)
+  val UPLOAD_LIMIT_30_MB: Size = Size.of(30, Size.Unit.M)
+  val LOCALHOST_CONFIGURATION: JmapRfc8621Configuration = JmapRfc8621Configuration(LOCALHOST_URL_PREFIX, UPLOAD_LIMIT_30_MB)
   val URL_PREFIX_PROPERTIES: String = "url.prefix"
+  val UPLOAD_LIMIT_PROPERTIES: String = "upload.max.size"
 
-  def from(configuration: Configuration): JmapRfc8621Configuration =
-    JmapRfc8621Configuration(Option(configuration.getString(URL_PREFIX_PROPERTIES)).getOrElse(LOCALHOST_URL_PREFIX))
+  def from(configuration: Configuration): JmapRfc8621Configuration = {
+    JmapRfc8621Configuration(
+      urlPrefixString = Option(configuration.getString(URL_PREFIX_PROPERTIES)).getOrElse(LOCALHOST_URL_PREFIX),
+      maxUploadSize = Option(configuration.getString(UPLOAD_LIMIT_PROPERTIES, null)).map(Size.parse).getOrElse(UPLOAD_LIMIT_30_MB))
+  }
 }
 
-case class JmapRfc8621Configuration(urlPrefixString: String) {
+case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: Size = UPLOAD_LIMIT_30_MB) {
   val urlPrefix: URL = new URL(urlPrefixString)
   val apiUrl: URL = new URL(s"$urlPrefixString/jmap")
   val downloadUrl: URL = new URL(urlPrefixString + "/download/$accountId/$blobId/?type=$type&name=$name")
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
index 6e37695..452543c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/UploadRoutes.scala
@@ -19,8 +19,7 @@
 
 package org.apache.james.jmap.routes
 
-import java.io.InputStream
-import java.time.ZonedDateTime
+import java.io.{IOException, InputStream, UncheckedIOException}
 import java.util.stream
 import java.util.stream.Stream
 
@@ -45,10 +44,11 @@ import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
 import eu.timepit.refined.auto._
 import eu.timepit.refined.numeric.NonNegative
 import eu.timepit.refined.refineV
+import org.apache.commons.fileupload.util.LimitedInputStream
 import org.apache.james.jmap.exceptions.UnauthorizedException
 import org.apache.james.jmap.json.UploadSerializer
 import org.apache.james.jmap.mail.BlobId
-import org.apache.james.jmap.model.{AccountId, Id}
+import org.apache.james.jmap.model.{AccountId, Id, JmapRfc8621Configuration}
 import org.apache.james.jmap.model.Id.Id
 
 object UploadRoutes {
@@ -60,7 +60,7 @@ object UploadRoutes {
   def sanitizeSize(value: Long): Size = {
     val size: Either[String, Size] = refineV[NonNegative](value)
     size.fold(e => {
-      LOGGER.error(s"Encountered an invalid Email size: $e")
+      LOGGER.error(s"Encountered an invalid upload files size: $e")
       Zero
     },
       refinedValue => refinedValue)
@@ -73,10 +73,12 @@ case class UploadResponse(accountId: AccountId,
                           size: Size)
 
 class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
+                             val configuration: JmapRfc8621Configuration,
                              val attachmentManager: AttachmentManager,
                              val serializer: UploadSerializer) extends JMAPRoutes {
 
   class CancelledUploadException extends RuntimeException
+  class MaxFileSizeUploadException extends RuntimeException
 
   private val accountIdParam: String = "accountId"
   private val uploadURI = s"/upload/{$accountIdParam}/"
@@ -105,6 +107,9 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
     }
   }
 
+  private def handleUncheckedIOException(response: HttpServerResponse, e: UncheckedIOException) =
+    response.status(BAD_REQUEST).sendString(SMono.just(e.getMessage))
+
   def post(request: HttpServerRequest, response: HttpServerResponse, contentType: ContentType, session: MailboxSession): SMono[Void] = {
     Id.validate(request.param(accountIdParam)) match {
       case Right(id: Id) => {
@@ -125,12 +130,23 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
     }
   }
 
-  def handle(accountId: AccountId, contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] =
-    uploadContent(accountId, contentType, content, mailboxSession)
+  def handle(accountId: AccountId, contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] = {
+    val maxSize: Long = configuration.maxUploadSize.asBytes()
+
+    SMono.fromCallable(() => new LimitedInputStream(content, maxSize) {
+      override def raiseError(max: Long, count: Long): Unit = if (count > max) {
+        throw new IOException("Attempt to upload exceed max size")
+      }})
+      .flatMap(uploadContent(accountId, contentType, _, mailboxSession))
       .flatMap(uploadResponse => SMono.fromPublisher(response
-            .header(CONTENT_TYPE, uploadResponse.`type`.asString())
-            .status(CREATED)
-            .sendString(SMono.just(serializer.serialize(uploadResponse).toString()))))
+              .header(CONTENT_TYPE, uploadResponse.`type`.asString())
+              .status(CREATED)
+              .sendString(SMono.just(serializer.serialize(uploadResponse).toString()))))
+      .onErrorResume {
+        case e: UncheckedIOException => SMono.fromPublisher(handleUncheckedIOException(response, e))
+      }
+  }
+
 
   def uploadContent(accountId: AccountId, contentType: ContentType, inputStream: InputStream, session: MailboxSession): SMono[UploadResponse] =
     SMono
diff --git a/src/site/xdoc/server/config-jmap.xml b/src/site/xdoc/server/config-jmap.xml
index b1d28ec..ce649ac 100644
--- a/src/site/xdoc/server/config-jmap.xml
+++ b/src/site/xdoc/server/config-jmap.xml
@@ -56,6 +56,14 @@
 
                     <dt><strong>jwt.publickeypem.url</strong></dt>
                     <dd>Optional. JWT tokens allows request to bypass authentication</dd>
+
+                    <dt><strong>url.prefix</strong></dt>
+                    <dd>Optional. Configuration urlPrefix for JMAP routes.</dd>
+                    <dd>Default value: http://localhost.</dd>
+
+                    <dt><strong>upload.max.size</strong></dt>
+                    <dd>Optional. Configuration max size Upload in new JMAP-RFC-8621.</dd>
+                    <dd>Default value: 30M. Supported units are B (bytes) K (KB) M (MB) G (GB).</dd>
                 </dl>
 
             </subsection>


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


[james-project] 03/05: JAMES-2891 use rfc6570 for JMAP downloadUrl

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 640cfb73fb4aa949e5552692312a94ba618d9a56
Author: Anton Lazarev <an...@gmail.com>
AuthorDate: Wed Oct 28 17:26:12 2020 -0400

    JAMES-2891 use rfc6570 for JMAP downloadUrl
---
 .../apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala    | 2 +-
 .../scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala  | 2 +-
 .../src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala | 2 +-
 .../org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala    | 4 ++--
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index 1355d78..aac2416 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -102,7 +102,7 @@ object SessionRoutesContract {
                          |  },
                          |  "username" : "bob@domain.tld",
                          |  "apiUrl" : "http://domain.com/jmap",
-                         |  "downloadUrl" : "http://domain.com/download/$accountId/$blobId/?type=$type&name=$name",
+                         |  "downloadUrl" : "http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}",
                          |  "uploadUrl" : "http://domain.com/upload",
                          |  "eventSourceUrl" : "http://domain.com/eventSource",
                          |  "state" : "000001"
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
index 994507e..aea142d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/JmapRfc8621Configuration.scala
@@ -45,7 +45,7 @@ object JmapRfc8621Configuration {
 case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: MaxSizeUpload = UPLOAD_LIMIT_30_MB) {
   val urlPrefix: URL = new URL(urlPrefixString)
   val apiUrl: URL = new URL(s"$urlPrefixString/jmap")
-  val downloadUrl: URL = new URL(urlPrefixString + "/download/$accountId/$blobId/?type=$type&name=$name")
+  val downloadUrl: URL = new URL(urlPrefixString + "/download/{accountId}/{blobId}/?type={type}&name={name}")
   val uploadUrl: URL = new URL(s"$urlPrefixString/upload")
   val eventSourceUrl: URL = new URL(s"$urlPrefixString/eventSource")
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index 46c1dca..b466742 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -114,7 +114,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
       .thenReturn
         .getBody
         .asString()
-    val downloadPath: String = "download/$accountId/$blobId/?type=$type&name=$name"
+    val downloadPath: String = "download/{accountId}/{blobId}/?type={type}&name={name}"
     val expectedJson = s"""{
                          |  "capabilities" : {
                          |    "urn:ietf:params:jmap:core" : {
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
index d483d80..1c84bae 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/JmapRfc8621ConfigurationTest.scala
@@ -42,7 +42,7 @@ class JmapRfc8621ConfigurationTest extends AnyWordSpec with Matchers {
       val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(providedConfiguration())
 
       jmapRfc8621Configuration.apiUrl must be(new URL("http://random-domain.com/jmap"))
-      jmapRfc8621Configuration.downloadUrl must be(new URL("http://random-domain.com/download/$accountId/$blobId/?type=$type&name=$name "))
+      jmapRfc8621Configuration.downloadUrl must be(new URL("http://random-domain.com/download/{accountId}/{blobId}/?type={type}&name={name}"))
       jmapRfc8621Configuration.uploadUrl must be(new URL("http://random-domain.com/upload"))
       jmapRfc8621Configuration.eventSourceUrl must be(new URL("http://random-domain.com/eventSource"))
     }
@@ -51,7 +51,7 @@ class JmapRfc8621ConfigurationTest extends AnyWordSpec with Matchers {
       val jmapRfc8621Configuration: JmapRfc8621Configuration = JmapRfc8621Configuration.from(emptyConfiguration)
 
       jmapRfc8621Configuration.apiUrl must be(new URL("http://localhost/jmap"))
-      jmapRfc8621Configuration.downloadUrl must be(new URL("http://localhost/download/$accountId/$blobId/?type=$type&name=$name"))
+      jmapRfc8621Configuration.downloadUrl must be(new URL("http://localhost/download/{accountId}/{blobId}/?type={type}&name={name}"))
       jmapRfc8621Configuration.uploadUrl must be(new URL("http://localhost/upload"))
       jmapRfc8621Configuration.eventSourceUrl must be(new URL("http://localhost/eventSource"))
     }


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