net-imap 0.4.10 → 0.5.8

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/BSDL +22 -0
  3. data/COPYING +56 -0
  4. data/Gemfile +8 -1
  5. data/LICENSE.txt +3 -22
  6. data/README.md +10 -4
  7. data/docs/styles.css +75 -14
  8. data/lib/net/imap/authenticators.rb +2 -2
  9. data/lib/net/imap/command_data.rb +61 -48
  10. data/lib/net/imap/config/attr_accessors.rb +75 -0
  11. data/lib/net/imap/config/attr_inheritance.rb +90 -0
  12. data/lib/net/imap/config/attr_type_coercion.rb +62 -0
  13. data/lib/net/imap/config.rb +552 -0
  14. data/lib/net/imap/connection_state.rb +48 -0
  15. data/lib/net/imap/data_encoding.rb +4 -4
  16. data/lib/net/imap/data_lite.rb +226 -0
  17. data/lib/net/imap/deprecated_client_options.rb +9 -6
  18. data/lib/net/imap/errors.rb +40 -1
  19. data/lib/net/imap/esearch_result.rb +180 -0
  20. data/lib/net/imap/fetch_data.rb +126 -47
  21. data/lib/net/imap/flags.rb +1 -1
  22. data/lib/net/imap/response_data.rb +126 -239
  23. data/lib/net/imap/response_parser/parser_utils.rb +11 -6
  24. data/lib/net/imap/response_parser.rb +188 -34
  25. data/lib/net/imap/response_reader.rb +73 -0
  26. data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -3
  27. data/lib/net/imap/sasl/authentication_exchange.rb +52 -20
  28. data/lib/net/imap/sasl/authenticators.rb +8 -4
  29. data/lib/net/imap/sasl/client_adapter.rb +77 -26
  30. data/lib/net/imap/sasl/cram_md5_authenticator.rb +4 -4
  31. data/lib/net/imap/sasl/digest_md5_authenticator.rb +218 -56
  32. data/lib/net/imap/sasl/external_authenticator.rb +3 -3
  33. data/lib/net/imap/sasl/gs2_header.rb +7 -7
  34. data/lib/net/imap/sasl/login_authenticator.rb +4 -3
  35. data/lib/net/imap/sasl/oauthbearer_authenticator.rb +6 -6
  36. data/lib/net/imap/sasl/plain_authenticator.rb +7 -7
  37. data/lib/net/imap/sasl/protocol_adapters.rb +60 -4
  38. data/lib/net/imap/sasl/scram_authenticator.rb +8 -8
  39. data/lib/net/imap/sasl.rb +8 -5
  40. data/lib/net/imap/sasl_adapter.rb +0 -1
  41. data/lib/net/imap/search_result.rb +2 -2
  42. data/lib/net/imap/sequence_set.rb +471 -176
  43. data/lib/net/imap/stringprep/nameprep.rb +1 -1
  44. data/lib/net/imap/stringprep/trace.rb +4 -4
  45. data/lib/net/imap/uidplus_data.rb +244 -0
  46. data/lib/net/imap/vanished_data.rb +56 -0
  47. data/lib/net/imap.rb +1225 -364
  48. data/net-imap.gemspec +4 -4
  49. data/rakelib/benchmarks.rake +1 -1
  50. data/rakelib/rfcs.rake +2 -0
  51. data/rakelib/string_prep_tables_generator.rb +2 -0
  52. metadata +17 -12
  53. data/.github/dependabot.yml +0 -6
  54. data/.github/workflows/pages.yml +0 -46
  55. data/.github/workflows/test.yml +0 -31
  56. data/.gitignore +0 -12
data/lib/net/imap.rb CHANGED
@@ -25,8 +25,8 @@ module Net
25
25
 
26
26
  # Net::IMAP implements Internet Message Access Protocol (\IMAP) client
27
27
  # functionality. The protocol is described
28
- # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501]
29
- # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051].
28
+ # in {IMAP4rev1 [RFC3501]}[https://www.rfc-editor.org/rfc/rfc3501]
29
+ # and {IMAP4rev2 [RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051].
30
30
  #
31
31
  # == \IMAP Overview
32
32
  #
@@ -43,10 +43,18 @@ module Net
43
43
  # To work on the messages within a mailbox, the client must
44
44
  # first select that mailbox, using either #select or #examine
45
45
  # (for read-only access). Once the client has successfully
46
- # selected a mailbox, they enter the "_selected_" state, and that
46
+ # selected a mailbox, they enter the +selected+ state, and that
47
47
  # mailbox becomes the _current_ mailbox, on which mail-item
48
48
  # related commands implicitly operate.
49
49
  #
50
+ # === Connection state
51
+ #
52
+ # Once an IMAP connection is established, the connection is in one of four
53
+ # states: <tt>not authenticated</tt>, +authenticated+, +selected+, and
54
+ # +logout+. Most commands are valid only in certain states.
55
+ #
56
+ # See #connection_state.
57
+ #
50
58
  # === Sequence numbers and UIDs
51
59
  #
52
60
  # Messages have two sorts of identifiers: message sequence
@@ -199,6 +207,42 @@ module Net
199
207
  #
200
208
  # This script invokes the FETCH command and the SEARCH command concurrently.
201
209
  #
210
+ # When running multiple commands, care must be taken to avoid ambiguity. For
211
+ # example, SEARCH responses are ambiguous about which command they are
212
+ # responding to, so search commands should not run simultaneously, unless the
213
+ # server supports +ESEARCH+ {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731] or
214
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]. See {RFC9051
215
+ # §5.5}[https://www.rfc-editor.org/rfc/rfc9051.html#section-5.5] for
216
+ # other examples of command sequences which should not be pipelined.
217
+ #
218
+ # == Unbounded memory use
219
+ #
220
+ # Net::IMAP reads server responses in a separate receiver thread per client.
221
+ # Unhandled response data is saved to #responses, and response_handlers run
222
+ # inside the receiver thread. See the list of methods for {handling server
223
+ # responses}[rdoc-ref:Net::IMAP@Handling+server+responses], below.
224
+ #
225
+ # Because the receiver thread continuously reads and saves new responses, some
226
+ # scenarios must be careful to avoid unbounded memory use:
227
+ #
228
+ # * Commands such as #list or #fetch can have an enormous number of responses.
229
+ # * Commands such as #fetch can result in an enormous size per response.
230
+ # * Long-lived connections will gradually accumulate unsolicited server
231
+ # responses, especially +EXISTS+, +FETCH+, and +EXPUNGE+ responses.
232
+ # * A buggy or untrusted server could send inappropriate responses, which
233
+ # could be very numerous, very large, and very rapid.
234
+ #
235
+ # Use paginated or limited versions of commands whenever possible.
236
+ #
237
+ # Use Config#max_response_size to impose a limit on incoming server responses
238
+ # as they are being read. <em>This is especially important for untrusted
239
+ # servers.</em>
240
+ #
241
+ # Use #add_response_handler to handle responses after each one is received.
242
+ # Use the +response_handlers+ argument to ::new to assign response handlers
243
+ # before the receiver thread is started. Use #extract_responses,
244
+ # #clear_responses, or #responses (with a block) to prune responses.
245
+ #
202
246
  # == Errors
203
247
  #
204
248
  # An \IMAP server can send three different types of responses to indicate
@@ -260,8 +304,9 @@ module Net
260
304
  #
261
305
  # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and
262
306
  # waits for a successful server greeting before the method returns.
307
+ # - #connection_state: Returns the connection state.
263
308
  # - #starttls: Asks the server to upgrade a clear-text connection to use TLS.
264
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
309
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
265
310
  # - #disconnect: Disconnects the connection (without sending #logout first).
266
311
  # - #disconnected?: True if the connection has been closed.
267
312
  #
@@ -288,6 +333,8 @@ module Net
288
333
  # pre-authenticated connection.
289
334
  # - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290
335
  # ResponseCode#data.
336
+ # - #extract_responses: Removes and returns the responses for which the block
337
+ # returns a true value.
291
338
  # - #clear_responses: Deletes unhandled data from #responses and returns it.
292
339
  # - #add_response_handler: Add a block to be called inside the receiver thread
293
340
  # with every server response.
@@ -297,15 +344,15 @@ module Net
297
344
  # === Core \IMAP commands
298
345
  #
299
346
  # The following commands are defined either by
300
- # the [IMAP4rev1[https://tools.ietf.org/html/rfc3501]] base specification, or
347
+ # the [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] base specification, or
301
348
  # by one of the following extensions:
302
- # [IDLE[https://tools.ietf.org/html/rfc2177]],
303
- # [NAMESPACE[https://tools.ietf.org/html/rfc2342]],
304
- # [UNSELECT[https://tools.ietf.org/html/rfc3691]],
305
- # [ENABLE[https://tools.ietf.org/html/rfc5161]],
306
- # [MOVE[https://tools.ietf.org/html/rfc6851]].
349
+ # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]],
350
+ # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]],
351
+ # [UNSELECT[https://www.rfc-editor.org/rfc/rfc3691]],
352
+ # [ENABLE[https://www.rfc-editor.org/rfc/rfc5161]],
353
+ # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]].
307
354
  # These extensions are widely supported by modern IMAP4rev1 servers and have
308
- # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]].
355
+ # all been integrated into [IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051]].
309
356
  # <em>*NOTE:* Net::IMAP doesn't support IMAP4rev2 yet.</em>
310
357
  #
311
358
  # ==== Any state
@@ -315,37 +362,36 @@ module Net
315
362
  # <em>In general, #capable? should be used rather than explicitly sending a
316
363
  # +CAPABILITY+ command to the server.</em>
317
364
  # - #noop: Allows the server to send unsolicited untagged #responses.
318
- # - #logout: Tells the server to end the session. Enters the "_logout_" state.
365
+ # - #logout: Tells the server to end the session. Enters the +logout+ state.
319
366
  #
320
367
  # ==== Not Authenticated state
321
368
  #
322
369
  # In addition to the commands for any state, the following commands are valid
323
- # in the "<em>not authenticated</em>" state:
370
+ # in the +not_authenticated+ state:
324
371
  #
325
372
  # - #starttls: Upgrades a clear-text connection to use TLS.
326
373
  #
327
374
  # <em>Requires the +STARTTLS+ capability.</em>
328
375
  # - #authenticate: Identifies the client to the server using the given
329
376
  # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
330
- # and credentials. Enters the "_authenticated_" state.
377
+ # and credentials. Enters the +authenticated+ state.
331
378
  #
332
379
  # <em>The server should list <tt>"AUTH=#{mechanism}"</tt> capabilities for
333
380
  # supported mechanisms.</em>
334
381
  # - #login: Identifies the client to the server using a plain text password.
335
- # Using #authenticate is generally preferred. Enters the "_authenticated_"
336
- # state.
382
+ # Using #authenticate is preferred. Enters the +authenticated+ state.
337
383
  #
338
384
  # <em>The +LOGINDISABLED+ capability</em> <b>must NOT</b> <em>be listed.</em>
339
385
  #
340
386
  # ==== Authenticated state
341
387
  #
342
388
  # In addition to the commands for any state, the following commands are valid
343
- # in the "_authenticated_" state:
389
+ # in the +authenticated+ state:
344
390
  #
345
391
  # - #enable: Enables backwards incompatible server extensions.
346
392
  # <em>Requires the +ENABLE+ or +IMAP4rev2+ capability.</em>
347
- # - #select: Open a mailbox and enter the "_selected_" state.
348
- # - #examine: Open a mailbox read-only, and enter the "_selected_" state.
393
+ # - #select: Open a mailbox and enter the +selected+ state.
394
+ # - #examine: Open a mailbox read-only, and enter the +selected+ state.
349
395
  # - #create: Creates a new mailbox.
350
396
  # - #delete: Permanently remove a mailbox.
351
397
  # - #rename: Change the name of a mailbox.
@@ -367,12 +413,12 @@ module Net
367
413
  #
368
414
  # ==== Selected state
369
415
  #
370
- # In addition to the commands for any state and the "_authenticated_"
371
- # commands, the following commands are valid in the "_selected_" state:
416
+ # In addition to the commands for any state and the +authenticated+
417
+ # commands, the following commands are valid in the +selected+ state:
372
418
  #
373
- # - #close: Closes the mailbox and returns to the "_authenticated_" state,
419
+ # - #close: Closes the mailbox and returns to the +authenticated+ state,
374
420
  # expunging deleted messages, unless the mailbox was opened as read-only.
375
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
421
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
376
422
  # without expunging any messages.
377
423
  # <em>Requires the +UNSELECT+ or +IMAP4rev2+ capability.</em>
378
424
  # - #expunge: Permanently removes messages which have the Deleted flag set.
@@ -393,7 +439,7 @@ module Net
393
439
  #
394
440
  # ==== Logout state
395
441
  #
396
- # No \IMAP commands are valid in the "_logout_" state. If the socket is still
442
+ # No \IMAP commands are valid in the +logout+ state. If the socket is still
397
443
  # open, Net::IMAP will close it after receiving server confirmation.
398
444
  # Exceptions will be raised by \IMAP commands that have already started and
399
445
  # are waiting for a response, as well as any that are called after logout.
@@ -402,7 +448,7 @@ module Net
402
448
  #
403
449
  # ==== RFC9051: +IMAP4rev2+
404
450
  #
405
- # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported
451
+ # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not supported
406
452
  # yet, Net::IMAP supports several extensions that have been folded into it:
407
453
  # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+,
408
454
  # <tt>STATUS=SIZE</tt>, and the fetch side of +BINARY+.
@@ -412,7 +458,7 @@ module Net
412
458
  # >>>
413
459
  # <em>The following are folded into +IMAP4rev2+ but are currently
414
460
  # unsupported or incompletely supported by</em> Net::IMAP<em>: RFC4466
415
- # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
461
+ # extensions, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+,
416
462
  # +LITERAL-+, and +SPECIAL-USE+.</em>
417
463
  #
418
464
  # ==== RFC2087: +QUOTA+
@@ -422,13 +468,13 @@ module Net
422
468
  # - #setquota: sets the resource limits for a given quota root.
423
469
  #
424
470
  # ==== RFC2177: +IDLE+
425
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
471
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
426
472
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
427
473
  # - #idle: Allows the server to send updates to the client, without the client
428
474
  # needing to poll using #noop.
429
475
  #
430
476
  # ==== RFC2342: +NAMESPACE+
431
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
477
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
432
478
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
433
479
  # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters.
434
480
  #
@@ -437,7 +483,7 @@ module Net
437
483
  #
438
484
  # ==== RFC3516: +BINARY+
439
485
  # The fetch side of +BINARY+ has been folded into
440
- # IMAP4rev2[https://tools.ietf.org/html/rfc9051].
486
+ # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
441
487
  # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and
442
488
  # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size.
443
489
  #
@@ -445,9 +491,9 @@ module Net
445
491
  # *NOTE:* The binary extension the #append command is _not_ supported yet.
446
492
  #
447
493
  # ==== RFC3691: +UNSELECT+
448
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
494
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
449
495
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
450
- # - #unselect: Closes the mailbox and returns to the "_authenticated_" state,
496
+ # - #unselect: Closes the mailbox and returns to the +authenticated+ state,
451
497
  # without expunging any messages.
452
498
  #
453
499
  # ==== RFC4314: +ACL+
@@ -457,19 +503,23 @@ module Net
457
503
  # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet.
458
504
  #
459
505
  # ==== RFC4315: +UIDPLUS+
460
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
506
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
461
507
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
462
508
  # - #uid_expunge: Restricts #expunge to only remove the specified UIDs.
463
509
  # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode
464
510
  # - Updates #append with the +APPENDUID+ ResponseCode
465
511
  # - Updates #copy, #move with the +COPYUID+ ResponseCode
466
512
  #
513
+ # ==== RFC4731: +ESEARCH+
514
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
515
+ # - Updates #search, #uid_search with +return+ options and ESearchResult.
516
+ #
467
517
  # ==== RFC4959: +SASL-IR+
468
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051].
518
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051].
469
519
  # - Updates #authenticate with the option to send an initial response.
470
520
  #
