From af010a09ece70a2f2c0c1d471baa224ca7f2e867 Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" Date: Tue, 8 Dec 2020 22:42:21 -0500 Subject: [PATCH 1/4] Add Net::IMAP::ResponseParser#accept method `accept` can be used to replace `lookahead` + check `token.symbol` + `shift_token`. It's not being used here, but has been separated out into its own commit to reduce merge conflicts from multiple branches which use it. --- lib/net/imap.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index ae23c0acf..f195aaea9 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -3355,6 +3355,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 From d35f54a7149e822d6bceb75f81bc67786aa2882e Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" Date: Tue, 21 Apr 2020 18:57:28 -0400 Subject: [PATCH 2/4] Net::IMAP: add atom and astring_chars `astring_chars` roughly matches the RFC2060 definition of `atom`, and is used by RFC3501's `astring`. `atom` matches the RFC3501 definition. Although nothing in the parser currently uses `atom`, future commits will update use it where it's used by RFC3501, RFC4466, etc. Made a helper method, `combine_adjacent` which is used by both `atom` and `astring_chars` to combine adjacent tokens into a single string. It would probably be better to update the lexer regexps, possibly using negative lookahead assertions, so that it returns a single token. --- lib/net/imap.rb | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index f195aaea9..71f02b6c8 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 @@ -3269,7 +3269,7 @@ def astring if string_token?(token) return string else - return atom + return astring_chars end end @@ -3299,34 +3299,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 From 0d93a8e52c197c8f48e1de4dcaf64a1a976759a1 Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" Date: Fri, 7 Feb 2020 17:52:47 -0500 Subject: [PATCH 3/4] Parsing CAPABILITY data for ResponseCode In RFC3501, "capability-data" is found both in "response-data" and in "resp-text-code". Many IMAP servers return CAPABILITY codes in the server greeting or in the tagged OK for STARTTLS, LOGIN, or AUTHENTICATE. This adds parsing for CAPABILITY data in a ResponseCode, the same as already existed for CAPABILITY data in an UntaggedResponse. --- lib/net/imap.rb | 10 ++++++++-- test/net/imap/test_imap_response_parser.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 71f02b6c8..e628dead4 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -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) diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index 4e470459c..bcadcdb8d 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 From fc14be6771c75ca55e7f14bbbbc27a7be6f3d33b Mon Sep 17 00:00:00 2001 From: "nicholas a. evans" Date: Fri, 7 Feb 2020 18:01:17 -0500 Subject: [PATCH 4/4] do not return leading space in resp-text A minor annoyance. :) The RFC3501 ABNF implies that the "text" we are concerned with comes after the space: resp-text = ["[" resp-text-code "]" SP] text --- lib/net/imap.rb | 1 + test/net/imap/test_imap_response_parser.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index e628dead4..92290b385 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -3175,6 +3175,7 @@ def resp_text_code end end match(T_RBRA) + @pos += 1 if @str[@pos] == " " @lex_state = EXPR_RTEXT return result end diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index bcadcdb8d..4d5660ae8 100644 --- a/test/net/imap/test_imap_response_parser.rb +++ b/test/net/imap/test_imap_response_parser.rb @@ -236,7 +236,7 @@ def test_capability 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) + assert_equal("IMAP4rev1 Hello", response.data.text) code = response.data.code assert_equal("CAPABILITY", code.name) assert_equal(