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 2023/01/12 08:11:43 UTC
[james-project] branch master updated: JAMES-3756 Jmap download/upload endpoint should support being called with accountIds of delegated accounts (#1382)
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 130f7aca89 JAMES-3756 Jmap download/upload endpoint should support being called with accountIds of delegated accounts (#1382)
130f7aca89 is described below
commit 130f7aca89284a970c698b15ba7906df5d1ca79d
Author: vttran <vt...@linagora.com>
AuthorDate: Thu Jan 12 15:11:37 2023 +0700
JAMES-3756 Jmap download/upload endpoint should support being called with accountIds of delegated accounts (#1382)
---
.../jmap/rfc8621/contract/DownloadContract.scala | 95 +++++++++++++++++++++-
.../jmap/rfc8621/contract/UploadContract.scala | 46 ++++++++++-
.../apache/james/jmap/routes/DownloadRoutes.scala | 31 +++----
.../apache/james/jmap/routes/UploadRoutes.scala | 36 ++++----
4 files changed, 163 insertions(+), 45 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/DownloadContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
index c4ddb5b961..650aad6e25 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/DownloadContract.scala
@@ -21,7 +21,6 @@ package org.apache.james.jmap.rfc8621.contract
import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets
-
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import org.apache.commons.io.IOUtils
@@ -29,7 +28,7 @@ import org.apache.http.HttpStatus.{SC_FORBIDDEN, SC_NOT_FOUND, SC_OK, SC_UNAUTHO
import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.DownloadContract.accountId
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ALICE_ACCOUNT_ID, ANDRE, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ALICE_ACCOUNT_ID, ANDRE, BOB, BOB_PASSWORD, CEDRIC, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
@@ -51,6 +50,8 @@ trait DownloadContract {
.fluent
.addDomain(DOMAIN.asString)
.addUser(BOB.asString, BOB_PASSWORD)
+ .addUser(ANDRE.asString, BOB_PASSWORD)
+ .addUser(CEDRIC.asString, BOB_PASSWORD)
requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
@@ -111,7 +112,7 @@ trait DownloadContract {
}
@Test
- def downloadMessageShouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = {
+ def downloadMessageShouldSucceedWhenAddedRightACL(server: GuiceJamesServer): Unit = {
val path = MailboxPath.inbox(ANDRE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
@@ -182,7 +183,7 @@ trait DownloadContract {
}
@Test
- def downloadPartShouldSucceedWhenDelegated(server: GuiceJamesServer): Unit = {
+ def downloadPartShouldSucceedWhenAddedRightACL(server: GuiceJamesServer): Unit = {
val path = MailboxPath.inbox(ANDRE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
@@ -533,4 +534,90 @@ trait DownloadContract {
.body("type", equalTo("about:blank"))
.body("detail", equalTo("The resource could not be found"))
}
+
+ @Test
+ def downloadMessageShouldSucceedWhenDelegatedAccount(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(ANDRE)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(ANDRE.asString, path, AppendCommand.from(
+ ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
+ .getMessageId
+
+ server.getProbe(classOf[DataProbeImpl]).addAuthorizedUser(ANDRE, BOB)
+
+ val response = `given`
+ .basePath("")
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .when
+ .get(s"/download/${Fixture.ANDRE_ACCOUNT_ID}/${messageId.serialize()}")
+ .`then`
+ .statusCode(SC_OK)
+ .contentType("message/rfc822")
+ .extract
+ .body
+ .asString
+
+ val expectedResponse: String = IOUtils.toString(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml"),
+ StandardCharsets.UTF_8)
+ assertThat(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8)))
+ .hasContent(expectedResponse)
+ }
+
+ @Test
+ def downloadMessageShouldFailWhenNotDelegatedAccount(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(ANDRE)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(ANDRE.asString, path, AppendCommand.from(
+ ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
+ .getMessageId
+
+ server.getProbe(classOf[DataProbeImpl]).addAuthorizedUser(ANDRE, CEDRIC)
+
+ `given`
+ .basePath("")
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .when
+ .get(s"/download/${Fixture.ANDRE_ACCOUNT_ID}/${messageId.serialize()}")
+ .`then`
+ .statusCode(SC_FORBIDDEN)
+ .body("status", equalTo(403))
+ .body("type", equalTo("about:blank"))
+ .body("detail", equalTo("You cannot download in others accounts"))
+ }
+
+ @Test
+ def downloadPartMessageShouldSucceedWhenDelegatedAccount(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(ANDRE)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(ANDRE.asString, path, AppendCommand.from(
+ ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
+ .getMessageId
+
+ server.getProbe(classOf[DataProbeImpl]).addAuthorizedUser(ANDRE, BOB)
+
+ val response = `given`
+ .basePath("")
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .when
+ .get(s"/download/${Fixture.ANDRE_ACCOUNT_ID}/${messageId.serialize()}_3")
+ .`then`
+ .statusCode(SC_OK)
+ .contentType("text/plain")
+ .extract
+ .body
+ .asString
+
+ val expectedResponse: String =
+ """-----BEGIN RSA PRIVATE KEY-----
+ |MIIEogIBAAKCAQEAx7PG0+E//EMpm7IgI5Q9TMDSFya/1hE+vvTJrk0iGFllPeHL
+ |A5/VlTM0YWgG6X50qiMfE3VLazf2c19iXrT0mq/21PZ1wFnogv4zxUNaih+Bng62
+ |F0SyruE/O/Njqxh/Ccq6K/e05TV4T643USxAeG0KppmYW9x8HA/GvV832apZuxkV
+ |i6NVkDBrfzaUCwu4zH+HwOv/pI87E7KccHYC++Biaj3
+ |""".stripMargin
+ assertThat(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8)))
+ .hasContent(expectedResponse)
+ }
}
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 da9a95ef0b..25e9ed1a5d 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
@@ -20,17 +20,18 @@ package org.apache.james.jmap.rfc8621.contract
import java.io.ByteArrayInputStream
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 org.apache.http.HttpStatus.{SC_BAD_REQUEST, SC_CREATED, SC_FORBIDDEN, SC_OK, SC_UNAUTHORIZED}
import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.core.AccountId
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, _2_DOT_DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ALICE, ALICE_ACCOUNT_ID, ALICE_PASSWORD, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, _2_DOT_DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.jmap.rfc8621.contract.UploadContract.{BIG_INPUT, VALID_INPUT}
import org.apache.james.utils.DataProbeImpl
import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.Matchers
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.{BeforeEach, RepeatedTest, Test}
import play.api.libs.json.{JsString, Json}
@@ -49,6 +50,7 @@ trait UploadContract {
.addUser(BOB.asString, BOB_PASSWORD)
.addDomain(_2_DOT_DOMAIN.asString())
.addUser(ALICE.asString(), ALICE_PASSWORD)
+ .addUser(ANDRE.asString(), ANDRE_PASSWORD)
requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
@@ -166,4 +168,44 @@ trait UploadContract {
.body("type", equalTo("about:blank"))
.body("detail", equalTo("No valid authentication methods provided"))
}
+
+ @Test
+ def bobShouldBeAllowedToUploadInAliceAccountWhenDelegated(server: GuiceJamesServer): Unit = {
+ server.getProbe(classOf[DataProbeImpl]).addAuthorizedUser(ALICE, BOB)
+
+ val aliceAccountId: String = AccountId.from(ALICE).toOption.get.id.value
+
+ `given`
+ .basePath("")
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(VALID_INPUT)
+ .when
+ .post(s"/upload/$aliceAccountId")
+ .`then`
+ .statusCode(SC_CREATED)
+ .body("size", equalTo(11534336))
+ .body("type", equalTo("application/json; charset=UTF-8"))
+ .body("blobId", Matchers.notNullValue())
+ .body("accountId", equalTo(aliceAccountId))
+ }
+
+ @Test
+ def bobShouldBeNotAllowedToUploadInAliceAccountWhenNotDelegated(server: GuiceJamesServer): Unit = {
+ server.getProbe(classOf[DataProbeImpl]).addAuthorizedUser(ALICE, ANDRE)
+
+ val aliceAccountId: String = AccountId.from(ALICE).toOption.get.id.value
+
+ `given`
+ .basePath("")
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(VALID_INPUT)
+ .when
+ .post(s"/upload/$aliceAccountId")
+ .`then`
+ .statusCode(SC_FORBIDDEN)
+ .body("status", equalTo(403))
+ .body("type", equalTo("about:blank"))
+ .body("detail", equalTo("Upload to other accounts is forbidden"))
+ }
+
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
index 305578a241..0b54980f25 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
@@ -18,11 +18,6 @@
****************************************************************/
package org.apache.james.jmap.routes
-import java.io.InputStream
-import java.nio.charset.StandardCharsets
-import java.util.stream
-import java.util.stream.Stream
-
import com.google.common.base.CharMatcher
import eu.timepit.refined.numeric.NonNegative
import eu.timepit.refined.refineV
@@ -30,19 +25,19 @@ import io.netty.buffer.Unpooled
import io.netty.handler.codec.http.HttpHeaderNames.{CONTENT_LENGTH, CONTENT_TYPE}
import io.netty.handler.codec.http.HttpResponseStatus._
import io.netty.handler.codec.http.{HttpMethod, HttpResponseStatus, QueryStringDecoder}
-import javax.inject.{Inject, Named}
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream
import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
import org.apache.james.jmap.api.model.Size.{Size, sanitizeSize}
import org.apache.james.jmap.api.model.{Upload, UploadId, UploadNotFoundException}
import org.apache.james.jmap.api.upload.UploadRepository
import org.apache.james.jmap.core.Id.Id
-import org.apache.james.jmap.core.{AccountId, Id, ProblemDetails}
+import org.apache.james.jmap.core.{AccountId, Id, ProblemDetails, SessionTranslator}
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.ResponseSerializer
import org.apache.james.jmap.mail.{BlobId, EmailBodyPart, PartId}
+import org.apache.james.jmap.method.AccountNotFoundException
import org.apache.james.jmap.routes.DownloadRoutes.{BUFFER_SIZE, LOGGER}
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
import org.apache.james.mailbox.model.ContentType.{MediaType, MimeType, SubType}
@@ -60,6 +55,11 @@ import reactor.core.scala.publisher.SMono
import reactor.core.scheduler.Schedulers
import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.util.stream
+import java.util.stream.Stream
+import javax.inject.{Inject, Named}
import scala.compat.java8.FunctionConverters._
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try}
@@ -235,7 +235,8 @@ class BlobResolvers(blobResolvers: Set[BlobResolver]) {
}
class DownloadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
- val blobResolvers: BlobResolvers) extends JMAPRoutes {
+ val blobResolvers: BlobResolvers,
+ val sessionTranslator: SessionTranslator) extends JMAPRoutes {
private val accountIdParam: String = "accountId"
private val blobIdParam: String = "blobId"
@@ -257,7 +258,7 @@ class DownloadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
SMono(authenticator.authenticate(request))
.flatMap(mailboxSession => getIfOwner(request, response, mailboxSession))
.onErrorResume {
- case e: ForbiddenException =>
+ case _: ForbiddenException | _: AccountNotFoundException =>
respondDetails(response,
ProblemDetails(status = FORBIDDEN, detail = "You cannot download in others accounts"),
FORBIDDEN)
@@ -295,16 +296,8 @@ class DownloadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
private def getIfOwner(request: HttpServerRequest, response: HttpServerResponse, mailboxSession: MailboxSession): SMono[Unit] =
Id.validate(request.param(accountIdParam)) match {
- case Right(id: Id) =>
- val targetAccountId: AccountId = AccountId(id)
- AccountId.from(mailboxSession.getUser).map(accountId => accountId.equals(targetAccountId))
- .fold[SMono[Unit]](
- e => SMono.error(e),
- value => if (value) {
- get(request, response, mailboxSession)
- } else {
- SMono.error(ForbiddenException())
- })
+ case Right(id: Id) => sessionTranslator.delegateIfNeeded(mailboxSession, AccountId(id))
+ .flatMap(session => get(request, response, session))
case Left(throwable: Throwable) => SMono.error(throwable)
}
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 4dd37081f8..971246e809 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,10 +24,10 @@ import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.util.stream
import java.util.stream.Stream
-
import io.netty.handler.codec.http.HttpHeaderNames.{CONTENT_LENGTH, CONTENT_TYPE}
import io.netty.handler.codec.http.HttpResponseStatus.{BAD_REQUEST, CREATED, FORBIDDEN, INTERNAL_SERVER_ERROR, UNAUTHORIZED}
import io.netty.handler.codec.http.{HttpMethod, HttpResponseStatus}
+
import javax.inject.{Inject, Named}
import org.apache.commons.fileupload.util.LimitedInputStream
import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
@@ -35,12 +35,13 @@ import org.apache.james.jmap.api.model.Size.Size
import org.apache.james.jmap.api.model.{UploadId, UploadMetaData}
import org.apache.james.jmap.api.upload.UploadRepository
import org.apache.james.jmap.core.Id.Id
-import org.apache.james.jmap.core.{AccountId, Id, JmapRfc8621Configuration, ProblemDetails}
+import org.apache.james.jmap.core.{AccountId, Id, JmapRfc8621Configuration, ProblemDetails, SessionTranslator}
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.{ResponseSerializer, UploadSerializer}
import org.apache.james.jmap.mail.BlobId
+import org.apache.james.jmap.method.AccountNotFoundException
import org.apache.james.jmap.routes.UploadRoutes.LOGGER
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
import org.apache.james.mailbox.MailboxSession
@@ -66,10 +67,8 @@ case class UploadResponse(accountId: AccountId,
class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
val configuration: JmapRfc8621Configuration,
val uploadRepository: UploadRepository,
- val serializer: UploadSerializer) extends JMAPRoutes {
-
- class CancelledUploadException extends RuntimeException
- class MaxFileSizeUploadException extends RuntimeException
+ val serializer: UploadSerializer,
+ val sessionTranslator: SessionTranslator) extends JMAPRoutes {
private val accountIdParam: String = "accountId"
private val uploadURI = s"/upload/{$accountIdParam}"
@@ -99,7 +98,7 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
respondDetails(response,
ProblemDetails(status = BAD_REQUEST, detail = "Attempt to upload exceed max size"),
BAD_REQUEST)
- case _: ForbiddenException =>
+ case _: ForbiddenException | _: AccountNotFoundException =>
respondDetails(response,
ProblemDetails(status = FORBIDDEN, detail = "Upload to other accounts is forbidden"),
FORBIDDEN)
@@ -116,27 +115,24 @@ class UploadRoutes @Inject()(@Named(InjectionKeys.RFC_8621) val authenticator: A
}
}
- def post(request: HttpServerRequest, response: HttpServerResponse, contentType: ContentType, session: MailboxSession): SMono[Void] = {
+ def post(request: HttpServerRequest, response: HttpServerResponse, contentType: ContentType, mailboxSession: MailboxSession): SMono[Void] = {
Id.validate(request.param(accountIdParam)) match {
case Right(id: Id) =>
val targetAccountId: AccountId = AccountId(id)
- AccountId.from(session.getUser).map(accountId => accountId.equals(targetAccountId))
- .fold[SMono[Void]](
- e => SMono.error(e),
- value => if (value) {
- SMono.fromCallable(() => ReactorUtils.toInputStream(request.receive
- // Unwrapping to byte array needed to solve data races and buffer reordering when using .asByteBuffer()
- .asByteArray()
- .map(array => ByteBuffer.wrap(array))))
- .flatMap(content => handle(targetAccountId, contentType, content, session, response))
- } else {
- SMono.error(ForbiddenException())
- })
+ sessionTranslator.delegateIfNeeded(mailboxSession, targetAccountId)
+ .flatMap(session => handle(request, response, contentType, session, targetAccountId))
case Left(throwable: Throwable) => SMono.error(throwable)
}
}
+ private def handle(request: HttpServerRequest, response: HttpServerResponse, contentType: ContentType, session: MailboxSession, targetAccountId: AccountId): SMono[Void] =
+ SMono.fromCallable(() => ReactorUtils.toInputStream(request.receive
+ // Unwrapping to byte array needed to solve data races and buffer reordering when using .asByteBuffer()
+ .asByteArray()
+ .map(array => ByteBuffer.wrap(array))))
+ .flatMap(content => handle(targetAccountId, contentType, content, session, response))
+
def handle(accountId: AccountId, contentType: ContentType, content: InputStream, mailboxSession: MailboxSession, response: HttpServerResponse): SMono[Void] = {
val maxSize: Long = configuration.maxUploadSize.value.value
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org