471
521
  # ==== RFC5161: +ENABLE+
472
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
522
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
473
523
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
474
524
  # - #enable: Enables backwards incompatible server extensions.
475
525
  #
@@ -493,7 +543,7 @@ module Net
493
543
  # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10).
494
544
  #
495
545
  # ==== RFC6851: +MOVE+
496
- # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included
546
+ # Folded into IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] and also included
497
547
  # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands].
498
548
  # - #move, #uid_move: Moves the specified messages to the end of the
499
549
  # specified destination mailbox, expunging them from the current mailbox.
@@ -528,6 +578,17 @@ module Net
528
578
  # See FetchData#emailid and FetchData#emailid.
529
579
  # - Updates #status with support for the +MAILBOXID+ status attribute.
530
580
  #
581
+ # ==== RFC9394: +PARTIAL+
582
+ # - Updates #search, #uid_search with the +PARTIAL+ return option which adds
583
+ # ESearchResult#partial return data.
584
+ # - Updates #uid_fetch with the +partial+ modifier.
585
+ #
586
+ # ==== RFC9586: +UIDONLY+
587
+ # - Updates #enable with +UIDONLY+ parameter.
588
+ # - Updates #uid_fetch and #uid_store to return +UIDFETCH+ response.
589
+ # - Updates #expunge and #uid_expunge to return +VANISHED+ response.
590
+ # - Prohibits use of message sequence numbers in responses or requests.
591
+ #
531
592
  # == References
532
593
  #
533
594
  # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]::
@@ -558,57 +619,57 @@ module Net
558
619
  # Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI
559
620
  # 10.17487/RFC2180, July 1997, <https://www.rfc-editor.org/info/rfc2180>.
560
621
  #
561
- # [UTF7[https://tools.ietf.org/html/rfc2152]]::
622
+ # [UTF7[https://www.rfc-editor.org/rfc/rfc2152]]::
562
623
  # Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of
563
624
  # Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997,
564
625
  # <https://www.rfc-editor.org/info/rfc2152>.
565
626
  #
566
627
  # === Message envelope and body structure
567
628
  #
568
- # [RFC5322[https://tools.ietf.org/html/rfc5322]]::
629
+ # [RFC5322[https://www.rfc-editor.org/rfc/rfc5322]]::
569
630
  # Resnick, P., Ed., "Internet Message Format",
570
631
  # RFC 5322, DOI 10.17487/RFC5322, October 2008,
571
632
  # <https://www.rfc-editor.org/info/rfc5322>.
572
633
  #
573
634
  # <em>Note: obsoletes</em>
574
- # RFC-2822[https://tools.ietf.org/html/rfc2822]<em> (April 2001) and</em>
575
- # RFC-822[https://tools.ietf.org/html/rfc822]<em> (August 1982).</em>
635
+ # RFC-2822[https://www.rfc-editor.org/rfc/rfc2822]<em> (April 2001) and</em>
636
+ # RFC-822[https://www.rfc-editor.org/rfc/rfc822]<em> (August 1982).</em>
576
637
  #
577
- # [CHARSET[https://tools.ietf.org/html/rfc2978]]::
638
+ # [CHARSET[https://www.rfc-editor.org/rfc/rfc2978]]::
578
639
  # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19,
579
640
  # RFC 2978, DOI 10.17487/RFC2978, October 2000,
580
641
  # <https://www.rfc-editor.org/info/rfc2978>.
581
642
  #
582
- # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]::
643
+ # [DISPOSITION[https://www.rfc-editor.org/rfc/rfc2183]]::
583
644
  # Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation
584
645
  # Information in Internet Messages: The Content-Disposition Header
585
646
  # Field", RFC 2183, DOI 10.17487/RFC2183, August 1997,
586
647
  # <https://www.rfc-editor.org/info/rfc2183>.
587
648
  #
588
- # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]::
649
+ # [MIME-IMB[https://www.rfc-editor.org/rfc/rfc2045]]::
589
650
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
590
651
  # (MIME) Part One: Format of Internet Message Bodies",
591
652
  # RFC 2045, DOI 10.17487/RFC2045, November 1996,
592
653
  # <https://www.rfc-editor.org/info/rfc2045>.
593
654
  #
594
- # [MIME-IMT[https://tools.ietf.org/html/rfc2046]]::
655
+ # [MIME-IMT[https://www.rfc-editor.org/rfc/rfc2046]]::
595
656
  # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions
596
657
  # (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046,
597
658
  # November 1996, <https://www.rfc-editor.org/info/rfc2046>.
598
659
  #
599
- # [MIME-HDRS[https://tools.ietf.org/html/rfc2047]]::
660
+ # [MIME-HDRS[https://www.rfc-editor.org/rfc/rfc2047]]::
600
661
  # Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three:
601
662
  # Message Header Extensions for Non-ASCII Text",
602
663
  # RFC 2047, DOI 10.17487/RFC2047, November 1996,
603
664
  # <https://www.rfc-editor.org/info/rfc2047>.
604
665
  #
605
- # [RFC2231[https://tools.ietf.org/html/rfc2231]]::
666
+ # [RFC2231[https://www.rfc-editor.org/rfc/rfc2231]]::
606
667
  # Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word
607
668
  # Extensions: Character Sets, Languages, and Continuations",
608
669
  # RFC 2231, DOI 10.17487/RFC2231, November 1997,
609
670
  # <https://www.rfc-editor.org/info/rfc2231>.
610
671
  #
611
- # [I18n-HDRS[https://tools.ietf.org/html/rfc6532]]::
672
+ # [I18n-HDRS[https://www.rfc-editor.org/rfc/rfc6532]]::
612
673
  # Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers",
613
674
  # RFC 6532, DOI 10.17487/RFC6532, February 2012,
614
675
  # <https://www.rfc-editor.org/info/rfc6532>.
@@ -624,12 +685,12 @@ module Net
624
685
  # RFC 2557, DOI 10.17487/RFC2557, March 1999,
625
686
  # <https://www.rfc-editor.org/info/rfc2557>.
626
687
  #
627
- # [MD5[https://tools.ietf.org/html/rfc1864]]::
688
+ # [MD5[https://www.rfc-editor.org/rfc/rfc1864]]::
628
689
  # Myers, J. and M. Rose, "The Content-MD5 Header Field",
629
690
  # RFC 1864, DOI 10.17487/RFC1864, October 1995,
630
691
  # <https://www.rfc-editor.org/info/rfc1864>.
631
692
  #
632
- # [RFC3503[https://tools.ietf.org/html/rfc3503]]::
693
+ # [RFC3503[https://www.rfc-editor.org/rfc/rfc3503]]::
633
694
  # Melnikov, A., "Message Disposition Notification (MDN)
634
695
  # profile for Internet Message Access Protocol (IMAP)",
635
696
  # RFC 3503, DOI 10.17487/RFC3503, March 2003,
@@ -637,27 +698,27 @@ module Net
637
698
  #
638
699
  # === \IMAP Extensions
639
700
  #
640
- # [QUOTA[https://tools.ietf.org/html/rfc9208]]::
701
+ # [QUOTA[https://www.rfc-editor.org/rfc/rfc9208]]::
641
702
  # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208,
642
703
  # March 2022, <https://www.rfc-editor.org/info/rfc9208>.
643
704
  #
644
705
  # <em>Note: obsoletes</em>
645
- # RFC-2087[https://tools.ietf.org/html/rfc2087]<em> (January 1997)</em>.
706
+ # RFC-2087[https://www.rfc-editor.org/rfc/rfc2087]<em> (January 1997)</em>.
646
707
  # <em>Net::IMAP does not fully support the RFC9208 updates yet.</em>
647
- # [IDLE[https://tools.ietf.org/html/rfc2177]]::
708
+ # [IDLE[https://www.rfc-editor.org/rfc/rfc2177]]::
648
709
  # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177,
649
710
  # June 1997, <https://www.rfc-editor.org/info/rfc2177>.
650
- # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]::
711
+ # [NAMESPACE[https://www.rfc-editor.org/rfc/rfc2342]]::
651
712
  # Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342,
652
713
  # DOI 10.17487/RFC2342, May 1998, <https://www.rfc-editor.org/info/rfc2342>.
653
- # [ID[https://tools.ietf.org/html/rfc2971]]::
714
+ # [ID[https://www.rfc-editor.org/rfc/rfc2971]]::
654
715
  # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971,
655
716
  # October 2000, <https://www.rfc-editor.org/info/rfc2971>.
656
- # [BINARY[https://tools.ietf.org/html/rfc3516]]::
717
+ # [BINARY[https://www.rfc-editor.org/rfc/rfc3516]]::
657
718
  # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516,
658
719
  # DOI 10.17487/RFC3516, April 2003,
659
720
  # <https://www.rfc-editor.org/info/rfc3516>.
660
- # [ACL[https://tools.ietf.org/html/rfc4314]]::
721
+ # [ACL[https://www.rfc-editor.org/rfc/rfc4314]]::
661
722
  # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314,
662
723
  # DOI 10.17487/RFC4314, December 2005,
663
724
  # <https://www.rfc-editor.org/info/rfc4314>.
@@ -665,36 +726,46 @@ module Net
665
726
  # Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS
666
727
  # extension", RFC 4315, DOI 10.17487/RFC4315, December 2005,
667
728
  # <https://www.rfc-editor.org/info/rfc4315>.
668
- # [SORT[https://tools.ietf.org/html/rfc5256]]::
729
+ # [SORT[https://www.rfc-editor.org/rfc/rfc5256]]::
669
730
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
670
731
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
671
732
  # <https://www.rfc-editor.org/info/rfc5256>.
672
- # [THREAD[https://tools.ietf.org/html/rfc5256]]::
733
+ # [THREAD[https://www.rfc-editor.org/rfc/rfc5256]]::
673
734
  # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and
674
735
  # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008,
675
736
  # <https://www.rfc-editor.org/info/rfc5256>.
676
737
  # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]::
677
738
  # Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530,
678
739
  # May 2009, <https://www.rfc-editor.org/info/rfc5530>.
679
- # [MOVE[https://tools.ietf.org/html/rfc6851]]::
740
+ # [MOVE[https://www.rfc-editor.org/rfc/rfc6851]]::
680
741
  # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol
681
742
  # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013,
682
743
  # <https://www.rfc-editor.org/info/rfc6851>.
683
- # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]::
684
- # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]::
744
+ # [UTF8=ACCEPT[https://www.rfc-editor.org/rfc/rfc6855]]::
745
+ # [UTF8=ONLY[https://www.rfc-editor.org/rfc/rfc6855]]::
685
746
  # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed.,
686
747
  # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013,
687
748
  # <https://www.rfc-editor.org/info/rfc6855>.
688
- # [CONDSTORE[https://tools.ietf.org/html/rfc7162]]::
689
- # [QRESYNC[https://tools.ietf.org/html/rfc7162]]::
749
+ # [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162]]::
750
+ # [QRESYNC[https://www.rfc-editor.org/rfc/rfc7162]]::
690
751
  # Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes
691
752
  # Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization
692
753
  # (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014,
693
754
  # <https://www.rfc-editor.org/info/rfc7162>.
694
- # [OBJECTID[https://tools.ietf.org/html/rfc8474]]::
755
+ # [OBJECTID[https://www.rfc-editor.org/rfc/rfc8474]]::
695
756
  # Gondwana, B., Ed., "IMAP Extension for Object Identifiers",
696
757
  # RFC 8474, DOI 10.17487/RFC8474, September 2018,
697
758
  # <https://www.rfc-editor.org/info/rfc8474>.
759
+ # [PARTIAL[https://www.rfc-editor.org/info/rfc9394]]::
760
+ # Melnikov, A., Achuthan, A., Nagulakonda, V., and L. Alves,
761
+ # "IMAP PARTIAL Extension for Paged SEARCH and FETCH", RFC 9394,
762
+ # DOI 10.17487/RFC9394, June 2023,
763
+ # <https://www.rfc-editor.org/info/rfc9394>.
764
+ # [UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.pdf]]::
765
+ # Melnikov, A., Achuthan, A., Nagulakonda, V., Singh, A., and L. Alves,
766
+ # "\IMAP Extension for Using and Returning Unique Identifiers (UIDs) Only",
767
+ # RFC 9586, DOI 10.17487/RFC9586, May 2024,
768
+ # <https://www.rfc-editor.org/info/rfc9586>.
698
769
  #
699
770
  # === IANA registries
700
771
  # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities]
@@ -708,7 +779,7 @@ module Net
708
779
  # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]:
709
780
  # +imap+
710
781
  # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
711
- # ===== For currently unsupported features:
782
+ # ==== For currently unsupported features:
712
783
  # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2]
713
784
  # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml]
714
785
  # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml]
@@ -717,7 +788,7 @@ module Net
717
788
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
718
789
  #
719
790
  class IMAP < Protocol
720
- VERSION = "0.4.10"
791
+ VERSION = "0.5.8"
721
792
 
722
793
  # Aliases for supported capabilities, to be used with the #enable command.
723
794
  ENABLE_ALIASES = {
@@ -725,9 +796,12 @@ module Net
725
796
  "UTF8=ONLY" => "UTF8=ACCEPT",
726
797
  }.freeze
727
798
 
728
- autoload :SASL, File.expand_path("imap/sasl", __dir__)
729
- autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
730
- autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
799
+ dir = File.expand_path("imap", __dir__)
800
+ autoload :ConnectionState, "#{dir}/connection_state"
801
+ autoload :ResponseReader, "#{dir}/response_reader"
802
+ autoload :SASL, "#{dir}/sasl"
803
+ autoload :SASLAdapter, "#{dir}/sasl_adapter"
804
+ autoload :StringPrep, "#{dir}/stringprep"
731
805
 
732
806
  include MonitorMixin
733
807
  if defined?(OpenSSL::SSL)
@@ -735,14 +809,17 @@ module Net
735
809
  include SSL
736
810
  end
737
811
 
738
- # Returns the debug mode.
739
- def self.debug
740
- return @@debug
741
- end
812
+ # Returns the global Config object
813
+ def self.config; Config.global end
814
+
815
+ # Returns the global debug mode.
816
+ # Delegates to {Net::IMAP.config.debug}[rdoc-ref:Config#debug].
817
+ def self.debug; config.debug end
742
818
 
743
- # Sets the debug mode.
819
+ # Sets the global debug mode.
820
+ # Delegates to {Net::IMAP.config.debug=}[rdoc-ref:Config#debug=].
744
821
  def self.debug=(val)
745
- return @@debug = val
822
+ config.debug = val
746
823
  end
747
824
 
748
825
  # The default port for IMAP connections, port 143
@@ -761,16 +838,37 @@ module Net
761
838
  alias default_ssl_port default_tls_port
762
839
  end
763
840
 
764
- # Returns the initial greeting the server, an UntaggedResponse.
841
+ # Returns the initial greeting sent by the server, an UntaggedResponse.
765
842
  attr_reader :greeting
766
843
 
767
- # Seconds to wait until a connection is opened.
768
- # If the IMAP object cannot open a connection within this time,
769
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
770
- attr_reader :open_timeout
844
+ # The client configuration. See Net::IMAP::Config.
845
+ #
846
+ # By default, the client's local configuration inherits from the global
847
+ # Net::IMAP.config.
848
+ attr_reader :config
849
+
850
+ ##
851
+ # :attr_reader: open_timeout
852
+ # Seconds to wait until a connection is opened. Also used by #starttls.
853
+ # Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout].
771
854
 
855
+ ##
856
+ # :attr_reader: idle_response_timeout
772
857
  # Seconds to wait until an IDLE response is received.
773
- attr_reader :idle_response_timeout
858
+ # Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout].
859
+
860
+ ##
861
+ # :attr_accessor: max_response_size
862
+ #
863
+ # The maximum allowed server response size, in bytes.
864
+ # Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size].
865
+
866
+ # :stopdoc:
867
+ def open_timeout; config.open_timeout end
868
+ def idle_response_timeout; config.idle_response_timeout end
869
+ def max_response_size; config.max_response_size end
870
+ def max_response_size=(val) config.max_response_size = val end
871
+ # :startdoc:
774
872
 
