net-imap 0.5.1 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ # An "extended search" response (+ESEARCH+). ESearchResult should be
6
+ # returned (instead of SearchResult) by IMAP#search, IMAP#uid_search,
7
+ # IMAP#sort, and IMAP#uid_sort under any of the following conditions:
8
+ #
9
+ # * Return options were specified for IMAP#search or IMAP#uid_search.
10
+ # The server must support a search extension which allows
11
+ # RFC4466[https://www.rfc-editor.org/rfc/rfc4466.html] +return+ options,
12
+ # such as +ESEARCH+, +PARTIAL+, or +IMAP4rev2+.
13
+ # * Return options were specified for IMAP#sort or IMAP#uid_sort.
14
+ # The server must support the +ESORT+ extension
15
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html#section-3].
16
+ #
17
+ # *NOTE:* IMAP#search and IMAP#uid_search do not support +ESORT+ yet.
18
+ # * The server supports +IMAP4rev2+ but _not_ +IMAP4rev1+, or +IMAP4rev2+
19
+ # has been enabled. +IMAP4rev2+ requires +ESEARCH+ results.
20
+ #
21
+ # Note that some servers may claim to support a search extension which
22
+ # requires an +ESEARCH+ result, such as +PARTIAL+, but still only return a
23
+ # +SEARCH+ result when +return+ options are specified.
24
+ #
25
+ # Some search extensions may result in the server sending ESearchResult
26
+ # responses after the initiating command has completed. Use
27
+ # IMAP#add_response_handler to handle these responses.
28
+ class ESearchResult < Data.define(:tag, :uid, :data)
29
+ def initialize(tag: nil, uid: nil, data: nil)
30
+ tag => String | nil; tag = -tag if tag
31
+ uid => true | false | nil; uid = !!uid
32
+ data => Array | nil; data ||= []; data.freeze
33
+ super
34
+ end
35
+
36
+ # :call-seq: to_a -> Array of integers
37
+ #
38
+ # When either #all or #partial contains a SequenceSet of message sequence
39
+ # numbers or UIDs, +to_a+ returns that set as an array of integers.
40
+ #
41
+ # When both #all and #partial are +nil+, either because the server
42
+ # returned no results or because +ALL+ and +PARTIAL+ were not included in
43
+ # the IMAP#search +RETURN+ options, #to_a returns an empty array.
44
+ #
45
+ # Note that SearchResult also implements +to_a+, so it can be used without
46
+ # checking if the server returned +SEARCH+ or +ESEARCH+ data.
47
+ def to_a; all&.numbers || partial&.to_a || [] end
48
+
49
+ ##
50
+ # attr_reader: tag
51
+ #
52
+ # The tag string for the command that caused this response to be returned.
53
+ #
54
+ # When +nil+, this response was not caused by a particular command.
55
+
56
+ ##
57
+ # attr_reader: uid
58
+ #
59
+ # Indicates whether #data in this response refers to UIDs (when +true+) or
60
+ # to message sequence numbers (when +false+).
61
+
62
+ ##
63
+ alias uid? uid
64
+
65
+ ##
66
+ # attr_reader: data
67
+ #
68
+ # Search return data, as an array of <tt>[name, value]</tt> pairs. Most
69
+ # return data corresponds to a search +return+ option with the same name.
70
+ #
71
+ # Note that some return data names may be used more than once per result.
72
+ #
73
+ # This data can be more simply retrieved by #min, #max, #all, #count,
74
+ # #modseq, and other methods.
75
+
76
+ # :call-seq: min -> integer or nil
77
+ #
78
+ # The lowest message number/UID that satisfies the SEARCH criteria.
79
+ #
80
+ # Returns +nil+ when the associated search command has no results, or when
81
+ # the +MIN+ return option wasn't specified.
82
+ #
83
+ # Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
84
+ # +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
85
+ def min; data.assoc("MIN")&.last end
86
+
87
+ # :call-seq: max -> integer or nil
88
+ #
89
+ # The highest message number/UID that satisfies the SEARCH criteria.
90
+ #
91
+ # Returns +nil+ when the associated search command has no results, or when
92
+ # the +MAX+ return option wasn't specified.
93
+ #
94
+ # Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
95
+ # +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
96
+ def max; data.assoc("MAX")&.last end
97
+
98
+ # :call-seq: all -> sequence set or nil
99
+ #
100
+ # A SequenceSet containing all message sequence numbers or UIDs that
101
+ # satisfy the SEARCH criteria.
102
+ #
103
+ # Returns +nil+ when the associated search command has no results, or when
104
+ # the +ALL+ return option was not specified but other return options were.
105
+ #
106
+ # Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
107
+ # +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
108
+ #
109
+ # See also: #to_a
110
+ def all; data.assoc("ALL")&.last end
111
+
112
+ # :call-seq: count -> integer or nil
113
+ #
114
+ # Returns the number of messages that satisfy the SEARCH criteria.
115
+ #
116
+ # Returns +nil+ when the associated search command has no results, or when
117
+ # the +COUNT+ return option wasn't specified.
118
+ #
119
+ # Requires +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.1] or
120
+ # +IMAP4rev2+ {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.4].
121
+ def count; data.assoc("COUNT")&.last end
122
+
123
+ # :call-seq: modseq -> integer or nil
124
+ #
125
+ # The highest +mod-sequence+ of all messages being returned.
126
+ #
127
+ # Returns +nil+ when the associated search command has no results, or when
128
+ # the +MODSEQ+ search criterion wasn't specified.
129
+ #
130
+ # Note that there is no search +return+ option for +MODSEQ+. It will be
131
+ # returned whenever the +CONDSTORE+ extension has been enabled. Using the
132
+ # +MODSEQ+ search criteria will implicitly enable +CONDSTORE+.
133
+ #
134
+ # Requires +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]
135
+ # and +ESEARCH+ {[RFC4731]}[https://www.rfc-editor.org/rfc/rfc4731.html#section-3.2].
136
+ def modseq; data.assoc("MODSEQ")&.last end
137
+
138
+ # Returned by ESearchResult#partial.
139
+ #
140
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
141
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
142
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
143
+ #
144
+ # See also: #to_a
145
+ class PartialResult < Data.define(:range, :results)
146
+ def initialize(range:, results:)
147
+ range => Range
148
+ results = SequenceSet[results] unless results.nil?
149
+ super
150
+ end
151
+
152
+ ##
153
+ # method: range
154
+ # :call-seq: range -> range
155
+
156
+ ##
157
+ # method: results
158
+ # :call-seq: results -> sequence set or nil
159
+
160
+ # Converts #results to an array of integers.
161
+ #
162
+ # See also: ESearchResult#to_a.
163
+ def to_a; results&.numbers || [] end
164
+ end
165
+
166
+ # :call-seq: partial -> PartialResult or nil
167
+ #
168
+ # A PartialResult containing a subset of the message sequence numbers or
169
+ # UIDs that satisfy the SEARCH criteria.
170
+ #
171
+ # Requires +PARTIAL+ {[RFC9394]}[https://www.rfc-editor.org/rfc/rfc9394.html]
172
+ # or <tt>CONTEXT=SEARCH</tt>/<tt>CONTEXT=SORT</tt>
173
+ # {[RFC5267]}[https://www.rfc-editor.org/rfc/rfc5267.html]
174
+ #
175
+ # See also: #to_a
176
+ def partial; data.assoc("PARTIAL")&.last end
177
+
178
+ end
179
+ end
180
+ end
@@ -3,9 +3,9 @@
3
3
  module Net
