diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index c192f512..c379e884 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -462,6 +462,9 @@ module Net
# +LITERAL-+, and +SPECIAL-USE+.
#
# ==== RFC2087: +QUOTA+
+ # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
+ # - Obsoleted by QUOTA=RES-* [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]],
+ # although the commands are backward compatible.
# - #getquota: returns the resource usage and limits for a quota root
# - #getquotaroot: returns the list of quota roots for a mailbox, as well as
# their resource usage and limits.
@@ -576,6 +579,16 @@ module Net
# See FetchData#emailid and FetchData#emailid.
# - Updates #status with support for the +MAILBOXID+ status attribute.
#
+ # ==== RFC9208: QUOTA=RES-*
+ # +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
+ # - Obsoletes the +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
+ # extension and provides strict semantics for different resource types.
+ # - #getquota: returns the resource usage and limits for a quota root
+ # - #getquotaroot: returns the list of quota roots for a mailbox, as well as
+ # their resource usage and limits.
+ # - #setquota: sets the resource limits for a given quota root.
+ # - Updates #status with "DELETED" and +DELETED-STORAGE+ attributes.
+ #
# ==== RFC9394: +PARTIAL+
# - Updates #search, #uid_search with the +PARTIAL+ return option which adds
# ESearchResult#partial return data.
@@ -696,13 +709,12 @@ module Net
#
# === \IMAP Extensions
#
- # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
- # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
- # March 2022, .
+ # [QUOTA[https://www.rfc-editor.org/rfc/rfc2087]]::
+ # Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
+ # January 1997, .
#
- # Note: obsoletes
- # RFC-2087[https://www.rfc-editor.org/rfc/rfc2087] (January 1997).
- # Net::IMAP does not fully support the RFC9208 updates yet.
+ # *NOTE*: _obsoleted_ by RFC9208[https://www.rfc-editor.org/rfc/rfc9208]
+ # (March 2022).
# [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
# Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
# June 1997, .
@@ -754,6 +766,11 @@ module Net
# Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
# RFC 8474, DOI 10.17487/RFC8474, September 2018,
# .
+ # [{QUOTA=RES-*}[https://www.rfc-editor.org/rfc/rfc9208]]::
+ # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
+ # March 2022, .
+ #
+ # Obsoletes RFC2087[https://www.rfc-editor.org/rfc/rfc2087].
# [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
# Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
# "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
@@ -767,6 +784,7 @@ module Net
#
# === IANA registries
# * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
+ # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
# * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
# * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
# * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
@@ -777,8 +795,8 @@ module Net
# * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
# +imap+
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
+ #
# ==== For currently unsupported features:
- # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
# * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
# * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml]
@@ -1848,12 +1866,18 @@ def xlist(refname, mailbox)
# to both admin and user. If this mailbox exists, it returns an array
# containing objects of type MailboxQuotaRoot and MailboxQuota.
#
+ # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
+ # resource type. This is usually +STORAGE+, but you may need to verify this
+ # with UntaggedResponse#raw_data.
+ #
# Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
#
# ==== Capabilities
#
- # The server's capabilities must include +QUOTA+
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
+ # capability, or a capability prefixed with QUOTA=RES-*
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
+ # resource type.
def getquotaroot(mailbox)
synchronize do
send_command("GETQUOTAROOT", mailbox)
@@ -1865,41 +1889,55 @@ def getquotaroot(mailbox)
end
# Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
- # along with specified +mailbox+. If this mailbox exists, then an array
- # containing a MailboxQuota object is returned. This command is generally
- # only available to server admin.
+ # for the +quota_root+. If this quota root exists, then an array
+ # containing a MailboxQuota object is returned.
+ #
+ # The names of quota roots that are applicable to a particular mailbox can
+ # be discovered with #getquotaroot.
+ #
+ # *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
+ # resource type. This is usually +STORAGE+, but you may need to verify this
+ # with UntaggedResponse#raw_data.
#
# Related: #getquotaroot, #setquota, MailboxQuota
#
# ==== Capabilities
#
- # The server's capabilities must include +QUOTA+
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
- def getquota(mailbox)
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
+ # capability, or a capability prefixed with QUOTA=RES-*
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
+ # resource type.
+ def getquota(quota_root)
synchronize do
- send_command("GETQUOTA", mailbox)
+ send_command("GETQUOTA", quota_root)
clear_responses("QUOTA")
end
end
# Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
- # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then
- # +quota+ will be unset for that mailbox. Typically one needs to be logged
- # in as a server admin for this to work.
+ # along with the specified +quota_root+ and +storage_limit+. If
+ # +storage_limit+ is +nil+, resource limits are unset for that quota root.
+ # Otherwise, it sets the +STORAGE+ resource limit.
+ #
+ # Typically one needs to be logged in as a server admin for this to work.
+ #
+ # *NOTE:* Currently, Net::IMAP only supports setting +STORAGE+ quota limits.
#
# Related: #getquota, #getquotaroot
#
# ==== Capabilities
#
- # The server's capabilities must include +QUOTA+
- # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
- def setquota(mailbox, quota)
- if quota.nil?
- data = '()'
+ # Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
+ # capability, or a capability prefixed with QUOTA=RES-*
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
+ # resource type.
+ def setquota(quota_root, storage_limit)
+ if storage_limit.nil?
+ list = '()'
else
- data = '(STORAGE ' + quota.to_s + ')'
+ list = '(STORAGE ' + storage_limit.to_s + ')'
end
- send_command("SETQUOTA", mailbox, RawData.new(data))
+ send_command("SETQUOTA", quota_root, RawData.new(list))
end
# Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1]
@@ -2006,7 +2044,10 @@ def lsub(refname, mailbox)
# STATUS=SIZE
# {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
#
- # +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
+ # +DELETED+ must be supported when the server's capabilities includes
+ # +IMAP4rev2+.
+ # or QUOTA=RES-MESSAGES
+ # {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html].
#
# +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
# {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
diff --git a/lib/net/imap/response_data.rb b/lib/net/imap/response_data.rb
index 41040889..bba5f9bc 100644
--- a/lib/net/imap/response_data.rb
+++ b/lib/net/imap/response_data.rb
@@ -380,6 +380,14 @@ class ResponseText < Struct.new(:code, :text)
# because the server doesn't allow deletion of mailboxes with children.
# #data is +nil+.
#
+ # === QUOTA=RES-* response codes
+ # See {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html#section-4.3].
+ # * +OVERQUOTA+ (also in RFC5530[https://www.rfc-editor.org/rfc/rfc5530]),
+ # with a tagged +NO+ response to an +APPEND+/+COPY+/+MOVE+ command when
+ # the command would put the target mailbox over any quota, and with an
+ # untagged +NO+ when a mailbox exceeds a soft quota (which may be caused
+ # be external events). #data is +nil+.
+ #
# === +CONDSTORE+ extension
# See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
# * +NOMODSEQ+, when selecting a mailbox that does not support
@@ -463,14 +471,23 @@ class MailboxList < Struct.new(:attr, :delim, :name)
# and MailboxQuota objects.
#
# == Required capability
+ #
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
- # capability.
+ # or QUOTA=RES-STORAGE
+ # [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]] capability.
class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
##
# method: mailbox
# :call-seq: mailbox -> string
#
- # The mailbox with the associated quota.
+ # The quota root with the associated quota.
+ #
+ # NOTE: this was mistakenly named "mailbox". But the quota root's name may
+ # differ from the mailbox. A single quota root may cover multiple
+ # mailboxes, and a single mailbox may be governed by multiple quota roots.
+
+ # The quota root with the associated quota.
+ alias quota_root mailbox
##
# method: usage
@@ -482,7 +499,7 @@ class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
# method: quota
# :call-seq: quota -> Integer
#
- # Quota limit imposed on the mailbox.
+ # Storage limit imposed on the mailbox.
#
end
diff --git a/test/net/imap/test_imap_quota.rb b/test/net/imap/test_imap_quota.rb
new file mode 100644
index 00000000..bbc7445b
--- /dev/null
+++ b/test/net/imap/test_imap_quota.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require "net/imap"
+require "test/unit"
+require_relative "fake_server"
+
+class IMAPQuotaTest < Net::IMAP::TestCase
+ include Net::IMAP::FakeServer::TestHelper
+
+ test "#setquota(quota_root, limit)" do
+ with_fake_server do |server, imap|
+ server.on "SETQUOTA", &:done_ok
+
+ # integer arg
+ imap.setquota "INBOX", 512
+ rcvd_cmd = server.commands.pop
+ assert_equal "SETQUOTA", rcvd_cmd.name
+ assert_equal "INBOX (STORAGE 512)", rcvd_cmd.args
+
+ # string arg
+ imap.setquota "INBOX", "512"
+ rcvd_cmd = server.commands.pop
+ assert_equal "SETQUOTA", rcvd_cmd.name
+ assert_equal "INBOX (STORAGE 512)", rcvd_cmd.args
+
+ # empty quota root, null limit
+ imap.setquota "", nil
+ rcvd_cmd = server.commands.pop
+ assert_equal "SETQUOTA", rcvd_cmd.name
+ assert_equal '"" ()', rcvd_cmd.args
+ end
+ end
+end