Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ module Net
# +LITERAL-+, and +SPECIAL-USE+.</em>
#
# ==== RFC2087: +QUOTA+
# +NOTE:+ Only the +STORAGE+ quota resource type is currently supported.
# - Obsoleted by <tt>QUOTA=RES-*</tt> [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.
Expand Down Expand Up @@ -576,6 +579,16 @@ module Net
# See FetchData#emailid and FetchData#emailid.
# - Updates #status with support for the +MAILBOXID+ status attribute.
#
# ==== RFC9208: <tt>QUOTA=RES-*</tt>
# +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 <tt>"DELETED"</tt> and +DELETED-STORAGE+ attributes.
#
# ==== RFC9394: +PARTIAL+
# - Updates #search, #uid_search with the +PARTIAL+ return option which adds
# ESearchResult#partial return data.
Expand Down Expand Up @@ -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, <https://www.rfc-editor.org/info/rfc9208>.
# [QUOTA[https://www.rfc-editor.org/rfc/rfc2087]]::
# Myers, J., "IMAP4 QUOTA extension", RFC 2087, DOI 10.17487/RFC2087,
# January 1997, <https://www.rfc-editor.org/info/rfc2087>.
#
# <em>Note: obsoletes</em>
# RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
# <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
# *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, <https://www.rfc-editor.org/info/rfc2177>.
Expand Down Expand Up @@ -754,6 +766,11 @@ module Net
# Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
# RFC 8474, DOI 10.17487/RFC8474, September 2018,
# <https://www.rfc-editor.org/info/rfc8474>.
# [{QUOTA=RES-*}[https://www.rfc-editor.org/rfc/rfc9208]]::
# Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
# March 2022, <https://www.rfc-editor.org/info/rfc9208>.
#
# 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,
Expand All @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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 <tt>QUOTA=RES-*</tt>
# {[RFC9208]}[https://www.rfc-editor.org/rfc/rfc9208] for each supported
# resource type.
def getquotaroot(mailbox)
synchronize do
send_command("GETQUOTAROOT", mailbox)
Expand All @@ -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 <tt>QUOTA=RES-*</tt>
# {[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 <tt>QUOTA=RES-*</tt>
# {[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]
Expand Down Expand Up @@ -2006,7 +2044,10 @@ def lsub(refname, mailbox)
# <tt>STATUS=SIZE</tt>
# {[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 <tt>QUOTA=RES-MESSAGES</tt>
# {[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].
Expand Down
23 changes: 20 additions & 3 deletions lib/net/imap/response_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,14 @@ class ResponseText < Struct.new(:code, :text)
# because the server doesn't allow deletion of mailboxes with children.
# #data is +nil+.
#
# === <tt>QUOTA=RES-*</tt> 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
Expand Down Expand Up @@ -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 <tt>QUOTA=RES-STORAGE</tt>
# [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
Expand All @@ -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

Expand Down
33 changes: 33 additions & 0 deletions test/net/imap/test_imap_quota.rb
Original file line number Diff line number Diff line change
@@ -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