net-imap 0.1.1 → 0.2.3

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.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

data/lib/net/imap.rb CHANGED
@@ -13,11 +13,8 @@
13
13
  # See Net::IMAP for documentation.
14
14
  #
15
15
 
16
-
17
16
  require "socket"
18
17
  require "monitor"
19
- require "digest/md5"
20
- require "strscan"
21
18
  require 'net/protocol'
22
19
  begin
23
20
  require "openssl"
@@ -28,12 +25,13 @@ module Net
28
25
 
29
26
  #
30
27
  # Net::IMAP implements Internet Message Access Protocol (IMAP) client
31
- # functionality. The protocol is described in [IMAP].
28
+ # functionality. The protocol is described in
29
+ # [IMAP[https://tools.ietf.org/html/rfc3501]].
32
30
  #
33
31
  # == IMAP Overview
34
32
  #
35
- # An IMAP client connects to a server, and then authenticates
36
- # itself using either #authenticate() or #login(). Having
33
+ # An \IMAP client connects to a server, and then authenticates
34
+ # itself using either #authenticate or #login. Having
37
35
  # authenticated itself, there is a range of commands
38
36
  # available to it. Most work with mailboxes, which may be
39
37
  # arranged in an hierarchical namespace, and each of which
@@ -43,8 +41,8 @@ module Net
43
41
  # within a hierarchy of directories.
44
42
  #
45
43
  # To work on the messages within a mailbox, the client must
46
- # first select that mailbox, using either #select() or (for
47
- # read-only access) #examine(). Once the client has successfully
44
+ # first select that mailbox, using either #select or (for
45
+ # read-only access) #examine. Once the client has successfully
48
46
  # selected a mailbox, they enter _selected_ state, and that
49
47
  # mailbox becomes the _current_ mailbox, on which mail-item
50
48
  # related commands implicitly operate.
@@ -65,7 +63,7 @@ module Net
65
63
  # be assigned in ascending (but not necessarily sequential)
66
64
  # order within a mailbox; this means that if a non-IMAP client
67
65
  # rearranges the order of mailitems within a mailbox, the
68
- # UIDs have to be reassigned. An IMAP client thus cannot
66
+ # UIDs have to be reassigned. An \IMAP client thus cannot
69
67
  # rearrange message orders.
70
68
  #
71
69
  # == Examples of Usage
@@ -155,40 +153,61 @@ module Net
155
153
  #
156
154
  # == References
157
155
  #
158
- # [[IMAP]]
159
- # M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
160
- # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501)
156
+ # [[IMAP[https://tools.ietf.org/html/rfc3501]]]
157
+ # Crispin, M. "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
158
+ # RFC-3501[https://tools.ietf.org/html/rfc3501], March 2003. (Note:
159
+ # obsoletes RFC-2060[https://tools.ietf.org/html/rfc2060], December 1996.)
160
+ #
161
+ # [[LANGUAGE-TAGS[https://tools.ietf.org/html/rfc1766]]]
162
+ # Phillips, A. and Davis, M. "Tags for Identifying Languages",
163
+ # RFC-5646[https://tools.ietf.org/html/rfc5646], September 2009.
164
+ # (Note: obsoletes
165
+ # RFC-3066[https://tools.ietf.org/html/rfc3066], January 2001,
166
+ # RFC-4646[https://tools.ietf.org/html/rfc4646], September 2006, and
167
+ # RFC-1766[https://tools.ietf.org/html/rfc1766], March 1995.)
161
168
  #
162
- # [[LANGUAGE-TAGS]]
163
- # Alvestrand, H., "Tags for the Identification of
164
- # Languages", RFC 1766, March 1995.
169
+ # [[MD5[https://tools.ietf.org/html/rfc1864]]]
170
+ # Myers, J. and M. Rose, "The Content-MD5 Header Field",
171
+ # RFC-1864[https://tools.ietf.org/html/rfc1864], October 1995.
165
172
  #
166
- # [[MD5]]
167
- # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC
168
- # 1864, October 1995.
173
+ # [[MIME-IMB[https://tools.ietf.org/html/rfc2045]]]
174
+ # Freed, N. and N. Borenstein, "MIME (Multipurpose Internet
175
+ # Mail Extensions) Part One: Format of Internet Message Bodies",
176
+ # RFC-2045[https://tools.ietf.org/html/rfc2045], November 1996.
169
177
  #
170
- # [[MIME-IMB]]
171
- # Freed, N., and N. Borenstein, "MIME (Multipurpose Internet
172
- # Mail Extensions) Part One: Format of Internet Message Bodies", RFC
173
- # 2045, November 1996.
178
+ # [[RFC-5322[https://tools.ietf.org/html/rfc5322]]]
179
+ # Resnick, P., "Internet Message Format",
180
+ # RFC-5322[https://tools.ietf.org/html/rfc5322], October 2008.
181
+ # (Note: obsoletes
182
+ # RFC-2822[https://tools.ietf.org/html/rfc2822], April 2001, and
183
+ # RFC-822[https://tools.ietf.org/html/rfc822], August 1982.)
174
184
  #
175
- # [[RFC-822]]
176
- # Crocker, D., "Standard for the Format of ARPA Internet Text
177
- # Messages", STD 11, RFC 822, University of Delaware, August 1982.
185
+ # [[EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]]
186
+ # Myers, J., "IMAP4 QUOTA extension",
187
+ # RFC-2087[https://tools.ietf.org/html/rfc2087], January 1997.
178
188
  #
179
- # [[RFC-2087]]
180
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997.
189
+ # [[EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]]
190
+ # Gahrns, M. and Newman, C., "IMAP4 Namespace",
191
+ # RFC-2342[https://tools.ietf.org/html/rfc2342], May 1998.
181
192
  #
182
- # [[RFC-2086]]
183
- # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997.
193
+ # [[EXT-ID[https://tools.ietf.org/html/rfc2971]]]
194
+ # Showalter, T., "IMAP4 ID extension",
195
+ # RFC-2971[https://tools.ietf.org/html/rfc2971], October 2000.
184
196
  #
185
- # [[RFC-2195]]
186
- # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension
187
- # for Simple Challenge/Response", RFC 2195, September 1997.
197
+ # [[EXT-ACL[https://tools.ietf.org/html/rfc4314]]]
198
+ # Melnikov, A., "IMAP4 ACL extension",
199
+ # RFC-4314[https://tools.ietf.org/html/rfc4314], December 2005. (Note:
200
+ # obsoletes RFC-2086[https://tools.ietf.org/html/rfc2086], January 1997.)
188
201
  #
189
- # [[SORT-THREAD-EXT]]
190
- # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD
191
- # Extensions", draft-ietf-imapext-sort, May 2003.
202
+ # [[EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]]]
203
+ # Crispin, M. and Muchison, K., "INTERNET MESSAGE ACCESS PROTOCOL - SORT
204
+ # and THREAD Extensions", RFC-5256[https://tools.ietf.org/html/rfc5256],
205
+ # June 2008.
206
+ #
207
+ # [[EXT-MOVE[https://tools.ietf.org/html/rfc6851]]]
208
+ # Gulbrandsen, A. and Freed, N., "Internet Message Access Protocol (\IMAP) -
209
+ # MOVE Extension", RFC-6851[https://tools.ietf.org/html/rfc6851], January
210
+ # 2013.
192
211
  #
193
212
  # [[OSSL]]
194
213
  # http://www.openssl.org
@@ -196,12 +215,12 @@ module Net
196
215
  # [[RSSL]]
197
216
  # http://savannah.gnu.org/projects/rubypki
198
217
  #
199
- # [[UTF7]]
218
+ # [[UTF7[https://tools.ietf.org/html/rfc2152]]]
200
219
  # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
201
- # Unicode", RFC 2152, May 1997.
220
+ # Unicode", RFC-2152[https://tools.ietf.org/html/rfc2152], May 1997.
202
221
  #
203
222
  class IMAP < Protocol
204
- VERSION = "0.1.1"
223
+ VERSION = "0.2.3"
205
224
 
206
225
  include MonitorMixin
207
226
  if defined?(OpenSSL::SSL)
@@ -229,46 +248,12 @@ module Net
229
248
  # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
230
249
  attr_reader :open_timeout
231
250
 
251
+ # Seconds to wait until an IDLE response is received.
252
+ attr_reader :idle_response_timeout
253
+
232
254
  # The thread to receive exceptions.
233
255
  attr_accessor :client_thread
234
256
 
235
- # Flag indicating a message has been seen.
236
- SEEN = :Seen
237
-
238
- # Flag indicating a message has been answered.
239
- ANSWERED = :Answered
240
-
241
- # Flag indicating a message has been flagged for special or urgent
242
- # attention.
243
- FLAGGED = :Flagged
244
-
245
- # Flag indicating a message has been marked for deletion. This
246
- # will occur when the mailbox is closed or expunged.
247
- DELETED = :Deleted
248
-
249
- # Flag indicating a message is only a draft or work-in-progress version.
250
- DRAFT = :Draft
251
-
252
- # Flag indicating that the message is "recent," meaning that this
253
- # session is the first session in which the client has been notified
254
- # of this message.
255
- RECENT = :Recent
256
-
257
- # Flag indicating that a mailbox context name cannot contain
258
- # children.
259
- NOINFERIORS = :Noinferiors
260
-
261
- # Flag indicating that a mailbox is not selected.
262
- NOSELECT = :Noselect
263
-
264
- # Flag indicating that a mailbox has been marked "interesting" by
265
- # the server; this commonly indicates that the mailbox contains
266
- # new messages.
267
- MARKED = :Marked
268
-
269
- # Flag indicating that the mailbox does not contains new messages.
270
- UNMARKED = :Unmarked
271
-
272
257
  # Returns the debug mode.
273
258
  def self.debug
274
259
  return @@debug
@@ -279,31 +264,6 @@ module Net
279
264
  return @@debug = val
280
265
  end
281
266
 
282
- # Returns the max number of flags interned to symbols.
283
- def self.max_flag_count
284
- return @@max_flag_count
285
- end
286
-
287
- # Sets the max number of flags interned to symbols.
288
- def self.max_flag_count=(count)
289
- @@max_flag_count = count
290
- end
291
-
292
- # Adds an authenticator for Net::IMAP#authenticate. +auth_type+
293
- # is the type of authentication this authenticator supports
294
- # (for instance, "LOGIN"). The +authenticator+ is an object
295
- # which defines a process() method to handle authentication with
296
- # the server. See Net::IMAP::LoginAuthenticator,
297
- # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
298
- # for examples.
299
- #
300
- #
301
- # If +auth_type+ refers to an existing authenticator, it will be
302
- # replaced by the new one.
303
- def self.add_authenticator(auth_type, authenticator)
304
- @@authenticators[auth_type] = authenticator
305
- end
306
-
307
267
  # The default port for IMAP connections, port 143
308
268
  def self.default_port
309
269
  return PORT
@@ -365,6 +325,30 @@ module Net
365
325
  end
366
326
  end
367
327
 
328
+ # Sends an ID command, and returns a hash of the server's
329
+ # response, or nil if the server does not identify itself.
330
+ #
331
+ # Note that the user should first check if the server supports the ID
332
+ # capability. For example:
333
+ #
334
+ # capabilities = imap.capability
335
+ # if capabilities.include?("ID")
336
+ # id = imap.id(
337
+ # name: "my IMAP client (ruby)",
338
+ # version: MyIMAP::VERSION,
339
+ # "support-url": "mailto:bugs@example.com",
340
+ # os: RbConfig::CONFIG["host_os"],
341
+ # )
342
+ # end
343
+ #
344
+ # See [EXT-ID[https://tools.ietf.org/html/rfc2971]] for field definitions.
345
+ def id(client_id=nil)
346
+ synchronize do
347
+ send_command("ID", ClientID.new(client_id))
348
+ @responses.delete("ID")&.last
349
+ end
350
+ end
351
+
368
352
  # Sends a NOOP command to the server. It does nothing.
369
353
  def noop
370
354
  send_command("NOOP")
@@ -404,11 +388,11 @@ module Net
404
388
  #
405
389
  # For both of these mechanisms, there should be two +args+: username
406
390
  # and (cleartext) password. A server may not support one or the other
407
- # of these mechanisms; check #capability() for a capability of
391
+ # of these mechanisms; check #capability for a capability of
408
392
  # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
409
393
  #
410
394
  # Authentication is done using the appropriate authenticator object:
411
- # see @@authenticators for more information on plugging in your own
395
+ # see +add_authenticator+ for more information on plugging in your own
412
396
  # authenticator.
413
397
  #
414
398
  # For example:
@@ -417,12 +401,7 @@ module Net
417
401
  #
418
402
  # A Net::IMAP::NoResponseError is raised if authentication fails.
