diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb index 189bc980..2c1d1dd1 100644 --- a/lib/net/imap/response_parser.rb +++ b/lib/net/imap/response_parser.rb @@ -1888,12 +1888,17 @@ def text? # We leniently re-interpret this as # resp-text = ["[" resp-text-code "]" [SP [text]] / [text] def resp_text + state = current_state if lbra? code = resp_text_code; rbra ResponseText.new(code, SP? && text? || "") else ResponseText.new(nil, text? || "") end + rescue ResponseParseError => error + raise if /\buid-set\b/i.match? error.message + restore_state state + text end # RFC3501 (See https://www.rfc-editor.org/errata/rfc3501): diff --git a/lib/net/imap/response_parser/parser_utils.rb b/lib/net/imap/response_parser/parser_utils.rb index 880d4cc2..99626d03 100644 --- a/lib/net/imap/response_parser/parser_utils.rb +++ b/lib/net/imap/response_parser/parser_utils.rb @@ -221,8 +221,14 @@ def exception(message) = ResponseParseError.new( message, parser_state:, parser_class: self.class ) - def current_state = [@lex_state, @pos, @token] - def parser_state = [@str, *current_state] + # This can be used to backtrack after a parse error, and re-attempt to + # parse using a fallback. + # + # NOTE: Reckless backtracking could lead to O(n²) situations, so this + # should very rarely be used. Ideally, fallbacks should not backtrack. + def restore_state(state) = (@lex_state, @pos, @token = state) + def current_state = [@lex_state, @pos, @token] + def parser_state = [@str, *current_state] end end diff --git a/test/net/imap/fixtures/response_parser/quirky_behaviors.yml b/test/net/imap/fixtures/response_parser/quirky_behaviors.yml index e6bf9d48..f6f3e790 100644 --- a/test/net/imap/fixtures/response_parser/quirky_behaviors.yml +++ b/test/net/imap/fixtures/response_parser/quirky_behaviors.yml @@ -31,6 +31,33 @@ unparsed_data: "froopy snood" raw_data: "* 86 NOOP froopy snood\r\n" + "Microsoft Exchange issues an invalid resp-text-code": + comment: | + net-imap issue: https://github.com/ruby/net-imap/issues/597 + + Microsoft Exchange is issuing an invalid resp-text-code, but net-imap + should fallback to `resp-text = text` when resp-text-code fails to parse. + :debug: false # current version of backtracking still prints parse_errors + :response: "RUBY0001 OK [Error=\"Microsoft.Exchange.Data.Storage.WrongServerException: + Cross Server access is not allowed for mailbox xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\" + AuthResultFromPopImapEnd=0 Proxy=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM:1993:SSL + MailboxBE=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM + Service=Imap4] AUTHENTICATE completed.\r\n" + :expected: !ruby/struct:Net::IMAP::TaggedResponse + tag: RUBY0001 + name: OK + data: '[Error="Microsoft.Exchange.Data.Storage.WrongServerException: Cross Server + access is not allowed for mailbox xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + AuthResultFromPopImapEnd=0 + Proxy=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM:1993:SSL + MailboxBE=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM Service=Imap4] + AUTHENTICATE completed.' + raw_data: "RUBY0001 OK [Error=\"Microsoft.Exchange.Data.Storage.WrongServerException: + Cross Server access is not allowed for mailbox xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\" + AuthResultFromPopImapEnd=0 Proxy=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM:1993:SSL + MailboxBE=XXXXXXXXXXXXX.EURXXXX.PROD.OUTLOOK.COM Service=Imap4] AUTHENTICATE + completed.\r\n" + outlook.com puts an extra SP in ENVELOPE address lists: comment: | An annoying bug from outlook.com. They've had the bug for years, and