You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/11/18 01:53:27 UTC

[james-project] branch master updated: JAMES-3856 RFC-9208 Implement IMAP QUOTA revision (#1311)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 44d26e878d JAMES-3856 RFC-9208 Implement IMAP QUOTA revision (#1311)
44d26e878d is described below

commit 44d26e878d02828172a083543a052987194b3976
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Fri Nov 18 08:53:22 2022 +0700

    JAMES-3856 RFC-9208 Implement IMAP QUOTA revision (#1311)
    
     - Adds QUOTA=RES-MESSAGE and QUOTA=RES-STORAGE capabilities
     - Adds support for DELETED status item (also mandated by RFC-9051 IMAP4Rev2)
     - Adds support for DELETED_STORAGE status item
     - Adds the optional OVERQUOTA response code for append commands
     - Document support for this specification
---
 .../james/mpt/imapmailbox/suite/QuotaTest.java     |  2 +-
 .../org/apache/james/imap/scripts/Quota.test       | 32 ++++++++++--
 .../org/apache/james/imap/scripts/Status.test      | 59 ++++++++++++++++++++++
 .../org/apache/james/imap/api/ImapConstants.java   |  6 +++
 .../james/imap/api/display/HumanReadableText.java  |  2 +
 .../james/imap/api/message/StatusDataItems.java    | 17 ++++++-
 .../imap/api/message/response/StatusResponse.java  |  5 ++
 .../imap/decode/parser/StatusCommandParser.java    | 26 ++++++++++
 .../imap/encode/MailboxStatusResponseEncoder.java  | 12 +++++
 .../message/response/MailboxStatusResponse.java    | 14 ++++-
 .../james/imap/processor/AppendProcessor.java      |  8 +++
 .../imap/processor/GetQuotaRootProcessor.java      |  2 +-
 .../james/imap/processor/StatusProcessor.java      | 53 ++++++++++++++++---
 .../encode/MailboxStatusResponseEncoderTest.java   |  7 ++-
 .../pages/architecture/implemented-standards.adoc  |  2 +-
 src/site/xdoc/protocols/imap4.xml                  |  1 +
 16 files changed, 231 insertions(+), 17 deletions(-)

diff --git a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/QuotaTest.java b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/QuotaTest.java
index 5a6a14f509..bbc04deabc 100644
--- a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/QuotaTest.java
+++ b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/QuotaTest.java
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test;
 
 public abstract class QuotaTest implements ImapTestConstants {
 
-    private static final QuotaCountLimit MAX_MESSAGE_QUOTA = QuotaCountLimit.count(4096);
+    private static final QuotaCountLimit MAX_MESSAGE_QUOTA = QuotaCountLimit.count(7);
     private static final QuotaSizeLimit MAX_STORAGE_QUOTA = QuotaSizeLimit.size(5 * 1024L * 1024L * 1024L);
 
     protected abstract ImapHostSystem createImapHostSystem();
diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Quota.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Quota.test
index a5037a02f7..5965061c0d 100644
--- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Quota.test
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Quota.test
@@ -63,6 +63,19 @@ S: \* 2 EXISTS
 S: \* 2 RECENT
 S: A004 OK (\[.+\] )?APPEND completed.
 
+C: A006 GETQUOTAROOT #private.imapuser.test
+S: \* QUOTAROOT "#private\.imapuser\.test" #private&imapuser
+S: \* QUOTA #private&imapuser \(MESSAGE 6 7\)
+S: \* QUOTA #private&imapuser \(STORAGE 1 5242880\)
+S: A006 OK GETQUOTAROOT completed.
+
+C: A007 GETQUOTA #private&imapuser
+# seven because of pre inserted messages for imapuser
+S: \* QUOTA #private&imapuser \(MESSAGE 6 7\)
+# 1 = 1KB : 254 * 4 + 3 * 310 = 1946 so shrinked to 1KB
+S: \* QUOTA #private&imapuser \(STORAGE 1 5242880\)
+S: A007 OK GETQUOTA completed.
+
 C: A005 APPEND #private.imapuser.test (\Seen \Draft) "09-Apr-2008 15:17:51 +0200" {310+}
 C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
 C: From: Fred Foobar <fo...@Blurdybloop.COM>
@@ -84,16 +97,29 @@ AWAIT
 
 C: A006 GETQUOTAROOT #private.imapuser.test
 S: \* QUOTAROOT "#private\.imapuser\.test" #private&imapuser
-S: \* QUOTA #private&imapuser \(MESSAGE 7 4096\)
+S: \* QUOTA #private&imapuser \(MESSAGE 7 7\)
 S: \* QUOTA #private&imapuser \(STORAGE 1 5242880\)
 S: A006 OK GETQUOTAROOT completed.
 
 C: A007 GETQUOTA #private&imapuser
 # seven because of pre inserted messages for imapuser
-S: \* QUOTA #private&imapuser \(MESSAGE 7 4096\)
+S: \* QUOTA #private&imapuser \(MESSAGE 7 7\)
 # 1 = 1KB : 254 * 4 + 3 * 310 = 1946 so shrinked to 1KB
 S: \* QUOTA #private&imapuser \(STORAGE 1 5242880\)
 S: A007 OK GETQUOTA completed.
 
 C: A007 SETQUOTA #private&imapuser (MESSAGE 4096) (STORAGE 5242880)
-S: A007 NO SETQUOTA You need the Full admin rights right to perform command SETQUOTA on mailbox Can not perform SETQUOTA commands.
\ No newline at end of file
+S: A007 NO SETQUOTA You need the Full admin rights right to perform command SETQUOTA on mailbox Can not perform SETQUOTA commands.
+
+C: A005 APPEND #private.imapuser.test (\Seen \Draft) "09-Apr-2008 15:17:51 +0200" {310+}
+C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
+C: From: Fred Foobar <fo...@Blurdybloop.COM>
+C: Subject: afternoon meeting 2
+C: To: mooch@owatagu.siam.edu
+C: Message-Id: <B2...@Blurdybloop.COM>
+C: MIME-Version: 1.0
+C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+C:
+C: Hello Joe, could we change that to 4:00pm tomorrow?
+C:
+S: A005 NO \[OVERQUOTA\] APPEND failed. Over quota.
\ No newline at end of file
diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test
index 1a0b8ccf69..0e060cba36 100644
--- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test
@@ -62,6 +62,65 @@ C: a008 STATUS statustest (UNSEEN SIZE MESSAGES )
 S: \* STATUS \"statustest\" \(MESSAGES 1 SIZE 254 UNSEEN 1\)
 S: a008 OK STATUS completed.
 
+C: A009 APPEND statustest {254+}
+C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
+C: From: Fred Foobar <fo...@Blurdybloop.COM>
+C: Subject: Test 01
+C: To: mooch@owatagu.siam.edu
+C: Message-Id: <B2...@Blurdybloop.COM>
+C: MIME-Version: 1.0
+C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+C:
+C: Test 01
+C:
+S: A009 OK (\[.+\] )?APPEND completed.
+
+C: A010 APPEND statustest {254+}
+C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
+C: From: Fred Foobar <fo...@Blurdybloop.COM>
+C: Subject: Test 01
+C: To: mooch@owatagu.siam.edu
+C: Message-Id: <B2...@Blurdybloop.COM>
+C: MIME-Version: 1.0
+C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+C:
+C: Test 01
+C:
+S: A010 OK (\[.+\] )?APPEND completed.
+
+C: A011 APPEND statustest {254+}
+C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
+C: From: Fred Foobar <fo...@Blurdybloop.COM>
+C: Subject: Test 01
+C: To: mooch@owatagu.siam.edu
+C: Message-Id: <B2...@Blurdybloop.COM>
+C: MIME-Version: 1.0
+C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+C:
+C: Test 01
+C:
+S: A011 OK (\[.+\] )?APPEND completed.
+
+C: 10 SELECT statustest
+S: \* FLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\Seen\)
+S: \* 4 EXISTS
+S: \* 4 RECENT
+S: \* OK \[UIDVALIDITY \d+\].*
+S: \* OK \[UNSEEN 1\] .*
+S: \* OK \[PERMANENTFLAGS \(\\Answered \\Deleted \\Draft \\Flagged \\\Seen( \\\*)?\)\].*
+S: \* OK \[HIGHESTMODSEQ \d+\].*
+S: \* OK \[UIDNEXT 5\].*
+S: 10 OK \[READ-WRITE\] SELECT completed.
+
+C: A012 STORE 1:2 FLAGS (\Deleted)
+S: \* 1 FETCH \(FLAGS \(\\Deleted \\Recent\)\)
+S: \* 2 FETCH \(FLAGS \(\\Deleted \\Recent\)\)
+S: A012 OK STORE completed.
+
+C: A013 STATUS statustest (UNSEEN SIZE MESSAGES DELETED DELETED-STORAGE)
+S: \* STATUS \"statustest\" \(MESSAGES 4 SIZE 1016 DELETED 2 DELETED-STORAGE 508 UNSEEN 4\)
+S: A013 OK STATUS completed.
+
 # Cleanup
 C: a1 DELETE statustest
 S: a1 OK DELETE 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 96a372c96c..d38f1d1244 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
@@ -92,6 +92,8 @@ public interface ImapConstants {
     Capability SUPPORTS_ACL = Capability.of("ACL");
 
     Capability SUPPORTS_QUOTA = Capability.of("QUOTA");
+    Capability SUPPORTS_QUOTA_RES_STORAGE = Capability.of("QUOTA=RES-STORAGE");
+    Capability SUPPORTS_QUOTA_RES_MESSAGE = Capability.of("QUOTA=RES-MESSAGE");
 
     Capability SUPPORTS_MOVE = Capability.of("MOVE");
 
@@ -152,6 +154,10 @@ public interface ImapConstants {
 
     String STATUS_SIZE = "SIZE";
 
+    String STATUS_DELETED = "DELETED";
+
+    String STATUS_DELETED_STORAGE = "DELETED-STORAGE";
+
     String STATUS_HIGHESTMODSEQ = "HIGHESTMODSEQ";
 
     ImapCommand CAPABILITY_COMMAND = ImapCommand.anyStateCommand("CAPABILITY");
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java b/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java
index 7690f96de6..d7a9ed6c34 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/display/HumanReadableText.java
@@ -84,6 +84,8 @@ public class HumanReadableText {
 
     public static final HumanReadableText FAILURE_NO_SUCH_MAILBOX = new HumanReadableText("org.apache.james.imap.FAILURE_NO_SUCH_MAILBOX", "failed. No such mailbox.");
 
+    public static final HumanReadableText FAILURE_OVERQUOTA = new HumanReadableText("org.apache.james.imap.OVERQUOTA", "failed. Over quota.");
+
     public static final HumanReadableText FAILURE_NO_QUOTA_RESOURCE = new HumanReadableText("org.apache.james.imap.FAILURE_NO_SUCH_QUOTA_RESOURCE", "failed. No such quota resource.");
 
     public static final HumanReadableText START_TRANSACTION_FAILED = new HumanReadableText("org.apache.james.imap.START_TRANSACTION_FAILED", "failed. Cannot start transaction.");
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/message/StatusDataItems.java b/protocols/imap/src/main/java/org/apache/james/imap/api/message/StatusDataItems.java
index 5f1d441062..ba3d7a1b0d 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/message/StatusDataItems.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/message/StatusDataItems.java
@@ -30,10 +30,15 @@ public class StatusDataItems {
         MESSAGES,
         RECENT,
         UID_NEXT,
-        SIZE,
         UID_VALIDITY,
         UNSEEN,
-        HIGHEST_MODSEQ
+        HIGHEST_MODSEQ,
+        // See https://www.iana.org/go/rfc8438
+        SIZE,
+        // See https://www.rfc-editor.org/rfc/rfc9208.html
+        DELETED,
+        // See https://www.rfc-editor.org/rfc/rfc9208.html
+        DELETED_STORAGE
     }
 
     private final EnumSet<StatusItem> statusItems;
@@ -70,6 +75,14 @@ public class StatusDataItems {
         return statusItems.contains(StatusItem.SIZE);
     }
 
+    public boolean isDeleted() {
+        return statusItems.contains(StatusItem.DELETED);
+    }
+
+    public boolean isDeletedStorage() {
+        return statusItems.contains(StatusItem.DELETED_STORAGE);
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(SIMPLE_NAME)
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java
index 44cf9b051d..ec4becc973 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java
@@ -153,6 +153,7 @@ public interface StatusResponse extends ImapResponseMessage {
 
         /** RFC2060 <code>TRYCREATE</code> response code */
         private static final ResponseCode TRYCREATE = new ResponseCode("TRYCREATE");
+        private static final ResponseCode OVERQUOTA = new ResponseCode("OVERQUOTA");
 
         /** RFC5162 <code>CLOSED</code> response code */
         private static final ResponseCode CLOSED = new ResponseCode("CLOSED");
@@ -289,6 +290,10 @@ public interface StatusResponse extends ImapResponseMessage {
             return TRYCREATE;
         }
 
+        public static ResponseCode overQuota() {
+            return OVERQUOTA;
+        }
+
         /**
          * Creates a RFC2060 <code>UIDVALIDITY</code> response code.
          * 
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java
index 356ea4c165..5d47339516 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java
@@ -75,6 +75,9 @@ public class StatusCommandParser extends AbstractImapCommandParser {
         if (c == 'm' || c == 'M') {
             return readMessages(request);
         }
+        if (c == 'd' || c == 'D') {
+            return readDeleted(request);
+        }
         if (c == 'r' || c == 'R') {
             return readRecent(request);
         }
@@ -182,6 +185,29 @@ public class StatusCommandParser extends AbstractImapCommandParser {
         return StatusDataItems.StatusItem.MESSAGES;
     }
 
+    private StatusDataItems.StatusItem readDeleted(ImapRequestLineReader request) throws DecodingException {
+        assertChar(request, 'd', 'D');
+        assertChar(request, 'e', 'E');
+        assertChar(request, 'l', 'L');
+        assertChar(request, 'e', 'E');
+        assertChar(request, 't', 'T');
+        assertChar(request, 'e', 'E');
+        assertChar(request, 'd', 'D');
+        char c = request.nextWordChar();
+        if (c == '-') {
+            assertChar(request, '-', '-');
+            assertChar(request, 's', 'S');
+            assertChar(request, 't', 'T');
+            assertChar(request, 'o', 'O');
+            assertChar(request, 'r', 'R');
+            assertChar(request, 'a', 'A');
+            assertChar(request, 'g', 'G');
+            assertChar(request, 'e', 'E');
+            return StatusDataItems.StatusItem.DELETED_STORAGE;
+        }
+        return StatusDataItems.StatusItem.DELETED;
+    }
+
     private void assertChar(ImapRequestLineReader reader, char low, char up) throws DecodingException {
         char c = reader.consume();
         if (c != low && c != up) {
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/MailboxStatusResponseEncoder.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/MailboxStatusResponseEncoder.java
index c213ab7732..62925f69f8 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/encode/MailboxStatusResponseEncoder.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/MailboxStatusResponseEncoder.java
@@ -41,6 +41,8 @@ public class MailboxStatusResponseEncoder implements ImapConstants, ImapResponse
         Long messages = response.getMessages();
         Long recent = response.getRecent();
         Long size = response.getSize();
+        Long deleted = response.getDeleted();
+        Long deletedStorage = response.getDeletedStorage();
         MessageUid uidNext = response.getUidNext();
         ModSeq highestModSeq = response.getHighestModSeq();
         UidValidity uidValidity = response.getUidValidity();
@@ -62,6 +64,16 @@ public class MailboxStatusResponseEncoder implements ImapConstants, ImapResponse
             composer.message(size);
         }
 
+        if (deleted != null) {
+            composer.message(STATUS_DELETED);
+            composer.message(deleted);
+        }
+
+        if (deletedStorage != null) {
+            composer.message(STATUS_DELETED_STORAGE);
+            composer.message(deletedStorage);
+        }
+
         if (recent != null) {
             composer.message(STATUS_RECENT);
             composer.message(recent);
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/MailboxStatusResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/MailboxStatusResponse.java
index 28458e6b8b..4e00153cbe 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/MailboxStatusResponse.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/MailboxStatusResponse.java
@@ -29,6 +29,8 @@ import org.apache.james.mailbox.model.UidValidity;
  */
 public class MailboxStatusResponse implements ImapResponseMessage {
     private final Long size;
+    private final Long deleted;
+    private final Long deletedStorage;
     private final Long messages;
     private final Long recent;
     private final MessageUid uidNext;
@@ -37,8 +39,10 @@ public class MailboxStatusResponse implements ImapResponseMessage {
     private final String mailbox;
     private final ModSeq highestModSeq;
 
-    public MailboxStatusResponse(Long size, Long messages, Long recent, MessageUid uidNext, ModSeq highestModSeq, UidValidity uidValidity, Long unseen, String mailbox) {
+    public MailboxStatusResponse(Long size, Long deleted, Long deletedStorage, Long messages, Long recent, MessageUid uidNext, ModSeq highestModSeq, UidValidity uidValidity, Long unseen, String mailbox) {
         this.size = size;
+        this.deleted = deleted;
+        this.deletedStorage = deletedStorage;
         this.messages = messages;
         this.recent = recent;
         this.uidNext = uidNext;
@@ -52,6 +56,14 @@ public class MailboxStatusResponse implements ImapResponseMessage {
         return size;
     }
 
+    public Long getDeleted() {
+        return deleted;
+    }
+
+    public Long getDeletedStorage() {
+        return deletedStorage;
+    }
+
     /**
      * Gets the <code>MESSAGES</code> count for the mailbox.
      * 
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AppendProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AppendProcessor.java
index cba9380f08..a3295336c6 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AppendProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AppendProcessor.java
@@ -39,6 +39,7 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
+import org.apache.james.mailbox.exception.OverQuotaException;
 import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.Content;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -79,6 +80,13 @@ public class AppendProcessor extends AbstractMailboxProcessor<AppendRequest> {
                 no(request, responder, HumanReadableText.FAILURE_NO_SUCH_MAILBOX, StatusResponse.ResponseCode.tryCreate());
                 return Mono.empty();
             })
+            .doOnEach(logOnError(OverQuotaException.class, e -> LOGGER.info("Append failed for mailbox {} because overquota", mailboxPath)))
+            .onErrorResume(OverQuotaException.class, e -> {
+                // Indicates that the mailbox does not exist
+                // So TRY CREATE
+                no(request, responder, HumanReadableText.FAILURE_OVERQUOTA, StatusResponse.ResponseCode.overQuota());
+                return Mono.empty();
+            })
             .doOnEach(logOnError(MailboxException.class, e -> LOGGER.error("Append failed for mailbox {}", mailboxPath, e)))
             .onErrorResume(MailboxException.class, e -> {
                 // Some other issue
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/GetQuotaRootProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/GetQuotaRootProcessor.java
index b92af1f24f..d1f761061f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/GetQuotaRootProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/GetQuotaRootProcessor.java
@@ -54,8 +54,8 @@ import reactor.core.publisher.Mono;
  * GETQUOTAROOT Processor
  */
 public class GetQuotaRootProcessor extends AbstractMailboxProcessor<GetQuotaRootRequest> implements CapabilityImplementingProcessor {
+    private static final List<Capability> CAPABILITIES = ImmutableList.of(ImapConstants.SUPPORTS_QUOTA, ImapConstants.SUPPORTS_QUOTA_RES_MESSAGE, ImapConstants.SUPPORTS_QUOTA_RES_STORAGE);
 
-    private static final List<Capability> CAPABILITIES = ImmutableList.of(ImapConstants.SUPPORTS_QUOTA);
     private final QuotaRootResolver quotaRootResolver;
     private final QuotaManager quotaManager;
 
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java
index a2d8ae0072..a7151e1539 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java
@@ -24,7 +24,8 @@ import static org.apache.james.mailbox.MessageManager.MailboxMetaData.RecentMode
 
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
+
+import javax.mail.Flags;
 
 import org.apache.james.imap.api.display.HumanReadableText;
 import org.apache.james.imap.api.message.Capability;
@@ -131,15 +132,18 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp
                                                               MessageManager.MailboxMetaData metaData,
                                                               MailboxSession session) {
         StatusDataItems statusDataItems = request.getStatusDataItems();
-        return size(statusDataItems, mailbox, session)
-            .map(maybeSize -> {
+        return iterateMailbox(statusDataItems, mailbox, session)
+            .map(maybeIterationResult -> {
                 Long messages = messages(statusDataItems, metaData);
                 Long recent = recent(statusDataItems, metaData);
                 MessageUid uidNext = uidNext(statusDataItems, metaData);
                 UidValidity uidValidity = uidValidity(statusDataItems, metaData);
                 Long unseen = unseen(statusDataItems, metaData);
                 ModSeq highestModSeq = highestModSeq(statusDataItems, metaData);
-                return new MailboxStatusResponse(maybeSize.orElse(null),
+                return new MailboxStatusResponse(
+                    maybeIterationResult.flatMap(result -> result.getSize(statusDataItems)).orElse(null),
+                    maybeIterationResult.flatMap(result -> result.getDeleted(statusDataItems)).orElse(null),
+                    maybeIterationResult.flatMap(result -> result.getDeletedStorage(statusDataItems)).orElse(null),
                     messages, recent, uidNext, highestModSeq, uidValidity, unseen, request.getMailboxName());
             });
     }
@@ -200,16 +204,53 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp
         }
     }
 
-    private Mono<Optional<Long>> size(StatusDataItems statusDataItems, MessageManager messageManager, MailboxSession session) {
+    private Mono<Optional<MailboxIterationResult>> iterateMailbox(StatusDataItems statusDataItems, MessageManager messageManager, MailboxSession session) {
         if (statusDataItems.isSize()) {
             return Flux.from(messageManager.getMessagesReactive(MessageRange.all(), FetchGroup.MINIMAL, session))
-                .collect(Collectors.summingLong(MessageResult::getSize))
+                .reduce(new MailboxIterationResult(), MailboxIterationResult::accumulate)
                 .map(Optional::of);
         } else {
             return Mono.just(Optional.empty());
         }
     }
 
+    public static class MailboxIterationResult {
+        private long size = 0;
+        private long deleted = 0;
+        private long deletedStorage = 0;
+
+        public MailboxIterationResult accumulate(MessageResult messageResult) {
+            if (messageResult.getFlags().contains(Flags.Flag.DELETED)) {
+                deleted++;
+                deletedStorage += messageResult.getSize();
+            }
+            size += messageResult.getSize();
+            return this;
+        }
+
+        public Optional<Long> getSize(StatusDataItems items) {
+            if (items.isSize()) {
+                return Optional.of(size);
+            }
+            return Optional.empty();
+        }
+
+        public Optional<Long> getDeleted(StatusDataItems items) {
+            if (items.isDeleted()) {
+                return Optional.of(deleted);
+            }
+            return Optional.empty();
+        }
+
+        public Optional<Long> getDeletedStorage(StatusDataItems items) {
+            if (items.isDeletedStorage()) {
+                return Optional.of(deletedStorage);
+            }
+            return Optional.empty();
+        }
+    }
+
+
     @Override
     protected MDCBuilder mdc(StatusRequest request) {
         return MDCBuilder.create()
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/encode/MailboxStatusResponseEncoderTest.java b/protocols/imap/src/test/java/org/apache/james/imap/encode/MailboxStatusResponseEncoderTest.java
index 2e404db171..eed9f47974 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/encode/MailboxStatusResponseEncoderTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/encode/MailboxStatusResponseEncoderTest.java
@@ -53,10 +53,13 @@ class MailboxStatusResponseEncoderTest  {
         final MessageUid uidNext = MessageUid.of(5);
         final UidValidity uidValidity = UidValidity.of(7L);
         final Long unseen = 11L;
+        final Long size = 42L;
+        final Long deleted = 23L;
+        final Long deletedStorage = 13L;
         final String mailbox = "A mailbox named desire";
 
-        encoder.encode(new MailboxStatusResponse(null, messages, recent, uidNext,
+        encoder.encode(new MailboxStatusResponse(null, null, deletedStorage, messages, recent, uidNext,
                 null, uidValidity, unseen, mailbox), composer);
-        assertThat(writer.getString()).isEqualTo("* STATUS \"A mailbox named desire\" (MESSAGES 2 RECENT 3 UIDNEXT 5 UIDVALIDITY 7 UNSEEN 11)\r\n");
+        assertThat(writer.getString()).isEqualTo("* STATUS \"A mailbox named desire\" (MESSAGES 2 DELETED-STORAGE 13 RECENT 3 UIDNEXT 5 UIDVALIDITY 7 UNSEEN 11)\r\n");
     }
 }
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 5437a24f09..4510321c51 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
@@ -50,7 +50,7 @@ The following IMAP specifications are implemented:
 
  - link:https://datatracker.ietf.org/doc/html/rfc3501.html[RFC-3501] INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
  - link:https://datatracker.ietf.org/doc/html/rfc2177.html[RFC-2177] IMAP IDLE (mailbox scoped push notifications)
- - link:https://datatracker.ietf.org/doc/html/rfc2087.html[RFC-2087] IMAP Quota
+ - link:https://www.rfc-editor.org/rfc/rfc9208.html[RFC-9208] IMAP QUOTA Extension
  - link:https://datatracker.ietf.org/doc/html/rfc2342.html[RFC-2342] IMAP namespace
  - link:https://datatracker.ietf.org/doc/html/rfc2088.html[RFC-2088] IMAP non synchronized literals
  - link:https://datatracker.ietf.org/doc/html/rfc4315.html[RFC-4315] IMAP UIDPLUS
diff --git a/src/site/xdoc/protocols/imap4.xml b/src/site/xdoc/protocols/imap4.xml
index f6bee705f6..34c0332474 100644
--- a/src/site/xdoc/protocols/imap4.xml
+++ b/src/site/xdoc/protocols/imap4.xml
@@ -59,6 +59,7 @@
        <li>MOVE (RFC 6851 https://tools.ietf.org/html/rfc6851 on master). This is enabled only if you use a MailboxManager exposing the Move capability</li>
        <li>METADATA Extension (RFC 5464 http://www.ietf.org/rfc/rfc5464.txt on master). This is enabled only if you use a MailboxManager exposing the Annotation capability</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>
      </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