419
403
  def authenticate(auth_type, *args)
420
- auth_type = auth_type.upcase
421
- unless @@authenticators.has_key?(auth_type)
422
- raise ArgumentError,
423
- format('unknown auth type - "%s"', auth_type)
424
- end
425
- authenticator = @@authenticators[auth_type].new(*args)
404
+ authenticator = self.class.authenticator(auth_type, *args)
426
405
  send_command("AUTHENTICATE", auth_type) do |resp|
427
406
  if resp.instance_of?(ContinuationRequest)
428
407
  data = authenticator.process(resp.data.text.unpack("m")[0])
@@ -435,8 +414,8 @@ module Net
435
414
 
436
415
  # Sends a LOGIN command to identify the client and carries
437
416
  # the plaintext +password+ authenticating this +user+. Note
438
- # that, unlike calling #authenticate() with an +auth_type+
439
- # of "LOGIN", #login() does *not* use the login authenticator.
417
+ # that, unlike calling #authenticate with an +auth_type+
418
+ # of "LOGIN", #login does *not* use the login authenticator.
440
419
  #
441
420
  # A Net::IMAP::NoResponseError is raised if authentication fails.
442
421
  def login(user, password)
@@ -447,10 +426,10 @@ module Net
447
426
  # in the +mailbox+ can be accessed.
448
427
  #
449
428
  # After you have selected a mailbox, you may retrieve the
450
- # number of items in that mailbox from @responses["EXISTS"][-1],
451
- # and the number of recent messages from @responses["RECENT"][-1].
429
+ # number of items in that mailbox from +@responses["EXISTS"][-1]+,
430
+ # and the number of recent messages from +@responses["RECENT"][-1]+.
452
431
  # Note that these values can change if new messages arrive
453
- # during a session; see #add_response_handler() for a way of
432
+ # during a session; see #add_response_handler for a way of
454
433
  # detecting this event.
455
434
  #
456
435
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
@@ -463,7 +442,7 @@ module Net
463
442
  end
464
443
 
465
444
  # Sends a EXAMINE command to select a +mailbox+ so that messages
466
- # in the +mailbox+ can be accessed. Behaves the same as #select(),
445
+ # in the +mailbox+ can be accessed. Behaves the same as #select,
467
446
  # except that the selected +mailbox+ is identified as read-only.
468
447
  #
469
448
  # A Net::IMAP::NoResponseError is raised if the mailbox does not
@@ -505,7 +484,7 @@ module Net
505
484
 
506
485
  # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
507
486
  # the server's set of "active" or "subscribed" mailboxes as returned
508
- # by #lsub().
487
+ # by #lsub.
509
488
  #
510
489
  # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
511
490
  # subscribed to; for instance, because it does not exist.
@@ -552,6 +531,63 @@ module Net
552
531
  end
553
532
  end
554
533
 
534
+ # Sends a NAMESPACE command and returns the namespaces that are available.
535
+ # The NAMESPACE command allows a client to discover the prefixes of
536
+ # namespaces used by a server for personal mailboxes, other users'
537
+ # mailboxes, and shared mailboxes.
538
+ #
539
+ # The NAMESPACE extension predates [IMAP4rev1[https://tools.ietf.org/html/rfc2501]],
540
+ # so most IMAP servers support it. Many popular IMAP servers are configured
541
+ # with the default personal namespaces as `("" "/")`: no prefix and "/"
542
+ # hierarchy delimiter. In that common case, the naive client may not have
543
+ # any trouble naming mailboxes.
544
+ #
545
+ # But many servers are configured with the default personal namespace as
546
+ # e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "."
547
+ # as the hierarchy delimiter. If the client does not check for this, but
548
+ # naively assumes it can use the same folder names for all servers, then
549
+ # folder creation (and listing, moving, etc) can lead to errors.
550
+ #
551
+ # From RFC2342:
552
+ #
553
+ # Although typically a server will support only a single Personal
554
+ # Namespace, and a single Other User's Namespace, circumstances exist
555
+ # where there MAY be multiples of these, and a client MUST be prepared
556
+ # for them. If a client is configured such that it is required to create
557
+ # a certain mailbox, there can be circumstances where it is unclear which
558
+ # Personal Namespaces it should create the mailbox in. In these
559
+ # situations a client SHOULD let the user select which namespaces to
560
+ # create the mailbox in.
561
+ #
562
+ # The user of this method should first check if the server supports the
563
+ # NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+
564
+ # object which has +personal+, +other+, and +shared+ fields, each an array
565
+ # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the
566
+ # server responds with nil.
567
+ #
568
+ # For example:
569
+ #
570
+ # capabilities = imap.capability
571
+ # if capabilities.include?("NAMESPACE")
572
+ # namespaces = imap.namespace
573
+ # if namespace = namespaces.personal.first
574
+ # prefix = namespace.prefix # e.g. "" or "INBOX."
575
+ # delim = namespace.delim # e.g. "/" or "."
576
+ # # personal folders should use the prefix and delimiter
577
+ # imap.create(prefix + "foo")
578
+ # imap.create(prefix + "bar")
579
+ # imap.create(prefix + %w[path to my folder].join(delim))
580
+ # end
581
+ # end
582
+ #
583
+ # The NAMESPACE extension is described in [EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]
584
+ def namespace
585
+ synchronize do
586
+ send_command("NAMESPACE")
587
+ return @responses.delete("NAMESPACE")[-1]
588
+ end
589
+ end
590
+
555
591
  # Sends a XLIST command, and returns a subset of names from
556
592
  # the complete set of all names available to the client.
557
593
  # +refname+ provides a context (for instance, a base directory
@@ -588,6 +624,8 @@ module Net
588
624
  # This command is generally available to both admin and user.
589
625
  # If this mailbox exists, it returns an array containing objects of type
590
626
  # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
627
+ #
628
+ # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
591
629
  def getquotaroot(mailbox)
592
630
  synchronize do
593
631
  send_command("GETQUOTAROOT", mailbox)
@@ -602,6 +640,8 @@ module Net
602
640
  # If this mailbox exists, then an array containing a
603
641
  # Net::IMAP::MailboxQuota object is returned. This
604
642
  # command is generally only available to server admin.
643
+ #
644
+ # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
605
645
  def getquota(mailbox)
606
646
  synchronize do
607
647
  send_command("GETQUOTA", mailbox)
@@ -612,8 +652,9 @@ module Net
612
652
  # Sends a SETQUOTA command along with the specified +mailbox+ and
613
653
  # +quota+. If +quota+ is nil, then +quota+ will be unset for that
614
654
  # mailbox. Typically one needs to be logged in as a server admin
615
- # for this to work. The IMAP quota commands are described in
616
- # [RFC-2087].
655
+ # for this to work.
656
+ #
657
+ # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
617
658
  def setquota(mailbox, quota)
618
659
  if quota.nil?
619
660
  data = '()'
@@ -626,7 +667,8 @@ module Net
626
667
  # Sends the SETACL command along with +mailbox+, +user+ and the
627
668
  # +rights+ that user is to have on that mailbox. If +rights+ is nil,
628
669
  # then that user will be stripped of any rights to that mailbox.
629
- # The IMAP ACL commands are described in [RFC-2086].
670
+ #
671
+ # The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
630
672
  def setacl(mailbox, user, rights)
631
673
  if rights.nil?
632
674
  send_command("SETACL", mailbox, user, "")
@@ -638,6 +680,8 @@ module Net
638
680
  # Send the GETACL command along with a specified +mailbox+.
639
681
  # If this mailbox exists, an array containing objects of
640
682
  # Net::IMAP::MailboxACLItem will be returned.
683
+ #
684
+ # The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
641
685
  def getacl(mailbox)
642
686
  synchronize do
643
687
  send_command("GETACL", mailbox)
@@ -648,7 +692,8 @@ module Net
648
692
  # Sends a LSUB command, and returns a subset of names from the set
649
693
  # of names that the user has declared as being "active" or
650
694
  # "subscribed." +refname+ and +mailbox+ are interpreted as
651
- # for #list().
695
+ # for #list.
696
+ #
652
697
  # The return value is an array of +Net::IMAP::MailboxList+.
653
698
  def lsub(refname, mailbox)
654
699
  synchronize do
@@ -776,7 +821,7 @@ module Net
776
821
  return search_internal("SEARCH", keys, charset)
777
822
  end
778
823
 
779
- # Similar to #search(), but returns unique identifiers.
824
+ # Similar to #search, but returns unique identifiers.
780
825
  def uid_search(keys, charset = nil)
781
826
  return search_internal("UID SEARCH", keys, charset)
782
827
  end
@@ -820,7 +865,7 @@ module Net
820
865
  return fetch_internal("FETCH", set, attr, mod)
821
866
  end
822
867
 
823
- # Similar to #fetch(), but +set+ contains unique identifiers.
868
+ # Similar to #fetch, but +set+ contains unique identifiers.
824
869
  def uid_fetch(set, attr, mod = nil)
825
870
  return fetch_internal("UID FETCH", set, attr, mod)
826
871
  end
@@ -843,7 +888,7 @@ module Net
843
888
  return store_internal("STORE", set, attr, flags)
844
889
  end
845
890
 
846
- # Similar to #store(), but +set+ contains unique identifiers.
891
+ # Similar to #store, but +set+ contains unique identifiers.
847
892
  def uid_store(set, attr, flags)
848
893
  return store_internal("UID STORE", set, attr, flags)
849
894
  end
@@ -856,7 +901,7 @@ module Net
856
901
  copy_internal("COPY", set, mailbox)
857
902
  end
858
903
 
859
- # Similar to #copy(), but +set+ contains unique identifiers.
904
+ # Similar to #copy, but +set+ contains unique identifiers.
860
905
  def uid_copy(set, mailbox)
861
906
  copy_internal("UID COPY", set, mailbox)
862
907
  end
@@ -865,12 +910,15 @@ module Net
865
910
  # of the specified destination +mailbox+. The +set+ parameter is
866
911
  # a number, an array of numbers, or a Range object. The number is
867
912
  # a message sequence number.
868
- # The IMAP MOVE extension is described in [RFC-6851].
913
+ #
914
+ # The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
869
915
  def move(set, mailbox)
870
916
  copy_internal("MOVE", set, mailbox)
871
917
  end
872
918
 
873
- # Similar to #move(), but +set+ contains unique identifiers.
919
+ # Similar to #move, but +set+ contains unique identifiers.
920
+ #
921
+ # The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
874
922
  def uid_move(set, mailbox)
875
923
  copy_internal("UID MOVE", set, mailbox)
876
924
  end
@@ -883,12 +931,14 @@ module Net
883
931
  # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
884
932
  # #=> [6, 7, 8, 1]
885
933
  #
886
- # See [SORT-THREAD-EXT] for more details.
934
+ # The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
887
935
  def sort(sort_keys, search_keys, charset)
888
936
  return sort_internal("SORT", sort_keys, search_keys, charset)
889
937
  end
890
938
 
891
- # Similar to #sort(), but returns an array of unique identifiers.
939
+ # Similar to #sort, but returns an array of unique identifiers.
940
+ #
941
+ # The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
892
942
  def uid_sort(sort_keys, search_keys, charset)
893
943
  return sort_internal("UID SORT", sort_keys, search_keys, charset)
894
944
  end
@@ -915,7 +965,7 @@ module Net
915
965
  @response_handlers.delete(handler)
916
966
  end
917
967
 
918
- # Similar to #search(), but returns message sequence numbers in threaded
968
+ # Similar to #search, but returns message sequence numbers in threaded
919
969
  # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
920
970
  # are:
921
971
  #
@@ -924,16 +974,18 @@ module Net
924
974
  # REFERENCES:: split into threads by parent/child relationships determined
925
975
  # by which message is a reply to which.
926
976
  #
927
- # Unlike #search(), +charset+ is a required argument. US-ASCII
977
+ # Unlike #search, +charset+ is a required argument. US-ASCII
928
978
  # and UTF-8 are sample values.
929
979
  #
930
- # See [SORT-THREAD-EXT] for more details.
980
+ # The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
931
981
  def thread(algorithm, search_keys, charset)
932
982
  return thread_internal("THREAD", algorithm, search_keys, charset)
933
983
  end
934
984
 
935
- # Similar to #thread(), but returns unique identifiers instead of
985
+ # Similar to #thread, but returns unique identifiers instead of
936
986
  # message sequence numbers.
987
+ #
988
+ # The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
937
989
  def uid_thread(algorithm, search_keys, charset)
938
990
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
939
991
  end
@@ -941,7 +993,7 @@ module Net
941
993
  # Sends an IDLE command that waits for notifications of new or expunged
942
994
  # messages. Yields responses from the server during the IDLE.
943
995
  #
