Skip to content

Commit ec208e3

Browse files
committed
✨ Add fetch support for EMAILID and THREADID
These are the `msg-att` added by the `OBJECTID` extension.
1 parent dcbdb21 commit ec208e3

File tree

6 files changed

+94
-3
lines changed

6 files changed

+94
-3
lines changed

lib/net/imap.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ module Net
492492
# - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses.
493493
#
494494
# *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and
495-
# +X-GM-THRID+, although neither Gmail nor Net::IMAP support it yet.
495+
# +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
496496
#
497497
# ==== RFC6851: +MOVE+
498498
# Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
@@ -504,6 +504,12 @@ module Net
504504
#
505505
# - See #enable for information about support for UTF-8 string encoding.
506506
#
507+
# ==== RFC8474: +OBJECTID+
508+
# - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items.
509+
# See FetchData#emailid and FetchData#emailid.
510+
# >>>
511+
# *NOTE: The +MAILBOXID+ attribute for #status is not supported yet.
512+
#
507513
# == References
508514
#
509515
# [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::

lib/net/imap/fetch_data.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ 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:
56+
# Net::IMAP supports static attributes defined by the following extensions:
57+
# * +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]
58+
# * <b><tt>"EMAILID"</tt></b> --- See #emailid.
59+
# * <b><tt>"THREADID"</tt></b> --- See #threadid.
60+
#
5761
# * +X-GM-EXT-1+ {[non-standard Gmail
5862
# extension]}[https://developers.google.com/gmail/imap/imap-extensions]
5963
# * <b><tt>"X-GM-MSGID"</tt></b> --- unique message ID. Access via #attr.
@@ -462,6 +466,40 @@ def binary_size(*part_nums)
462466
# identified message.
463467
def modseq; attr["MODSEQ"] end
464468

469+
# :call-seq: emailid -> string or nil
470+
#
471+
# An ObjectID that uniquely identifies the immutable content of a single
472+
# message.
473+
#
474+
# The server must return the same +EMAILID+ for both the source and
475+
# destination messages after a COPY or MOVE command. However, it is
476+
# possible for different messages with the same EMAILID to have different
477+
# mutable attributes, such as flags.
478+
#
479+
# This is the same as getting the value for <tt>"EMAILID"</tt> from
480+
# #attr.
481+
#
482+
# The server must support the +OBJECTID+ extension
483+
# {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
484+
def emailid; attr["EMAILID"] end
485+
486+
# :call-seq: threadid -> string or nil
487+
#
488+
# An ObjectID that uniquely identifies a set of messages that the server
489+
# believes should be grouped together.
490+
#
491+
# It is generally based on some combination of References, In-Reply-To,
492+
# and Subject, but the exact implementation is left up to the server
493+
# implementation. The server should return the same thread identifier for
494+
# related messages, even if they are in different mailboxes.
495+
#
496+
# This is the same as getting the value for <tt>"THREADID"</tt> from
497+
# #attr.
498+
#
499+
# The server must support the +OBJECTID+ extension
500+
# {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
501+
def threadid; attr["THREADID"] end
502+
465503
private
466504

467505
def body_section_attr(...) section_attr("BODY", ...) end

lib/net/imap/response_parser.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,8 @@ def msg_att(n)
893893
when "RFC822.HEADER" then nstring # not in rev2
894894
when "RFC822.TEXT" then nstring # not in rev2
895895
when "MODSEQ" then parens__modseq # CONDSTORE
896+
when "EMAILID" then parens__objectid # OBJECTID
897+
when "THREADID" then nparens__objectid # OBJECTID
896898
when "X-GM-MSGID" then x_gm_id # GMail
897899
when "X-GM-THRID" then x_gm_id # GMail
898900
when "X-GM-LABELS" then x_gm_labels # GMail
@@ -1976,6 +1978,15 @@ def charset; quoted? || atom end
19761978

19771979
def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end
19781980

1981+
# RFC8474:
1982+
# objectid = 1*255(ALPHA / DIGIT / "_" / "-")
1983+
# ; characters in object identifiers are case
1984+
# ; significant
1985+
alias objectid atom
1986+
1987+
def parens__objectid; lpar; _ = objectid; rpar; _ end
1988+
def nparens__objectid; NIL? ? nil : parens__objectid end
1989+
19791990
# RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2):
19801991
# uid-set = (uniqueid / uid-range) *("," uid-set)
19811992
# uid-range = (uniqueid ":" uniqueid)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
:tests:
3+
rfc8474_example_5.3_EMAILID_and_THREADID:
4+
:response: "* 3 FETCH (EMAILID (M5fdc09b49ea703) THREADID (T11863d02dd95b5))\r\n"
5+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
6+
name: FETCH
7+
data: !ruby/struct:Net::IMAP::FetchData
8+
seqno: 3
9+
attr:
10+
EMAILID: M5fdc09b49ea703
11+
THREADID: T11863d02dd95b5
12+
raw_data: "* 3 FETCH (EMAILID (M5fdc09b49ea703) THREADID (T11863d02dd95b5))\r\n"
13+
14+
rfc8474_example_5.3_no_THREADID_support:
15+
:response: "* 2 FETCH (EMAILID (M00000002) THREADID NIL)\r\n"
16+
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
17+
name: FETCH
18+
data: !ruby/struct:Net::IMAP::FetchData
19+
seqno: 2
20+
attr:
21+
EMAILID: M00000002
22+
THREADID:
23+
raw_data: "* 2 FETCH (EMAILID (M00000002) THREADID NIL)\r\n"

test/net/imap/test_fetch_data.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,20 @@ class FetchDataTest < Test::Unit::TestCase
3737
end
3838

3939
test "#modseq returns MODSEQ value (RFC7162: CONDSTORE)" do
40-
data = FetchData.new( 22222, {"MODSEQ" => 123_456_789})
40+
data = FetchData.new(22222, {"MODSEQ" => 123_456_789})
4141
assert_equal(123_456_789, data.modseq)
4242
end
4343

44+
test "#emailid returns EMAILID value (RFC8474: OBJECTID)" do
45+
data = FetchData.new(22222, {"EMAILID" => "THIS-IS-IT-01234"})
46+
assert_equal "THIS-IS-IT-01234", data.emailid
47+
end
48+
49+
test "#threadid returns THREADID value (RFC8474: OBJECTID)" do
50+
data = FetchData.new(22222, {"THREADID" => "THAT-IS-THAT-98765"})
51+
assert_equal "THAT-IS-THAT-98765", data.threadid
52+
end
53+
4454
test "simple RFC822 attrs accessors (deprecated by RFC9051)" do
4555
data = FetchData.new(
4656
22222, {

test/net/imap/test_imap_response_parser.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def teardown
9090
# RFC 5256: THREAD response
9191
generate_tests_from fixture_file: "thread_responses.yml"
9292

93+
# RFC 8474: OBJECTID responses
94+
generate_tests_from fixture_file: "rfc8474_objectid_responses.yml"
95+
9396
############################################################################
9497
# Workarounds or unspecified extensions:
9598
generate_tests_from fixture_file: "quirky_behaviors.yml"

0 commit comments

Comments
 (0)