diff --git a/core/src/main/java/org/apache/james/core/Domain.java b/core/src/main/java/org/apache/james/core/Domain.java index 50ad81c4126..b4372e2b807 100644 --- a/core/src/main/java/org/apache/james/core/Domain.java +++ b/core/src/main/java/org/apache/james/core/Domain.java @@ -20,6 +20,7 @@ package org.apache.james.core; import java.io.Serializable; +import java.net.IDN; import java.util.Locale; import java.util.Objects; @@ -54,9 +55,16 @@ public static Domain of(String domain) { Preconditions.checkArgument(domain.length() <= MAXIMUM_DOMAIN_LENGTH, "Domain name length should not exceed %s characters", MAXIMUM_DOMAIN_LENGTH); - String domainWithoutBrackets = removeBrackets(domain); + String domainWithoutBrackets = IDN.toASCII(removeBrackets(domain), IDN.ALLOW_UNASSIGNED); Preconditions.checkArgument(PART_CHAR_MATCHER.matchesAllOf(domainWithoutBrackets), - "Domain parts ASCII chars must be a-z A-Z 0-9 - or _ in %s", domain); + "Domain parts ASCII chars must be a-z A-Z 0-9 - or _ in %s", domain); + + if (domainWithoutBrackets.startsWith("xn--") || + domainWithoutBrackets.contains(".xn--")) { + domainWithoutBrackets = IDN.toUnicode(domainWithoutBrackets); + Preconditions.checkArgument(!domainWithoutBrackets.startsWith("xn--") && + !domainWithoutBrackets.contains(".xn--")); + } int pos = 0; int nextDot = domainWithoutBrackets.indexOf('.'); diff --git a/core/src/main/java/org/apache/james/core/MailAddress.java b/core/src/main/java/org/apache/james/core/MailAddress.java index 41e88b2b90c..3be44aa8426 100644 --- a/core/src/main/java/org/apache/james/core/MailAddress.java +++ b/core/src/main/java/org/apache/james/core/MailAddress.java @@ -19,6 +19,7 @@ package org.apache.james.core; +import java.net.IDN; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -418,7 +419,7 @@ public Optional toInternetAddress() { try { return Optional.of(new InternetAddress(toString())); } catch (AddressException ae) { - LOGGER.warn("A valid address '{}' as per James criterial fails to parse as a jakarta.mail InternetAdrress", asString()); + LOGGER.warn("A valid address '{}' as per James criteria fails to parse as a jakarta.mail InternetAdrress", asString()); return Optional.empty(); } } @@ -549,15 +550,15 @@ private int parseUnquotedLocalPart(StringBuilder lpSB, String address, int pos) //End of local-part break; } else { - // ::= any one of the 128 ASCII characters, but not any - // or + // ::= any printable ASCII character, or any non-ASCII + // unicode codepoint, but not or // ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "." // | "," | ";" | ":" | "@" """ | the control // characters (ASCII codes 0 through 31 inclusive and // 127) // ::= the space character (ASCII code 32) char c = address.charAt(pos); - if (c <= 31 || c >= 127 || c == ' ') { + if (c <= 31 || c == 127 || c == ' ') { throw new AddressException("Invalid character in local-part (user account) at position " + (pos + 1) + " in '" + address + "'", address, pos + 1); } @@ -688,6 +689,7 @@ private int parseDomain(StringBuilder dSB, String address, int pos) throws Addre // in practice though, we should relax this as domain names can start // with digits as well as letters. So only check that doesn't start // or end with hyphen. + boolean unicode = false; while (true) { if (pos >= address.length()) { break; @@ -700,6 +702,11 @@ private int parseDomain(StringBuilder dSB, String address, int pos) throws Addre resultSB.append(ch); pos++; continue; + } else if (ch >= 0x0080) { + resultSB.append(ch); + pos++; + unicode = true; + continue; } if (ch == '.') { break; @@ -707,6 +714,19 @@ private int parseDomain(StringBuilder dSB, String address, int pos) throws Addre throw new AddressException("Invalid character at " + pos + " in '" + address + "'", address, pos); } String result = resultSB.toString(); + if (unicode) { + try { + result = IDN.toASCII(result, IDN.ALLOW_UNASSIGNED); + } catch (IllegalArgumentException e) { + throw new AddressException("Domain invalid according to IDNA", address); + } + } + if (result.startsWith("xn--") || result.contains(".xn--")) { + result = IDN.toUnicode(result); + if (result.startsWith("xn--") || result.contains(".xn--")) { + throw new AddressException("Domain invalid according to IDNA", address); + } + } if (result.startsWith("-") || result.endsWith("-")) { throw new AddressException("Domain name cannot begin or end with a hyphen \"-\" at position " + (pos + 1) + " in '" + address + "'", address, pos + 1); diff --git a/core/src/test/java/org/apache/james/core/DomainTest.java b/core/src/test/java/org/apache/james/core/DomainTest.java new file mode 100644 index 00000000000..8a9e4a56214 --- /dev/null +++ b/core/src/test/java/org/apache/james/core/DomainTest.java @@ -0,0 +1,81 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + + +class DomainTest { + @Test + void testPlainDomain() { + Domain d1 = Domain.of("example.com"); + assertThat(d1.name().equals(d1.asString())); + Domain d2 = Domain.of("Example.com"); + assertThat(d2.name()).isNotEqualTo(d2.asString()); + assertThat(d1.asString()).isEqualTo(d2.asString()); + } + + @Test + void testIPv4Domain() { + Domain d1 = Domain.of("192.0.4.1"); + assertThat(d1.asString()).isEqualTo("192.0.4.1"); + } + + @Test + void testPunycodeIDN() { + Domain d1 = Domain.of("xn--gr-zia.example"); + assertThat(d1.asString()).isEqualTo("grå.example"); + } + + @Test + void testDevanagariDomain() { + Domain d1 = Domain.of("डाटामेल.भारत"); + assertThat(d1.asString()).isEqualTo(d1.name()); + } + + private static Stream malformedDomains() { + return Stream.of( + "😊☺️.example", // emoji not permitted by IDNA + "#.example", // really and truly not permitted + "\uFEFF.example", // U+FEFF is the byte order mark + "\u200C.example", // U+200C is a zero-width non-joiner + "\u200Eibm.example" // U+200E is left-to-right + ) + .map(Arguments::of); + } + + @ParameterizedTest + @MethodSource("malformedDomains") + void testMalformedDomains(String malformed) { + assertThatThrownBy(() -> Domain.of(malformed)) + .as("rejecting malformed domain " + malformed) + .isInstanceOf(IllegalArgumentException.class); + } +} + + diff --git a/core/src/test/java/org/apache/james/core/MailAddressTest.java b/core/src/test/java/org/apache/james/core/MailAddressTest.java index 86e5adece4b..e9874c9800f 100644 --- a/core/src/test/java/org/apache/james/core/MailAddressTest.java +++ b/core/src/test/java/org/apache/james/core/MailAddressTest.java @@ -22,12 +22,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import java.util.Properties; import java.util.stream.Stream; +import jakarta.mail.Session; import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -55,6 +58,13 @@ private static Stream goodAddresses() { "\\.server-dev@james.apache.org", "Abc@10.42.0.1", "Abc.123@example.com", + "Loïc.Accentué@voilà.fr8", + "pelé@exemple.com", + "δοκιμή@παράδειγμα.δοκιμή", + "我買@屋企.香港", + "二ノ宮@黒川.日本", + "медведь@с-балалайкой.рф", + //"संपर्क@डाटामेल.भारत", fails in Jakarta, reason still unknown "user+mailbox/department=shipping@example.com", "user+mailbox@example.com", "\"Abc@def\"@example.com", @@ -96,26 +106,30 @@ private static Stream badAddresses() { "server-dev@[127.0.1.1.1]", "server-dev@[127.0.1.-1]", "test@dom+ain.com", + "test@xn--.example", "\"a..b\"@domain.com", // jakarta.mail is unable to handle this so we better reject it "server-dev\\.@james.apache.org", // jakarta.mail is unable to handle this so we better reject it "a..b@domain.com", - // According to wikipedia these addresses are valid but as jakarta.mail is unable - // to work with them we shall rather reject them (note that this is not breaking retro-compatibility) - "Loïc.Accentué@voilà.fr8", - "pelé@exemple.com", - "δοκιμή@παράδειγμα.δοκιμή", - "我買@屋企.香港", - "二ノ宮@黒川.日本", - "медведь@с-балалайкой.рф", - "संपर्क@डाटामेल.भारत", + "sales@\u200Eibm.example", // U+200E is left-to-right + // According to wikipedia this address is valid but as jakarta.mail is unable + // to work with it we shall rather reject them (note that this is not breaking retro-compatibility) "mail.allow\\,d@james.apache.org") .map(Arguments::of); } + @BeforeEach + void setup() { + Properties props = new Properties(); + props.setProperty("mail.mime.allowutf8", "true"); + Session s = Session.getDefaultInstance(props); + assertThat(Boolean.parseBoolean(s.getProperties().getProperty("mail.mime.allowutf8", "false"))); + } + @ParameterizedTest @MethodSource("goodAddresses") void testGoodMailAddressString(String mailAddress) { assertThatCode(() -> new MailAddress(mailAddress)) + .as("parses " + mailAddress) .doesNotThrowAnyException(); } @@ -123,6 +137,7 @@ void testGoodMailAddressString(String mailAddress) { @MethodSource("goodAddresses") void toInternetAddressShouldNoop(String mailAddress) throws Exception { assertThat(new MailAddress(mailAddress).toInternetAddress()) + .as("tries to parse " + mailAddress + " using jakarta.mail") .isNotEmpty(); } @@ -130,6 +145,7 @@ void toInternetAddressShouldNoop(String mailAddress) throws Exception { @MethodSource("badAddresses") void testBadMailAddressString(String mailAddress) { Assertions.assertThatThrownBy(() -> new MailAddress(mailAddress)) + .as("fails to parse " + mailAddress) .isInstanceOf(AddressException.class); } diff --git a/mailbox/opensearch/src/test/resources/eml/cve-2024-23184.eml b/mailbox/opensearch/src/test/resources/eml/cve-2024-23184.eml new file mode 100644 index 00000000000..30b57dd7430 --- /dev/null +++ b/mailbox/opensearch/src/test/resources/eml/cve-2024-23184.eml @@ -0,0 +1,45 @@ +MIME-Version: 1.0 +Subject: Test +From: Benoit TELLIER +To: Benoit TELLIER +Date: Tue, 13 Feb 2024 23:01:18 +0000 +Message-ID: +Content-Type: multipart/mixed; + boundary="-=Part.17f.732e3d28e1c76db4.18da4b40791.62ef5e3fa995057d=-" + +---=Part.17f.732e3d28e1c76db4.18da4b40791.62ef5e3fa995057d=- +Content-Type: multipart/alternative; + boundary="-=Part.17e.48ac92d73c356567.18da4b40791.360a293e2f389efe=-" + +---=Part.17e.48ac92d73c356567.18da4b40791.360a293e2f389efe=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +Test + +---=Part.17e.48ac92d73c356567.18da4b40791.360a293e2f389efe=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +
Test

