diff --git a/lib/net/imap.rb b/lib/net/imap.rb index ae23c0acf..92290b385 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -2316,7 +2316,7 @@ def response_untagged end def response_tagged - tag = atom + tag = astring_chars match(T_SPACE) token = match(T_ATOM) name = token.value.upcase @@ -3114,11 +3114,15 @@ def capability_response token = match(T_ATOM) name = token.value.upcase match(T_SPACE) + UntaggedResponse.new(name, capability_data, @str) + end + + def capability_data data = [] while true token = lookahead case token.symbol - when T_CRLF + when T_CRLF, T_RBRA break when T_SPACE shift_token @@ -3126,7 +3130,7 @@ def capability_response end data.push(atom.upcase) end - return UntaggedResponse.new(name, data, @str) + data end def resp_text @@ -3150,6 +3154,8 @@ def resp_text_code case name when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n result = ResponseCode.new(name, nil) + when /\A(?:CAPABILITY)\z/ni + result = ResponseCode.new(name, capability_data) when /\A(?:PERMANENTFLAGS)\z/n match(T_SPACE) result = ResponseCode.new(name, flag_list) @@ -3169,6 +3175,7 @@ def resp_text_code end end match(T_RBRA) + @pos += 1 if @str[@pos] == " " @lex_state = EXPR_RTEXT return result end @@ -3269,7 +3276,7 @@ def astring if string_token?(token) return string else - return atom + return astring_chars end end @@ -3299,34 +3306,38 @@ def case_insensitive_string return token.value.upcase end - def atom - result = String.new - while true - token = lookahead - if atom_token?(token) - result.concat(token.value) - shift_token - else - if result.empty? - parse_error("unexpected token %s", token.symbol) - else - return result - end - end - end - end - + # atom = 1*ATOM-CHAR + # ATOM-CHAR = ATOM_TOKENS = [ T_ATOM, T_NUMBER, T_NIL, T_LBRA, - T_RBRA, T_PLUS ] - def atom_token?(token) - return ATOM_TOKENS.include?(token.symbol) + def atom + -combine_adjacent(*ATOM_TOKENS) + end + + # ASTRING-CHAR = ATOM-CHAR / resp-specials + # resp-specials = "]" + ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA] + + def astring_chars + combine_adjacent(*ASTRING_CHARS_TOKENS) + end + + def combine_adjacent(*tokens) + result = "".b + while token = accept(*tokens) + result << token.value + end + if result.empty? + parse_error('unexpected token %s (expected %s)', + lookahead.symbol, args.join(" or ")) + end + result end def number @@ -3355,6 +3366,18 @@ def match(*args) return token end + # like match, but does not raise error on failure. + # + # returns and shifts token on successful match + # returns nil and leaves @token unshifted on no match + def accept(*args) + token = lookahead + if args.include?(token.symbol) + shift_token + token + end + end + def lookahead unless @token @token = next_token diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index 4e470459c..4d5660ae8 100644 --- a/test/net/imap/test_imap_response_parser.rb +++ b/test/net/imap/test_imap_response_parser.rb @@ -234,6 +234,15 @@ def test_capability response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN \r\n") assert_equal("CAPABILITY", response.name) assert_equal("AUTH=PLAIN", response.data.last) + response = parser.parse("* OK [CAPABILITY IMAP4rev1 SASL-IR 1234 NIL THIS+THAT + AUTH=PLAIN ID] IMAP4rev1 Hello\r\n") + assert_equal("OK", response.name) + assert_equal("IMAP4rev1 Hello", response.data.text) + code = response.data.code + assert_equal("CAPABILITY", code.name) + assert_equal( + ["IMAP4REV1", "SASL-IR", "1234", "NIL", "THIS+THAT", "+", "AUTH=PLAIN", "ID"], + code.data + ) end def test_mixed_boundary