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/12/05 07:10:12 UTC
[james-project] 05/17: JAMES-2884 Extract text from HTML for
fetchTextBodyValues
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 4f0700c779ba9424152409d30dc8d954a1e63efb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Nov 26 11:57:33 2020 +0700
JAMES-2884 Extract text from HTML for fetchTextBodyValues
This is required for text/plain clients
---
.../src/main/resources/eml/alternative.eml | 28 +++++
.../src/main/resources/eml/html.eml | 64 ++++++++++
.../rfc8621/contract/EmailGetMethodContract.scala | 130 +++++++++++++++++++++
.../scala/org/apache/james/jmap/mail/Email.scala | 40 +++++--
.../org/apache/james/jmap/mail/EmailBodyPart.scala | 25 ++++
5 files changed, 275 insertions(+), 12 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/alternative.eml b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/alternative.eml
new file mode 100644
index 0000000..99357a0
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/alternative.eml
@@ -0,0 +1,28 @@
+Return-Path: <fr...@linagora.com>
+To: to@linagora.com
+From: Lina <fr...@linagora.com>
+Subject: MultiAttachment
+Message-ID: <13...@linagora.com>
+Date: Mon, 27 Feb 2017 11:24:48 +0700
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
+ Thunderbird/45.2.0
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+ boundary="------------64D8D789FC30153D6ED18258"
+
+This is a multi-part message in MIME format.
+--------------64D8D789FC30153D6ED18258
+Content-Type: text/html; charset=utf-8; format=flowed
+Content-Transfer-Encoding: 7bit
+
+<p>Send<br/>concerted from html</p>
+
+
+--------------64D8D789FC30153D6ED18258
+Content-Type: text/plain; charset=UTF-8;
+ name="text1"
+Content-Transfer-Encoding: 7bit
+
+I am the text plain part!
+
+--------------64D8D789FC30153D6ED18258
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/html.eml b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/html.eml
new file mode 100644
index 0000000..64d64fd
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/html.eml
@@ -0,0 +1,64 @@
+Return-Path: <fr...@linagora.com>
+To: to@linagora.com
+From: Lina <fr...@linagora.com>
+Subject: MultiAttachment
+Message-ID: <13...@linagora.com>
+Date: Mon, 27 Feb 2017 11:24:48 +0700
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101
+ Thunderbird/45.2.0
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------64D8D789FC30153D6ED18258"
+
+This is a multi-part message in MIME format.
+--------------64D8D789FC30153D6ED18258
+Content-Type: text/html; charset=utf-8; format=flowed
+Content-Transfer-Encoding: 7bit
+
+<p>Send<br/>concerted from html</p>
+
+
+--------------64D8D789FC30153D6ED18258
+Content-Type: text/plain; charset=UTF-8;
+ name="text1"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="text1"
+
+LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBeDdQRzAr
+RS8vRU1wbTdJZ0k1UTlUTURTRnlhLzFoRSt2dlRKcmswaUdGbGxQZUhMCkE1L1ZsVE0wWVdn
+RzZYNTBxaU1mRTNWTGF6ZjJjMTlpWHJUMG1xLzIxUFoxd0Zub2d2NHp4VU5haWgrQm5nNjIK
+RjBTeXJ1RS9PL05qcXhoL0NjcTZLL2UwNVRWNFQ2NDNVU3hBZUcwS3BwbVlXOXg4SEEvR3ZW
+ODMyYXBadXhrVgppNk5Wa0RCcmZ6YVVDd3U0ekgrSHdPdi9wSTg3RTdLY2NIWUMrK0JpYWoz
+Cg==
+--------------64D8D789FC30153D6ED18258
+Content-Type: application/vnd.ms-publisher;
+ name="text2"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="text2"
+
+c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFESHM4YlQ0VC84UXltYnNp
+QWpsRDFNd05JWEpyL1dFVDYrOU1tdVRTSVlXV1U5NGNzRG45V1ZNelJoYUFicGZuU3FJeDhU
+ZFV0ck4vWnpYMkpldFBTYXIvYlU5blhBV2VpQy9qUEZRMXFLSDRHZURyWVhSTEt1NFQ4Nzgy
+T3JHSDhKeXJvcjk3VGxOWGhQcmpkUkxFQjRiUXFtbVpoYjNId2NEOGE5WHpmWnFsbTdHUldM
+bzFXUU1HdC9OcFFMQzdqTWY0ZkE2LytranpzVHNweHdkZ0w3NEdKcVBmT1hPaXdnTEhYOENa
+Ni81UnlUcWhUNnBEM01rdFNOV2F6L3pJSFBORXFmNUJZOUNCTTFURlI1dys2TURIbzBnbWlJ
+c1hGRUpUUG5maEJ2SERoU2pCMVJJMEt4VUNseVlySjRmQmxVVmVLZm5hd29WY3U3WXZDcUY0
+RjUgcXV5bmhubkBsaW5hZ29yYQo=
+--------------64D8D789FC30153D6ED18258
+Content-Type: text/plain; charset=UTF-8;
+ name="text3"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="text3"
+
+fDF8b1M3NU9nTDN2RjJHZGw5OUNKRGJFcGFKM3lFPXxJTkdxbGpDVzFYTWY0Z2dPUW0yNi9C
+Tm5LR2M9IHNzaC1yc2EgQUFBQUIzTnphQzF5YzJFQUFBQUJJd0FBQVFFQXEyQTdoUkdtZG5t
+OXRVRGJPOUlEU3dCSzZUYlFhK1BYWVBDUHk2cmJUclR0dzdQSGtjY0tycHAweVZocDVIZEVJ
+Y0tyNnBMbFZEQmZPTFg5UVVzeUNPVjB3emZqSUpObEdFWXNkbExKaXpIaGJuMm1VanZTQUhR
+cVpFVFlQODFlRnpMUU5uUEh0NEVWVlVoN1ZmREVTVTg0S2V6bUQ1UWxXcFhMbXZVMzEveU1m
+K1NlOHhoSFR2S1NDWklGSW1Xd29HNm1iVW9XZjluenBJb2FTakIrd2VxcVVVbXBhYWFzWFZh
+bDcySitVWDJCKzJSUFczUmNUMGVPelFncWxKTDNSS3JUSnZkc2pFM0pFQXZHcTNsR0hTWlh5
+TjZtNVU0aHBwaDl1T3Y1NGFIYzRYcjhqaEFhL1NYNU1KCg==
+--------------64D8D789FC30153D6ED18258--
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 e2469db..ff1eaf8 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
@@ -4214,6 +4214,136 @@ trait EmailGetMethodContract {
}
@Test
+ def textBodyValuesForHtmlMessage(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/html.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":["bodyValues"],
+ | "fetchTextBodyValues": 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).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [
+ | {
+ | "id": "${messageId.serialize}",
+ | "bodyValues": {
+ | "2": {
+ | "value": "Send\\nconcerted from html\\n\\n\\r\\n\\r\\n",
+ | "isEncodingProblem": false,
+ | "isTruncated": false
+ | }
+ | }
+ | }
+ | ],
+ | "notFound": []
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def textBodyValuesForAlternativeMessage(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/alternative.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":["bodyValues"],
+ | "fetchTextBodyValues": 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).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [
+ | [
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "notFound": [
+ |
+ | ],
+ | "state": "000001",
+ | "list": [
+ | {
+ | "id": "1",
+ | "bodyValues": {
+ | "3": {
+ | "value": "I am the text plain part!\\r\\n",
+ | "isEncodingProblem": false,
+ | "isTruncated": false
+ | }
+ | }
+ | }
+ | ]
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
def textBodyValuesForComplexMultipart(server: GuiceJamesServer): Unit = {
val path = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
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 f923fc7..0c0f868 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
@@ -50,6 +50,7 @@ import org.apache.james.mime4j.field.AddressListFieldLenientImpl
import org.apache.james.mime4j.message.DefaultMessageBuilder
import org.apache.james.mime4j.stream.{Field, MimeConfig, RawFieldParser}
import org.apache.james.mime4j.util.MimeUtil
+import org.apache.james.util.html.HtmlTextExtractor
import org.slf4j.{Logger, LoggerFactory}
import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
@@ -394,11 +395,12 @@ sealed trait EmailViewReader[+EmailView] {
}
private sealed trait EmailViewFactory[+EmailView] {
- def toEmail(request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailView]
+ def toEmail(htmlTextExtractor: HtmlTextExtractor, request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailView]
}
private class GenericEmailViewReader[+EmailView](messageIdManager: MessageIdManager,
fetchGroup: FetchGroup,
+ htmlTextExtractor: HtmlTextExtractor,
metadataViewFactory: EmailViewFactory[EmailView]) extends EmailViewReader[EmailView] {
override def read[T >: EmailView](ids: Seq[MessageId], request: EmailGetRequest, mailboxSession: MailboxSession): SFlux[T] =
SFlux.fromPublisher(messageIdManager.getMessagesReactive(
@@ -407,7 +409,7 @@ private class GenericEmailViewReader[+EmailView](messageIdManager: MessageIdMana
mailboxSession))
.collectSeq()
.flatMapIterable(messages => messages.groupBy(_.getMessageId).toSet)
- .map(metadataViewFactory.toEmail(request))
+ .map(metadataViewFactory.toEmail(htmlTextExtractor, request))
.handle[T]((aTry, sink) => aTry match {
case Success(value) => sink.next(value)
case Failure(e) => sink.error(e)
@@ -415,7 +417,7 @@ private class GenericEmailViewReader[+EmailView](messageIdManager: MessageIdMana
}
private class EmailMetadataViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) extends EmailViewFactory[EmailMetadataView] {
- override def toEmail(request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailMetadataView] = {
+ override def toEmail(htmlTextExtractor: HtmlTextExtractor, request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailMetadataView] = {
val messageId: MessageId = message._1
val mailboxIds: MailboxIds = MailboxIds(message._2
.map(_.getMailboxId)
@@ -443,7 +445,7 @@ private class EmailMetadataViewFactory @Inject()(zoneIdProvider: ZoneIdProvider)
}
private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) extends EmailViewFactory[EmailHeaderView] {
- override def toEmail(request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailHeaderView] = {
+ override def toEmail(htmlTextExtractor: HtmlTextExtractor, request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailHeaderView] = {
val messageId: MessageId = message._1
val mailboxIds: MailboxIds = MailboxIds(message._2
.map(_.getMailboxId)
@@ -474,7 +476,7 @@ private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) e
}
private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, previewFactory: Preview.Factory) extends EmailViewFactory[EmailFullView] {
- override def toEmail(request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailFullView] = {
+ override def toEmail(htmlTextExtractor: HtmlTextExtractor, request: EmailGetRequest)(message: (MessageId, Seq[MessageResult])): Try[EmailFullView] = {
val messageId: MessageId = message._1
val mailboxIds: MailboxIds = MailboxIds(message._2
.map(_.getMailboxId)
@@ -487,7 +489,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
.getOrElse(Failure(new IllegalArgumentException("No message supplied")))
mime4JMessage <- Email.parseAsMime4JMessage(firstMessage)
bodyStructure <- EmailBodyPart.of(messageId, mime4JMessage)
- bodyValues <- extractBodyValues(bodyStructure, request)
+ bodyValues <- extractBodyValues(htmlTextExtractor)(bodyStructure, request)
blobId <- BlobId.of(messageId)
preview <- Try(previewFactory.fromMessageResult(firstMessage))
keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
@@ -515,8 +517,8 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
}
}
- private def extractBodyValues(bodyStructure: EmailBodyPart, request: EmailGetRequest): Try[Map[PartId, EmailBodyValue]] = for {
- textBodyValues <- extractBodyValues(bodyStructure.textBody, request, request.fetchTextBodyValues.exists(_.value))
+ private def extractBodyValues(htmlTextExtractor: HtmlTextExtractor)(bodyStructure: EmailBodyPart, request: EmailGetRequest): Try[Map[PartId, EmailBodyValue]] = for {
+ textBodyValues <- extractTextBodyValues(htmlTextExtractor)(bodyStructure.textBody, request, request.fetchTextBodyValues.exists(_.value))
htmlBodyValues <- extractBodyValues(bodyStructure.htmlBody, request, request.fetchHTMLBodyValues.exists(_.value))
allBodyValues <- extractBodyValues(bodyStructure.flatten, request, request.fetchAllBodyValues.exists(_.value))
} yield {
@@ -534,27 +536,40 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
} else {
Success(Nil)
}
+
+ private def extractTextBodyValues(htmlTextExtractor: HtmlTextExtractor)(parts: List[EmailBodyPart], request: EmailGetRequest, shouldFetch: Boolean): Try[List[(PartId, EmailBodyValue)]] =
+ if (shouldFetch) {
+ parts
+ .map(part => part.textBodyContent(htmlTextExtractor).map(bodyValue => bodyValue.map(b => (part.partId, b.truncate(request.maxBodyValueBytes)))))
+ .sequence
+ .map(list => list.flatten)
+ } else {
+ Success(Nil)
+ }
}
private class EmailMetadataViewReader @Inject()(messageIdManager: MessageIdManager,
+ htmlTextExtractor: HtmlTextExtractor,
metadataViewFactory: EmailMetadataViewFactory) extends EmailViewReader[EmailMetadataView] {
- private val reader: GenericEmailViewReader[EmailMetadataView] = new GenericEmailViewReader[EmailMetadataView](messageIdManager, MINIMAL, metadataViewFactory)
+ private val reader: GenericEmailViewReader[EmailMetadataView] = new GenericEmailViewReader[EmailMetadataView](messageIdManager, MINIMAL, htmlTextExtractor, metadataViewFactory)
override def read[T >: EmailMetadataView](ids: Seq[MessageId], request: EmailGetRequest, mailboxSession: MailboxSession): SFlux[T] =
reader.read(ids, request, mailboxSession)
}
private class EmailHeaderViewReader @Inject()(messageIdManager: MessageIdManager,
+ htmlTextExtractor: HtmlTextExtractor,
headerViewFactory: EmailHeaderViewFactory) extends EmailViewReader[EmailHeaderView] {
- private val reader: GenericEmailViewReader[EmailHeaderView] = new GenericEmailViewReader[EmailHeaderView](messageIdManager, HEADERS, headerViewFactory)
+ private val reader: GenericEmailViewReader[EmailHeaderView] = new GenericEmailViewReader[EmailHeaderView](messageIdManager, HEADERS, htmlTextExtractor, headerViewFactory)
override def read[T >: EmailHeaderView](ids: Seq[MessageId], request: EmailGetRequest, mailboxSession: MailboxSession): SFlux[T] =
reader.read(ids, request, mailboxSession)
}
private class EmailFullViewReader @Inject()(messageIdManager: MessageIdManager,
+ htmlTextExtractor: HtmlTextExtractor,
fullViewFactory: EmailFullViewFactory) extends EmailViewReader[EmailFullView] {
- private val reader: GenericEmailViewReader[EmailFullView] = new GenericEmailViewReader[EmailFullView](messageIdManager, FULL_CONTENT, fullViewFactory)
+ private val reader: GenericEmailViewReader[EmailFullView] = new GenericEmailViewReader[EmailFullView](messageIdManager, FULL_CONTENT, htmlTextExtractor, fullViewFactory)
override def read[T >: EmailFullView](ids: Seq[MessageId], request: EmailGetRequest, mailboxSession: MailboxSession): SFlux[T] =
@@ -573,9 +588,10 @@ private case class FastViewUnavailable(id: MessageId) extends FastViewResult
private class EmailFastViewReader @Inject()(messageIdManager: MessageIdManager,
messageFastViewProjection: MessageFastViewProjection,
+ htmlTextExtractor: HtmlTextExtractor,
zoneIdProvider: ZoneIdProvider,
fullViewFactory: EmailFullViewFactory) extends EmailViewReader[EmailView] {
- private val fullReader: GenericEmailViewReader[EmailFullView] = new GenericEmailViewReader[EmailFullView](messageIdManager, FULL_CONTENT, fullViewFactory)
+ private val fullReader: GenericEmailViewReader[EmailFullView] = new GenericEmailViewReader[EmailFullView](messageIdManager, FULL_CONTENT, htmlTextExtractor, fullViewFactory)
override def read[T >: EmailView](ids: Seq[MessageId], request: EmailGetRequest, mailboxSession: MailboxSession): SFlux[T] =
SMono.fromPublisher(messageFastViewProjection.retrieve(ids.asJava))
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 c5eb21f..e3d08a8 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
@@ -38,6 +38,7 @@ import org.apache.james.mime4j.dom.field.{ContentLanguageField, ContentTypeField
import org.apache.james.mime4j.dom.{Entity, Message, Multipart, TextBody => Mime4JTextBody}
import org.apache.james.mime4j.message.{DefaultMessageBuilder, DefaultMessageWriter}
import org.apache.james.mime4j.stream.{Field, MimeConfig, RawField}
+import org.apache.james.util.html.HtmlTextExtractor
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try}
@@ -196,6 +197,22 @@ case class Location(value: String) extends AnyVal {
def asField: Field = new RawField("Content-Location", value)
}
+object Context {
+ def of(`type`: Type): Context = `type` match {
+ case MULTIPART_ALTERNATIVE => AlternativeContext
+ case _ => NoContext
+ }
+ def of(`type`: Type, previousContext: Context): Context = (`type`, previousContext) match {
+ case (_, AlternativeContext) => AlternativeContext
+ case (MULTIPART_ALTERNATIVE, _) => AlternativeContext
+ case _ => NoContext
+ }
+}
+
+sealed trait Context
+case object NoContext extends Context
+case object AlternativeContext extends Context
+
case class EmailBodyPart(partId: PartId,
blobId: Option[BlobId],
headers: List[EmailHeader],
@@ -222,6 +239,14 @@ case class EmailBodyPart(partId: PartId,
case _ => Success(None)
}
+ def textBodyContent(htmlTextExtractor: HtmlTextExtractor): Try[Option[EmailBodyValue]] = `type` match {
+ case TEXT_HTML => bodyContent.map(maybeContent => maybeContent.map(
+ content => EmailBodyValue(htmlTextExtractor.toPlainText(content.value),
+ content.isEncodingProblem,
+ content.isTruncated)))
+ case _ => bodyContent
+ }
+
private def charset(charset: Option[String]): java.nio.charset.Charset = charset
.map(java.nio.charset.Charset.forName)
.getOrElse(org.apache.james.mime4j.Charsets.DEFAULT_CHARSET)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org