You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2022/12/05 02:56:13 UTC

[james-project] branch master updated: JAMES-3754 Implement RFC-2971 IMAP4 ID extension

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 10154970ed JAMES-3754 Implement RFC-2971 IMAP4 ID extension
10154970ed is described below

commit 10154970edf2ca3f70c8dc735672df2fb4efcfdd
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Nov 29 17:44:19 2022 +0700

    JAMES-3754 Implement RFC-2971 IMAP4 ID extension
    
    Allows collecting information on the client fleet.
---
 .../mpt/imapmailbox/suite/AuthenticatedState.java  |  5 ++
 .../org/apache/james/imap/scripts/Id.test          | 27 +++++++
 .../org/apache/james/imap/api/ImapConstants.java   |  1 +
 .../james/imap/decode/parser/IDCommandParser.java  | 83 ++++++++++++++++++++++
 .../imap/decode/parser/ImapParserFactory.java      |  1 +
 .../james/imap/encode/IdResponseEncoder.java       | 40 +++++++++++
 .../encode/main/DefaultImapEncoderFactory.java     |  2 +
 .../james/imap/message/request/IDRequest.java      | 39 ++++++++++
 .../james/imap/message/response/IdResponse.java    | 27 +++++++
 .../james/imap/processor/DefaultProcessor.java     |  1 +
 .../apache/james/imap/processor/IdProcessor.java   | 70 ++++++++++++++++++
 .../pages/architecture/implemented-standards.adoc  |  1 +
 src/site/xdoc/protocols/imap4.xml                  |  1 +
 13 files changed, 298 insertions(+)

diff --git a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
index 1c1606d174..59ac0ee443 100644
--- a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
+++ b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
@@ -49,6 +49,11 @@ public abstract class AuthenticatedState extends BasicImapCommands {
         BasicImapCommands.authenticate(simpleScriptedTestProtocol);
     }
     
