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 a36760613d1df34f0133ae962526a13357272f16
Author: LanKhuat <[email protected]>
AuthorDate: Thu Sep 17 11:27:05 2020 +0700

    JAMES-3379 Email/get specific parsed headers: asAddresses
---
 .../rfc8621/contract/EmailGetMethodContract.scala  | 161 +++++++++++++++++++++
 .../james/jmap/json/EmailGetSerializer.scala       |   4 +-
 .../scala/org/apache/james/jmap/mail/Email.scala   |  42 +++---
 .../org/apache/james/jmap/mail/EmailAddress.scala  |  15 +-
 .../org/apache/james/jmap/mail/EmailGet.scala      |   5 +-
 .../org/apache/james/jmap/mail/EmailHeader.scala   |   6 +
 6 files changed, 205 insertions(+), 28 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 c8783c8..6f71695 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
@@ -5834,4 +5834,165 @@ trait EmailGetMethodContract {
            |    "header:Subject:asText": "World domination and this is also 
part of the header"
            |}""".stripMargin)
   }
+
+  @Test
+  def emailGetShouldReturnSpecificHeadersAsAddresses(server: 
GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+    val alicePath = MailboxPath.inbox(ALICE)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(ANDRE.asString())
+      .setFrom(ANDRE.asString())
+      .addField(new RawField("To", "\"  user1  \" <[email protected]>, 
\"user2\" <[email protected]>"))
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+      .getMessageId
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |     "Email/get",
+               |     {
+               |       "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |       "ids": ["${messageId.serialize}"],
+               |       "properties": ["header:To:asAddresses"]
+               |     },
+               |     "c1"]]
+               |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |    "id": "${messageId.serialize}",
+           |    "header:To:asAddresses": [
+           |      { "name": "user1", "email": "[email protected]" },
+           |      { "name": "user2", "email": "[email protected]" }
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def emailGetShouldReturnSpecificHeadersAsAddressesAndIgnoresGroup(server: 
GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+    val alicePath = MailboxPath.inbox(ALICE)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(ANDRE.asString())
+      .setFrom(ANDRE.asString())
+      .addField(new RawField("To", "\"  user1  \" <[email protected]>, Friends: 
\"user2\" <[email protected]>, \"user3\" <[email protected]>;"))
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+      .getMessageId
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |     "Email/get",
+               |     {
+               |       "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |       "ids": ["${messageId.serialize}"],
+               |       "properties": ["header:To:asAddresses"]
+               |     },
+               |     "c1"]]
+               |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |    "id": "${messageId.serialize}",
+           |    "header:To:asAddresses": [
+           |      { "name": "user1", "email": "[email protected]" },
+           |      { "name": "user2", "email": "[email protected]" },
+           |      { "name": "user3", "email": "[email protected]" }
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def emailGetShouldReturnEmptyWhenCannotParseAsAdresses(server: 
GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+    val alicePath = MailboxPath.inbox(ALICE)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(ANDRE.asString())
+      .setFrom(ANDRE.asString())
+      .addField(new RawField("To", "blahblah"))
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+      .getMessageId
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(
+        s"""{
+           |  "using": [
+           |    "urn:ietf:params:jmap:core",
+           |    "urn:ietf:params:jmap:mail"],
+           |  "methodCalls": [[
+           |     "Email/get",
+           |     {
+           |       "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |       "ids": ["${messageId.serialize}"],
+           |       "properties": ["header:To:asAddresses"]
+           |     },
+           |     "c1"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |    "id": "${messageId.serialize}",
+           |    "header:To:asAddresses": []
+           }""".stripMargin)
+  }
 }
\ No newline at end of file
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
index 34ca387..475e1df 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -20,7 +20,7 @@
 package org.apache.james.jmap.json
 
 import org.apache.james.jmap.api.model.Preview
-import org.apache.james.jmap.mail.{Address, BlobId, Charset, Disposition, 
EmailAddress, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, 
EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, 
EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, 
EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, 
FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, HasAttachment, 
HeaderMessageId, IsEncodingProblem, IsT [...]
+import org.apache.james.jmap.mail.{Address, AddressesHeaderValue, BlobId, 
Charset, Disposition, EmailAddress, EmailBody, EmailBodyMetadata, 
EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, 
EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, 
EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, 
EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, 
FetchTextBodyValues, HasAttachment, HeaderMessageId,  [...]
 import org.apache.james.jmap.model._
 import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId}
 import play.api.libs.functional.syntax._
@@ -56,9 +56,11 @@ object EmailGetSerializer {
   private implicit val headerNameWrites: Writes[EmailHeaderName] = 
Json.valueWrites[EmailHeaderName]
   private implicit val rawHeaderWrites: Writes[RawHeaderValue] = 
Json.valueWrites[RawHeaderValue]
   private implicit val textHeaderWrites: Writes[TextHeaderValue] = 
Json.valueWrites[TextHeaderValue]
+  private implicit val addressesHeaderWrites: Writes[AddressesHeaderValue] = 
Json.valueWrites[AddressesHeaderValue]
   private implicit val emailHeaderWrites: Writes[EmailHeaderValue] = {
     case headerValue: RawHeaderValue => 
Json.toJson[RawHeaderValue](headerValue)
     case headerValue: TextHeaderValue => 
Json.toJson[TextHeaderValue](headerValue)
+    case headerValue: AddressesHeaderValue => 
Json.toJson[AddressesHeaderValue](headerValue)
   }
   private implicit val headersWrites: Writes[EmailHeader] = 
Json.writes[EmailHeader]
   private implicit val bodyValueWrites: Writes[EmailBodyValue] = 
Json.writes[EmailBodyValue]
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 3261372..560e509 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
@@ -39,7 +39,7 @@ import org.apache.james.jmap.method.ZoneIdProvider
 import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY
 import org.apache.james.jmap.model.{Keywords, Properties, UTCDate}
 import org.apache.james.mailbox.model.FetchGroup.{FULL_CONTENT, HEADERS, 
MINIMAL}
-import org.apache.james.mailbox.model.{FetchGroup, MessageResult}
+import org.apache.james.mailbox.model.{FetchGroup, MailboxId, MessageId, 
MessageResult}
 import org.apache.james.mailbox.{MailboxSession, MessageIdManager}
 import org.apache.james.mime4j.codec.DecodeMonitor
 import org.apache.james.mime4j.dom.field.{AddressListField, DateTimeField, 
MailboxField, MailboxListField}
@@ -47,8 +47,6 @@ import org.apache.james.mime4j.dom.{Header, Message}
 import org.apache.james.mime4j.message.DefaultMessageBuilder
 import org.apache.james.mime4j.stream.{Field, MimeConfig}
 import org.apache.james.mime4j.util.MimeUtil
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
-import org.apache.james.mime4j.stream.Field
 import org.slf4j.{Logger, LoggerFactory}
 import reactor.core.scala.publisher.{SFlux, SMono}
 import reactor.core.scheduler.Schedulers
@@ -159,19 +157,16 @@ object HeaderMessageId {
   }
 }
 
-
 object ParseOptions {
   val allowedParseOption: Set[String] = Set("asRaw", "asText", "asAddresses", 
"asGroupedAddresses", "asMessageIds", "asDate", "asURLs")
 
   def validate(parseOption: String): Boolean = from(parseOption).isDefined
 
-  def from(value: String): Option[ParseOption] = {
-    allowedParseOption
-      .find(_.equals(value))
-      .map({
-        case "asRaw" => AsRaw
-        case "asText" => AsText
-      })
+  def from(value: String): Option[ParseOption] = value match {
+      case "asRaw" => Some(AsRaw)
+      case "asText" => Some(AsText)
+      case "asAddresses" => Some(AsAddresses)
+      case _ => None
   }
 }
 
@@ -184,6 +179,9 @@ case object AsRaw extends ParseOption {
 case object AsText extends ParseOption {
   override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = 
Some(TextHeaderValue.from(field))
 }
+case object AsAddresses extends ParseOption {
+  override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = 
Some(AddressesHeaderValue.from(field))
+}
 
 case class HeaderMessageId(value: String) extends AnyVal
 
@@ -252,15 +250,15 @@ object EmailHeaders {
         .toList)
       .filter(_.nonEmpty)
 
-  private def extractAddresses(mime4JMessage: Message, fieldName: String): 
Option[List[EmailAddress]] =
+  private def extractAddresses(mime4JMessage: Message, fieldName: String): 
Option[AddressesHeaderValue] =
     extractLastField(mime4JMessage, fieldName)
       .flatMap {
-        case f: AddressListField => Some(EmailAddress.from(f.getAddressList))
-        case f: MailboxListField => Some(EmailAddress.from(f.getMailboxList))
-        case f: MailboxField => Some(List(EmailAddress.from(f.getMailbox)))
+        case f: AddressListField => 
Some(AddressesHeaderValue(EmailAddress.from(f.getAddressList)))
+        case f: MailboxListField => 
Some(AddressesHeaderValue(EmailAddress.from(f.getMailboxList)))
+        case f: MailboxField => 
Some(AddressesHeaderValue(List(EmailAddress.from(f.getMailbox))))
         case _ => None
       }
-      .filter(_.nonEmpty)
+      .filter(_.value.nonEmpty)
 
   private def extractDate(mime4JMessage: Message, fieldName: String): 
Option[Date] =
     extractLastField(mime4JMessage, fieldName)
@@ -280,12 +278,12 @@ case class EmailHeaders(headers: List[EmailHeader],
                         messageId: Option[List[HeaderMessageId]],
                         inReplyTo: Option[List[HeaderMessageId]],
                         references: Option[List[HeaderMessageId]],
-                        to: Option[List[EmailAddress]],
-                        cc: Option[List[EmailAddress]],
-                        bcc: Option[List[EmailAddress]],
-                        from: Option[List[EmailAddress]],
-                        sender: Option[List[EmailAddress]],
-                        replyTo: Option[List[EmailAddress]],
+                        to: Option[AddressesHeaderValue],
+                        cc: Option[AddressesHeaderValue],
+                        bcc: Option[AddressesHeaderValue],
+                        from: Option[AddressesHeaderValue],
+                        sender: Option[AddressesHeaderValue],
+                        replyTo: Option[AddressesHeaderValue],
                         subject: Option[Subject],
                         sentAt: Option[UTCDate])
 
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala
index 5fa00bb..d995610 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala
@@ -23,22 +23,29 @@ import org.apache.james.mime4j.dom.address.{AddressList, 
MailboxList, Mailbox =>
 
 import scala.jdk.CollectionConverters._
 
+object EmailerName {
+  def from(value: String): EmailerName = EmailerName(value.strip())
+}
+
 case class EmailerName(value: String) extends AnyVal
+
 case class Address(value: String) extends AnyVal
 
 object EmailAddress {
-  def from(addressList: AddressList): List[EmailAddress] =
-    from(addressList.flatten())
+  def from(addressList: AddressList): List[EmailAddress] = Option(addressList)
+    .map(addressList => from(addressList.flatten()))
+    .getOrElse(List())
 
   def from(addressList: MailboxList): List[EmailAddress] =
     addressList.asScala
       .toList
       .map(from)
 
-  def from(mailbox: Mime4jMailbox): EmailAddress =
+  def from(mailbox: Mime4jMailbox): EmailAddress = {
     EmailAddress(
-      name = Option(mailbox.getName).map(EmailerName),
+      name = Option(mailbox.getName).map(EmailerName.from),
       email = Address(mailbox.getAddress))
+  }
 }
 
 case class EmailAddress(name: Option[EmailerName], email: Address)
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
index cd1dcea..43e8c84 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
@@ -30,6 +30,7 @@ import org.apache.james.jmap.model.State.State
 import org.apache.james.jmap.model.{AccountId, Properties}
 import org.apache.james.mime4j.dom.Message
 import org.apache.james.mime4j.stream.Field
+import scala.jdk.CollectionConverters._
 
 case class EmailIds(value: List[UnparsedEmailId])
 
@@ -83,7 +84,9 @@ case class EmailGetResponse(accountId: AccountId,
 
 case class SpecificHeaderRequest(headerName: NonEmptyString, property: String, 
parseOption: Option[ParseOption]) {
   def retrieveHeader(message: Message): (String, Option[EmailHeaderValue]) = {
-    val field: Option[Field] = Option(message.getHeader.getField(property))
+    val field: Option[Field] = Option(message.getHeader.getFields(property))
+      .map(_.asScala)
+      .flatMap(fields => fields.reverse.headOption)
 
     (headerName, 
field.flatMap(parseOption.getOrElse(AsRaw).extractHeaderValue(_)))
   }
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
index 282813b..98133ec 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
@@ -22,6 +22,7 @@ package org.apache.james.jmap.mail
 import java.nio.charset.StandardCharsets.US_ASCII
 
 import org.apache.james.mime4j.codec.{DecodeMonitor, DecoderUtil}
+import org.apache.james.mime4j.field.AddressListFieldImpl
 import org.apache.james.mime4j.stream.Field
 import org.apache.james.mime4j.util.MimeUtil
 
@@ -37,10 +38,15 @@ object TextHeaderValue extends EmailHeaderValue {
   def from(field: Field): TextHeaderValue = 
TextHeaderValue(MimeUtil.unfold(DecoderUtil.decodeEncodedWords(field.getBody, 
DecodeMonitor.SILENT)).stripLeading())
 }
 
+object AddressesHeaderValue extends EmailHeaderValue {
+  def from(field: Field): AddressesHeaderValue = 
AddressesHeaderValue(EmailAddress.from(AddressListFieldImpl.PARSER.parse(field, 
DecodeMonitor.SILENT).getAddressList))
+}
+
 case class EmailHeaderName(value: String) extends AnyVal
 
 sealed trait EmailHeaderValue
 case class RawHeaderValue(value: String) extends EmailHeaderValue
 case class TextHeaderValue(value: String) extends EmailHeaderValue
+case class AddressesHeaderValue(value: List[EmailAddress]) extends 
EmailHeaderValue
 
 case class EmailHeader(name: EmailHeaderName, value: EmailHeaderValue)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to