775
873
  # The hostname this client connected to
776
874
  attr_reader :host
@@ -793,6 +891,67 @@ module Net
793
891
  # Returns +false+ for a plaintext connection.
794
892
  attr_reader :ssl_ctx_params
795
893
 
894
+ # Returns the current connection state.
895
+ #
896
+ # Once an IMAP connection is established, the connection is in one of four
897
+ # states: +not_authenticated+, +authenticated+, +selected+, and +logout+.
898
+ # Most commands are valid only in certain states.
899
+ #
900
+ # The connection state object responds to +to_sym+ and +name+ with the name
901
+ # of the current connection state, as a Symbol or String. Future versions
902
+ # of +net-imap+ may store additional information on the state object.
903
+ #
904
+ # From {RFC9051}[https://www.rfc-editor.org/rfc/rfc9051#section-3]:
905
+ # +----------------------+
906
+ # |connection established|
907
+ # +----------------------+
908
+ # ||
909
+ # \/
910
+ # +--------------------------------------+
911
+ # | server greeting |
912
+ # +--------------------------------------+
913
+ # || (1) || (2) || (3)
914
+ # \/ || ||
915
+ # +-----------------+ || ||
916
+ # |Not Authenticated| || ||
917
+ # +-----------------+ || ||
918
+ # || (7) || (4) || ||
919
+ # || \/ \/ ||
920
+ # || +----------------+ ||
921
+ # || | Authenticated |<=++ ||
922
+ # || +----------------+ || ||
923
+ # || || (7) || (5) || (6) ||
924
+ # || || \/ || ||
925
+ # || || +--------+ || ||
926
+ # || || |Selected|==++ ||
927
+ # || || +--------+ ||
928
+ # || || || (7) ||
929
+ # \/ \/ \/ \/
930
+ # +--------------------------------------+
931
+ # | Logout |
932
+ # +--------------------------------------+
933
+ # ||
934
+ # \/
935
+ # +-------------------------------+
936
+ # |both sides close the connection|
937
+ # +-------------------------------+
938
+ #
939
+ # >>>
940
+ # Legend for the above diagram:
941
+ #
942
+ # 1. connection without pre-authentication (+OK+ #greeting)
943
+ # 2. pre-authenticated connection (+PREAUTH+ #greeting)
944
+ # 3. rejected connection (+BYE+ #greeting)
945
+ # 4. successful #login or #authenticate command
946
+ # 5. successful #select or #examine command
947
+ # 6. #close or #unselect command, unsolicited +CLOSED+ response code, or
948
+ # failed #select or #examine command
949
+ # 7. #logout command, server shutdown, or connection closed
950
+ #
951
+ # Before the server greeting, the state is +not_authenticated+.
952
+ # After the connection closes, the state remains +logout+.
953
+ attr_reader :connection_state
954
+
796
955
  # Creates a new Net::IMAP object and connects it to the specified
797
956
  # +host+.
798
957
  #
@@ -809,18 +968,50 @@ module Net
809
968
  # If +ssl+ is a hash, it's passed to
810
969
  # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params];
811
970
  # the keys are names of attribute assignment methods on
812
- # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html].
971
+ # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. For example:
972
+ #
973
+ # [{ca_file}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_file]]
974
+ # The path to a file containing a PEM-format CA certificate.
975
+ # [{ca_path}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-ca_path]]
976
+ # The path to a directory containing CA certificates in PEM format.
977
+ # [{min_version}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D]]
978
+ # Sets the lower bound on the supported SSL/TLS protocol version. Set to
979
+ # an +OpenSSL+ constant such as +OpenSSL::SSL::TLS1_2_VERSION+,
980
+ # [{verify_mode}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#attribute-i-verify_mode]]
981
+ # SSL session verification mode. Valid modes include
982
+ # +OpenSSL::SSL::VERIFY_PEER+ and +OpenSSL::SSL::VERIFY_NONE+.
983
+ #
984
+ # See {OpenSSL::SSL::SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] for other valid SSL context params.
985
+ #
986
+ # See DeprecatedClientOptions.new for deprecated SSL arguments.
987
+ #
988
+ # [response_handlers]
989
+ # A list of response handlers to be added before the receiver thread is
990
+ # started. This ensures every server response is handled, including the
991
+ # #greeting. Note that the greeting is handled in the current thread, but
992
+ # all other responses are handled in the receiver thread.
993
+ #
994
+ # [config]
995
+ # A Net::IMAP::Config object to use as the basis for #config. By default,
996
+ # the global Net::IMAP.config is used.
997
+ #
998
+ # >>>
999
+ # *NOTE:* +config+ does not set #config directly---it sets the _parent_
1000
+ # config for inheritance. Every client creates its own unique #config.
813
1001
  #
814
- # [open_timeout]
815
- # Seconds to wait until a connection is opened
816
- # [idle_response_timeout]
817
- # Seconds to wait until an IDLE response is received
1002
+ # All other keyword arguments are forwarded to Net::IMAP::Config.new, to
1003
+ # initialize the client's #config. For example:
818
1004
  #
819
- # See DeprecatedClientOptions.new for deprecated arguments.
1005
+ # [{open_timeout}[rdoc-ref:Config#open_timeout]]
1006
+ # Seconds to wait until a connection is opened
1007
+ # [{idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]]
1008
+ # Seconds to wait until an IDLE response is received
1009
+ #
1010
+ # See Net::IMAP::Config for other valid options.
820
1011
  #
821
1012
  # ==== Examples
822
1013
  #
823
- # Connect to cleartext port 143 at mail.example.com and recieve the server greeting:
1014
+ # Connect to cleartext port 143 at mail.example.com and receive the server greeting:
824
1015
  # imap = Net::IMAP.new('mail.example.com', ssl: false) # => #<Net::IMAP:0x00007f79b0872bd0>
825
1016
  # imap.port => 143
826
1017
  # imap.tls_verified? => false
@@ -871,14 +1062,13 @@ module Net
871
1062
  # [Net::IMAP::ByeResponseError]
872
1063
  # Connected to the host successfully, but it immediately said goodbye.
873
1064
  #
874
- def initialize(host, port: nil, ssl: nil,
875
- open_timeout: 30, idle_response_timeout: 5)
1065
+ def initialize(host, port: nil, ssl: nil, response_handlers: nil,
1066
+ config: Config.global, **config_options)
876
1067
  super()
877
1068
  # Config options
878
1069
  @host = host
1070
+ @config = Config.new(config, **config_options)
879
1071
  @port = port || (ssl ? SSL_PORT : PORT)
880
- @open_timeout = Integer(open_timeout)
881
- @idle_response_timeout = Integer(idle_response_timeout)
882
1072
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl)
883
1073
 
884
1074
  # Basic Client State
@@ -887,14 +1077,17 @@ module Net
887
1077
  @exception = nil
888
1078
  @greeting = nil
889
1079
  @capabilities = nil
1080
+ @tls_verified = false
1081
+ @connection_state = ConnectionState::NotAuthenticated.new
890
1082
 
891
- # Client Protocol Reciever
892
- @parser = ResponseParser.new
1083
+ # Client Protocol Receiver
1084
+ @parser = ResponseParser.new(config: @config)
893
1085
  @responses = Hash.new {|h, k| h[k] = [] }
894
1086
  @response_handlers = []
895
1087
  @receiver_thread = nil
896
1088
  @receiver_thread_exception = nil
897
1089
  @receiver_thread_terminating = false
1090
+ response_handlers&.each do add_response_handler(_1) end
898
1091
 
899
1092
  # Client Protocol Sender (including state for currently running commands)
900
1093
  @tag_prefix = "RUBY"
@@ -908,13 +1101,10 @@ module Net
908
1101
  @logout_command_tag = nil
909
1102
 
910
1103
  # Connection
911
- @tls_verified = false
912
1104
  @sock = tcp_socket(@host, @port)
1105
+ @reader = ResponseReader.new(self, @sock)
913
1106
  start_tls_session if ssl_ctx
914
1107
  start_imap_connection
915
-
916
- # DEPRECATED: to remove in next version
917
- @client_thread = Thread.current
918
1108
  end
919
1109
 
920
1110
  # Returns true after the TLS negotiation has completed and the remote
@@ -922,16 +1112,12 @@ module Net
922
1112
  # but peer verification was disabled.
923
1113
  def tls_verified?; @tls_verified end
924
1114
 
925
- def client_thread # :nodoc:
926
- warn "Net::IMAP#client_thread is deprecated and will be removed soon."
927
- @client_thread
928
- end
929
-
930
1115
  # Disconnects from the server.
931
1116
  #
932
1117
  # Related: #logout, #logout!
933
1118
  def disconnect
934
1119
  return if disconnected?
1120
+ state_logout!
935
1121
  begin
936
1122
  begin
937
1123
  # try to call SSL::SSLSocket#io.
@@ -1095,12 +1281,12 @@ module Net
1095
1281
  # )
1096
1282
  # end
1097
1283
  #
1098
- # See [ID[https://tools.ietf.org/html/rfc2971]] for field definitions.
1284
+ # See [ID[https://www.rfc-editor.org/rfc/rfc2971]] for field definitions.
1099
1285
  #
1100
- # ===== Capabilities
1286
+ # ==== Capabilities
1101
1287
  #
1102
1288
  # The server's capabilities must include +ID+
1103
- # [RFC2971[https://tools.ietf.org/html/rfc2971]].
1289
+ # [RFC2971[https://www.rfc-editor.org/rfc/rfc2971]].
1104
1290
  def id(client_id=nil)
1105
1291
  synchronize do
1106
1292
  send_command("ID", ClientID.new(client_id))
@@ -1170,6 +1356,10 @@ module Net
1170
1356
  # both successful. Any error indicates that the connection has not been
1171
1357
  # secured.
1172
1358
  #
1359
+ # After the server agrees to start a TLS connection, this method waits up to
1360
+ # {config.open_timeout}[rdoc-ref:Config#open_timeout] before raising
1361
+ # +Net::OpenTimeout+.
1362
+ #
1173
1363
  # *Note:*
1174
1364
  # >>>
1175
1365
  # Any #response_handlers added before STARTTLS should be aware that the
@@ -1179,7 +1369,7 @@ module Net
1179
1369
  #
1180
1370
  # Related: Net::IMAP.new, #login, #authenticate
1181
1371
  #
1182
- # ===== Capability
1372
+ # ==== Capability
1183
1373
  # Clients should not call #starttls unless the server advertises the
1184
1374
  # +STARTTLS+ capability.
1185
1375
  #
@@ -1188,17 +1378,25 @@ module Net
1188
1378
  #
1189
1379
  def starttls(**options)
1190
1380
  @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options)
1191
- send_command("STARTTLS") do |resp|
1381
+ error = nil
1382
+ ok = send_command("STARTTLS") do |resp|
1192
1383
  if resp.kind_of?(TaggedResponse) && resp.name == "OK"
1193
1384
  clear_cached_capabilities
1194
1385
  clear_responses
1195
1386
  start_tls_session
1196
1387
  end
1388
+ rescue Exception => error
1389
+ raise # note that the error backtrace is in the receiver_thread
1197
1390
  end
1391
+ if error
1392
+ disconnect
1393
+ raise error
1394
+ end
1395
+ ok
1198
1396
  end
1199
1397
 
1200
1398
  # :call-seq:
1201
- # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
1399
+ # authenticate(mechanism, *, sasl_ir: config.sasl_ir, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp
1202
1400
  #
1203
1401
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
1204
1402
  # to authenticate the client. If successful, the connection enters the
@@ -1207,7 +1405,11 @@ module Net
1207
1405
  # +mechanism+ is the name of the \SASL authentication mechanism to be used.
1208
1406
  #
1209
1407
  # +sasl_ir+ allows or disallows sending an "initial response" (see the
1210
- # +SASL-IR+ capability, below).
1408
+ # +SASL-IR+ capability, below). Defaults to the #config value for
1409
+ # {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
1410
+ #
1411
+ # The +registry+ kwarg can be used to select the mechanism implementation
1412
+ # from a custom registry. See SASL.authenticator and SASL::Authenticators.
1211
1413
  #
1212
1414
  # All other arguments are forwarded to the registered SASL authenticator for
1213
1415
  # the requested mechanism. <em>The documentation for each individual
@@ -1303,27 +1505,9 @@ module Net
1303
1505
  # Previously cached #capabilities will be cleared when this method
1304
1506
  # completes. If the TaggedResponse to #authenticate includes updated
1305
1507
  # capabilities, they will be cached.
1306
- def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback)
1307
- mechanism = mechanism.to_s.tr("_", "-").upcase
1308
- authenticator = SASL.authenticator(mechanism, *creds, **props, &callback)
1309
- cmdargs = ["AUTHENTICATE", mechanism]
1310
- if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) &&
1311
- authenticator.respond_to?(:initial_response?) &&
1312
- authenticator.initial_response?
1313
- response = authenticator.process(nil)
1314
- cmdargs << (response.empty? ? "=" : [response].pack("m0"))
1315
- end
1316
- result = send_command_with_continuations(*cmdargs) {|data|
1317
- challenge = data.unpack1("m")
1318
- response = authenticator.process challenge
1319
- [response].pack("m0")
1320
- }
1321
- if authenticator.respond_to?(:done?) && !authenticator.done?
1322
- logout!
1323
- raise SASL::AuthenticationIncomplete, result
1324
- end
1325
- @capabilities = capabilities_from_resp_code result
1326
- result
1508
+ def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback)
1509
+ sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback)
1510
+ .tap do state_authenticated! _1 end
1327
1511
  end
1328
1512
 
1329
1513
  # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3]
@@ -1340,16 +1524,12 @@ module Net
1340
1524
  #
1341
1525
  # Related: #authenticate, #starttls
1342
1526
  #
1343
- # ===== Capabilities
1527
+ # ==== Capabilities
1344
1528
  #
1345
1529
  # An IMAP client MUST NOT call #login when the server advertises the
1346
- # +LOGINDISABLED+ capability.
1347
- #
1348
- # if imap.capability? "LOGINDISABLED"
1349
- # raise "Remote server has disabled the login command"
1350
- # else
1351
- # imap.login username, password
1352
- # end
1530
+ # +LOGINDISABLED+ capability. By default, Net::IMAP will raise a
1531
+ # LoginDisabledError when that capability is present. See
1532
+ # Config#enforce_logindisabled.
1353
1533
  #
1354
1534
  # Server capabilities may change after #starttls, #login, and #authenticate.
1355
1535
  # Cached capabilities _must_ be invalidated after this method completes.
@@ -1357,8 +1537,11 @@ module Net
1357
1537
  # ResponseCode.
1358
1538
  #
1359
1539
  def login(user, password)
1540
+ if enforce_logindisabled? && capability?("LOGINDISABLED")
1541
+ raise LoginDisabledError
1542
+ end
1360
1543
  send_command("LOGIN", user, password)
1361
- .tap { @capabilities = capabilities_from_resp_code _1 }
1544
+ .tap do state_authenticated! _1 end
1362
1545
  end
1363
1546
 
1364
1547
  # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1]
@@ -1382,7 +1565,7 @@ module Net
1382
1565
  #
1383
1566
  # Related: #examine
1384
1567
  #
1385
- # ===== Capabilities
1568
+ # ==== Capabilities
1386
1569
  #
1387
1570
  # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported,
1388
1571
  # the server may return an untagged "NO" response with a "UIDNOTSTICKY"
@@ -1398,8 +1581,10 @@ module Net
1398
1581
  args = ["SELECT", mailbox]
1399
1582
  args << ["CONDSTORE"] if condstore
1400
1583
  synchronize do
1584
+ state_unselected! # implicitly closes current mailbox
1401
1585
  @responses.clear
1402
1586
  send_command(*args)
1587
+ .tap do state_selected! end
1403
1588
  end
1404
1589
  end
1405
1590
 
@@ -1416,8 +1601,10 @@ module Net
1416
1601
  args = ["EXAMINE", mailbox]
1417
1602
  args << ["CONDSTORE"] if condstore
1418
1603
  synchronize do
1604
+ state_unselected! # implicitly closes current mailbox
1419
1605
  @responses.clear
