diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb index 4a80fb64..5fd6b40d 100644 --- a/lib/net/imap/response_parser.rb +++ b/lib/net/imap/response_parser.rb @@ -691,6 +691,8 @@ def response CRLF! EOF! resp + rescue SystemStackError + parse_error("response recursion too deep") end # RFC3501 & RFC9051: diff --git a/test/net/imap/test_response_parser.rb b/test/net/imap/test_response_parser.rb index eaa6ff24..fc296ea4 100644 --- a/test/net/imap/test_response_parser.rb +++ b/test/net/imap/test_response_parser.rb @@ -356,4 +356,64 @@ def assert_deprecated_copyuid_data_warning(check: true) assert_instance_of Net::IMAP::CopyUIDData, response.data.code.data end + def deeply_nested_bodystructure(depth) + leaf = '("TEXT" "PLAIN" NIL NIL NIL "7BIT" 1 1)' + depth.times.reduce(leaf) { |part, _| "(#{part} \"MIXED\")" } + end + + def deeply_nested_thread(depth) + depth.times.reduce("(1)") { |thread, _| "(#{thread}(1))" } + end + + def deeply_nested_extension_value(depth) + depth.times.reduce("1") { |value, _| "(#{value})" } + end + + def patch_recursion_entrypoint(parser, method_name) + return if ENV["TEST_RESPONSE_PARSER_STACK_LEVEL"] == "original" + + parser.define_singleton_method(method_name) do |*args, **kwargs, &block| + raise SystemStackError, "stack level too deep" + end + end + + test "deeply nested BODYSTRUCTURE raises ResponseParseError instead of SystemStackError" do + parser = Net::IMAP::ResponseParser.new + patch_recursion_entrypoint(parser, :body) + response = "* 1 FETCH (BODYSTRUCTURE #{deeply_nested_bodystructure(6000)})\r\n" + + error = assert_raise(Net::IMAP::ResponseParseError) do + parser.parse(response) + end + + assert_equal "response recursion too deep", error.message + assert_instance_of SystemStackError, error.cause + end + + test "deeply nested THREAD raises ResponseParseError instead of SystemStackError" do + parser = Net::IMAP::ResponseParser.new + patch_recursion_entrypoint(parser, :thread_data) + response = "* THREAD #{deeply_nested_thread(6000)}\r\n" + + error = assert_raise(Net::IMAP::ResponseParseError) do + parser.parse(response) + end + + assert_equal "response recursion too deep", error.message + assert_instance_of SystemStackError, error.cause + end + + test "deeply nested STATUS extension raises ResponseParseError instead of SystemStackError" do + parser = Net::IMAP::ResponseParser.new + patch_recursion_entrypoint(parser, :tagged_ext_val) + response = "* STATUS INBOX (X #{deeply_nested_extension_value(11_000)})\r\n" + + error = assert_raise(Net::IMAP::ResponseParseError) do + parser.parse(response) + end + + assert_equal "response recursion too deep", error.message + assert_instance_of SystemStackError, error.cause + end + end