+ +---=Part.17e.48ac92d73c356567.18da4b40791.360a293e2f389efe=--- + +---=Part.17f.732e3d28e1c76db4.18da4b40791.62ef5e3fa995057d=- +Content-Type: application/json; name="=?US-ASCII?Q?id=5Frsa.txt?=" +Content-Disposition: attachment +Content-Transfer-Encoding: base64 + +c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFDQVFDa0dXMkp5c2lKR2hQZXdBOXRr +bVFFQm5EVjRaQ0llLy92ZFoyV0RybnZiNlZLQzdpWldjODFpU1ZkTFcxUkRBTll4c3ExN0dQanpV +OFlWdk9sRkFJSk1WTm9ESWhuQWtYOU9VUUJpd1hpOHlHZ3FLNGR0RmIxczJBRzNrQmxNUFFJOE5K +MkpLT2Z5MW51VnJubEtoVDlCVnpYMm5iSjNOak9PZlkxQlJEaDZZcVl1a2RuejBUT2k1Rkp1YUJT +NDZQemx3eWdIa0dzeXBLVHM2Y2FUNjBRdjl3eWFadm4yenN1RmNML3o2Mmd3aGZyZGFsakF1UGRX +cERlNG1IRVFmMXA2SXNRMDdPb0lwTmRHQ0tLZHRZQlVTcktzTXRpMllLUGZpSzB2WGU1L3owRWJE +VlRja1BrY3NwQ2cwYVZuZTB2eFVsRGt2U2pwV2tiQkZ0YTk5ekJjOVlJL0ROK28vRmtONlFTdXV5 +U29tNDZkamZpUjdqSzNMRmJKUkhaem9BblNvaTZvRlR0MW1LWjNzam44bnZWUG1PV3pJWHY0Tm1O +R1ExZHFrV1hXcUtyQjlIZUZiQnRPWVAzaEkxQ0kvaVhNbVR1SkdvcHVTUmlTNW1QZXlSQWV6VGtk +UG8vZ2NSVWNzbklhVW1EallUWHBFNzU3Yk5LWVNHbFJsS3FrbEhKc2JveEdTK0NaVzBJS2dZeTdG +cmZRZ1FGMTdvaUpWM1JJQ1VHcU9rM1I2VnZOYlhlL2VmZS9IT24xd0lZUS9qVGRzY0hCamRIM2FF +MmY4Y3dVS1IzNUtWNlJ1SE4vYVpiekxiVkJxUEMvUTcwd3NMQlloV29Da1dRMElUUmxGV2N3bnN3 +VTE5NnlGWkVHSmthOUNEaHZQdUVBV0NLWnFRT3gyMnRoYWVSQlE9PSBiZW53YUBob3Jpem9uCg== \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5509d511fdc..720f9ff0d4d 100644 --- a/pom.xml +++ b/pom.xml @@ -622,7 +622,7 @@ org.apache.james ${james.groupId}.protocols 6.1.6 - 0.8.12 + 0.8.13-SNAPSHOT 4.0.0 10.14.2.0 2.23.1 diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java index ee711356b03..e0339369312 100644 --- a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java +++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java @@ -19,10 +19,9 @@ package org.apache.james.protocols.api; -import static java.nio.charset.StandardCharsets.US_ASCII; - import java.net.InetSocketAddress; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -216,7 +215,7 @@ public Optional getAttachment(AttachmentKey key, State state) { */ @Override public Charset getCharset() { - return US_ASCII; + return StandardCharsets.UTF_8; } /** diff --git a/server/container/core/src/main/java/org/apache/james/server/core/InternetHeadersInputStream.java b/server/container/core/src/main/java/org/apache/james/server/core/InternetHeadersInputStream.java index 290bc914ebb..7759e89f136 100644 --- a/server/container/core/src/main/java/org/apache/james/server/core/InternetHeadersInputStream.java +++ b/server/container/core/src/main/java/org/apache/james/server/core/InternetHeadersInputStream.java @@ -69,7 +69,7 @@ private boolean readNextLine() { if (!headerLines.hasMoreElements()) { line += LINE_SEPERATOR; } - currLine = line.getBytes(StandardCharsets.US_ASCII); + currLine = line.getBytes(StandardCharsets.UTF_8); return true; } else { return false; diff --git a/server/container/core/src/main/java/org/apache/james/server/core/MailHeaders.java b/server/container/core/src/main/java/org/apache/james/server/core/MailHeaders.java index 71d70e60dcb..be77e707eeb 100644 --- a/server/container/core/src/main/java/org/apache/james/server/core/MailHeaders.java +++ b/server/container/core/src/main/java/org/apache/james/server/core/MailHeaders.java @@ -39,8 +39,8 @@ * */ public class MailHeaders extends InternetHeaders implements Serializable, Cloneable { - private static final long serialVersionUID = 238748126601L; + private static final boolean ALLOWUTF_8 = true; private boolean modified = false; private long size = -1; @@ -67,7 +67,7 @@ public MailHeaders() { */ public MailHeaders(InputStream in) throws MessagingException { super(); - load(in); + load(in, ALLOWUTF_8); } /** diff --git a/server/container/core/src/test/java/org/apache/james/server/core/MimeMessageWrapperTest.java b/server/container/core/src/test/java/org/apache/james/server/core/MimeMessageWrapperTest.java index bc96f14fe90..23ed471c13e 100644 --- a/server/container/core/src/test/java/org/apache/james/server/core/MimeMessageWrapperTest.java +++ b/server/container/core/src/test/java/org/apache/james/server/core/MimeMessageWrapperTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.Properties; @@ -87,6 +88,7 @@ public synchronized void loadMessage() throws MessagingException { TestableMimeMessageWrapper mw = null; TestableMimeMessageWrapper onlyHeader = null; final String content = "Subject: foo\r\nContent-Transfer-Encoding2: plain"; + final String contentUtf8 = "Subject: fée\r\nContent-Transfer-Encoding2: plain"; final String sep = "\r\n\r\n"; final String body = "bar\r\n"; @@ -276,6 +278,35 @@ public void testSize() throws MessagingException { assertThat(mw.getSize()).isEqualTo(body.length()); } + @Test + public void testSizeUtf8() throws Exception { + TestableMimeMessageWrapper message = getMessageFromSources(contentUtf8 + sep + body); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + message.writeTo(baos); + + assertThat(message.getMessageSize()) + .isEqualTo(baos.size()); + } + + @Test + public void testWriteToUtf8() throws Exception { + TestableMimeMessageWrapper message = getMessageFromSources(contentUtf8 + sep + body); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + message.writeTo(baos); + + assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo(contentUtf8 + sep + body); + } + + @Test + public void testWriteToUtf8AfterHeaderModification() throws Exception { + TestableMimeMessageWrapper message = getMessageFromSources(contentUtf8 + sep + body); + message.addHeader("Another", "header"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + message.writeTo(baos); + + assertThat(baos.toString(StandardCharsets.UTF_8)).contains("Subject: fée\r\n"); + } + @Test public void getSizeShouldReturnZeroWhenNoHeaderAndAddHeader() throws MessagingException { onlyHeader.addHeader("a", "b"); diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/AddDeliveredToHeaderTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/AddDeliveredToHeaderTest.java index d5bb6c788aa..20f7db4c792 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/AddDeliveredToHeaderTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/AddDeliveredToHeaderTest.java @@ -29,10 +29,21 @@ import java.io.File; +import org.apache.james.jmap.mailet.filter.JMAPFiltering; +import org.apache.james.mailets.configuration.MailetConfiguration; +import org.apache.james.mailets.configuration.ProcessorConfiguration; import org.apache.james.modules.protocols.ImapGuiceProbe; import org.apache.james.modules.protocols.SmtpGuiceProbe; import org.apache.james.probe.DataProbe; import org.apache.james.transport.mailets.AddDeliveredToHeader; +import org.apache.james.transport.mailets.LocalDelivery; +import org.apache.james.transport.mailets.RecipientRewriteTable; +import org.apache.james.transport.mailets.RemoteDelivery; +import org.apache.james.transport.mailets.ToProcessor; +import org.apache.james.transport.mailets.VacationMailet; +import org.apache.james.transport.matchers.All; +import org.apache.james.transport.matchers.RecipientIsLocal; +import org.apache.james.transport.matchers.SMTPAuthSuccessful; import org.apache.james.utils.DataProbeImpl; import org.apache.james.utils.SMTPMessageSender; import org.apache.james.utils.TestIMAPClient; @@ -43,6 +54,9 @@ import org.junit.jupiter.api.io.TempDir; class AddDeliveredToHeaderTest { + private static final String POSTMASTER = "postmaster@" + DEFAULT_DOMAIN; + public static final String RECIPIENT2_UTF8 = "rené@" + DEFAULT_DOMAIN; + public static final String RECIPIENT2 = "rene@" + DEFAULT_DOMAIN; @RegisterExtension public TestIMAPClient testIMAPClient = new TestIMAPClient(); @RegisterExtension @@ -52,12 +66,17 @@ class AddDeliveredToHeaderTest { @BeforeEach void setup(@TempDir File temporaryFolder) throws Exception { - jamesServer = TemporaryJamesServer.builder().build(temporaryFolder); + jamesServer = TemporaryJamesServer.builder() + .withMailetContainer(TemporaryJamesServer.defaultMailetContainerConfiguration() + .postmaster(POSTMASTER) + .putProcessor(transport())) + .build(temporaryFolder); jamesServer.start(); DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class); dataProbe.addDomain(DEFAULT_DOMAIN); dataProbe.addUser(RECIPIENT, PASSWORD); + dataProbe.addUser(RECIPIENT2, PASSWORD); dataProbe.addUser(FROM, PASSWORD); } @@ -79,4 +98,62 @@ void receivedMessagesShouldContainDeliveredToHeaders() throws Exception { assertThat(testIMAPClient.readFirstMessageHeaders()) .contains(AddDeliveredToHeader.DELIVERED_TO + ": " + RECIPIENT); } + + @Test + void receivedMessagesShouldContainDeliveredToHeadersI8N() throws Exception { + jamesServer.getProbe(DataProbeImpl.class).addUserAliasMapping("rené", "james.org", RECIPIENT2); + String message = "FROM: " + RECIPIENT2_UTF8 + "\r\n" + + "subject: testé\r\n" + + "Content-Type: text/plain; charset=UTF-8\r\n" + + "Content-Encoding: 8bit\r\n" + + "\r\n" + + "contenté\r\n"; + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(FROM, PASSWORD) + .sendMessageWithHeaders(FROM, RECIPIENT2_UTF8, message); + + Thread.sleep(1000); + + testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort()) + .login(RECIPIENT2, PASSWORD) + .select(TestIMAPClient.INBOX) + .awaitMessage(awaitAtMostOneMinute); + + assertThat(testIMAPClient.readFirstMessage()) + .contains(RECIPIENT2_UTF8) + .contains("testé") + .contains("contenté"); + } + + private ProcessorConfiguration.Builder transport() { + return ProcessorConfiguration.transport() + .enableJmx(false) + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(RecipientRewriteTable.class)) + .addMailet(MailetConfiguration.builder() + .matcher(RecipientIsLocal.class) + .mailet(VacationMailet.class)) + .addMailet(MailetConfiguration.builder() + .matcher(RecipientIsLocal.class) + .mailet(JMAPFiltering.class)) + .addMailet(MailetConfiguration.builder() + .matcher(RecipientIsLocal.class) + .mailet(LocalDelivery.class)) + .addMailet(MailetConfiguration.builder() + .matcher(SMTPAuthSuccessful.class) + .mailet(RemoteDelivery.class) + .addProperty("outgoingQueue", "outgoing") + .addProperty("delayTime", "5000, 100000, 500000") + .addProperty("maxRetries", "3") + .addProperty("maxDnsProblemRetries", "0") + .addProperty("deliveryThreads", "10") + .addProperty("sendpartial", "true") + .addProperty("bounceProcessor", "bounces")) + .addMailet(MailetConfiguration.BCC_STRIPPER) + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(ToProcessor.class) + .addProperty("processor", "error")); + } }