1420
1606
  send_command(*args)
1607
+ .tap do state_selected! end
1421
1608
  end
1422
1609
  end
1423
1610
 
@@ -1500,7 +1687,7 @@ module Net
1500
1687
  #
1501
1688
  # Related: #lsub, MailboxList
1502
1689
  #
1503
- # ===== For example:
1690
+ # ==== For example:
1504
1691
  #
1505
1692
  # imap.create("foo/bar")
1506
1693
  # imap.create("foo/baz")
@@ -1538,7 +1725,7 @@ module Net
1538
1725
  # servers, then folder creation (and listing, moving, etc) can lead to
1539
1726
  # errors.
1540
1727
  #
1541
- # From RFC2342[https://tools.ietf.org/html/rfc2342]:
1728
+ # From RFC2342[https://www.rfc-editor.org/rfc/rfc2342]:
1542
1729
  # >>>
1543
1730
  # <em>Although typically a server will support only a single Personal
1544
1731
  # Namespace, and a single Other User's Namespace, circumstances exist
@@ -1551,7 +1738,7 @@ module Net
1551
1738
  #
1552
1739
  # Related: #list, Namespaces, Namespace
1553
1740
  #
1554
- # ===== For example:
1741
+ # ==== For example:
1555
1742
  #
1556
1743
  # if capable?("NAMESPACE")
1557
1744
  # namespaces = imap.namespace
@@ -1565,10 +1752,10 @@ module Net
1565
1752
  # end
1566
1753
  # end
1567
1754
  #
1568
- # ===== Capabilities
1755
+ # ==== Capabilities
1569
1756
  #
1570
- # The server's capabilities must include +NAMESPACE+
1571
- # [RFC2342[https://tools.ietf.org/html/rfc2342]].
1757
+ # The server's capabilities must include either +IMAP4rev2+ or +NAMESPACE+
1758
+ # [RFC2342[https://www.rfc-editor.org/rfc/rfc2342]].
1572
1759
  def namespace
1573
1760
  synchronize do
1574
1761
  send_command("NAMESPACE")
@@ -1604,7 +1791,7 @@ module Net
1604
1791
  #
1605
1792
  # Related: #list, MailboxList
1606
1793
  #
1607
- # ===== Capabilities
1794
+ # ==== Capabilities
1608
1795
  #
1609
1796
  # The server's capabilities must include +XLIST+,
1610
1797
  # a deprecated Gmail extension (replaced by +SPECIAL-USE+).
@@ -1627,10 +1814,10 @@ module Net
1627
1814
  #
1628
1815
  # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota
1629
1816
  #
1630
- # ===== Capabilities
1817
+ # ==== Capabilities
1631
1818
  #
1632
1819
  # The server's capabilities must include +QUOTA+
1633
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1820
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1634
1821
  def getquotaroot(mailbox)
1635
1822
  synchronize do
1636
1823
  send_command("GETQUOTAROOT", mailbox)
@@ -1648,10 +1835,10 @@ module Net
1648
1835
  #
1649
1836
  # Related: #getquotaroot, #setquota, MailboxQuota
1650
1837
  #
1651
- # ===== Capabilities
1838
+ # ==== Capabilities
1652
1839
  #
1653
1840
  # The server's capabilities must include +QUOTA+
1654
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1841
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1655
1842
  def getquota(mailbox)
1656
1843
  synchronize do
1657
1844
  send_command("GETQUOTA", mailbox)
@@ -1666,10 +1853,10 @@ module Net
1666
1853
  #
1667
1854
  # Related: #getquota, #getquotaroot
1668
1855
  #
1669
- # ===== Capabilities
1856
+ # ==== Capabilities
1670
1857
  #
1671
1858
  # The server's capabilities must include +QUOTA+
1672
- # [RFC2087[https://tools.ietf.org/html/rfc2087]].
1859
+ # [RFC2087[https://www.rfc-editor.org/rfc/rfc2087]].
1673
1860
  def setquota(mailbox, quota)
1674
1861
  if quota.nil?
1675
1862
  data = '()'
@@ -1686,10 +1873,10 @@ module Net
1686
1873
  #
1687
1874
  # Related: #getacl
1688
1875
  #
1689
- # ===== Capabilities
1876
+ # ==== Capabilities
1690
1877
  #
1691
1878
  # The server's capabilities must include +ACL+
1692
- # [RFC4314[https://tools.ietf.org/html/rfc4314]].
1879
+ # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
1693
1880
  def setacl(mailbox, user, rights)
1694
1881
  if rights.nil?
1695
1882
  send_command("SETACL", mailbox, user, "")
@@ -1704,10 +1891,10 @@ module Net
1704
1891
  #
1705
1892
  # Related: #setacl, MailboxACLItem
1706
1893
  #
1707
- # ===== Capabilities
1894
+ # ==== Capabilities
1708
1895
  #
1709
1896
  # The server's capabilities must include +ACL+
1710
- # [RFC4314[https://tools.ietf.org/html/rfc4314]].
1897
+ # [RFC4314[https://www.rfc-editor.org/rfc/rfc4314]].
1711
1898
  def getacl(mailbox)
1712
1899
  synchronize do
1713
1900
  send_command("GETACL", mailbox)
@@ -1741,7 +1928,7 @@ module Net
1741
1928
  # for +mailbox+ cannot be returned; for instance, because it
1742
1929
  # does not exist.
1743
1930
  #
1744
- # ===== Supported attributes
1931
+ # ==== Supported attributes
1745
1932
  #
1746
1933
  # +MESSAGES+:: The number of messages in the mailbox.
1747
1934
  #
@@ -1772,12 +1959,12 @@ module Net
1772
1959
  # Unsupported attributes may be requested. The attribute value will be
1773
1960
  # either an Integer or an ExtensionData object.
1774
1961
  #
1775
- # ===== For example:
1962
+ # ==== For example:
1776
1963
  #
1777
1964
  # p imap.status("inbox", ["MESSAGES", "RECENT"])
1778
1965
  # #=> {"RECENT"=>0, "MESSAGES"=>44}
1779
1966
  #
1780
- # ===== Capabilities
1967
+ # ==== Capabilities
1781
1968
  #
1782
1969
  # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or
1783
1970
  # <tt>STATUS=SIZE</tt>
@@ -1817,7 +2004,7 @@ module Net
1817
2004
  # not exist (it is not created automatically), or if the flags,
1818
2005
  # date_time, or message arguments contain errors.
1819
2006
  #
1820
- # ===== Capabilities
2007
+ # ==== Capabilities
1821
2008
  #
1822
2009
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
1823
2010
  # supported and the destination supports persistent UIDs, the server's
@@ -1856,6 +2043,7 @@ module Net
1856
2043
  # Related: #unselect
1857
2044
  def close
1858
2045
  send_command("CLOSE")
2046
+ .tap do state_authenticated! end
1859
2047
  end
1860
2048
 
1861
2049
  # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3]
@@ -1866,129 +2054,493 @@ module Net
1866
2054
  #
1867
2055
  # Related: #close
1868
2056
  #
1869
- # ===== Capabilities
2057
+ # ==== Capabilities
1870
2058
  #
1871
- # The server's capabilities must include +UNSELECT+
1872
- # [RFC3691[https://tools.ietf.org/html/rfc3691]].
2059
+ # The server's capabilities must include either +IMAP4rev2+ or +UNSELECT+
2060
+ # [RFC3691[https://www.rfc-editor.org/rfc/rfc3691]].
1873
2061
  def unselect
1874
2062
  send_command("UNSELECT")
2063
+ .tap do state_authenticated! end
1875
2064
  end
1876
2065
 
2066
+ # call-seq:
2067
+ # expunge -> array of message sequence numbers
2068
+ # expunge -> VanishedData of UIDs
2069
+ #
1877
2070
  # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3]
1878
- # Sends a EXPUNGE command to permanently remove from the currently
1879
- # selected mailbox all messages that have the \Deleted flag set.
2071
+ # to permanently remove all messages with the +\Deleted+ flag from the
2072
+ # currently selected mailbox.
2073
+ #
2074
+ # Returns either an array of expunged message <em>sequence numbers</em> or
2075
+ # (when the appropriate capability is enabled) VanishedData of expunged
2076
+ # UIDs. Previously unhandled +EXPUNGE+ or +VANISHED+ responses are merged
2077
+ # with the direct response to this command. <tt>VANISHED (EARLIER)</tt>
2078
+ # responses will _not_ be merged.
2079
+ #
2080
+ # When no messages have been expunged, an empty array is returned,
2081
+ # regardless of which extensions are enabled. In a future release, an empty
2082
+ # VanishedData may be returned, based on the currently enabled extensions.
1880
2083
  #
1881
2084
  # Related: #uid_expunge
2085
+ #
2086
+ # ==== Capabilities
2087
+ #
2088
+ # When either QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] or
2089
+ # UIDONLY[https://www.rfc-editor.org/rfc/rfc9586] are enabled, #expunge
2090
+ # returns VanishedData, which contains UIDs---<em>not message sequence
2091
+ # numbers</em>.
1882
2092
  def expunge
1883
- synchronize do
1884
- send_command("EXPUNGE")
1885
- clear_responses("EXPUNGE")
1886
- end
2093
+ expunge_internal("EXPUNGE")
1887
2094
  end
1888
2095
 
2096
+ # call-seq:
2097
+ # uid_expunge{uid_set) -> array of message sequence numbers
2098
+ # uid_expunge{uid_set) -> VanishedData of UIDs
2099
+ #
1889
2100
  # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1]
1890
2101
  # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9]
1891
2102
  # to permanently remove all messages that have both the <tt>\\Deleted</tt>
1892
2103
  # flag set and a UID that is included in +uid_set+.
1893
2104
  #
2105
+ # Returns the same result type as #expunge.
2106
+ #
1894
2107
  # By using #uid_expunge instead of #expunge when resynchronizing with
1895
2108
  # the server, the client can ensure that it does not inadvertantly
1896
2109
  # remove any messages that have been marked as <tt>\\Deleted</tt> by other
1897
2110
  # clients between the time that the client was last connected and
1898
2111
  # the time the client resynchronizes.
1899
2112
  #
1900
- # *Note:*
1901
- # >>>
1902
- # Although the command takes a set of UIDs for its argument, the
1903
- # server still returns regular EXPUNGE responses, which contain
1904
- # a <em>sequence number</em>. These will be deleted from
1905
- # #responses and this method returns them as an array of
1906
- # <em>sequence number</em> integers.
1907
- #
1908
2113
  # Related: #expunge
1909
2114
  #
1910
- # ===== Capabilities
2115
+ # ==== Capabilities
1911
2116
  #
1912
- # The server's capabilities must include +UIDPLUS+
2117
+ # The server's capabilities must include either +IMAP4rev2+ or +UIDPLUS+
1913
2118
  # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]].
2119
+ #
2120
+ # Otherwise, #uid_expunge is updated by extensions in the same way as
2121
+ # #expunge.
1914
2122
  def uid_expunge(uid_set)
1915
- synchronize do
1916
- send_command("UID EXPUNGE", MessageSet.new(uid_set))
1917
- clear_responses("EXPUNGE")
1918
- end
2123
+ expunge_internal("UID EXPUNGE", SequenceSet.new(uid_set))
1919
2124
  end
1920
2125
 
1921
- # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
1922
- # to search the mailbox for messages that match the given searching
1923
- # criteria, and returns message sequence numbers. +keys+ can either be a
1924
- # string holding the entire search string, or a single-dimension array of
1925
- # search keywords and arguments.
2126
+ # :call-seq:
2127
+ # search(criteria, charset = nil) -> result
2128
+ # search(criteria, charset: nil, return: nil) -> result
1926
2129
  #
1927
- # Returns a SearchResult object. SearchResult inherits from Array (for
1928
- # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1929
- # capability has been enabled.
2130
+ # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4]
2131
+ # to search the mailbox for messages that match the given search +criteria+,
2132
+ # and returns either a SearchResult or an ESearchResult. SearchResult
2133
+ # inherits from Array (for backward compatibility) but adds
2134
+ # SearchResult#modseq when the +CONDSTORE+ capability has been enabled.
2135
+ # ESearchResult also implements {#to_a}[rdoc-ref:ESearchResult#to_a], for
2136
+ # compatibility with SearchResult.
2137
+ #
2138
+ # +criteria+ is one or more search keys and their arguments, which may be
2139
+ # provided as an array or a string.
2140
+ # See {"Argument translation"}[rdoc-ref:#search@Argument+translation]
2141
+ # and {"Search criteria"}[rdoc-ref:#search@Search+criteria], below.
2142
+ #
2143
+ # +return+ options control what kind of information is returned about
2144
+ # messages matching the search +criteria+. Specifying +return+ should force
2145
+ # the server to return an ESearchResult instead of a SearchResult, but some
2146
+ # servers disobey this requirement. <em>Requires an extended search
2147
+ # capability, such as +ESEARCH+ or +IMAP4rev2+.</em>
2148
+ # See {"Argument translation"}[rdoc-ref:#search@Argument+translation] and
2149
+ # {"Supported return options"}[rdoc-ref:#search@Supported+return+options],
2150
+ # below.
2151
+ #
2152
+ # +charset+ is the name of the {registered character
2153
+ # set}[https://www.iana.org/assignments/character-sets/character-sets.xhtml]
2154
+ # used by strings in the search +criteria+. When +charset+ isn't specified,
2155
+ # either <tt>"US-ASCII"</tt> or <tt>"UTF-8"</tt> is assumed, depending on
2156
+ # the server's capabilities.
2157
+ #
2158
+ # _NOTE:_ Return options and charset may be sent as part of +criteria+. Do
2159
+ # not use the +return+ or +charset+ arguments when either return options or
2160
+ # charset are embedded in +criteria+.
1930
2161
  #
1931
2162
  # Related: #uid_search
1932
2163
  #
1933
- # ===== Search criteria
2164
+ # ==== For example:
1934
2165
  #
