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
6 changes: 3 additions & 3 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2021,7 +2021,7 @@ def status(mailbox, attr)
#
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
# supported and the destination supports persistent UIDs, the server's
# response should include an +APPENDUID+ response code with UIDPlusData.
# response should include an +APPENDUID+ response code with AppendUIDData.
# This will report the UIDVALIDITY of the destination mailbox and the
# assigned UID of the appended message.
#
Expand Down Expand Up @@ -2778,7 +2778,7 @@ def uid_store(set, attr, flags, unchangedsince: nil)
#
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
# supported, the server's response should include a +COPYUID+ response code
# with UIDPlusData. This will report the UIDVALIDITY of the destination
# with CopyUIDData. This will report the UIDVALIDITY of the destination
# mailbox, the UID set of the source messages, and the assigned UID set of
# the moved messages.
#
Expand Down Expand Up @@ -2819,7 +2819,7 @@ def uid_copy(set, mailbox)
#
# If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
# supported, the server's response should include a +COPYUID+ response code
# with UIDPlusData. This will report the UIDVALIDITY of the destination
# with CopyUIDData. This will report the UIDVALIDITY of the destination
# mailbox, the UID set of the source messages, and the assigned UID set of
# the moved messages.
#
Expand Down
61 changes: 27 additions & 34 deletions lib/net/imap/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -360,39 +360,32 @@ def self.[](config)
#
# Alias for responses_without_block

# Whether ResponseParser should use the deprecated UIDPlusData or
# CopyUIDData for +COPYUID+ response codes, and UIDPlusData or
# AppendUIDData for +APPENDUID+ response codes.
# **NOTE:** <em>+UIDPlusData+ has been removed since +v0.6.0+, and this
# config option only affects deprecation warnings.
# This config option will be **removed** in +v0.7.0+.</em>
#
# UIDPlusData stores its data in arrays of numbers, which is vulnerable to
# a memory exhaustion denial of service attack from an untrusted or
# compromised server. Set this option to +false+ to completely block this
# vulnerability. Otherwise, parser_max_deprecated_uidplus_data_size
# mitigates this vulnerability.
# ResponseParser always returns CopyUIDData for +COPYUID+ response codes,
# and AppendUIDData for +APPENDUID+ response codes. Previously, this
# option determined when UIDPlusData would be returned instead.
#
# AppendUIDData and CopyUIDData are _mostly_ backward-compatible with
# UIDPlusData. Most applications should be able to upgrade with little
# or no changes.
# Parser support for +UIDPLUS+ added in +v0.3.2+.
#
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
# Config option added in +v0.4.19+ and +v0.5.6+.
#
# <em>(Config option added in +v0.4.19+ and +v0.5.6+.)</em>
# <em>UIDPlusData removed in +v0.6.0+.</em>
#
# <em>UIDPlusData will be removed in +v0.6+ and this config setting will
# be ignored.</em>
#
# ==== Valid options
# ==== Options
#
# [+true+ <em>(original default)</em>]
# ResponseParser only uses UIDPlusData.
# <em>Since v0.6.0:</em>
# Prints a deprecation warning when parsing +COPYUID+ or +APPENDUID+.
#
# [+:up_to_max_size+ <em>(default since +v0.5.6+)</em>]
# ResponseParser uses UIDPlusData when the +uid-set+ size is below
# parser_max_deprecated_uidplus_data_size. Above that size,
# ResponseParser uses AppendUIDData or CopyUIDData.
# <em>Since v0.6.0:</em>
# Prints a deprecation warning when parsing +COPYUID+ or +APPENDUID+.
#
# [+false+ <em>(planned default for +v0.6+)</em>]
# ResponseParser _only_ uses AppendUIDData and CopyUIDData.
# [+false+ <em>(default since +v0.6.0+)</em>]
# This is the only supported option <em>(since v0.6.0)</em>.
attr_accessor :parser_use_deprecated_uidplus_data, type: Enum[
true, :up_to_max_size, false
], defaults: {
Expand All @@ -401,22 +394,22 @@ def self.[](config)
0.6r => false,
}

