Skip to content

Commit

Permalink
✨ Add support for GMail's X-GM-EXT-1 extension
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nevans committed Nov 10, 2023
1 parent 3410e14 commit f5ef196
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 1 deletion.
10 changes: 9 additions & 1 deletion lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down
9 changes: 9 additions & 0 deletions lib/net/imap/fetch_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ class IMAP < Protocol
# * <b><tt>"RFC822.TEXT"</tt></b> --- See #rfc822_text or replace with
# <tt>"BODY[TEXT]"</tt> 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]
# * <b><tt>"X-GM-MSGID"</tt></b> --- unique message ID. Access via #attr.
# * <b><tt>"X-GM-THRID"</tt></b> --- Thread ID. Access via #attr.
#
# [Note:]
# >>>
# Additional static fields are defined in other \IMAP extensions, but
Expand All @@ -73,6 +79,9 @@ class IMAP < Protocol
#
# * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]:
# * <b><tt>"MODSEQ"</tt></b> --- See #modseq.
# * +X-GM-EXT-1+ {[non-standard Gmail
# extension]}[https://developers.google.com/gmail/imap/imap-extensions]
# * <b><tt>"X-GM-LABELS"</tt></b> --- Gmail labels. Access via #attr.
#
# [Note:]
# >>>
Expand Down
22 changes: 22 additions & 0 deletions lib/net/imap/response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions test/net/imap/fixtures/response_parser/fetch_responses.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f5ef196

Please sign in to comment.