1935
- # For a full list of search criteria,
2166
+ # imap.search(["SUBJECT", "hello", "NOT", "SEEN"])
2167
+ # #=> [1, 6, 7, 8]
2168
+ #
2169
+ # The following assumes the server supports +ESEARCH+ and +CONDSTORE+:
2170
+ #
2171
+ # result = imap.uid_search(["UID", 12345.., "MODSEQ", 620_162_338],
2172
+ # return: %w(all count min max))
2173
+ # # => #<data Net::IMAP::ESearchResult tag="RUBY0123", uid=true,
2174
+ # # data=[["ALL", Net::IMAP::SequenceSet["12346:12349,22222:22230"]],
2175
+ # # ["COUNT", 13], ["MIN", 12346], ["MAX", 22230],
2176
+ # # ["MODSEQ", 917162488]]>
2177
+ # result.to_a # => [12346, 12347, 12348, 12349, 22222, 22223, 22224,
2178
+ # # 22225, 22226, 22227, 22228, 22229, 22230]
2179
+ # result.uid? # => true
2180
+ # result.count # => 13
2181
+ # result.min # => 12346
2182
+ # result.max # => 22230
2183
+ # result.modseq # => 917162488
2184
+ #
2185
+ # Using +return+ options to limit the result to only min, max, and count:
2186
+ #
2187
+ # result = imap.uid_search(["UID", 12345..,], return: %w(count min max))
2188
+ # # => #<data Net::IMAP::ESearchResult tag="RUBY0124", uid=true,
2189
+ # # data=[["COUNT", 13], ["MIN", 12346], ["MAX", 22230]]>
2190
+ # result.to_a # => []
2191
+ # result.count # => 13
2192
+ # result.min # => 12346
2193
+ # result.max # => 22230
2194
+ #
2195
+ # Return options and charset may be sent as keyword args or embedded in the
2196
+ # +criteria+ arg, but they must be in the correct order: <tt>"RETURN (...)
2197
+ # CHARSET ... criteria..."</tt>. The following searches
2198
+ # send the exact same command to the server:
2199
+ #
2200
+ # # Return options and charset as keyword arguments (preferred)
2201
+ # imap.search(%w(OR UNSEEN FLAGGED), return: %w(MIN MAX), charset: "UTF-8")
2202
+ # # Embedding return and charset in the criteria array
2203
+ # imap.search(["RETURN", %w(MIN MAX), "CHARSET", "UTF-8", *%w(OR UNSEEN FLAGGED)])
2204
+ # # Embedding return and charset in the criteria string
2205
+ # imap.search("RETURN (MIN MAX) CHARSET UTF-8 OR UNSEEN FLAGGED")
2206
+ #
2207
+ # Sending charset as the second positional argument is supported for
2208
+ # backward compatibility. Future versions may print a deprecation warning:
2209
+ # imap.search(%w(OR UNSEEN FLAGGED), "UTF-8", return: %w(MIN MAX))
2210
+ #
2211
+ # ==== Argument translation
2212
+ #
2213
+ # [+return+ options]
2214
+ # Must be an Array. Return option names may be either strings or symbols.
2215
+ # +Range+ elements which begin and end with negative integers are encoded
2216
+ # for use with +PARTIAL+--any other ranges are converted to SequenceSet.
2217
+ # Unlike +criteria+, other return option arguments are not automatically
2218
+ # converted to SequenceSet.
2219
+ #
2220
+ # [When +criteria+ is an Array]
2221
+ # When the array begins with <tt>"RETURN"</tt> (case insensitive), the
2222
+ # second array element is translated like the +return+ parameter (as
2223
+ # described above).
2224
+ #
2225
+ # Every other member is a +SEARCH+ command argument:
2226
+ # [SequenceSet]
2227
+ # Encoded as an \IMAP +sequence-set+ with SequenceSet#valid_string.
2228
+ # [Set, Range, <tt>-1</tt>, +:*+, responds to +#to_sequence_set+]
2229
+ # Converted to SequenceSet for validation and encoding.
2230
+ # [nested sequence-set +Array+]
2231
+ # When every element in a nested array is one of the above types, a
2232
+ # positive +Integer+, a sequence-set formatted +String+, or a deeply
2233
+ # nested +Array+ of these same types, the array will be converted to
2234
+ # SequenceSet for validation and encoding.
2235
+ # [Any other nested +Array+]
2236
+ # Otherwise, a nested array is encoded as a parenthesized list, to
2237
+ # combine multiple search keys (e.g., for use with +OR+ and +NOT+).
2238
+ # [+String+]
2239
+ # Sent verbatim when it is a valid \IMAP +atom+, and encoded as an \IMAP
2240
+ # +quoted+ or +literal+ string otherwise. Every standard search key
2241
+ # name is a valid \IMAP +atom+ and every standard search key string
2242
+ # argument is an +astring+ which may be encoded as +atom+, +quoted+, or
2243
+ # +literal+.
2244
+ #
2245
+ # *Note:* <tt>*</tt> is not a valid \IMAP +atom+ character. Any string
2246
+ # containing <tt>*</tt> will be encoded as a +quoted+ string, _not_ a
2247
+ # +sequence-set+.
2248
+ # [+Integer+ (except for <tt>-1</tt>)]
2249
+ # Encoded using +#to_s+.
2250
+ # [+Date+]
2251
+ # Encoded as an \IMAP date (see ::encode_date).
2252
+ #
2253
+ # [When +criteria+ is a String]
2254
+ # +criteria+ will be sent directly to the server <em>without any
2255
+ # validation or encoding</em>.
2256
+ #
2257
+ # <em>*WARNING:* This is vulnerable to injection attacks when external
2258
+ # inputs are used.</em>
2259
+ #
2260
+ # ==== Supported return options
2261
+ #
2262
+ # For full definitions of the standard return options and return data, see
2263
+ # the relevant RFCs.
2264
+ #
2265
+ # [+ALL+]
2266
+ # Returns ESearchResult#all with a SequenceSet of all matching sequence
2267
+ # numbers or UIDs. This is the default, when return options are empty.
2268
+ #
2269
+ # For compatibility with SearchResult, ESearchResult#to_a returns an
2270
+ # Array of message sequence numbers or UIDs.
2271
+ #
2272
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2273
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2274
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2275
+ #
2276
+ # [+COUNT+]
2277
+ # Returns ESearchResult#count with the number of matching messages.
2278
+ #
2279
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2280
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2281
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2282
+ #
2283
+ # [+MAX+]
2284
+ # Returns ESearchResult#max with the highest matching sequence number or
2285
+ # UID.
2286
+ #
2287
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2288
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2289
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2290
+ #
2291
+ # [+MIN+]
2292
+ # Returns ESearchResult#min with the lowest matching sequence number or
2293
+ # UID.
2294
+ #
2295
+ # <em>Requires either the +ESEARCH+ or +IMAP4rev2+ capabability.</em>
2296
+ # {[RFC4731]}[https://rfc-editor.org/rfc/rfc4731]
2297
+ # {[RFC9051]}[https://rfc-editor.org/rfc/rfc9051]
2298
+ #
2299
+ # [+PARTIAL+ _range_]
2300
+ # Returns ESearchResult#partial with a SequenceSet of a subset of
2301
+ # matching sequence numbers or UIDs, as selected by _range_. As with
2302
+ # sequence numbers, the first result is +1+: <tt>1..500</tt> selects the
2303
+ # first 500 search results (in mailbox order), <tt>501..1000</tt> the
2304
+ # second 500, and so on. _range_ may also be negative: <tt>-500..-1</tt>
2305
+ # selects the last 500 search results.
2306
+ #
2307
+ # <em>Requires either the <tt>CONTEXT=SEARCH</tt> or +PARTIAL+ capabability.</em>
2308
+ # {[RFC5267]}[https://rfc-editor.org/rfc/rfc5267]
2309
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2310
+ #
2311
+ # ===== +MODSEQ+ return data
2312
+ #
2313
+ # ESearchResult#modseq return data does not have a corresponding return
2314
+ # option. Instead, it is returned if the +MODSEQ+ search key is used or
2315
+ # when the +CONDSTORE+ extension is enabled for the selected mailbox.
2316
+ # See [{RFC4731 §3.2}[https://www.rfc-editor.org/rfc/rfc4731#section-3.2]]
2317
+ # or [{RFC7162 §2.1.5}[https://www.rfc-editor.org/rfc/rfc7162#section-3.1.5]].
2318
+ #
2319
+ # ===== +RFC4466+ compatible extensions
2320
+ #
2321
+ # {RFC4466 §2.6}[https://www.rfc-editor.org/rfc/rfc4466.html#section-2.6]
2322
+ # defines standard syntax for search extensions. Net::IMAP allows sending
2323
+ # unsupported search return options and will parse unsupported search
2324
+ # extensions' return values into ExtensionData. Please note that this is an
2325
+ # intentionally _unstable_ API. Future releases may return different
2326
+ # (incompatible) objects, <em>without deprecation or warning</em>.
2327
+ #
2328
+ # ==== Search keys
2329
+ #
2330
+ # For full definitions of the standard search +criteria+,
1936
2331
  # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]],
1937
2332
  # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]],
1938
2333
  # in addition to documentation for
1939
- # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]]
1940
- # reported by #capabilities which may define additional search filters, e.g:
2334
+ # any #capabilities which may define additional search filters, such as
1941
2335
  # +CONDSTORE+, +WITHIN+, +FILTERS+, <tt>SEARCH=FUZZY</tt>, +OBJECTID+, or
1942
- # +SAVEDATE+. The following are some common search criteria:
2336
+ # +SAVEDATE+.
1943
2337
  #
1944
- # <message set>:: a set of message sequence numbers. "<tt>,</tt>" indicates
1945
- # an interval, "+:+" indicates a range. For instance,
1946
- # "<tt>2,10:12,15</tt>" means "<tt>2,10,11,12,15</tt>".
2338
+ # With the exception of <em>sequence-set</em> and <em>parenthesized
2339
+ # list</em>, all search keys are composed of prefix label with zero or more
2340
+ # arguments. The number and type of arguments is specific to each search
2341
+ # key.
1947
2342
  #
1948
- # BEFORE <date>:: messages with an internal date strictly before
1949
- # <b><date></b>. The date argument has a format similar
1950
- # to <tt>8-Aug-2002</tt>, and can be formatted using
1951
- # Net::IMAP.format_date.
2343
+ # ===== Search keys that match all messages
1952
2344
  #
1953
- # BODY <string>:: messages that contain <string> within their body.
2345
+ # [+ALL+]
2346
+ # The default initial key. Matches every message in the mailbox.
1954
2347
  #
1955
- # CC <string>:: messages containing <string> in their CC field.
2348
+ # [+SAVEDATESUPPORTED+]
2349
+ # Matches every message in the mailbox when the mailbox supports the save
2350
+ # date attribute. Otherwise, it matches no messages.
1956
2351
  #
1957
- # FROM <string>:: messages that contain <string> in their FROM field.
2352
+ # <em>Requires +SAVEDATE+ capability</em>.
2353
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
1958
2354
  #
1959
- # NEW:: messages with the \Recent, but not the \Seen, flag set.
2355
+ # ===== Sequence set search keys
1960
2356
  #
1961
- # NOT <search-key>:: negate the following search key.
2357
+ # [_sequence-set_]
2358
+ # Matches messages with message sequence numbers in _sequence-set_.
1962
2359
  #
1963
- # OR <search-key> <search-key>:: "or" two search keys together.
2360
+ # _Note:_ this search key has no label.
1964
2361
  #
1965
- # ON <date>:: messages with an internal date exactly equal to <date>,
1966
- # which has a format similar to 8-Aug-2002.
2362
+ # <em>+UIDONLY+ must *not* be enabled.</em>
2363
+ # {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.html]
1967
2364
  #
1968
- # SINCE <date>:: messages with an internal date on or after <date>.
2365
+ # [+UID+ _sequence-set_]
2366
+ # Matches messages with a UID in _sequence-set_.
1969
2367
  #
1970
- # SUBJECT <string>:: messages with <string> in their subject.
2368
+ # ===== Compound search keys
1971
2369
  #
1972
- # TO <string>:: messages with <string> in their TO field.
2370
+ # [(_search-key_ _search-key_...)]
2371
+ # Combines one or more _search-key_ arguments to match
2372
+ # messages which match all contained search keys. Useful for +OR+, +NOT+,
2373
+ # and other search keys with _search-key_ arguments.
1973
2374
  #
1974
- # ===== For example:
2375
+ # _Note:_ this search key has no label.
1975
2376
  #
1976
- # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
1977
- # #=> [1, 6, 7, 8]
2377
+ # [+OR+ _search-key_ _search-key_]
2378
+ # Matches messages which match either _search-key_ argument.
2379
+ #
2380
+ # [+NOT+ _search-key_]
2381
+ # Matches messages which do not match _search-key_.
2382
+ #
2383
+ # [+FUZZY+ _search-key_]
2384
+ # Uses fuzzy matching for the specified search key.
2385
+ #
2386
+ # <em>Requires <tt>SEARCH=FUZZY</tt> capability.</em>
2387
+ # {[RFC6203]}[https://www.rfc-editor.org/rfc/rfc6203.html#section-6].
2388
+ #
2389
+ # ===== Flags search keys
2390
+ #
2391
+ # [+ANSWERED+, +UNANSWERED+]
2392
+ # Matches messages with or without the <tt>\\Answered</tt> flag.
2393
+ # [+DELETED+, +UNDELETED+]
2394
+ # Matches messages with or without the <tt>\\Deleted</tt> flag.
2395
+ # [+DRAFT+, +UNDRAFT+]
2396
+ # Matches messages with or without the <tt>\\Draft</tt> flag.
2397
+ # [+FLAGGED+, +UNFLAGGED+]
2398
+ # Matches messages with or without the <tt>\\Flagged</tt> flag.
2399
+ # [+SEEN+, +UNSEEN+]
2400
+ # Matches messages with or without the <tt>\\Seen</tt> flag.
2401
+ # [+KEYWORD+ _keyword_, +UNKEYWORD+ _keyword_]
2402
+ # Matches messages with or without the specified _keyword_.
2403
+ #
2404
+ # [+RECENT+, +UNRECENT+]
2405
+ # Matches messages with or without the <tt>\\Recent</tt> flag.
2406
+ #
2407
+ # *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
2408
+ # [+NEW+]
2409
+ # Equivalent to <tt>(RECENT UNSEEN)</tt>.
2410
+ #
2411
+ # *NOTE:* The <tt>\\Recent</tt> flag has been removed from +IMAP4rev2+.
2412
+ #
2413
+ # ===== Header field substring search keys
2414
+ #
2415
+ # [+BCC+ _substring_]
2416
+ # Matches when _substring_ is in the envelope's +BCC+ field.
2417
+ # [+CC+ _substring_]
2418
+ # Matches when _substring_ is in the envelope's +CC+ field.
2419
+ # [+FROM+ _substring_]
2420
+ # Matches when _substring_ is in the envelope's +FROM+ field.
2421
+ # [+SUBJECT+ _substring_]
2422
+ # Matches when _substring_ is in the envelope's +SUBJECT+ field.
2423
+ # [+TO+ _substring_]
2424
+ # Matches when _substring_ is in the envelope's +TO+ field.
2425
+ #
2426
+ # [+HEADER+ _field_ _substring_]
2427
+ # Matches when _substring_ is in the specified header _field_.
2428
+ #
2429
+ # ===== Body text search keys
2430
+ # [+BODY+ _string_]
2431
+ # Matches when _string_ is in the body of the message.
2432
+ # Does not match on header fields.
2433
+ #
2434
+ # The server _may_ use flexible matching, rather than simple substring
2435
+ # matches. For example, this may use stemming or match only full words.
2436
+ #
2437
+ # [+TEXT+ _string_]
2438
+ # Matches when _string_ is in the header or body of the message.
2439
+ #
2440
+ # The server _may_ use flexible matching, rather than simple substring
2441
+ # matches. For example, this may use stemming or match only full words.
2442
+ #
2443
+ # ===== Date/Time search keys
2444
+ #
2445
+ # [+SENTBEFORE+ _date_]
2446
+ # [+SENTON+ _date_]
2447
+ # [+SENTSINCE+ _date_]
2448
+ # Matches when the +Date+ header is earlier than, on, or later than _date_.
2449
+ #
2450
+ # [+BEFORE+ _date_]
2451
+ # [+ON+ _date_]
2452
+ # [+SINCE+ _date_]
2453
+ # Matches when the +INTERNALDATE+ is earlier than, on, or later than
2454
+ # _date_.
2455
+ #
2456
+ # [+OLDER+ _interval_]
2457
+ # [+YOUNGER+ _interval_]
2458
+ # Matches when the +INTERNALDATE+ is more/less than _interval_ seconds ago.
2459
+ #
2460
+ # <em>Requires +WITHIN+ capability</em>.
2461
+ # {[RFC5032]}[https://www.rfc-editor.org/rfc/rfc5032.html]
2462
+ #
2463
+ # [+SAVEDBEFORE+ _date_]
2464
+ # [+SAVEDON+ _date_]
2465
+ # [+SAVEDSINCE+ _date_]
2466
+ # Matches when the save date is earlier than, on, or later than _date_.
2467
+ #
2468
+ # <em>Requires +SAVEDATE+ capability.</em>
2469
+ # {[RFC8514]}[https://www.rfc-editor.org/rfc/rfc8514.html#section-4.3]
2470
+ #
2471
+ # ===== Other message attribute search keys
2472
+ #
2473
+ # [+SMALLER+ _bytes_]
2474
+ # [+LARGER+ _bytes_]
2475
+ # Matches when +RFC822.SIZE+ is smaller or larger than _bytes_.
2476
+ #
2477
+ # [+ANNOTATION+ _entry_ _attr_ _value_]
2478
+ # Matches messages that have annotations with entries matching _entry_,
2479
+ # attributes matching _attr_, and _value_ in the attribute's values.
2480
+ #
2481
+ # <em>Requires +ANNOTATE-EXPERIMENT-1+ capability</em>.
2482
+ # {[RFC5257]}[https://www.rfc-editor.org/rfc/rfc5257.html].
2483
+ #
2484
+ # [+FILTER+ _filter_]
2485
+ # References a _filter_ that is stored on the server and matches all
2486
+ # messages which would be matched by that filter's search criteria.
2487
+ #
2488
+ # <em>Requires +FILTERS+ capability</em>.
2489
+ # {[RFC5466]}[https://www.rfc-editor.org/rfc/rfc5466.html#section-3.1]
2490
+ #
2491
+ # [+MODSEQ+ _modseq_]
2492
+ # Matches when +MODSEQ+ is greater than or equal to _modseq_.
2493
+ #
2494
+ # <em>Requires +CONDSTORE+ capability</em>.
2495
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2496
+ #
2497
+ # [+MODSEQ+ _entry_ _entry-type_ _modseq_]
2498
+ # Matches when a specific metadata _entry_ has been updated since
2499
+ # _modseq_.
2500
+ #
2501
+ # For flags, the corresponding _entry_ name is
2502
+ # <tt>"/flags/#{flag_name}"</tt>, where _flag_name_ includes the
2503
+ # <tt>\\</tt> prefix. _entry-type_ can be one of <tt>"shared"</tt>,
2504
+ # <tt>"priv"</tt> (private), or <tt>"all"</tt>.
2505
+ #
2506
+ # <em>Requires +CONDSTORE+ capability</em>.
2507
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.5].
2508
+ #
2509
+ # [+EMAILID+ _objectid_]
2510
+ # [+THREADID+ _objectid_]
2511
+ # Matches when +EMAILID+/+THREADID+ is equal to _objectid_
2512
+ # (substring matches are not supported).
2513
+ #
2514
+ # <em>Requires +OBJECTID+ capability</em>.
2515
+ # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html#section-6]
2516
+ #
2517
+ # ==== Capabilities
2518
+ #
2519
+ # Return options should only be specified when the server supports
2520
+ # +IMAP4rev2+ or an extension that allows them, such as +ESEARCH+
2521
+ # [RFC4731[https://rfc-editor.org/rfc/rfc4731#section-3.1]].
1978
2522
  #
