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