# The maximum +uid-set+ size that ResponseParser will parse into
# deprecated UIDPlusData. This limit only applies when
# parser_use_deprecated_uidplus_data is not +false+.
# **NOTE:** <em>+UIDPlusData+ has been removed since +v0.6.0+, and this
# config option is ignored.
# This config option will be **removed** in +v0.7.0+.</em>
#
# <em>(Parser support for +UIDPLUS+ added in +v0.3.2+.)</em>
# ResponseParser always returns CopyUIDData for +COPYUID+ response codes,
# and AppendUIDData for +APPENDUID+ response codes. Previously, this
# option determined when UIDPlusData would be returned instead.
#
# <em>Support for limiting UIDPlusData to a maximum size was added in
# +v0.3.8+, +v0.4.19+, and +v0.5.6+.</em>
# Parser support for +UIDPLUS+ added in +v0.3.2+.
#
# <em>UIDPlusData will be removed in +v0.6+.</em>
# Support for limiting UIDPlusData to a maximum size was added in
# +v0.3.8+, +v0.4.19+, and +v0.5.6+.
#
# ==== Versioned Defaults
# <em>UIDPlusData was removed in +v0.6.0+.</em>
#
# Because this limit guards against a remote server causing catastrophic
# memory exhaustion, the versioned default (used by #load_defaults) also
# applies to versions without the feature.
# ==== Versioned Defaults
#
# * +0.3+ and prior: <tt>10,000</tt>
# * +0.4+: <tt>1,000</tt>
Expand Down
5 changes: 2 additions & 3 deletions lib/net/imap/response_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class IMAP < Protocol
autoload :FetchData, "#{__dir__}/fetch_data"
autoload :UIDFetchData, "#{__dir__}/fetch_data"
autoload :SearchResult, "#{__dir__}/search_result"
autoload :UIDPlusData, "#{__dir__}/uidplus_data"
autoload :AppendUIDData, "#{__dir__}/uidplus_data"
autoload :CopyUIDData, "#{__dir__}/uidplus_data"
autoload :VanishedData, "#{__dir__}/vanished_data"
Expand Down Expand Up @@ -260,8 +259,8 @@ class ResponseText < Struct.new(:code, :text)
#
# === +UIDPLUS+ extension
# See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3].
# * +APPENDUID+, #data is UIDPlusData. See IMAP#append.
# * +COPYUID+, #data is UIDPlusData. See IMAP#copy.
# * +APPENDUID+, #data is AppendUIDData. See IMAP#append.
# * +COPYUID+, #data is CopyUIDData. See IMAP#copy.
# * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select.
#
# === +SEARCHRES+ extension
Expand Down
21 changes: 8 additions & 13 deletions lib/net/imap/response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2017,24 +2017,19 @@ def resp_code_copy__data
CopyUID(validity, src_uids, dst_uids)
end

def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end

# TODO: remove this code in the v0.6.0 release
def DeprecatedUIDPlus(validity, src_uids = nil, dst_uids)
return unless config.parser_use_deprecated_uidplus_data
compact_uid_sets = [src_uids, dst_uids].compact
count = compact_uid_sets.map { _1.count_with_duplicates }.max
max = config.parser_max_deprecated_uidplus_data_size
if count <= max
src_uids &&= src_uids.each_ordered_number.to_a
dst_uids = dst_uids.each_ordered_number.to_a
UIDPlusData.new(validity, src_uids, dst_uids)
elsif config.parser_use_deprecated_uidplus_data != :up_to_max_size
parse_error("uid-set is too large: %d > %d", count, max)
end
warn("#{Config}#parser_use_deprecated_uidplus_data is ignored " \
"since v0.6.0. Disable this warning by setting " \
"config.parser_use_deprecated_uidplus_data = false.",
category: :deprecated, uplevel: 9)
nil
end

def AppendUID(...) DeprecatedUIDPlus(...) || AppendUIDData.new(...) end
def CopyUID(...) DeprecatedUIDPlus(...) || CopyUIDData.new(...) end