1979
- # ===== Capabilities
2523
+ # When +IMAP4rev2+ is enabled, or when the server supports +IMAP4rev2+ but
2524
+ # not +IMAP4rev1+, ESearchResult is always returned instead of SearchResult.
1980
2525
  #
1981
- # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported
2526
+ # If CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html] is supported
1982
2527
  # and enabled for the selected mailbox, a non-empty SearchResult will
1983
2528
  # include a +MODSEQ+ value.
1984
2529
  # imap.select("mbox", condstore: true)
1985
- # result = imap.search(["SUBJECT", "hi there", "not", "new")
2530
+ # result = imap.search(["SUBJECT", "hi there", "not", "new"])
1986
2531
  # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594]
1987
2532
  # result.modseq # => 5594
1988
- def search(keys, charset = nil)
1989
- return search_internal("SEARCH", keys, charset)
2533
+ #
2534
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2535
+ # the +SEARCH+ command is prohibited. Use #uid_search instead.
2536
+ def search(...)
2537
+ search_internal("SEARCH", ...)
1990
2538
  end
1991
2539
 
2540
+ # :call-seq:
2541
+ # uid_search(criteria, charset = nil) -> result
2542
+ # uid_search(criteria, charset: nil, return: nil) -> result
2543
+ #
1992
2544
  # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
1993
2545
  # to search the mailbox for messages that match the given searching
1994
2546
  # criteria, and returns unique identifiers (<tt>UID</tt>s).
@@ -1997,9 +2549,19 @@ module Net
1997
2549
  # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+
1998
2550
  # capability has been enabled.
1999
2551
  #
2000
- # See #search for documentation of search criteria.
2001
- def uid_search(keys, charset = nil)
2002
- return search_internal("UID SEARCH", keys, charset)
2552
+ # See #search for documentation of parameters.
2553
+ #
2554
+ # ==== Capabilities
2555
+ #
2556
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2557
+ # #uid_search must be used instead of #search, and the <tt><message
2558
+ # set></tt> search criterion is prohibited. Use +ALL+ or <tt>UID
2559
+ # sequence-set</tt> instead.
2560
+ #
2561
+ # Otherwise, #uid_search is updated by extensions in the same way as
2562
+ # #search.
2563
+ def uid_search(...)
2564
+ search_internal("UID SEARCH", ...)
2003
2565
  end
2004
2566
 
2005
2567
  # :call-seq:
@@ -2008,26 +2570,21 @@ module Net
2008
2570
  # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5]
2009
2571
  # to retrieve data associated with a message in the mailbox.
2010
2572
  #
2011
- # The +set+ parameter is a number or a range between two numbers,
2012
- # or an array of those. The number is a message sequence number,
2013
- # where -1 represents a '*' for use in range notation like 100..-1
2014
- # being interpreted as '100:*'. Beware that the +exclude_end?+
2015
- # property of a Range object is ignored, and the contents of a
2016
- # range are independent of the order of the range endpoints as per
2017
- # the protocol specification, so 1...5, 5..1 and 5...1 are all
2018
- # equivalent to 1..5.
2573
+ # +set+ is the message sequence numbers to fetch, and may be any valid input
2574
+ # to {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2575
+ # (For UIDs, use #uid_fetch instead.)
2019
2576
  #
2020
- # +attr+ is a list of attributes to fetch; see the documentation
2021
- # for FetchData for a list of valid attributes.
2577
+ # +attr+ is a list of attributes to fetch; see FetchStruct documentation for
2578
+ # a list of supported attributes.
2022
2579
  #
2023
2580
  # +changedsince+ is an optional integer mod-sequence. It limits results to
2024
2581
  # messages with a mod-sequence greater than +changedsince+.
2025
2582
  #
2026
2583
  # The return value is an array of FetchData.
2027
2584
  #
2028
- # Related: #uid_search, FetchData
2585
+ # Related: #uid_fetch, FetchData
2029
2586
  #
2030
- # ===== For example:
2587
+ # ==== For example:
2031
2588
  #
2032
2589
  # p imap.fetch(6..8, "UID")
2033
2590
  # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
@@ -2045,39 +2602,82 @@ module Net
2045
2602
  # p data.attr["UID"]
2046
2603
  # #=> 98
2047
2604
  #
2048
- # ===== Capabilities
2605
+ # ==== Capabilities
2049
2606
  #
2050
- # Many extensions define new message +attr+ names. See FetchData for a list
2051
- # of supported extension fields.
2607
+ # Many extensions define new message +attr+ names. See FetchStruct for a
2608
+ # list of supported extension fields.
2052
2609
  #
2053
2610
  # The server's capabilities must include +CONDSTORE+
2054
- # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
2611
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
2055
2612
  # +changedsince+ argument. Using +changedsince+ implicitly enables the
2056
2613
  # +CONDSTORE+ extension.
2057
- def fetch(set, attr, mod = nil, changedsince: nil)
2058
- fetch_internal("FETCH", set, attr, mod, changedsince: changedsince)
2614
+ #
2615
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2616
+ # +FETCH+ command is prohibited. Use #uid_fetch instead.
2617
+ def fetch(...)
2618
+ fetch_internal("FETCH", ...)
2059
2619
  end
2060
2620
 
2061
2621
  # :call-seq:
2062
- # uid_fetch(set, attr, changedsince: nil) -> array of FetchData
2622
+ # uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
2063
2623
  #
2064
2624
  # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
2065
2625
  # to retrieve data associated with a message in the mailbox.
2066
2626
  #
2067
- # Similar to #fetch, but the +set+ parameter contains unique identifiers
2068
- # instead of message sequence numbers.
2627
+ # +set+ is the message UIDs to fetch, and may be any valid input to
2628
+ # {SequenceSet[...]}[rdoc-ref:SequenceSet@Creating+sequence+sets].
2629
+ # (For message sequence numbers, use #fetch instead.)
2069
2630
  #
2631
+ # +attr+ behaves the same as with #fetch.
2070
2632
  # >>>
2071
2633
  # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as
2072
2634
  # part of any +FETCH+ response caused by a +UID+ command, regardless of
2073
2635
  # whether a +UID+ was specified as a message data item to the +FETCH+.
2074
2636
  #
2637
+ # +changedsince+ (optional) behaves the same as with #fetch.
2638
+ #
2639
+ # +partial+ is an optional range to limit the number of results returned.
2640
+ # It's useful when +set+ contains an unknown number of messages.
2641
+ # <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
2642
+ # order), <tt>501..1000</tt> the second 500, and so on. +partial+ may also
2643
+ # be negative: <tt>-500..-1</tt> selects the last 500 messages in +set+.
2644
+ # <em>Requires the +PARTIAL+ capabability.</em>
2645
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394]
2646
+ #
2647
+ # For example:
2648
+ #
2649
+ # # Without partial, the size of the results may be unknown beforehand:
2650
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS))
2651
+ # # ... maybe wait for a long time ... and allocate a lot of memory ...
2652
+ # results.size # => 0..2**32-1
2653
+ # process results # may also take a long time and use a lot of memory...
2654
+ #
2655
+ # # Using partial, the results may be paginated:
2656
+ # loop do
2657
+ # results = imap.uid_fetch(next_uid_to_fetch.., %w(UID FLAGS),
2658
+ # partial: 1..500)
2659
+ # # fetch should return quickly and allocate little memory
2660
+ # results.size # => 0..500
2661
+ # break if results.empty?
2662
+ # next_uid_to_fetch = results.last.uid + 1
2663
+ # process results
2664
+ # end
2665
+ #
2075
2666
  # Related: #fetch, FetchData
2076
2667
  #
2077
- # ===== Capabilities
2078
- # Same as #fetch.
2079
- def uid_fetch(set, attr, mod = nil, changedsince: nil)
2080
- fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince)
2668
+ # ==== Capabilities
2669
+ #
2670
+ # The server's capabilities must include +PARTIAL+
2671
+ # {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
2672
+ # +partial+ argument.
2673
+ #
2674
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2675
+ # #uid_fetch must be used instead of #fetch, and UIDFetchData will be
2676
+ # returned instead of FetchData.
2677
+ #
2678
+ # Otherwise, #uid_fetch is updated by extensions in the same way as #fetch.
2679
+ def uid_fetch(...)
2680
+ fetch_internal("UID FETCH", ...)
2081
2681
  end
2082
2682
 
2083
2683
  # :call-seq:
@@ -2108,27 +2708,30 @@ module Net
2108
2708
  #
2109
2709
  # Related: #uid_store
2110
2710
  #
2111
- # ===== For example:
2711
+ # ==== For example:
2112
2712
  #
2113
2713
  # p imap.store(6..8, "+FLAGS", [:Deleted])
2114
2714
  # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>,
2115
2715
  # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>,
2116
2716
  # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
2117
2717
  #
2118
- # ===== Capabilities
2718
+ # ==== Capabilities
2119
2719
  #
2120
2720
  # Extensions may define new data items to be used with #store.
2121
2721
  #
2122
2722
  # The server's capabilities must include +CONDSTORE+
2123
- # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the
2723
+ # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162] in order to use the
2124
2724
  # +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the
2125
2725
  # +CONDSTORE+ extension.
2726
+ #
2727
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2728
+ # +STORE+ command is prohibited. Use #uid_store instead.
2126
2729
  def store(set, attr, flags, unchangedsince: nil)
2127
2730
  store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince)
2128
2731
  end
2129
2732
 
2130
2733
  # :call-seq:
2131
- # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData
2734
+ # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData (or UIDFetchData)
2132
2735
  #
2133
2736
  # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
2134
2737
  # to alter data associated with messages in the mailbox, in particular their
@@ -2139,8 +2742,13 @@ module Net
2139
2742
  #
2140
2743
  # Related: #store
2141
2744
  #
2142
- # ===== Capabilities
2143
- # Same as #store.
2745
+ # ==== Capabilities
2746
+ #
2747
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2748
+ # #uid_store must be used instead of #store, and UIDFetchData will be
2749
+ # returned instead of FetchData.
2750
+ #
2751
+ # Otherwise, #uid_store is updated by extensions in the same way as #store.
2144
2752
  def uid_store(set, attr, flags, unchangedsince: nil)
2145
2753
  store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince)
2146
2754
  end
@@ -2152,13 +2760,16 @@ module Net
2152
2760
  #
2153
2761
  # Related: #uid_copy
2154
2762
  #
2155
- # ===== Capabilities
2763
+ # ==== Capabilities
2156
2764
  #
2157
2765
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
2158
2766
  # supported, the server's response should include a +COPYUID+ response code
2159
2767
  # with UIDPlusData. This will report the UIDVALIDITY of the destination
2160
2768
  # mailbox, the UID set of the source messages, and the assigned UID set of
2161
2769
  # the moved messages.
2770
+ #
2771
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2772
+ # +COPY+ command is prohibited. Use #uid_copy instead.
2162
2773
  def copy(set, mailbox)
2163
2774
  copy_internal("COPY", set, mailbox)
2164
2775
  end
@@ -2169,9 +2780,12 @@ module Net
2169
2780
  #
2170
2781
  # Similar to #copy, but +set+ contains unique identifiers.
2171
2782
  #
2172
- # ===== Capabilities
2783
+ # ==== Capabilities
2784
+ #
2785
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] in enabled,
2786
+ # #uid_copy must be used instead of #copy.
2173
2787
  #
2174
- # +UIDPLUS+ affects #uid_copy the same way it affects #copy.
2788
+ # Otherwise, #uid_copy is updated by extensions in the same way as #copy.
2175
2789
  def uid_copy(set, mailbox)
2176
2790
  copy_internal("UID COPY", set, mailbox)
2177
2791
  end
@@ -2184,10 +2798,10 @@ module Net
2184
2798
  #
2185
2799
  # Related: #uid_move
2186
2800
  #
2187
- # ===== Capabilities
2801
+ # ==== Capabilities
2188
2802
  #
2189
- # The server's capabilities must include +MOVE+
2190
- # [RFC6851[https://tools.ietf.org/html/rfc6851]].
2803
+ # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
2804
+ # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
2191
2805
  #
2192
2806
  # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is
2193
2807
  # supported, the server's response should include a +COPYUID+ response code
@@ -2195,6 +2809,8 @@ module Net
2195
2809
  # mailbox, the UID set of the source messages, and the assigned UID set of
2196
2810
  # the moved messages.
2197
2811
  #
2812
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled, the
2813
+ # +MOVE+ command is prohibited. Use #uid_move instead.
2198
2814
  def move(set, mailbox)
2199
2815
  copy_internal("MOVE", set, mailbox)
2200
2816
  end
@@ -2208,11 +2824,15 @@ module Net
2208
2824
  #
2209
2825
  # Related: #move
2210
2826
  #
2211
- # ===== Capabilities
2827
+ # ==== Capabilities
2828
+ #
2829
+ # The server's capabilities must include either +IMAP4rev2+ or +MOVE+
2830
+ # [RFC6851[https://www.rfc-editor.org/rfc/rfc6851]].
2831
+ #
2832
+ # When UIDONLY[https://www.rfc-editor.org/rfc/rfc9586.html] is enabled,
2833
+ # #uid_move must be used instead of #move.
2212
2834
  #
2213
- # Same as #move: The server's capabilities must include +MOVE+
2214
- # [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects
2215
- # #uid_move the same way it affects #move.
2835
+ # Otherwise, #uid_move is updated by extensions in the same way as #move.
2216
2836
  def uid_move(set, mailbox)
2217
2837
  copy_internal("UID MOVE", set, mailbox)
2218
2838
  end
@@ -2228,17 +2848,17 @@ module Net
2228
2848
  #
2229
2849
  # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread
2230
2850
  #
2231
- # ===== For example:
2851
+ # ==== For example:
2232
2852
  #
2233
2853
  # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
2234
2854
  # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
2235
2855
  # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
2236
2856
  # #=> [6, 7, 8, 1]
2237
2857
  #
2238
- # ===== Capabilities
2858
+ # ==== Capabilities
2239
2859
  #
2240
2860
  # The server's capabilities must include +SORT+
2241
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2861
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
2242
2862
  def sort(sort_keys, search_keys, charset)
2243
2863
  return sort_internal("SORT", sort_keys, search_keys, charset)
