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 00441ebc676176ff290287a5249a35c908fa8951 Author: LanKhuat <[email protected]> AuthorDate: Tue Sep 22 18:36:56 2020 +0700 JAMES-3379 Email/get specific parsed headers: asDate --- .../rfc8621/contract/EmailGetMethodContract.scala | 107 +++++++++++++++++++++ .../james/jmap/json/EmailGetSerializer.scala | 4 +- .../scala/org/apache/james/jmap/mail/Email.scala | 18 ++-- .../org/apache/james/jmap/mail/EmailGet.scala | 13 ++- .../org/apache/james/jmap/mail/EmailHeader.scala | 11 ++- 5 files changed, 142 insertions(+), 11 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 f266760..59b4a52 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 @@ -6415,4 +6415,111 @@ trait EmailGetMethodContract { | ] }""".stripMargin) } + + @Test + def emailGetShouldReturnSpecificHeaderAsDate(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("Date", + "Wed, 9 Sep 2020 07:00:26 +0200")) + .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:Date:asDate"] + | }, + | "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:Date:asDate": "2020-09-09T05:00:26Z" + }""".stripMargin) + } + + @Test + def emailGetShouldReturnNullWhenInvalidSpecificHeaderAsDate(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("Date", "Invalid")) + .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:Date:asDate"] + | }, + | "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:Date:asDate": null + }""".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 f937c44..6712ef1 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, AddressesHeaderValue, BlobId, Charset, Disposition, EmailAddress, EmailAddressGroup, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, GroupName, Gr [...] +import org.apache.james.jmap.mail.{Address, AddressesHeaderValue, BlobId, Charset, DateHeaderValue, Disposition, EmailAddress, EmailAddressGroup, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValu [...] import org.apache.james.jmap.model._ import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId} import play.api.libs.functional.syntax._ @@ -64,12 +64,14 @@ object EmailGetSerializer { "addresses" -> Json.toJson(o.addresses)) private implicit val groupedAddressesHeaderWrites: Writes[GroupedAddressesHeaderValue] = Json.valueWrites[GroupedAddressesHeaderValue] private implicit val messageIdsHeaderWrites: Writes[MessageIdsHeaderValue] = Json.valueWrites[MessageIdsHeaderValue] + private implicit val dateHeaderWrites: Writes[DateHeaderValue] = Json.valueWrites[DateHeaderValue] 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) case headerValue: GroupedAddressesHeaderValue => Json.toJson[GroupedAddressesHeaderValue](headerValue) case headerValue: MessageIdsHeaderValue => Json.toJson[MessageIdsHeaderValue](headerValue) + case headerValue: DateHeaderValue => Json.toJson[DateHeaderValue](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 de92da1..62d225c 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 @@ -168,6 +168,7 @@ object ParseOptions { case "asAddresses" => Some(AsAddresses) case "asGroupedAddresses" => Some(AsGroupedAddresses) case "asMessageIds" => Some(AsMessageIds) + case "asDate" => Some(AsDate) case _ => None } } @@ -190,7 +191,11 @@ case object AsGroupedAddresses extends ParseOption { case object AsMessageIds extends ParseOption { override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = Some(MessageIdsHeaderValue.from(field)) } +case object AsDate extends ParseOption { + override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = Some(DateHeaderValue.from(field, ZoneId.systemDefault())) + def extractHeaderValue(field: Field, zoneId: ZoneId): Option[EmailHeaderValue] = Some(DateHeaderValue.from(field, zoneId)) +} case class HeaderMessageId(value: String) extends AnyVal case class Subject(value: String) extends AnyVal @@ -228,10 +233,10 @@ object EmailHeaders { sentAt = extractDate(mime4JMessage, "Date").map(date => UTCDate.from(date, zoneId))) } - def extractSpecificHeaders(properties: Option[Properties])(mime4JMessage: Message) = { + def extractSpecificHeaders(properties: Option[Properties])(zoneId: ZoneId, mime4JMessage: Message) = { properties.getOrElse(Properties.empty()).value .flatMap(property => SpecificHeaderRequest.from(property).toOption) - .map(_.retrieveHeader(mime4JMessage)) + .map(_.retrieveHeader(zoneId, mime4JMessage)) .toMap } @@ -272,7 +277,7 @@ object EmailHeaders { private def extractDate(mime4JMessage: Message, fieldName: String): Option[Date] = extractLastField(mime4JMessage, fieldName) .flatMap { - case f: DateTimeField => Some(f.getDate) + case f: DateTimeField => Option(f.getDate) case _ => None } @@ -424,10 +429,9 @@ private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) e size = sanitizeSize(firstMessage.getSize), keywords = keywords), header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(mime4JMessage)) + specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) } } - } private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, previewFactory: Preview.Factory) extends EmailViewFactory[EmailFullView] { @@ -468,7 +472,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre htmlBody = bodyStructure.htmlBody, attachments = bodyStructure.attachments, bodyValues = bodyValues), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(mime4JMessage)) + specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) } } @@ -612,7 +616,7 @@ private class EmailFastViewReader @Inject()(messageIdManager: MessageIdManager, hasAttachment = HasAttachment(fastView.hasAttachment), preview = fastView.getPreview), header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(mime4JMessage)) + specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) } } } 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 43e8c84..ea49946 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 @@ -19,6 +19,8 @@ package org.apache.james.jmap.mail +import java.time.ZoneId + import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.numeric.NonNegative @@ -30,6 +32,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,11 +86,17 @@ case class EmailGetResponse(accountId: AccountId, notFound: EmailNotFound) case class SpecificHeaderRequest(headerName: NonEmptyString, property: String, parseOption: Option[ParseOption]) { - def retrieveHeader(message: Message): (String, Option[EmailHeaderValue]) = { + def retrieveHeader(zoneId: ZoneId, message: Message): (String, Option[EmailHeaderValue]) = { val field: Option[Field] = Option(message.getHeader.getFields(property)) .map(_.asScala) .flatMap(fields => fields.reverse.headOption) - (headerName, field.flatMap(parseOption.getOrElse(AsRaw).extractHeaderValue(_))) + (headerName, field.flatMap({ + val option = parseOption.getOrElse(AsRaw) + option match { + case AsDate => AsDate.extractHeaderValue(_, zoneId) + case _ => option.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 58db534..343f6dd 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 @@ -20,9 +20,10 @@ package org.apache.james.jmap.mail import java.nio.charset.StandardCharsets.US_ASCII -import java.util.Date +import java.time.ZoneId import org.apache.commons.lang3.StringUtils +import org.apache.james.jmap.model.UTCDate import org.apache.james.mime4j.codec.{DecodeMonitor, DecoderUtil} import org.apache.james.mime4j.dom.address.{AddressList, Group, Address => Mime4jAddress, Mailbox => Mime4jMailbox} import org.apache.james.mime4j.field.{AddressListFieldImpl, DateTimeFieldImpl} @@ -94,6 +95,13 @@ object MessageIdsHeaderValue { } } +object DateHeaderValue extends EmailHeaderValue { + def from(field: Field, zoneId: ZoneId): DateHeaderValue = + Option(DateTimeFieldImpl.PARSER.parse(field, DecodeMonitor.SILENT).getDate) + .map(date => DateHeaderValue(Some(UTCDate.from(date, zoneId)))) + .getOrElse(DateHeaderValue(None)) +} + case class EmailHeaderName(value: String) extends AnyVal sealed trait EmailHeaderValue @@ -102,5 +110,6 @@ case class TextHeaderValue(value: String) extends EmailHeaderValue case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderValue case class GroupedAddressesHeaderValue(value: List[EmailAddressGroup]) extends EmailHeaderValue case class MessageIdsHeaderValue(value: Option[List[HeaderMessageId]]) extends EmailHeaderValue +case class DateHeaderValue(value: Option[UTCDate]) extends EmailHeaderValue case class EmailHeader(name: EmailHeaderName, value: EmailHeaderValue) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
