diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 08e8b2ac..56c7d63b 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -483,9 +483,17 @@ module Net # which arranges the results into ordered groups or threads according to a # chosen algorithm. # - # ==== +XLIST+ (non-standard, deprecated) + # ==== +X-GM-EXT-1+ + # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's + # documentation}[https://developers.google.com/gmail/imap/imap-extensions]. + # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique + # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels). + # - Updates #search with the +X-GM-RAW+ search attribute. # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses. # + # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and + # +X-GM-THRID+, although neither Gmail nor Net::IMAP support it yet. + # # ==== RFC6851: +MOVE+ # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. diff --git a/lib/net/imap/fetch_data.rb b/lib/net/imap/fetch_data.rb index 254f4d8a..294d334c 100644 --- a/lib/net/imap/fetch_data.rb +++ b/lib/net/imap/fetch_data.rb @@ -53,6 +53,12 @@ class IMAP < Protocol # * "RFC822.TEXT" --- See #rfc822_text or replace with # "BODY[TEXT]" and #text. # + # Net::IMAP supports dynamic attributes defined by the following extensions: + # * +X-GM-EXT-1+ {[non-standard Gmail + # extension]}[https://developers.google.com/gmail/imap/imap-extensions] + # * "X-GM-MSGID" --- unique message ID. Access via #attr. + # * "X-GM-THRID" --- Thread ID. Access via #attr. + # # [Note:] # >>> # Additional static fields are defined in other \IMAP extensions, but @@ -73,6 +79,9 @@ class IMAP < Protocol # # * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]: # * "MODSEQ" --- See #modseq. + # * +X-GM-EXT-1+ {[non-standard Gmail + # extension]}[https://developers.google.com/gmail/imap/imap-extensions] + # * "X-GM-LABELS" --- Gmail labels. Access via #attr. # # [Note:] # >>> diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb index a56a7dd3..90b99bf3 100644 --- a/lib/net/imap/response_parser.rb +++ b/lib/net/imap/response_parser.rb @@ -513,6 +513,12 @@ def case_insensitive__nstring # ; Strictly ascending alias uniqueid nz_number + # valid number ranges are not enforced by parser + # + # a 64-bit unsigned integer and is the decimal equivalent for the ID hex + # string used in the web interface and the Gmail API. + alias x_gm_id number + # [RFC3501 & RFC9051:] # response = *(continue-req / response-data) response-done # @@ -765,6 +771,9 @@ def msg_att(n) when "RFC822.HEADER" then nstring # not in rev2 when "RFC822.TEXT" then nstring # not in rev2 when "MODSEQ" then parens__modseq # CONDSTORE + when "X-GM-MSGID" then x_gm_id # GMail + when "X-GM-THRID" then x_gm_id # GMail + when "X-GM-LABELS" then x_gm_labels # GMail else parse_error("unknown attribute `%s' for {%d}", name, n) end attr[name] = val @@ -1765,6 +1774,19 @@ def parens__mbx_list_flags .split(nil).map! { _1.capitalize.to_sym } end + # See https://developers.google.com/gmail/imap/imap-extensions + def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end + + # See https://developers.google.com/gmail/imap/imap-extensions + def x_gm_labels + lpar; return [] if rpar? + labels = [] + labels << x_gm_label + labels << x_gm_label while SP? + rpar + labels + end + # See https://www.rfc-editor.org/errata/rfc3501 # # charset = atom / quoted diff --git a/test/net/imap/fixtures/response_parser/fetch_responses.yml b/test/net/imap/fixtures/response_parser/fetch_responses.yml index 02881889..11d6905c 100644 --- a/test/net/imap/fixtures/response_parser/fetch_responses.yml +++ b/test/net/imap/fixtures/response_parser/fetch_responses.yml @@ -123,6 +123,71 @@ RFC822: "foo\r\n" raw_data: "* 123 FETCH (RFC822 {5}\r\nfoo\r\n)\r\n" + test_fetch_msg_att_X-GM-MSGID: + :comments: | + Example copied from https://developers.google.com/gmail/imap/imap-extensions + :response: "* 1 FETCH (X-GM-MSGID 1278455344230334865)\r\n" + :expected: !ruby/struct:Net::IMAP::UntaggedResponse + name: FETCH + data: !ruby/struct:Net::IMAP::FetchData + seqno: 1 + attr: + X-GM-MSGID: 1278455344230334865 + raw_data: "* 1 FETCH (X-GM-MSGID 1278455344230334865)\r\n" + + test_fetch_msg_att_X-GM-THRID: + :comments: | + Example copied from https://developers.google.com/gmail/imap/imap-extensions + :response: "* 4 FETCH (X-GM-THRID 1266894439832287888)\r\n" + :expected: !ruby/struct:Net::IMAP::UntaggedResponse + name: FETCH + data: !ruby/struct:Net::IMAP::FetchData + seqno: 4 + attr: + X-GM-THRID: 1266894439832287888 + raw_data: "* 4 FETCH (X-GM-THRID 1266894439832287888)\r\n" + + test_fetch_msg_att_X-GM-LABELS_1: + :comments: | + Example copied from https://developers.google.com/gmail/imap/imap-extensions + :response: "* 1 FETCH (X-GM-LABELS (\\Inbox \\Sent Important \"Muy Importante\"))\r\n" + :expected: !ruby/struct:Net::IMAP::UntaggedResponse + name: FETCH + data: !ruby/struct:Net::IMAP::FetchData + seqno: 1 + attr: + X-GM-LABELS: + - :Inbox + - :Sent + - Important + - Muy Importante + raw_data: "* 1 FETCH (X-GM-LABELS (\\Inbox \\Sent Important \"Muy Importante\"))\r\n" + + test_fetch_msg_att_X-GM-LABELS_2: + :comments: | + Example copied from https://developers.google.com/gmail/imap/imap-extensions + :response: "* 2 FETCH (X-GM-LABELS (foo))\r\n" + :expected: !ruby/struct:Net::IMAP::UntaggedResponse + name: FETCH + data: !ruby/struct:Net::IMAP::FetchData + seqno: 2 + attr: + X-GM-LABELS: + - foo + raw_data: "* 2 FETCH (X-GM-LABELS (foo))\r\n" + + test_fetch_msg_att_X-GM-LABELS_3: + :comments: | + Example copied from https://developers.google.com/gmail/imap/imap-extensions + :response: "* 3 FETCH (X-GM-LABELS ())\r\n" + :expected: !ruby/struct:Net::IMAP::UntaggedResponse + name: FETCH + data: !ruby/struct:Net::IMAP::FetchData + seqno: 3 + attr: + X-GM-LABELS: [] + raw_data: "* 3 FETCH (X-GM-LABELS ())\r\n" + test_invalid_fetch_msg_att_rfc822_with_brackets: :response: "* 123 FETCH (RFC822[] {5}\r\nfoo\r\n)\r\n" :expected: !ruby/struct:Net::IMAP::UntaggedResponse