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 f170f615e83f16b9f529b82a9519206293d934c0 Author: LanKhuat <[email protected]> AuthorDate: Wed Sep 16 18:14:12 2020 +0700 JAMES-3379 Email/get specific parsed headers: asText --- .../rfc8621/contract/EmailGetMethodContract.scala | 105 +++++++++++++++++++++ .../james/jmap/json/EmailGetSerializer.scala | 4 +- .../scala/org/apache/james/jmap/mail/Email.scala | 30 +++--- .../org/apache/james/jmap/mail/EmailGet.scala | 10 +- .../org/apache/james/jmap/mail/EmailHeader.scala | 7 ++ 5 files changed, 138 insertions(+), 18 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 9a03780..c8783c8 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 @@ -5729,4 +5729,109 @@ trait EmailGetMethodContract { | "header:Sender:asRaw": " [email protected]" |}""".stripMargin) } + + @Test + def emailGetShouldReturnSpecificHeadersAsText(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()) + .setSubject(" World domination\r\n" + + " and this is also part of the header\r\n") + .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:Subject:asText"] + | }, + | "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:Subject:asText": "World domination and this is also part of the header" + |}""".stripMargin) + } + + @Test + def emailGetShouldSupportDifferentSpecificHeadersTypeOnSameMessage(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()) + .setSubject(" World domination\r\n" + + " and this is also part of the header\r\n") + .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:Subject:asText", "header:Subject:asRaw"] + | }, + | "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:Subject:asRaw": " =?US-ASCII?Q?___World_domination=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header=0D=0A?=", + | "header:Subject:asText": "World domination and this is also part of the header" + |}""".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 693a8fa..34ca387 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, 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.model._ import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId} import play.api.libs.functional.syntax._ @@ -55,8 +55,10 @@ object EmailGetSerializer { private implicit val hasAttachmentWrites: Writes[HasAttachment] = Json.valueWrites[HasAttachment] 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 emailHeaderWrites: Writes[EmailHeaderValue] = { case headerValue: RawHeaderValue => Json.toJson[RawHeaderValue](headerValue) + case headerValue: TextHeaderValue => Json.toJson[TextHeaderValue](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 a6872c7..3261372 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, MailboxId, MessageId, MessageResult} +import org.apache.james.mailbox.model.{FetchGroup, 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,6 +47,8 @@ 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,23 +161,29 @@ object HeaderMessageId { object ParseOptions { - val allowedParseOption: Properties = Properties("asRaw", "asText", "asAddresses", "asGroupedAddresses", "asMessageIds", "asDate", "asURLs") + val allowedParseOption: Set[String] = Set("asRaw", "asText", "asAddresses", "asGroupedAddresses", "asMessageIds", "asDate", "asURLs") - def validate(value: String): Boolean = { - from(value).isDefined - } + def validate(parseOption: String): Boolean = from(parseOption).isDefined def from(value: String): Option[ParseOption] = { - allowedParseOption.value - .find(_.value.equals(value)) - .map({parseOption => parseOption.value match { + allowedParseOption + .find(_.equals(value)) + .map({ case "asRaw" => AsRaw - }}) + case "asText" => AsText + }) } } -sealed trait ParseOption -case object AsRaw extends ParseOption +sealed trait ParseOption { + def extractHeaderValue(field: Field): Option[EmailHeaderValue] +} +case object AsRaw extends ParseOption { + override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = Some(RawHeaderValue.from(field)) +} +case object AsText extends ParseOption { + override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = Some(TextHeaderValue.from(field)) +} case class HeaderMessageId(value: String) extends AnyVal 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 0eef2da..cd1dcea 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 @@ -49,9 +49,10 @@ object SpecificHeaderRequest { case property if property.startsWith(SPECIFIC_HEADER_PREFIX) => val headerName = property.substring(SPECIFIC_HEADER_PREFIX.length) if (headerName.contains(":")) { - val parseOption = headerName.substring(headerName.indexOf(":") + 1) + val indexOfFirstColon = headerName.indexOf(":") + val parseOption = headerName.substring(indexOfFirstColon + 1) if (ParseOptions.validate(parseOption)) { - scala.Right(SpecificHeaderRequest(property, headerName.substring(0, headerName.indexOf(":")), ParseOptions.from(parseOption))) + scala.Right(SpecificHeaderRequest(property, headerName.substring(0, indexOfFirstColon), ParseOptions.from(parseOption))) } else { Left(property) } @@ -84,9 +85,6 @@ case class SpecificHeaderRequest(headerName: NonEmptyString, property: String, p def retrieveHeader(message: Message): (String, Option[EmailHeaderValue]) = { val field: Option[Field] = Option(message.getHeader.getField(property)) - parseOption.getOrElse(AsRaw) match { - case AsRaw => (headerName, field.map(RawHeaderValue.from)) - case _ => (headerName, field.map(RawHeaderValue.from)) - } + (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 4eb0575..282813b 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 @@ -21,7 +21,9 @@ 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.stream.Field +import org.apache.james.mime4j.util.MimeUtil object EmailHeader { def apply(field: Field): EmailHeader = EmailHeader(EmailHeaderName(field.getName), RawHeaderValue.from(field)) @@ -31,9 +33,14 @@ object RawHeaderValue extends EmailHeaderValue { def from(field: Field): RawHeaderValue = RawHeaderValue(new String(field.getRaw.toByteArray, US_ASCII).substring(field.getName.length + 1)) } +object TextHeaderValue extends EmailHeaderValue { + def from(field: Field): TextHeaderValue = TextHeaderValue(MimeUtil.unfold(DecoderUtil.decodeEncodedWords(field.getBody, DecodeMonitor.SILENT)).stripLeading()) +} + 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 EmailHeader(name: EmailHeaderName, value: EmailHeaderValue) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
