diff --git a/backport-0001-CVE-2025-43857.patch b/backport-0001-CVE-2025-43857.patch new file mode 100644 index 0000000000000000000000000000000000000000..dffef8e00e4d17d3862c76ebb8f0bc80e9999a62 --- /dev/null +++ b/backport-0001-CVE-2025-43857.patch @@ -0,0 +1,197 @@ +From 20c16a2eaec1dc6775675abbd8f3f2c412e7533f Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 19 Apr 2025 22:21:59 -0400 +Subject: [PATCH 16/19] =?UTF-8?q?=E2=9C=A8=20Limit=20max=20response=20size?= + =?UTF-8?q?=20to=20512MiB=20(hard-coded)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +_Please note:_ this only limits the size per response. It does _not_ +limit how many unhandled responses may be stored on the responses hash. +--- + lib/net/imap.rb | 33 ++++++++++++++++++++++ + lib/net/imap/response_reader.rb | 31 +++++++++++++++++++-- + test/net/imap/test_errors.rb | 40 +++++++++++++++++++++++++++ + test/net/imap/test_response_reader.rb | 23 +++++++++++++++ + 4 files changed, 125 insertions(+), 2 deletions(-) + create mode 100644 test/net/imap/test_errors.rb + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 3c6ec2b..d1f337f 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -3685,6 +3685,39 @@ module Net + class DataFormatError < Error + end + ++ # Error raised when the socket cannot be read, due to a configured limit. ++ class ResponseReadError < Error ++ end ++ ++ # Error raised when a response is larger than IMAP#max_response_size. ++ class ResponseTooLargeError < ResponseReadError ++ attr_reader :bytes_read, :literal_size ++ attr_reader :max_response_size ++ ++ def initialize(msg = nil, *args, ++ bytes_read: nil, ++ literal_size: nil, ++ max_response_size: nil, ++ **kwargs) ++ @bytes_read = bytes_read ++ @literal_size = literal_size ++ @max_response_size = max_response_size ++ msg ||= [ ++ "Response size", response_size_msg, "exceeds max_response_size", ++ max_response_size && "(#{max_response_size}B)", ++ ].compact.join(" ") ++ super(msg, *args, **kwargs) ++ end ++ ++ private ++ ++ def response_size_msg ++ if bytes_read && literal_size ++ "(#{bytes_read}B read + #{literal_size}B literal)" ++ end ++ end ++ end ++ + # Error raised when a response from the server is non-parseable. + class ResponseParseError < Error + end +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index 6a608b1..3c33dea 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -28,19 +28,46 @@ module Net + + attr_reader :buff, :literal_size + ++ def bytes_read = buff.bytesize ++ def empty? = buff.empty? ++ def done? = line_done? && !get_literal_size ++ def line_done? = buff.end_with?(CRLF) + def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end + + def read_line +- buff << (@sock.gets(CRLF) or throw :eof) ++ buff << (@sock.gets(CRLF, read_limit) or throw :eof) ++ max_response_remaining! unless line_done? + end + + def read_literal ++ # check before allocating memory for literal ++ max_response_remaining! + literal = String.new(capacity: literal_size) +- buff << (@sock.read(literal_size, literal) or throw :eof) ++ buff << (@sock.read(read_limit(literal_size), literal) or throw :eof) + ensure + @literal_size = nil + end + ++ def read_limit(limit = nil) ++ [limit, max_response_remaining!].compact.min ++ end ++ ++ def max_response_size = 512 << 20 # TODO: Config#max_response_size ++ def max_response_remaining = max_response_size &.- bytes_read ++ def response_too_large? = max_response_size &.< min_response_size ++ def min_response_size = bytes_read + min_response_remaining ++ ++ def min_response_remaining ++ empty? ? 3 : done? ? 0 : (literal_size || 0) + 2 ++ end ++ ++ def max_response_remaining! ++ return max_response_remaining unless response_too_large? ++ raise ResponseTooLargeError.new( ++ max_response_size:, bytes_read:, literal_size:, ++ ) ++ end ++ + end + end + end +diff --git a/test/net/imap/test_errors.rb b/test/net/imap/test_errors.rb +new file mode 100644 +index 0000000..a6a7cb0 +--- /dev/null ++++ b/test/net/imap/test_errors.rb +@@ -0,0 +1,40 @@ ++# frozen_string_literal: true ++ ++require "net/imap" ++require "test/unit" ++ ++class IMAPErrorsTest < Test::Unit::TestCase ++ ++ test "ResponseTooLargeError" do ++ err = Net::IMAP::ResponseTooLargeError.new ++ assert_nil err.bytes_read ++ assert_nil err.literal_size ++ assert_nil err.max_response_size ++ ++ err = Net::IMAP::ResponseTooLargeError.new("manually set message") ++ assert_equal "manually set message", err.message ++ assert_nil err.bytes_read ++ assert_nil err.literal_size ++ assert_nil err.max_response_size ++ ++ err = Net::IMAP::ResponseTooLargeError.new(max_response_size: 1024) ++ assert_equal "Response size exceeds max_response_size (1024B)", err.message ++ assert_nil err.bytes_read ++ assert_nil err.literal_size ++ assert_equal 1024, err.max_response_size ++ ++ err = Net::IMAP::ResponseTooLargeError.new(bytes_read: 1200, ++ max_response_size: 1200) ++ assert_equal 1200, err.bytes_read ++ assert_equal "Response size exceeds max_response_size (1200B)", err.message ++ ++ err = Net::IMAP::ResponseTooLargeError.new(bytes_read: 800, ++ literal_size: 1000, ++ max_response_size: 1200) ++ assert_equal 800, err.bytes_read ++ assert_equal 1000, err.literal_size ++ assert_equal("Response size (800B read + 1000B literal) " \ ++ "exceeds max_response_size (1200B)", err.message) ++ end ++ ++end +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 9a8c63d..5a1491b 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -44,4 +44,27 @@ class ResponseReaderTest < Test::Unit::TestCase + assert_equal "", rcvr.read_response_buffer.to_str + end + ++ class LimitedResponseReader < Net::IMAP::ResponseReader ++ attr_reader :max_response_size ++ def initialize(*args, max_response_size:) ++ super(*args) ++ @max_response_size = max_response_size ++ end ++ end ++ ++ test "#read_response_buffer with max_response_size" do ++ client = FakeClient.new ++ max_response_size = 10 ++ under = "+ 3456\r\n" ++ exact = "+ 345678\r\n" ++ over = "+ 3456789\r\n" ++ io = StringIO.new([under, exact, over].join) ++ rcvr = LimitedResponseReader.new(client, io, max_response_size:) ++ assert_equal under, rcvr.read_response_buffer.to_str ++ assert_equal exact, rcvr.read_response_buffer.to_str ++ assert_raise Net::IMAP::ResponseTooLargeError do ++ rcvr.read_response_buffer ++ end ++ end ++ + end +-- +2.27.0 + diff --git a/backport-0002-CVE-2025-43857.patch b/backport-0002-CVE-2025-43857.patch new file mode 100644 index 0000000000000000000000000000000000000000..ad323c8509170120b85ef0f45a8a2972846d53b5 --- /dev/null +++ b/backport-0002-CVE-2025-43857.patch @@ -0,0 +1,176 @@ +From 5431e16b779254ad7b2786e4367bc04328418264 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sun, 20 Apr 2025 17:38:43 -0400 +Subject: [PATCH 17/19] =?UTF-8?q?=E2=9C=A8=20Make=20max=5Fresponse=5Fsize?= + =?UTF-8?q?=20configurable=20[=F0=9F=9A=A7=20partial]?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Note that this cherry-picked commit is missing key paits that are +incompatible with net-imap before 0.4. I'm keeping the conflict +resolution here, and the updates for net-imap 0.3 in the next commit. + +------ + +Though it would be useful to also have limits based on response type and +what commands are currently running, that's out of scope for now. + +_Please note:_ this only limits the size per response. It does _not_ +limit how many unhandled responses may be stored on the responses hash. +--- + lib/net/imap.rb | 11 ++++ + lib/net/imap/response_reader.rb | 2 +- + test/net/imap/test_imap_max_response_size.rb | 67 ++++++++++++++++++++ + test/net/imap/test_response_reader.rb | 13 +--- + 4 files changed, 82 insertions(+), 11 deletions(-) + create mode 100644 test/net/imap/test_imap_max_response_size.rb + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index d1f337f..f1456d5 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -322,6 +322,17 @@ module Net + alias default_ssl_port default_tls_port + end + ++ ## ++ # :attr_accessor: max_response_size ++ # ++ # The maximum allowed server response size, in bytes. ++ # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. ++ ++ # :stopdoc: ++ def max_response_size; config.max_response_size end ++ def max_response_size=(val) config.max_response_size = val end ++ # :startdoc: ++ + # Disconnects from the server. + def disconnect + return if disconnected? +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index 3c33dea..bcf3601 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -52,7 +52,7 @@ module Net + [limit, max_response_remaining!].compact.min + end + +- def max_response_size = 512 << 20 # TODO: Config#max_response_size ++ def max_response_size = client.max_response_size + def max_response_remaining = max_response_size &.- bytes_read + def response_too_large? = max_response_size &.< min_response_size + def min_response_size = bytes_read + min_response_remaining +diff --git a/test/net/imap/test_imap_max_response_size.rb b/test/net/imap/test_imap_max_response_size.rb +new file mode 100644 +index 0000000..3751d0b +--- /dev/null ++++ b/test/net/imap/test_imap_max_response_size.rb +@@ -0,0 +1,67 @@ ++# frozen_string_literal: true ++ ++require "net/imap" ++require "test/unit" ++require_relative "fake_server" ++ ++class IMAPMaxResponseSizeTest < Test::Unit::TestCase ++ include Net::IMAP::FakeServer::TestHelper ++ ++ def setup ++ Net::IMAP.config.reset ++ @do_not_reverse_lookup = Socket.do_not_reverse_lookup ++ Socket.do_not_reverse_lookup = true ++ @threads = [] ++ end ++ ++ def teardown ++ if !@threads.empty? ++ assert_join_threads(@threads) ++ end ++ ensure ++ Socket.do_not_reverse_lookup = @do_not_reverse_lookup ++ end ++ ++ test "#max_response_size reading literals" do ++ with_fake_server(preauth: true) do |server, imap| ++ imap.max_response_size = 12_345 + 30 ++ server.on("NOOP") do |resp| ++ resp.untagged("1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")") ++ resp.done_ok ++ end ++ imap.noop ++ assert_equal "a" * 12_345, imap.responses("FETCH").first.message ++ end ++ end ++ ++ test "#max_response_size closes connection for too long line" do ++ Net::IMAP.config.max_response_size = 10 ++ run_fake_server_in_thread(preauth: false, ignore_io_error: true) do |server| ++ assert_raise_with_message( ++ Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ ++ ) do ++ with_client("localhost", port: server.port) do ++ fail "should not get here (greeting longer than max_response_size)" ++ end ++ end ++ end ++ end ++ ++ test "#max_response_size closes connection for too long literal" do ++ Net::IMAP.config.max_response_size = 1<<20 ++ with_fake_server(preauth: false, ignore_io_error: true) do |server, client| ++ client.max_response_size = 50 ++ server.on("NOOP") do |resp| ++ resp.untagged("1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")") ++ end ++ assert_raise_with_message( ++ Net::IMAP::ResponseTooLargeError, ++ /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ ++ ) do ++ client.noop ++ fail "should not get here (FETCH literal longer than max_response_size)" ++ end ++ end ++ end ++ ++end +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 5a1491b..6b58d55 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -6,6 +6,7 @@ require "test/unit" + + class ResponseReaderTest < Test::Unit::TestCase + class FakeClient ++ def max_response_size = config.max_response_size + end + + def literal(str) "{#{str.bytesize}}\r\n#{str}" end +@@ -44,22 +45,14 @@ class ResponseReaderTest < Test::Unit::TestCase + assert_equal "", rcvr.read_response_buffer.to_str + end + +- class LimitedResponseReader < Net::IMAP::ResponseReader +- attr_reader :max_response_size +- def initialize(*args, max_response_size:) +- super(*args) +- @max_response_size = max_response_size +- end +- end +- + test "#read_response_buffer with max_response_size" do + client = FakeClient.new +- max_response_size = 10 ++ client.config.max_response_size = 10 + under = "+ 3456\r\n" + exact = "+ 345678\r\n" + over = "+ 3456789\r\n" + io = StringIO.new([under, exact, over].join) +- rcvr = LimitedResponseReader.new(client, io, max_response_size:) ++ rcvr = Net::IMAP::ResponseReader.new(client, io) + assert_equal under, rcvr.read_response_buffer.to_str + assert_equal exact, rcvr.read_response_buffer.to_str + assert_raise Net::IMAP::ResponseTooLargeError do +-- +2.27.0 + diff --git a/backport-0003-CVE-2025-43857.patch b/backport-0003-CVE-2025-43857.patch new file mode 100644 index 0000000000000000000000000000000000000000..c2f0a8f4fa762dd501be206eea8f08d861736e11 --- /dev/null +++ b/backport-0003-CVE-2025-43857.patch @@ -0,0 +1,75 @@ +From 450bb4d757d9b9f2866ebd6e1efdd5d94a311b05 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sun, 20 Apr 2025 21:01:48 -0400 +Subject: [PATCH 18/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20compatibilit?= + =?UTF-8?q?y=20with=20ruby=202.7?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + lib/net/imap/response_reader.rb | 20 +++++++++++--------- + test/net/imap/test_response_reader.rb | 2 +- + 2 files changed, 12 insertions(+), 10 deletions(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index bcf3601..fd7561f 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -28,10 +28,10 @@ module Net + + attr_reader :buff, :literal_size + +- def bytes_read = buff.bytesize +- def empty? = buff.empty? +- def done? = line_done? && !get_literal_size +- def line_done? = buff.end_with?(CRLF) ++ def bytes_read; buff.bytesize end ++ def empty?; buff.empty? end ++ def done?; line_done? && !get_literal_size end ++ def line_done?; buff.end_with?(CRLF) end + def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end + + def read_line +@@ -52,10 +52,10 @@ module Net + [limit, max_response_remaining!].compact.min + end + +- def max_response_size = client.max_response_size +- def max_response_remaining = max_response_size &.- bytes_read +- def response_too_large? = max_response_size &.< min_response_size +- def min_response_size = bytes_read + min_response_remaining ++ def max_response_size; client.max_response_size end ++ def max_response_remaining; max_response_size &.- bytes_read end ++ def response_too_large?; max_response_size &.< min_response_size end ++ def min_response_size; bytes_read + min_response_remaining end + + def min_response_remaining + empty? ? 3 : done? ? 0 : (literal_size || 0) + 2 +@@ -64,7 +64,9 @@ module Net + def max_response_remaining! + return max_response_remaining unless response_too_large? + raise ResponseTooLargeError.new( +- max_response_size:, bytes_read:, literal_size:, ++ max_response_size: max_response_size, ++ bytes_read: bytes_read, ++ literal_size: literal_size, + ) + end + +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 6b58d55..716922d 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -6,7 +6,7 @@ require "test/unit" + + class ResponseReaderTest < Test::Unit::TestCase + class FakeClient +- def max_response_size = config.max_response_size ++ def max_response_size; config.max_response_size end + end + + def literal(str) "{#{str.bytesize}}\r\n#{str}" end +-- +2.27.0 + diff --git a/backport-0004-CVE-2025-43857.patch b/backport-0004-CVE-2025-43857.patch new file mode 100644 index 0000000000000000000000000000000000000000..904da21d4f755acfada1f90602ca3b562c3cce10 --- /dev/null +++ b/backport-0004-CVE-2025-43857.patch @@ -0,0 +1,274 @@ +From 673cab874374670fca850dc0e16ddc62ee3b8a68 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 9 Apr 2025 09:54:51 -0400 +Subject: [PATCH 19/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20to=20not-ima?= + =?UTF-8?q?p=200.3=20and=20ruby=202.6?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For the net-imap v0.3 backport, two major changes were needed: +* the tests needed to be almost completely rewritten because FakeServer + was added for v0.4. +* `max_response_size` needed to be on Net::IMAP directly, because Config + was added for v0.4. +--- + lib/net/imap.rb | 48 ++++++-- + test/net/imap/test_imap_max_response_size.rb | 109 +++++++++++++------ + test/net/imap/test_response_reader.rb | 4 +- + 3 files changed, 117 insertions(+), 44 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index f1456d5..100b956 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -231,6 +231,40 @@ module Net + # it raises a Net::OpenTimeout exception. The default value is 30 seconds. + attr_reader :open_timeout + ++ # The maximum allowed server response size. When +nil+, there is no limit ++ # on response size. ++ # ++ # The default value is _unlimited_ (after +v0.5.8+, the default is 512 MiB). ++ # A _much_ lower value should be used with untrusted servers (for example, ++ # when connecting to a user-provided hostname). When using a lower limit, ++ # message bodies should be fetched in chunks rather than all at once. ++ # ++ # Please Note: this only limits the size per response. It does ++ # not prevent a flood of individual responses and it does not limit how ++ # many unhandled responses may be stored on the responses hash. See ++ # Net::IMAP@Unbounded+memory+use. ++ # ++ # Socket reads are limited to the maximum remaining bytes for the current ++ # response: max_response_size minus the bytes that have already been read. ++ # When the limit is reached, or reading a +literal+ _would_ go over the ++ # limit, ResponseTooLargeError is raised and the connection is closed. ++ # See also #socket_read_limit. ++ # ++ # Note that changes will not take effect immediately, because the receiver ++ # thread may already be waiting for the next response using the previous ++ # value. Net::IMAP#noop can force a response and enforce the new setting ++ # immediately. ++ # ++ # ==== Versioned Defaults ++ # ++ # Net::IMAP#max_response_size was added in +v0.2.5+ and +v0.3.9+ as an ++ # attr_accessor, and in +v0.4.20+ and +v0.5.7+ as a delegator to a config ++ # attribute. ++ # ++ # * original: +nil+ (no limit) ++ # * +0.5+: 512 MiB ++ attr_accessor :max_response_size ++ + # The thread to receive exceptions. + attr_accessor :client_thread + +@@ -322,17 +356,6 @@ module Net + alias default_ssl_port default_tls_port + end + +- ## +- # :attr_accessor: max_response_size +- # +- # The maximum allowed server response size, in bytes. +- # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. +- +- # :stopdoc: +- def max_response_size; config.max_response_size end +- def max_response_size=(val) config.max_response_size = val end +- # :startdoc: +- + # Disconnects from the server. + def disconnect + return if disconnected? +@@ -1077,6 +1100,7 @@ module Net + # If options[:ssl] is a hash, it's passed to + # OpenSSL::SSL::SSLContext#set_params as parameters. + # open_timeout:: Seconds to wait until a connection is opened ++ # max_response_size:: See #max_response_size. + # + # The most common errors are: + # +@@ -1106,6 +1130,7 @@ module Net + @tag_prefix = "RUBY" + @tagno = 0 + @open_timeout = options[:open_timeout] || 30 ++ @max_response_size = options[:max_response_size] + @parser = ResponseParser.new + @sock = tcp_socket(@host, @port) + @reader = ResponseReader.new(self, @sock) +@@ -3717,6 +3742,7 @@ module Net + "Response size", response_size_msg, "exceeds max_response_size", + max_response_size && "(#{max_response_size}B)", + ].compact.join(" ") ++ return super(msg, *args) if kwargs.empty? # ruby 2.6 compatibility + super(msg, *args, **kwargs) + end + +diff --git a/test/net/imap/test_imap_max_response_size.rb b/test/net/imap/test_imap_max_response_size.rb +index 3751d0b..7ec554c 100644 +--- a/test/net/imap/test_imap_max_response_size.rb ++++ b/test/net/imap/test_imap_max_response_size.rb +@@ -2,13 +2,10 @@ + + require "net/imap" + require "test/unit" +-require_relative "fake_server" + + class IMAPMaxResponseSizeTest < Test::Unit::TestCase +- include Net::IMAP::FakeServer::TestHelper + + def setup +- Net::IMAP.config.reset + @do_not_reverse_lookup = Socket.do_not_reverse_lookup + Socket.do_not_reverse_lookup = true + @threads = [] +@@ -23,45 +20,95 @@ class IMAPMaxResponseSizeTest < Test::Unit::TestCase + end + + test "#max_response_size reading literals" do +- with_fake_server(preauth: true) do |server, imap| ++ _, port = with_server_socket do |sock| ++ sock.gets # => NOOP ++ sock.print("RUBY0001 OK done\r\n") ++ sock.gets # => NOOP ++ sock.print("* 1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")\r\n") ++ sock.print("RUBY0002 OK done\r\n") ++ "RUBY0003" ++ end ++ Timeout.timeout(5) do ++ imap = Net::IMAP.new("localhost", port: port, max_response_size: 640 << 20) ++ assert_equal 640 << 20, imap.max_response_size + imap.max_response_size = 12_345 + 30 +- server.on("NOOP") do |resp| +- resp.untagged("1 FETCH (BODY[] {12345}\r\n" + "a" * 12_345 + ")") +- resp.done_ok +- end +- imap.noop +- assert_equal "a" * 12_345, imap.responses("FETCH").first.message ++ assert_equal 12_345 + 30, imap.max_response_size ++ imap.noop # to reset the get_response limit ++ imap.noop # to send the FETCH ++ assert_equal "a" * 12_345, imap.responses["FETCH"].first.attr["BODY[]"] ++ ensure ++ imap.logout rescue nil ++ imap.disconnect rescue nil + end + end + + test "#max_response_size closes connection for too long line" do +- Net::IMAP.config.max_response_size = 10 +- run_fake_server_in_thread(preauth: false, ignore_io_error: true) do |server| +- assert_raise_with_message( +- Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ +- ) do +- with_client("localhost", port: server.port) do +- fail "should not get here (greeting longer than max_response_size)" +- end +- end ++ _, port = with_server_socket do |sock| ++ sock.gets or next # => never called ++ fail "client disconnects first" ++ end ++ assert_raise_with_message( ++ Net::IMAP::ResponseTooLargeError, /exceeds max_response_size .*\b10B\b/ ++ ) do ++ Net::IMAP.new("localhost", port: port, max_response_size: 10) ++ fail "should not get here (greeting longer than max_response_size)" + end + end + + test "#max_response_size closes connection for too long literal" do +- Net::IMAP.config.max_response_size = 1<<20 +- with_fake_server(preauth: false, ignore_io_error: true) do |server, client| +- client.max_response_size = 50 +- server.on("NOOP") do |resp| +- resp.untagged("1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")") +- end +- assert_raise_with_message( +- Net::IMAP::ResponseTooLargeError, +- /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ +- ) do +- client.noop +- fail "should not get here (FETCH literal longer than max_response_size)" ++ _, port = with_server_socket(ignore_io_error: true) do |sock| ++ sock.gets # => NOOP ++ sock.print "* 1 FETCH (BODY[] {1000}\r\n" + "a" * 1000 + ")\r\n" ++ sock.print("RUBY0001 OK done\r\n") ++ end ++ client = Net::IMAP.new("localhost", port: port, max_response_size: 1000) ++ assert_equal 1000, client.max_response_size ++ client.max_response_size = 50 ++ assert_equal 50, client.max_response_size ++ assert_raise_with_message( ++ Net::IMAP::ResponseTooLargeError, ++ /\d+B read \+ 1000B literal.* exceeds max_response_size .*\b50B\b/ ++ ) do ++ client.noop ++ fail "should not get here (FETCH literal longer than max_response_size)" ++ end ++ end ++ ++ def with_server_socket(ignore_io_error: false) ++ server = create_tcp_server ++ port = server.addr[1] ++ start_server do ++ Timeout.timeout(5) do ++ sock = server.accept ++ sock.print("* OK connection established\r\n") ++ logout_tag = yield sock if block_given? ++ sock.gets # => LOGOUT ++ sock.print("* BYE terminating connection\r\n") ++ sock.print("#{logout_tag} OK LOGOUT completed\r\n") if logout_tag ++ rescue IOError, EOFError, Errno::ECONNABORTED, Errno::ECONNRESET, ++ Errno::EPIPE, Errno::ETIMEDOUT ++ ignore_io_error or raise ++ ensure ++ sock.close rescue nil ++ server.close rescue nil + end + end ++ return server, port ++ end ++ ++ def start_server ++ th = Thread.new do ++ yield ++ end ++ @threads << th ++ sleep 0.1 until th.stop? + end + ++ def create_tcp_server ++ return TCPServer.new(server_addr, 0) ++ end ++ ++ def server_addr ++ Addrinfo.tcp("localhost", 0).ip_address ++ end + end +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 716922d..d2c1c11 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -6,7 +6,7 @@ require "test/unit" + + class ResponseReaderTest < Test::Unit::TestCase + class FakeClient +- def max_response_size; config.max_response_size end ++ attr_accessor :max_response_size + end + + def literal(str) "{#{str.bytesize}}\r\n#{str}" end +@@ -47,7 +47,7 @@ class ResponseReaderTest < Test::Unit::TestCase + + test "#read_response_buffer with max_response_size" do + client = FakeClient.new +- client.config.max_response_size = 10 ++ client.max_response_size = 10 + under = "+ 3456\r\n" + exact = "+ 345678\r\n" + over = "+ 3456789\r\n" +-- +2.27.0 + diff --git a/backport-Add-response_handlers-kwarg-to-Net-IMAP.new.patch b/backport-Add-response_handlers-kwarg-to-Net-IMAP.new.patch new file mode 100644 index 0000000000000000000000000000000000000000..6a1bb9f1f86826103ee974b3273d5590440c96f6 --- /dev/null +++ b/backport-Add-response_handlers-kwarg-to-Net-IMAP.new.patch @@ -0,0 +1,96 @@ +From 527568a8cab481c9d0ff19e95640bc62ffd007e7 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 22 Mar 2025 14:53:00 -0400 +Subject: [PATCH 10/19] =?UTF-8?q?=E2=9C=A8=20Add=20`response=5Fhandlers`?= + =?UTF-8?q?=20kwarg=20to=20Net::IMAP.new?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This ensures every server response is handled, including the greeting. +--- + lib/net/imap.rb | 7 ++++ + test/net/imap/test_imap_response_handlers.rb | 37 ++++++++++++++++++++ + 2 files changed, 44 insertions(+) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index f7b04af..3c6ec2b 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -907,6 +907,11 @@ module Net + # end + # } + # ++ # Response handlers can also be added when the client is created before the ++ # receiver thread is started, by the +response_handlers+ argument to ::new. ++ # This ensures every server response is handled, including the #greeting. ++ # ++ # Related: #remove_response_handler, #response_handlers + def add_response_handler(handler = nil, &block) + raise ArgumentError, "two Procs are passed" if handler && block + @response_handlers.push(block || handler) +@@ -1103,6 +1108,7 @@ module Net + @responses = Hash.new([].freeze) + @tagged_responses = {} + @response_handlers = [] ++ options[:response_handlers]&.each do |h| add_response_handler(h) end + @tagged_response_arrival = new_cond + @continued_command_tag = nil + @continuation_request_arrival = new_cond +@@ -1119,6 +1125,7 @@ module Net + if @greeting.name == "BYE" + raise ByeResponseError, @greeting + end ++ @response_handlers.each do |handler| handler.call(@greeting) end + + @client_thread = Thread.current + @receiver_thread = Thread.start { +diff --git a/test/net/imap/test_imap_response_handlers.rb b/test/net/imap/test_imap_response_handlers.rb +index 900c2eb..f513d86 100644 +--- a/test/net/imap/test_imap_response_handlers.rb ++++ b/test/net/imap/test_imap_response_handlers.rb +@@ -50,4 +50,41 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + end + end + ++ test "::new with response_handlers kwarg" do ++ greeting = nil ++ expunges = [] ++ alerts = [] ++ untagged = 0 ++ handler0 = ->{ greeting ||= _1 } ++ handler1 = ->{ alerts << _1.data.text if _1 in {data: {code: {name: "ALERT"}}} } ++ handler2 = ->{ expunges << _1.data if _1 in {name: "EXPUNGE"} } ++ handler3 = ->{ untagged += 1 if _1.is_a?(Net::IMAP::UntaggedResponse) } ++ response_handlers = [handler0, handler1, handler2, handler3] ++ ++ run_fake_server_in_thread do |server| ++ port = server.port ++ imap = Net::IMAP.new("localhost", port:, response_handlers:) ++ assert_equal response_handlers, imap.response_handlers ++ refute_same response_handlers, imap.response_handlers ++ ++ # handler0 recieved the greeting and handler3 counted it ++ assert_equal imap.greeting, greeting ++ assert_equal 1, untagged ++ ++ server.on("NOOP") do |resp| ++ resp.untagged "1 EXPUNGE" ++ resp.untagged "1 EXPUNGE" ++ resp.untagged "OK [ALERT] The first alert." ++ resp.done_ok "[ALERT] Did you see the alert?" ++ end ++ ++ imap.noop ++ assert_equal 4, untagged ++ assert_equal [1, 1], expunges # from handler2 ++ assert_equal ["The first alert.", "Did you see the alert?"], alerts ++ ensure ++ imap&.logout! unless imap&.disconnected? ++ end ++ end ++ + end +-- +2.27.0 + diff --git a/backport-Add-tests-for-add_response_handler.patch b/backport-Add-tests-for-add_response_handler.patch new file mode 100644 index 0000000000000000000000000000000000000000..abf02dcd84e70945e506dc76ce26e63dc6a2e275 --- /dev/null +++ b/backport-Add-tests-for-add_response_handler.patch @@ -0,0 +1,77 @@ +From 3d7271044d5f66272a6a44c54dfb1f5d596a8489 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 22 Mar 2025 15:33:02 -0400 +Subject: [PATCH 08/19] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20#add=5Fre?= + =?UTF-8?q?sponse=5Fhandler?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +There weren't any response_handler tests at all, prior to this! +--- + test/net/imap/test_imap_response_handlers.rb | 53 ++++++++++++++++++++ + 1 file changed, 53 insertions(+) + create mode 100644 test/net/imap/test_imap_response_handlers.rb + +diff --git a/test/net/imap/test_imap_response_handlers.rb b/test/net/imap/test_imap_response_handlers.rb +new file mode 100644 +index 0000000..900c2eb +--- /dev/null ++++ b/test/net/imap/test_imap_response_handlers.rb +@@ -0,0 +1,53 @@ ++# frozen_string_literal: true ++ ++require "net/imap" ++require "test/unit" ++require_relative "fake_server" ++ ++class IMAPResponseHandlersTest < Test::Unit::TestCase ++ include Net::IMAP::FakeServer::TestHelper ++ ++ def setup ++ Net::IMAP.config.reset ++ @do_not_reverse_lookup = Socket.do_not_reverse_lookup ++ Socket.do_not_reverse_lookup = true ++ @threads = [] ++ end ++ ++ def teardown ++ if !@threads.empty? ++ assert_join_threads(@threads) ++ end ++ ensure ++ Socket.do_not_reverse_lookup = @do_not_reverse_lookup ++ end ++ ++ test "#add_response_handlers" do ++ responses = [] ++ with_fake_server do |server, imap| ++ server.on("NOOP") do |resp| ++ 3.times do resp.untagged("#{_1 + 1} EXPUNGE") end ++ resp.done_ok ++ end ++ ++ assert_equal 0, imap.response_handlers.length ++ imap.add_response_handler do responses << [:block, _1] end ++ assert_equal 1, imap.response_handlers.length ++ imap.add_response_handler(->{ responses << [:proc, _1] }) ++ assert_equal 2, imap.response_handlers.length ++ ++ imap.noop ++ assert_pattern do ++ responses => [ ++ [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 1]], ++ [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 1]], ++ [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 2]], ++ [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 2]], ++ [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 3]], ++ [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 3]], ++ ] ++ end ++ end ++ end ++ ++end +-- +2.27.0 + diff --git a/backport-Allocate-string-literals-with-specific-capacity.patch b/backport-Allocate-string-literals-with-specific-capacity.patch new file mode 100644 index 0000000000000000000000000000000000000000..c51de774aea97e0340e4c4957abdee84f88bacff --- /dev/null +++ b/backport-Allocate-string-literals-with-specific-capacity.patch @@ -0,0 +1,32 @@ +From 549510e0041414466d8e2e9a11217ec236d52d33 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 25 Mar 2025 13:44:50 -0400 +Subject: [PATCH 04/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Allocate=20string=20?= + =?UTF-8?q?literals=20with=20specific=20capacity?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +We know exactly how much memory we're going to need, so we can allocate +this up-front, and save a few malloc/memcpy calls on larger literals. +--- + lib/net/imap.rb | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index b08268d..9eb707d 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1315,7 +1315,8 @@ module Net + end + + def get_response_literal(buff, literal_size) +- literal = @sock.read(literal_size) or return ++ literal = String.new(capacity: literal_size) ++ @sock.read(literal_size, literal) or return + buff << literal + end + +-- +2.27.0 + diff --git a/backport-Anchor-literal-regexp-to-the-end-of-the-buffer.patch b/backport-Anchor-literal-regexp-to-the-end-of-the-buffer.patch new file mode 100644 index 0000000000000000000000000000000000000000..a389c4780d77b50e1dcb57f793db36034c8fb479 --- /dev/null +++ b/backport-Anchor-literal-regexp-to-the-end-of-the-buffer.patch @@ -0,0 +1,33 @@ +From 936ecde3bc2e0446194bf76c231ac562a708c370 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 25 Mar 2025 13:08:21 -0400 +Subject: [PATCH 02/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Anchor=20literal=20r?= + =?UTF-8?q?egexp=20to=20the=20end=20of=20the=20buffer?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This also allows us to check against the concatenated buffer, rather +than the smaller line buffer. That distinction doesn't really matter +now, since we always read an entire line at once. But it will matter if +we read partial lines. +--- + lib/net/imap.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 831e56e..712a29d 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1300,7 +1300,7 @@ module Net + s = @sock.gets(CRLF) + break unless s + buff.concat(s) +- if /\{(\d+)\}\r\n/n =~ s ++ if /\{(\d+)\}\r\n\z/n =~ buff + s = @sock.read($1.to_i) + buff.concat(s) + else +-- +2.27.0 + diff --git a/backport-Explicitly-throw-eof-for-EOF-in-get_response.patch b/backport-Explicitly-throw-eof-for-EOF-in-get_response.patch new file mode 100644 index 0000000000000000000000000000000000000000..549a6df6af39a8482ba75398a144adcbac8bdb42 --- /dev/null +++ b/backport-Explicitly-throw-eof-for-EOF-in-get_response.patch @@ -0,0 +1,57 @@ +From f9248a9689c169f6fcd0242b829020e01b97d67c Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 25 Mar 2025 14:00:40 -0400 +Subject: [PATCH 06/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Explicitly=20"throw?= + =?UTF-8?q?=20:eof"=20for=20EOF=20in=20get=5Fresponse?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This feels a lot more self-documenting than returning nil then breaking +when nil is returned. Also, it lets me refactor the return values for +the get_response_line/get_response_literal methods, or throw from even +deeper in the stack. +--- + lib/net/imap.rb | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 855548b..0b2d826 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1296,10 +1296,12 @@ module Net + + def get_response + buff = String.new +- while true +- get_response_line(buff) or break +- break unless /\{(\d+)\}\r\n\z/n =~ buff +- get_response_literal(buff, $1.to_i) or break ++ catch :eof do ++ while true ++ get_response_line(buff) ++ break unless /\{(\d+)\}\r\n\z/n =~ buff ++ get_response_literal(buff, $1.to_i) ++ end + end + return nil if buff.length == 0 + $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug +@@ -1307,13 +1309,13 @@ module Net + end + + def get_response_line(buff) +- line = @sock.gets(CRLF) or return ++ line = @sock.gets(CRLF) or throw :eof + buff << line + end + + def get_response_literal(buff, literal_size) + literal = String.new(capacity: literal_size) +- @sock.read(literal_size, literal) or return ++ @sock.read(literal_size, literal) or throw :eof + buff << literal + end + +-- +2.27.0 + diff --git a/backport-Extract-ResponseReader-from-get_response.patch b/backport-Extract-ResponseReader-from-get_response.patch new file mode 100644 index 0000000000000000000000000000000000000000..91bd3c778aaa419cede5fec6bd29ce7b61af0c6c --- /dev/null +++ b/backport-Extract-ResponseReader-from-get_response.patch @@ -0,0 +1,184 @@ +From 570bd632e61df1084e9923c8e1c4d1810e59ef10 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 2 Apr 2025 20:50:15 -0400 +Subject: [PATCH 07/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20ResponseRe?= + =?UTF-8?q?ader=20from=20get=5Fresponse?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +It's nice to extract a little bit of the complexity from the core +`Net::IMAP` class. But my primary motivation was so that I could +directly test this code quickly and in isolation from needing to +simulate a full IMAP connection. +--- + lib/net/imap.rb | 24 +++---------- + lib/net/imap/response_reader.rb | 38 +++++++++++++++++++++ + test/net/imap/test_response_reader.rb | 49 +++++++++++++++++++++++++++ + 3 files changed, 92 insertions(+), 19 deletions(-) + create mode 100644 lib/net/imap/response_reader.rb + create mode 100644 test/net/imap/test_response_reader.rb + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 9875c74..f7b04af 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -203,6 +203,8 @@ module Net + class IMAP < Protocol + VERSION = "0.1.1" + ++ autoload :ResponseReader, File.expand_path("imap/response_reader", __dir__) ++ + include MonitorMixin + if defined?(OpenSSL::SSL) + include OpenSSL +@@ -1090,6 +1092,7 @@ module Net + @open_timeout = options[:open_timeout] || 30 + @parser = ResponseParser.new + @sock = tcp_socket(@host, @port) ++ @reader = ResponseReader.new(self, @sock) + begin + if options[:ssl] + start_tls_session(options[:ssl]) +@@ -1230,30 +1233,12 @@ module Net + end + + def get_response +- buff = String.new +- catch :eof do +- while true +- get_response_line(buff) +- break unless /\{(\d+)\}\r\n\z/n =~ buff +- get_response_literal(buff, $1.to_i) +- end +- end ++ buff = @reader.read_response_buffer + return nil if buff.length == 0 + $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug + @parser.parse(buff) + end + +- def get_response_line(buff) +- line = @sock.gets(CRLF) or throw :eof +- buff << line +- end +- +- def get_response_literal(buff, literal_size) +- literal = String.new(capacity: literal_size) +- @sock.read(literal_size, literal) or throw :eof +- buff << literal +- end +- + ############################# + + def record_response(name, data) +@@ -1541,6 +1526,7 @@ module Net + context.verify_callback = VerifyCallbackProc + end + @sock = SSLSocket.new(@sock, context) ++ @reader = ResponseReader.new(self, @sock) + @sock.sync_close = true + @sock.hostname = @host if @sock.respond_to? :hostname= + ssl_socket_connect(@sock, @open_timeout) +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +new file mode 100644 +index 0000000..57770e3 +--- /dev/null ++++ b/lib/net/imap/response_reader.rb +@@ -0,0 +1,38 @@ ++# frozen_string_literal: true ++ ++module Net ++ class IMAP ++ # See https://www.rfc-editor.org/rfc/rfc9051#section-2.2.2 ++ class ResponseReader # :nodoc: ++ attr_reader :client ++ ++ def initialize(client, sock) ++ @client, @sock = client, sock ++ end ++ ++ def read_response_buffer ++ buff = String.new ++ catch :eof do ++ while true ++ read_line(buff) ++ break unless /\{(\d+)\}\r\n\z/n =~ buff ++ read_literal(buff, $1.to_i) ++ end ++ end ++ buff ++ end ++ ++ private ++ ++ def read_line(buff) ++ buff << (@sock.gets(CRLF) or throw :eof) ++ end ++ ++ def read_literal(buff, literal_size) ++ literal = String.new(capacity: literal_size) ++ buff << (@sock.read(literal_size, literal) or throw :eof) ++ end ++ ++ end ++ end ++end +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +new file mode 100644 +index 0000000..c24f126 +--- /dev/null ++++ b/test/net/imap/test_response_reader.rb +@@ -0,0 +1,49 @@ ++# frozen_string_literal: true ++ ++require "net/imap" ++require "stringio" ++require "test/unit" ++ ++class ResponseReaderTest < Test::Unit::TestCase ++ def setup ++ Net::IMAP.config.reset ++ end ++ ++ class FakeClient ++ def config = @config ||= Net::IMAP.config.new ++ end ++ ++ def literal(str) = "{#{str.bytesize}}\r\n#{str}" ++ ++ test "#read_response_buffer" do ++ client = FakeClient.new ++ aaaaaaaaa = "a" * (20 << 10) ++ many_crs = "\r" * 1000 ++ many_crlfs = "\r\n" * 500 ++ simple = "* OK greeting\r\n" ++ long_line = "tag ok #{aaaaaaaaa} #{aaaaaaaaa}\r\n" ++ literal_aaaa = "* fake #{literal aaaaaaaaa}\r\n" ++ literal_crlf = "tag ok #{literal many_crlfs} #{literal many_crlfs}\r\n" ++ illegal_crs = "tag ok #{many_crs} #{many_crs}\r\n" ++ illegal_lfs = "tag ok #{literal "\r"}\n#{literal "\r"}\n\r\n" ++ io = StringIO.new([ ++ simple, ++ long_line, ++ literal_aaaa, ++ literal_crlf, ++ illegal_crs, ++ illegal_lfs, ++ simple, ++ ].join) ++ rcvr = Net::IMAP::ResponseReader.new(client, io) ++ assert_equal simple, rcvr.read_response_buffer.to_str ++ assert_equal long_line, rcvr.read_response_buffer.to_str ++ assert_equal literal_aaaa, rcvr.read_response_buffer.to_str ++ assert_equal literal_crlf, rcvr.read_response_buffer.to_str ++ assert_equal illegal_crs, rcvr.read_response_buffer.to_str ++ assert_equal illegal_lfs, rcvr.read_response_buffer.to_str ++ assert_equal simple, rcvr.read_response_buffer.to_str ++ assert_equal "", rcvr.read_response_buffer.to_str ++ end ++ ++end +-- +2.27.0 + diff --git a/backport-Extract-line-and-literal-parts-of-get_response.patch b/backport-Extract-line-and-literal-parts-of-get_response.patch new file mode 100644 index 0000000000000000000000000000000000000000..017a507ceac817830ef037926115412ef7237a18 --- /dev/null +++ b/backport-Extract-line-and-literal-parts-of-get_response.patch @@ -0,0 +1,56 @@ +From 2b3fa22e6ae5f64f5dd9ae83aaf7e3c4d878bd4f Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 25 Mar 2025 12:45:23 -0400 +Subject: [PATCH 03/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20line=20and?= + =?UTF-8?q?=20literal=20parts=20of=20get=5Fresponse?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +IMO, this refactoring makes `get_response` much easier to understand. +Which will be useful, because I'm about to complicate it. 😉 +--- + lib/net/imap.rb | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 712a29d..b08268d 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1297,12 +1297,9 @@ module Net + def get_response + buff = String.new + while true +- s = @sock.gets(CRLF) +- break unless s +- buff.concat(s) ++ get_response_line(buff) or break + if /\{(\d+)\}\r\n\z/n =~ buff +- s = @sock.read($1.to_i) +- buff.concat(s) ++ get_response_literal(buff, $1.to_i) or break + else + break + end +@@ -1312,6 +1309,18 @@ module Net + @parser.parse(buff) + end + ++ def get_response_line(buff) ++ line = @sock.gets(CRLF) or return ++ buff << line ++ end ++ ++ def get_response_literal(buff, literal_size) ++ literal = @sock.read(literal_size) or return ++ buff << literal ++ end ++ ++ ############################# ++ + def record_response(name, data) + unless @responses.has_key?(name) + @responses[name] = [] +-- +2.27.0 + diff --git a/backport-Fix-backport-compatibility-with-ruby-2.7-2.patch b/backport-Fix-backport-compatibility-with-ruby-2.7-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..7ddd655da7c3a15b6994ffcd749c9687980177a5 --- /dev/null +++ b/backport-Fix-backport-compatibility-with-ruby-2.7-2.patch @@ -0,0 +1,29 @@ +From 335872f4620132673ff0b096a19b574d66f13210 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 19 Apr 2025 21:30:58 -0400 +Subject: [PATCH 15/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20compatibilit?= + =?UTF-8?q?y=20with=20ruby=202.7?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + lib/net/imap/response_reader.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index 7d3a536..6a608b1 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -28,7 +28,7 @@ module Net + + attr_reader :buff, :literal_size + +- def get_literal_size = /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i ++ def get_literal_size; /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i end + + def read_line + buff << (@sock.gets(CRLF) or throw :eof) +-- +2.27.0 + diff --git a/backport-Fix-backport-compatibility-with-ruby-2.7.patch b/backport-Fix-backport-compatibility-with-ruby-2.7.patch new file mode 100644 index 0000000000000000000000000000000000000000..3d808cd8b0ac50e200ffb21ca88d922847dd338a --- /dev/null +++ b/backport-Fix-backport-compatibility-with-ruby-2.7.patch @@ -0,0 +1,33 @@ +From 202df6e9ba622f3a7984d42c1e1251be12ff1084 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 19 Apr 2025 17:39:14 -0400 +Subject: [PATCH 09/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20compatibilit?= + =?UTF-8?q?y=20with=20ruby=202.7?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + test/net/imap/test_response_reader.rb | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index c24f126..319dece 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -10,10 +10,10 @@ class ResponseReaderTest < Test::Unit::TestCase + end + + class FakeClient +- def config = @config ||= Net::IMAP.config.new ++ def config; @config ||= Net::IMAP.config.new end + end + +- def literal(str) = "{#{str.bytesize}}\r\n#{str}" ++ def literal(str) "{#{str.bytesize}}\r\n#{str}" end + + test "#read_response_buffer" do + client = FakeClient.new +-- +2.27.0 + diff --git a/backport-Fix-backport-for-net-imap-v0.3.patch b/backport-Fix-backport-for-net-imap-v0.3.patch new file mode 100644 index 0000000000000000000000000000000000000000..abf366eb6068c99a195735e9a5fad4942b7e3cc0 --- /dev/null +++ b/backport-Fix-backport-for-net-imap-v0.3.patch @@ -0,0 +1,34 @@ +From 7a200056848795874dab476e4f6b36137d453224 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sun, 20 Apr 2025 14:27:54 -0400 +Subject: [PATCH 11/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20for=20net-im?= + =?UTF-8?q?ap=20v0.3?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +`Net::IMAP::Config` was introduced by `net-imap` v0.4. +--- + test/net/imap/test_response_reader.rb | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 319dece..28bd857 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -5,12 +5,7 @@ require "stringio" + require "test/unit" + + class ResponseReaderTest < Test::Unit::TestCase +- def setup +- Net::IMAP.config.reset +- end +- + class FakeClient +- def config; @config ||= Net::IMAP.config.new end + end + + def literal(str) "{#{str.bytesize}}\r\n#{str}" end +-- +2.27.0 + diff --git a/backport-Fix-backport-to-net-imap-0.3-and-ruby-2.6.patch b/backport-Fix-backport-to-net-imap-0.3-and-ruby-2.6.patch new file mode 100644 index 0000000000000000000000000000000000000000..042c9f5f6ea0102afb5cb8a5a09a6e6cad6d1320 --- /dev/null +++ b/backport-Fix-backport-to-net-imap-0.3-and-ruby-2.6.patch @@ -0,0 +1,163 @@ +From 15b6a65e655da6e2f7240e30fbaddd7a15b3b7a1 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Sat, 22 Mar 2025 15:33:02 -0400 +Subject: [PATCH 14/19] =?UTF-8?q?=E2=9C=85=20Fix=20backport=20to=20net-ima?= + =?UTF-8?q?p=200.3=20and=20ruby=202.6?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +FakeServer was introduced by v0.4, so the tests needed to be rewritten +without it. + +And ruby 2.6 doesn't support numbered params or "...". +--- + test/net/imap/test_imap_response_handlers.rb | 86 +++++++++++++++----- + 1 file changed, 64 insertions(+), 22 deletions(-) + +diff --git a/test/net/imap/test_imap_response_handlers.rb b/test/net/imap/test_imap_response_handlers.rb +index 7e79cf6..3786f24 100644 +--- a/test/net/imap/test_imap_response_handlers.rb ++++ b/test/net/imap/test_imap_response_handlers.rb +@@ -2,13 +2,10 @@ + + require "net/imap" + require "test/unit" +-require_relative "fake_server" + + class IMAPResponseHandlersTest < Test::Unit::TestCase +- include Net::IMAP::FakeServer::TestHelper + + def setup +- Net::IMAP.config.reset + @do_not_reverse_lookup = Socket.do_not_reverse_lookup + Socket.do_not_reverse_lookup = true + @threads = [] +@@ -23,17 +20,32 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + end + + test "#add_response_handlers" do +- responses = [] +- with_fake_server do |server, imap| +- server.on("NOOP") do |resp| +- 3.times do resp.untagged("#{_1 + 1} EXPUNGE") end +- resp.done_ok ++ server = create_tcp_server ++ port = server.addr[1] ++ start_server do ++ sock = server.accept ++ Timeout.timeout(5) do ++ sock.print("* OK connection established\r\n") ++ sock.gets # => NOOP ++ sock.print("* 1 EXPUNGE\r\n") ++ sock.print("* 2 EXPUNGE\r\n") ++ sock.print("* 3 EXPUNGE\r\n") ++ sock.print("RUBY0001 OK NOOP completed\r\n") ++ sock.gets # => LOGOUT ++ sock.print("* BYE terminating connection\r\n") ++ sock.print("RUBY0002 OK LOGOUT completed\r\n") ++ ensure ++ sock.close ++ server.close + end +- ++ end ++ begin ++ responses = [] ++ imap = Net::IMAP.new(server_addr, port: port) + assert_equal 0, imap.response_handlers.length +- imap.add_response_handler do responses << [:block, _1] end ++ imap.add_response_handler do |r| responses << [:block, r] end + assert_equal 1, imap.response_handlers.length +- imap.add_response_handler(->{ responses << [:proc, _1] }) ++ imap.add_response_handler(->(r) { responses << [:proc, r] }) + assert_equal 2, imap.response_handlers.length + + imap.noop +@@ -48,6 +60,9 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + [:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 3], + [:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 3], + ], responses ++ ensure ++ imap&.logout ++ imap&.disconnect + end + end + +@@ -56,14 +71,32 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + expunges = [] + alerts = [] + untagged = 0 +- handler0 = ->{ greeting ||= _1 } ++ handler0 = ->(r) { greeting ||= r } + handler1 = ->(r) { alerts << r.data.text if r.data.code.name == "ALERT" rescue nil } + handler2 = ->(r) { expunges << r.data if r.name == "EXPUNGE" } + handler3 = ->(r) { untagged += 1 if r.is_a?(Net::IMAP::UntaggedResponse) } + response_handlers = [handler0, handler1, handler2, handler3] + +- run_fake_server_in_thread do |server| +- port = server.port ++ server = create_tcp_server ++ port = server.addr[1] ++ start_server do ++ sock = server.accept ++ Timeout.timeout(5) do ++ sock.print("* OK connection established\r\n") ++ sock.gets # => NOOP ++ sock.print("* 1 EXPUNGE\r\n") ++ sock.print("* 1 EXPUNGE\r\n") ++ sock.print("* OK [ALERT] The first alert.\r\n") ++ sock.print("RUBY0001 OK [ALERT] Did you see the alert?\r\n") ++ sock.gets # => LOGOUT ++ sock.print("* BYE terminating connection\r\n") ++ sock.print("RUBY0002 OK LOGOUT completed\r\n") ++ ensure ++ sock.close ++ server.close ++ end ++ end ++ begin + imap = Net::IMAP.new("localhost", port: port, + response_handlers: response_handlers) + assert_equal response_handlers, imap.response_handlers +@@ -73,20 +106,29 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + assert_equal imap.greeting, greeting + assert_equal 1, untagged + +- server.on("NOOP") do |resp| +- resp.untagged "1 EXPUNGE" +- resp.untagged "1 EXPUNGE" +- resp.untagged "OK [ALERT] The first alert." +- resp.done_ok "[ALERT] Did you see the alert?" +- end +- + imap.noop + assert_equal 4, untagged + assert_equal [1, 1], expunges # from handler2 + assert_equal ["The first alert.", "Did you see the alert?"], alerts + ensure +- imap&.logout! unless imap&.disconnected? ++ imap&.logout ++ imap&.disconnect + end + end + ++ def start_server ++ th = Thread.new do ++ yield ++ end ++ @threads << th ++ sleep 0.1 until th.stop? ++ end ++ ++ def create_tcp_server ++ return TCPServer.new(server_addr, 0) ++ end ++ ++ def server_addr ++ Addrinfo.tcp("localhost", 0).ip_address ++ end + end +-- +2.27.0 + diff --git a/backport-Fix-ruby-2.7-compatability-in-backported-tests.patch b/backport-Fix-ruby-2.7-compatability-in-backported-tests.patch new file mode 100644 index 0000000000000000000000000000000000000000..95c40724cc8a00af2d419901ee0b24534823c9eb --- /dev/null +++ b/backport-Fix-ruby-2.7-compatability-in-backported-tests.patch @@ -0,0 +1,68 @@ +From b87b2883f61354da88b2f863d630e7072c5641de Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Wed, 16 Apr 2025 23:11:24 -0400 +Subject: [PATCH 12/19] =?UTF-8?q?=E2=9C=85=20Fix=20ruby=202.7=20compatabil?= + =?UTF-8?q?ity=20in=20backported=20tests?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + test/net/imap/test_imap_response_handlers.rb | 30 +++++++++++--------- + 1 file changed, 16 insertions(+), 14 deletions(-) + +diff --git a/test/net/imap/test_imap_response_handlers.rb b/test/net/imap/test_imap_response_handlers.rb +index f513d86..7e79cf6 100644 +--- a/test/net/imap/test_imap_response_handlers.rb ++++ b/test/net/imap/test_imap_response_handlers.rb +@@ -37,16 +37,17 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + assert_equal 2, imap.response_handlers.length + + imap.noop +- assert_pattern do +- responses => [ +- [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 1]], +- [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 1]], +- [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 2]], +- [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 2]], +- [:block, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 3]], +- [:proc, Net::IMAP::UntaggedResponse[name: "EXPUNGE", data: 3]], +- ] +- end ++ responses = responses[0, 6].map {|which, resp| ++ [which, resp.class, resp.name, resp.data] ++ } ++ assert_equal [ ++ [:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 1], ++ [:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 1], ++ [:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 2], ++ [:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 2], ++ [:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 3], ++ [:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 3], ++ ], responses + end + end + +@@ -56,14 +57,15 @@ class IMAPResponseHandlersTest < Test::Unit::TestCase + alerts = [] + untagged = 0 + handler0 = ->{ greeting ||= _1 } +- handler1 = ->{ alerts << _1.data.text if _1 in {data: {code: {name: "ALERT"}}} } +- handler2 = ->{ expunges << _1.data if _1 in {name: "EXPUNGE"} } +- handler3 = ->{ untagged += 1 if _1.is_a?(Net::IMAP::UntaggedResponse) } ++ handler1 = ->(r) { alerts << r.data.text if r.data.code.name == "ALERT" rescue nil } ++ handler2 = ->(r) { expunges << r.data if r.name == "EXPUNGE" } ++ handler3 = ->(r) { untagged += 1 if r.is_a?(Net::IMAP::UntaggedResponse) } + response_handlers = [handler0, handler1, handler2, handler3] + + run_fake_server_in_thread do |server| + port = server.port +- imap = Net::IMAP.new("localhost", port:, response_handlers:) ++ imap = Net::IMAP.new("localhost", port: port, ++ response_handlers: response_handlers) + assert_equal response_handlers, imap.response_handlers + refute_same response_handlers, imap.response_handlers + +-- +2.27.0 + diff --git a/backport-Reformat-get_response-debug-trace-printing.patch b/backport-Reformat-get_response-debug-trace-printing.patch new file mode 100644 index 0000000000000000000000000000000000000000..c6567e99a6844b6020ff442f8ece88f7a23e9777 --- /dev/null +++ b/backport-Reformat-get_response-debug-trace-printing.patch @@ -0,0 +1,33 @@ +From 5a7828e97f74a563d8b1adc497cbb83487e3e0c8 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Mon, 24 Mar 2025 17:35:10 -0400 +Subject: [PATCH 01/19] =?UTF-8?q?=F0=9F=8E=A8=20Reformat=20get=5Fresponse?= + =?UTF-8?q?=20debug=20trace=20printing?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + lib/net/imap.rb | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 17f3148..831e56e 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1308,10 +1308,8 @@ module Net + end + end + return nil if buff.length == 0 +- if @@debug +- $stderr.print(buff.gsub(/^/n, "S: ")) +- end +- return @parser.parse(buff) ++ $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug ++ @parser.parse(buff) + end + + def record_response(name, data) +-- +2.27.0 + diff --git a/backport-Save-ResponseReader-ivars-buff-literal_size.patch b/backport-Save-ResponseReader-ivars-buff-literal_size.patch new file mode 100644 index 0000000000000000000000000000000000000000..320cbb259fc7c547a710256871116943ed2a3254 --- /dev/null +++ b/backport-Save-ResponseReader-ivars-buff-literal_size.patch @@ -0,0 +1,94 @@ +From 64c0b51e9cc17ddab42c5fd6f19b1ca916322ca5 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Mon, 14 Apr 2025 09:23:58 -0400 +Subject: [PATCH 13/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Save=20ResponseReade?= + =?UTF-8?q?r=20ivars:=20@buff=20&=20@literal=5Fsize?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This avoids the need to pass these to every method that uses them. +That's not a big deal now, but it simplifies the next few changes. + +Also added a missing test for empty literals: "{0}\r\n" +--- + lib/net/imap/response_reader.rb | 20 ++++++++++++++------ + test/net/imap/test_response_reader.rb | 3 +++ + 2 files changed, 17 insertions(+), 6 deletions(-) + +diff --git a/lib/net/imap/response_reader.rb b/lib/net/imap/response_reader.rb +index 57770e3..7d3a536 100644 +--- a/lib/net/imap/response_reader.rb ++++ b/lib/net/imap/response_reader.rb +@@ -11,26 +11,34 @@ module Net + end + + def read_response_buffer +- buff = String.new ++ @buff = String.new + catch :eof do + while true +- read_line(buff) +- break unless /\{(\d+)\}\r\n\z/n =~ buff +- read_literal(buff, $1.to_i) ++ read_line ++ break unless (@literal_size = get_literal_size) ++ read_literal + end + end + buff ++ ensure ++ @buff = nil + end + + private + +- def read_line(buff) ++ attr_reader :buff, :literal_size ++ ++ def get_literal_size = /\{(\d+)\}\r\n\z/n =~ buff && $1.to_i ++ ++ def read_line + buff << (@sock.gets(CRLF) or throw :eof) + end + +- def read_literal(buff, literal_size) ++ def read_literal + literal = String.new(capacity: literal_size) + buff << (@sock.read(literal_size, literal) or throw :eof) ++ ensure ++ @literal_size = nil + end + + end +diff --git a/test/net/imap/test_response_reader.rb b/test/net/imap/test_response_reader.rb +index 28bd857..9a8c63d 100644 +--- a/test/net/imap/test_response_reader.rb ++++ b/test/net/imap/test_response_reader.rb +@@ -19,6 +19,7 @@ class ResponseReaderTest < Test::Unit::TestCase + long_line = "tag ok #{aaaaaaaaa} #{aaaaaaaaa}\r\n" + literal_aaaa = "* fake #{literal aaaaaaaaa}\r\n" + literal_crlf = "tag ok #{literal many_crlfs} #{literal many_crlfs}\r\n" ++ zero_literal = "tag ok #{literal ""} #{literal ""}\r\n" + illegal_crs = "tag ok #{many_crs} #{many_crs}\r\n" + illegal_lfs = "tag ok #{literal "\r"}\n#{literal "\r"}\n\r\n" + io = StringIO.new([ +@@ -26,6 +27,7 @@ class ResponseReaderTest < Test::Unit::TestCase + long_line, + literal_aaaa, + literal_crlf, ++ zero_literal, + illegal_crs, + illegal_lfs, + simple, +@@ -35,6 +37,7 @@ class ResponseReaderTest < Test::Unit::TestCase + assert_equal long_line, rcvr.read_response_buffer.to_str + assert_equal literal_aaaa, rcvr.read_response_buffer.to_str + assert_equal literal_crlf, rcvr.read_response_buffer.to_str ++ assert_equal zero_literal, rcvr.read_response_buffer.to_str + assert_equal illegal_crs, rcvr.read_response_buffer.to_str + assert_equal illegal_lfs, rcvr.read_response_buffer.to_str + assert_equal simple, rcvr.read_response_buffer.to_str +-- +2.27.0 + diff --git a/backport-Simplify-get_response-loop-further.patch b/backport-Simplify-get_response-loop-further.patch new file mode 100644 index 0000000000000000000000000000000000000000..e2ca6b63ac0ca49df11b2e6c60a26c7a04a8df93 --- /dev/null +++ b/backport-Simplify-get_response-loop-further.patch @@ -0,0 +1,34 @@ +From acfedcf9a7f2f6e4da271c11a98044125fd459c3 Mon Sep 17 00:00:00 2001 +From: nick evans +Date: Tue, 25 Mar 2025 12:46:59 -0400 +Subject: [PATCH 05/19] =?UTF-8?q?=F0=9F=8E=A8=20Simplify=20get=5Fresponse?= + =?UTF-8?q?=20loop=20further?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + lib/net/imap.rb | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/lib/net/imap.rb b/lib/net/imap.rb +index 9eb707d..855548b 100644 +--- a/lib/net/imap.rb ++++ b/lib/net/imap.rb +@@ -1298,11 +1298,8 @@ module Net + buff = String.new + while true + get_response_line(buff) or break +- if /\{(\d+)\}\r\n\z/n =~ buff +- get_response_literal(buff, $1.to_i) or break +- else +- break +- end ++ break unless /\{(\d+)\}\r\n\z/n =~ buff ++ get_response_literal(buff, $1.to_i) or break + end + return nil if buff.length == 0 + $stderr.print(buff.gsub(/^/n, "S: ")) if @@debug +-- +2.27.0 + diff --git a/ruby.spec b/ruby.spec index ff7063a3d6114b4092d13622d3402b119becc5f3..72aa40a7f13d64688655d1be4e5a80891d0a5822 100644 --- a/ruby.spec +++ b/ruby.spec @@ -33,7 +33,7 @@ Name: ruby Version: %{ruby_version} -Release: 142 +Release: 143 Summary: Object-oriented scripting language interpreter License: (Ruby or BSD) and Public Domain and MIT and CC0 and zlib and UCD URL: https://www.ruby-lang.org/en/ @@ -210,6 +210,25 @@ Patch6038: backport-CVE-2025-27219.patch Patch6039: backport-CVE-2025-27220.patch Patch6040: backport-0001-CVE-2025-27221.patch Patch6041: backport-0002-CVE-2025-27221.patch +Patch6042: backport-Reformat-get_response-debug-trace-printing.patch +Patch6043: backport-Anchor-literal-regexp-to-the-end-of-the-buffer.patch +Patch6044: backport-Extract-line-and-literal-parts-of-get_response.patch +Patch6045: backport-Allocate-string-literals-with-specific-capacity.patch +Patch6046: backport-Simplify-get_response-loop-further.patch +Patch6047: backport-Explicitly-throw-eof-for-EOF-in-get_response.patch +Patch6048: backport-Extract-ResponseReader-from-get_response.patch +Patch6049: backport-Add-tests-for-add_response_handler.patch +Patch6050: backport-Fix-backport-compatibility-with-ruby-2.7.patch +Patch6051: backport-Add-response_handlers-kwarg-to-Net-IMAP.new.patch +Patch6052: backport-Fix-backport-for-net-imap-v0.3.patch +Patch6053: backport-Fix-ruby-2.7-compatability-in-backported-tests.patch +Patch6054: backport-Save-ResponseReader-ivars-buff-literal_size.patch +Patch6055: backport-Fix-backport-to-net-imap-0.3-and-ruby-2.6.patch +Patch6056: backport-Fix-backport-compatibility-with-ruby-2.7-2.patch +Patch6057: backport-0001-CVE-2025-43857.patch +Patch6058: backport-0002-CVE-2025-43857.patch +Patch6059: backport-0003-CVE-2025-43857.patch +Patch6060: backport-0004-CVE-2025-43857.patch Provides: %{name}-libs = %{version}-%{release} Obsoletes: %{name}-libs < %{version}-%{release} @@ -1208,6 +1227,9 @@ make runruby TESTRUN_SCRIPT=%{SOURCE13} %doc %{gem_dir}/gems/typeprof-%{typeprof_version}/testbed %changelog +* Thu Jun 05 2025 shixuantong - 3.0.3-143 +- fix CVE-2025-43857 + * Fri Feb 28 2025 shixuantong - 3.0.3-142 - fix CVE-2025-27219 CVE-2025-27220 CVE-2025-27221