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 68d900776eec4216a2c7c48017385dc2cddb1f99 Author: LanKhuat <dlkh...@linagora.com> AuthorDate: Thu Oct 29 16:29:54 2020 +0700 JAMES-3438 Email/set create htmlBody error handling --- .../rfc8621/contract/EmailSetMethodContract.scala | 556 ++++++++++++++++++++- .../james/jmap/json/EmailSetSerializer.scala | 9 +- .../org/apache/james/jmap/mail/EmailSet.scala | 16 +- .../apache/james/jmap/method/EmailSetMethod.scala | 36 +- 4 files changed, 585 insertions(+), 32 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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala index 59de94d..025bd7a 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala @@ -854,7 +854,6 @@ trait EmailSetMethodContract { def createShouldSupportHtmlBody(server: GuiceJamesServer): Unit = { val bobPath = MailboxPath.inbox(BOB) val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) - val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" val request = @@ -864,7 +863,7 @@ trait EmailSetMethodContract { | ["Email/set", { | "accountId": "$ACCOUNT_ID", | "create": { - | "aaaaaa":{ + | "aaaaaa": { | "mailboxIds": { | "${mailboxId.serialize}": true | }, @@ -878,7 +877,8 @@ trait EmailSetMethodContract { | "bodyValues": { | "a49d": { | "value": "$htmlBody", - | "isTruncated": false + | "isTruncated": false, + | "isEncodingProblem": false | } | } | } @@ -888,17 +888,18 @@ trait EmailSetMethodContract { | { | "accountId": "$ACCOUNT_ID", | "ids": ["#aaaaaa"], - | "properties": ["mailboxIds", "subject", "bodyValues"] + | "properties": ["mailboxIds", "subject", "bodyValues"], + | "fetchHTMLBodyValues": true | }, | "c2"] - | ] + | ] |}""".stripMargin val response = `given` .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .body(request) .when - .post.prettyPeek() + .post .`then` .statusCode(SC_OK) .contentType(JSON) @@ -922,13 +923,554 @@ trait EmailSetMethodContract { | "subject": "World domination", | "bodyValues": { | "1": { - | "value": "$htmlBody" + | "value": "$htmlBody", + | "isEncodingProblem": false, + | "isTruncated": false | } | } |}]""".stripMargin) } @Test + def createShouldSucceedWhenPartPropertiesOmitted(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody" + | } + | } + | } + | } + | }, "c1"], + | ["Email/get", + | { + | "accountId": "$ACCOUNT_ID", + | "ids": ["#aaaaaa"], + | "properties": ["mailboxIds", "subject", "bodyValues"], + | "fetchHTMLBodyValues": true + | }, + | "c2"] + | ] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].created.aaaaaa") + .isEqualTo("{}".stripMargin) + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[1][1].list[0].id") + .inPath(s"methodResponses[1][1].list") + .isEqualTo( + s"""[{ + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "bodyValues": { + | "1": { + | "value": "$htmlBody", + | "isEncodingProblem": false, + | "isTruncated": false + | } + | } + |}]""".stripMargin) + } + + @Test + def createShouldFailWhenMultipleBodyParts(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html" + | }, + | { + | "partId": "a49e", + | "type": "text/html" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody", + | "isTruncated": false + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "Expecting htmlBody to contains only 1 part" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenPartIdMisMatch(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html" + | } + | ], + | "bodyValues": { + | "a49e": { + | "value": "$htmlBody", + | "isTruncated": false + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "Expecting bodyValues to contain the part specified in htmlBody" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenHtmlBodyIsNotHtmlType(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/plain" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody", + | "isTruncated": false + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "Expecting htmlBody type to be text/html" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenIsTruncatedIsTrue(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody", + | "isTruncated": true + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "Expecting isTruncated to be false" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenIsEncodingProblemIsTrue(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody", + | "isEncodingProblem": true + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "Expecting isEncodingProblem to be false" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenCharsetIsSpecified(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html", + | "charset": "UTF-8" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody" + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "List((/htmlBody(0),List(JsonValidationError(List(charset must not be specified in htmlBody),ArraySeq()))))" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenSizeIsSpecified(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html", + | "size": 123 + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody" + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "List((/htmlBody(0),List(JsonValidationError(List(size must not be specified in htmlBody),ArraySeq()))))" + |}""".stripMargin) + } + + @Test + def createShouldFailWhenContentTransferEncodingIsSpecified(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>" + + val request = + s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa": { + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "subject": "World domination", + | "htmlBody": [ + | { + | "partId": "a49d", + | "type": "text/html", + | "header:Content-Transfer-Encoding:asText": "8BIT" + | } + | ], + | "bodyValues": { + | "a49d": { + | "value": "$htmlBody" + | } + | } + | } + | } + | }, "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].notCreated.aaaaaa") + .isEqualTo( + s"""{ + | "type": "invalidArguments", + | "description": "List((/htmlBody(0),List(JsonValidationError(List(Content-Transfer-Encoding must not be specified in htmlBody),ArraySeq()))))" + |}""".stripMargin) + } + + @Test def shouldNotResetKeywordWhenInvalidKeyword(server: GuiceJamesServer): Unit = { val message: Message = Fixture.createTestMessage diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala index 5c76a58..77ce173 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala @@ -248,7 +248,14 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI private implicit val clientEmailBodyValueReads: Reads[ClientEmailBodyValue] = Json.reads[ClientEmailBodyValue] private implicit val typeReads: Reads[Type] = Json.valueReads[Type] private implicit val clientPartIdReads: Reads[ClientPartId] = Json.valueReads[ClientPartId] - private implicit val clientHtmlBodyReads: Reads[ClientHtmlBody] = Json.reads[ClientHtmlBody] + private implicit val clientHtmlBodyReads: Reads[ClientHtmlBody] = { + case JsObject(underlying) if underlying.contains("charset") => JsError("charset must not be specified in htmlBody") + case JsObject(underlying) if underlying.contains("size") => JsError("size must not be specified in htmlBody") + case JsObject(underlying) if underlying.contains("header:Content-Transfer-Encoding:asText") => JsError("Content-Transfer-Encoding must not be specified in htmlBody") + case o: JsObject => Json.reads[ClientHtmlBody].reads(o) + case _ => JsError("Expecting a JsObject to represent an ClientHtmlBody") + } + private implicit val bodyValuesReads: Reads[Map[ClientPartId, ClientEmailBodyValue]] = readMapEntry[ClientPartId, ClientEmailBodyValue](s => Id.validate(s).fold(e => Left(e.getMessage), partId => Right(ClientPartId(partId))), clientEmailBodyValueReads) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala index e5b31b3..89c7c98 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala @@ -80,6 +80,7 @@ case class EmailCreationRequest(mailboxIds: MailboxIds, def toMime4JMessage: Either[IllegalArgumentException, Message] = validateHtmlBody.map(maybeHtmlBody => { val builder = Message.Builder.of + val htmlSubType = "html" references.flatMap(_.asString).map(new RawField("References", _)).foreach(builder.setField) inReplyTo.flatMap(_.asString).map(new RawField("In-Reply-To", _)).foreach(builder.setField) messageId.flatMap(_.asString).map(new RawField(FieldName.MESSAGE_ID, _)).foreach(builder.setField) @@ -91,15 +92,22 @@ case class EmailCreationRequest(mailboxIds: MailboxIds, sender.flatMap(_.asMime4JMailboxList).map(_.asJava).map(Fields.addressList(FieldName.SENDER, _)).foreach(builder.setField) replyTo.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setReplyTo) sentAt.map(_.asUTC).map(_.toInstant).map(Date.from).foreach(builder.setDate) - builder.setBody(maybeHtmlBody.getOrElse(""), "html", StandardCharsets.UTF_8) + builder.setBody(maybeHtmlBody.getOrElse(""), htmlSubType, StandardCharsets.UTF_8) builder.build() }) def validateHtmlBody: Either[IllegalArgumentException, Option[String]] = htmlBody match { case None => Right(None) - case Some(html :: Nil) => bodyValues.getOrElse(Map()).get(html.partId) - .map(part => Right(Some(part.value))) - .getOrElse(Left(new IllegalArgumentException("todo"))) + case Some(html :: Nil) if !html.`type`.value.equals("text/html") => Left(new IllegalArgumentException("Expecting htmlBody type to be text/html")) + case Some(html :: Nil) => bodyValues.getOrElse(Map()) + .get(html.partId) + .map { + case part if part.isTruncated.isDefined && part.isTruncated.get.value.equals(true) => Left(new IllegalArgumentException("Expecting isTruncated to be false")) + case part if part.isEncodingProblem.isDefined && part.isEncodingProblem.get.value.equals(true) => Left(new IllegalArgumentException("Expecting isEncodingProblem to be false")) + case part => Right(Some(part.value)) + } + .getOrElse(Left(new IllegalArgumentException("Expecting bodyValues to contain the part specified in htmlBody"))) + case _ => Left(new IllegalArgumentException("Expecting htmlBody to contains only 1 part")) } } 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 cf36efc..bdf9722 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 @@ -55,7 +55,6 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer, val metricFactory: MetricFactory, val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[EmailSetRequest] { - case class DestroyResults(results: Seq[DestroyResult]) { def destroyed: Option[DestroyIds] = Option(results.flatMap{ @@ -220,27 +219,24 @@ class EmailSetMethod @Inject()(serializer: EmailSetSerializer, .map(CreationResults) private def create(clientId: EmailCreationId, request: EmailCreationRequest, mailboxSession: MailboxSession): SMono[CreationResult] = { - if (request.mailboxIds.value.size != 1) { + val mailboxIds: List[MailboxId] = request.mailboxIds.value + if (mailboxIds.size != 1) { SMono.just(CreationFailure(clientId, new IllegalArgumentException("mailboxIds need to have size 1"))) } else { - SMono.fromCallable[CreationResult](() => { - request.toMime4JMessage - .fold(e => CreationFailure(clientId, e), - message => { - val mailboxId: MailboxId = request.mailboxIds.value.headOption.get - val appendResult = mailboxManager.getMailbox(mailboxId, mailboxSession) - .appendMessage(AppendCommand.builder() - .recent() - .withFlags(request.keywords.map(_.asFlags).getOrElse(new Flags())) - .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant)) - .build(message), - mailboxSession) - CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId)) - } - ) - }) - .subscribeOn(Schedulers.elastic()) - .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e))) + request.toMime4JMessage + .fold(e => SMono.just(CreationFailure(clientId, e)), + message => SMono.fromCallable[CreationResult](() => { + val appendResult = mailboxManager.getMailbox(mailboxIds.head, mailboxSession) + .appendMessage(AppendCommand.builder() + .recent() + .withFlags(request.keywords.map(_.asFlags).getOrElse(new Flags())) + .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant)) + .build(message), + mailboxSession) + CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId)) + }) + .subscribeOn(Schedulers.elastic()) + .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e)))) } } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org