ADDRESS_REGEXP = /\G
\( (?: NIL | #{Patterns::QUOTED_rev2} ) # 1: NAME
\s (?: NIL | #{Patterns::QUOTED_rev2} ) # 2: ROUTE
Expand Down
65 changes: 2 additions & 63 deletions lib/net/imap/uidplus_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,8 @@
module Net
class IMAP < Protocol

# *NOTE:* <em>UIDPlusData is deprecated and will be removed in the +0.6.0+
# release.</em> To use AppendUIDData and CopyUIDData before +0.6.0+, set
# Config#parser_use_deprecated_uidplus_data to +false+.
#
# UIDPlusData represents the ResponseCode#data that accompanies the
# +APPENDUID+ and +COPYUID+ {response codes}[rdoc-ref:ResponseCode].
#
# A server that supports +UIDPLUS+ should send UIDPlusData in response to
# the append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy],
# move[rdoc-ref:Net::IMAP#move], {uid copy}[rdoc-ref:Net::IMAP#uid_copy],
# and {uid move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the
# destination mailbox reports +UIDNOTSTICKY+.
#
# Note that append[rdoc-ref:Net::IMAP#append], copy[rdoc-ref:Net::IMAP#copy]
# and {uid_copy}[rdoc-ref:Net::IMAP#uid_copy] return UIDPlusData in their
# TaggedResponse. But move[rdoc-ref:Net::IMAP#copy] and
# {uid_move}[rdoc-ref:Net::IMAP#uid_move] _should_ send UIDPlusData in an
# UntaggedResponse response before sending their TaggedResponse. However
# some servers do send UIDPlusData in the TaggedResponse for +MOVE+
# commands---this complies with the older +UIDPLUS+ specification but is
# discouraged by the +MOVE+ extension and disallowed by +IMAP4rev2+.
#
# == Required capability
# Requires either +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315]]
# or +IMAP4rev2+ capability.
#
class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids)
##
# method: uidvalidity
# :call-seq: uidvalidity -> nonzero uint32
#
# The UIDVALIDITY of the destination mailbox.

##
# method: source_uids
# :call-seq: source_uids -> nil or an array of nonzero uint32
#
# The UIDs of the copied or moved messages.
#
# Note:: Returns +nil+ for Net::IMAP#append.

##
# method: assigned_uids
# :call-seq: assigned_uids -> an array of nonzero uint32
#
# The newly assigned UIDs of the copied, moved, or appended messages.
#
# Note:: This always returns an array, even when it contains only one UID.

##
# :call-seq: uid_mapping -> nil or a hash
#
# Returns a hash mapping each source UID to the newly assigned destination
# UID.
#
# Note:: Returns +nil+ for Net::IMAP#append.
def uid_mapping
source_uids&.zip(assigned_uids)&.to_h
end
end

# >>>
# *NOTE:* <em>AppendUIDData will replace UIDPlusData for +APPENDUID+ in the
# *NOTE:* <em>AppendUIDData replaced UIDPlusData for +APPENDUID+ in the
# +0.6.0+ release.</em> To use AppendUIDData before +0.6.0+, set
# Config#parser_use_deprecated_uidplus_data to +false+.
#
Expand Down Expand Up @@ -109,7 +48,7 @@ def size
end

# >>>
# *NOTE:* <em>CopyUIDData will replace UIDPlusData for +COPYUID+ in the
# *NOTE:* <em>CopyUIDData replaced UIDPlusData for +COPYUID+ in the
# +0.6.0+ release.</em> To use CopyUIDData before +0.6.0+, set
# Config#parser_use_deprecated_uidplus_data to +false+.
#
Expand Down
79 changes: 55 additions & 24 deletions test/net/imap/test_response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,28 +201,40 @@ def test_fetch_binary_and_binary_size
end
end

def assert_deprecated_appenduid_data_warning
assert_warn(/#{__FILE__}.*warning.*parser_use_deprecated_uidplus_data is ignored/) do
result = yield
assert_instance_of Net::IMAP::AppendUIDData, result.data.code.data
result
end
end

test "APPENDUID with parser_use_deprecated_uidplus_data = true" do
parser = Net::IMAP::ResponseParser.new(config: {
parser_use_deprecated_uidplus_data: true,
parser_max_deprecated_uidplus_data_size: 10_000,
})
assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do
assert_deprecated_appenduid_data_warning do
parser.parse(
"A004 OK [APPENDUID 1 10000:20000,1] Done\r\n"
)
end
response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n")
response = assert_deprecated_appenduid_data_warning do
parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n")
end
uidplus = response.data.code.data
assert_equal 101, uidplus.assigned_uids.size
parser.config.parser_max_deprecated_uidplus_data_size = 100
assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do
assert_deprecated_appenduid_data_warning do
parser.parse(
"A004 OK [APPENDUID 1 100:200] Done\r\n"
)
end
response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n")
response = assert_deprecated_appenduid_data_warning do
parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n")
end
uidplus = response.data.code.data
assert_instance_of Net::IMAP::UIDPlusData, uidplus
assert_instance_of Net::IMAP::AppendUIDData, uidplus
assert_equal 100, uidplus.assigned_uids.size
end

Expand All @@ -231,9 +243,13 @@ def test_fetch_binary_and_binary_size
parser_use_deprecated_uidplus_data: :up_to_max_size,
parser_max_deprecated_uidplus_data_size: 100
})
response = parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n")
assert_instance_of Net::IMAP::UIDPlusData, response.data.code.data
response = parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n")
response = assert_deprecated_appenduid_data_warning do
parser.parse("A004 OK [APPENDUID 1 101:200] Done\r\n")
end
assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data
response = assert_deprecated_appenduid_data_warning do
parser.parse("A004 OK [APPENDUID 1 100:200] Done\r\n")
end
assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data
end

Expand All @@ -242,8 +258,10 @@ def test_fetch_binary_and_binary_size
parser_use_deprecated_uidplus_data: false,
parser_max_deprecated_uidplus_data_size: 10_000_000,
})
response = parser.parse("A004 OK [APPENDUID 1 10] Done\r\n")
assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data
assert_warn("") do
response = parser.parse("A004 OK [APPENDUID 1 10] Done\r\n")
assert_instance_of Net::IMAP::AppendUIDData, response.data.code.data
end
end

