net-imap 0.3.7 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +3 -22
  6. data/README.md +25 -8
  7. data/Rakefile +0 -7
  8. data/docs/styles.css +72 -23
  9. data/lib/net/imap/authenticators.rb +26 -57
  10. data/lib/net/imap/command_data.rb +74 -54
  11. data/lib/net/imap/config/attr_accessors.rb +75 -0
  12. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  13. data/lib/net/imap/config/attr_type_coercion.rb +61 -0
  14. data/lib/net/imap/config.rb +470 -0
  15. data/lib/net/imap/data_encoding.rb +18 -6
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +142 -0
  18. data/lib/net/imap/errors.rb +27 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +597 -0
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +250 -440
  23. data/lib/net/imap/response_parser/parser_utils.rb +245 -0
  24. data/lib/net/imap/response_parser.rb +1867 -1184
  25. data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
  26. data/lib/net/imap/sasl/authentication_exchange.rb +139 -0
  27. data/lib/net/imap/sasl/authenticators.rb +122 -0
  28. data/lib/net/imap/sasl/client_adapter.rb +123 -0
  29. data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +24 -14
  30. data/lib/net/imap/sasl/digest_md5_authenticator.rb +342 -0
  31. data/lib/net/imap/sasl/external_authenticator.rb +83 -0
  32. data/lib/net/imap/sasl/gs2_header.rb +80 -0
  33. data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +28 -18
  34. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
  35. data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
  36. data/lib/net/imap/sasl/protocol_adapters.rb +101 -0
  37. data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
  38. data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
  39. data/lib/net/imap/sasl/stringprep.rb +6 -66
  40. data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
  41. data/lib/net/imap/sasl.rb +148 -44
  42. data/lib/net/imap/sasl_adapter.rb +20 -0
  43. data/lib/net/imap/search_result.rb +146 -0
  44. data/lib/net/imap/sequence_set.rb +1565 -0
  45. data/lib/net/imap/stringprep/nameprep.rb +70 -0
  46. data/lib/net/imap/stringprep/saslprep.rb +69 -0
  47. data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
  48. data/lib/net/imap/stringprep/tables.rb +146 -0
  49. data/lib/net/imap/stringprep/trace.rb +85 -0
  50. data/lib/net/imap/stringprep.rb +159 -0
  51. data/lib/net/imap/uidplus_data.rb +244 -0
  52. data/lib/net/imap/vanished_data.rb +56 -0
  53. data/lib/net/imap.rb +2090 -823
  54. data/net-imap.gemspec +7 -8
  55. data/rakelib/benchmarks.rake +91 -0
  56. data/rakelib/rfcs.rake +2 -0
  57. data/rakelib/saslprep.rake +4 -4
  58. data/rakelib/string_prep_tables_generator.rb +84 -60
  59. data/sample/net-imap.rb +167 -0
  60. metadata +45 -49
  61. data/.github/dependabot.yml +0 -6
  62. data/.github/workflows/test.yml +0 -38
  63. data/.gitignore +0 -10
  64. data/benchmarks/stringprep.yml +0 -65
  65. data/benchmarks/table-regexps.yml +0 -39
  66. data/lib/net/imap/authenticators/digest_md5.rb +0 -115
  67. data/lib/net/imap/authenticators/plain.rb +0 -41
  68. data/lib/net/imap/authenticators/xoauth2.rb +0 -20
  69. data/lib/net/imap/sasl/saslprep.rb +0 -55
  70. data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
  71. data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -0,0 +1,597 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ # Net::IMAP::FetchStruct is the superclass for FetchData and UIDFetchData.