944
- # Use #idle_done() to leave IDLE.
996
+ # Use #idle_done to leave IDLE.
945
997
  #
946
998
  # If +timeout+ is given, this method returns after +timeout+ seconds passed.
947
999
  # +timeout+ can be used for keep-alive. For example, the following code
@@ -973,7 +1025,7 @@ module Net
973
1025
  unless @receiver_thread_terminating
974
1026
  remove_response_handler(response_handler)
975
1027
  put_string("DONE#{CRLF}")
976
- response = get_tagged_response(tag, "IDLE")
1028
+ response = get_tagged_response(tag, "IDLE", @idle_response_timeout)
977
1029
  end
978
1030
  end
979
1031
  end
@@ -991,46 +1043,6 @@ module Net
991
1043
  end
992
1044
  end
993
1045
 
994
- # Decode a string from modified UTF-7 format to UTF-8.
995
- #
996
- # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
997
- # slightly modified version of this to encode mailbox names
998
- # containing non-ASCII characters; see [IMAP] section 5.1.3.
999
- #
1000
- # Net::IMAP does _not_ automatically encode and decode
1001
- # mailbox names to and from UTF-7.
1002
- def self.decode_utf7(s)
1003
- return s.gsub(/&([^-]+)?-/n) {
1004
- if $1
1005
- ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
1006
- else
1007
- "&"
1008
- end
1009
- }
1010
- end
1011
-
1012
- # Encode a string from UTF-8 format to modified UTF-7.
1013
- def self.encode_utf7(s)
1014
- return s.gsub(/(&)|[^\x20-\x7e]+/) {
1015
- if $1
1016
- "&-"
1017
- else
1018
- base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
1019
- "&" + base64.delete("=").tr("/", ",") + "-"
1020
- end
1021
- }.force_encoding("ASCII-8BIT")
1022
- end
1023
-
1024
- # Formats +time+ as an IMAP-style date.
1025
- def self.format_date(time)
1026
- return time.strftime('%d-%b-%Y')
1027
- end
1028
-
1029
- # Formats +time+ as an IMAP-style date-time.
1030
- def self.format_datetime(time)
1031
- return time.strftime('%d-%b-%Y %H:%M %z')
1032
- end
1033
-
1034
1046
  private
1035
1047
 
1036
1048
  CRLF = "\r\n" # :nodoc:
@@ -1038,8 +1050,6 @@ module Net
1038
1050
  SSL_PORT = 993 # :nodoc:
1039
1051
 
1040
1052
  @@debug = false
1041
- @@authenticators = {}
1042
- @@max_flag_count = 10000
1043
1053
 
1044
1054
  # :call-seq:
1045
1055
  # Net::IMAP.new(host, options = {})
@@ -1052,13 +1062,14 @@ module Net
1052
1062
  # The available options are:
1053
1063
  #
1054
1064
  # port:: Port number (default value is 143 for imap, or 993 for imaps)
1055
- # ssl:: If options[:ssl] is true, then an attempt will be made
1065
+ # ssl:: If +options[:ssl]+ is true, then an attempt will be made
1056
1066
  # to use SSL (now TLS) to connect to the server. For this to work
1057
1067
  # OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
1058
1068
  # be installed.
1059
- # If options[:ssl] is a hash, it's passed to
1069
+ # If +options[:ssl]+ is a hash, it's passed to
1060
1070
  # OpenSSL::SSL::SSLContext#set_params as parameters.
1061
1071
  # open_timeout:: Seconds to wait until a connection is opened
1072
+ # idle_response_timeout:: Seconds to wait until an IDLE response is received
1062
1073
  #
1063
1074
  # The most common errors are:
1064
1075
  #
@@ -1088,6 +1099,7 @@ module Net
1088
1099
  @tag_prefix = "RUBY"
1089
1100
  @tagno = 0
1090
1101
  @open_timeout = options[:open_timeout] || 30
1102
+ @idle_response_timeout = options[:idle_response_timeout] || 5
1091
1103
  @parser = ResponseParser.new
1092
1104
  @sock = tcp_socket(@host, @port)
1093
1105
  begin
@@ -1211,19 +1223,30 @@ module Net
1211
1223
  end
1212
1224
  end
1213
1225
 
1214
- def get_tagged_response(tag, cmd)
1226
+ def get_tagged_response(tag, cmd, timeout = nil)
1227
+ if timeout
1228
+ deadline = Time.now + timeout
1229
+ end
1215
1230
  until @tagged_responses.key?(tag)
1216
1231
  raise @exception if @exception
1217
- @tagged_response_arrival.wait
1232
+ if timeout
1233
+ timeout = deadline - Time.now
1234
+ if timeout <= 0
1235
+ return nil
1236
+ end
1237
+ end
1238
+ @tagged_response_arrival.wait(timeout)
1218
1239
  end
1219
1240
  resp = @tagged_responses.delete(tag)
1220
1241
  case resp.name
1242
+ when /\A(?:OK)\z/ni
1243
+ return resp
1221
1244
  when /\A(?:NO)\z/ni
1222
1245
  raise NoResponseError, resp
1223
1246
  when /\A(?:BAD)\z/ni
1224
1247
  raise BadResponseError, resp
1225
1248
  else
1226
- return resp
1249
+ raise UnknownResponseError, resp
1227
1250
  end
1228
1251
  end
1229
1252
 
@@ -1302,114 +1325,6 @@ module Net
1302
1325
  end
1303
1326
  end
1304
1327
 