4
4
  class IMAP < Protocol
5
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.
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
9
  #
10
10
  # === Fetch attributes
11
11
  #
@@ -63,8 +63,7 @@ module Net
63
63
  # * <b><tt>"X-GM-MSGID"</tt></b> --- unique message ID. Access via #attr.
64
64
  # * <b><tt>"X-GM-THRID"</tt></b> --- Thread ID. Access via #attr.
65
65
  #
66
- # [Note:]
67
- # >>>
66
+ # [NOTE:]
68
67
  # Additional static fields are defined in other \IMAP extensions, but
69
68
  # Net::IMAP can't parse them yet.
70
69
  #
@@ -87,34 +86,23 @@ module Net
87
86
  # extension]}[https://developers.google.com/gmail/imap/imap-extensions]
88
87
  # * <b><tt>"X-GM-LABELS"</tt></b> --- Gmail labels. Access via #attr.
89
88
  #
90
- # [Note:]
91
- # >>>
89
+ # [NOTE:]
92
90
  # Additional dynamic fields are defined in other \IMAP extensions, but
93
91
  # Net::IMAP can't parse them yet.
94
92
  #
95
93
  # === Implicitly setting <tt>\Seen</tt> and using +PEEK+
96
94
  #
97
- # Unless the mailbox is has been opened as read-only, fetching
95
+ # Unless the mailbox has been opened as read-only, fetching
98
96
  # <tt>BODY[#{section}]</tt> or <tt>BINARY[#{section}]</tt>
