Skip to content

Commit 888ba70

Browse files
authored
🔀 Merge pull request #636 from ruby/document-incomplete-quota-support-and-update-names
📚️ Fix QUOTA documentation, ✅ Test `#setquota`, ♻️ Add `MailboxQuota#quota_root` alias
2 parents f3358d7 + 0ea729c commit 888ba70

File tree

3 files changed

+121
-30
lines changed

3 files changed

+121
-30
lines changed

lib/net/imap.rb

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ module Net
462462
# +LITERAL-+, and +SPECIAL-USE+.</em>
463463
#
464464
# ==== RFC2087: +QUOTA+
465+
# +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
466+
# - Obsoleted by <tt>QUOTA=RES-*</tt> [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]],
467+
# although the commands are backward compatible.
465468
# - #getquota: returns the resource usage and limits for a quota root
466469
# - #getquotaroot: returns the list of quota roots for a mailbox, as well as
467470
# their resource usage and limits.
@@ -576,6 +579,16 @@ module Net
576579
# See FetchData#emailid and FetchData#emailid.
577580
# - Updates #status with support for the +MAILBOXID+ status attribute.
578581
#
582+
# ==== RFC9208: <tt>QUOTA=RES-*</tt>
583+
# +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
584+
# - Obsoletes the +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
585+
# extension and provides strict semantics for different resource types.
586+
# - #getquota: returns the resource usage and limits for a quota root
587+
# - #getquotaroot: returns the list of quota roots for a mailbox, as well as
588+
# their resource usage and limits.
589+
# - #setquota: sets the resource limits for a given quota root.
590+
# - Updates #status with <tt>"DELETED"</tt> and +DELETED-STORAGE+ attributes.
591+
#
579592
# ==== RFC9394: +PARTIAL+
580593
# - Updates #search, #uid_search with the +PARTIAL+ return option which adds
581594
# ESearchResult#partial return data.
@@ -696,13 +709,12 @@ module Net
696709
#
697710
# === \IMAP Extensions
698711
#
699-
# [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
700-
# Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
701-
# March 2022, <https://www.rfc-editor.org/info/rfc9208>.
712+
# [QUOTA[https://www.rfc-editor.org/rfc/rfc2087]]::
713+
# Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
714+
# January 1997, <https://www.rfc-editor.org/info/rfc2087>.
702715
#
703-
# <em>Note: obsoletes</em>
704-
# RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
705-
# <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
716+
# *NOTE*: _obsoleted_ by RFC9208[https://www.rfc-editor.org/rfc/rfc9208]
717+
# (March 2022).
706718
# [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
707719
# Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
708720
# June 1997, <https://www.rfc-editor.org/info/rfc2177>.
@@ -754,6 +766,11 @@ module Net
754766
# Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
755767
# RFC 8474, DOI 10.17487/RFC8474, September 2018,
756768
# <https://www.rfc-editor.org/info/rfc8474>.
769+
# [{QUOTA=RES-*}[https://www.rfc-editor.org/rfc/rfc9208]]::
770+
# Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
771+
# March 2022, <https://www.rfc-editor.org/info/rfc9208>.
772+
#
773+
# Obsoletes RFC2087[https://www.rfc-editor.org/rfc/rfc2087].
757774
# [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
758775
# Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
759776
# "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
@@ -767,6 +784,7 @@ module Net
767784
#
768785
# === IANA registries
769786
# * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
787+
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
770788
# * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml]
771789
# * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]
772790
# * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml]
@@ -777,8 +795,8 @@ module Net
777795
# * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
778796
# +imap+
779797
# * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
798+
#
780799
# ==== For currently unsupported features:
781-
# * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
782800
# * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
783801
# * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
784802
# * {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)
18481866
# to both admin and user. If this mailbox exists, it returns an array
18491867
# containing objects of type MailboxQuotaRoot and MailboxQuota.
18501868
#
1869+
# *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1870+
# resource type. This is usually +STORAGE+, but you may need to verify this
1871+
# with UntaggedResponse#raw_data.
1872+
#
18511873
# Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
18521874
#
18531875
# ==== Capabilities
18541876
#
1855-
# The server's capabilities must include +QUOTA+
1856-
# [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1877+
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1878+
# capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1879+
# {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1880+
# resource type.
18571881
def getquotaroot(mailbox)
18581882
synchronize do
18591883
send_command("GETQUOTAROOT", mailbox)
@@ -1865,41 +1889,55 @@ def getquotaroot(mailbox)
18651889
end
18661890

18671891
# Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2]
1868-
# along with specified +mailbox+. If this mailbox exists, then an array
1869-
# containing a MailboxQuota object is returned. This command is generally
1870-
# only available to server admin.
1892+
# for the +quota_root+. If this quota root exists, then an array
1893+
# containing a MailboxQuota object is returned.
1894+
#
1895+
# The names of quota roots that are applicable to a particular mailbox can
1896+
# be discovered with #getquotaroot.
1897+
#
1898+
# *NOTE:* Currently, Net::IMAP only supports +QUOTA+ responses with a single
1899+
# resource type. This is usually +STORAGE+, but you may need to verify this
1900+
# with UntaggedResponse#raw_data.
18711901
#
18721902
# Related: #getquotaroot, #setquota, MailboxQuota
18731903
#
18741904
# ==== Capabilities
18751905
#
1876-
# The server's capabilities must include +QUOTA+
1877-
# [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1878-
def getquota(mailbox)
1906+
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1907+
# capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1908+
# {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1909+
# resource type.
1910+
def getquota(quota_root)
18791911
synchronize do
1880-
send_command("GETQUOTA", mailbox)
1912+
send_command("GETQUOTA", quota_root)
18811913
clear_responses("QUOTA")
18821914
end
18831915
end
18841916

