net-imap 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da8cb634d9ee1613035c81a25d28ae9f36b3d0985bcb61da9a38aacb87854a5f
4
- data.tar.gz: e504b145415da3a025c6f4ab973b212764c75ef4214d0fed485fdb0b17e83752
3
+ metadata.gz: e4e6a6afb6888a5be474a97195f41e06a2e1706d9f65a0700f17bce7f6dd82af
4
+ data.tar.gz: 3dd62a4e251d29e9e72e5538f548623662ed2ff16da46deadf8843585f8db658
5
5
  SHA512:
6
- metadata.gz: 0cef39a5ac6454ded84c395fb2671df678e17611d7eff42665113c21f955ebcb6ffdf9035dd1e6fdb3de38aa38f43431537ded3d484ffba7d699b478bb8c3e1e
7
- data.tar.gz: 23f777092b2940b725685285a6372b56744a9968fcc76872ebf9ef3136fb3665a89a85174dc34636e547b90d8d3a1945627ce79390f10122aadb57b57004f789
6
+ metadata.gz: bc73faf474cb22965f3ec95bdae48d13514505d4072f0fe3ca47f1dbccc8b04aad404e90ecd2b04afa7dc401dc2f66d087fea316b26e46edf1403945e7e3c305
7
+ data.tar.gz: 9c10a3b062c88f6145deb1adbbe3e8a2574e9f4dac39c2b7706fbc84643ee031891dae270197f910ceda3e5d0513f0929d574889e506c53a6f5048cb9be36dfa
data/.gitignore CHANGED
@@ -8,4 +8,5 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /Gemfile.lock
11
- benchmarks/Gemfile*
11
+ /benchmarks/Gemfile*
12
+ /benchmarks/parser.yml
data/docs/styles.css CHANGED
@@ -22,15 +22,3 @@ body {
22
22
  */
23
23
  font-weight: 400;
24
24
  }
25
-
26
- @media only screen and (min-width: 600px) {
27
- nav {
28
- height: 100%;
29
- position: fixed;
30
- overflow-y: scroll;
31
- }
32
-
33
- nav #class-metadata {
34
- margin-bottom: 5em;
35
- }
36
- }
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
+ require "time"
4
5
 
5
6
  require_relative "errors"
6
7
 
@@ -102,8 +103,16 @@ module Net
102
103
  #
103
104
  # Decodes +string+ as an IMAP4 formatted "date-time".
104
105
  #
105
- # Note that double quotes are not optional. See STRFTIME.
106
+ # NOTE: Although double-quotes are not optional in the IMAP grammar,
107
+ # Net::IMAP currently parses "date-time" values as "quoted" strings and this
108
+ # removes the quotation marks. To be useful for strings which have already
109
+ # been parsed as a quoted string, this method makes double-quotes optional.
110
+ #
111
+ # See STRFTIME.
106
112
  def self.decode_datetime(string)
113
+ unless string.start_with?(?") && string.end_with?(?")
114
+ string = '"%s"' % [string]
115
+ end
107
116
  DateTime.strptime(string, STRFTIME)
108
117
  end
109
118
 
@@ -113,7 +122,10 @@ module Net
113
122
  #
114
123
  # Same as +decode_datetime+, but returning a Time instead.
115
124
  def self.decode_time(string)
116
- decode_datetime(string).to_time
125
+ unless string.start_with?(?") && string.end_with?(?")
126
+ string = '"%s"' % [string]
127
+ end
128
+ Time.strptime(string, STRFTIME)
117
129
  end
118
130
 
119
131
  class << self
