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 08c28e928f34a2043d3bdb25b0bfe44ddd229628 Author: Benoit Tellier <[email protected]> AuthorDate: Tue Oct 27 11:16:53 2020 +0700 JAMES-3436 Email/set create: Support convenience messageId headers --- .../rfc8621/contract/EmailSetMethodContract.scala | 58 ++++++++++++++++++++++ .../james/jmap/json/EmailSetSerializer.scala | 13 ++++- .../scala/org/apache/james/jmap/mail/Email.scala | 8 +-- .../org/apache/james/jmap/mail/EmailHeader.scala | 7 ++- .../org/apache/james/jmap/mail/EmailSet.scala | 7 +++ 5 files changed, 86 insertions(+), 7 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/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala index c5ed96d..0e2f4bd 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala @@ -530,6 +530,64 @@ trait EmailSetMethodContract { } @Test + def createShouldSupportMessageIdHeaders(server: GuiceJamesServer): Unit = { + val bobPath = MailboxPath.inbox(BOB) + val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) + + val request = s"""{ + | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + | "methodCalls": [ + | ["Email/set", { + | "accountId": "$ACCOUNT_ID", + | "create": { + | "aaaaaa":{ + | "mailboxIds": { + | "${mailboxId.serialize}": true + | }, + | "references": ["aa@bb", "cc@dd"], + | "inReplyTo": ["ee@ff", "gg@hh"], + | "messageId": ["ii@jj", "kk@ll"] + | } + | } + | }, "c1"], + | ["Email/get", + | { + | "accountId": "$ACCOUNT_ID", + | "ids": ["#aaaaaa"], + | "properties": ["references", "inReplyTo", "messageId"] + | }, + | "c2"]] + |}""".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) + .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id") + .inPath("methodResponses[0][1].created.aaaaaa") + .isEqualTo("{}".stripMargin) + + assertThatJson(response) + .whenIgnoringPaths("methodResponses[1][1].list[0].id") + .inPath(s"methodResponses[1][1].list") + .isEqualTo( + s"""[{ + | "references": ["aa@bb", "cc@dd"], + | "inReplyTo": ["ee@ff", "gg@hh"], + | "messageId": ["ii@jj", "kk@ll"] + |}]""".stripMargin) + } + + @Test def createShouldFailIfForbidden(server: GuiceJamesServer): Unit = { val andrePath = MailboxPath.inbox(ANDRE) val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala index a5f0cd2..caacce8 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala @@ -23,12 +23,12 @@ import cats.implicits._ import eu.timepit.refined.refineV import javax.inject.Inject import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId, UnparsedMessageIdConstraint} -import org.apache.james.jmap.mail.{AddressesHeaderValue, DestroyIds, EmailAddress, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, EmailerName, MailboxIds, Subject} +import org.apache.james.jmap.mail.{AddressesHeaderValue, DestroyIds, EmailAddress, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, EmailerName, HeaderMessageId, MailboxIds, MessageIdsHeaderValue, Subject} import org.apache.james.jmap.model.Id.IdConstraint import org.apache.james.jmap.model.KeywordsFactory.STRICT_KEYWORDS_FACTORY import org.apache.james.jmap.model.{Keyword, Keywords, SetError} import org.apache.james.mailbox.model.{MailboxId, MessageId} -import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OWrites, Reads, Writes} +import play.api.libs.json.{JsArray, JsBoolean, JsError, JsNull, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OWrites, Reads, Writes} import scala.util.Try @@ -231,8 +231,17 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject] private implicit val emailerNameReads: Reads[EmailerName] = Json.valueReads[EmailerName] + private implicit val headerMessageIdReads: Reads[HeaderMessageId] = Json.valueReads[HeaderMessageId] private implicit val emailAddressReads: Reads[EmailAddress] = Json.reads[EmailAddress] private implicit val addressesHeaderValueReads: Reads[AddressesHeaderValue] = Json.valueReads[AddressesHeaderValue] + private implicit val messageIdsHeaderValueReads: Reads[MessageIdsHeaderValue] = { + case JsArray(value) => value.map(headerMessageIdReads.reads) + .map(_.asEither) + .toList + .sequence + .fold(e => JsError(e), + ids => JsSuccess(MessageIdsHeaderValue(Some(ids).filter(_.nonEmpty)))) + } private implicit val emailCreationRequestReads: Reads[EmailCreationRequest] = Json.reads[EmailCreationRequest] def deserialize(input: JsValue): JsResult[EmailSetRequest] = Json.fromJson[EmailSetRequest](input) 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 42c2358..fb93168 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 @@ -276,10 +276,10 @@ object EmailHeaders { private def extractMessageId(mime4JMessage: Message, fieldName: String): MessageIdsHeaderValue = MessageIdsHeaderValue( Option(mime4JMessage.getHeader.getFields(fieldName)) - .map(_.asScala - .map(_.getBody) - .map(HeaderMessageId.from) - .toList) + .map(_.asScala.toList) + .flatMap(fields => fields.map(field => MessageIdsHeaderValue.from(field).value) + .sequence + .map(_.flatten)) .filter(_.nonEmpty)) private def extractAddresses(mime4JMessage: Message, fieldName: String): Option[AddressesHeaderValue] = 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 02f6645..9b877fb 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 @@ -128,7 +128,12 @@ case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderVa def asMime4JMailboxList: Option[List[Mime4jMailbox]] = Some(value.map(_.asMime4JMailbox)).filter(_.nonEmpty) } case class GroupedAddressesHeaderValue(value: List[EmailAddressGroup]) extends EmailHeaderValue -case class MessageIdsHeaderValue(value: Option[List[HeaderMessageId]]) extends EmailHeaderValue +case class MessageIdsHeaderValue(value: Option[List[HeaderMessageId]]) extends EmailHeaderValue { + def asString: Option[String] = value.map(messageIds => messageIds + .map(_.value) + .map(messageId => s"<${messageId}>") + .mkString(" ")) +} case class DateHeaderValue(value: Option[UTCDate]) extends EmailHeaderValue case class URLsHeaderValue(value: Option[List[HeaderURL]]) extends EmailHeaderValue diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala index 42d68fc..767f021 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala @@ -33,6 +33,7 @@ import org.apache.james.mailbox.model.MessageId import org.apache.james.mime4j.dom.Message import org.apache.james.mime4j.dom.field.FieldName import org.apache.james.mime4j.field.Fields +import org.apache.james.mime4j.stream.RawField import play.api.libs.json.JsObject import scala.jdk.CollectionConverters._ @@ -53,6 +54,9 @@ object EmailSet { } case class EmailCreationRequest(mailboxIds: MailboxIds, + messageId: Option[MessageIdsHeaderValue], + references: Option[MessageIdsHeaderValue], + inReplyTo: Option[MessageIdsHeaderValue], from: Option[AddressesHeaderValue], to: Option[AddressesHeaderValue], cc: Option[AddressesHeaderValue], @@ -65,6 +69,9 @@ case class EmailCreationRequest(mailboxIds: MailboxIds, receivedAt: Option[UTCDate]) { def toMime4JMessage: Message = { val builder = Message.Builder.of + references.flatMap(_.asString).map(new RawField("References", _)).foreach(builder.setField) + inReplyTo.flatMap(_.asString).map(new RawField("In-Reply-To", _)).foreach(builder.setField) + messageId.flatMap(_.asString).map(new RawField(FieldName.MESSAGE_ID, _)).foreach(builder.setField) subject.foreach(value => builder.setSubject(value.value)) from.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setFrom) to.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setTo) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