99
97
  # will implicitly set the <tt>\Seen</tt> flag. To avoid this, fetch using
100
98
  # <tt>BODY.PEEK[#{section}]</tt> or <tt>BINARY.PEEK[#{section}]</tt>
101
99
  # instead.
102
100
  #
103
- # Note that the data will always be _returned_ without <tt>".PEEK"</tt>, in
104
- # <tt>BODY[#{specifier}]</tt> or <tt>BINARY[#{section}]</tt>.
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>.
105
104
  #
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
-
105
+ class FetchStruct < Struct
118
106
  ##
119
107
  # method: attr
120
108
  # :call-seq: attr -> hash
@@ -123,9 +111,6 @@ module Net
123
111
  # corresponding data item. Standard data items have corresponding
124
112
  # accessor methods. The definitions of each attribute type is documented
125
113
  # on its accessor.
126
- #
127
- # >>>
128
- # *Note:* #seqno is not a message attribute.
129
114
 
130
115
  # :call-seq: attr_upcase -> hash
131
116
  #
@@ -142,7 +127,7 @@ module Net
142
127
  #
143
128
  # This is the same as getting the value for <tt>"BODY"</tt> from #attr.
144
129
  #
145
- # [Note]
130
+ # [NOTE:]
146
131
  # Use #message, #part, #header, #header_fields, #header_fields_not,
147
132
  # #text, or #mime to retrieve <tt>BODY[#{section_spec}]</tt> attributes.
148
133
  def body; attr["BODY"] end
@@ -235,7 +220,7 @@ module Net
235
220
  fields && except and
236
221
  raise ArgumentError, "conflicting 'fields' and 'except' arguments"
237
222
  if fields
238
- text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase]
223
+ text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase]
239
224
  attr_upcase[body_section_attr(part_nums, text, offset: offset)]
240
225
  elsif except
241
226
  text = "HEADER.FIELDS.NOT (%s)" % [except.join(" ").upcase]
@@ -308,6 +293,7 @@ module Net
308
293
  # This is the same as getting the value for <tt>"BODYSTRUCTURE"</tt> from
309
294
  # #attr.
310
295
  def bodystructure; attr["BODYSTRUCTURE"] end
296
+
311
297
  alias body_structure bodystructure
312
298
 
313
299
  # :call-seq: envelope -> Envelope or nil
@@ -320,7 +306,7 @@ module Net
320
306
  # #attr.
321
307
  def envelope; attr["ENVELOPE"] end
322
308
 
323
- # :call-seq: flags -> array of Symbols and Strings
309
+ # :call-seq: flags -> array of Symbols and Strings, or nil
324
310
  #
325
311
  # A array of flags that are set for this message. System flags are
326
312
  # symbols that have been capitalized by String#capitalize. Keyword flags
@@ -328,7 +314,7 @@ module Net
328
314
  #
329
315
  # This is the same as getting the value for <tt>"FLAGS"</tt> from #attr.
330
316
  #
331
- # [Note]
317
+ # [NOTE:]
332
318
  # The +FLAGS+ field is dynamic, and can change for a uniquely identified
333
319
  # message.
334
320
  def flags; attr["FLAGS"] end
@@ -336,40 +322,41 @@ module Net
336
322
  # :call-seq: internaldate -> Time or nil
337
323
  #
338
324
  # 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]]
325
+ # the date and time in the [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]
340
326
  # header, but rather a date and time which reflects when the message was
341
327
  # received.
342
328
  #
343
329
  # This is similar to getting the value for <tt>"INTERNALDATE"</tt> from
344
330
  # #attr.
345
331
  #
346
- # [Note]
332
+ # [NOTE:]
347
333
  # <tt>attr["INTERNALDATE"]</tt> returns a string, and this method
348
334
  # returns a Time object.
349
335
  def internaldate
