net-imap 0.1.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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"