You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/12/05 07:10:11 UTC
[james-project] 04/17: JAMES-2884 EmailSubmission should not
mandate envelope
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 5808b9ed868df73eb123458f73f87baaf21129af
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Nov 26 11:02:11 2020 +0700
JAMES-2884 EmailSubmission should not mandate envelope
Some clients like LTT.RS do not supply them.
Cyrus seems deducts the envelope when missing from the mime fields.
---
.../EmailSubmissionSetMethodContract.scala | 78 +++++++++++++++++++++-
.../james/jmap/mail/EmailSubmissionSet.scala | 2 +-
.../jmap/method/EmailSubmissionSetMethod.scala | 56 ++++++++++++----
3 files changed, 121 insertions(+), 15 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/EmailSubmissionSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
index 5f359dc..108c5d7 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
@@ -30,7 +30,7 @@ import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.http.UserCredential
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, ANDRE_ACCOUNT_ID, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_ACCOUNT_ID, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.DefaultMailboxes
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
@@ -146,6 +146,82 @@ trait EmailSubmissionSetMethodContract {
}
@Test
+ def envelopeShouldBeOptional(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(BOB.asString)
+ .setFrom(BOB.asString)
+ .setTo(ANDRE.asString)
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+
+ val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val andreInboxPath = MailboxPath.inbox(ANDRE)
+ val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andreInboxPath)
+
+ val requestBob =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [
+ | ["EmailSubmission/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "k1490": {
+ | "emailId": "${messageId.serialize}"
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin
+
+ `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(requestBob)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+
+ val requestAndre =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "$ANDRE_ACCOUNT_ID",
+ | "filter": {"inMailbox": "${andreInboxId.serialize}"}
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ val response = `given`(
+ baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+ .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .setBody(requestAndre)
+ .build, new ResponseSpecBuilder().build)
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].ids")
+ .isArray
+ .hasSize(1)
+ }
+ }
+
+ @Test
def emailSubmissionSetCreateShouldSendMailSuccessfullyToSelf(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
index 88a81fb..55fea6b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
@@ -88,4 +88,4 @@ object EmailSubmissionCreationRequest {
case class EmailSubmissionCreationRequest(emailId: MessageId,
identityId: Option[Id],
- envelope: Envelope)
\ No newline at end of file
+ envelope: Option[Envelope])
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
index b008d32..1a8d681 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
@@ -21,9 +21,12 @@ package org.apache.james.jmap.method
import java.io.InputStream
+import cats.implicits._
import eu.timepit.refined.auto._
import javax.annotation.PreDestroy
import javax.inject.Inject
+import javax.mail.Address
+import javax.mail.Message.RecipientType
import javax.mail.internet.{InternetAddress, MimeMessage}
import org.apache.james.core.{MailAddress, Username}
import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION}
@@ -32,7 +35,7 @@ import org.apache.james.jmap.core.SetError.{SetErrorDescription, SetErrorType}
import org.apache.james.jmap.core.{ClientId, Id, Invocation, Properties, ServerId, SetError, State}
import org.apache.james.jmap.json.{EmailSubmissionSetSerializer, ResponseSerializer}
import org.apache.james.jmap.mail.EmailSubmissionSet.EmailSubmissionCreationId
-import org.apache.james.jmap.mail.{EmailSubmissionCreationRequest, EmailSubmissionCreationResponse, EmailSubmissionId, EmailSubmissionSetRequest, EmailSubmissionSetResponse, Envelope}
+import org.apache.james.jmap.mail.{EmailSubmissionAddress, EmailSubmissionCreationRequest, EmailSubmissionCreationResponse, EmailSubmissionId, EmailSubmissionSetRequest, EmailSubmissionSetResponse, Envelope}
import org.apache.james.jmap.method.EmailSubmissionSetMethod.{LOGGER, MAIL_METADATA_USERNAME_ATTRIBUTE}
import org.apache.james.jmap.routes.{ProcessingContext, SessionSupplier}
import org.apache.james.lifecycle.api.{LifecycleUtil, Startable}
@@ -206,18 +209,22 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
message.flatMap(m => {
val submissionId = EmailSubmissionId.generate
- toMimeMessage(submissionId.value, m.getFullContent.getInputStream)
- .flatMap(message => validate(mailboxSession)(message, request.envelope))
- .flatMap(mimeMessage => {
- Try(queue.enQueue(MailImpl.builder()
- .name(submissionId.value)
- .addRecipients(request.envelope.rcptTo.map(_.email).asJava)
- .sender(request.envelope.mailFrom.email)
- .mimeMessage(mimeMessage)
- .addAttribute(new Attribute(MAIL_METADATA_USERNAME_ATTRIBUTE, AttributeValue.of(mailboxSession.getUser.asString())))
- .build()))
- .map(_ => EmailSubmissionCreationResponse(submissionId))
- }).toEither
+
+ val result: Try[EmailSubmissionCreationResponse] = for {
+ message <- toMimeMessage(submissionId.value, m.getFullContent.getInputStream)
+ envelope <- resolveEnvelope(message, request.envelope)
+ validation <- validate(mailboxSession)(message, envelope)
+ enqueue <- Try(queue.enQueue(MailImpl.builder()
+ .name(submissionId.value)
+ .addRecipients(envelope.rcptTo.map(_.email).asJava)
+ .sender(envelope.mailFrom.email)
+ .mimeMessage(message)
+ .addAttribute(new Attribute(MAIL_METADATA_USERNAME_ATTRIBUTE, AttributeValue.of(mailboxSession.getUser.asString())))
+ .build()))
+ } yield {
+ EmailSubmissionCreationResponse(submissionId)
+ }
+ result.toEither
})
}
@@ -248,6 +255,29 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
}
}
+ private def resolveEnvelope(mimeMessage: MimeMessage, maybeEnvelope: Option[Envelope]): Try[Envelope] =
+ maybeEnvelope.map(Success(_)).getOrElse(extractEnvelope(mimeMessage))
+
+ private def extractEnvelope(mimeMessage: MimeMessage): Try[Envelope] = {
+ val to: List[Address] = Option(mimeMessage.getRecipients(RecipientType.TO)).toList.flatten
+ val cc: List[Address] = Option(mimeMessage.getRecipients(RecipientType.CC)).toList.flatten
+ val bcc: List[Address] = Option(mimeMessage.getRecipients(RecipientType.BCC)).toList.flatten
+ for {
+ mailFrom <- Option(mimeMessage.getFrom).toList.flatten
+ .headOption
+ .map(_.asInstanceOf[InternetAddress].getAddress)
+ .map(s => Try(new MailAddress(s)))
+ .getOrElse(Failure(new IllegalArgumentException("Implicit envelope detection requires a from field")))
+ .map(EmailSubmissionAddress)
+ rcptTo <- (to ++ cc ++ bcc)
+ .map(_.asInstanceOf[InternetAddress].getAddress)
+ .map(s => Try(new MailAddress(s)))
+ .sequence
+ } yield {
+ Envelope(mailFrom, rcptTo.map(EmailSubmissionAddress))
+ }
+ }
+
private def recordCreationIdInProcessingContext(emailSubmissionCreationId: EmailSubmissionCreationId,
processingContext: ProcessingContext,
emailSubmissionId: EmailSubmissionId): Either[IllegalArgumentException, ProcessingContext] =
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org