2244
2864
  end
@@ -2250,10 +2870,10 @@ module Net
2250
2870
  #
2251
2871
  # Related: #sort, #search, #uid_search, #thread, #uid_thread
2252
2872
  #
2253
- # ===== Capabilities
2873
+ # ==== Capabilities
2254
2874
  #
2255
2875
  # The server's capabilities must include +SORT+
2256
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2876
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
2257
2877
  def uid_sort(sort_keys, search_keys, charset)
2258
2878
  return sort_internal("UID SORT", sort_keys, search_keys, charset)
2259
2879
  end
@@ -2275,10 +2895,10 @@ module Net
2275
2895
  #
2276
2896
  # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort
2277
2897
  #
2278
- # ===== Capabilities
2898
+ # ==== Capabilities
2279
2899
  #
2280
2900
  # The server's capabilities must include +THREAD+
2281
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2901
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
2282
2902
  def thread(algorithm, search_keys, charset)
2283
2903
  return thread_internal("THREAD", algorithm, search_keys, charset)
2284
2904
  end
@@ -2289,10 +2909,10 @@ module Net
2289
2909
  #
2290
2910
  # Related: #thread, #search, #uid_search, #sort, #uid_sort
2291
2911
  #
2292
- # ===== Capabilities
2912
+ # ==== Capabilities
2293
2913
  #
2294
2914
  # The server's capabilities must include +THREAD+
2295
- # [RFC5256[https://tools.ietf.org/html/rfc5256]].
2915
+ # [RFC5256[https://www.rfc-editor.org/rfc/rfc5256]].
2296
2916
  def uid_thread(algorithm, search_keys, charset)
2297
2917
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
2298
2918
  end
@@ -2308,11 +2928,11 @@ module Net
2308
2928
  #
2309
2929
  # Related: #capable?, #capabilities, #capability
2310
2930
  #
2311
- # ===== Capabilities
2931
+ # ==== Capabilities
2312
2932
  #
2313
2933
  # The server's capabilities must include
2314
- # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]]
2315
- # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]].
2934
+ # +ENABLE+ [RFC5161[https://www.rfc-editor.org/rfc/rfc5161]]
2935
+ # or +IMAP4REV2+ [RFC9051[https://www.rfc-editor.org/rfc/rfc9051]].
2316
2936
  #
2317
2937
  # Additionally, the server capabilities must include a capability matching
2318
2938
  # each enabled extension (usually the same name as the enabled extension).
@@ -2331,7 +2951,7 @@ module Net
2331
2951
  # <tt>"UTF8=ACCEPT"</tt> or <tt>"IMAP4rev2"</tt>, depending on server
2332
2952
  # capabilities.
2333
2953
  #
2334
- # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2954
+ # [<tt>"UTF8=ACCEPT"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
2335
2955
  #
2336
2956
  # The server's capabilities must include <tt>UTF8=ACCEPT</tt> _or_
2337
2957
  # <tt>UTF8=ONLY</tt>.
@@ -2350,13 +2970,23 @@ module Net
2350
2970
  # encoding, even if they generally contain UTF-8 data, if they are
2351
2971
  # text at all.
2352
2972
  #
2353
- # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://tools.ietf.org/html/rfc6855]]]
2973
+ # [<tt>"UTF8=ONLY"</tt> [RFC6855[https://www.rfc-editor.org/rfc/rfc6855]]]
2354
2974
  #
2355
2975
  # A server that reports the <tt>UTF8=ONLY</tt> capability _requires_ that
2356
2976
  # the client <tt>enable("UTF8=ACCEPT")</tt> before any mailboxes may be
2357
2977
  # selected. For convenience, <tt>enable("UTF8=ONLY")</tt> is aliased to
2358
2978
  # <tt>enable("UTF8=ACCEPT")</tt>.
2359
2979
  #
2980
+ # [+UIDONLY+ {[RFC9586]}[https://www.rfc-editor.org/rfc/rfc9586.pdf]]
2981
+ #
2982
+ # When UIDONLY is enabled, the #fetch, #store, #search, #copy, and #move
2983
+ # commands are prohibited and result in a tagged BAD response. Clients
2984
+ # should instead use uid_fetch, uid_store, uid_search, uid_copy, or
2985
+ # uid_move, respectively. All +FETCH+ responses that would be returned are
2986
+ # replaced by +UIDFETCH+ responses. All +EXPUNGED+ responses that would be
2987
+ # returned are replaced by +VANISHED+ responses. The "<sequence set>"
2988
+ # uid_search criterion is prohibited.
2989
+ #
2360
2990
  # ===== Unsupported capabilities
2361
2991
  #
2362
2992
  # *Note:* Some extensions that use ENABLE permit the server to send syntax
@@ -2397,17 +3027,23 @@ module Net
2397
3027
  # checks the connection for each 60 seconds.
2398
3028
  #
2399
3029
  # loop do
2400
- # imap.idle(60) do |res|
2401
- # ...
3030
+ # imap.idle(60) do |response|
3031
+ # do_something_with(response)
3032
+ # imap.idle_done if some_condition?(response)
2402
3033
  # end
2403
3034
  # end
2404
3035
  #
3036
+ # Returns the server's response to indicate the IDLE state has ended.
3037
+ # Returns +nil+ if the server does not respond to #idle_done within
3038
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
3039
+ # seconds.
3040
+ #
2405
3041
  # Related: #idle_done, #noop, #check
2406
3042
  #
2407
- # ===== Capabilities
3043
+ # ==== Capabilities
2408
3044
  #
2409
- # The server's capabilities must include +IDLE+
2410
- # [RFC2177[https://tools.ietf.org/html/rfc2177]].
3045
+ # The server's capabilities must include either +IMAP4rev2+ or +IDLE+
3046
+ # [RFC2177[https://www.rfc-editor.org/rfc/rfc2177]].
2411
3047
  def idle(timeout = nil, &response_handler)
2412
3048
  raise LocalJumpError, "no block given" unless response_handler
2413
3049
 
@@ -2429,7 +3065,7 @@ module Net
2429
3065
  unless @receiver_thread_terminating
2430
3066
  remove_response_handler(response_handler)
2431
3067
  put_string("DONE#{CRLF}")
2432
- response = get_tagged_response(tag, "IDLE", @idle_response_timeout)
3068
+ response = get_tagged_response(tag, "IDLE", idle_response_timeout)
2433
3069
  end
2434
3070
  end
2435
3071
  end
@@ -2437,7 +3073,11 @@ module Net
2437
3073
  return response
2438
3074
  end
2439
3075
 
2440
- # Leaves IDLE.
3076
+ # Leaves IDLE, allowing #idle to return.
3077
+ #
3078
+ # If the server does not respond within
3079
+ # {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]
3080
+ # seconds, #idle will return +nil+.
2441
3081
  #
2442
3082
  # Related: #idle
2443
3083
  def idle_done
@@ -2449,40 +3089,98 @@ module Net
2449
3089
  end
2450
3090
  end
2451
3091
 
3092
+ RESPONSES_DEPRECATION_MSG =
3093
+ "Pass a type or block to #responses, " \
3094
+ "set config.responses_without_block to :frozen_dup " \
3095
+ "or :silence_deprecation_warning, " \
3096
+ "or use #extract_responses or #clear_responses."
3097
+ private_constant :RESPONSES_DEPRECATION_MSG
3098
+
2452
3099
  # :call-seq:
3100
+ # responses -> hash of {String => Array} (see config.responses_without_block)
3101
+ # responses(type) -> frozen array
2453
3102
  # responses {|hash| ...} -> block result
2454
3103
  # responses(type) {|array| ...} -> block result
2455
3104
  #
2456
- # Yields unhandled responses and returns the result of the block.
3105
+ # Yields or returns unhandled server responses. Unhandled responses are
3106
+ # stored in a hash, with arrays of UntaggedResponse#data keyed by
3107
+ # UntaggedResponse#name and <em>non-+nil+</em> untagged ResponseCode#data
3108
+ # keyed by ResponseCode#name.
3109
+ #
3110
+ # When a block is given, yields unhandled responses and returns the block's
3111
+ # result. Without a block, returns the unhandled responses.
2457
3112
  #
2458
- # Unhandled responses are stored in a hash, with arrays of
2459
- # <em>non-+nil+</em> UntaggedResponse#data keyed by UntaggedResponse#name
2460
- # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to
2461
- # yield the entire responses hash. Call with +type+ to yield only the array
2462
- # of responses for that type.
3113
+ # [With +type+]
3114
+ # Yield or return only the array of responses for that +type+.
3115
+ # When no block is given, the returned array is a frozen copy.
3116
+ # [Without +type+]
3117
+ # Yield or return the entire responses hash.
3118
+ #
3119
+ # When no block is given, the behavior is determined by
3120
+ # Config#responses_without_block:
3121
+ # >>>
3122
+ # [+:silence_deprecation_warning+ <em>(original behavior)</em>]
3123
+ # Returns the mutable responses hash (without any warnings).
3124
+ # <em>This is not thread-safe.</em>
3125
+ #
3126
+ # [+:warn+ <em>(default since +v0.5+)</em>]
3127
+ # Prints a warning and returns the mutable responses hash.
3128
+ # <em>This is not thread-safe.</em>
3129
+ #
3130
+ # [+:frozen_dup+ <em>(planned default for +v0.6+)</em>]
3131
+ # Returns a frozen copy of the unhandled responses hash, with frozen
3132
+ # array values.
3133
+ #
3134
+ # [+:raise+]
3135
+ # Raise an +ArgumentError+ with the deprecation warning.
2463
3136
  #
2464
3137
  # For example:
2465
3138
  #
2466
3139
  # imap.select("inbox")
2467
- # p imap.responses("EXISTS", &:last)
3140
+ # p imap.responses("EXISTS").last
2468
3141
  # #=> 2
3142
+ # p imap.responses("UIDNEXT", &:last)
3143
+ # #=> 123456
2469
3144
  # p imap.responses("UIDVALIDITY", &:last)
2470
3145
  # #=> 968263756
3146
+ # p imap.responses {|responses|
3147
+ # {
3148
+ # exists: responses.delete("EXISTS").last,
3149
+ # uidnext: responses.delete("UIDNEXT").last,
3150
+ # uidvalidity: responses.delete("UIDVALIDITY").last,
3151
+ # }
3152
+ # }
3153
+ # #=> {:exists=>2, :uidnext=>123456, :uidvalidity=>968263756}
3154
+ # # "EXISTS", "UIDNEXT", and "UIDVALIDITY" have been removed:
3155
+ # p imap.responses(&:keys)
3156
+ # #=> ["FLAGS", "OK", "PERMANENTFLAGS", "RECENT", "HIGHESTMODSEQ"]
3157
+ #
3158
+ # Related: #extract_responses, #clear_responses, #response_handlers, #greeting
2471
3159
  #
3160
+ # ==== Thread safety
2472
3161
  # >>>
2473
3162
  # *Note:* Access to the responses hash is synchronized for thread-safety.
2474
3163
  # The receiver thread and response_handlers cannot process new responses
2475
3164
  # until the block completes. Accessing either the response hash or its
2476
- # response type arrays outside of the block is unsafe.
3165
+ # response type arrays outside of the block is unsafe. They can be safely
3166
+ # updated inside the block. Consider using #clear_responses or
3167
+ # #extract_responses instead.
2477
3168
  #
2478
- # Calling without a block is unsafe and deprecated. Future releases will
2479
- # raise ArgumentError unless a block is given.
3169
+ # Net::IMAP will add and remove responses from the responses hash and its
3170
+ # array values, in the calling threads for commands and in the receiver
3171
+ # thread, but will not modify any responses after adding them to the
3172
+ # responses hash.
3173
+ #
3174
+ # ==== Clearing responses
2480
3175
  #
2481
3176
  # Previously unhandled responses are automatically cleared before entering a
2482
3177
  # mailbox with #select or #examine. Long-lived connections can receive many
2483
3178
  # unhandled server responses, which must be pruned or they will continually
2484
3179
  # consume more memory. Update or clear the responses hash or arrays inside
2485
- # the block, or use #clear_responses.
3180
+ # the block, or remove responses with #extract_responses, #clear_responses,
3181
+ # or #add_response_handler.
3182
+ #
3183
+ # ==== Missing responses
2486
3184
  #
2487
3185
  # Only non-+nil+ data is stored. Many important response codes have no data
2488
3186
  # of their own, but are used as "tags" on the ResponseText object they are
@@ -2493,15 +3191,25 @@ module Net
2493
3191
  # ResponseCode#data on tagged responses. Although some command methods do
2494
3192
  # return the TaggedResponse directly, #add_response_handler must be used to
2495
3193
  # handle all response codes.
2496
- #
2497
- # Related: #clear_responses, #response_handlers, #greeting
2498
3194
  def responses(type = nil)
2499
3195
  if block_given?
2500
3196
  synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
2501
3197
  elsif type
2502
- raise ArgumentError, "Pass a block or use #clear_responses"
3198
+ synchronize { @responses[type.to_s.upcase].dup.freeze }
2503
3199
  else
2504
- # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1)
3200
+ case config.responses_without_block
3201
+ when :raise
3202
+ raise ArgumentError, RESPONSES_DEPRECATION_MSG
3203
+ when :warn
3204
+ warn(RESPONSES_DEPRECATION_MSG, uplevel: 1, category: :deprecated)
3205
+ when :frozen_dup
3206
+ synchronize {
3207
+ responses = @responses.transform_values(&:freeze)
3208
+ responses.default_proc = nil
3209
+ responses.default = [].freeze
3210
+ return responses.freeze
3211
+ }
3212
+ end
2505
3213
  @responses
2506
3214
  end
2507
3215
  end
@@ -2516,7 +3224,7 @@ module Net
2516
3224
  # Clearing responses is synchronized with other threads. The lock is
2517
3225
  # released before returning.
2518
3226
  #
2519
- # Related: #responses, #response_handlers
3227
+ # Related: #extract_responses, #responses, #response_handlers
2520
3228
  def clear_responses(type = nil)
