Skip to content

Commit f5ef196

Browse files
committed
✨ Add support for GMail's X-GM-EXT-1 extension
Adds support for fetching `X-GM-MSGID`, `X-GM-THRID`, and `X-GM-LABELS` on GMail servers. Note that the `X-GM-RAW` search attribute is documented but it does not need any special code to support it. See specs at https://developers.google.com/gmail/imap/imap-extensions.
1 parent 3410e14 commit f5ef196

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

lib/net/imap.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,9 +483,17 @@ module Net
483483
# which arranges the results into ordered groups or threads according to a
484484
# chosen algorithm.
485485
#
486-
# ==== +XLIST+ (non-standard, deprecated)
486+
# ==== +X-GM-EXT-1+
487+
# +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's
488+
# documentation}[https://developers.google.com/gmail/imap/imap-extensions].
489+
# - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique
490+
# message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels).
491+
# - Updates #search with the +X-GM-RAW+ search attribute.
487492
# - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
488493
#
494+
# *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
495+
# +X-GM-THRID+, although neither Gmail nor Net::IMAP support it yet.
496+
#
489497
# ==== RFC6851: +MOVE+
490498
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
491499
# above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].

lib/net/imap/fetch_data.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ class IMAP < Protocol
5353
# * <b><tt>"RFC822.TEXT"</tt></b> --- See #rfc822_text or replace with
5454
# <tt>"BODY[TEXT]"</tt> and #text.
5555
#
56+
# Net::IMAP supports dynamic attributes defined by the following extensions:
57+
# * +X-GM-EXT-1+ {[non-standard Gmail
58+
# extension]}[https://developers.google.com/gmail/imap/imap-extensions]
59+
# * <b><tt>"X-GM-MSGID"</tt></b> --- unique message ID. Access via #attr.
60+
# * <b><tt>"X-GM-THRID"</tt></b> --- Thread ID. Access via #attr.
61+
#
5662
# [Note:]
5763
# >>>
5864
# Additional static fields are defined in other \IMAP extensions, but
@@ -73,6 +79,9 @@ class IMAP < Protocol
7379
#
7480
# * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]:
7581
# * <b><tt>"MODSEQ"</tt></b> --- See #modseq.
82+
# * +X-GM-EXT-1+ {[non-standard Gmail
83+
# extension]}[https://developers.google.com/gmail/imap/imap-extensions]
84+
# * <b><tt>"X-GM-LABELS"</tt></b> --- Gmail labels. Access via #attr.
7685
#
7786
# [Note:]
7887
# >>>

lib/net/imap/response_parser.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,12 @@ def case_insensitive__nstring
513513
# ; Strictly ascending
514514
alias uniqueid nz_number
515515

516+
# valid number ranges are not enforced by parser
517+
#
518+
# a 64-bit unsigned integer and is the decimal equivalent for the ID hex
519+
# string used in the web interface and the Gmail API.
520+
alias x_gm_id number
521+
516522
# [RFC3501 & RFC9051:]
517523
# response = *(continue-req / response-data) response-done
518524
#
@@ -765,6 +771,9 @@ def msg_att(n)
765771
when "RFC822.HEADER" then nstring # not in rev2
766772
when "RFC822.TEXT" then nstring # not in rev2
767773
when "MODSEQ" then parens__modseq # CONDSTORE
774+
when "X-GM-MSGID" then x_gm_id # GMail
775+
when "X-GM-THRID" then x_gm_id # GMail
776+
when "X-GM-LABELS" then x_gm_labels # GMail
768777
else parse_error("unknown attribute `%s' for {%d}", name, n)
769778
end
770779
attr[name] = val
@@ -1765,6 +1774,19 @@ def parens__mbx_list_flags
17651774
.split(nil).map! { _1.capitalize.to_sym }
17661775
end
17671776