@@ -0,0 +1,518 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ # Net::IMAP::FetchData represents the contents of a FETCH response.
7
+ # Net::IMAP#fetch and Net::IMAP#uid_fetch both return an array of
8
+ # FetchData 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
+ # >>>
68
+ # Additional static fields are defined in other \IMAP extensions, but
69
+ # Net::IMAP can't parse them yet.
70
+ #
71
+ # ==== Dynamic message attributes
72
+ #
73
+ # Some message attributes can be dynamically changed, for example using the
74
+ # {STORE command}[rdoc-ref:Net::IMAP#store].
75
+ #
76
+ # The only dynamic message attribute defined by
77
+ # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and
78
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] is:
79
+ #
80
+ # * <b><tt>"FLAGS"</tt></b> --- See #flags.
81
+ #
82
+ # Net::IMAP supports dynamic attributes defined by the following extensions:
83
+ #
84
+ # * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]:
85
+ # * <b><tt>"MODSEQ"</tt></b> --- See #modseq.
86
+ # * +X-GM-EXT-1+ {[non-standard Gmail
87
+ # extension]}[https://developers.google.com/gmail/imap/imap-extensions]
88
+ # * <b><tt>"X-GM-LABELS"</tt></b> --- Gmail labels. Access via #attr.
89
+ #
90
+ # [Note:]
91
+ # >>>
92
+ # Additional dynamic fields are defined in other \IMAP extensions, but
93
+ # Net::IMAP can't parse them yet.
94
+ #
95
+ # === Implicitly setting <tt>\Seen</tt> and using +PEEK+
96
+ #
97
+ # Unless the mailbox is has been opened as read-only, fetching
98
+ # <tt>BODY[#{section}]</tt> or <tt>BINARY[#{section}]</tt>
99
+ # will implicitly set the <tt>\Seen</tt> flag. To avoid this, fetch using
100
+ # <tt>BODY.PEEK[#{section}]</tt> or <tt>BINARY.PEEK[#{section}]</tt>
101
+ # instead.
102
+ #
103
+ # Note that the data will always be _returned_ without <tt>".PEEK"</tt>, in
104
+ # <tt>BODY[#{specifier}]</tt> or <tt>BINARY[#{section}]</tt>.
105
+ #
106
+ class FetchData < Struct.new(:seqno, :attr)
107
+ ##
108
+ # method: seqno
109
+ # :call-seq: seqno -> Integer
110
+ #
111
+ # The message sequence number.
112
+ #
113
+ # [Note]
114
+ # This is never the unique identifier (UID), not even for the
115
+ # Net::IMAP#uid_fetch result. The UID is available from #uid, if it was
116
+ # returned.
117
+
118
+ ##
119
+ # method: attr
120
+ # :call-seq: attr -> hash
121
+ #
122
+ # Each key specifies a message attribute, and the value is the
123
+ # corresponding data item. Standard data items have corresponding
124
+ # accessor methods. The definitions of each attribute type is documented
125
+ # on its accessor.
126
+ #
127
+ # >>>
128
+ # *Note:* #seqno is not a message attribute.
129
+
130
+ # :call-seq: attr_upcase -> hash
131
+ #
132
+ # A transformation of #attr, with all the keys converted to upper case.
133
+ #
134
+ # Header field names are case-preserved but not not case-sensitive, so
135
+ # this is used by #header_fields and #header_fields_not.
136
+ def attr_upcase; attr.transform_keys(&:upcase) end
137
+
138
+ # :call-seq:
139
+ # body -> body structure or nil
140
+ #
141
+ # Returns an alternate form of #bodystructure, without any extension data.
142
+ #
143
+ # This is the same as getting the value for <tt>"BODY"</tt> from #attr.
144
+ #
145
+ # [Note]
146
+ # Use #message, #part, #header, #header_fields, #header_fields_not,
147
+ # #text, or #mime to retrieve <tt>BODY[#{section_spec}]</tt> attributes.
148
+ def body; attr["BODY"] end
149
+
150
+ # :call-seq:
151
+ # message(offset: bytes) -> string or nil
152
+ #
153
+ # The RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html]
154
+ # expression of the entire message, as a string.
155
+ #
156
+ # See #part for a description of +offset+.
157
+ #
158
+ # <em>RFC5322 messages can be parsed using the "mail" gem.</em>
159
+ #
160
+ # This is the same as getting the value for <tt>"BODY[]"</tt> or
161
+ # <tt>"BODY[]<#{offset}>"</tt> from #attr.
162
+ #
163
+ # See also: #header, #text, and #mime.
164
+ def message(offset: nil) attr[body_section_attr(offset: offset)] end
165
+
166
+ # :call-seq:
167
+ # part(*part_nums, offset: bytes) -> string or nil
168
+ #
169
+ # The string representation of a particular MIME part.
170
+ #
171
+ # +part_nums+ forms a path of MIME part numbers, counting up from +1+,
172
+ # which may specify an arbitrarily nested part, similarly to Array#dig.
173
+ # Messages that don't use MIME, or MIME messages that are not multipart
174
+ # and don't hold an encapsulated message, only have part +1+.
175
+ #
176
+ # If a zero-based +offset+ is given, the returned string is a substring of
177
+ # the entire contents, starting at that origin octet. This means that
178
+ # <tt>BODY[]<0></tt> MAY be truncated, but <tt>BODY[]</tt> is never
179
+ # truncated.
180
+ #
181
+ # This is the same as getting the value of
182
+ # <tt>"BODY[#{part_nums.join(".")}]"</tt> or
183
+ # <tt>"BODY[#{part_nums.join(".")}]<#{offset}>"</tt> from #attr.
184
+ #
185
+ # See also: #message, #header, #text, and #mime.
186
+ def part(index, *subparts, offset: nil)
187
+ attr[body_section_attr([index, *subparts], offset: offset)]
188
+ end
189
+
190
+ # :call-seq:
191
+ # header(*part_nums, offset: nil) -> string or nil
192
+ # header(*part_nums, fields: names, offset: nil) -> string or nil
193
+ # header(*part_nums, except: names, offset: nil) -> string or nil
194
+ #
195
+ # The {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] header of a
196
+ # message or of an encapsulated
197
+ # {[MIME-IMT]}[https://www.rfc-editor.org/rfc/rfc2046.html]
198
+ # MESSAGE/RFC822 or MESSAGE/GLOBAL message.
199
+ #
200
+ # <em>Headers can be parsed using the "mail" gem.</em>
201
+ #
202
+ # See #part for a description of +part_nums+ and +offset+.
203
+ #
204
+ # ==== Without +fields+ or +except+
205
+ # This is the same as getting the value from #attr for one of:
206
+ # * <tt>BODY[HEADER]</tt>
207
+ # * <tt>BODY[HEADER]<#{offset}></tt>
208
+ # * <tt>BODY[#{part_nums.join "."}.HEADER]"</tt>
209
+ # * <tt>BODY[#{part_nums.join "."}.HEADER]<#{offset}>"</tt>
210
+ #
211
+ # ==== With +fields+
212
+ # When +fields+ is sent, returns a subset of the header which contains
213
+ # only the header fields that match one of the names in the list.
214
+ #
215
+ # This is the same as getting the value from #attr_upcase for one of:
216
+ # * <tt>BODY[HEADER.FIELDS (#{names.join " "})]</tt>
217
+ # * <tt>BODY[HEADER.FIELDS (#{names.join " "})]<#{offset}></tt>
218
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]</tt>
219
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]<#{offset}></tt>
220
+ #
221
+ # See also: #header_fields
222
+ #
223
+ # ==== With +except+
224
+ # When +except+ is sent, returns a subset of the header which contains
225
+ # only the header fields that do _not_ match one of the names in the list.
226
+ #
227
+ # This is the same as getting the value from #attr_upcase for one of:
228
+ # * <tt>BODY[HEADER.FIELDS.NOT (#{names.join " "})]</tt>
229
+ # * <tt>BODY[HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}></tt>
230
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]</tt>
231
+ # * <tt>BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}></tt>
232
+ #
233
+ # See also: #header_fields_not
234
+ def header(*part_nums, fields: nil, except: nil, offset: nil)
235
+ fields && except and
236
+ raise ArgumentError, "conflicting 'fields' and 'except' arguments"
237
+ if fields
238
+ text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase]
239
+ attr_upcase[body_section_attr(part_nums, text, offset: offset)]
240
+ elsif except
241
+ text = "HEADER.FIELDS.NOT (%s)" % [except.join(" ").upcase]
242
+ attr_upcase[body_section_attr(part_nums, text, offset: offset)]
243
+ else
244
+ attr[body_section_attr(part_nums, "HEADER", offset: offset)]
245
+ end
246
+ end
247
+
248
+ # :call-seq:
249
+ # header_fields(*names, part: [], offset: nil) -> string or nil
250
+ #
251
+ # The result from #header when called with <tt>fields: names</tt>.
252
+ def header_fields(first, *rest, part: [], offset: nil)
253
+ header(*part, fields: [first, *rest], offset: offset)
254
+ end
255
+
256
+ # :call-seq:
257
+ # header_fields_not(*names, part: [], offset: nil) -> string or nil
258
+ #
259
+ # The result from #header when called with <tt>except: names</tt>.
260
+ def header_fields_not(first, *rest, part: [], offset: nil)
261
+ header(*part, except: [first, *rest], offset: offset)
262
+ end
263
+
264
+ # :call-seq:
265
+ # mime(*part_nums) -> string or nil
266
+ # mime(*part_nums, offset: bytes) -> string or nil
267
+ #
268
+ # The {[MIME-IMB]}[https://www.rfc-editor.org/rfc/rfc2045.html] header for
269
+ # a message part, if it was fetched.
270
+ #
271
+ # See #part for a description of +part_nums+ and +offset+.
272
+ #
273
+ # This is the same as getting the value for
274
+ # <tt>"BODY[#{part_nums}.MIME]"</tt> or
275
+ # <tt>"BODY[#{part_nums}.MIME]<#{offset}>"</tt> from #attr.
276
+ #
277
+ # See also: #message, #header, and #text.
278
+ def mime(part, *subparts, offset: nil)
279
+ attr[body_section_attr([part, *subparts], "MIME", offset: offset)]
280
+ end
281
+
282
+ # :call-seq:
283
+ # text(*part_nums) -> string or nil
284
+ # text(*part_nums, offset: bytes) -> string or nil
285
+ #
286
+ # The text body of a message or a message part, if it was fetched,
287
+ # omitting the {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html]
288
+ # header.
289
+ #
290
+ # See #part for a description of +part_nums+ and +offset+.
291
+ #
292
+ # This is the same as getting the value from #attr for one of:
293
+ # * <tt>"BODY[TEXT]"</tt>,
294
+ # * <tt>"BODY[TEXT]<#{offset}>"</tt>,
295
+ # * <tt>"BODY[#{section}.TEXT]"</tt>, or
296
+ # * <tt>"BODY[#{section}.TEXT]<#{offset}>"</tt>.
297
+ #
298
+ # See also: #message, #header, and #mime.
299
+ def text(*part, offset: nil)
300
+ attr[body_section_attr(part, "TEXT", offset: offset)]
301
+ end
302
+
303
+ # :call-seq:
304
+ # bodystructure -> BodyStructure struct or nil
305
+ #
306
+ # A BodyStructure object that describes the message, if it was fetched.
307
+ #
308
+ # This is the same as getting the value for <tt>"BODYSTRUCTURE"</tt> from
309
+ # #attr.
310
+ def bodystructure; attr["BODYSTRUCTURE"] end
311
+ alias body_structure bodystructure
312
+
313
+ # :call-seq: envelope -> Envelope or nil
314
+ #
315
+ # An Envelope object that describes the envelope structure of a message.
316
+ # See the documentation for Envelope for a description of the envelope
317
+ # structure attributes.
318
+ #
319
+ # This is the same as getting the value for <tt>"ENVELOPE"</tt> from
320
+ # #attr.
321
+ def envelope; attr["ENVELOPE"] end
322
+
323
+ # :call-seq: flags -> array of Symbols and Strings
324
+ #
325
+ # A array of flags that are set for this message. System flags are
326
+ # symbols that have been capitalized by String#capitalize. Keyword flags
327
+ # are strings and their case is not changed.
328
+ #
329
+ # This is the same as getting the value for <tt>"FLAGS"</tt> from #attr.
330
+ #
331
+ # [Note]
332
+ # The +FLAGS+ field is dynamic, and can change for a uniquely identified
333
+ # message.
334
+ def flags; attr["FLAGS"] end
335
+
336
+ # :call-seq: internaldate -> Time or nil
337
+ #
338
+ # The internal date and time of the message on the server. This is not
339
+ # the date and time in the [RFC5322[https://tools.ietf.org/html/rfc5322]]
340
+ # header, but rather a date and time which reflects when the message was
341
+ # received.
342
+ #
343
+ # This is similar to getting the value for <tt>"INTERNALDATE"</tt> from
344
+ # #attr.
345
+ #
346
+ # [Note]
347
+ # <tt>attr["INTERNALDATE"]</tt> returns a string, and this method
348
+ # returns a Time object.
349
+ def internaldate
350
+ attr["INTERNALDATE"]&.then { IMAP.decode_time _1 }
351
+ end
352
+ alias internal_date internaldate
353
+
354
+ # :call-seq: rfc822 -> String
355
+ #
356
+ # Semantically equivalent to #message with no arguments.
357
+ #
358
+ # This is the same as getting the value for <tt>"RFC822"</tt> from #attr.
359
+ #
360
+ # [Note]
361
+ # +IMAP4rev2+ deprecates <tt>RFC822</tt>.
362
+ def rfc822; attr["RFC822"] end
363
+
364
+ # :call-seq: rfc822_size -> Integer
365
+ #
366
+ # A number expressing the [RFC5322[https://tools.ietf.org/html/rfc5322]]
367
+ # size of the message.
368
+ #
369
+ # This is the same as getting the value for <tt>"RFC822.SIZE"</tt> from
370
+ # #attr.
371
+ #
372
+ # [Note]
373
+ # \IMAP was originally developed for the older
374
+ # RFC822[https://www.rfc-editor.org/rfc/rfc822.html] standard, and as a
375
+ # consequence several fetch items in \IMAP incorporate "RFC822" in their
376
+ # name. With the exception of +RFC822.SIZE+, there are more modern
377
+ # replacements; for example, the modern version of +RFC822.HEADER+ is
378
+ # <tt>BODY.PEEK[HEADER]</tt>. In all cases, "RFC822" should be
379
+ # interpreted as a reference to the updated
380
+ # RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] standard.
381
+ def rfc822_size; attr["RFC822.SIZE"] end
382
+ alias size rfc822_size
383
+
384
+ # :call-seq: rfc822_header -> String
385
+ #
386
+ # Semantically equivalent to #header, with no arguments.
387
+ #
388
+ # This is the same as getting the value for <tt>"RFC822.HEADER"</tt> from #attr.
389
+ #
390
+ # [Note]
391
+ # +IMAP4rev2+ deprecates <tt>RFC822.HEADER</tt>.
392
+ def rfc822_header; attr["RFC822.HEADER"] end
393
+
394
+ # :call-seq: rfc822_text -> String
395
+ #
396
+ # Semantically equivalent to #text, with no arguments.
397
+ #
398
+ # This is the same as getting the value for <tt>"RFC822.TEXT"</tt> from
399
+ # #attr.
400
+ #
401
+ # [Note]
402
+ # +IMAP4rev2+ deprecates <tt>RFC822.TEXT</tt>.
403
+ def rfc822_text; attr["RFC822.TEXT"] end
404
+
405
+ # :call-seq: uid -> Integer
406
+ #
407
+ # A number expressing the unique identifier of the message.
408
+ #
409
+ # This is the same as getting the value for <tt>"UID"</tt> from #attr.
410
+ def uid; attr["UID"] end
411
+
412
+ # :call-seq:
413
+ # binary(*part_nums, offset: nil) -> string or nil
414
+ #
415
+ # Returns the binary representation of a particular MIME part, which has
416
+ # already been decoded according to its Content-Transfer-Encoding.
417
+ #
418
+ # See #part for a description of +part_nums+ and +offset+.
419
+ #
420
+ # This is the same as getting the value of
421
+ # <tt>"BINARY[#{part_nums.join(".")}]"</tt> or
422
+ # <tt>"BINARY[#{part_nums.join(".")}]<#{offset}>"</tt> from #attr.
423
+ #
424
+ # The server must support either
425
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]
426
+ # or the +BINARY+ extension
427
+ # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html].
428
+ #
429
+ # See also: #binary_size, #mime
430
+ def binary(*part_nums, offset: nil)
431
+ attr[section_attr("BINARY", part_nums, offset: offset)]
432
+ end
433
+
434
+ # :call-seq:
435
+ # binary_size(*part_nums) -> integer or nil
436
+ #
437
+ # Returns the decoded size of a particular MIME part (the size to expect
438
+ # in response to a <tt>BINARY</tt> fetch request).
439
+ #
440
+ # See #part for a description of +part_nums+.
441
+ #
442
+ # This is the same as getting the value of
443
+ # <tt>"BINARY.SIZE[#{part_nums.join(".")}]"</tt> from #attr.
444
+ #
445
+ # The server must support either
446
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]
447
+ # or the +BINARY+ extension
448
+ # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html].
449
+ #
450
+ # See also: #binary, #mime
451
+ def binary_size(*part_nums)
452
+ attr[section_attr("BINARY.SIZE", part_nums)]
453
+ end
454
+
455
+ # :call-seq: modseq -> Integer
456
+ #
457
+ # The modification sequence number associated with this IMAP message.
458
+ #
459
+ # This is the same as getting the value for <tt>"MODSEQ"</tt> from #attr.
460
+ #
461
+ # The server must support the +CONDSTORE+ extension
462
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
463
+ #
464
+ # [Note]
465
+ # The +MODSEQ+ field is dynamic, and can change for a uniquely
466
+ # identified message.
467
+ def modseq; attr["MODSEQ"] end
468
+
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
+
503
+ private
504
+
505
+ def body_section_attr(...) section_attr("BODY", ...) end
506
+
507
+ def section_attr(attr, part = [], text = nil, offset: nil)
508
+ spec = Array(part).flatten.map { Integer(_1) }
509
+ spec << text if text
510
+ spec = spec.join(".")
511
+ if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)]
512
+ else "%s[%s]" % [attr, spec]
513
+ end
514
+ end
515
+
516
+ end
517
+ end
518
+ end