You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by "thanhbv200585 (via GitHub)" <gi...@apache.org> on 2023/03/23 04:15:38 UTC

[GitHub] [james-project] thanhbv200585 opened a new pull request, #1501: Temp

thanhbv200585 opened a new pull request, #1501:
URL: https://github.com/apache/james-project/pull/1501

   The first pull request 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1145750952


##########
.gitignore:
##########
@@ -6,4 +6,19 @@ dockerfiles/run/**/keystore
 dockerfiles/run/**/glowroot
 .m2
 test-run.log
-build
\ No newline at end of file
+build
+*.iml
+.idea
+*.lib
+*.jar
+.floo
+.flooignore
+.m2
+_site/
+keystore
+derby.log
+src/homepage/Gemfile.lock
+mailbox/var/
+src/homepage/.sass-cache/
+KahaDB/
+.attach_pid*

Review Comment:
   I asked him to set this as global git ignore instead, so I think this change can be removed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa merged pull request #1501: JAMES-3822 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa merged PR #1501:
URL: https://github.com/apache/james-project/pull/1501


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1514285421

   Or get the checkstyle plugin in the IDE and run it on the project? :)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1147062842


##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {

Review Comment:
   Ok I will change it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1145774707


##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{
+        public static Optional<Holdfor> fromSMTPArgLine(Map<String, Integer> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(Holdfor::of);
+        }
+
+        public static Holdfor fromAttributeValue(AttributeValue<Integer> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Integer value;
+
+        public static Holdfor of (Integer value) {
+            Preconditions.checkNotNull(value);
+//            Preconditions.checkArgument(XText.isValid(value), "According to RFC-4865 Holdfor should be a valid xtext" +
+//                ", thus composed of CHARs between \"!\" (33) and \"~\" (126) inclusive, except for \"+\" and \"=\" or follow the hexadecimal escape sequence.");

Review Comment:
   Not working at the moment?



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,21 @@
+package org.apache.james.smtpserver.futurerelease;

Review Comment:
   Apache License missing !



##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -53,7 +53,8 @@ public class DsnParameters {
     public static final String ORCPT_PARAMETER = "ORCPT";
     public static final String ENVID_PARAMETER = "ENVID";
     public static final String RET_PARAMETER = "RET";
-
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";

Review Comment:
   I actually see the same constants declared in `FUTURERELEASEParameters` and I think it's enough (no need to have them duplicated)
   
   Just remove those from `DsnParameters` class :)



##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{

Review Comment:
   @thanhbv200585 don't hesitate to ask the team about checkstyle, I'm not certain you are familiar with it!



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.mailet.DsnParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.DsnParameters.HOLDUNITL_PARAMETER;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.DsnParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger logger = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", DsnParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", DsnParameters.Holduntil.class);
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        if(paramName.equals(HOLDFOR_PARAMETER)){
+            logger.debug("HoldFor parameter is set to {}", paramValue);
+            Integer value = Integer.parseInt(paramValue);
+            DsnParameters.Holdfor holdfor = DsnParameters.Holdfor.of(value);
+            session.setAttachment(FUTURERELEASE_HOLDFOR, holdfor, Transaction);
+        }else if(paramName.equals(HOLDUNITL_PARAMETER)){

Review Comment:
   Careful with the syntaxt of your if {} else {} structures.
   
   As you saw quickly in some comments we have some checkstyle plugin in place to enforce some coding rules to guarantee a minimum of readability and quality in our code (and when those rules are not respected, it breaks).
   
   Give more space in your structures. For example instead of:
   ```
   if(CONDITION){
       // if body
   }else(CONDITION){
       // else body
   }
   ```
   prefer giving more space for a better readability : 
   ```
   if (CONDITION) {
       // if body
   } else (CONDITION) {
       // else body
   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153994520


##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/DSNTest.java:
##########
@@ -177,7 +179,7 @@ protected void setUpFakeLoader() {
     void tearDown() {
         smtpServer.destroy();
     }
-
+ // check whether ehlo contains DSN Extensions or not

Review Comment:
   Many unrelated changes in this file



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1168147787


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+            Instant now = LocalDateTime.now(clock).toInstant(ZoneOffset.UTC);
+            return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant());
+        }
+        throw new IllegalArgumentException("Invalid parameter name " + paramName);
+    }
+
+    private DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }

Review Comment:
   I guess Benoit means to use `ImapDateTimeFormatter.rfc5322()`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1166290471


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+            Instant now = LocalDateTime.now(clock).toInstant(ZoneOffset.UTC);
+            return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant());
+        }
+        throw new IllegalArgumentException("Invalid parameter name " + paramName);
+    }
+
+    private DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }

Review Comment:
   Use `optionalStart` to come up with a dedicated `DateTimeFormatter`.
   
   Or even better: https://www.rfc-editor.org/errata/rfc4865
   
   We do not need to support time zone at all. Let's remove `ISO_OFFSET_DATE_TIME` altogether...



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1147063224


##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -53,7 +53,8 @@ public class DsnParameters {
     public static final String ORCPT_PARAMETER = "ORCPT";
     public static final String ENVID_PARAMETER = "ENVID";
     public static final String RET_PARAMETER = "RET";
-
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";

Review Comment:
   Oh I see, so I created a FututeReleaseParameters class and move HOLDFOR_PARAMETER and HOLDUNTIL_PARAMETER to this class in my local repo, you will see it in next commit.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1145738093


##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {

Review Comment:
   CAN WE DISABLE CAPS LOCK AndUseSnakeCase as it is more readable?



##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -53,7 +53,8 @@ public class DsnParameters {
     public static final String ORCPT_PARAMETER = "ORCPT";
     public static final String ENVID_PARAMETER = "ENVID";
     public static final String RET_PARAMETER = "RET";
-
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";

Review Comment:
   Future release parameters are not DSN parameters. Please move them to a dedicated file.



##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -239,6 +240,8 @@ public String toString() {
         }
     }
 
+
+

Review Comment:
   Please remove these unrelated changes.



##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{

Review Comment:
   Please use equalsverifier to test the behaviour of this POJO...



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,21 @@
+package org.apache.james.smtpserver.futurerelease;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        return ImmutableSet.of("FUTURERELEASE");
+    }
+
+    @Override
+    public HookResult doHelo(SMTPSession session, String helo) {return HookResult.DECLINED;}

Review Comment:
   ```suggestion
       public HookResult doHelo(SMTPSession session, String helo) {
           return HookResult.DECLINED;
       }
   ```



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.mailet.DsnParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.DsnParameters.HOLDUNITL_PARAMETER;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.DsnParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger logger = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", DsnParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", DsnParameters.Holduntil.class);
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        if(paramName.equals(HOLDFOR_PARAMETER)){
+            logger.debug("HoldFor parameter is set to {}", paramValue);
+            Integer value = Integer.parseInt(paramValue);
+            DsnParameters.Holdfor holdfor = DsnParameters.Holdfor.of(value);
+            session.setAttachment(FUTURERELEASE_HOLDFOR, holdfor, Transaction);
+        }else if(paramName.equals(HOLDUNITL_PARAMETER)){
+            logger.debug("HoldUntil parameter is set to {}", paramValue);
+            DsnParameters.Holduntil holduntil = DsnParameters.Holduntil.of(paramValue);
+            session.setAttachment(FUTURERELEASE_HOLDUNTIL, holduntil, Transaction);
+        }
+        return null;

Review Comment:
   IMO we should convert Hold-Until into Hold-For in order to get a signle parameter



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,17 @@
+package org.apache.james.smtpserver;

Review Comment:
   Missing license



##########
.gitignore:
##########
@@ -6,4 +6,19 @@ dockerfiles/run/**/keystore
 dockerfiles/run/**/glowroot
 .m2
 test-run.log
-build
\ No newline at end of file
+build
+*.iml
+.idea
+*.lib
+*.jar
+.floo
+.flooignore
+.m2
+_site/
+keystore
+derby.log
+src/homepage/Gemfile.lock
+mailbox/var/
+src/homepage/.sass-cache/
+KahaDB/
+.attach_pid*

Review Comment:
   Please use git rebase to move changes of `.gitignore` in a distinct commit.



##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{

Review Comment:
   ```suggestion
       public static class Holdfor {
   ```
   
   checkstyle



##########
mailet/api/src/main/java/org/apache/mailet/Mail.java:
##########
@@ -451,4 +451,6 @@ default void setDsnParameters(DsnParameters dsnParameters) {
             .asAttributes()
             .forEach(this::setAttribute);
     }
+
+

Review Comment:
   Unrelated change



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.mailet.DsnParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.DsnParameters.HOLDUNITL_PARAMETER;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.DsnParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger logger = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", DsnParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<DsnParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", DsnParameters.Holduntil.class);
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {

Review Comment:
   ```suggestion
       public static final ProtocolSession.AttachmentKey<DsnParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", DsnParameters.Holduntil.class);
       
       @Override
       public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
   ```



##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{
+        public static Optional<Holdfor> fromSMTPArgLine(Map<String, Integer> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(Holdfor::of);
+        }
+
+        public static Holdfor fromAttributeValue(AttributeValue<Integer> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Integer value;
+
+        public static Holdfor of (Integer value) {
+            Preconditions.checkNotNull(value);
+//            Preconditions.checkArgument(XText.isValid(value), "According to RFC-4865 Holdfor should be a valid xtext" +
+//                ", thus composed of CHARs between \"!\" (33) and \"~\" (126) inclusive, except for \"+\" and \"=\" or follow the hexadecimal escape sequence.");
+
+            return new Holdfor(value);
+        }
+
+        private Holdfor(Integer value) {
+            this.value = value;
+        }
+
+        public Integer asString() {
+            return value;
+        }
+
+        public AttributeValue<Integer> toAttributeValue() {
+            return AttributeValue.of(value);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Holdfor) {
+                Holdfor that = (Holdfor) o;
+
+                return Objects.equals(this.value, that.value);
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("value", value)
+                .toString();
+        }
+    }
+
+    public static class Holduntil{

Review Comment:
   ```suggestion
       public static class Holduntil {
   ```
   
   + test it with equals verifier



##########
server/apps/distributed-app/docs/modules/ROOT/pages/extending/smtp-hooks.adoc:
##########
@@ -1,7 +1,7 @@
 = Distributed James Server &mdash; Custom SMTP hooks
 :navtitle: Custom SMTP hooks
 
-SMTP hooks enable extening capabilities of the SMTP server and are run synchronously upon email reception, before the email is
+SMTP hooks enable extending capabilities of the SMTP server and are run synchronously upon email reception, before the email is

Review Comment:
   Please split this typo fixing to its own commit



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/DSNTest.java:
##########
@@ -228,7 +228,8 @@ void dsnParametersShouldBeSetOnTheFinalEmail() throws Exception {
                 .addRcptParameter(new MailAddress("rcpt@localhost"), DsnParameters.RecipientDsnParameters.of(
                     EnumSet.of(SUCCESS, FAILURE, DELAY),
                     new MailAddress("orcpt@localhost")
-                )).build().get());
+                )).build().
+                get());
     }

Review Comment:
   Unrelated



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153993534


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.mailet.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.HOLDUNITL_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.holdforValue;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.FutureReleaseParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", FutureReleaseParameters.Holduntil.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long interval = new Long(0);
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            LOGGER.debug("HoldFor parameter is set to {}", paramValue);
+            interval = Long.parseLong(paramValue);

Review Comment:
   Please do not realocate variables.
   
   A better way to proceed is to extract a submethod.
   
   Have a read at this book: `clean code` https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1154319946


##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;

Review Comment:
   So let's wait for other opinions. Maybe DsnParameters is in the wrong place at the beginning, or there is a rationale behind that as well. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1161596314


##########
protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/MailCmdHandler.java:
##########
@@ -138,7 +138,7 @@ protected Response doFilterChecks(SMTPSession session, String command,
      */
     private Response doMAILFilter(SMTPSession session, String argument) {
         String sender = null;
-
+        if (invalidFutureReleaseParameter(argument)) return null;

Review Comment:
   This do not match the checkstyle



##########
protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/MailCmdHandler.java:
##########
@@ -212,6 +212,13 @@ private Response doMAILFilter(SMTPSession session, String argument) {
         return null;
     }
 
+    private static boolean invalidFutureReleaseParameter(String argument) {
+        if (argument.contains("HOLDFOR") && argument.contains("HOLDUNTIL")) {
+            return true;
+        }
+        return false;
+    }

Review Comment:
   Move this to FutureReleaseMailParameterHook
   
   Rationnals:
    - We have some specific code to future release leaking in a SMTP code code piece. This causes maintainability problems.
    - Separation from the extension makes the behaviour of the extension harder to understand



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static String maxFutureReleaseInterval;
+    private static LocalDateTime maxFutureReleaseDateTime;
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        maxFutureReleaseDateTime = LocalDateTime.now();

Review Comment:
   Variable reallovation spotted!



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static String maxFutureReleaseInterval;

Review Comment:
   unused?



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    public static final long FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM = 0L;
+
+    public static final DateTimeFormatter utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+        if (requestedHoldFor <= MAX_HOLD_FOR_SUPPORTED) {
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+        }
+        return HookResult.DECLINED;

Review Comment:
   When something is wrong, DENY (with the right error message and a log)



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMessageHook.java:
##########
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDFOR;
+
+import java.util.Optional;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.smtpserver.JamesMessageHook;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMessageHook implements JamesMessageHook {
+
+    public static final AttributeName MAIL_ATTRIBUTE_HOLD_FOR = AttributeName.of("futurerelease-hold-for");
+    public static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMessageHook.class);
+    @Override
+    public HookResult onMessage(SMTPSession session, Mail mail) {
+        Optional<FutureReleaseParameters.HoldFor> holdForOptional = session.getAttachment(FUTURERELEASE_HOLDFOR, ProtocolSession.State.Transaction);
+
+        Long holdFor = holdForOptional.map(FutureReleaseParameters.HoldFor::value)
+            .orElse(0L);
+        if (holdFor != 0L) {
+            mail.setAttribute(new Attribute(MAIL_ATTRIBUTE_HOLD_FOR, AttributeValue.of(holdFor)));
+            return HookResult.DECLINED;
+        } else {
+            LOGGER.debug("Mail is rejected because holdForValue is greater than holdForValue is supported.");
+            return HookResult.DENY;
+        }
+

Review Comment:
   So many things wrong here:
   
    - 1. We accept a value at MAIL FROM command and reject it several smtp command later. 
    - 2. We still need to send regular emails without future release, right? This code do not distinguish email without any future relase params from those with invalid one.



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+

Review Comment:
   ```suggestion
   
   ```



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        String[] listReplyString = smtpProtocol.getReplyString().split(" ");
+        int length = listReplyString.length;
+        String maxInterval = listReplyString[length-2];
+        String maxDateTime = listReplyString[length-1];
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(getMaxFurtureReleaseInterval(maxInterval)).isGreaterThan(0);
+            softly.assertThat(validDateTime(maxDateTime));
+        });
+    }
+
+    private int getMaxFurtureReleaseInterval(String interval) {
+        try {
+            return Integer.parseInt(interval);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private boolean validDateTime(String dateTime) {
+        try {
+            LocalDateTime.parse(dateTime, utcDateTimeFormatter);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseInterval() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=93200");

Review Comment:
   Assert that this command fails



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        String[] listReplyString = smtpProtocol.getReplyString().split(" ");
+        int length = listReplyString.length;
+        String maxInterval = listReplyString[length-2];
+        String maxDateTime = listReplyString[length-1];
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(getMaxFurtureReleaseInterval(maxInterval)).isGreaterThan(0);
+            softly.assertThat(validDateTime(maxDateTime));
+        });
+    }
+
+    private int getMaxFurtureReleaseInterval(String interval) {
+        try {
+            return Integer.parseInt(interval);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private boolean validDateTime(String dateTime) {
+        try {
+            LocalDateTime.parse(dateTime, utcDateTimeFormatter);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseInterval() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=93200");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body testSimpleMailSendWithFutureRelease\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDUNTIL=2023-04-12T11:00:00Z");

Review Comment:
   And this one fails



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static String maxFutureReleaseInterval;
+    private static LocalDateTime maxFutureReleaseDateTime;
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        maxFutureReleaseDateTime = LocalDateTime.now();
+        return ImmutableSet.of("FUTURERELEASE" + " 86400 " +  maxFutureReleaseDateTime.plusWeeks(1).format(utcDateTimeFormatter).toString());

Review Comment:
   `86400` is a magic constant. We can give it a name with a COnstant



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SendMailHandler.java:
##########
@@ -72,7 +76,10 @@ public HookResult onMessage(SMTPSession session, Mail mail) {
         LOGGER.debug("sending mail");
 
         try (Closeable closeable = MDCBuilder.ofValue("messageId", mail.getMessage().getMessageID()).build()) {
-            queue.enQueue(mail);
+            Long holdFor = AttributeUtils.getValueAndCastFromMail(mail, MAIL_ATTRIBUTE_HOLD_FOR, Long.class)
+                .orElse(0L);

Review Comment:
   Mail attributes will be visible for mail processing.
   Why not read the SMTPSession instead?



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMessageHook.java:
##########
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDFOR;
+
+import java.util.Optional;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.smtpserver.JamesMessageHook;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMessageHook implements JamesMessageHook {

Review Comment:
   Completly delete this class if `SendMailHandler` works with `SMTPSession` attachments directly



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SendMailHandler.java:
##########
@@ -72,7 +76,10 @@ public HookResult onMessage(SMTPSession session, Mail mail) {
         LOGGER.debug("sending mail");
 
         try (Closeable closeable = MDCBuilder.ofValue("messageId", mail.getMessage().getMessageID()).build()) {
-            queue.enQueue(mail);
+            Long holdFor = AttributeUtils.getValueAndCastFromMail(mail, MAIL_ATTRIBUTE_HOLD_FOR, Long.class)
+                .orElse(0L);
+            queue.enQueue(mail, holdFor, TimeUnit.SECONDS);
+            System.out.println("SendMailHandler.java: " + mail.getMessage().getContent().toString());

Review Comment:
   System.out spotted...



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();

Review Comment:
   Move this common piece of code in the setup?



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    public static final long FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM = 0L;
+
+    public static final DateTimeFormatter utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+        if (requestedHoldFor <= MAX_HOLD_FOR_SUPPORTED) {
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+        }
+        return HookResult.DECLINED;

Review Comment:
   DENY on too high values
   
   ```
      8) An MSA supporting Future Message Release MUST reject a MAIL
         command containing the "until" option specifying a value that is
         later than the advertised max-future-release-date-time, or
         otherwise invalid.
   ```



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    public static final long FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM = 0L;
+
+    public static final DateTimeFormatter utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+        if (requestedHoldFor <= MAX_HOLD_FOR_SUPPORTED) {
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+        }
+        return HookResult.DECLINED;

Review Comment:
   + handle parsing errors (date formats, number formats)
   + negative numbers



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    public static final long FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM = 0L;
+
+    public static final DateTimeFormatter utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+        if (requestedHoldFor <= MAX_HOLD_FOR_SUPPORTED) {
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+        }
+        return HookResult.DECLINED;
+    }
+
+    private static Long evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Long.parseLong(paramValue);
+        } else if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            if (paramValue.length() == UTC_TIMESTAMP_LENGTH) {
+                LocalDateTime holdUntil = LocalDateTime.parse(paramValue, utcDateTimeFormatter);
+                return Duration.between(LocalDateTime.now(), holdUntil).toSeconds();
+            } else if (paramValue.length() == ZONED_TIMESTAMP_LENGTH) {
+                LocalDateTime holdUntil = ZonedDateTime.parse(paramValue).toLocalDateTime();
+                return Duration.between(LocalDateTime.now(), holdUntil).toSeconds();
+            }
+        }
+        return FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM;
+    }

Review Comment:
   1. Readability
   
   Method is too long. Too much indentation levels. Some code is duplicated.
   
   Solutions: extract submethods, flatten our if else.
   
   ```suggestion
       private static Long evaluateHoldFor(String paramName, String paramValue) {
           if (paramName.equals(HOLDFOR_PARAMETER)) {
               return Long.parseLong(paramValue);
           } 
           if (paramName.equals(HOLDUNTIL_PARAMETER)) {
              DateTimeFormatter formatter = selectFormatter(paramValue);
              LocalDateTime holdUntil = LocalDateTime.parse(paramValue, formatter);
              return Duration.between(LocalDateTime.now(), holdUntil).toSeconds();
           }
           return FALLBACK_HOLD_FOR_WHEN_INVALID_PARAM;
       }
       
       private DateTimeFormatter selectFormatter(String s) {
               if (paramValue.length() == UTC_TIMESTAMP_LENGTH) {
                   return utcDateTimeFormatter;
               } 
               if (paramValue.length() == ZONED_TIMESTAMP_LENGTH) {
                   return XXXX;
               }
               ???
       }
   ```
   
   2. Error management
   
   Falling back to zero all the time is an opinionated choice. We have no way to understand what's failing.
   Always have a log for failures. But here I think explicitly managing the error and returning an error SMTP code is better.
   
   Throw exceptions instead of falling back + handle them into `doMailParameter`.
   
   3. java time wizardry....
   
   CF https://github.com/apache/james-mime4j/blob/26c8c38f14bdccf917dbbe56ade8cd137548694e/dom/src/main/java/org/apache/james/mime4j/field/DateTimeFieldLenientImpl.java#L54
   
   Use optionalStart / optionalEnd so that the formatter himself is able to handle missing timezones.



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static String maxFutureReleaseInterval;
+    private static LocalDateTime maxFutureReleaseDateTime;
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        maxFutureReleaseDateTime = LocalDateTime.now();

Review Comment:
   ```suggestion
           LocalDateTime maxFutureReleaseDateTime = LocalDateTime.now();
   ```



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseParameters.java:
##########
@@ -0,0 +1,194 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class FutureReleaseParameters {

Review Comment:
   Remove unused code from this class



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMessageHook.java:
##########
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDFOR;
+
+import java.util.Optional;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.smtpserver.JamesMessageHook;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMessageHook implements JamesMessageHook {
+
+    public static final AttributeName MAIL_ATTRIBUTE_HOLD_FOR = AttributeName.of("futurerelease-hold-for");
+    public static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMessageHook.class);
+    @Override
+    public HookResult onMessage(SMTPSession session, Mail mail) {
+        Optional<FutureReleaseParameters.HoldFor> holdForOptional = session.getAttachment(FUTURERELEASE_HOLDFOR, ProtocolSession.State.Transaction);
+
+        Long holdFor = holdForOptional.map(FutureReleaseParameters.HoldFor::value)
+            .orElse(0L);
+        if (holdFor != 0L) {

Review Comment:
   ```
   holdForOptional.map(FutureReleaseParameters.HoldFor::value)
    .ifPresentOrElse(value -> xxx,
       () -> yyy);
   ```



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseParameters.java:
##########
@@ -0,0 +1,194 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class FutureReleaseParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNTIL_PARAMETER = "HOLDUNTIL";
+    public static final long MAX_HOLD_FOR_SUPPORTED = 86400;
+
+    public static class HoldFor {
+        public static Optional<HoldFor> fromSMTPArgLine(Map<String, Long> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(HoldFor::of);
+        }
+
+        public static HoldFor fromAttributeValue(AttributeValue<Long> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Long value;
+
+        public static HoldFor of(Long value) {

Review Comment:
   ```suggestion
           public static HoldFor of(long value) {
   ```



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseParameters.java:
##########
@@ -0,0 +1,194 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class FutureReleaseParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNTIL_PARAMETER = "HOLDUNTIL";
+    public static final long MAX_HOLD_FOR_SUPPORTED = 86400;
+
+    public static class HoldFor {
+        public static Optional<HoldFor> fromSMTPArgLine(Map<String, Long> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(HoldFor::of);
+        }
+
+        public static HoldFor fromAttributeValue(AttributeValue<Long> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Long value;

Review Comment:
   ```suggestion
           private final long value;
   ```



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");

Review Comment:
   If we are using a clock to get time, by providing a fixed clock in the tests, we could "cheat"
   
   ```suggestion
               softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE 86400 2023-04-10Z18:00:00-UTC+7");
   ```
   
   (non contractual date format)



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {

Review Comment:
   Not needed with proper clock settup



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        String[] listReplyString = smtpProtocol.getReplyString().split(" ");
+        int length = listReplyString.length;
+        String maxInterval = listReplyString[length-2];
+        String maxDateTime = listReplyString[length-1];
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(getMaxFurtureReleaseInterval(maxInterval)).isGreaterThan(0);
+            softly.assertThat(validDateTime(maxDateTime));
+        });
+    }
+
+    private int getMaxFurtureReleaseInterval(String interval) {
+        try {
+            return Integer.parseInt(interval);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private boolean validDateTime(String dateTime) {
+        try {
+            LocalDateTime.parse(dateTime, utcDateTimeFormatter);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseInterval() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=93200");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body testSimpleMailSendWithFutureRelease\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDUNTIL=2023-04-12T11:00:00Z");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body in case of max-future-release-date-time in UTC format\r\n.\r\n");
+
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDUNTIL=2023-04-12T11:00:00-08:00");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body in case of max-future-release-date-time in ZonedDateTime format\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+
+    @Test
+    void mailShouldBeRejectedWhenMailParametersContainBothHoldForAndHoldUntil() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=83017 HOLDUNTIL=2023-04-12T11:00:00Z");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body in case of max-future-release-date-time in UTC format\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+}

Review Comment:
   Miss
   
    - Success case (with future release) in both holdfor + holdUntil
    - Mails that do not specify future release should still pass



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+
+    @Test
+    void testEqualsVerifiersForHoldForAndHoldUntilClass() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldUntil.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        String[] listReplyString = smtpProtocol.getReplyString().split(" ");
+        int length = listReplyString.length;
+        String maxInterval = listReplyString[length-2];
+        String maxDateTime = listReplyString[length-1];
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(getMaxFurtureReleaseInterval(maxInterval)).isGreaterThan(0);
+            softly.assertThat(validDateTime(maxDateTime));
+        });
+    }
+
+    private int getMaxFurtureReleaseInterval(String interval) {
+        try {
+            return Integer.parseInt(interval);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private boolean validDateTime(String dateTime) {
+        try {
+            LocalDateTime.parse(dateTime, utcDateTimeFormatter);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseInterval() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=93200");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body testSimpleMailSendWithFutureRelease\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+
+    @Test
+    void mailShouldbeRejectedWhenMaxFutureReleaseDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDUNTIL=2023-04-12T11:00:00Z");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body in case of max-future-release-date-time in UTC format\r\n.\r\n");
+
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDUNTIL=2023-04-12T11:00:00-08:00");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body in case of max-future-release-date-time in ZonedDateTime format\r\n.\r\n");
+
+        assertThat(queue.getSize()).isEqualTo(0l);
+    }
+
+    @Test
+    void mailShouldBeRejectedWhenMailParametersContainBothHoldForAndHoldUntil() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=83017 HOLDUNTIL=2023-04-12T11:00:00Z");

Review Comment:
   Fails here



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static String maxFutureReleaseInterval;
+    private static LocalDateTime maxFutureReleaseDateTime;
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        maxFutureReleaseDateTime = LocalDateTime.now();

Review Comment:
   And take the time from a `Clock`



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.utcDateTimeFormatter;
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.EnumSet;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseEHLOHookTest {

Review Comment:
   ```suggestion
   public class FutureReleaseTest {
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1160417588


##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,201 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+import java.util.Set;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");

Review Comment:
   I don't know how to make server respond these 2 params. Can someone help me?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1507937593

   Rework and force push


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1173278941


##########
server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp-hooks.adoc:
##########
@@ -301,4 +301,21 @@ Example configuration:
     <!-- ... -->
     <handler class="org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler"/>
 </handlerchain>
+....
+
+== FUTURERELEASE hooks
+
+The Distributed server has optional support for FUTURERELEASE (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])
+
+....
+<smtpserver enabled="true">
+    <...> <!-- The rest of your SMTP configuration, unchanged -->
+    <handlerchain>
+    <handlerchain>
+        <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook"/>
+        <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook"/>
+        <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+    </handlerchain>
+    </handlerchain>
+</smtpserver>

Review Comment:
   ```suggestion
       <...> <!-- The rest of your SMTP configuration, unchanged -->
       <handlerchain>
           <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook"/>
           <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook"/>
           <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
       </handlerchain>
   </smtpserver>
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153992846


##########
mailet/api/src/main/java/org/apache/mailet/Mail.java:
##########
@@ -451,4 +451,6 @@ default void setDsnParameters(DsnParameters dsnParameters) {
             .asAttributes()
             .forEach(this::setAttribute);
     }
+
+

Review Comment:
   Still unrelated



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1154286213


##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;

Review Comment:
   I write thís class in mailet package since I see DsnParameters in this package too 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1154313934


##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,17 @@
+package org.apache.james.smtpserver;

Review Comment:
   I'm done this



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1514396030

   Please add documentation for this FUTURE RELEASE extension.
   In `docs/modules/ROOT/pages/configure/smtp-hooks.adoc` (similar to DSN hooks)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1155455476


##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;

Review Comment:
   Not sure about the inital purpose of DsnParameters but you can see it's used in a lot of mailets. Maybe initially it was created alongside mailet classes (that would be my guess). It's used in a more wide range now.
   
   I agree that FutureReleaseParameters are used as smtp hooks in protocols-smtp module, not in mailets. @quantranhong1999 comment makes sense IMO :)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on pull request #1501: JAMES-3822 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1530766851

   > I think it's worth investigating why, there might be an issue
   
   Yes, an issue with that PR. A method within MemoryMailQueue was not taking its time from the clock, causing issues when the clock is overriden...


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153993807


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.mailet.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.HOLDUNITL_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.holdforValue;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.FutureReleaseParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", FutureReleaseParameters.Holduntil.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long interval = new Long(0);
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            LOGGER.debug("HoldFor parameter is set to {}", paramValue);
+            interval = Long.parseLong(paramValue);
+        } else if (paramName.equals(HOLDUNITL_PARAMETER)) {
+            LOGGER.debug("Convert holdunitl parameters into holdfor parameters");
+            LocalDateTime now = LocalDateTime.now();
+            if (paramValue.length() == 21) {

Review Comment:
   That's dirty! Please write your own date pattern with optional part.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] vttranlina commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "vttranlina (via GitHub)" <gi...@apache.org>.
vttranlina commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1495249055

   Maybe you will like it 
   https://plugins.jetbrains.com/plugin/1065-checkstyle-idea
   The `checkstyle.xml` file already in root directory 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1173326060


##########
server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp-hooks.adoc:
##########
@@ -301,4 +301,21 @@ Example configuration:
     <!-- ... -->
     <handler class="org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler"/>
 </handlerchain>
+....
+
+== FUTURERELEASE hooks
+
+The Distributed server has optional support for FUTURERELEASE (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])
+
+....
+<smtpserver enabled="true">
+    <...> <!-- The rest of your SMTP configuration, unchanged -->
+    <handlerchain>
+    <handlerchain>
+        <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook"/>
+        <handler class="org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook"/>
+        <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+    </handlerchain>
+    </handlerchain>
+</smtpserver>

Review Comment:
   I fixed it up already



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1511170095

   `05:30:51,312 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.1.2:check (check-style) on project james-server-protocols-smtp: You have 5 Checkstyle violations. -> [Help 1]`
   
   Protip: run `mvn clean install -DskipTests -T 3` locally...


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1172121321


##########
server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp-hooks.adoc:
##########
@@ -305,7 +305,7 @@ Example configuration:
 
 == FUTURE-RELEASE hooks
 
-The Distributed server has optional support for DSN (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])
+The Distributed server has optional support for FUTURE-RELEASE (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])

Review Comment:
   IMO we should respect the specification
   ```
   1) The Extended Hello (EHLO) keyword associated with this service
      extension is "FUTURERELEASE".
   ```
   
   No hyphen in between



##########
server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp-hooks.adoc:
##########
@@ -305,7 +305,7 @@ Example configuration:
 
 == FUTURE-RELEASE hooks

Review Comment:
   this too



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1166290038


##########
server/apps/distributed-app/docs/modules/ROOT/pages/extending/smtp-hooks.adoc:
##########
@@ -1,7 +1,7 @@
 = Distributed James Server &mdash; Custom SMTP hooks
 :navtitle: Custom SMTP hooks
 
-SMTP hooks enable extending capabilities of the SMTP server and are run synchronously upon email reception, before the email is
+SMTP hooks enable extening capabilities of the SMTP server and are run synchronously upon email reception, before the email is

Review Comment:
   Previous version was the correct one, put back extending please :)



##########
.gitignore:
##########
@@ -6,4 +6,4 @@ dockerfiles/run/**/keystore
 dockerfiles/run/**/glowroot
 .m2
 test-run.log
-build
\ No newline at end of file
+build

Review Comment:
   unrelated change



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+            Instant now = LocalDateTime.now(clock).toInstant(ZoneOffset.UTC);
+            return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant());
+        }
+        throw new IllegalArgumentException("Invalid parameter name " + paramName);
+    }
+
+    private DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }

Review Comment:
   Use `optionalStart` to come up with a dedicated `DateTimeFormatter`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1168147787


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+            Instant now = LocalDateTime.now(clock).toInstant(ZoneOffset.UTC);
+            return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant());
+        }
+        throw new IllegalArgumentException("Invalid parameter name " + paramName);
+    }
+
+    private DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }

Review Comment:
   I guess Benoit means to use `ImapDateTimeFormatter.rfc5322()`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1172065387


##########
server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp-hooks.adoc:
##########
@@ -301,4 +301,21 @@ Example configuration:
     <!-- ... -->
     <handler class="org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler"/>
 </handlerchain>
+....
+
+== FUTURE-RELEASE hooks
+
+The Distributed server has optional support for DSN (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])

Review Comment:
   ```suggestion
   The Distributed server has optional support for FUTURERELEASE (link:https://www.rfc-editor.org/rfc/rfc4865.html[RFC-4865])
   ```
   



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -52,9 +61,9 @@ public HookResult doMailParameter(SMTPSession session, String paramName, String
         if (session.getUsername() == null) {
             LOGGER.debug("Needs to be logged in in order to use future release extension");
             return HookResult.builder()
-                .hookReturnCode(HookReturnCode.deny())
-                .smtpDescription("Needs to be logged in in order to use future release extension")
-                .build();
+                    .hookReturnCode(HookReturnCode.deny())
+                    .smtpDescription("Needs to be logged in in order to use future release extension")
+                    .build();

Review Comment:
   IMO do not change the old indent (check your indent settings).
   
   1 Tab (4 spaces) is good enough, not 2 Tab.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1152919427


##########
.gitignore:
##########
@@ -6,4 +6,19 @@ dockerfiles/run/**/keystore
 dockerfiles/run/**/glowroot
 .m2
 test-run.log
-build
\ No newline at end of file
+build
+*.iml
+.idea
+*.lib
+*.jar
+.floo
+.flooignore
+.m2
+_site/
+keystore
+derby.log
+src/homepage/Gemfile.lock
+mailbox/var/
+src/homepage/.sass-cache/
+KahaDB/
+.attach_pid*

Review Comment:
   Still not removed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1154144679


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,41 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        return ImmutableSet.of("FUTURERELEASE");

Review Comment:
   The spec requires 2 other parameters like max interval and max date time.
   E.g from your Jira comment example: `S: 250-FUTURERELEASE 172800 2023-03-20T20:05:05Z`
   
   Please implement it.



##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FutureReleaseParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+    public static final long holdforValue = 86400;
+
+    public static class Holdfor {
+        public static Optional<Holdfor> fromSMTPArgLine(Map<String, Long> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(Holdfor::of);
+        }
+
+        public static Holdfor fromAttributeValue(AttributeValue<Long> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Long value;
+
+        public static Holdfor of(Long value) {
+            Preconditions.checkNotNull(value);
+            return new Holdfor(value);
+        }
+
+        private Holdfor(Long value) {
+            this.value = value;
+        }
+
+        public Long asString() {
+            return value;
+        }
+
+        public AttributeValue<Long> toAttributeValue() {
+            return AttributeValue.of(value);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Holdfor) {
+                Holdfor holdfor = (Holdfor) o;
+                return Objects.equals(this.value, holdfor.value);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(value);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("value", value)
+                .toString();
+        }
+    }
+
+    public static class Holduntil {

Review Comment:
   ```suggestion
       public static class HoldUntil {
   ```
   
   tip: use IntelliJ refactoring feature to easily refactor the class name.



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/DSNTest.java:
##########
@@ -23,10 +23,12 @@
 import static org.apache.mailet.DsnParameters.Notify.FAILURE;
 import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.INPUT_STREAM;

Review Comment:
   Unrelated change. Please do not change this `DSNTest`.



##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;

Review Comment:
   Not in this mailet package I believe.
   Should be in `org.apache.james.smtpserver.futurerelease`.
   (move it to `protocols-smtp` module)



##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FutureReleaseParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+    public static final long holdforValue = 86400;
+
+    public static class Holdfor {
+        public static Optional<Holdfor> fromSMTPArgLine(Map<String, Long> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(Holdfor::of);
+        }
+
+        public static Holdfor fromAttributeValue(AttributeValue<Long> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Long value;
+
+        public static Holdfor of(Long value) {
+            Preconditions.checkNotNull(value);
+            return new Holdfor(value);
+        }
+
+        private Holdfor(Long value) {
+            this.value = value;
+        }
+
+        public Long asString() {
+            return value;
+        }
+
+        public AttributeValue<Long> toAttributeValue() {
+            return AttributeValue.of(value);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Holdfor) {
+                Holdfor holdfor = (Holdfor) o;
+                return Objects.equals(this.value, holdfor.value);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(value);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("value", value)
+                .toString();
+        }
+    }
+
+    public static class Holduntil {
+        public static Optional<Holduntil> fromSMTPArgLine(Map<String, String> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDUNITL_PARAMETER))
+                .map(Holduntil::of);
+        }
+
+        public static Holduntil fromAttributeValue(AttributeValue<String> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final String value;
+
+        private Holduntil(String value) {
+            this.value = value;
+        }
+
+        public static Holduntil of(String value) {
+            Preconditions.checkNotNull(value);
+            return new Holduntil(value);
+        }
+
+        public String asString() {
+            return value;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(value);
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Holduntil) {
+                Holduntil that = (Holduntil) o;
+                return Objects.equals(this.value, that.value);
+            }
+            return false;
+        }

Review Comment:
   Are we still missing the EqualsVerifier tests for `HoldUntil` and `HoldFor` pojos (as Benoit requested)?



##########
mailet/api/src/main/java/org/apache/mailet/FutureReleaseParameters.java:
##########
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FutureReleaseParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+    public static final long holdforValue = 86400;
+
+    public static class Holdfor {

Review Comment:
   ```suggestion
       public static class HoldFor {
   ```
   
   tip: use IntelliJ refactoring feature to easily refactor the class name.



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMessageHook.java:
##########
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDFOR;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDUNTIL;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.smtpserver.JamesMessageHook;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.FutureReleaseParameters;
+import org.apache.mailet.Mail;
+
+public class FutureReleaseMessageHook implements JamesMessageHook {
+    @Override
+    public HookResult onMessage(SMTPSession session, Mail mail) {
+        Optional<FutureReleaseParameters.Holdfor> holdfor = session.getAttachment(FUTURERELEASE_HOLDFOR, ProtocolSession.State.Transaction);
+        Optional<FutureReleaseParameters.Holduntil> holduntil = session.getAttachment(FUTURERELEASE_HOLDUNTIL, ProtocolSession.State.Transaction);
+
+

Review Comment:
   remove 1 redundant empty line



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,201 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+import java.util.Set;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE");

Review Comment:
   TODO: test for the other 2 params 
   e.g from your jira comment: `250-FUTURERELEASE 172800 2023-03-20T20:05:05Z`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153994633


##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -0,0 +1,201 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseEHLOHook;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+import java.util.Set;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+public class FutureReleaseEHLOHookTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+    @Test

Review Comment:
   ```suggestion
       }
       
       @Test
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153994001


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.mailet.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.HOLDUNITL_PARAMETER;
+import static org.apache.mailet.FutureReleaseParameters.holdforValue;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.apache.mailet.FutureReleaseParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holdfor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.Holdfor.class);
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.Holduntil> FUTURERELEASE_HOLDUNTIL = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDUNTIL", FutureReleaseParameters.Holduntil.class);
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        Long interval = new Long(0);
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            LOGGER.debug("HoldFor parameter is set to {}", paramValue);
+            interval = Long.parseLong(paramValue);
+        } else if (paramName.equals(HOLDUNITL_PARAMETER)) {
+            LOGGER.debug("Convert holdunitl parameters into holdfor parameters");
+            LocalDateTime now = LocalDateTime.now();
+            if (paramValue.length() == 21) {
+                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");

Review Comment:
   Date formatters can (and should) be static so that the pattern is not parsed on every requests.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1508565015

   A big one...
   
   Please ensure that ubaithenticated users can't use future release (security requirement)
   
   So for unautgenticated users:
    - EHLO should not advertise future release.
    - MAIL should reject HoldFor and holdUntil.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1160421501


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,41 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+
+    @Override
+    public Set<String> implementedEsmtpFeatures() {
+        return ImmutableSet.of("FUTURERELEASE");

Review Comment:
   > I don't know how to make server respond these 2 params. Can someone help me?
   
   I am not sure if we want to configure the max HOLD_UNTIL yet. Maybe start with something hardcode and work first?
   Something like (pseudo code)
   
   ```java
           return ImmutableSet.of("FUTURERELEASE", Duration.of(2days), Now + Duration.of(2days));
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1489579535

   What is this commit https://github.com/apache/james-project/pull/1501/commits/900a9bf3f0e656c2c2542ebf6ab320cd67cf8a13 ?
   Did not properly commit the fix-up commit (that solves the comments)?
   
   BTW checkstyle warning from the CI:
   `ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.1.2:check (check-style) on project apache-mailet-api: You have 7 Checkstyle violations`
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] quantranhong1999 commented on pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "quantranhong1999 (via GitHub)" <gi...@apache.org>.
quantranhong1999 commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1480682509

   `JAMES-4865 Add FUTURERELEASEParameters` -> `JAMES-3822 Add FutureRelease Hook` maybe?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1145842865


##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{
+        public static Optional<Holdfor> fromSMTPArgLine(Map<String, Integer> mailFromArgLine) {
+            return Optional.ofNullable(mailFromArgLine.get(HOLDFOR_PARAMETER))
+                .map(Holdfor::of);
+        }
+
+        public static Holdfor fromAttributeValue(AttributeValue<Integer> attributeValue) {
+            return of(attributeValue.value());
+        }
+
+        private final Integer value;
+
+        public static Holdfor of (Integer value) {
+            Preconditions.checkNotNull(value);
+//            Preconditions.checkArgument(XText.isValid(value), "According to RFC-4865 Holdfor should be a valid xtext" +
+//                ", thus composed of CHARs between \"!\" (33) and \"~\" (126) inclusive, except for \"+\" and \"=\" or follow the hexadecimal escape sequence.");

Review Comment:
   XText is only for DSN



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1147070226


##########
mailet/api/src/main/java/org/apache/mailet/FUTURERELEASEParameters.java:
##########
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.mailet;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class FUTURERELEASEParameters {
+    public static final String HOLDFOR_PARAMETER = "HOLDFOR";
+    public static final String HOLDUNITL_PARAMETER = "HOLDUNITL";
+
+    public static class Holdfor{

Review Comment:
   Ok, I will change it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FUTURERELEASEParameters

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1147079955


##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -239,6 +240,8 @@ public String toString() {
         }
     }
 
+
+

Review Comment:
   Ok I got it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1153992721


##########
.gitignore:
##########
@@ -6,4 +6,19 @@ dockerfiles/run/**/keystore
 dockerfiles/run/**/glowroot
 .m2
 test-run.log
-build
\ No newline at end of file
+build
+*.iml
+.idea
+*.lib
+*.jar
+.floo
+.flooignore
+.m2
+_site/
+keystore
+derby.log
+src/homepage/Gemfile.lock
+mailbox/var/
+src/homepage/.sass-cache/
+KahaDB/
+.attach_pid*

Review Comment:
   Still not removed



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1493598244

   Nothing else to add. Keep on the good effort :)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1161402686


##########
server/apps/distributed-app/docs/modules/ROOT/pages/extending/smtp-hooks.adoc:
##########
@@ -1,7 +1,7 @@
 = Distributed James Server &mdash; Custom SMTP hooks
 :navtitle: Custom SMTP hooks
 
-SMTP hooks enable extending capabilities of the SMTP server and are run synchronously upon email reception, before the email is
+SMTP hooks enable extening capabilities of the SMTP server and are run synchronously upon email reception, before the email is

Review Comment:
   Previous version was the correct one, put back `extending` please :)



##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -44,10 +44,9 @@
 
 /**
  * Represents DSN parameters attached to the envelope of an Email transiting over SMTP
- *
  * See https://tools.ietf.org/html/rfc3461
  */
-public class DsnParameters {
+public class    DsnParameters {

Review Comment:
   Unnecessary spaces 



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseEHLOHookTest.java:
##########
@@ -198,4 +199,47 @@ private void authenticate(SMTPClient smtpProtocol) throws IOException {
             .isEqualTo(235);
     }
 
+    @Test
+    void ehloShouldContainMaxFutureReleaseIntervalAndDateTime() throws Exception {
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        String[] listReplyString = smtpProtocol.getReplyString().split(" ");
+        int length = listReplyString.length;
+        System.out.println("listReplyString[-2]: " + listReplyString[length-2] + "listReplyString[-1]: " + listReplyString[length-1]);
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(getMaxFurtureReleaseInterval(listReplyString[length-2])).isGreaterThan(0);
+            softly.assertThat(validDateTime(listReplyString[length-1]));
+        });
+    }
+
+    private int getMaxFurtureReleaseInterval(String interval) {
+        try {
+            return Integer.parseInt(interval);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private boolean validDateTime(String dateTime) {
+        try {
+            LocalDateTime.parse(dateTime, dateTimeFormatter);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Test
+    void test1() throws Exception {

Review Comment:
   Don't forget this :)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1166296939


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);

Review Comment:
   ```suggestion
               DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1168162196


##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseTest.java:
##########
@@ -184,6 +178,15 @@ void tearDown() {
         smtpServer.destroy();
     }
 
+    @Test
+    void cannotAuthenticateWhenSendingWrongPassword() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand("Wrong Password");
+        assertThat(smtpProtocol.getReplyCode()).isEqualTo(501);
+    }

Review Comment:
   What for ?
   
   Not what I asked....



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] chibenwa commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "chibenwa (via GitHub)" <gi...@apache.org>.
chibenwa commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1166181451


##########
mailet/api/src/main/java/org/apache/mailet/Mail.java:
##########
@@ -451,4 +451,4 @@ default void setDsnParameters(DsnParameters dsnParameters) {
             .asAttributes()
             .forEach(this::setAttribute);
     }
-}
+}

Review Comment:
   idem unrelated



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseEHLOHook.java:
##########
@@ -0,0 +1,43 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import java.util.Set;
+
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+
+import com.google.common.collect.ImmutableSet;
+
+public class FutureReleaseEHLOHook implements HeloHook {
+    private static final long MAX_FUTURE_RELEASE_INTERVAL = 86400;
+    private static final String MAX_FUTURE_RELEASE_DATE_TIME = "2023-04-14T10:00:00Z";

Review Comment:
   Hard coding the date do not make sens!
   
   Get it from a `Clock`



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMessageHook.java:
##########
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseMailParameterHook.FUTURERELEASE_HOLDFOR;
+
+import java.util.Optional;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.smtpserver.JamesMessageHook;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMessageHook implements JamesMessageHook {

Review Comment:
   > Completly delete this class if SendMailHandler works with SMTPSession attachments directly
   
   ?



##########
protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/MailCmdHandler.java:
##########
@@ -138,7 +138,6 @@ protected Response doFilterChecks(SMTPSession session, String command,
      */
     private Response doMAILFilter(SMTPSession session, String argument) {
         String sender = null;
-

Review Comment:
   unrelated



##########
mailet/api/src/main/java/org/apache/mailet/DsnParameters.java:
##########
@@ -44,7 +44,6 @@
 
 /**
  * Represents DSN parameters attached to the envelope of an Email transiting over SMTP
- *

Review Comment:
   unrelated



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,107 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+    private static final Instant now = ZonedDateTime.parse("2023-04-13T11:00:00Z").toInstant();
+    public static final long INVALID_HOLDUNTIL_VALUE = -1L;
+    private static final long NUMBER_PARSE_EXCEPTION = -2L;
+    private static final long DATE_TIME_FORMAT_EXCEPTION = -3L;
+    private static final long INVALID_PARAM_NAME = -4L;

Review Comment:
   We are not in `C` we do not need to use numbers to encode exceptions.



##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,107 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+    private static final Instant now = ZonedDateTime.parse("2023-04-13T11:00:00Z").toInstant();
+    public static final long INVALID_HOLDUNTIL_VALUE = -1L;
+    private static final long NUMBER_PARSE_EXCEPTION = -2L;
+    private static final long DATE_TIME_FORMAT_EXCEPTION = -3L;
+    private static final long INVALID_PARAM_NAME = -4L;
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        long requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+        if (requestedHoldFor > MAX_HOLD_FOR_SUPPORTED) {
+            LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+            return HookResult.DENY;
+        }
+        if (requestedHoldFor < 0) {
+            LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+            return HookResult.DENY;
+        }
+        if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+            LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+            return HookResult.DENY;
+        }
+        session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+        return HookResult.DECLINED;
+    }
+
+    private static Long evaluateHoldFor(String paramName, String paramValue) {
+        try {
+            if (paramName.equals(HOLDFOR_PARAMETER)) {
+                return Long.parseLong(paramValue);
+            }
+            if (selectDateTimeFormatter(paramValue).equals(DateTimeFormatter.ISO_DATE)) {
+                return INVALID_HOLDUNTIL_VALUE;
+            }
+            if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+                DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+                return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant()).toSeconds();
+            }
+        } catch (NumberFormatException e) {
+            LOGGER.debug("Failed to parse number format");
+            return NUMBER_PARSE_EXCEPTION;
+        } catch (DateTimeParseException e) {
+            LOGGER.debug("Failed to parse date format");
+            return DATE_TIME_FORMAT_EXCEPTION;
+        }
+        return INVALID_PARAM_NAME;
+    }
+
+    private static DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }
+        return DateTimeFormatter.ISO_DATE;
+    }
+    @Override
+    public String[] getMailParamNames() {

Review Comment:
   ```suggestion
       }
       
       @Override
       public String[] getMailParamNames() {
   ```



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseTest.java:
##########
@@ -0,0 +1,359 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+    @Test
+    void testEqualsVerifiersForHoldForClass() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+    }
+
+    @Test
+    void testEqualsVerifierForHoldUntilClass() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+    }

Review Comment:
   Equals verifiers can be moved in another class and shall be alone in their test class



##########
server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/FutureReleaseTest.java:
##########
@@ -0,0 +1,359 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.UserEntityValidator;
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.lib.DomainListConfiguration;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.mailbox.Authorizator;
+import org.apache.james.mailrepository.api.MailRepositoryStore;
+import org.apache.james.mailrepository.api.Protocol;
+import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
+import org.apache.james.mailrepository.memory.MemoryMailRepository;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryStore;
+import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
+import org.apache.james.mailrepository.memory.SimpleMailRepositoryLoader;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
+import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
+import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+import org.apache.james.smtpserver.futurerelease.FutureReleaseParameters;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.smtpserver.netty.SmtpMetricsImpl;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.TypeLiteral;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class FutureReleaseTest {
+    public static final String LOCAL_DOMAIN = "example.local";
+    public static final Username BOB = Username.of("bob@localhost");
+    public static final String PASSWORD = "bobpwd";
+
+    protected MemoryDomainList domainList;
+    protected MemoryUsersRepository usersRepository;
+    protected SMTPServerTest.AlterableDNSServer dnsServer;
+    protected MemoryMailRepositoryStore mailRepositoryStore;
+    protected FileSystemImpl fileSystem;
+    protected Configuration configuration;
+    protected MockProtocolHandlerLoader chain;
+    protected MemoryMailQueueFactory queueFactory;
+    protected MemoryMailQueueFactory.MemoryCacheableMailQueue queue;
+
+    private SMTPServer smtpServer;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = new MemoryDomainList(new InMemoryDNSService());
+        domainList.configure(DomainListConfiguration.DEFAULT);
+
+        domainList.addDomain(Domain.of(LOCAL_DOMAIN));
+        domainList.addDomain(Domain.of("examplebis.local"));
+        usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
+        usersRepository.addUser(BOB, PASSWORD);
+
+        createMailRepositoryStore();
+
+        setUpFakeLoader();
+        setUpSMTPServer();
+
+        smtpServer.configure(FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("smtpserver-futurerelease.xml")));
+        smtpServer.init();
+    }
+
+    protected void createMailRepositoryStore() throws Exception {
+        configuration = Configuration.builder()
+            .workingDirectory("../")
+            .configurationFromClasspath()
+            .build();
+        fileSystem = new FileSystemImpl(configuration.directories());
+        MemoryMailRepositoryUrlStore urlStore = new MemoryMailRepositoryUrlStore();
+
+        MailRepositoryStoreConfiguration configuration = MailRepositoryStoreConfiguration.forItems(
+            new MailRepositoryStoreConfiguration.Item(
+                ImmutableList.of(new Protocol("memory")),
+                MemoryMailRepository.class.getName(),
+                new BaseHierarchicalConfiguration()));
+
+        mailRepositoryStore = new MemoryMailRepositoryStore(urlStore, new SimpleMailRepositoryLoader(), configuration);
+        mailRepositoryStore.init();
+    }
+
+    protected SMTPServer createSMTPServer(SmtpMetricsImpl smtpMetrics) {
+        return new SMTPServer(smtpMetrics);
+    }
+
+    protected void setUpSMTPServer() {
+        SmtpMetricsImpl smtpMetrics = mock(SmtpMetricsImpl.class);
+        when(smtpMetrics.getCommandsMetric()).thenReturn(mock(Metric.class));
+        when(smtpMetrics.getConnectionMetric()).thenReturn(mock(Metric.class));
+        smtpServer = createSMTPServer(smtpMetrics);
+        smtpServer.setDnsService(dnsServer);
+        smtpServer.setFileSystem(fileSystem);
+        smtpServer.setProtocolHandlerLoader(chain);
+    }
+
+    protected void setUpFakeLoader() {
+        dnsServer = new SMTPServerTest.AlterableDNSServer();
+
+        MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable();
+        rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
+        queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
+
+        chain = MockProtocolHandlerLoader.builder()
+            .put(binder -> binder.bind(DomainList.class).toInstance(domainList))
+            .put(binder -> binder.bind(new TypeLiteral<MailQueueFactory<?>>() {}).toInstance(queueFactory))
+            .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable))
+            .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom))
+            .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MailRepositoryStore.class).toInstance(mailRepositoryStore))
+            .put(binder -> binder.bind(DNSService.class).toInstance(dnsServer))
+            .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
+            .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class))
+            .put(binder -> binder.bind(UserEntityValidator.class).toInstance(UserEntityValidator.NOOP))
+            .put(binder -> binder.bind(Authorizator.class).toInstance((userId, otherUserId) -> Authorizator.AuthorizationState.ALLOWED))
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        smtpServer.destroy();
+    }
+
+    @Test
+    void testEqualsVerifiersForHoldForClass() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+    }
+
+    @Test
+    void testEqualsVerifierForHoldUntilClass() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+        EqualsVerifier.forClass(FutureReleaseParameters.HoldFor.class).verify();
+    }
+
+    @Test
+    void ehloShouldAdvertiseFutureReleaseExtension() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+        smtpProtocol.sendCommand("EHLO localhost");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250 FUTURERELEASE 86400 2023-04-14T10:00:00Z");
+        });
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+
+    @Test
+    void testSuccessCaseWithHoldForParams() throws Exception {
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(smtpServer).retrieveBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bo...@localhost> HOLDFOR=83200");
+        smtpProtocol.sendCommand("RCPT TO:<rc...@localhost>");
+        smtpProtocol.sendShortMessageData("Subject: test mail\r\n\r\nTest body testSimpleMailSendWithFutureRelease\r\n.\r\n");
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(smtpProtocol.getReplyCode()).isEqualTo(250);
+            softly.assertThat(smtpProtocol.getReplyString()).contains("250");
+        });

Review Comment:
   Please instead assert that the mail ended up in the mail queue



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] thanhbv200585 commented on a diff in pull request #1501: [wip] JAMES-4865 Add FutureRelease Extension

Posted by "thanhbv200585 (via GitHub)" <gi...@apache.org>.
thanhbv200585 commented on code in PR #1501:
URL: https://github.com/apache/james-project/pull/1501#discussion_r1168134257


##########
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:
##########
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.smtpserver.futurerelease;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Transaction;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDFOR_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.HOLDUNTIL_PARAMETER;
+import static org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.MAX_HOLD_FOR_SUPPORTED;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.inject.Inject;
+
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.MailParametersHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FutureReleaseMailParameterHook implements MailParametersHook {
+    private static final int UTC_TIMESTAMP_LENGTH = 20;
+    private static final int ZONED_TIMESTAMP_LENGTH = 25;
+    private static final Logger LOGGER = LoggerFactory.getLogger(FutureReleaseMailParameterHook.class);
+
+    public static final ProtocolSession.AttachmentKey<FutureReleaseParameters.HoldFor> FUTURERELEASE_HOLDFOR = ProtocolSession.AttachmentKey.of("FUTURERELEASE_HOLDFOR", FutureReleaseParameters.HoldFor.class);
+
+    private final Clock clock;
+
+    @Inject
+    public FutureReleaseMailParameterHook(Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public HookResult doMailParameter(SMTPSession session, String paramName, String paramValue) {
+        try {
+            Duration requestedHoldFor = evaluateHoldFor(paramName, paramValue);
+
+            if (requestedHoldFor.compareTo(MAX_HOLD_FOR_SUPPORTED) > 0) {
+                LOGGER.debug("HoldFor is greater than max-future-release-interval or holdUntil exceeded max-future-release-date-time");
+                return HookResult.DENY;
+            }
+            if (requestedHoldFor.isNegative()) {
+                LOGGER.debug("HoldFor value is negative or holdUntil value is before now");
+                return HookResult.DENY;
+            }
+            if (session.getAttachment(FUTURERELEASE_HOLDFOR, Transaction).isPresent()) {
+                LOGGER.debug("Mail parameter cannot contains both holdFor and holdUntil parameters");
+                return HookResult.DENY;
+            }
+            session.setAttachment(FUTURERELEASE_HOLDFOR, FutureReleaseParameters.HoldFor.of(requestedHoldFor), Transaction);
+            return HookResult.DECLINED;
+        } catch (IllegalArgumentException e) {
+            LOGGER.debug("Incorrect syntax when handling FUTURE-RELEASE mail parameter", e);
+            return HookResult.DENY;
+        }
+    }
+
+    private Duration evaluateHoldFor(String paramName, String paramValue) {
+        if (paramName.equals(HOLDFOR_PARAMETER)) {
+            return Duration.ofSeconds(Long.parseLong(paramValue));
+        }
+        if (paramName.equals(HOLDUNTIL_PARAMETER)) {
+            DateTimeFormatter formatter = selectDateTimeFormatter(paramValue);
+            Instant now = LocalDateTime.now(clock).toInstant(ZoneOffset.UTC);
+            return Duration.between(now, ZonedDateTime.parse(paramValue, formatter).toInstant());
+        }
+        throw new IllegalArgumentException("Invalid parameter name " + paramName);
+    }
+
+    private DateTimeFormatter selectDateTimeFormatter(String dateTime) {
+        if (dateTime.length() == UTC_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("Z"));
+        }
+        if (dateTime.length() == ZONED_TIMESTAMP_LENGTH) {
+            return DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+        }

Review Comment:
   There is no optional part in this formatter. How do I use `optionalStart`?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on pull request #1501: JAMES-4865 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1515675702

   ```
   [INFO] There are 5 errors reported by Checkstyle 10.3.4 with checkstyle.xml ruleset.
   [ERROR] src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:[31,1] (imports) ImportOrder: Wrong order for 'javax.inject.Inject' import.
   [ERROR] src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:[32,1] (imports) ImportOrder: Wrong order for 'java.time.*' import.                
   [ERROR] src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:[32,17] (imports) AvoidStarImport: Using the '.*' form of import should be avoided 
   - java.time.*.                                                                             
   [ERROR] src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:[35,1] (imports) ImportOrder: Wrong order for 'org.apache.james.protocols.api.Proto
   colSession.State.Transaction' import.                                                      
   [ERROR] src/main/java/org/apache/james/smtpserver/futurerelease/FutureReleaseMailParameterHook.java:[36,80] (imports) AvoidStarImport: Using the '.*' form of import should be avoided 
   - org.apache.james.smtpserver.futurerelease.FutureReleaseParameters.*.   
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[GitHub] [james-project] Arsnael commented on pull request #1501: JAMES-3822 Add FutureRelease Extension

Posted by "Arsnael (via GitHub)" <gi...@apache.org>.
Arsnael commented on PR #1501:
URL: https://github.com/apache/james-project/pull/1501#issuecomment-1529281603

   I see those tests failing regularly in the last few builds: https://ci-builds.apache.org/job/james/job/ApacheJames/job/PR-1501/24/#showFailuresLink
   
   I think it's worth investigating why, there might be an issue


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org