1777+
# See https://developers.google.com/gmail/imap/imap-extensions
1778+
def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end
1779+
1780+
# See https://developers.google.com/gmail/imap/imap-extensions
1781+
def x_gm_labels
1782+
lpar; return [] if rpar?
1783+
labels = []
1784+
labels << x_gm_label
1785+
labels << x_gm_label while SP?
1786+
rpar
1787+
labels
1788+
end
1789+
17681790
# See https://www.rfc-editor.org/errata/rfc3501
17691791
#
17701792
# charset = atom / quoted

test/net/imap/fixtures/response_parser/fetch_responses.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,71 @@
123123
RFC822: "foo\r\n"
124124
raw_data: "* 123 FETCH (RFC822 {5}\r\nfoo\r\n)\r\n"
125125

126+
test_fetch_msg_att_X-GM-MSGID:
127+
:comments: |
128+
Example copied from https://developers.google.com/gmail/imap/imap-extensions
129+
:response: "* 1 FETCH (X-GM-MSGID 1278455344230334865)\r\n"
130+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
131+
name: FETCH
132+
data: !ruby/struct:Net::IMAP::FetchData
133+
seqno: 1
134+
attr:
135+
X-GM-MSGID: 1278455344230334865
136+
raw_data: "* 1 FETCH (X-GM-MSGID 1278455344230334865)\r\n"
137+
138+
test_fetch_msg_att_X-GM-THRID:
139+
:comments: |
140+
Example copied from https://developers.google.com/gmail/imap/imap-extensions
141+
:response: "* 4 FETCH (X-GM-THRID 1266894439832287888)\r\n"
142+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
143+
name: FETCH
144+
data: !ruby/struct:Net::IMAP::FetchData
145+
seqno: 4
146+
attr:
147+
X-GM-THRID: 1266894439832287888
148+
raw_data: "* 4 FETCH (X-GM-THRID 1266894439832287888)\r\n"
149+
150+
test_fetch_msg_att_X-GM-LABELS_1:
151+
:comments: |
152+
Example copied from https://developers.google.com/gmail/imap/imap-extensions
153+
:response: "* 1 FETCH (X-GM-LABELS (\\Inbox \\Sent Important \"Muy Importante\"))\r\n"
154+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
155+
name: FETCH
156+
data: !ruby/struct:Net::IMAP::FetchData
157+
seqno: 1
158+
attr:
159+
X-GM-LABELS:
160+
- :Inbox
161+
- :Sent
162+
- Important
163+
- Muy Importante
164+
raw_data: "* 1 FETCH (X-GM-LABELS (\\Inbox \\Sent Important \"Muy Importante\"))\r\n"
165+
166+
test_fetch_msg_att_X-GM-LABELS_2:
167+
:comments: |
168+
Example copied from https://developers.google.com/gmail/imap/imap-extensions
169+
:response: "* 2 FETCH (X-GM-LABELS (foo))\r\n"
170+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
171+
name: FETCH
172+
data: !ruby/struct:Net::IMAP::FetchData
173+
seqno: 2
174+
attr:
175+
X-GM-LABELS:
176+
- foo
177+
raw_data: "* 2 FETCH (X-GM-LABELS (foo))\r\n"
178+
179+
test_fetch_msg_att_X-GM-LABELS_3:
180+
:comments: |
181+
Example copied from https://developers.google.com/gmail/imap/imap-extensions
182+
:response: "* 3 FETCH (X-GM-LABELS ())\r\n"
183+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
184+
name: FETCH
185+
data: !ruby/struct:Net::IMAP::FetchData
186+
seqno: 3
187+
attr:
188+
X-GM-LABELS: []
189+
raw_data: "* 3 FETCH (X-GM-LABELS ())\r\n"
190+
126191
test_invalid_fetch_msg_att_rfc822_with_brackets:
127192
:response: "* 123 FETCH (RFC822[] {5}\r\nfoo\r\n)\r\n"
128193
:expected: !ruby/struct:Net::IMAP::UntaggedResponse

0 commit comments

Comments
 (0)