7
+ # Net::IMAP#fetch, Net::IMAP#uid_fetch, Net::IMAP#store, and
8
+ # Net::IMAP#uid_store all return arrays of FetchStruct objects.
9
+ #
10
+ # === Fetch attributes
11
+ #
12
+ # See {[IMAP4rev1 §7.4.2]}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2]
13
+ # and {[IMAP4rev2 §7.5.2]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2]
14
+ # for a full description of the standard fetch response data items, and
15
+ # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs.
16
+ #
17
+ # ==== Static fetch data items
18
+ #
19
+ # Most message attributes are static, and must never change for a given
20
+ # <tt>(server, account, mailbox, UIDVALIDITY, UID)</tt> tuple.
21
+ #
22
+ # The static fetch data items defined by both
23
+ # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and
24
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] are:
25
+ #
26
+ # * <b><tt>"UID"</tt></b> --- See #uid.
27
+ # * <b><tt>"BODY"</tt></b> --- See #body.
28
+ # * <b><tt>"BODY[#{section_spec}]"</tt></b>,
29
+ # <b><tt>"BODY[#{section_spec}]<#{offset}>"</tt></b> --- See #message,
30
+ # #part, #header, #header_fields, #header_fields_not, #mime, and #text.
31
+ # * <b><tt>"BODYSTRUCTURE"</tt></b> --- See #bodystructure.
32
+ # * <b><tt>"ENVELOPE"</tt></b> --- See #envelope.
33
+ # * <b><tt>"INTERNALDATE"</tt></b> --- See #internaldate.
34
+ # * <b><tt>"RFC822.SIZE"</tt></b> --- See #rfc822_size.
35
+ #
36
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] adds the
37
+ # additional fetch items from the +BINARY+ extension
38
+ # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]:
39
+ #
40
+ # * <b><tt>"BINARY[#{part}]"</tt></b>,
41
+ # <b><tt>"BINARY[#{part}]<#{offset}>"</tt></b> -- See #binary.
42
+ # * <b><tt>"BINARY.SIZE[#{part}]"</tt></b> -- See #binary_size.
43
+ #
44
+ # Several static message attributes in
45
+ # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] are obsolete and
46
+ # been removed from
47
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]:
48
+ #
49
+ # * <b><tt>"RFC822"</tt></b> --- See #rfc822 or replace with
50
+ # <tt>"BODY[]"</tt> and #message.
51
+ # * <b><tt>"RFC822.HEADER"</tt></b> --- See #rfc822_header or replace with
52
+ # <tt>"BODY[HEADER]"</tt> and #header.
53
+ # * <b><tt>"RFC822.TEXT"</tt></b> --- See #rfc822_text or replace with
54
+ # <tt>"BODY[TEXT]"</tt> and #text.
55
+ #
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
+ #
61
+ # * +X-GM-EXT-1+ {[non-standard Gmail
62
+ # extension]}[https://developers.google.com/gmail/imap/imap-extensions]
63
+ # * <b><tt>"X-GM-MSGID"</tt></b> --- unique message ID. Access via #attr.
64
+ # * <b><tt>"X-GM-THRID"</tt></b> --- Thread ID. Access via #attr.
65
+ #
66
+ # [NOTE:]
67
+ # Additional static fields are defined in other \IMAP extensions, but
68
+ # Net::IMAP can't parse them yet.
69
+ #
70
+ # ==== Dynamic message attributes
71
+ #
72
+ # Some message attributes can be dynamically changed, for example using the
73
+ # {STORE command}[rdoc-ref:Net::IMAP#store].
74
+ #
75
+ # The only dynamic message attribute defined by
76
+ # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and
77
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] is:
78
+ #
79
+ # * <b><tt>"FLAGS"</tt></b> --- See #flags.
80
+ #
81
+ # Net::IMAP supports dynamic attributes defined by the following extensions:
82
+ #
83
+ # * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]:
84
+ # * <b><tt>"MODSEQ"</tt></b> --- See #modseq.
85
+ # * +X-GM-EXT-1+ {[non-standard Gmail
86
+ # extension]}[https://developers.google.com/gmail/imap/imap-extensions]
87
+ # * <b><tt>"X-GM-LABELS"</tt></b> --- Gmail labels. Access via #attr.
88
+ #
89
+ # [NOTE:]
90
+ # Additional dynamic fields are defined in other \IMAP extensions, but
91
+ # Net::IMAP can't parse them yet.
92
+ #
93
+ # === Implicitly setting <tt>\Seen</tt> and using +PEEK+
94
+ #
95
+ # Unless the mailbox has been opened as read-only, fetching
96
+ # <tt>BODY[#{section}]</tt> or <tt>BINARY[#{section}]</tt>
97
+ # will implicitly set the <tt>\Seen</tt> flag. To avoid this, fetch using
98
+ # <tt>BODY.PEEK[#{section}]</tt> or <tt>BINARY.PEEK[#{section}]</tt>
99
+ # instead.
100
+ #
101
+ # [NOTE:]
102
+ # The data will always be _returned_ without the <tt>".PEEK"</tt> suffix,
103
+ # as <tt>BODY[#{specifier}]</tt> or <tt>BINARY[#{section}]</tt>.
104
+ #
105
+ class FetchStruct < Struct
106
+ ##
107
+ # method: attr
108
+ # :call-seq: attr -> hash
109
+ #
110
+ # Each key specifies a message attribute, and the value is the
111
+ # corresponding data item. Standard data items have corresponding
112
+ # accessor methods. The definitions of each attribute type is documented
113
+ # on its accessor.
114
+
115
+ # :call-seq: attr_upcase -> hash
116
+ #
117
+ # A transformation of #attr, with all the keys converted to upper case.
118
+ #
119
+ # Header field names are case-preserved but not case-sensitive, so this is
120
+ # used by #header_fields and #header_fields_not.
121
+ def attr_upcase; attr.transform_keys(&:upcase) end
122
+
123
+ # :call-seq:
124
+ # body -> body structure or nil
125
+ #
126
+ # Returns an alternate form of #bodystructure, without any extension data.
127
+ #
128
+ # This is the same as getting the value for <tt>"BODY"</tt> from #attr.
129
+ #
130
+ # [NOTE:]
131
+ # Use #message, #part, #header, #header_fields, #header_fields_not,
132
+ # #text, or #mime to retrieve <tt>BODY[#{section_spec}]</tt> attributes.
133
+ def body; attr["BODY"] end
134
+
135
+ # :call-seq:
136
+ # message(offset: bytes) -> string or nil
137
+ #
138
+ # The RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html]
139
+ # expression of the entire message, as a string.
140
+ #
141
+ # See #part for a description of +offset+.
142
+ #
143
+ # <em>RFC5322 messages can be parsed using the "mail" gem.</em>
144
+ #
145
+ # This is the same as getting the value for <tt>"BODY[]"</tt> or
146
+ # <tt>"BODY[]<#{offset}>"</tt> from #attr.
147
+ #
148
+ # See also: #header, #text, and #mime.
149
+ def message(offset: nil) attr[body_section_attr(offset: offset)] end
150
+
151
+ # :call-seq:
152
+ # part(*part_nums, offset: bytes) -> string or nil
153
+ #
154
+ # The string representation of a particular MIME part.
155
+ #
156
+ # +part_nums+ forms a path of MIME part numbers, counting up from +1+,
157
+ # which may specify an arbitrarily nested part, similarly to Array#dig.
158
+ # Messages that don't use MIME, or MIME messages that are not multipart
159
+ # and don't hold an encapsulated message, only have part +1+.
160
+ #
161
+ # If a zero-based +offset+ is given, the returned string is a substring of
162
+ # the entire contents, starting at that origin octet. This means that
163
+ # <tt>BODY[]<0></tt> MAY be truncated, but <tt>BODY[]</tt> is never
164
+ # truncated.
165
+ #
166
+ # This is the same as getting the value of
167
+ # <tt>"BODY[#{part_nums.join(".")}]"</tt> or
168
+ # <tt>"BODY[#{part_nums.join(".")}]<#{offset}>"</tt> from #attr.
169
+ #
170
+ # See also: #message, #header, #text, and #mime.
171
+ def part(index, *subparts, offset: nil)
172
+ attr[body_section_attr([index, *subparts], offset: offset)]
173
+ end
174
+
175
+ # :call-seq:
176
+ # header(*part_nums, offset: nil) -> string or nil
177
+ # header(*part_nums, fields: names, offset: nil) -> string or nil
178
+ # header(*part_nums, except: names, offset: nil) -> string or nil
179
+ #
180
+ # The {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] header of a
181
+ # message or of an encapsulated
182
+ # {[MIME-IMT]}[https://www.rfc-editor.org/rfc/rfc2046.html]
183
+ # MESSAGE/RFC822 or MESSAGE/GLOBAL message.
184
+ #
185
+ # <em>Headers can be parsed using the "mail" gem.</em>
186
+ #
187
+ # See #part for a description of +part_nums+ and +offset+.
188
+ #
189
+ # ==== Without +fields+ or +except+
190
+ # This is the same as getting the value from #attr for one of:
191
+ # * <tt>BODY[HEADER]</tt>
192
+ # * <tt>BODY[HEADER]<#{offset}></tt>
193
+ # * <tt>BODY[#{part_nums.join "."}.HEADER]"</tt>
194
+ # * <tt>BODY[#{part_nums.join "."}.HEADER]<#{offset}>"</tt>
195
+ #
196
+ # ==== With +fields+
197
+ # When +fields+ is sent, returns a subset of the header which contains
198
+ # only the header fields that match one of the names in the list.
199
+ #
200
+ # This is the same as getting the value from #attr_upcase for one of:
201
+ # * <tt>BODY[HEADER.FIELDS (#{names.join " "})]</tt>
202
+ # * <tt>BODY[HEADER.FIELDS (#{names.join " "})]<#{offset}></tt>
203
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]</tt>
204
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]<#{offset}></tt>
205
+ #
206
+ # See also: #header_fields
207
+ #
208
+ # ==== With +except+
209
+ # When +except+ is sent, returns a subset of the header which contains
210
+ # only the header fields that do _not_ match one of the names in the list.
211
+ #
212
+ # This is the same as getting the value from #attr_upcase for one of:
213
+ # * <tt>BODY[HEADER.FIELDS.NOT (#{names.join " "})]</tt>
214
+ # * <tt>BODY[HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}></tt>
215
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]</tt>
216
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}></tt>
217
+ #
218
+ # See also: #header_fields_not
219
+ def header(*part_nums, fields: nil, except: nil, offset: nil)
220
+ fields && except and
221
+ raise ArgumentError, "conflicting 'fields' and 'except' arguments"
222
+ if fields
223
+ text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase]
224
+ attr_upcase[body_section_attr(part_nums, text, offset: offset)]
225
+ elsif except
226
+ text = "HEADER.FIELDS.NOT (%s)" % [except.join(" ").upcase]
227
+ attr_upcase[body_section_attr(part_nums, text, offset: offset)]
228
+ else
229
+ attr[body_section_attr(part_nums, "HEADER", offset: offset)]
230
+ end
231
+ end
232
+
233
+ # :call-seq:
234
+ # header_fields(*names, part: [], offset: nil) -> string or nil
235
+ #
236
+ # The result from #header when called with <tt>fields: names</tt>.
237
+ def header_fields(first, *rest, part: [], offset: nil)
238
+ header(*part, fields: [first, *rest], offset: offset)
239
+ end
240
+
241
+ # :call-seq:
242
+ # header_fields_not(*names, part: [], offset: nil) -> string or nil
243
+ #
244
+ # The result from #header when called with <tt>except: names</tt>.
245
+ def header_fields_not(first, *rest, part: [], offset: nil)
246
+ header(*part, except: [first, *rest], offset: offset)
247
+ end
248
+
249
+ # :call-seq:
250
+ # mime(*part_nums) -> string or nil
251
+ # mime(*part_nums, offset: bytes) -> string or nil
252
+ #
253
+ # The {[MIME-IMB]}[https://www.rfc-editor.org/rfc/rfc2045.html] header for
254
+ # a message part, if it was fetched.
255
+ #
256
+ # See #part for a description of +part_nums+ and +offset+.
257
+ #
258
+ # This is the same as getting the value for
259
+ # <tt>"BODY[#{part_nums}.MIME]"</tt> or
260
+ # <tt>"BODY[#{part_nums}.MIME]<#{offset}>"</tt> from #attr.
261
+ #
262
+ # See also: #message, #header, and #text.
263
+ def mime(part, *subparts, offset: nil)
264
+ attr[body_section_attr([part, *subparts], "MIME", offset: offset)]
265
+ end
266
+
267
+ # :call-seq:
268
+ # text(*part_nums) -> string or nil
269
+ # text(*part_nums, offset: bytes) -> string or nil
270
+ #
271
+ # The text body of a message or a message part, if it was fetched,
272
+ # omitting the {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html]
273
+ # header.
274
+ #
275
+ # See #part for a description of +part_nums+ and +offset+.
276
+ #
277
+ # This is the same as getting the value from #attr for one of:
278
+ # * <tt>"BODY[TEXT]"</tt>,
279
+ # * <tt>"BODY[TEXT]<#{offset}>"</tt>,
280
+ # * <tt>"BODY[#{section}.TEXT]"</tt>, or
281
+ # * <tt>"BODY[#{section}.TEXT]<#{offset}>"</tt>.
282
+ #
283
+ # See also: #message, #header, and #mime.
284
+ def text(*part, offset: nil)
285
+ attr[body_section_attr(part, "TEXT", offset: offset)]
286
+ end
287
+
288
+ # :call-seq:
289
+ # bodystructure -> BodyStructure struct or nil
290
+ #
291
+ # A BodyStructure object that describes the message, if it was fetched.
292
+ #
293
+ # This is the same as getting the value for <tt>"BODYSTRUCTURE"</tt> from
294
+ # #attr.
295
+ def bodystructure; attr["BODYSTRUCTURE"] end
296
+
297
+ alias body_structure bodystructure
298
+
299
+ # :call-seq: envelope -> Envelope or nil
300
+ #
301
+ # An Envelope object that describes the envelope structure of a message.
302
+ # See the documentation for Envelope for a description of the envelope
303
+ # structure attributes.
304
+ #
305
+ # This is the same as getting the value for <tt>"ENVELOPE"</tt> from
306
+ # #attr.
307
+ def envelope; attr["ENVELOPE"] end
308
+
309
+ # :call-seq: flags -> array of Symbols and Strings, or nil
310
+ #
311
+ # A array of flags that are set for this message. System flags are
312
+ # symbols that have been capitalized by String#capitalize. Keyword flags
313
+ # are strings and their case is not changed.
314
+ #
315
+ # This is the same as getting the value for <tt>"FLAGS"</tt> from #attr.
316
+ #
317
+ # [NOTE:]
318
+ # The +FLAGS+ field is dynamic, and can change for a uniquely identified
319
+ # message.
320
+ def flags; attr["FLAGS"] end
321
+
322
+ # :call-seq: internaldate -> Time or nil
323
+ #
324
+ # The internal date and time of the message on the server. This is not
325
+ # the date and time in the [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]
326
+ # header, but rather a date and time which reflects when the message was
327
+ # received.
328
+ #
329
+ # This is similar to getting the value for <tt>"INTERNALDATE"</tt> from
330
+ # #attr.
331
+ #
332
+ # [NOTE:]
333
+ # <tt>attr["INTERNALDATE"]</tt> returns a string, and this method
334
+ # returns a Time object.
335
+ def internaldate
336
+ attr["INTERNALDATE"]&.then { IMAP.decode_time _1 }
337
+ end
338
+
339
+ alias internal_date internaldate
340
+
341
+ # :call-seq: rfc822 -> String or nil
342
+ #
343
+ # Semantically equivalent to #message with no arguments.
344
+ #
345
+ # This is the same as getting the value for <tt>"RFC822"</tt> from #attr.
346
+ #
347
+ # [NOTE:]
348
+ # +IMAP4rev2+ deprecates <tt>RFC822</tt>.
349
+ def rfc822; attr["RFC822"] end
350
+
351
+ # :call-seq: rfc822_size -> Integer or nil
352
+ #
353
+ # A number expressing the [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]
354
+ # size of the message.
355
+ #
356
+ # This is the same as getting the value for <tt>"RFC822.SIZE"</tt> from
357
+ # #attr.
358
+ #
359
+ # [NOTE:]
360
+ # \IMAP was originally developed for the older
361
+ # RFC822[https://www.rfc-editor.org/rfc/rfc822.html] standard, and as a
362
+ # consequence several fetch items in \IMAP incorporate "RFC822" in their
363
+ # name. With the exception of +RFC822.SIZE+, there are more modern
364
+ # replacements; for example, the modern version of +RFC822.HEADER+ is
365
+ # <tt>BODY.PEEK[HEADER]</tt>. In all cases, "RFC822" should be
366
+ # interpreted as a reference to the updated
367
+ # RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] standard.
368
+ def rfc822_size; attr["RFC822.SIZE"] end
369
+
370
+ # NOTE: a bug in rdoc 6.7 prevents us from adding a call-seq to
371
+ # rfc822_size _and_ aliasing size => rfc822_size. Is it because this
372
+ # class inherits from Struct?
373
+
374
+ # Alias for: rfc822_size
375
+ def size; rfc822_size end
376
+
377
+
378
+ # :call-seq: rfc822_header -> String or nil
379
+ #
380
+ # Semantically equivalent to #header, with no arguments.
381
+ #
382
+ # This is the same as getting the value for <tt>"RFC822.HEADER"</tt> from #attr.
383
+ #
384
+ # [NOTE:]
385
+ # +IMAP4rev2+ deprecates <tt>RFC822.HEADER</tt>.
386
+ def rfc822_header; attr["RFC822.HEADER"] end
387
+
388
+ # :call-seq: rfc822_text -> String or nil
389
+ #
390
+ # Semantically equivalent to #text, with no arguments.
391
+ #
392
+ # This is the same as getting the value for <tt>"RFC822.TEXT"</tt> from
393
+ # #attr.
394
+ #
395
+ # [NOTE:]
396
+ # +IMAP4rev2+ deprecates <tt>RFC822.TEXT</tt>.
397
+ def rfc822_text; attr["RFC822.TEXT"] end
398
+
399
+ # :call-seq: uid -> Integer or nil
400
+ #
401
+ # A number expressing the unique identifier of the message.
402
+ #
403
+ # This is the same as getting the value for <tt>"UID"</tt> from #attr.
404
+ #
405
+ # [NOTE:]
406
+ # For UIDFetchData, this returns the uniqueid at the beginning of the
407
+ # +UIDFETCH+ response, _not_ the value from #attr.
408
+ def uid; attr["UID"] end
409
+
410
+ # :call-seq:
411
+ # binary(*part_nums, offset: nil) -> string or nil
412
+ #
413
+ # Returns the binary representation of a particular MIME part, which has
414
+ # already been decoded according to its Content-Transfer-Encoding.
415
+ #
416
+ # See #part for a description of +part_nums+ and +offset+.
417
+ #
418
+ # This is the same as getting the value of
419
+ # <tt>"BINARY[#{part_nums.join(".")}]"</tt> or
420
+ # <tt>"BINARY[#{part_nums.join(".")}]<#{offset}>"</tt> from #attr.
421
+ #
422
+ # The server must support either
423
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]
424
+ # or the +BINARY+ extension
425
+ # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html].
426
+ #
427
+ # See also: #binary_size, #mime
428
+ def binary(*part_nums, offset: nil)
429
+ attr[section_attr("BINARY", part_nums, offset: offset)]
430
+ end
431
+
432
+ # :call-seq:
433
+ # binary_size(*part_nums) -> integer or nil
434
+ #
435
+ # Returns the decoded size of a particular MIME part (the size to expect
436
+ # in response to a <tt>BINARY</tt> fetch request).
437
+ #
438
+ # See #part for a description of +part_nums+.
439
+ #
440
+ # This is the same as getting the value of
441
+ # <tt>"BINARY.SIZE[#{part_nums.join(".")}]"</tt> from #attr.
442
+ #
443
+ # The server must support either
444
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]
445
+ # or the +BINARY+ extension
446
+ # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html].
447
+ #
448
+ # See also: #binary, #mime
449
+ def binary_size(*part_nums)
450
+ attr[section_attr("BINARY.SIZE", part_nums)]
451
+ end
452
+
453
+ # :call-seq: modseq -> Integer or nil
454
+ #
455
+ # The modification sequence number associated with this IMAP message.
456
+ #
457
+ # This is the same as getting the value for <tt>"MODSEQ"</tt> from #attr.
458
+ #
459
+ # The server must support the +CONDSTORE+ extension
460
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
461
+ #
462
+ # [NOTE:]
463
+ # The +MODSEQ+ field is dynamic, and can change for a uniquely
464
+ # identified message.
465
+ def modseq; attr["MODSEQ"] end
466
+
467
+ # :call-seq: emailid -> string or nil
468
+ #
469
+ # An ObjectID that uniquely identifies the immutable content of a single
470
+ # message.
471
+ #
472
+ # The server must return the same +EMAILID+ for both the source and
473
+ # destination messages after a COPY or MOVE command. However, it is
474
+ # possible for different messages with the same EMAILID to have different
475
+ # mutable attributes, such as flags.
476
+ #
477
+ # This is the same as getting the value for <tt>"EMAILID"</tt> from
478
+ # #attr.
479
+ #
480
+ # The server must support the +OBJECTID+ extension
481
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
482
+ def emailid; attr["EMAILID"] end
483
+
484
+ # :call-seq: threadid -> string or nil
485
+ #
486
+ # An ObjectID that uniquely identifies a set of messages that the server
487
+ # believes should be grouped together.
488
+ #
489
+ # It is generally based on some combination of References, In-Reply-To,
490
+ # and Subject, but the exact implementation is left up to the server
491
+ # implementation. The server should return the same thread identifier for
492
+ # related messages, even if they are in different mailboxes.
493
+ #
494
+ # This is the same as getting the value for <tt>"THREADID"</tt> from
495
+ # #attr.
496
+ #
497
+ # The server must support the +OBJECTID+ extension
498
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html].
499
+ def threadid; attr["THREADID"] end
500
+
501
+ private
502
+
503
+ def body_section_attr(...) section_attr("BODY", ...) end
504
+
505
+ def section_attr(attr, part = [], text = nil, offset: nil)
506
+ spec = Array(part).flatten.map { Integer(_1) }
507
+ spec << text if text
508
+ spec = spec.join(".")
509
+ if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)] else "%s[%s]" % [attr, spec] end
510
+ end
511
+ end
512
+
513
+ # Net::IMAP::FetchData represents the contents of a +FETCH+ response.
514
+ # Net::IMAP#fetch, Net::IMAP#uid_fetch, Net::IMAP#store, and
515
+ # Net::IMAP#uid_store all return arrays of FetchData objects, except when
516
+ # the +UIDONLY+ extension is enabled.
517
+ #
518
+ # See FetchStruct documentation for a list of standard message attributes.
519
+ class FetchData < FetchStruct.new(:seqno, :attr)
520
+ ##
521
+ # method: seqno
522
+ # :call-seq: seqno -> Integer
523
+ #
524
+ # The message sequence number.
525
+ #
526
+ # [NOTE:]
527
+ # This is not the same as the unique identifier (UID), not even for the
528
+ # Net::IMAP#uid_fetch result. The UID is available from #uid, if it was
529
+ # returned.
530
+ #
531
+ # [NOTE:]
532
+ # UIDFetchData will raise a NoMethodError.
533
+
534
+ ##
535
+ # method: attr
536
+ # :call-seq: attr -> hash
537
+ #
538
+ # Each key specifies a message attribute, and the value is the
539
+ # corresponding data item. Standard data items have corresponding
540
+ # accessor methods. The definitions of each attribute type is documented
541
+ # on its accessor.
542
+ #
543
+ # See FetchStruct documentation for message attribute accessors.
544
+ #
545
+ # [NOTE:]
546
+ # #seqno is not a message attribute.
547
+ end
548
+
549
+ # Net::IMAP::UIDFetchData represents the contents of a +UIDFETCH+ response,
550
+ # When the +UIDONLY+ extension has been enabled, Net::IMAP#uid_fetch and
551
+ # Net::IMAP#uid_store will both return an array of UIDFetchData objects.
552
+ #
553
+ # UIDFetchData contains the same message attributes as FetchData. However,
554
+ # +UIDFETCH+ responses return the UID at the beginning of the response,
555
+ # replacing FetchData#seqno. UIDFetchData never contains a message sequence
556
+ # number.
557
+ #
558
+ # See FetchStruct documentation for a list of standard message attributes.
559
+ class UIDFetchData < FetchStruct.new(:uid, :attr)
560
+ ##
561
+ # method: uid
562
+ # call-seq: uid -> Integer
563
+ #
564
+ # A number expressing the unique identifier of the message.
565
+ #
566
+ # [NOTE:]
567
+ # Although #attr may _also_ have a redundant +UID+ attribute, #uid
568
+ # returns the uniqueid at the beginning of the +UIDFETCH+ response.
569
+
570
+ ##
571
+ # method: attr
572
+ # call-seq: attr -> hash
573
+ #
574
+ # Each key specifies a message attribute, and the value is the
575
+ # corresponding data item. Standard data items have corresponding
576
+ # accessor methods. The definitions of each attribute type is documented
577
+ # on its accessor.
578
+ #
579
+ # See FetchStruct documentation for message attribute accessors.
580
+ #
581
+ # [NOTE:]
582
+ # #uid is not a message attribute. Although the server may return a
583
+ # +UID+ message attribute, it is not required to. #uid is taken from
584
+ # its corresponding +UIDFETCH+ field.
585
+
586
+ # UIDFetchData will print a warning if <tt>#attr["UID"]</tt> is present
587
+ # but not identical to #uid.
588
+ def initialize(...)
589
+ super
590
+ attr and
591
+ attr_uid = attr["UID"] and
592
+ attr_uid != uid and
593
+ warn "#{self.class} UIDs do not match (#{attr_uid} != #{uid})"
594
+ end
595
+ end
596
+ end
597
+ end
@@ -71,7 +71,7 @@ module Net
71
71
  # Mailbox name attributes are not case-sensitive. <em>The current
72
72
  # implementation</em> normalizes mailbox attribute case using
73
73
  # String#capitalize, such as +:Noselect+ (not +:NoSelect+). The constants
74
- # (such as NO_SELECT) can also be used for comparison. The contants have
74
+ # (such as NO_SELECT) can also be used for comparison. The constants have
75
75
  # been defined both with and without underscores between words.
76
76
  #
77
77
  # <em>The descriptions here were copied from</em> {[RFC-9051 §