This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch 3.9.x in repository https://gitbox.apache.org/repos/asf/james-project.git
commit a03e06e26e8b423a8107ad660f591141230c6429 Author: Quan Tran <[email protected]> AuthorDate: Tue Sep 30 11:12:17 2025 +0700 JAMES-4148 JMAPFiltering mailet should support moveTo action --- .../mailets/FilterForwardIntegrationTest.java | 197 ------------- .../james/mailets/FilterIntegrationTest.java | 311 +++++++++++++++++++++ .../james/jmap/mailet/filter/ActionApplier.java | 28 +- 3 files changed, 336 insertions(+), 200 deletions(-) diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java deleted file mode 100644 index d5bde11b11..0000000000 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/**************************************************************** - * 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.mailets; - -import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS; -import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.FROM; -import static org.apache.james.mailets.configuration.CommonProcessors.RRT_ERROR_REPOSITORY; -import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN; -import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP; -import static org.apache.james.mailets.configuration.Constants.PASSWORD; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; - -import org.apache.james.core.Username; -import org.apache.james.jmap.api.filtering.Rule; -import org.apache.james.jmap.api.filtering.Rule.Action; -import org.apache.james.jmap.api.filtering.Rule.Action.Forward; -import org.apache.james.jmap.mailet.filter.JMAPFiltering; -import org.apache.james.mailets.configuration.CommonProcessors; -import org.apache.james.mailets.configuration.MailetConfiguration; -import org.apache.james.mailets.configuration.MailetContainer; -import org.apache.james.mailets.configuration.ProcessorConfiguration; -import org.apache.james.mailrepository.api.MailRepositoryUrl; -import org.apache.james.modules.protocols.SmtpGuiceProbe; -import org.apache.james.probe.DataProbe; -import org.apache.james.transport.mailets.ToRepository; -import org.apache.james.transport.matchers.All; -import org.apache.james.utils.DataProbeImpl; -import org.apache.james.utils.FilteringManagementProbeImpl; -import org.apache.james.utils.GuiceProbe; -import org.apache.james.utils.MailRepositoryProbeImpl; -import org.apache.james.utils.SMTPMessageSender; -import org.apache.mailet.Mail; -import org.assertj.core.api.SoftAssertions; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.io.TempDir; - -import com.github.fge.lambdas.Throwing; -import com.google.inject.multibindings.Multibinder; - -public class FilterForwardIntegrationTest { - - private static final Username ALICE = Username.of("alice@" + DEFAULT_DOMAIN); - public static final Rule.ConditionGroup CONDITION_GROUP = Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, Rule.Condition.of(FROM, CONTAINS, ALICE.asString())); - private static final Username BOB = Username.of("bob@" + DEFAULT_DOMAIN); - private static final Username CEDRIC = Username.of("cedric@" + DEFAULT_DOMAIN); - private static final MailRepositoryUrl CUSTOM_REPOSITORY = MailRepositoryUrl.from("memory://var/mail/custom/"); - - private static Rule.Builder asRule(Action.Forward forward) { - return Rule.builder() - .id(Rule.Id.of("1")) - .name("rule 1") - .conditionGroup(CONDITION_GROUP) - .action(Action.builder().setForward(forward)); - } - - private TemporaryJamesServer jamesServer; - private FilteringManagementProbeImpl filteringManagementProbe; - private MailRepositoryProbeImpl mailRepositoryProbe; - - @RegisterExtension - public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN); - - @BeforeEach - void setup(@TempDir File temporaryFolder) throws Exception { - jamesServer = TemporaryJamesServer.builder() - .withOverrides(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(FilteringManagementProbeImpl.class)) - .withMailetContainer(MailetContainer.builder() - .putProcessor(ProcessorConfiguration.root() - .addMailet(MailetConfiguration.builder() - .matcher(All.class) - .mailet(JMAPFiltering.class)) - .addMailet(MailetConfiguration.builder() - .matcher(All.class) - .mailet(ToRepository.class) - .addProperty("repositoryPath", CUSTOM_REPOSITORY.asString()))) - .putProcessor(CommonProcessors.error()) - .putProcessor(CommonProcessors.rrtError()) - .putProcessor(CommonProcessors.transport()) - .putProcessor(CommonProcessors.bounces())) - .build(temporaryFolder); - - jamesServer.start(); - - DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class); - dataProbe.addDomain(DEFAULT_DOMAIN); - - dataProbe.addUser(ALICE.asString(), PASSWORD); - dataProbe.addUser(BOB.asString(), PASSWORD); - dataProbe.addUser(CEDRIC.asString(), PASSWORD); - - mailRepositoryProbe = jamesServer.getProbe(MailRepositoryProbeImpl.class); - - filteringManagementProbe = jamesServer.getProbe(FilteringManagementProbeImpl.class); - } - - @AfterEach - void tearDown() { - jamesServer.shutdown(); - } - - @Test - void forwardShouldWork() throws Exception { - filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).keepACopy())); - - messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) - .authenticate(ALICE.asString(), PASSWORD) - .sendMessage(ALICE.asString(), BOB.asString()); - - Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 2L); - - SoftAssertions.assertSoftly(Throwing.consumer(softly -> { - Mail mail1 = mailRepositoryProbe.listMails(CUSTOM_REPOSITORY, BOB.asMailAddress()).get(0); - softly.assertThat(mail1.getRecipients()).containsOnly(BOB.asMailAddress()); - softly.assertThat(mail1.getMaybeSender().asOptional()).contains(ALICE.asMailAddress()); - - Mail mail2 = mailRepositoryProbe.listMails(CUSTOM_REPOSITORY, CEDRIC.asMailAddress()).get(0); - softly.assertThat(mail2.getRecipients()).containsOnly(CEDRIC.asMailAddress()); - softly.assertThat(mail2.getMaybeSender().asOptional()).contains(BOB.asMailAddress()); - })); - } - - @Test - void forwardShouldNotKeepACopyWhenKeepACopyIsFalse() throws Exception { - filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); - - messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) - .authenticate(ALICE.asString(), PASSWORD) - .sendMessage(ALICE.asString(), BOB.asString()); - - Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); - - SoftAssertions.assertSoftly(Throwing.consumer(softly -> softly.assertThat(mailRepositoryProbe.listMails(CUSTOM_REPOSITORY) - .anyMatch(Throwing.predicate(mail -> mail.getRecipients().contains(BOB.asMailAddress())))).isFalse())); - } - - @Test - void regularForwardShouldNotLeadToRecordingRRTError() throws Exception { - filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); - - messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) - .authenticate(ALICE.asString(), PASSWORD) - .sendMessage(ALICE.asString(), BOB.asString()); - - Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); - - assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); - } - - @Test - void localCopyShouldNotLeadToRecordingRRTError() throws Exception { - filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).keepACopy())); - - messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) - .authenticate(ALICE.asString(), PASSWORD) - .sendMessage(ALICE.asString(), BOB.asString()); - - Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 2L); - - assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); - } - - @Test - void localCopyAloneShouldNotLeadToRecordingRRTError() throws Exception { - filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to().keepACopy())); - - messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) - .authenticate(ALICE.asString(), PASSWORD) - .sendMessage(ALICE.asString(), BOB.asString()); - - Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); - - assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); - } -} diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java new file mode 100644 index 0000000000..6901446449 --- /dev/null +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterIntegrationTest.java @@ -0,0 +1,311 @@ +/**************************************************************** + * 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.mailets; + +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.FROM; +import static org.apache.james.mailets.configuration.CommonProcessors.RRT_ERROR_REPOSITORY; +import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN; +import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP; +import static org.apache.james.mailets.configuration.Constants.PASSWORD; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Optional; + +import org.apache.james.core.Username; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.jmap.api.filtering.Rule.Action; +import org.apache.james.jmap.api.filtering.Rule.Action.Forward; +import org.apache.james.jmap.mailet.filter.JMAPFiltering; +import org.apache.james.mailbox.DefaultMailboxes; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailets.configuration.CommonProcessors; +import org.apache.james.mailets.configuration.Constants; +import org.apache.james.mailets.configuration.MailetConfiguration; +import org.apache.james.mailets.configuration.MailetContainer; +import org.apache.james.mailets.configuration.ProcessorConfiguration; +import org.apache.james.mailrepository.api.MailRepositoryUrl; +import org.apache.james.modules.MailboxProbeImpl; +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.ToProcessor; +import org.apache.james.transport.mailets.ToRepository; +import org.apache.james.transport.matchers.All; +import org.apache.james.utils.DataProbeImpl; +import org.apache.james.utils.FilteringManagementProbeImpl; +import org.apache.james.utils.GuiceProbe; +import org.apache.james.utils.MailRepositoryProbeImpl; +import org.apache.james.utils.SMTPMessageSender; +import org.apache.james.utils.TestIMAPClient; +import org.apache.mailet.Mail; +import org.assertj.core.api.SoftAssertions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; + +import com.github.fge.lambdas.Throwing; +import com.google.inject.multibindings.Multibinder; + +public class FilterIntegrationTest { + + private static final Username ALICE = Username.of("alice@" + DEFAULT_DOMAIN); + public static final Rule.ConditionGroup CONDITION_GROUP = Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, Rule.Condition.of(FROM, CONTAINS, ALICE.asString())); + private static final Username BOB = Username.of("bob@" + DEFAULT_DOMAIN); + private static final Username CEDRIC = Username.of("cedric@" + DEFAULT_DOMAIN); + private static final MailRepositoryUrl CUSTOM_REPOSITORY = MailRepositoryUrl.from("memory://var/mail/custom/"); + + private static Rule.Builder asRule(Action.Builder actionBuilder) { + return Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .conditionGroup(CONDITION_GROUP) + .action(actionBuilder); + } + + private TemporaryJamesServer jamesServer; + private FilteringManagementProbeImpl filteringManagementProbe; + private MailRepositoryProbeImpl mailRepositoryProbe; + + @RegisterExtension + public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN); + + @RegisterExtension + public TestIMAPClient imapClient = new TestIMAPClient(); + + @Nested + class ForwardTests { + @BeforeEach + void setup(@TempDir File temporaryFolder) throws Exception { + jamesServer = TemporaryJamesServer.builder() + .withOverrides(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(FilteringManagementProbeImpl.class)) + .withMailetContainer(MailetContainer.builder() + .putProcessor(ProcessorConfiguration.root() + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(JMAPFiltering.class)) + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(ToRepository.class) + .addProperty("repositoryPath", CUSTOM_REPOSITORY.asString()))) + .putProcessor(CommonProcessors.error()) + .putProcessor(CommonProcessors.rrtError()) + .putProcessor(CommonProcessors.transport()) + .putProcessor(CommonProcessors.bounces())) + .build(temporaryFolder); + + jamesServer.start(); + + DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class); + dataProbe.addDomain(DEFAULT_DOMAIN); + + dataProbe.addUser(ALICE.asString(), PASSWORD); + dataProbe.addUser(BOB.asString(), PASSWORD); + dataProbe.addUser(CEDRIC.asString(), PASSWORD); + + mailRepositoryProbe = jamesServer.getProbe(MailRepositoryProbeImpl.class); + + filteringManagementProbe = jamesServer.getProbe(FilteringManagementProbeImpl.class); + } + + @AfterEach + void tearDown() { + jamesServer.shutdown(); + } + + @Test + void forwardShouldWork() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder().setForward(Forward.to(CEDRIC.asMailAddress()).keepACopy()))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 2L); + + SoftAssertions.assertSoftly(Throwing.consumer(softly -> { + Mail mail1 = mailRepositoryProbe.listMails(CUSTOM_REPOSITORY, BOB.asMailAddress()).get(0); + softly.assertThat(mail1.getRecipients()).containsOnly(BOB.asMailAddress()); + softly.assertThat(mail1.getMaybeSender().asOptional()).contains(ALICE.asMailAddress()); + + Mail mail2 = mailRepositoryProbe.listMails(CUSTOM_REPOSITORY, CEDRIC.asMailAddress()).get(0); + softly.assertThat(mail2.getRecipients()).containsOnly(CEDRIC.asMailAddress()); + softly.assertThat(mail2.getMaybeSender().asOptional()).contains(BOB.asMailAddress()); + })); + } + + @Test + void forwardShouldNotKeepACopyWhenKeepACopyIsFalse() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder().setForward(Forward.to(CEDRIC.asMailAddress()).withoutACopy()))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + SoftAssertions.assertSoftly(Throwing.consumer(softly -> softly.assertThat(mailRepositoryProbe.listMails(CUSTOM_REPOSITORY) + .anyMatch(Throwing.predicate(mail -> mail.getRecipients().contains(BOB.asMailAddress())))).isFalse())); + } + + @Test + void regularForwardShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder().setForward(Forward.to(CEDRIC.asMailAddress()).withoutACopy()))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void localCopyShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder().setForward(Forward.to(CEDRIC.asMailAddress()).keepACopy()))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 2L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void localCopyAloneShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder().setForward(Forward.to().keepACopy()))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + } + + @Nested + class MoveToTests { + @BeforeEach + void setup(@TempDir File temporaryFolder) throws Exception { + jamesServer = TemporaryJamesServer.builder() + .withOverrides(binder -> Multibinder.newSetBinder(binder, GuiceProbe.class).addBinding().to(FilteringManagementProbeImpl.class)) + .withMailetContainer(MailetContainer.builder() + .putProcessor(ProcessorConfiguration.root() + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(JMAPFiltering.class)) + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(ToProcessor.class) + .addProperty("processor", "transport"))) + .putProcessor(CommonProcessors.error()) + .putProcessor(CommonProcessors.rrtError()) + .putProcessor(CommonProcessors.transport()) + .putProcessor(CommonProcessors.bounces())) + .build(temporaryFolder); + + jamesServer.start(); + + DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class); + dataProbe.addDomain(DEFAULT_DOMAIN); + + dataProbe.addUser(ALICE.asString(), PASSWORD); + dataProbe.addUser(BOB.asString(), PASSWORD); + + mailRepositoryProbe = jamesServer.getProbe(MailRepositoryProbeImpl.class); + filteringManagementProbe = jamesServer.getProbe(FilteringManagementProbeImpl.class); + } + + @AfterEach + void tearDown() { + jamesServer.shutdown(); + } + + @Test + void moveToShouldWork() throws Exception { + MailboxProbeImpl mailboxProbe = jamesServer.getProbe(MailboxProbeImpl.class); + mailboxProbe.createMailbox("#private", BOB.asString(), DefaultMailboxes.TRASH); + + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder() + .setMoveTo(Optional.of(new Action.MoveTo(DefaultMailboxes.TRASH))))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + imapClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort()) + .login(BOB, PASSWORD) + .select(DefaultMailboxes.TRASH) + .awaitMessageCount(Constants.awaitAtMostOneMinute, 1); + } + + @Test + void moveToWithAppendInShouldWork() throws Exception { + MailboxProbeImpl mailboxProbe = jamesServer.getProbe(MailboxProbeImpl.class); + MailboxId archiveMailboxId = mailboxProbe.createMailbox("#private", BOB.asString(), DefaultMailboxes.ARCHIVE); + mailboxProbe.createMailbox("#private", BOB.asString(), DefaultMailboxes.TRASH); + + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder() + .setAppendInMailboxes(Action.AppendInMailboxes.withMailboxIds(archiveMailboxId.serialize())) + .setMoveTo(Optional.of(new Action.MoveTo(DefaultMailboxes.TRASH))))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + imapClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort()) + .login(BOB, PASSWORD) + .select(DefaultMailboxes.TRASH) + .awaitMessageCount(Constants.awaitAtMostOneMinute, 1) + .select(DefaultMailboxes.ARCHIVE) + .awaitMessageCount(Constants.awaitAtMostOneMinute, 1); + } + + @Test + void moveToWithAppendInTheSameMailboxShouldNotDuplicateMails() throws Exception { + MailboxProbeImpl mailboxProbe = jamesServer.getProbe(MailboxProbeImpl.class); + MailboxId archiveMailboxId = mailboxProbe.createMailbox("#private", BOB.asString(), DefaultMailboxes.ARCHIVE); + + filteringManagementProbe.defineRulesForUser(BOB, asRule(Action.builder() + .setAppendInMailboxes(Action.AppendInMailboxes.withMailboxIds(archiveMailboxId.serialize())) + .setMoveTo(Optional.of(new Action.MoveTo(DefaultMailboxes.ARCHIVE))))); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + imapClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort()) + .login(BOB, PASSWORD) + .select(DefaultMailboxes.ARCHIVE) + .awaitMessageCount(Constants.awaitAtMostOneMinute, 1); + } + } + +} diff --git a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java index b5adf822c4..cca4fa0b00 100644 --- a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java +++ b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java @@ -39,6 +39,7 @@ import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.server.core.MailImpl; import org.apache.james.util.AuditTrail; import org.apache.mailet.LoopPrevention; @@ -198,9 +199,14 @@ public class ActionApplier { } private Optional<ImmutableList<String>> computeTargetMailboxes(Rule.Action action) { - return Optional.of(action.getAppendInMailboxes().getMailboxIds() - .stream() - .flatMap(this::asMailboxName) + Stream<String> appendInMailboxes = action.getAppendInMailboxes().getMailboxIds() + .stream() + .flatMap(this::asMailboxName); + Stream<String> moveToMailboxes = action.getMoveTo() + .map(moveTo -> validateMailboxName(moveTo.getMailboxName())) + .orElse(Stream.empty()); + + return Optional.of(Stream.concat(appendInMailboxes, moveToMailboxes) .collect(ImmutableList.toImmutableList())) .filter(mailboxes -> !mailboxes.isEmpty()); } @@ -222,6 +228,22 @@ public class ActionApplier { } } + private Stream<String> validateMailboxName(String mailboxName) { + try { + MailboxSession mailboxSession = mailboxManager.createSystemSession(username); + MessageManager messageManager = mailboxManager.getMailbox(MailboxPath.forUser(username, mailboxName), mailboxSession); + mailboxManager.endProcessingRequest(mailboxSession); + + return Stream.of(messageManager.getMailboxPath().getName()); + } catch (MailboxNotFoundException e) { + LOGGER.info("Mailbox {} does not exist for user {}, but it was mentioned in a JMAP filtering rule", mailboxName, username.asString(), e); + return Stream.empty(); + } catch (Exception e) { + LOGGER.error("Unexpected failure while validating mailbox name {} for user {}", mailboxName, username.asString(), e); + return Stream.empty(); + } + } + private void sendACopy(LoopPrevention.RecordedRecipients recordedRecipients, Set<MailAddress> newRecipients) throws MessagingException { MailImpl copy = MailImpl.duplicate(mail); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
