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