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