1305
- def validate_data(data)
1306
- case data
1307
- when nil
1308
- when String
1309
- when Integer
1310
- NumValidator.ensure_number(data)
1311
- when Array
1312
- if data[0] == 'CHANGEDSINCE'
1313
- NumValidator.ensure_mod_sequence_value(data[1])
1314
- else
1315
- data.each do |i|
1316
- validate_data(i)
1317
- end
1318
- end
1319
- when Time
1320
- when Symbol
1321
- else
1322
- data.validate
1323
- end
1324
- end
1325
-
1326
- def send_data(data, tag = nil)
1327
- case data
1328
- when nil
1329
- put_string("NIL")
1330
- when String
1331
- send_string_data(data, tag)
1332
- when Integer
1333
- send_number_data(data)
1334
- when Array
1335
- send_list_data(data, tag)
1336
- when Time
1337
- send_time_data(data)
1338
- when Symbol
1339
- send_symbol_data(data)
1340
- else
1341
- data.send_data(self, tag)
1342
- end
1343
- end
1344
-
1345
- def send_string_data(str, tag = nil)
1346
- case str
1347
- when ""
1348
- put_string('""')
1349
- when /[\x80-\xff\r\n]/n
1350
- # literal
1351
- send_literal(str, tag)
1352
- when /[(){ \x00-\x1f\x7f%*"\\]/n
1353
- # quoted string
1354
- send_quoted_string(str)
1355
- else
1356
- put_string(str)
1357
- end
1358
- end
1359
-
1360
- def send_quoted_string(str)
1361
- put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
1362
- end
1363
-
1364
- def send_literal(str, tag = nil)
1365
- synchronize do
1366
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
1367
- @continued_command_tag = tag
1368
- @continuation_request_exception = nil
1369
- begin
1370
- @continuation_request_arrival.wait
1371
- e = @continuation_request_exception || @exception
1372
- raise e if e
1373
- put_string(str)
1374
- ensure
1375
- @continued_command_tag = nil
1376
- @continuation_request_exception = nil
1377
- end
1378
- end
1379
- end
1380
-
1381
- def send_number_data(num)
1382
- put_string(num.to_s)
1383
- end
1384
-
1385
- def send_list_data(list, tag = nil)
1386
- put_string("(")
1387
- first = true
1388
- list.each do |i|
1389
- if first
1390
- first = false
1391
- else
1392
- put_string(" ")
1393
- end
1394
- send_data(i, tag)
1395
- end
1396
- put_string(")")
1397
- end
1398
-
1399
- DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
1400
-
1401
- def send_time_data(time)
1402
- t = time.dup.gmtime
1403
- s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
1404
- t.day, DATE_MONTH[t.month - 1], t.year,
1405
- t.hour, t.min, t.sec)
1406
- put_string(s)
1407
- end
1408
-
1409
- def send_symbol_data(symbol)
1410
- put_string("\\" + symbol.to_s)
1411
- end
1412
-
1413
1328
  def search_internal(cmd, keys, charset)
1414
1329
  if keys.instance_of?(String)
1415
1330
  keys = [RawData.new(keys)]
@@ -1540,2191 +1455,13 @@ module Net
1540
1455
  end
1541
1456
  end
1542
1457
 
1543
- class RawData # :nodoc:
1544
- def send_data(imap, tag)
1545
- imap.__send__(:put_string, @data)
1546
- end
1547
-
1548
- def validate
1549
- end
1550
-
1551
- private
1552
-
1553
- def initialize(data)
1554
- @data = data
1555
- end
1556
- end
1557
-
1558
- class Atom # :nodoc:
1559
- def send_data(imap, tag)
1560
- imap.__send__(:put_string, @data)
1561
- end
1562
-
1563
- def validate
1564
- end
1565
-
1566
- private
1567
-
1568
- def initialize(data)
1569
- @data = data
1570
- end
1571
- end
1572
-
1573
- class QuotedString # :nodoc:
1574
- def send_data(imap, tag)
1575
- imap.__send__(:send_quoted_string, @data)
1576
- end
1577
-
1578
- def validate
1579
- end
1580
-
1581
- private
1582
-
1583
- def initialize(data)
1584
- @data = data
1585
- end
1586
- end
1587
-
1588
- class Literal # :nodoc:
1589
- def send_data(imap, tag)
1590
- imap.__send__(:send_literal, @data, tag)
1591
- end
1592
-
1593
- def validate
1594
- end
1595
-
1596
- private
1597
-
1598
- def initialize(data)
1599
- @data = data
1600
- end
1601
- end
1602
-
1603
- class MessageSet # :nodoc:
1604
- def send_data(imap, tag)
1605
- imap.__send__(:put_string, format_internal(@data))
1606
- end
1607
-
1608
- def validate
1609
- validate_internal(@data)
1610
- end
1611
-
1612
- private
1613
-
1614
- def initialize(data)
1615
- @data = data
1616
- end
1617
-
1618
- def format_internal(data)
1619
- case data
1620
- when "*"
1621
- return data
1622
- when Integer
1623
- if data == -1
1624
- return "*"
1625
- else
1626
- return data.to_s
1627
- end
1628
- when Range
1629
- return format_internal(data.first) +
1630
- ":" + format_internal(data.last)
1631
- when Array
1632
- return data.collect {|i| format_internal(i)}.join(",")
1633
- when ThreadMember
1634
- return data.seqno.to_s +
1635
- ":" + data.children.collect {|i| format_internal(i).join(",")}
1636
- end
1637
- end
1638
-
1639
- def validate_internal(data)
1640
- case data
1641
- when "*"
1642
- when Integer
1643
- NumValidator.ensure_nz_number(data)
1644
- when Range
1645
- when Array
1646
- data.each do |i|
1647
- validate_internal(i)
1648
- end
1649
- when ThreadMember
1650
- data.children.each do |i|
1651
- validate_internal(i)
1652
- end
1653
- else
1654
- raise DataFormatError, data.inspect
1655
- end
1656
- end
1657
- end
1658
-
1659
- # Common validators of number and nz_number types
1660
- module NumValidator # :nodoc
1661
- class << self
1662
- # Check is passed argument valid 'number' in RFC 3501 terminology
1663
- def valid_number?(num)
1664
- # [RFC 3501]
1665
- # number = 1*DIGIT
1666
- # ; Unsigned 32-bit integer
1667
- # ; (0 <= n < 4,294,967,296)
1668
- num >= 0 && num < 4294967296
1669
- end
1670
-
1671
- # Check is passed argument valid 'nz_number' in RFC 3501 terminology
1672
- def valid_nz_number?(num)
1673
- # [RFC 3501]
1674
- # nz-number = digit-nz *DIGIT
1675
- # ; Non-zero unsigned 32-bit integer
1676
- # ; (0 < n < 4,294,967,296)
1677
- num != 0 && valid_number?(num)
1678
- end
1679
-
1680
- # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology
1681
- def valid_mod_sequence_value?(num)
1682
- # mod-sequence-value = 1*DIGIT
1683
- # ; Positive unsigned 64-bit integer
1684
- # ; (mod-sequence)
1685
- # ; (1 <= n < 18,446,744,073,709,551,615)
1686
- num >= 1 && num < 18446744073709551615
1687
- end
1688
-
1689
- # Ensure argument is 'number' or raise DataFormatError
1690
- def ensure_number(num)
1691
- return if valid_number?(num)
1692
-
1693
- msg = "number must be unsigned 32-bit integer: #{num}"
1694
- raise DataFormatError, msg
1695
- end
1696
-
1697
- # Ensure argument is 'nz_number' or raise DataFormatError
1698
- def ensure_nz_number(num)
1699
- return if valid_nz_number?(num)
1700
-
1701
- msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
1702
- raise DataFormatError, msg
1703
- end
1704
-
1705
- # Ensure argument is 'mod_sequence_value' or raise DataFormatError
1706
- def ensure_mod_sequence_value(num)
1707
- return if valid_mod_sequence_value?(num)
1708
-
1709
- msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
1710
- raise DataFormatError, msg
1711
- end
1712
- end
1713
- end
1714
-
1715
- # Net::IMAP::ContinuationRequest represents command continuation requests.
1716
- #
1717
- # The command continuation request response is indicated by a "+" token
1718
- # instead of a tag. This form of response indicates that the server is
1719
- # ready to accept the continuation of a command from the client. The
1720
- # remainder of this response is a line of text.
1721
- #
1722
- # continue_req ::= "+" SPACE (resp_text / base64)
1723
- #
1724
- # ==== Fields:
1725
- #
1726
- # data:: Returns the data (Net::IMAP::ResponseText).
1727
- #
1728
- # raw_data:: Returns the raw data string.
1729
- ContinuationRequest = Struct.new(:data, :raw_data)
1730
-
1731
- # Net::IMAP::UntaggedResponse represents untagged responses.
1732
- #
1733
- # Data transmitted by the server to the client and status responses
1734
- # that do not indicate command completion are prefixed with the token
1735
- # "*", and are called untagged responses.
1736
- #
1737
- # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
1738
- # mailbox_data / message_data / capability_data)
1739
- #
1740
- # ==== Fields:
1741
- #
1742
- # name:: Returns the name, such as "FLAGS", "LIST", or "FETCH".
1743
- #
1744
- # data:: Returns the data such as an array of flag symbols,
1745
- # a ((<Net::IMAP::MailboxList>)) object.
1746
- #
1747
- # raw_data:: Returns the raw data string.
1748
- UntaggedResponse = Struct.new(:name, :data, :raw_data)
1749
-
1750
- # Net::IMAP::TaggedResponse represents tagged responses.
1751
- #
1752
- # The server completion result response indicates the success or
1753
- # failure of the operation. It is tagged with the same tag as the
1754
- # client command which began the operation.
1755
- #
1756
- # response_tagged ::= tag SPACE resp_cond_state CRLF
1757
- #
1758
- # tag ::= 1*<any ATOM_CHAR except "+">
1759
- #
1760
- # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1761
- #
1762
- # ==== Fields:
1763
- #
1764
- # tag:: Returns the tag.
1765
- #
1766
- # name:: Returns the name, one of "OK", "NO", or "BAD".
1767
- #
1768
- # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
1769
- #
1770
- # raw_data:: Returns the raw data string.
1771
- #
1772
- TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
1773
-
1774
- # Net::IMAP::ResponseText represents texts of responses.
1775
- # The text may be prefixed by the response code.
1776
- #
1777
- # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
1778
- # ;; text SHOULD NOT begin with "[" or "="
1779
- #
1780
- # ==== Fields:
1781
- #
1782
- # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
1783
- #
1784
- # text:: Returns the text.
1785
- #
1786
- ResponseText = Struct.new(:code, :text)
1787
-
1788
- # Net::IMAP::ResponseCode represents response codes.
1789
- #
1790
- # resp_text_code ::= "ALERT" / "PARSE" /
1791
- # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
1792
- # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1793
- # "UIDVALIDITY" SPACE nz_number /
1794
- # "UNSEEN" SPACE nz_number /
1795
- # atom [SPACE 1*<any TEXT_CHAR except "]">]
1796
- #
1797
- # ==== Fields:
1798
- #
1799
- # name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY".
1800
- #
1801
- # data:: Returns the data, if it exists.
1802
- #
1803
- ResponseCode = Struct.new(:name, :data)
1804
-
1805
- # Net::IMAP::MailboxList represents contents of the LIST response.
1806
- #
1807
- # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
1808
- # "\Noselect" / "\Unmarked" / flag_extension) ")"
1809
- # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
1810
- #
1811
- # ==== Fields:
1812
- #
1813
- # attr:: Returns the name attributes. Each name attribute is a symbol
1814
- # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
1815
- #
1816
- # delim:: Returns the hierarchy delimiter.
1817
- #
1818
- # name:: Returns the mailbox name.
1819
- #
1820
- MailboxList = Struct.new(:attr, :delim, :name)
1821
-
1822
- # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
1823
- # This object can also be a response to GETQUOTAROOT. In the syntax
1824
- # specification below, the delimiter used with the "#" construct is a
1825
- # single space (SPACE).
1826
- #
1827
- # quota_list ::= "(" #quota_resource ")"
1828
- #
1829
- # quota_resource ::= atom SPACE number SPACE number
1830
- #
1831
- # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
1832
- #
1833
- # ==== Fields:
1834
- #
1835
- # mailbox:: The mailbox with the associated quota.
1836
- #
1837
- # usage:: Current storage usage of the mailbox.
1838
- #
1839
- # quota:: Quota limit imposed on the mailbox.
1840
- #
1841
- MailboxQuota = Struct.new(:mailbox, :usage, :quota)
1842
-
1843
- # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
1844
- # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
1845
- #
1846
- # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
1847
- #
1848
- # ==== Fields:
1849
- #
1850
- # mailbox:: The mailbox with the associated quota.
1851
- #
1852
- # quotaroots:: Zero or more quotaroots that affect the quota on the
1853
- # specified mailbox.
1854
- #
1855
- MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
1856
-
1857
- # Net::IMAP::MailboxACLItem represents the response from GETACL.
1858
- #
1859
- # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
1860
- #
1861
- # identifier ::= astring
1862
- #
1863
- # rights ::= astring
1864
- #
1865
- # ==== Fields:
1866
- #
1867
- # user:: Login name that has certain rights to the mailbox
1868
- # that was specified with the getacl command.
1869
- #
1870
- # rights:: The access rights the indicated user has to the
1871
- # mailbox.
1872
- #
1873
- MailboxACLItem = Struct.new(:user, :rights, :mailbox)
1874
-
1875
- # Net::IMAP::StatusData represents the contents of the STATUS response.
1876
- #
1877
- # ==== Fields:
1878
- #
1879
- # mailbox:: Returns the mailbox name.
1880
- #
1881
- # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
1882
- # "UIDVALIDITY", "UNSEEN". Each value is a number.
1883
- #
1884
- StatusData = Struct.new(:mailbox, :attr)
1885
-
1886
- # Net::IMAP::FetchData represents the contents of the FETCH response.
1887
- #
1888
- # ==== Fields:
1889
- #
1890
- # seqno:: Returns the message sequence number.
1891
- # (Note: not the unique identifier, even for the UID command response.)
1892
- #
1893
- # attr:: Returns a hash. Each key is a data item name, and each value is
1894
- # its value.
1895
- #
1896
- # The current data items are:
1897
- #
1898
- # [BODY]
1899
- # A form of BODYSTRUCTURE without extension data.
1900
- # [BODY[<section>]<<origin_octet>>]
1901
- # A string expressing the body contents of the specified section.
1902
- # [BODYSTRUCTURE]
1903
- # An object that describes the [MIME-IMB] body structure of a message.
1904
- # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
1905
- # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
1906
- # [ENVELOPE]
1907
- # A Net::IMAP::Envelope object that describes the envelope
1908
- # structure of a message.
1909
- # [FLAGS]
1910
- # A array of flag symbols that are set for this message. Flag symbols
1911
- # are capitalized by String#capitalize.
1912
- # [INTERNALDATE]
1913
- # A string representing the internal date of the message.
1914
- # [RFC822]
1915
- # Equivalent to BODY[].
1916
- # [RFC822.HEADER]
1917
- # Equivalent to BODY.PEEK[HEADER].
1918
- # [RFC822.SIZE]
1919
- # A number expressing the [RFC-822] size of the message.
1920
- # [RFC822.TEXT]
1921
- # Equivalent to BODY[TEXT].
1922
- # [UID]
1923
- # A number expressing the unique identifier of the message.
1924
- #
1925
- FetchData = Struct.new(:seqno, :attr)
1458
+ end
1459
+ end
1926
1460
 
1927
- # Net::IMAP::Envelope represents envelope structures of messages.
1928
- #
1929
- # ==== Fields:
1930
- #
1931
- # date:: Returns a string that represents the date.
1932
- #
1933
- # subject:: Returns a string that represents the subject.
1934
- #
1935
- # from:: Returns an array of Net::IMAP::Address that represents the from.
1936
- #
1937
- # sender:: Returns an array of Net::IMAP::Address that represents the sender.
1938
- #
1939
- # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
1940
- #
1941
- # to:: Returns an array of Net::IMAP::Address that represents the to.
1942
- #
1943
- # cc:: Returns an array of Net::IMAP::Address that represents the cc.
1944
- #
1945
- # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
1946
- #
1947
- # in_reply_to:: Returns a string that represents the in-reply-to.
1948
- #
1949
- # message_id:: Returns a string that represents the message-id.
1950
- #
1951
- Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
1952
- :to, :cc, :bcc, :in_reply_to, :message_id)
1953
-
1954
- #
1955
- # Net::IMAP::Address represents electronic mail addresses.
1956
- #
1957
- # ==== Fields:
1958
- #
1959
- # name:: Returns the phrase from [RFC-822] mailbox.
1960
- #
1961
- # route:: Returns the route from [RFC-822] route-addr.
1962
- #
1963
- # mailbox:: nil indicates end of [RFC-822] group.
1964
- # If non-nil and host is nil, returns [RFC-822] group name.
1965
- # Otherwise, returns [RFC-822] local-part.
1966
- #
1967
- # host:: nil indicates [RFC-822] group syntax.
1968
- # Otherwise, returns [RFC-822] domain name.
1969
- #
1970
- Address = Struct.new(:name, :route, :mailbox, :host)
1971
-
1972
- #
1973
- # Net::IMAP::ContentDisposition represents Content-Disposition fields.
1974
- #
1975
- # ==== Fields:
1976
- #
1977
- # dsp_type:: Returns the disposition type.
1978
- #
1979
- # param:: Returns a hash that represents parameters of the Content-Disposition
1980
- # field.
1981
- #
1982
- ContentDisposition = Struct.new(:dsp_type, :param)
1983
-
1984
- # Net::IMAP::ThreadMember represents a thread-node returned
1985
- # by Net::IMAP#thread.
1986
- #
1987
- # ==== Fields:
1988
- #
1989
- # seqno:: The sequence number of this message.
1990
- #
1991
- # children:: An array of Net::IMAP::ThreadMember objects for mail
1992
- # items that are children of this in the thread.
1993
- #
1994
- ThreadMember = Struct.new(:seqno, :children)
1995
-
1996
- # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
1997
- #
1998
- # ==== Fields:
1999
- #
2000
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
2001
- #
2002
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
2003
- #
2004
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
2005
- #
2006
- # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
2007
- #
2008
- # description:: Returns a string giving the content description as defined in
2009
- # [MIME-IMB].
2010
- #
2011
- # encoding:: Returns a string giving the content transfer encoding as defined in
2012
- # [MIME-IMB].
2013
- #
2014
- # size:: Returns a number giving the size of the body in octets.
2015
- #
2016
- # md5:: Returns a string giving the body MD5 value as defined in [MD5].
2017
- #
2018
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
2019
- # the content disposition.
2020
- #
2021
- # language:: Returns a string or an array of strings giving the body
2022
- # language value as defined in [LANGUAGE-TAGS].
2023
- #
2024
- # extension:: Returns extension data.
2025
- #
2026
- # multipart?:: Returns false.
2027
- #
2028
- class BodyTypeBasic < Struct.new(:media_type, :subtype,
2029
- :param, :content_id,
2030
- :description, :encoding, :size,
2031
- :md5, :disposition, :language,
2032
- :extension)
2033
- def multipart?
2034
- return false
2035
- end
2036
-
2037
- # Obsolete: use +subtype+ instead. Calling this will
2038
- # generate a warning message to +stderr+, then return
2039
- # the value of +subtype+.
2040
- def media_subtype
2041
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2042
- return subtype
2043
- end
2044
- end
2045
-
2046
- # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
2047
- #
2048
- # ==== Fields:
2049
- #
2050
- # lines:: Returns the size of the body in text lines.
2051
- #
2052
- # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
2053
- #
2054
- class BodyTypeText < Struct.new(:media_type, :subtype,
2055
- :param, :content_id,
2056
- :description, :encoding, :size,
2057
- :lines,
2058
- :md5, :disposition, :language,
2059
- :extension)
2060
- def multipart?
2061
- return false
2062
- end
2063
-
2064
- # Obsolete: use +subtype+ instead. Calling this will
2065
- # generate a warning message to +stderr+, then return
2066
- # the value of +subtype+.
2067
- def media_subtype
2068
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2069
- return subtype
2070
- end
2071
- end
2072
-
2073
- # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
2074
- #
2075
- # ==== Fields:
2076
- #
2077
- # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
2078
- #
2079
- # body:: Returns an object giving the body structure.
2080
- #
2081
- # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
2082
- #
2083
- class BodyTypeMessage < Struct.new(:media_type, :subtype,
2084
- :param, :content_id,
2085
- :description, :encoding, :size,
2086
- :envelope, :body, :lines,
2087
- :md5, :disposition, :language,
2088
- :extension)
2089
- def multipart?
2090
- return false
2091
- end
2092
-
2093
- # Obsolete: use +subtype+ instead. Calling this will
2094
- # generate a warning message to +stderr+, then return
2095
- # the value of +subtype+.
2096
- def media_subtype
2097
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2098
- return subtype
2099
- end
2100
- end
2101
-
2102
- # Net::IMAP::BodyTypeAttachment represents attachment body structures
2103
- # of messages.
2104
- #
2105
- # ==== Fields:
2106
- #
2107
- # media_type:: Returns the content media type name.
2108
- #
2109
- # subtype:: Returns +nil+.
2110
- #
2111
- # param:: Returns a hash that represents parameters.
2112
- #
2113
- # multipart?:: Returns false.
2114
- #
2115
- class BodyTypeAttachment < Struct.new(:media_type, :subtype,
2116
- :param)
2117
- def multipart?
2118
- return false
2119
- end
2120
- end
2121
-
2122
- # Net::IMAP::BodyTypeMultipart represents multipart body structures
2123
- # of messages.
2124
- #
2125
- # ==== Fields:
2126
- #
2127
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
2128
- #
2129
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
2130
- #
2131
- # parts:: Returns multiple parts.
2132
- #
2133
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
2134
- #
2135
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
2136
- # the content disposition.
2137
- #
2138
- # language:: Returns a string or an array of strings giving the body
2139
- # language value as defined in [LANGUAGE-TAGS].
2140
- #
2141
- # extension:: Returns extension data.
2142
- #
2143
- # multipart?:: Returns true.
2144
- #
2145
- class BodyTypeMultipart < Struct.new(:media_type, :subtype,
2146
- :parts,
2147
- :param, :disposition, :language,
2148
- :extension)
2149
- def multipart?
2150
- return true
2151
- end
2152
-
2153
- # Obsolete: use +subtype+ instead. Calling this will
2154
- # generate a warning message to +stderr+, then return
2155
- # the value of +subtype+.
2156
- def media_subtype
2157
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2158
- return subtype
2159
- end
2160
- end
2161
-
2162
- class BodyTypeExtension < Struct.new(:media_type, :subtype,
2163
- :params, :content_id,
2164
- :description, :encoding, :size)
2165
- def multipart?
2166
- return false
2167
- end
2168
- end
2169
-
2170
- class ResponseParser # :nodoc:
2171
- def initialize
2172
- @str = nil
2173
- @pos = nil
2174
- @lex_state = nil
2175
- @token = nil
2176
- @flag_symbols = {}
2177
- end
2178
-
2179
- def parse(str)
2180
- @str = str
2181
- @pos = 0
2182
- @lex_state = EXPR_BEG
2183
- @token = nil
2184
- return response
2185
- end
2186
-
2187
- private
2188
-
2189
- EXPR_BEG = :EXPR_BEG
2190
- EXPR_DATA = :EXPR_DATA
2191
- EXPR_TEXT = :EXPR_TEXT
2192
- EXPR_RTEXT = :EXPR_RTEXT
2193
- EXPR_CTEXT = :EXPR_CTEXT
2194
-
2195
- T_SPACE = :SPACE
2196
- T_NIL = :NIL
2197
- T_NUMBER = :NUMBER
2198
- T_ATOM = :ATOM
2199
- T_QUOTED = :QUOTED
2200
- T_LPAR = :LPAR
2201
- T_RPAR = :RPAR
2202
- T_BSLASH = :BSLASH
2203
- T_STAR = :STAR
2204
- T_LBRA = :LBRA
2205
- T_RBRA = :RBRA
2206
- T_LITERAL = :LITERAL
2207
- T_PLUS = :PLUS
2208
- T_PERCENT = :PERCENT
2209
- T_CRLF = :CRLF
2210
- T_EOF = :EOF
2211
- T_TEXT = :TEXT
2212
-
2213
- BEG_REGEXP = /\G(?:\
2214
- (?# 1: SPACE )( +)|\
2215
- (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
2216
- (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
2217
- (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
2218
- (?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
2219
- (?# 6: LPAR )(\()|\
2220
- (?# 7: RPAR )(\))|\
2221
- (?# 8: BSLASH )(\\)|\
2222
- (?# 9: STAR )(\*)|\
2223
- (?# 10: LBRA )(\[)|\
2224
- (?# 11: RBRA )(\])|\
2225
- (?# 12: LITERAL )\{(\d+)\}\r\n|\
2226
- (?# 13: PLUS )(\+)|\
2227
- (?# 14: PERCENT )(%)|\
2228
- (?# 15: CRLF )(\r\n)|\
2229
- (?# 16: EOF )(\z))/ni
2230
-
2231
- DATA_REGEXP = /\G(?:\
2232
- (?# 1: SPACE )( )|\
2233
- (?# 2: NIL )(NIL)|\
2234
- (?# 3: NUMBER )(\d+)|\
2235
- (?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
2236
- (?# 5: LITERAL )\{(\d+)\}\r\n|\
2237
- (?# 6: LPAR )(\()|\
2238
- (?# 7: RPAR )(\)))/ni
2239
-
2240
- TEXT_REGEXP = /\G(?:\
2241
- (?# 1: TEXT )([^\x00\r\n]*))/ni
2242
-
2243
- RTEXT_REGEXP = /\G(?:\
2244
- (?# 1: LBRA )(\[)|\
2245
- (?# 2: TEXT )([^\x00\r\n]*))/ni
2246
-
2247
- CTEXT_REGEXP = /\G(?:\
2248
- (?# 1: TEXT )([^\x00\r\n\]]*))/ni
2249
-
2250
- Token = Struct.new(:symbol, :value)
2251
-
2252
- def response
2253
- token = lookahead
2254
- case token.symbol
2255
- when T_PLUS
2256
- result = continue_req
2257
- when T_STAR
2258
- result = response_untagged
2259
- else
2260
- result = response_tagged
2261
- end
2262
- while lookahead.symbol == T_SPACE
2263
- # Ignore trailing space for Microsoft Exchange Server
2264
- shift_token
2265
- end
2266
- match(T_CRLF)
2267
- match(T_EOF)
2268
- return result
2269
- end
2270
-
2271
- def continue_req
2272
- match(T_PLUS)
2273
- token = lookahead
2274
- if token.symbol == T_SPACE
2275
- shift_token
2276
- return ContinuationRequest.new(resp_text, @str)
2277
- else
2278
- return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
2279
- end
2280
- end
2281
-
2282
- def response_untagged
2283
- match(T_STAR)
2284
- match(T_SPACE)
2285
- token = lookahead
2286
- if token.symbol == T_NUMBER
2287
- return numeric_response
2288
- elsif token.symbol == T_ATOM
2289
- case token.value
2290
- when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
2291
- return response_cond
2292
- when /\A(?:FLAGS)\z/ni
2293
- return flags_response
2294
- when /\A(?:LIST|LSUB|XLIST)\z/ni
2295
- return list_response
2296
- when /\A(?:QUOTA)\z/ni
2297
- return getquota_response
2298
- when /\A(?:QUOTAROOT)\z/ni
2299
- return getquotaroot_response
2300
- when /\A(?:ACL)\z/ni
2301
- return getacl_response
2302
- when /\A(?:SEARCH|SORT)\z/ni
2303
- return search_response
2304
- when /\A(?:THREAD)\z/ni
2305
- return thread_response
2306
- when /\A(?:STATUS)\z/ni
2307
- return status_response
2308
- when /\A(?:CAPABILITY)\z/ni
2309
- return capability_response
2310
- else
2311
- return text_response
2312
- end
2313
- else
2314
- parse_error("unexpected token %s", token.symbol)
2315
- end
2316
- end
2317
-
2318
- def response_tagged
2319
- tag = atom
2320
- match(T_SPACE)
2321
- token = match(T_ATOM)
2322
- name = token.value.upcase
2323
- match(T_SPACE)
2324
- return TaggedResponse.new(tag, name, resp_text, @str)
2325
- end
2326
-
2327
- def response_cond
2328
- token = match(T_ATOM)
2329
- name = token.value.upcase
2330
- match(T_SPACE)
2331
- return UntaggedResponse.new(name, resp_text, @str)
2332
- end
2333
-
2334
- def numeric_response
2335
- n = number
2336
- match(T_SPACE)
2337
- token = match(T_ATOM)
2338
- name = token.value.upcase
2339
- case name
2340
- when "EXISTS", "RECENT", "EXPUNGE"
2341
- return UntaggedResponse.new(name, n, @str)
2342
- when "FETCH"
2343
- shift_token
2344
- match(T_SPACE)
2345
- data = FetchData.new(n, msg_att(n))
2346
- return UntaggedResponse.new(name, data, @str)
2347
- end
2348
- end
2349
-
2350
- def msg_att(n)
2351
- match(T_LPAR)
2352
- attr = {}
2353
- while true
2354
- token = lookahead
2355
- case token.symbol
2356
- when T_RPAR
2357
- shift_token
2358
- break
2359
- when T_SPACE
2360
- shift_token
2361
- next
2362
- end
2363
- case token.value
2364
- when /\A(?:ENVELOPE)\z/ni
2365
- name, val = envelope_data
2366
- when /\A(?:FLAGS)\z/ni
2367
- name, val = flags_data
2368
- when /\A(?:INTERNALDATE)\z/ni
2369
- name, val = internaldate_data
2370
- when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
2371
- name, val = rfc822_text
2372
- when /\A(?:RFC822\.SIZE)\z/ni
2373
- name, val = rfc822_size
2374
- when /\A(?:BODY(?:STRUCTURE)?)\z/ni
2375
- name, val = body_data
2376
- when /\A(?:UID)\z/ni
2377
- name, val = uid_data
2378
- when /\A(?:MODSEQ)\z/ni
2379
- name, val = modseq_data
2380
- else
2381
- parse_error("unknown attribute `%s' for {%d}", token.value, n)
2382
- end
2383
- attr[name] = val
2384
- end
2385
- return attr
2386
- end
2387
-
2388
- def envelope_data
2389
- token = match(T_ATOM)
2390
- name = token.value.upcase
2391
- match(T_SPACE)
2392
- return name, envelope
2393
- end
2394
-
2395
- def envelope
2396
- @lex_state = EXPR_DATA
2397
- token = lookahead
2398
- if token.symbol == T_NIL
2399
- shift_token
2400
- result = nil
2401
- else
2402
- match(T_LPAR)
2403
- date = nstring
2404
- match(T_SPACE)
2405
- subject = nstring
2406
- match(T_SPACE)
2407
- from = address_list
2408
- match(T_SPACE)
2409
- sender = address_list
2410
- match(T_SPACE)
2411
- reply_to = address_list
2412
- match(T_SPACE)
2413
- to = address_list
2414
- match(T_SPACE)
2415
- cc = address_list
2416
- match(T_SPACE)
2417
- bcc = address_list
2418
- match(T_SPACE)
2419
- in_reply_to = nstring
2420
- match(T_SPACE)
2421
- message_id = nstring
2422
- match(T_RPAR)
2423
- result = Envelope.new(date, subject, from, sender, reply_to,
2424
- to, cc, bcc, in_reply_to, message_id)
2425
- end
2426
- @lex_state = EXPR_BEG
2427
- return result
2428
- end
2429
-
2430
- def flags_data
2431
- token = match(T_ATOM)
2432
- name = token.value.upcase
2433
- match(T_SPACE)
2434
- return name, flag_list
2435
- end
2436
-
2437
- def internaldate_data
2438
- token = match(T_ATOM)
2439
- name = token.value.upcase
2440
- match(T_SPACE)
2441
- token = match(T_QUOTED)
2442
- return name, token.value
2443
- end
2444
-
2445
- def rfc822_text
2446
- token = match(T_ATOM)
2447
- name = token.value.upcase
2448
- token = lookahead
2449
- if token.symbol == T_LBRA
2450
- shift_token
2451
- match(T_RBRA)
2452
- end
2453
- match(T_SPACE)
2454
- return name, nstring
2455
- end
2456
-
2457
- def rfc822_size
2458
- token = match(T_ATOM)
2459
- name = token.value.upcase
2460
- match(T_SPACE)
2461
- return name, number
2462
- end
2463
-
2464
- def body_data
2465
- token = match(T_ATOM)
2466
- name = token.value.upcase
2467
- token = lookahead
2468
- if token.symbol == T_SPACE
2469
- shift_token
2470
- return name, body
2471
- end
2472
- name.concat(section)
2473
- token = lookahead
2474
- if token.symbol == T_ATOM
2475
- name.concat(token.value)
2476
- shift_token
2477
- end
2478
- match(T_SPACE)
2479
- data = nstring
2480
- return name, data
2481
- end
2482
-
2483
- def body
2484
- @lex_state = EXPR_DATA
2485
- token = lookahead
2486
- if token.symbol == T_NIL
2487
- shift_token
2488
- result = nil
2489
- else
2490
- match(T_LPAR)
2491
- token = lookahead
2492
- if token.symbol == T_LPAR
2493
- result = body_type_mpart
2494
- else
2495
- result = body_type_1part
2496
- end
2497
- match(T_RPAR)
2498
- end
2499
- @lex_state = EXPR_BEG
2500
- return result
2501
- end
2502
-
2503
- def body_type_1part
2504
- token = lookahead
2505
- case token.value
2506
- when /\A(?:TEXT)\z/ni
2507
- return body_type_text
2508
- when /\A(?:MESSAGE)\z/ni
2509
- return body_type_msg
2510
- when /\A(?:ATTACHMENT)\z/ni
2511
- return body_type_attachment
2512
- when /\A(?:MIXED)\z/ni
2513
- return body_type_mixed
2514
- else
2515
- return body_type_basic
2516
- end
2517
- end
2518
-
2519
- def body_type_basic
2520
- mtype, msubtype = media_type
2521
- token = lookahead
2522
- if token.symbol == T_RPAR
2523
- return BodyTypeBasic.new(mtype, msubtype)
2524
- end
2525
- match(T_SPACE)
2526
- param, content_id, desc, enc, size = body_fields
2527
- md5, disposition, language, extension = body_ext_1part
2528
- return BodyTypeBasic.new(mtype, msubtype,
2529
- param, content_id,
2530
- desc, enc, size,
2531
- md5, disposition, language, extension)
2532
- end
2533
-
2534
- def body_type_text
2535
- mtype, msubtype = media_type
2536
- match(T_SPACE)
2537
- param, content_id, desc, enc, size = body_fields
2538
- match(T_SPACE)
2539
- lines = number
2540
- md5, disposition, language, extension = body_ext_1part
2541
- return BodyTypeText.new(mtype, msubtype,
2542
- param, content_id,
2543
- desc, enc, size,
2544
- lines,
2545
- md5, disposition, language, extension)
2546
- end
2547
-
2548
- def body_type_msg
2549
- mtype, msubtype = media_type
2550
- match(T_SPACE)
2551
- param, content_id, desc, enc, size = body_fields
2552
-
2553
- token = lookahead
2554
- if token.symbol == T_RPAR
2555
- # If this is not message/rfc822, we shouldn't apply the RFC822
2556
- # spec to it. We should handle anything other than
2557
- # message/rfc822 using multipart extension data [rfc3501] (i.e.
2558
- # the data itself won't be returned, we would have to retrieve it
2559
- # with BODYSTRUCTURE instead of with BODY
2560
-
2561
- # Also, sometimes a message/rfc822 is included as a large
2562
- # attachment instead of having all of the other details
2563
- # (e.g. attaching a .eml file to an email)
2564
- if msubtype == "RFC822"
2565
- return BodyTypeMessage.new(mtype, msubtype, param, content_id,
2566
- desc, enc, size, nil, nil, nil, nil,
2567
- nil, nil, nil)
2568
- else
2569
- return BodyTypeExtension.new(mtype, msubtype,
2570
- param, content_id,
2571
- desc, enc, size)
2572
- end
2573
- end
2574
-
2575
- match(T_SPACE)
2576
- env = envelope
2577
- match(T_SPACE)
2578
- b = body
2579
- match(T_SPACE)
2580
- lines = number
2581
- md5, disposition, language, extension = body_ext_1part
2582
- return BodyTypeMessage.new(mtype, msubtype,
2583
- param, content_id,
2584
- desc, enc, size,
2585
- env, b, lines,
2586
- md5, disposition, language, extension)
2587
- end
2588
-
2589
- def body_type_attachment
2590
- mtype = case_insensitive_string
2591
- match(T_SPACE)
2592
- param = body_fld_param
2593
- return BodyTypeAttachment.new(mtype, nil, param)
2594
- end
2595
-
2596
- def body_type_mixed
2597
- mtype = "MULTIPART"
2598
- msubtype = case_insensitive_string
2599
- param, disposition, language, extension = body_ext_mpart
2600
- return BodyTypeBasic.new(mtype, msubtype, param, nil, nil, nil, nil, nil, disposition, language, extension)
2601
- end
2602
-
2603
- def body_type_mpart
2604
- parts = []
2605
- while true
2606
- token = lookahead
2607
- if token.symbol == T_SPACE
2608
- shift_token
2609
- break
2610
- end
2611
- parts.push(body)
2612
- end
2613
- mtype = "MULTIPART"
2614
- msubtype = case_insensitive_string
2615
- param, disposition, language, extension = body_ext_mpart
2616
- return BodyTypeMultipart.new(mtype, msubtype, parts,
2617
- param, disposition, language,
2618
- extension)
2619
- end
2620
-
2621
- def media_type
2622
- mtype = case_insensitive_string
2623
- token = lookahead
2624
- if token.symbol != T_SPACE
2625
- return mtype, nil
2626
- end
2627
- match(T_SPACE)
2628
- msubtype = case_insensitive_string
2629
- return mtype, msubtype
2630
- end
2631
-
2632
- def body_fields
2633
- param = body_fld_param
2634
- match(T_SPACE)
2635
- content_id = nstring
2636
- match(T_SPACE)
2637
- desc = nstring
2638
- match(T_SPACE)
2639
- enc = case_insensitive_string
2640
- match(T_SPACE)
2641
- size = number
2642
- return param, content_id, desc, enc, size
2643
- end
2644
-
2645
- def body_fld_param
2646
- token = lookahead
2647
- if token.symbol == T_NIL
2648
- shift_token
2649
- return nil
2650
- end
2651
- match(T_LPAR)
2652
- param = {}
2653
- while true
2654
- token = lookahead
2655
- case token.symbol
2656
- when T_RPAR
2657
- shift_token
2658
- break
2659
- when T_SPACE
2660
- shift_token
2661
- end
2662
- name = case_insensitive_string
2663
- match(T_SPACE)
2664
- val = string
2665
- param[name] = val
2666
- end
2667
- return param
2668
- end
2669
-
2670
- def body_ext_1part
2671
- token = lookahead
2672
- if token.symbol == T_SPACE
2673
- shift_token
2674
- else
2675
- return nil
2676
- end
2677
- md5 = nstring
2678
-
2679
- token = lookahead
2680
- if token.symbol == T_SPACE
2681
- shift_token
2682
- else
2683
- return md5
2684
- end
2685
- disposition = body_fld_dsp
2686
-
2687
- token = lookahead
2688
- if token.symbol == T_SPACE
2689
- shift_token
2690
- else
2691
- return md5, disposition
2692
- end
2693
- language = body_fld_lang
2694
-
2695
- token = lookahead
2696
- if token.symbol == T_SPACE
2697
- shift_token
2698
- else
2699
- return md5, disposition, language
2700
- end
2701
-
2702
- extension = body_extensions
2703
- return md5, disposition, language, extension
2704
- end
2705
-
2706
- def body_ext_mpart
2707
- token = lookahead
2708
- if token.symbol == T_SPACE
2709
- shift_token
2710
- else
2711
- return nil
2712
- end
2713
- param = body_fld_param
2714
-
2715
- token = lookahead
2716
- if token.symbol == T_SPACE
2717
- shift_token
2718
- else
2719
- return param
2720
- end
2721
- disposition = body_fld_dsp
2722
-
2723
- token = lookahead
2724
- if token.symbol == T_SPACE
2725
- shift_token
2726
- else
2727
- return param, disposition
2728
- end
2729
- language = body_fld_lang
2730
-
2731
- token = lookahead
2732
- if token.symbol == T_SPACE
2733
- shift_token
2734
- else
2735
- return param, disposition, language
2736
- end
2737
-
2738
- extension = body_extensions
2739
- return param, disposition, language, extension
2740
- end
2741
-
2742
- def body_fld_dsp
2743
- token = lookahead
2744
- if token.symbol == T_NIL
2745
- shift_token
2746
- return nil
2747
- end
2748
- match(T_LPAR)
2749
- dsp_type = case_insensitive_string
2750
- match(T_SPACE)
2751
- param = body_fld_param
2752
- match(T_RPAR)
2753
- return ContentDisposition.new(dsp_type, param)
2754
- end
2755
-
2756
- def body_fld_lang
2757
- token = lookahead
2758
- if token.symbol == T_LPAR
2759
- shift_token
2760
- result = []
2761
- while true
2762
- token = lookahead
2763
- case token.symbol
2764
- when T_RPAR
2765
- shift_token
2766
- return result
2767
- when T_SPACE
2768
- shift_token
2769
- end
2770
- result.push(case_insensitive_string)
2771
- end
2772
- else
2773
- lang = nstring
2774
- if lang
2775
- return lang.upcase
2776
- else
2777
- return lang
2778
- end
2779
- end
2780
- end
2781
-
2782
- def body_extensions
2783
- result = []
2784
- while true
2785
- token = lookahead
2786
- case token.symbol
2787
- when T_RPAR
2788
- return result
2789
- when T_SPACE
2790
- shift_token
2791
- end
2792
- result.push(body_extension)
2793
- end
2794
- end
2795
-
2796
- def body_extension
2797
- token = lookahead
2798
- case token.symbol
2799
- when T_LPAR
2800
- shift_token
2801
- result = body_extensions
2802
- match(T_RPAR)
2803
- return result
2804
- when T_NUMBER
2805
- return number
2806
- else
2807
- return nstring
2808
- end
2809
- end
2810
-
2811
- def section
2812
- str = String.new
2813
- token = match(T_LBRA)
2814
- str.concat(token.value)
2815
- token = match(T_ATOM, T_NUMBER, T_RBRA)
2816
- if token.symbol == T_RBRA
2817
- str.concat(token.value)
2818
- return str
2819
- end
2820
- str.concat(token.value)
2821
- token = lookahead
2822
- if token.symbol == T_SPACE
2823
- shift_token
2824
- str.concat(token.value)
2825
- token = match(T_LPAR)
2826
- str.concat(token.value)
2827
- while true
2828
- token = lookahead
2829
- case token.symbol
2830
- when T_RPAR
2831
- str.concat(token.value)
2832
- shift_token
2833
- break
2834
- when T_SPACE
2835
- shift_token
2836
- str.concat(token.value)
2837
- end
2838
- str.concat(format_string(astring))
2839
- end
2840
- end
2841
- token = match(T_RBRA)
2842
- str.concat(token.value)
2843
- return str
2844
- end
2845
-
2846
- def format_string(str)
2847
- case str
2848
- when ""
2849
- return '""'
2850
- when /[\x80-\xff\r\n]/n
2851
- # literal
2852
- return "{" + str.bytesize.to_s + "}" + CRLF + str
2853
- when /[(){ \x00-\x1f\x7f%*"\\]/n
2854
- # quoted string
2855
- return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
2856
- else
2857
- # atom
2858
- return str
2859
- end
2860
- end
2861
-
2862
- def uid_data
2863
- token = match(T_ATOM)
2864
- name = token.value.upcase
2865
- match(T_SPACE)
2866
- return name, number
2867
- end
2868
-
2869
- def modseq_data
2870
- token = match(T_ATOM)
2871
- name = token.value.upcase
2872
- match(T_SPACE)
2873
- match(T_LPAR)
2874
- modseq = number
2875
- match(T_RPAR)
2876
- return name, modseq
2877
- end
2878
-
2879
- def text_response
2880
- token = match(T_ATOM)
2881
- name = token.value.upcase
2882
- match(T_SPACE)
2883
- @lex_state = EXPR_TEXT
2884
- token = match(T_TEXT)
2885
- @lex_state = EXPR_BEG
2886
- return UntaggedResponse.new(name, token.value)
2887
- end
2888
-
2889
- def flags_response
2890
- token = match(T_ATOM)
2891
- name = token.value.upcase
2892
- match(T_SPACE)
2893
- return UntaggedResponse.new(name, flag_list, @str)
2894
- end
2895
-
2896
- def list_response
2897
- token = match(T_ATOM)
2898
- name = token.value.upcase
2899
- match(T_SPACE)
2900
- return UntaggedResponse.new(name, mailbox_list, @str)
2901
- end
2902
-
2903
- def mailbox_list
2904
- attr = flag_list
2905
- match(T_SPACE)
2906
- token = match(T_QUOTED, T_NIL)
2907
- if token.symbol == T_NIL
2908
- delim = nil
2909
- else
2910
- delim = token.value
2911
- end
2912
- match(T_SPACE)
2913
- name = astring
2914
- return MailboxList.new(attr, delim, name)
2915
- end
2916
-
2917
- def getquota_response
2918
- # If quota never established, get back
2919
- # `NO Quota root does not exist'.
2920
- # If quota removed, get `()' after the
2921
- # folder spec with no mention of `STORAGE'.
2922
- token = match(T_ATOM)
2923
- name = token.value.upcase
2924
- match(T_SPACE)
2925
- mailbox = astring
2926
- match(T_SPACE)
2927
- match(T_LPAR)
2928
- token = lookahead
2929
- case token.symbol
2930
- when T_RPAR
2931
- shift_token
2932
- data = MailboxQuota.new(mailbox, nil, nil)
2933
- return UntaggedResponse.new(name, data, @str)
2934
- when T_ATOM
2935
- shift_token
2936
- match(T_SPACE)
2937
- token = match(T_NUMBER)
2938
- usage = token.value
2939
- match(T_SPACE)
2940
- token = match(T_NUMBER)
2941
- quota = token.value
2942
- match(T_RPAR)
2943
- data = MailboxQuota.new(mailbox, usage, quota)
2944
- return UntaggedResponse.new(name, data, @str)
2945
- else
2946
- parse_error("unexpected token %s", token.symbol)
2947
- end
2948
- end
2949
-
2950
- def getquotaroot_response
2951
- # Similar to getquota, but only admin can use getquota.
2952
- token = match(T_ATOM)
2953
- name = token.value.upcase
2954
- match(T_SPACE)
2955
- mailbox = astring
2956
- quotaroots = []
2957
- while true
2958
- token = lookahead
2959
- break unless token.symbol == T_SPACE
2960
- shift_token
2961
- quotaroots.push(astring)
2962
- end
2963
- data = MailboxQuotaRoot.new(mailbox, quotaroots)
2964
- return UntaggedResponse.new(name, data, @str)
2965
- end
2966
-
2967
- def getacl_response
2968
- token = match(T_ATOM)
2969
- name = token.value.upcase
2970
- match(T_SPACE)
2971
- mailbox = astring
2972
- data = []
2973
- token = lookahead
2974
- if token.symbol == T_SPACE
2975
- shift_token
2976
- while true
2977
- token = lookahead
2978
- case token.symbol
2979
- when T_CRLF
2980
- break
2981
- when T_SPACE
2982
- shift_token
2983
- end
2984
- user = astring
2985
- match(T_SPACE)
2986
- rights = astring
2987
- data.push(MailboxACLItem.new(user, rights, mailbox))
2988
- end
2989
- end
2990
- return UntaggedResponse.new(name, data, @str)
2991
- end
2992
-
2993
- def search_response
2994
- token = match(T_ATOM)
2995
- name = token.value.upcase
2996
- token = lookahead
2997
- if token.symbol == T_SPACE
2998
- shift_token
2999
- data = []
3000
- while true
3001
- token = lookahead
3002
- case token.symbol
3003
- when T_CRLF
3004
- break
3005
- when T_SPACE
3006
- shift_token
3007
- when T_NUMBER
3008
- data.push(number)
3009
- when T_LPAR
3010
- # TODO: include the MODSEQ value in a response
3011
- shift_token
3012
- match(T_ATOM)
3013
- match(T_SPACE)
3014
- match(T_NUMBER)
3015
- match(T_RPAR)
3016
- end
3017
- end
3018
- else
3019
- data = []
3020
- end
3021
- return UntaggedResponse.new(name, data, @str)
3022
- end
3023
-
3024
- def thread_response
3025
- token = match(T_ATOM)
3026
- name = token.value.upcase
3027
- token = lookahead
3028
-
3029
- if token.symbol == T_SPACE
3030
- threads = []
3031
-
3032
- while true
3033
- shift_token
3034
- token = lookahead
3035
-
3036
- case token.symbol
3037
- when T_LPAR
3038
- threads << thread_branch(token)
3039
- when T_CRLF
3040
- break
3041
- end
3042
- end
3043
- else
3044
- # no member
3045
- threads = []
3046
- end
3047
-
3048
- return UntaggedResponse.new(name, threads, @str)
3049
- end
3050
-
3051
- def thread_branch(token)
3052
- rootmember = nil
3053
- lastmember = nil
3054
-
3055
- while true
3056
- shift_token # ignore first T_LPAR
3057
- token = lookahead
3058
-
3059
- case token.symbol
3060
- when T_NUMBER
3061
- # new member
3062
- newmember = ThreadMember.new(number, [])
3063
- if rootmember.nil?
3064
- rootmember = newmember
3065
- else
3066
- lastmember.children << newmember
3067
- end
3068
- lastmember = newmember
3069
- when T_SPACE
3070
- # do nothing
3071
- when T_LPAR
3072
- if rootmember.nil?
3073
- # dummy member
3074
- lastmember = rootmember = ThreadMember.new(nil, [])
3075
- end
3076
-
3077
- lastmember.children << thread_branch(token)
3078
- when T_RPAR
3079
- break
3080
- end
3081
- end
3082
-
3083
- return rootmember
3084
- end
3085
-
3086
- def status_response
3087
- token = match(T_ATOM)
3088
- name = token.value.upcase
3089
- match(T_SPACE)
3090
- mailbox = astring
3091
- match(T_SPACE)
3092
- match(T_LPAR)
3093
- attr = {}
3094
- while true
3095
- token = lookahead
3096
- case token.symbol
3097
- when T_RPAR
3098
- shift_token
3099
- break
3100
- when T_SPACE
3101
- shift_token
3102
- end
3103
- token = match(T_ATOM)
3104
- key = token.value.upcase
3105
- match(T_SPACE)
3106
- val = number
3107
- attr[key] = val
3108
- end
3109
- data = StatusData.new(mailbox, attr)
3110
- return UntaggedResponse.new(name, data, @str)
3111
- end
3112
-
3113
- def capability_response
3114
- token = match(T_ATOM)
3115
- name = token.value.upcase
3116
- match(T_SPACE)
3117
- data = []
3118
- while true
3119
- token = lookahead
3120
- case token.symbol
3121
- when T_CRLF
3122
- break
3123
- when T_SPACE
3124
- shift_token
3125
- next
3126
- end
3127
- data.push(atom.upcase)
3128
- end
3129
- return UntaggedResponse.new(name, data, @str)
3130
- end
3131
-
3132
- def resp_text
3133
- @lex_state = EXPR_RTEXT
3134
- token = lookahead
3135
- if token.symbol == T_LBRA
3136
- code = resp_text_code
3137
- else
3138
- code = nil
3139
- end
3140
- token = match(T_TEXT)
3141
- @lex_state = EXPR_BEG
3142
- return ResponseText.new(code, token.value)
3143
- end
3144
-
3145
- def resp_text_code
3146
- @lex_state = EXPR_BEG
3147
- match(T_LBRA)
3148
- token = match(T_ATOM)
3149
- name = token.value.upcase
3150
- case name
3151
- when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
3152
- result = ResponseCode.new(name, nil)
3153
- when /\A(?:PERMANENTFLAGS)\z/n
3154
- match(T_SPACE)
3155
- result = ResponseCode.new(name, flag_list)
3156
- when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
3157
- match(T_SPACE)
3158
- result = ResponseCode.new(name, number)
3159
- else
3160
- token = lookahead
3161
- if token.symbol == T_SPACE
3162
- shift_token
3163
- @lex_state = EXPR_CTEXT
3164
- token = match(T_TEXT)
3165
- @lex_state = EXPR_BEG
3166
- result = ResponseCode.new(name, token.value)
3167
- else
3168
- result = ResponseCode.new(name, nil)
3169
- end
3170
- end
3171
- match(T_RBRA)
3172
- @lex_state = EXPR_RTEXT
3173
- return result
3174
- end
3175
-
3176
- def address_list
3177
- token = lookahead
3178
- if token.symbol == T_NIL
3179
- shift_token
3180
- return nil
3181
- else
3182
- result = []
3183
- match(T_LPAR)
3184
- while true
3185
- token = lookahead
3186
- case token.symbol
3187
- when T_RPAR
3188
- shift_token
3189
- break
3190
- when T_SPACE
3191
- shift_token
3192
- end
3193
- result.push(address)
3194
- end
3195
- return result
3196
- end
3197
- end
3198
-
3199
- ADDRESS_REGEXP = /\G\
3200
- (?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
3201
- (?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
3202
- (?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
3203
- (?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
3204
- \)/ni
3205
-
3206
- def address
3207
- match(T_LPAR)
3208
- if @str.index(ADDRESS_REGEXP, @pos)
3209
- # address does not include literal.
3210
- @pos = $~.end(0)
3211
- name = $1
3212
- route = $2
3213
- mailbox = $3
3214
- host = $4
3215
- for s in [name, route, mailbox, host]
3216
- if s
3217
- s.gsub!(/\\(["\\])/n, "\\1")
3218
- end
3219
- end
3220
- else
3221
- name = nstring
3222
- match(T_SPACE)
3223
- route = nstring
3224
- match(T_SPACE)
3225
- mailbox = nstring
3226
- match(T_SPACE)
3227
- host = nstring
3228
- match(T_RPAR)
3229
- end
3230
- return Address.new(name, route, mailbox, host)
3231
- end
3232
-
3233
- FLAG_REGEXP = /\
3234
- (?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
3235
- (?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
3236
-
3237
- def flag_list
3238
- if @str.index(/\(([^)]*)\)/ni, @pos)
3239
- @pos = $~.end(0)
3240
- return $1.scan(FLAG_REGEXP).collect { |flag, atom|
3241
- if atom
3242
- atom
3243
- else
3244
- symbol = flag.capitalize.intern
3245
- @flag_symbols[symbol] = true
3246
- if @flag_symbols.length > IMAP.max_flag_count
3247
- raise FlagCountError, "number of flag symbols exceeded"
3248
- end
3249
- symbol
3250
- end
3251
- }
3252
- else
3253
- parse_error("invalid flag list")
3254
- end
3255
- end
3256
-
3257
- def nstring
3258
- token = lookahead
3259
- if token.symbol == T_NIL
3260
- shift_token
3261
- return nil
3262
- else
3263
- return string
3264
- end
3265
- end
3266
-
3267
- def astring
3268
- token = lookahead
3269
- if string_token?(token)
3270
- return string
3271
- else
3272
- return atom
3273
- end
3274
- end
3275
-
3276
- def string
3277
- token = lookahead
3278
- if token.symbol == T_NIL
3279
- shift_token
3280
- return nil
3281
- end
3282
- token = match(T_QUOTED, T_LITERAL)
3283
- return token.value
3284
- end
3285
-
3286
- STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
3287
-
3288
- def string_token?(token)
3289
- return STRING_TOKENS.include?(token.symbol)
3290
- end
3291
-
3292
- def case_insensitive_string
3293
- token = lookahead
3294
- if token.symbol == T_NIL
3295
- shift_token
3296
- return nil
3297
- end
3298
- token = match(T_QUOTED, T_LITERAL)
3299
- return token.value.upcase
3300
- end
3301
-
3302
- def atom
3303
- result = String.new
3304
- while true
3305
- token = lookahead
3306
- if atom_token?(token)
3307
- result.concat(token.value)
3308
- shift_token
3309
- else
3310
- if result.empty?
3311
- parse_error("unexpected token %s", token.symbol)
3312
- else
3313
- return result
3314
- end
3315
- end
3316
- end
3317
- end
3318
-
3319
- ATOM_TOKENS = [
3320
- T_ATOM,
3321
- T_NUMBER,
3322
- T_NIL,
3323
- T_LBRA,
3324
- T_RBRA,
3325
- T_PLUS
3326
- ]
3327
-
3328
- def atom_token?(token)
3329
- return ATOM_TOKENS.include?(token.symbol)
3330
- end
3331
-
3332
- def number
3333
- token = lookahead
3334
- if token.symbol == T_NIL
3335
- shift_token
3336
- return nil
3337
- end
3338
- token = match(T_NUMBER)
3339
- return token.value.to_i
3340
- end
3341
-
3342
- def nil_atom
3343
- match(T_NIL)
3344
- return nil
3345
- end
3346
-
3347
- def match(*args)
3348
- token = lookahead
3349
- unless args.include?(token.symbol)
3350
- parse_error('unexpected token %s (expected %s)',
3351
- token.symbol.id2name,
3352
- args.collect {|i| i.id2name}.join(" or "))
3353
- end
3354
- shift_token
3355
- return token
3356
- end
3357
-
3358
- def lookahead
3359
- unless @token
3360
- @token = next_token
3361
- end
3362
- return @token
3363
- end
3364
-
3365
- def shift_token
3366
- @token = nil
3367
- end
3368
-
3369
- def next_token
3370
- case @lex_state
3371
- when EXPR_BEG
3372
- if @str.index(BEG_REGEXP, @pos)
3373
- @pos = $~.end(0)
3374
- if $1
3375
- return Token.new(T_SPACE, $+)
3376
- elsif $2
3377
- return Token.new(T_NIL, $+)
3378
- elsif $3
3379
- return Token.new(T_NUMBER, $+)
3380
- elsif $4
3381
- return Token.new(T_ATOM, $+)
3382
- elsif $5
3383
- return Token.new(T_QUOTED,
3384
- $+.gsub(/\\(["\\])/n, "\\1"))
3385
- elsif $6
3386
- return Token.new(T_LPAR, $+)
3387
- elsif $7
3388
- return Token.new(T_RPAR, $+)
3389
- elsif $8
3390
- return Token.new(T_BSLASH, $+)
3391
- elsif $9
3392
- return Token.new(T_STAR, $+)
3393
- elsif $10
3394
- return Token.new(T_LBRA, $+)
3395
- elsif $11
3396
- return Token.new(T_RBRA, $+)
3397
- elsif $12
3398
- len = $+.to_i
3399
- val = @str[@pos, len]
3400
- @pos += len
3401
- return Token.new(T_LITERAL, val)
3402
- elsif $13
3403
- return Token.new(T_PLUS, $+)
3404
- elsif $14
3405
- return Token.new(T_PERCENT, $+)
3406
- elsif $15
3407
- return Token.new(T_CRLF, $+)
3408
- elsif $16
3409
- return Token.new(T_EOF, $+)
3410
- else
3411
- parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
3412
- end
3413
- else
3414
- @str.index(/\S*/n, @pos)
3415
- parse_error("unknown token - %s", $&.dump)
3416
- end
3417
- when EXPR_DATA
3418
- if @str.index(DATA_REGEXP, @pos)
3419
- @pos = $~.end(0)
3420
- if $1
3421
- return Token.new(T_SPACE, $+)
3422
- elsif $2
3423
- return Token.new(T_NIL, $+)
3424
- elsif $3
3425
- return Token.new(T_NUMBER, $+)
3426
- elsif $4
3427
- return Token.new(T_QUOTED,
3428
- $+.gsub(/\\(["\\])/n, "\\1"))
3429
- elsif $5
3430
- len = $+.to_i
3431
- val = @str[@pos, len]
3432
- @pos += len
3433
- return Token.new(T_LITERAL, val)
3434
- elsif $6
3435
- return Token.new(T_LPAR, $+)
3436
- elsif $7
3437
- return Token.new(T_RPAR, $+)
3438
- else
3439
- parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
3440
- end
3441
- else
3442
- @str.index(/\S*/n, @pos)
3443
- parse_error("unknown token - %s", $&.dump)
3444
- end
3445
- when EXPR_TEXT
3446
- if @str.index(TEXT_REGEXP, @pos)
3447
- @pos = $~.end(0)
3448
- if $1
3449
- return Token.new(T_TEXT, $+)
3450
- else
3451
- parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
3452
- end
3453
- else
3454
- @str.index(/\S*/n, @pos)
3455
- parse_error("unknown token - %s", $&.dump)
3456
- end
3457
- when EXPR_RTEXT
3458
- if @str.index(RTEXT_REGEXP, @pos)
3459
- @pos = $~.end(0)
3460
- if $1
3461
- return Token.new(T_LBRA, $+)
3462
- elsif $2
3463
- return Token.new(T_TEXT, $+)
3464
- else
3465
- parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
3466
- end
3467
- else
3468
- @str.index(/\S*/n, @pos)
3469
- parse_error("unknown token - %s", $&.dump)
3470
- end
3471
- when EXPR_CTEXT
3472
- if @str.index(CTEXT_REGEXP, @pos)
3473
- @pos = $~.end(0)
3474
- if $1
3475
- return Token.new(T_TEXT, $+)
3476
- else
3477
- parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
3478
- end
3479
- else
3480
- @str.index(/\S*/n, @pos) #/
3481
- parse_error("unknown token - %s", $&.dump)
3482
- end
3483
- else
3484
- parse_error("invalid @lex_state - %s", @lex_state.inspect)
3485
- end
3486
- end
3487
-
3488
- def parse_error(fmt, *args)
3489
- if IMAP.debug
3490
- $stderr.printf("@str: %s\n", @str.dump)
3491
- $stderr.printf("@pos: %d\n", @pos)
3492
- $stderr.printf("@lex_state: %s\n", @lex_state)
3493
- if @token
3494
- $stderr.printf("@token.symbol: %s\n", @token.symbol)
3495
- $stderr.printf("@token.value: %s\n", @token.value.inspect)
3496
- end
3497
- end
3498
- raise ResponseParseError, format(fmt, *args)
3499
- end
3500
- end
3501
-
3502
- # Authenticator for the "LOGIN" authentication type. See
3503
- # #authenticate().
3504
- class LoginAuthenticator
3505
- def process(data)
3506
- case @state
3507
- when STATE_USER
3508
- @state = STATE_PASSWORD
3509
- return @user
3510
- when STATE_PASSWORD
3511
- return @password
3512
- end
3513
- end
3514
-
3515
- private
3516
-
3517
- STATE_USER = :USER
3518
- STATE_PASSWORD = :PASSWORD
3519
-
3520
- def initialize(user, password)
3521
- @user = user
3522
- @password = password
3523
- @state = STATE_USER
3524
- end
3525
- end
3526
- add_authenticator "LOGIN", LoginAuthenticator
3527
-
3528
- # Authenticator for the "PLAIN" authentication type. See
3529
- # #authenticate().
3530
- class PlainAuthenticator
3531
- def process(data)
3532
- return "\0#{@user}\0#{@password}"
3533
- end
3534
-
3535
- private
3536
-
3537
- def initialize(user, password)
3538
- @user = user
3539
- @password = password
3540
- end
3541
- end
3542
- add_authenticator "PLAIN", PlainAuthenticator
3543
-
3544
- # Authenticator for the "CRAM-MD5" authentication type. See
3545
- # #authenticate().
3546
- class CramMD5Authenticator
3547
- def process(challenge)
3548
- digest = hmac_md5(challenge, @password)
3549
- return @user + " " + digest
3550
- end
3551
-
3552
- private
3553
-
3554
- def initialize(user, password)
3555
- @user = user
3556
- @password = password
3557
- end
3558
-
3559
- def hmac_md5(text, key)
3560
- if key.length > 64
3561
- key = Digest::MD5.digest(key)
3562
- end
3563
-
3564
- k_ipad = key + "\0" * (64 - key.length)
3565
- k_opad = key + "\0" * (64 - key.length)
3566
- for i in 0..63
3567
- k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
3568
- k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
3569
- end
3570
-
3571
- digest = Digest::MD5.digest(k_ipad + text)
3572
-
3573
- return Digest::MD5.hexdigest(k_opad + digest)
3574
- end
3575
- end
3576
- add_authenticator "CRAM-MD5", CramMD5Authenticator
3577
-
3578
- # Authenticator for the "DIGEST-MD5" authentication type. See
3579
- # #authenticate().
3580
- class DigestMD5Authenticator
3581
- def process(challenge)
3582
- case @stage
3583
- when STAGE_ONE
3584
- @stage = STAGE_TWO
3585
- sparams = {}
3586
- c = StringScanner.new(challenge)
3587
- while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
3588
- k, v = c[1], c[2]
3589
- if v =~ /^"(.*)"$/
3590
- v = $1
3591
- if v =~ /,/
3592
- v = v.split(',')
3593
- end
3594
- end
3595
- sparams[k] = v
3596
- end
3597
-
3598
- raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
3599
- raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
3600
-
3601
- response = {
3602
- :nonce => sparams['nonce'],
3603
- :username => @user,
3604
- :realm => sparams['realm'],
3605
- :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
3606
- :'digest-uri' => 'imap/' + sparams['realm'],
3607
- :qop => 'auth',
3608
- :maxbuf => 65535,
3609
- :nc => "%08d" % nc(sparams['nonce']),
3610
- :charset => sparams['charset'],
3611
- }
3612
-
3613
- response[:authzid] = @authname unless @authname.nil?
3614
-
3615
- # now, the real thing
3616
- a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
3617
-
3618
- a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
3619
- a1 << ':' + response[:authzid] unless response[:authzid].nil?
3620
-
3621
- a2 = "AUTHENTICATE:" + response[:'digest-uri']
3622
- a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
3623
-
3624
- response[:response] = Digest::MD5.hexdigest(
3625
- [
3626
- Digest::MD5.hexdigest(a1),
3627
- response.values_at(:nonce, :nc, :cnonce, :qop),
3628
- Digest::MD5.hexdigest(a2)
3629
- ].join(':')
3630
- )
3631
-
3632
- return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
3633
- when STAGE_TWO
3634
- @stage = nil
3635
- # if at the second stage, return an empty string
3636
- if challenge =~ /rspauth=/
3637
- return ''
3638
- else
3639
- raise ResponseParseError, challenge
3640
- end
3641
- else
3642
- raise ResponseParseError, challenge
3643
- end
3644
- end
3645
-
3646
- def initialize(user, password, authname = nil)
3647
- @user, @password, @authname = user, password, authname
3648
- @nc, @stage = {}, STAGE_ONE
3649
- end
3650
-
3651
- private
3652
-
3653
- STAGE_ONE = :stage_one
3654
- STAGE_TWO = :stage_two
3655
-
3656
- def nc(nonce)
3657
- if @nc.has_key? nonce
3658
- @nc[nonce] = @nc[nonce] + 1
3659
- else
3660
- @nc[nonce] = 1
3661
- end
3662
- return @nc[nonce]
3663
- end
3664
-
3665
- # some responses need quoting
3666
- def qdval(k, v)
3667
- return if k.nil? or v.nil?
3668
- if %w"username authzid realm nonce cnonce digest-uri qop".include? k
3669
- v.gsub!(/([\\"])/, "\\\1")
3670
- return '%s="%s"' % [k, v]
3671
- else
3672
- return '%s=%s' % [k, v]
3673
- end
3674
- end
3675
- end
3676
- add_authenticator "DIGEST-MD5", DigestMD5Authenticator
3677
-
3678
- # Superclass of IMAP errors.
3679
- class Error < StandardError
3680
- end
3681
-
3682
- # Error raised when data is in the incorrect format.
3683
- class DataFormatError < Error
3684
- end
3685
-
3686
- # Error raised when a response from the server is non-parseable.
3687
- class ResponseParseError < Error
3688
- end
3689
-
3690
- # Superclass of all errors used to encapsulate "fail" responses
3691
- # from the server.
3692
- class ResponseError < Error
3693
-
3694
- # The response that caused this error
3695
- attr_accessor :response
3696
-
3697
- def initialize(response)
3698
- @response = response
3699
-
3700
- super @response.data.text
3701
- end
3702
-
3703
- end
3704
-
3705
- # Error raised upon a "NO" response from the server, indicating
3706
- # that the client command could not be completed successfully.
3707
- class NoResponseError < ResponseError
3708
- end
3709
-
3710
- # Error raised upon a "BAD" response from the server, indicating
3711
- # that the client command violated the IMAP protocol, or an internal
3712
- # server failure has occurred.
3713
- class BadResponseError < ResponseError
3714
- end
3715
-
3716
- # Error raised upon a "BYE" response from the server, indicating
3717
- # that the client is not being allowed to login, or has been timed
3718
- # out due to inactivity.
3719
- class ByeResponseError < ResponseError
3720
- end
3721
-
3722
- RESPONSE_ERRORS = Hash.new(ResponseError)
3723
- RESPONSE_ERRORS["NO"] = NoResponseError
3724
- RESPONSE_ERRORS["BAD"] = BadResponseError
3725
-
3726
- # Error raised when too many flags are interned to symbols.
3727
- class FlagCountError < Error
3728
- end
3729
- end
3730
- end
1461
+ require_relative "imap/errors"
1462
+ require_relative "imap/command_data"
1463
+ require_relative "imap/data_encoding"
1464
+ require_relative "imap/flags"
1465
+ require_relative "imap/response_data"
1466
+ require_relative "imap/response_parser"
1467
+ require_relative "imap/authenticators"