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 07:15:52 UTC
[james-project] 02/02: JAMES-3438 Email/set create htmlBody error
handling
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 <dl...@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-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org