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 7ce20ab746f043b62c200bb0a557a452f2ed4217 Author: LanKhuat <[email protected]> AuthorDate: Wed Sep 23 11:37:12 2020 +0700 JAMES-3379 Email/get specific parsed headers: asURLs --- .../rfc8621/contract/EmailGetMethodContract.scala | 162 +++++++++++++++++++++ .../james/jmap/json/EmailGetSerializer.scala | 5 +- .../scala/org/apache/james/jmap/mail/Email.scala | 16 +- .../org/apache/james/jmap/mail/EmailHeader.scala | 20 ++- 4 files changed, 200 insertions(+), 3 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 59b4a52..61fb12f 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 @@ -6522,4 +6522,166 @@ trait EmailGetMethodContract { | "header:Date:asDate": null }""".stripMargin) } + + @Test + def emailGetShouldReturnSpecificHeaderAsURLs(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("List-Help", "<http://www.host.com/list/>, <mailto:[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:List-Help:asURLs"] + | }, + | "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:List-Help:asURLs": [ + | "http://www.host.com/list/", + | "mailto:[email protected]" + | ] + }""".stripMargin) + } + + @Test + def emailGetShouldReturnSpecificHeaderAsURLsAndIgnoresComments(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("List-Help", "<http://www.host.com/list/>, (FTP) <mailto:[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:List-Help:asURLs"] + | }, + | "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:List-Help:asURLs": [ + | "http://www.host.com/list/", + | "mailto:[email protected]" + | ] + }""".stripMargin) + } + + @Test + def emailGetShouldReturnNullWhenInvalidSpecificHeaderAsURLs(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("List-Help", "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:List-Help:asURLs"] + | }, + | "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:List-Help:asURLs": 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 6712ef1..9ec87f6 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, 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.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._ @@ -65,6 +65,8 @@ object EmailGetSerializer { 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 headerURLWrites: Writes[HeaderURL] = Json.valueWrites[HeaderURL] + private implicit val urlsHeaderWrites: Writes[URLsHeaderValue] = Json.valueWrites[URLsHeaderValue] private implicit val emailHeaderWrites: Writes[EmailHeaderValue] = { case headerValue: RawHeaderValue => Json.toJson[RawHeaderValue](headerValue) case headerValue: TextHeaderValue => Json.toJson[TextHeaderValue](headerValue) @@ -72,6 +74,7 @@ object EmailGetSerializer { case headerValue: GroupedAddressesHeaderValue => Json.toJson[GroupedAddressesHeaderValue](headerValue) case headerValue: MessageIdsHeaderValue => Json.toJson[MessageIdsHeaderValue](headerValue) case headerValue: DateHeaderValue => Json.toJson[DateHeaderValue](headerValue) + case headerValue: URLsHeaderValue => Json.toJson[URLsHeaderValue](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 62d225c..0653d80 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 @@ -34,6 +34,7 @@ import eu.timepit.refined.types.string.NonEmptyString import javax.inject.Inject import org.apache.james.jmap.api.model.Preview import org.apache.james.jmap.api.projections.{MessageFastViewPrecomputedProperties, MessageFastViewProjection} +import org.apache.james.jmap.mail.BracketHeader.sanitize import org.apache.james.jmap.mail.Email.{Size, sanitizeSize} import org.apache.james.jmap.method.ZoneIdProvider import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY @@ -149,8 +150,14 @@ case object FullReadLevel extends ReadLevel object HeaderMessageId { def from(string: String): HeaderMessageId = HeaderMessageId(sanitize(string)) +} + +object HeaderURL { + def from(string: String): HeaderURL = HeaderURL(sanitize(string)) +} - private def sanitize(string: String): String = string match { +object BracketHeader { + def sanitize(string: String): String = string match { case s if s.startsWith("<") => sanitize(s.substring(1)) case s if s.endsWith(">") => sanitize(s.substring(0, s.length - 1)) case s => s @@ -169,6 +176,7 @@ object ParseOptions { case "asGroupedAddresses" => Some(AsGroupedAddresses) case "asMessageIds" => Some(AsMessageIds) case "asDate" => Some(AsDate) + case "asURLs" => Some(AsURLs) case _ => None } } @@ -196,6 +204,10 @@ case object AsDate extends ParseOption { def extractHeaderValue(field: Field, zoneId: ZoneId): Option[EmailHeaderValue] = Some(DateHeaderValue.from(field, zoneId)) } +case object AsURLs extends ParseOption { + override def extractHeaderValue(field: Field): Option[EmailHeaderValue] = Some(URLsHeaderValue.from(field)) +} + case class HeaderMessageId(value: String) extends AnyVal case class Subject(value: String) extends AnyVal @@ -206,6 +218,8 @@ case class ThreadId(value: String) extends AnyVal case class HasAttachment(value: Boolean) extends AnyVal +case class HeaderURL(value: String) extends AnyVal + case class EmailMetadata(id: MessageId, blobId: BlobId, threadId: ThreadId, 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 343f6dd..7f05aec 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 @@ -26,7 +26,7 @@ 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} +import org.apache.james.mime4j.field.{AddressListFieldImpl, ContentLocationFieldImpl, DateTimeFieldImpl} import org.apache.james.mime4j.stream.Field import org.apache.james.mime4j.util.MimeUtil @@ -102,6 +102,23 @@ object DateHeaderValue extends EmailHeaderValue { .getOrElse(DateHeaderValue(None)) } +object URLsHeaderValue extends EmailHeaderValue { + def from(field: Field): URLsHeaderValue = { + val url: Option[List[HeaderURL]] = Option(ContentLocationFieldImpl.PARSER.parse(field, DecodeMonitor.SILENT).getLocation) + .map(urls => urls.split(',') + .toList + .flatMap(url => { + if(url.startsWith("<") && url.endsWith(">")) { + scala.Right(HeaderURL.from(url)) + } else { + Left() + } + }.toOption)) + + URLsHeaderValue(url.filter(_.nonEmpty)) + } +} + case class EmailHeaderName(value: String) extends AnyVal sealed trait EmailHeaderValue @@ -111,5 +128,6 @@ case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderVa 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 URLsHeaderValue(value: Option[List[HeaderURL]]) extends EmailHeaderValue case class EmailHeader(name: EmailHeaderName, value: EmailHeaderValue) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