18851917
# Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1]
1886-
# along with the specified +mailbox+ and +quota+. If +quota+ is nil, then
1887-
# +quota+ will be unset for that mailbox. Typically one needs to be logged
1888-
# in as a server admin for this to work.
1918+
# along with the specified +quota_root+ and +storage_limit+. If
1919+
# +storage_limit+ is +nil+, resource limits are unset for that quota root.
1920+
# Otherwise, it sets the +STORAGE+ resource limit.
1921+
#
1922+
# Typically one needs to be logged in as a server admin for this to work.
1923+
#
1924+
# *NOTE:* Currently, Net::IMAP only supports setting +STORAGE+ quota limits.
18891925
#
18901926
# Related: #getquota, #getquotaroot
18911927
#
18921928
# ==== Capabilities
18931929
#
1894-
# The server's capabilities must include +QUOTA+
1895-
# [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1896-
def setquota(mailbox, quota)
1897-
if quota.nil?
1898-
data = '()'
1930+
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
1931+
# capability, or a capability prefixed with <tt>QUOTA=RES-*</tt>
1932+
# {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
1933+
# resource type.
1934+
def setquota(quota_root, storage_limit)
1935+
if storage_limit.nil?
1936+
list = '()'
18991937
else
1900-
data = '(STORAGE ' + quota.to_s + ')'
1938+
list = '(STORAGE ' + storage_limit.to_s + ')'
19011939
end
1902-
send_command("SETQUOTA", mailbox, RawData.new(data))
1940+
send_command("SETQUOTA", quota_root, RawData.new(list))
19031941
end
19041942

19051943
# 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)
20062044
# <tt>STATUS=SIZE</tt>
20072045
# {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html].
20082046
#
2009-
# +DELETED+ requires the server's capabilities to include +IMAP4rev2+.
2047+
# +DELETED+ must be supported when the server's capabilities includes
2048+
# +IMAP4rev2+.
2049+
# or <tt>QUOTA=RES-MESSAGES</tt>
2050+
# {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html].
20102051
#
20112052
# +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+
20122053
# {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].

lib/net/imap/response_data.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,14 @@ class ResponseText < Struct.new(:code, :text)
380380
# because the server doesn't allow deletion of mailboxes with children.
381381
# #data is +nil+.
382382
#
383+
# === <tt>QUOTA=RES-*</tt> response codes
384+
# See {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208.html#section-4.3].
385+
# * +OVERQUOTA+ (also in RFC5530[https://www.rfc-editor.org/rfc/rfc5530]),
386+
# with a tagged +NO+ response to an +APPEND+/+COPY+/+MOVE+ command when
387+
# the command would put the target mailbox over any quota, and with an
388+
# untagged +NO+ when a mailbox exceeds a soft quota (which may be caused
389+
# be external events). #data is +nil+.
390+
#
383391
# === +CONDSTORE+ extension
384392
# See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
385393
# * +NOMODSEQ+, when selecting a mailbox that does not support
@@ -463,14 +471,23 @@ class MailboxList < Struct.new(:attr, :delim, :name)
463471
# and MailboxQuota objects.
464472
#
465473
# == Required capability
474+
#
466475
# Requires +QUOTA+ [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]]
467-
# capability.
476+
# or <tt>QUOTA=RES-STORAGE</tt>
477+
# [RFC9208[https://www.rfc-editor.org/rfc/rfc9208]] capability.
468478
class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
469479
##
470480
# method: mailbox
471481
# :call-seq: mailbox -> string
472482
#
473-
# The mailbox with the associated quota.
483+
# The quota root with the associated quota.
484+
#
485+
# NOTE: this was mistakenly named "mailbox". But the quota root's name may
486+
# differ from the mailbox. A single quota root may cover multiple
487+
# mailboxes, and a single mailbox may be governed by multiple quota roots.
488+
489+
# The quota root with the associated quota.
490+
alias quota_root mailbox
474491

475492
##
476493
# method: usage
@@ -482,7 +499,7 @@ class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
482499
# method: quota
483500
# :call-seq: quota -> Integer
484501
#
485-
# Quota limit imposed on the mailbox.
502+
# Storage limit imposed on the mailbox.
486503
#
487504
end
488505

test/net/imap/test_imap_quota.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require "net/imap"
4+
require "test/unit"
5+
require_relative "fake_server"
6+
7+
class IMAPQuotaTest < Net::IMAP::TestCase
8+
include Net::IMAP::FakeServer::TestHelper
9+
10+
test "#setquota(quota_root, limit)" do
11+
with_fake_server do |server, imap|
12+
server.on "SETQUOTA", &:done_ok
13+
14+
# integer arg
15+
imap.setquota "INBOX", 512
16+
rcvd_cmd = server.commands.pop
17+
assert_equal "SETQUOTA", rcvd_cmd.name
18+
assert_equal "INBOX (STORAGE 512)", rcvd_cmd.args
19+
20+
# string arg
21+
imap.setquota "INBOX", "512"
22+
rcvd_cmd = server.commands.pop
23+
assert_equal "SETQUOTA", rcvd_cmd.name
24+
assert_equal "INBOX (STORAGE 512)", rcvd_cmd.args
25+
26+
# empty quota root, null limit
27+
imap.setquota "", nil
28+
rcvd_cmd = server.commands.pop
29+
assert_equal "SETQUOTA", rcvd_cmd.name
30+
assert_equal '"" ()', rcvd_cmd.args
31+
end
32+
end
33+
end

0 commit comments

Comments
 (0)