test "COPYUID with backwards ranges" do
Expand Down Expand Up @@ -276,29 +294,42 @@ def test_fetch_binary_and_binary_size
end
end

def assert_deprecated_copyuid_data_warning(check: true)
assert_warn(/#{__FILE__}.*warning.*parser_use_deprecated_uidplus_data is ignored/) do
result = yield
assert_instance_of Net::IMAP::CopyUIDData, result.data.code.data if check
result
end
end

test "COPYUID with parser_use_deprecated_uidplus_data = true" do
parser = Net::IMAP::ResponseParser.new(config: {
parser_use_deprecated_uidplus_data: true,
parser_max_deprecated_uidplus_data_size: 10_000,
})
assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do
parser.parse(
"A004 OK [copyUID 1 10000:20000,1 1:10001] Done\r\n"
)
assert_deprecated_copyuid_data_warning(check: false) do
assert_raise_with_message Net::IMAP::DataFormatError, /mismatched uid-set sizes/ do
parser.parse(
"A004 OK [copyUID 1 10000:20000,1 1:10001] Done\r\n"
)
end
end
response = assert_deprecated_copyuid_data_warning do
parser.parse("A004 OK [copyUID 1 100:200 1:101] Done\r\n")
end
response = parser.parse("A004 OK [copyUID 1 100:200 1:101] Done\r\n")
uidplus = response.data.code.data
assert_equal 101, uidplus.assigned_uids.size
assert_equal 101, uidplus.source_uids.size
parser.config.parser_max_deprecated_uidplus_data_size = 100
assert_raise_with_message Net::IMAP::ResponseParseError, /uid-set is too large/ do
assert_deprecated_copyuid_data_warning do
parser.parse(
"A004 OK [copyUID 1 100:200 1:101] Done\r\n"
)
end
response = parser.parse("A004 OK [copyUID 1 101:200 1:100] Done\r\n")
response = assert_deprecated_copyuid_data_warning do
parser.parse("A004 OK [copyUID 1 101:200 1:100] Done\r\n")
end
uidplus = response.data.code.data
assert_instance_of Net::IMAP::UIDPlusData, uidplus
assert_equal 100, uidplus.assigned_uids.size
assert_equal 100, uidplus.source_uids.size
end
Expand All @@ -308,12 +339,12 @@ def test_fetch_binary_and_binary_size
parser_use_deprecated_uidplus_data: :up_to_max_size,
parser_max_deprecated_uidplus_data_size: 100
})
response = parser.parse("A004 OK [COPYUID 1 101:200 1:100] Done\r\n")
copyuid = response.data.code.data
assert_instance_of Net::IMAP::UIDPlusData, copyuid
response = parser.parse("A004 OK [COPYUID 1 100:200 1:101] Done\r\n")
copyuid = response.data.code.data
assert_instance_of Net::IMAP::CopyUIDData, copyuid
assert_deprecated_copyuid_data_warning do
parser.parse("A004 OK [COPYUID 1 101:200 1:100] Done\r\n")
end
assert_deprecated_copyuid_data_warning do
parser.parse("A004 OK [COPYUID 1 100:200 1:101] Done\r\n")
end
end

test "COPYUID with parser_use_deprecated_uidplus_data = false" do
Expand Down
Loading