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/09/08 03:43:06 UTC
[james-project] 12/19: JAMES-3369 Email/get textBody & htmlBody
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 b2c259a9b20587406b09a2bfaa7bceba56d61ddb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Sep 7 11:27:27 2020 +0700
JAMES-3369 Email/get textBody & htmlBody
---
.../rfc8621/contract/EmailGetMethodContract.scala | 222 +++++++++++++++++++++
.../scala/org/apache/james/jmap/mail/Email.scala | 14 +-
.../org/apache/james/jmap/mail/EmailBodyPart.scala | 35 ++++
3 files changed, 267 insertions(+), 4 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/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index c076dc8..295ff83 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -1419,4 +1419,226 @@ trait EmailGetMethodContract {
| ]
|}""".stripMargin)
}
+
+ @Test
+ def textBodyForSimpleMultipart(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, path, AppendCommand.from(
+ ClassLoader.getSystemResourceAsStream("eml/multipart_simple.eml")))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties":["textBody"]
+ | },
+ | "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).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [
+ | {
+ | "id": "${messageId.serialize()}",
+ | "textBody": [
+ | {
+ | "partId": "2",
+ | "blobId": "${messageId.serialize()}_2",
+ | "headers": [
+ | {
+ | "name": "Content-Type",
+ | "value": "text/plain; charset=utf-8; format=flowed"
+ | },
+ | {
+ | "name": "Content-Transfer-Encoding",
+ | "value": "7bit"
+ | }
+ | ],
+ | "size": 97,
+ | "type": "text/plain",
+ | "charset": "utf-8"
+ | }
+ | ]
+ | }
+ | ],
+ | "notFound": []
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def textBodyForComplexMultipart(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, path, AppendCommand.from(
+ ClassLoader.getSystemResourceAsStream("eml/multipart_complex.eml")))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties":["textBody"]
+ | },
+ | "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).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [
+ | {
+ | "id": "${messageId.serialize()}",
+ | "textBody": [
+ | {
+ | "partId": "3",
+ | "blobId": "${messageId.serialize()}_3",
+ | "headers": [
+ | {
+ | "name": "Content-Type",
+ | "value": "text/plain; charset=ISO-8859-1; format=flowed"
+ | },
+ | {
+ | "name": "Content-Transfer-Encoding",
+ | "value": "8bit"
+ | }
+ | ],
+ | "size": 114,
+ | "type": "text/plain",
+ | "charset": "ISO-8859-1"
+ | }
+ | ]
+ | }
+ | ],
+ | "notFound": []
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def htmlBodyForComplexMultipart(server: GuiceJamesServer): Unit = {
+ val path = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, path, AppendCommand.from(
+ ClassLoader.getSystemResourceAsStream("eml/multipart_complex.eml")))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties":["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).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [
+ | {
+ | "id": "${messageId.serialize()}",
+ | "htmlBody": [
+ | {
+ | "partId": "4",
+ | "blobId": "${messageId.serialize()}_4",
+ | "headers": [
+ | {
+ | "name": "Content-Type",
+ | "value": "text/html; charset=ISO-8859-1"
+ | },
+ | {
+ | "name": "Content-Transfer-Encoding",
+ | "value": "7bit"
+ | }
+ | ],
+ | "size": 108,
+ | "type": "text/html",
+ | "charset": "ISO-8859-1"
+ | }
+ | ]
+ | }
+ | ],
+ | "notFound": []
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index 5f499e9..bf2c2e2 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -29,7 +29,6 @@ import org.apache.james.jmap.mail.Email.Size
import org.apache.james.jmap.model.Properties
import org.apache.james.mailbox.model.{MessageId, MessageResult}
import org.apache.james.mime4j.codec.DecodeMonitor
-import org.apache.james.mime4j.dom.Message
import org.apache.james.mime4j.message.DefaultMessageBuilder
import org.apache.james.mime4j.stream.MimeConfig
import org.slf4j.{Logger, LoggerFactory}
@@ -43,7 +42,7 @@ object Email {
type UnparsedEmailId = String Refined UnparsedEmailIdConstraint
val defaultProperties: Properties = Properties("id", "size")
- val allowedProperties: Properties = Properties("id", "size", "bodyStructure")
+ val allowedProperties: Properties = Properties("id", "size", "bodyStructure", "textBody", "htmlBody")
val idProperty: Properties = Properties("id")
def asUnparsed(messageId: MessageId): Try[UnparsedEmailId] =
@@ -79,11 +78,18 @@ object Email {
mime4JMessage <- Try(defaultMessageBuilder.parseMessage(firstMessage.getFullContent.getInputStream))
bodyStructure <- EmailBodyPart.of(messageId, mime4JMessage)
} yield {
- Email(messageId, sanitizeSize(firstMessage.getSize), bodyStructure)
+ Email(
+ id = messageId,
+ size = sanitizeSize(firstMessage.getSize),
+ bodyStructure = bodyStructure,
+ textBody = bodyStructure.textBody,
+ htmlBody = bodyStructure.htmlBody)
}
}
}
case class Email(id: MessageId,
size: Size,
- bodyStructure: EmailBodyPart)
+ bodyStructure: EmailBodyPart,
+ textBody: List[EmailBodyPart],
+ htmlBody: List[EmailBodyPart])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
index 0606275..608a045 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
@@ -28,6 +28,7 @@ import eu.timepit.refined.auto._
import eu.timepit.refined.numeric.NonNegative
import eu.timepit.refined.refineV
import org.apache.james.jmap.mail.Email.Size
+import org.apache.james.jmap.mail.EmailBodyPart.{MULTIPART_ALTERNATIVE, TEXT_HTML, TEXT_PLAIN}
import org.apache.james.jmap.mail.PartId.PartIdValue
import org.apache.james.mailbox.model.{Cid, MessageId}
import org.apache.james.mime4j.dom.{Entity, Message, Multipart}
@@ -51,6 +52,10 @@ case class PartId(value: PartIdValue) {
}
object EmailBodyPart {
+ val TEXT_PLAIN: String = "text/plain"
+ val TEXT_HTML: String = "text/html"
+ val MULTIPART_ALTERNATIVE: String = "multipart/alternative"
+
def of(messageId: MessageId, message: Message): Try[EmailBodyPart] =
of(messageId, PartId(1), message).map(_._1)
@@ -154,4 +159,34 @@ case class EmailBodyPart(partId: PartId,
location: Option[Location],
subParts: Option[List[EmailBodyPart]]) {
+ def textBody: List[EmailBodyPart] = selfBody ++ textBodyOfMultipart
+
+ def htmlBody: List[EmailBodyPart] = selfBody ++ htmlBodyOfMultipart
+
+ private def selfBody: List[EmailBodyPart] = if (shouldBeDisplayedAsBody) {
+ List(this)
+ } else {
+ Nil
+ }
+
+ private val hasTextMediaType: Boolean = `type`.equals(TEXT_PLAIN) || `type`.equals(TEXT_HTML)
+ private val shouldBeDisplayedAsBody: Boolean = hasTextMediaType && disposition.isEmpty && cid.isEmpty
+
+ private def textBodyOfMultipart: List[EmailBodyPart] = `type` match {
+ case MULTIPART_ALTERNATIVE => textPlainSubparts
+ case _ => subParts.getOrElse(Nil)
+ .flatMap(subPart => subPart.textBody)
+ }
+
+ private def htmlBodyOfMultipart: List[EmailBodyPart] = `type` match {
+ case MULTIPART_ALTERNATIVE => textHtmlSubparts
+ case _ => subParts.getOrElse(Nil)
+ .flatMap(subPart => subPart.htmlBody)
+ }
+
+ private def textPlainSubparts: List[EmailBodyPart] = subParts.getOrElse(Nil)
+ .filter(subPart => subPart.`type`.equals(TEXT_PLAIN))
+
+ private def textHtmlSubparts: List[EmailBodyPart] = subParts.getOrElse(Nil)
+ .filter(subPart => subPart.`type`.equals(TEXT_HTML))
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org