+    @Test
+    public void testId() throws Exception {
+        simpleScriptedTestProtocol.run("Id");
+    }
+
     @Test
     public void testNoopUS() throws Exception {
         simpleScriptedTestProtocol.run("Noop");
diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Id.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Id.test
new file mode 100644
index 0000000000..c52fda89d4
--- /dev/null
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Id.test
@@ -0,0 +1,27 @@
+################################################################
+# 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.                                           #
+################################################################
+
+C: a0 ID NIL
+S: \* ID \(\)
+S: a0 OK ID completed.
+
+
+C: a1 ID ("vendor", "whatever", "version", "3.7.0", "random", "random value")
+S: \* ID \(\)
+S: a1 OK ID completed.
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
index c1029b683e..0e4f1a19e6 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
@@ -173,6 +173,7 @@ public interface ImapConstants {
     ImapCommand COMPRESS_COMMAND = ImapCommand.anyStateCommand("COMPRESS");
     ImapCommand LOGOUT_COMMAND = ImapCommand.anyStateCommand("LOGOUT");
     ImapCommand NOOP_COMMAND = ImapCommand.anyStateCommand("NOOP");
+    ImapCommand ID_COMMAND = ImapCommand.anyStateCommand("ID");
 
     ImapCommand AUTHENTICATE_COMMAND = ImapCommand.nonAuthenticatedStateCommand("AUTHENTICATE");
     ImapCommand LOGIN_COMMAND = ImapCommand.nonAuthenticatedStateCommand("LOGIN");
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/IDCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/IDCommandParser.java
new file mode 100644
index 0000000000..cf306a43ee
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/IDCommandParser.java
@@ -0,0 +1,83 @@
+/****************************************************************
+ * 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.imap.decode.parser;
+
+import java.util.Optional;
+
+import org.apache.james.imap.api.ImapConstants;
+import org.apache.james.imap.api.ImapMessage;
+import org.apache.james.imap.api.Tag;
+import org.apache.james.imap.api.message.response.StatusResponseFactory;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.decode.DecodingException;
+import org.apache.james.imap.decode.ImapRequestLineReader;
+import org.apache.james.imap.decode.base.AbstractImapCommandParser;
+import org.apache.james.imap.message.request.IDRequest;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Parses ID commands
+ *
+ * CF https://www.rfc-editor.org/rfc/rfc2971.html
+ */
+public class IDCommandParser extends AbstractImapCommandParser {
+    public IDCommandParser(StatusResponseFactory statusResponseFactory) {
+        super(ImapConstants.ID_COMMAND, statusResponseFactory);
+    }
+
+    @Override
+    protected ImapMessage decode(ImapRequestLineReader request, Tag tag, ImapSession session) throws DecodingException {
+        char c = request.nextWordChar();
+        if (c != '(') {
+            String s = request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE);
+            if (s.equalsIgnoreCase("NIL")) {
+                request.eol();
+                return new IDRequest(tag, Optional.empty());
+            }
+        }
+        request.consumeChar('(');
+
+        ImmutableMap.Builder<String, String> parameters = ImmutableMap.builder();
+        boolean first = true;
+
+        while (c != ')') {
+            if (first) {
+                first = false;
+            } else {
+                request.nextWordChar();
+                request.consumeChar(',');
+            }
+            request.nextWordChar();
+            String field = request.consumeQuoted();
+            request.nextWordChar();
+            request.consumeChar(',');
+            request.nextWordChar();
+            String value = request.consumeQuoted();
+
+            parameters.put(field, value);
+
+            c = request.nextWordChar();
+        }
+
+        request.consumeChar(')');
+        request.eol();
+        return new IDRequest(tag, Optional.of(parameters.build()));
+    }
+}
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
index 81515ff876..2676ae4cf1 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
@@ -90,6 +90,7 @@ public class ImapParserFactory implements ImapCommandParserFactory {
             new StoreCommandParser(statusResponseFactory),
             new UidCommandParser(this, statusResponseFactory),
             new IdleCommandParser(statusResponseFactory),
+            new IDCommandParser(statusResponseFactory),
             new StartTLSCommandParser(statusResponseFactory),
 
             // RFC3691
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/IdResponseEncoder.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/IdResponseEncoder.java
new file mode 100644
index 0000000000..7c11c56c5a
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/IdResponseEncoder.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.imap.encode;
+
+import java.io.IOException;
+
+import org.apache.james.imap.message.response.IdResponse;
+
+public class IdResponseEncoder implements ImapResponseEncoder<IdResponse> {
+    @Override
+    public Class<IdResponse> acceptableMessages() {
+        return IdResponse.class;
+    }
+
+    @Override
+    public void encode(IdResponse existsResponse, ImapResponseComposer composer) throws IOException {
+        composer.untagged()
+            .message("ID")
+            .openParen()
+            .closeParen()
+            .end();
+    }
+}
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/main/DefaultImapEncoderFactory.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/main/DefaultImapEncoderFactory.java
index a135f457f1..9208107a5a 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/encode/main/DefaultImapEncoderFactory.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/main/DefaultImapEncoderFactory.java
@@ -36,6 +36,7 @@ import org.apache.james.imap.encode.ExistsResponseEncoder;
 import org.apache.james.imap.encode.ExpungeResponseEncoder;
 import org.apache.james.imap.encode.FetchResponseEncoder;
 import org.apache.james.imap.encode.FlagsResponseEncoder;
+import org.apache.james.imap.encode.IdResponseEncoder;
 import org.apache.james.imap.encode.ImapEncoder;
 import org.apache.james.imap.encode.ImapEncoderFactory;
 import org.apache.james.imap.encode.ImapResponseComposer;
@@ -109,6 +110,7 @@ public class DefaultImapEncoderFactory implements ImapEncoderFactory {
             new FetchResponseEncoder(neverAddBodyStructureExtensions),
             new ExpungeResponseEncoder(),
             new ExistsResponseEncoder(),
+            new IdResponseEncoder(),
             new MailboxStatusResponseEncoder(),
             new SearchResponseEncoder(),
             new LSubResponseEncoder(),
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/request/IDRequest.java b/protocols/imap/src/main/java/org/apache/james/imap/message/request/IDRequest.java
new file mode 100644
index 0000000000..40411135fd
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/request/IDRequest.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.imap.message.request;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.james.imap.api.ImapConstants;
+import org.apache.james.imap.api.Tag;
+
+public class IDRequest extends AbstractImapRequest {
+
+    private final Optional<Map<String, String>> parameters;
+
+    public IDRequest(Tag tag, Optional<Map<String, String>> parameters) {
+        super(tag, ImapConstants.ID_COMMAND);
+        this.parameters = parameters;
+    }
+
+    public Optional<Map<String, String>> getParameters() {
+        return parameters;
+    }
+}
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/IdResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/IdResponse.java
new file mode 100644
index 0000000000..463d18bf83
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/IdResponse.java
@@ -0,0 +1,27 @@
+/****************************************************************
+ * 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.imap.message.response;
+
+import org.apache.james.imap.api.message.response.ImapResponseMessage;
+
+public class IdResponse implements ImapResponseMessage {
+    public IdResponse() {
+    }
+}
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
index 7812c1fe6a..356d161990 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
@@ -61,6 +61,7 @@ public class DefaultProcessor implements ImapProcessor {
         builder.add(new SystemMessageProcessor(mailboxManager));
         builder.add(new LogoutProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(capabilityProcessor);
+        builder.add(new IdProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(new CheckProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(new LoginProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(new RenameProcessor(mailboxManager, statusResponseFactory, metricFactory));
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/IdProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/IdProcessor.java
new file mode 100644
index 0000000000..9a09c2540d
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/IdProcessor.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * 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.imap.processor;
+
+import java.util.List;
+
+import org.apache.james.imap.api.message.Capability;
+import org.apache.james.imap.api.message.response.StatusResponseFactory;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.message.request.IDRequest;
+import org.apache.james.imap.message.response.IdResponse;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.util.MDCBuilder;
+import org.apache.james.util.MDCStructuredLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Mono;
+
+public class IdProcessor extends AbstractMailboxProcessor<IDRequest> implements CapabilityImplementingProcessor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IdProcessor.class);
+    private static final ImmutableList<Capability> CAPABILITIES = ImmutableList.of(Capability.of("ID"));
+
+    public IdProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MetricFactory metricFactory) {
+        super(IDRequest.class, mailboxManager, factory, metricFactory);
+    }
+
+    @Override
+    protected Mono<Void> processRequestReactive(IDRequest request, ImapSession session, Responder responder) {
+        MDCStructuredLogger.forLogger(LOGGER)
+            .field("parameters", request.getParameters().map(Object::toString).orElse("NIL"))
+            .log(logger -> logger.info("Received id information"));
+
+        responder.respond(new IdResponse());
+
+        return unsolicitedResponses(session, responder, false)
+            .then(Mono.fromRunnable(() -> okComplete(request, responder)));
+    }
+
+    @Override
+    protected MDCBuilder mdc(IDRequest message) {
+        return MDCBuilder.create()
+            .addToContext(MDCBuilder.ACTION, "ID");
+    }
+
+    @Override
+    public List<Capability> getImplementedCapabilities(ImapSession session) {
+        return CAPABILITIES;
+    }
+}
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc
index d3b54aa1f7..f2992260a9 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc
@@ -67,6 +67,7 @@ The following IMAP specifications are implemented:
  - link:https://www.rfc-editor.org/rfc/rfc7889.html[RFC-7889] IMAP Extension for APPENDLIMIT
  - link:https://www.rfc-editor.org/rfc/rfc8474.html[RFC-8474] IMAP Extension for Object Identifiers
  - link:https://datatracker.ietf.org/doc/html/rfc8438.html[RFC-8438] IMAP Extension for STATUS=SIZE
+ - link:https://datatracker.ietf.org/doc/html/rfc2971.html[RFC-2971] IMAP ID Extension
 
 Partially implemented specifications:
 
diff --git a/src/site/xdoc/protocols/imap4.xml b/src/site/xdoc/protocols/imap4.xml
index 14e852096e..c2f747b455 100644
--- a/src/site/xdoc/protocols/imap4.xml
+++ b/src/site/xdoc/protocols/imap4.xml
@@ -62,6 +62,7 @@
        <li>IMAP Extension for OBJECTID (https://www.rfc-editor.org/rfc/rfc8474.html)</li>
        <li>IMAP Extension for STATUS=SIZE (https://www.rfc-editor.org/rfc/rfc8438.html)</li>
        <li>IMAP QUOTA (https://www.rfc-editor.org/rfc/rfc9208.html)</li>
+       <li>IMAP ID (https://www.rfc-editor.org/rfc/rfc2971.html)</li>
      </ul>
      <p>We follow RFC2683 recommendations for our implementations:</p>
      <ul>


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