2521
3229
  synchronize {
2522
3230
  if type
@@ -2530,6 +3238,30 @@ module Net
2530
3238
  .freeze
2531
3239
  end
2532
3240
 
3241
+ # :call-seq:
3242
+ # extract_responses(type) {|response| ... } -> array
3243
+ #
3244
+ # Yields all of the unhandled #responses for a single response +type+.
3245
+ # Removes and returns the responses for which the block returns a true
3246
+ # value.
3247
+ #
3248
+ # Extracting responses is synchronized with other threads. The lock is
3249
+ # released before returning.
3250
+ #
3251
+ # Related: #responses, #clear_responses
3252
+ def extract_responses(type)
3253
+ type = String.try_convert(type) or
3254
+ raise ArgumentError, "type must be a string"
3255
+ raise ArgumentError, "must provide a block" unless block_given?
3256
+ extracted = []
3257
+ responses(type) do |all|
3258
+ all.reject! do |response|
3259
+ extracted << response if yield response
3260
+ end
3261
+ end
3262
+ extracted
3263
+ end
3264
+
2533
3265
  # Returns all response handlers, including those that are added internally
2534
3266
  # by commands. Each response handler will be called with every new
2535
3267
  # UntaggedResponse, TaggedResponse, and ContinuationRequest.
@@ -2559,6 +3291,10 @@ module Net
2559
3291
  # end
2560
3292
  # }
2561
3293
  #
3294
+ # Response handlers can also be added when the client is created before the
3295
+ # receiver thread is started, by the +response_handlers+ argument to ::new.
3296
+ # This ensures every server response is handled, including the #greeting.
3297
+ #
2562
3298
  # Related: #remove_response_handler, #response_handlers
2563
3299
  def add_response_handler(handler = nil, &block)
2564
3300
  raise ArgumentError, "two Procs are passed" if handler && block
@@ -2582,13 +3318,13 @@ module Net
2582
3318
  PORT = 143 # :nodoc:
2583
3319
  SSL_PORT = 993 # :nodoc:
2584
3320
 
2585
- @@debug = false
2586
-
2587
3321
  def start_imap_connection
2588
3322
  @greeting = get_server_greeting
2589
3323
  @capabilities = capabilities_from_resp_code @greeting
3324
+ @response_handlers.each do |handler| handler.call(@greeting) end
2590
3325
  @receiver_thread = start_receiver_thread
2591
3326
  rescue Exception
3327
+ state_logout!
2592
3328
  @sock.close
2593
3329
  raise
2594
3330
  end
@@ -2597,7 +3333,10 @@ module Net
2597
3333
  greeting = get_response
2598
3334
  raise Error, "No server greeting - connection closed" unless greeting
2599
3335
  record_untagged_response_code greeting
2600
- raise ByeResponseError, greeting if greeting.name == "BYE"
3336
+ case greeting.name
3337
+ when "PREAUTH" then state_authenticated!
3338
+ when "BYE" then state_logout!; raise ByeResponseError, greeting
3339
+ end
2601
3340
  greeting
2602
3341
  end
2603
3342
 
@@ -2607,16 +3346,18 @@ module Net
2607
3346
  rescue Exception => ex
2608
3347
  @receiver_thread_exception = ex
2609
3348
  # don't exit the thread with an exception
3349
+ ensure
3350
+ state_logout!
2610
3351
  end
2611
3352
  end
2612
3353
 
2613
3354
  def tcp_socket(host, port)
2614
- s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
3355
+ s = Socket.tcp(host, port, :connect_timeout => open_timeout)
2615
3356
  s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
2616
3357
  s
2617
3358
  rescue Errno::ETIMEDOUT
2618
3359
  raise Net::OpenTimeout, "Timeout to open TCP connection to " +
2619
- "#{host}:#{port} (exceeds #{@open_timeout} seconds)"
3360
+ "#{host}:#{port} (exceeds #{open_timeout} seconds)"
2620
3361
  end
2621
3362
 
2622
3363
  def receive_responses
@@ -2629,6 +3370,7 @@ module Net
2629
3370
  resp = get_response
2630
3371
  rescue Exception => e
2631
3372
  synchronize do
3373
+ state_logout!
2632
3374
  @sock.close
2633
3375
  @exception = e
2634
3376
  end
@@ -2648,6 +3390,7 @@ module Net
2648
3390
  @tagged_response_arrival.broadcast
2649
3391
  case resp.tag
2650
3392
  when @logout_command_tag
3393
+ state_logout!
2651
3394
  return
2652
3395
  when @continued_command_tag
2653
3396
  @continuation_request_exception =
@@ -2657,6 +3400,7 @@ module Net
2657
3400
  when UntaggedResponse
2658
3401
  record_untagged_response(resp)
2659
3402
  if resp.name == "BYE" && @logout_command_tag.nil?
3403
+ state_logout!
2660
3404
  @sock.close
2661
3405
  @exception = ByeResponseError.new(resp)
2662
3406
  connection_closed = true
@@ -2664,6 +3408,7 @@ module Net
2664
3408
  when ContinuationRequest
2665
3409
  @continuation_request_arrival.signal
2666
3410
  end
3411
+ state_unselected! if resp in {data: {code: {name: "CLOSED"}}}
2667
3412
  @response_handlers.each do |handler|
2668
3413
  handler.call(resp)
2669
3414
  end
@@ -2715,23 +3460,10 @@ module Net
2715
3460
  end
2716
3461
 
2717
3462
  def get_response
2718
- buff = String.new
2719
- while true
2720
- s = @sock.gets(CRLF)
2721
- break unless s
2722
- buff.concat(s)
2723
- if /\{(\d+)\}\r\n/n =~ s
2724
- s = @sock.read($1.to_i)
2725
- buff.concat(s)
2726
- else
2727
- break
2728
- end
2729
- end
3463
+ buff = @reader.read_response_buffer
2730
3464
  return nil if buff.length == 0
2731
- if @@debug
2732
- $stderr.print(buff.gsub(/^/n, "S: "))
2733
- end
2734
- return @parser.parse(buff)
3465
+ $stderr.print(buff.gsub(/^/n, "S: ")) if config.debug?
3466
+ @parser.parse(buff)
2735
3467
  end
2736
3468
 
2737
3469
  #############################
@@ -2807,7 +3539,7 @@ module Net
2807
3539
 
2808
3540
  def put_string(str)
2809
3541
  @sock.print(str)
2810
- if @@debug
3542
+ if config.debug?
2811
3543
  if @debug_output_bol
2812
3544
  $stderr.print("C: ")
2813
3545
  end
@@ -2820,23 +3552,115 @@ module Net
2820
3552
  end
2821
3553
  end
2822
3554
 
2823
- def search_internal(cmd, keys, charset)
2824
- if keys.instance_of?(String)
2825
- keys = [RawData.new(keys)]
3555
+ def enforce_logindisabled?
3556
+ if config.enforce_logindisabled == :when_capabilities_cached
3557
+ capabilities_cached?
2826
3558
  else
2827
- normalize_searching_criteria(keys)
3559
+ config.enforce_logindisabled
3560
+ end
3561
+ end
3562
+
3563
+ def expunge_internal(...)
3564
+ synchronize do
3565
+ send_command(...)
3566
+ expunged_array = clear_responses("EXPUNGE")
3567
+ vanished_array = extract_responses("VANISHED") { !_1.earlier? }
3568
+ if vanished_array.empty?
3569
+ expunged_array
3570
+ elsif vanished_array.length == 1
3571
+ vanished_array.first
3572
+ else
3573
+ merged_uids = SequenceSet[*vanished_array.map(&:uids)]
3574
+ VanishedData[uids: merged_uids, earlier: false]
3575
+ end
3576
+ end
3577
+ end
3578
+
3579
+ RETURN_WHOLE = /\ARETURN\z/i
3580
+ RETURN_START = /\ARETURN\b/i
3581
+ private_constant :RETURN_WHOLE, :RETURN_START
3582
+
3583
+ def search_args(keys, charset_arg = nil, return: nil, charset: nil)
3584
+ {return:} => {return: return_kw}
3585
+ case [return_kw, keys]
3586
+ in [nil, Array[RETURN_WHOLE, return_opts, *keys]]
3587
+ return_opts = convert_return_opts(return_opts)
3588
+ esearch = true
3589
+ in [nil => return_opts, RETURN_START]
3590
+ esearch = true
3591
+ in [nil => return_opts, keys]
3592
+ esearch = false
3593
+ in [_, Array[RETURN_WHOLE, _, *] | RETURN_START]
3594
+ raise ArgumentError, "conflicting return options"
3595
+ in [_, Array[RETURN_WHOLE, _, *]] # workaround for https://bugs.ruby-lang.org/issues/20956
3596
+ raise ArgumentError, "conflicting return options"
3597
+ in [_, RETURN_START] # workaround for https://bugs.ruby-lang.org/issues/20956
3598
+ raise ArgumentError, "conflicting return options"
3599
+ in [return_opts, keys]
3600
+ return_opts = convert_return_opts(return_opts)
3601
+ esearch = true
3602
+ end
3603
+ if charset && charset_arg
3604
+ raise ArgumentError, "multiple charset arguments"
2828
3605
  end
3606
+ charset ||= charset_arg
3607
+ # NOTE: not handling combined RETURN and CHARSET for raw strings
3608
+ if charset && keys in /\ACHARSET\b/i | Array[/\ACHARSET\z/i, *]
3609
+ raise ArgumentError, "multiple charset arguments"
3610
+ end
3611
+ args = normalize_searching_criteria(keys)
3612
+ args.prepend("CHARSET", charset) if charset
3613
+ args.prepend("RETURN", return_opts) if return_opts
3614
+ return args, esearch
3615
+ end
3616
+
3617
+ def convert_return_opts(unconverted)
3618
+ return_opts = Array.try_convert(unconverted) or
3619
+ raise TypeError, "expected return options to be Array, got %s" % [
3620
+ unconverted.class
3621
+ ]
3622
+ return_opts.map {|opt|
3623
+ case opt
3624
+ when Symbol then opt.to_s
3625
+ when PartialRange::Negative then PartialRange[opt]
3626
+ when Range then SequenceSet[opt]
3627
+ else opt
3628
+ end
3629
+ }
3630
+ end
3631
+
3632
+ def search_internal(cmd, ...)
3633
+ args, esearch = search_args(...)
2829
3634
  synchronize do
2830
- if charset
2831
- send_command(cmd, "CHARSET", charset, *keys)
3635
+ tagged = send_command(cmd, *args)
3636
+ tag = tagged.tag
3637
+ # Only the last ESEARCH or SEARCH is used. Excess results are ignored.
3638
+ esearch_result = extract_responses("ESEARCH") {|response|
3639
+ response in ESearchResult(tag: ^tag)
3640
+ }.last
3641
+ search_result = clear_responses("SEARCH").last
3642
+ if esearch_result
3643
+ # silently ignore SEARCH results, if any
3644
+ esearch_result
3645
+ elsif search_result
3646
+ # warn EXPECTED_ESEARCH_RESULT if esearch
3647
+ search_result
3648
+ elsif esearch
3649
+ # warn NO_SEARCH_RESPONSE
3650
+ ESearchResult[tag:, uid: cmd.start_with?("UID ")]
2832
3651
  else
2833
- send_command(cmd, *keys)
3652
+ # warn NO_SEARCH_RESPONSE
3653
+ SearchResult[]
2834
3654
  end
2835
- clear_responses("SEARCH").last || []
2836
3655
  end
2837
3656
  end
2838
3657
 
2839
- def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil)
3658
+ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3659
+ set = SequenceSet[set]
3660
+ if partial
3661
+ mod ||= []
3662
+ mod << "PARTIAL" << PartialRange[partial]
3663
+ end
2840
3664
  if changedsince
2841
3665
  mod ||= []
2842
3666
  mod << "CHANGEDSINCE" << Integer(changedsince)
@@ -2850,39 +3674,36 @@ module Net
2850
3674
  }
2851
3675
  end
2852
3676
 
2853
- synchronize do
2854
- clear_responses("FETCH")
2855
- if mod
2856
- send_command(cmd, MessageSet.new(set), attr, mod)
2857
- else
2858
- send_command(cmd, MessageSet.new(set), attr)
2859
- end
2860
- clear_responses("FETCH")
2861
- end
3677
+ args = [cmd, set, attr]
3678
+ args << mod if mod
3679
+ send_command_returning_fetch_results(*args)
2862
3680
  end
2863
3681
 
2864
3682
  def store_internal(cmd, set, attr, flags, unchangedsince: nil)
2865
3683
  attr = RawData.new(attr) if attr.instance_of?(String)
2866
- args = [MessageSet.new(set)]
3684
+ args = [SequenceSet.new(set)]
2867
3685
  args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince
2868
3686
  args << attr << flags
3687
+ send_command_returning_fetch_results(cmd, *args)
3688
+ end
3689
+
3690
+ def send_command_returning_fetch_results(...)
2869
3691
  synchronize do
2870
3692
  clear_responses("FETCH")
2871
- send_command(cmd, *args)
2872
- clear_responses("FETCH")
3693
+ clear_responses("UIDFETCH")
3694
+ send_command(...)
3695
+ fetches = clear_responses("FETCH")
3696
+ uidfetches = clear_responses("UIDFETCH")
3697
+ uidfetches.any? ? uidfetches : fetches
2873
3698
  end
2874
3699
  end
2875
3700
 
2876
3701
  def copy_internal(cmd, set, mailbox)
2877
- send_command(cmd, MessageSet.new(set), mailbox)
3702
+ send_command(cmd, SequenceSet.new(set), mailbox)
2878
3703
  end
2879
3704
 
2880
3705
  def sort_internal(cmd, sort_keys, search_keys, charset)
2881
- if search_keys.instance_of?(String)
2882
- search_keys = [RawData.new(search_keys)]
2883
- else
2884
- normalize_searching_criteria(search_keys)
2885
- end
3706
+ search_keys = normalize_searching_criteria(search_keys)
2886
3707
  synchronize do
2887
3708
  send_command(cmd, sort_keys, charset, *search_keys)
2888
3709
  clear_responses("SORT").last || []
@@ -2890,25 +3711,39 @@ module Net
2890
3711
  end
2891
3712
 
2892
3713
  def thread_internal(cmd, algorithm, search_keys, charset)
2893
- if search_keys.instance_of?(String)
2894
- search_keys = [RawData.new(search_keys)]
2895
- else
2896
- normalize_searching_criteria(search_keys)
2897
- end
3714
+ search_keys = normalize_searching_criteria(search_keys)
2898
3715
  synchronize do
2899
3716
  send_command(cmd, algorithm, charset, *search_keys)
2900
3717
  clear_responses("THREAD").last || []
2901
3718
  end
2902
3719
  end
2903
3720
 
2904
- def normalize_searching_criteria(keys)
2905
- keys.collect! do |i|
2906
- case i
2907
- when -1, Range, Array
2908
- MessageSet.new(i)
3721
+ def normalize_searching_criteria(criteria)
3722
+ return [RawData.new(criteria)] if criteria.is_a?(String)
3723
+ criteria.map {|i|
3724
+ if coerce_search_arg_to_seqset?(i)
3725
+ SequenceSet[i]
2909
3726
  else
2910
3727
  i
2911
3728
  end
3729
+ }
3730
+ end
3731
+
3732
+ def coerce_search_arg_to_seqset?(obj)
3733
+ case obj
3734
+ when Set, -1, :* then true
3735
+ when Range then true
3736
+ when Array then obj.all? { coerce_search_array_arg_to_seqset? _1 }
3737
+ else obj.respond_to?(:to_sequence_set)
3738
+ end
3739
+ end
3740
+
3741
+ def coerce_search_array_arg_to_seqset?(obj)
3742
+ case obj
3743
+ when Integer then obj.positive? || obj == -1
3744
+ when String then ResponseParser::Patterns::SEQUENCE_SET_STR.match?(obj.b)
3745
+ else
3746
+ coerce_search_arg_to_seqset?(obj)
2912
3747
  end
2913
3748
  end
2914
3749
 
@@ -2932,15 +3767,39 @@ module Net
2932
3767
  raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
2933
3768
  raise "cannot start TLS without SSLContext" unless ssl_ctx
2934
3769
  @sock = SSLSocket.new(@sock, ssl_ctx)
3770
+ @reader = ResponseReader.new(self, @sock)
2935
3771
  @sock.sync_close = true
2936
3772
  @sock.hostname = @host if @sock.respond_to? :hostname=
2937
- ssl_socket_connect(@sock, @open_timeout)
3773
+ ssl_socket_connect(@sock, open_timeout)
2938
3774
  if ssl_ctx.verify_mode != VERIFY_NONE
2939
3775
  @sock.post_connection_check(@host)
2940
3776
  @tls_verified = true
2941
3777
  end
2942
3778
  end
2943
3779
 
3780
+ def state_authenticated!(resp = nil)
3781
+ synchronize do
3782
+ @capabilities = capabilities_from_resp_code resp if resp
3783
+ @connection_state = ConnectionState::Authenticated.new
3784
+ end
3785
+ end
3786
+
3787
+ def state_selected!
3788
+ synchronize do
3789
+ @connection_state = ConnectionState::Selected.new
3790
+ end
3791
+ end
3792
+
3793
+ def state_unselected!
3794
+ state_authenticated! if connection_state.to_sym == :selected
3795
+ end
3796
+
3797
+ def state_logout!
3798
+ synchronize do
3799
+ @connection_state = ConnectionState::Logout.new
3800
+ end
3801
+ end
3802
+
2944
3803
  def sasl_adapter
2945
3804
  SASLAdapter.new(self, &method(:send_command_with_continuations))
2946
3805
  end
@@ -2959,8 +3818,10 @@ module Net
2959
3818
  end
2960
3819
 
2961
3820
  require_relative "imap/errors"
3821
+ require_relative "imap/config"
2962
3822
  require_relative "imap/command_data"
2963
3823
  require_relative "imap/data_encoding"
3824
+ require_relative "imap/data_lite"
2964
3825
  require_relative "imap/flags"
2965
3826
  require_relative "imap/response_data"
2966
3827
  require_relative "imap/response_parser"