diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb index 9a5749b5..d0a5fc51 100644 --- a/lib/net/imap/command_data.rb +++ b/lib/net/imap/command_data.rb @@ -85,15 +85,23 @@ def send_binary_literal(*, **) = send_literal(*, **, binary: true) # `non_sync` is an optional tri-state flag: # * `true` -> Force non-synchronizing `LITERAL+`/`LITERAL-` behavior. - # TODO: raise or warn when capabilities don't allow non_sync. + # NOTE: raises DataFormatError when server doesn't support + # non-synchronizing literal, or literal is too large for LITERAL-. # * `false` -> Force normal synchronizing literal behavior. # * `nil` -> (default) Currently behaves like `false` (will be dynamic). def send_literal(str, tag = nil, binary: false, non_sync: nil) + bytesize = str.bytesize synchronize do - non_sync = non_sync_literal?(str.bytesize) if non_sync.nil? + if non_sync && !non_sync_literal_allowed?(bytesize) + # TODO: check in Printer, so we don't need to close the connection. + @sock.close + raise DataFormatError, "Connection closed: " \ + "Cannot send non-synchronizing literal without known server support" + end + non_sync = non_sync_literal?(bytesize) if non_sync.nil? prefix = "~" if binary plus = "+" if non_sync - put_string("#{prefix}{#{str.bytesize}#{plus}}\r\n") + put_string("#{prefix}{#{bytesize}#{plus}}\r\n") if non_sync put_string(str) return @@ -113,12 +121,19 @@ def send_literal(str, tag = nil, binary: false, non_sync: nil) end def non_sync_literal?(bytesize) - capabilities_cached? && - bytesize <= config.max_non_synchronizing_literal && - (capable?("LITERAL+") || - bytesize <= 4096 && (capable?("IMAP4rev2") || capable?("LITERAL-"))) + bytesize <= config.max_non_synchronizing_literal \ + && non_sync_literal_allowed?(bytesize) + end + + def non_sync_literal_allowed?(bytesize) + return unless capabilities_cached? + return "+" if capable?("LITERAL+") + return "-" if capable_literal_minus? && bytesize <= 4096 + false end + def capable_literal_minus? = capable?("LITERAL-") || capable?("IMAP4rev2") + # NOTE: +num+ should already be an Integer def send_number_data(num) put_string(Integer(num).to_s) diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index f43bdcc7..321cc258 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -869,6 +869,55 @@ def test_raw_data end end + test("send non-synchronizing literals with LITERAL+") do + with_fake_server( + with_extensions: %w[LITERAL+], greeting_capabilities: true, + ) do |server, imap| + def imap.send_test_args(*args) = send_command("TEST", *args) + server.on "TEST", &:done_ok + + imap.config.max_non_synchronizing_literal = 5_000 + large = "\xff".b * 5_000 + imap.send_test_args Net::IMAP::Literal[large, nil] + assert_equal("{5000+}\r\n#{large}".b, server.commands.pop.args) + + large = "\xff".b * 10_000 + imap.send_test_args Net::IMAP::Literal[large, nil] + assert_equal("{10000}\r\n#{large}".b, server.commands.pop.args) + + imap.send_test_args Net::IMAP::Literal[large, true] + assert_equal("{10000+}\r\n#{large}".b, server.commands.pop.args) + end + end + + test("send non-synchronizing literal that's too large for LITERAL-") do + with_fake_server( + with_extensions: %w[LITERAL-], greeting_capabilities: true, + ignore_abrupt_eof: true, ignore_io_error: true + ) do |server, imap| + def imap.send_test_args(*args) = send_command("TEST", *args) + server.on "TEST", &:done_ok + assert_raise(Net::IMAP::DataFormatError) do + imap.send_test_args Net::IMAP::Literal["\xff".b * 5000, true] + end + assert imap.disconnected? + end + end + + test("send non-synchronizing literal without known server support") do + with_fake_server( + with_extensions: %w[LITERAL+], greeting_capabilities: false, + ignore_abrupt_eof: true, ignore_io_error: true + ) do |server, imap| + def imap.send_test_args(*args) = send_command("TEST", *args) + server.on "TEST", &:done_ok + assert_raise(Net::IMAP::DataFormatError) do + imap.send_test_args Net::IMAP::Literal["\xff".b * 100, true] + end + assert imap.disconnected? + end + end + def test_disconnect server = create_tcp_server port = server.addr[1]