350
336
  attr["INTERNALDATE"]&.then { IMAP.decode_time _1 }
351
337
  end
338
+
352
339
  alias internal_date internaldate
353
340
 
354
- # :call-seq: rfc822 -> String
341
+ # :call-seq: rfc822 -> String or nil
355
342
  #
356
343
  # Semantically equivalent to #message with no arguments.
357
344
  #
358
345
  # This is the same as getting the value for <tt>"RFC822"</tt> from #attr.
359
346
  #
360
- # [Note]
347
+ # [NOTE:]
361
348
  # +IMAP4rev2+ deprecates <tt>RFC822</tt>.
362
349
  def rfc822; attr["RFC822"] end
363
350
 
364
- # :call-seq: rfc822_size -> Integer
351
+ # :call-seq: rfc822_size -> Integer or nil
365
352
  #
366
- # A number expressing the [RFC5322[https://tools.ietf.org/html/rfc5322]]
353
+ # A number expressing the [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]
367
354
  # size of the message.
368
355
  #
369
356
  # This is the same as getting the value for <tt>"RFC822.SIZE"</tt> from
370
357
  # #attr.
371
358
  #
372
- # [Note]
359
+ # [NOTE:]
373
360
  # \IMAP was originally developed for the older
374
361
  # RFC822[https://www.rfc-editor.org/rfc/rfc822.html] standard, and as a
375
362
  # consequence several fetch items in \IMAP incorporate "RFC822" in their
@@ -379,34 +366,45 @@ module Net
379
366
  # interpreted as a reference to the updated
380
367
  # RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] standard.
381
368
  def rfc822_size; attr["RFC822.SIZE"] end
382
- alias size rfc822_size
383
369
 
384
- # :call-seq: rfc822_header -> String
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
385
379
  #
386
380
  # Semantically equivalent to #header, with no arguments.
387
381
  #
388
382
  # This is the same as getting the value for <tt>"RFC822.HEADER"</tt> from #attr.
389
383
  #
390
- # [Note]
384
+ # [NOTE:]
391
385
  # +IMAP4rev2+ deprecates <tt>RFC822.HEADER</tt>.
392
386
  def rfc822_header; attr["RFC822.HEADER"] end
393
387
 
394
- # :call-seq: rfc822_text -> String
388
+ # :call-seq: rfc822_text -> String or nil
395
389
  #
396
390
  # Semantically equivalent to #text, with no arguments.
397
391
  #
398
392
  # This is the same as getting the value for <tt>"RFC822.TEXT"</tt> from
399
393
  # #attr.
400
394
  #
401
- # [Note]
395
+ # [NOTE:]
402
396
  # +IMAP4rev2+ deprecates <tt>RFC822.TEXT</tt>.
403
397
  def rfc822_text; attr["RFC822.TEXT"] end
404
398
 
405
- # :call-seq: uid -> Integer
399
+ # :call-seq: uid -> Integer or nil
406
400
  #
407
401
  # A number expressing the unique identifier of the message.
408
402
  #
409
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.
410
408
  def uid; attr["UID"] end
411
409
 
412
410
  # :call-seq:
@@ -452,7 +450,7 @@ module Net
452
450
  attr[section_attr("BINARY.SIZE", part_nums)]
453
451
  end
454
452
 
455
- # :call-seq: modseq -> Integer
453
+ # :call-seq: modseq -> Integer or nil
456
454
  #
457
455
  # The modification sequence number associated with this IMAP message.
458
456
  #
@@ -461,7 +459,7 @@ module Net
461
459
  # The server must support the +CONDSTORE+ extension
462
460
  # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html].
463
461
  #
464
- # [Note]
462
+ # [NOTE:]
465
463
  # The +MODSEQ+ field is dynamic, and can change for a uniquely
466
464
  # identified message.
467
465
  def modseq; attr["MODSEQ"] end
@@ -508,11 +506,92 @@ module Net
508
506
  spec = Array(part).flatten.map { Integer(_1) }
509
507
  spec << text if text
510
508
  spec = spec.join(".")
511
- if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)]
512
- else "%s[%s]" % [attr, spec]
513
- end
509
+ if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)] else "%s[%s]" % [attr, spec] end
514
510
  end
511
+ end
515
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
516
595
  end
517
596
  end
518
597
  end