rubysl-net-imap 1.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 841f3c85cbbc3507e5e11e282dd6198c4a244533
4
- data.tar.gz: c3ad2c88d26bb75102d40e430c4d8aa71e8a34a9
3
+ metadata.gz: 3308e84d0e8024e4ece77eec112a7334dd2dbb2a
4
+ data.tar.gz: 8b643041df74e584d02daff83af3890bd5c3b9cc
5
5
  SHA512:
6
- metadata.gz: c8071c4a15639b9388e9d1eb9425b5b4fe088955e65a7c14499a56bd886bc34c9fb75c7d4c98a39cc22b9f496443ffc2dcc61439f7495a33b6ab4ec978f5a5db
7
- data.tar.gz: 67d571ea592de7a3640dd61e10b85ea6f5850f32060493c18edb0e21a625f0c079cf74a9787368502c50d4ff52e4160ee9554ff3be2eabcfefc24b9850813347
6
+ metadata.gz: 4e67f4cb84f0d12d8b957dde9604e0c5baea70d9da4249dfc29230b2e3deff38b77c38d1fd7366ed5d01c80835359ca14c6a0d3f7f8dc830c85ade0531b37873
7
+ data.tar.gz: efac146ad3c2c8b579f2454f39b342aaa7130c83ef6da4e72473b1cd8dfb5bd3e483900eed0c5f549e0812dff90d86e47f1ef113cb98eb17f90e5a0314f3f267
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
2
  before_install:
3
+ - rvm use $RVM --install --binary --fuzzy
3
4
  - gem update --system
4
5
  - gem --version
5
6
  - gem install rubysl-bundler
7
+ env:
8
+ - RVM=rbx-nightly-d21 RUBYLIB=lib
6
9
  script: bundle exec mspec spec
7
- rvm:
8
- - rbx-nightly-18mode
@@ -9,13 +9,14 @@
9
9
  # Documentation: Shugo Maeda, with RDoc conversion and overview by William
10
10
  # Webber.
11
11
  #
12
- # See Net::IMAP for documentation.
12
+ # See Net::IMAP for documentation.
13
13
  #
14
14
 
15
15
 
16
16
  require "socket"
17
17
  require "monitor"
18
18
  require "digest/md5"
19
+ require "strscan"
19
20
  begin
20
21
  require "openssl"
21
22
  rescue LoadError
@@ -44,12 +45,12 @@ module Net
44
45
  # read-only access) #examine(). Once the client has successfully
45
46
  # selected a mailbox, they enter _selected_ state, and that
46
47
  # mailbox becomes the _current_ mailbox, on which mail-item
47
- # related commands implicitly operate.
48
+ # related commands implicitly operate.
48
49
  #
49
50
  # Messages have two sorts of identifiers: message sequence
50
- # numbers, and UIDs.
51
+ # numbers, and UIDs.
51
52
  #
52
- # Message sequence numbers number messages within a mail box
53
+ # Message sequence numbers number messages within a mail box
53
54
  # from 1 up to the number of items in the mail box. If new
54
55
  # message arrives during a session, it receives a sequence
55
56
  # number equal to the new size of the mail box. If messages
@@ -57,7 +58,7 @@ module Net
57
58
  # sequence numbers "shuffled down" to fill the gaps.
58
59
  #
59
60
  # UIDs, on the other hand, are permanently guaranteed not to
60
- # identify another message within the same mailbox, even if
61
+ # identify another message within the same mailbox, even if
61
62
  # the existing message is deleted. UIDs are required to
62
63
  # be assigned in ascending (but not necessarily sequential)
63
64
  # order within a mailbox; this means that if a non-IMAP client
@@ -90,11 +91,11 @@ module Net
90
91
  # imap.store(message_id, "+FLAGS", [:Deleted])
91
92
  # end
92
93
  # imap.expunge
93
- #
94
+ #
94
95
  # == Thread Safety
95
96
  #
96
97
  # Net::IMAP supports concurrent threads. For example,
97
- #
98
+ #
98
99
  # imap = Net::IMAP.new("imap.foo.net", "imap2")
99
100
  # imap.authenticate("cram-md5", "bar", "password")
100
101
  # imap.select("inbox")
@@ -102,7 +103,7 @@ module Net
102
103
  # search_result = imap.search(["BODY", "hello"])
103
104
  # fetch_result = fetch_thread.value
104
105
  # imap.disconnect
105
- #
106
+ #
106
107
  # This script invokes the FETCH command and the SEARCH command concurrently.
107
108
  #
108
109
  # == Errors
@@ -112,9 +113,9 @@ module Net
112
113
  #
113
114
  # NO:: the attempted command could not be successfully completed. For
114
115
  # instance, the username/password used for logging in are incorrect;
115
- # the selected mailbox does not exists; etc.
116
+ # the selected mailbox does not exists; etc.
116
117
  #
117
- # BAD:: the request from the client does not follow the server's
118
+ # BAD:: the request from the client does not follow the server's
118
119
  # understanding of the IMAP protocol. This includes attempting
119
120
  # commands from the wrong client state; for instance, attempting
120
121
  # to perform a SEARCH command without having SELECTed a current
@@ -146,8 +147,8 @@ module Net
146
147
  #
147
148
  # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
148
149
  # is found to be in an incorrect format (for instance, when converting
149
- # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
150
- # thrown if a server response is non-parseable.
150
+ # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
151
+ # thrown if a server response is non-parseable.
151
152
  #
152
153
  #
153
154
  # == References
@@ -199,7 +200,7 @@ module Net
199
200
  #
200
201
  class IMAP
201
202
  include MonitorMixin
202
- if defined?(OpenSSL)
203
+ if defined?(OpenSSL::SSL)
203
204
  include OpenSSL
204
205
  include SSL
205
206
  end
@@ -269,12 +270,24 @@ module Net
269
270
  return @@debug = val
270
271
  end
271
272
 
273
+ # Returns the max number of flags interned to symbols.
274
+ def self.max_flag_count
275
+ return @@max_flag_count
276
+ end
277
+
278
+ # Sets the max number of flags interned to symbols.
279
+ def self.max_flag_count=(count)
280
+ @@max_flag_count = count
281
+ end
282
+
272
283
  # Adds an authenticator for Net::IMAP#authenticate. +auth_type+
273
284
  # is the type of authentication this authenticator supports
274
285
  # (for instance, "LOGIN"). The +authenticator+ is an object
275
286
  # which defines a process() method to handle authentication with
276
- # the server. See Net::IMAP::LoginAuthenticator and
277
- # Net::IMAP::CramMD5Authenticator for examples.
287
+ # the server. See Net::IMAP::LoginAuthenticator,
288
+ # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
289
+ # for examples.
290
+ #
278
291
  #
279
292
  # If +auth_type+ refers to an existing authenticator, it will be
280
293
  # replaced by the new one.
@@ -282,17 +295,44 @@ module Net
282
295
  @@authenticators[auth_type] = authenticator
283
296
  end
284
297
 
298
+ # The default port for IMAP connections, port 143
299
+ def self.default_port
300
+ return PORT
301
+ end
302
+
303
+ # The default port for IMAPS connections, port 993
304
+ def self.default_tls_port
305
+ return SSL_PORT
306
+ end
307
+
308
+ class << self
309
+ alias default_imap_port default_port
310
+ alias default_imaps_port default_tls_port
311
+ alias default_ssl_port default_tls_port
312
+ end
313
+
285
314
  # Disconnects from the server.
286
315
  def disconnect
287
316
  begin
288
- # try to call SSL::SSLSocket#io.
289
- @sock.io.shutdown
290
- rescue NoMethodError
291
- # @sock is not an SSL::SSLSocket.
292
- @sock.shutdown
317
+ begin
318
+ # try to call SSL::SSLSocket#io.
319
+ @sock.io.shutdown
320
+ rescue NoMethodError
321
+ # @sock is not an SSL::SSLSocket.
322
+ @sock.shutdown
323
+ end
324
+ rescue Errno::ENOTCONN
325
+ # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
326
+ rescue Exception => e
327
+ @receiver_thread.raise(e)
293
328
  end
294
329
  @receiver_thread.join
295
- @sock.close
330
+ synchronize do
331
+ unless @sock.closed?
332
+ @sock.close
333
+ end
334
+ end
335
+ raise e if e
296
336
  end
297
337
 
298
338
  # Returns true if disconnected from the server.
@@ -307,7 +347,7 @@ module Net
307
347
  #
308
348
  # Note that the Net::IMAP class does not modify its
309
349
  # behaviour according to the capabilities of the server;
310
- # it is up to the user of the class to ensure that
350
+ # it is up to the user of the class to ensure that
311
351
  # a certain capability is supported by a server before
312
352
  # using it.
313
353
  def capability
@@ -328,12 +368,27 @@ module Net
328
368
  send_command("LOGOUT")
329
369
  end
330
370
 
371
+ # Sends a STARTTLS command to start TLS session.
372
+ def starttls(options = {}, verify = true)
373
+ send_command("STARTTLS") do |resp|
374
+ if resp.kind_of?(TaggedResponse) && resp.name == "OK"
375
+ begin
376
+ # for backward compatibility
377
+ certs = options.to_str
378
+ options = create_ssl_params(certs, verify)
379
+ rescue NoMethodError
380
+ end
381
+ start_tls_session(options)
382
+ end
383
+ end
384
+ end
385
+
331
386
  # Sends an AUTHENTICATE command to authenticate the client.
332
387
  # The +auth_type+ parameter is a string that represents
333
388
  # the authentication mechanism to be used. Currently Net::IMAP
334
389
  # supports authentication mechanisms:
335
390
  #
336
- # LOGIN:: login using cleartext user and password.
391
+ # LOGIN:: login using cleartext user and password.
337
392
  # CRAM-MD5:: login with cleartext user and encrypted password
338
393
  # (see [RFC-2195] for a full description). This
339
394
  # mechanism requires that the server have the user's
@@ -381,7 +436,7 @@ module Net
381
436
  end
382
437
 
383
438
  # Sends a SELECT command to select a +mailbox+ so that messages
384
- # in the +mailbox+ can be accessed.
439
+ # in the +mailbox+ can be accessed.
385
440
  #
386
441
  # After you have selected a mailbox, you may retrieve the
387
442
  # number of items in that mailbox from @responses["EXISTS"][-1],
@@ -432,7 +487,7 @@ module Net
432
487
  # Sends a RENAME command to change the name of the +mailbox+ to
433
488
  # +newname+.
434
489
  #
435
- # A Net::IMAP::NoResponseError is raised if a mailbox with the
490
+ # A Net::IMAP::NoResponseError is raised if a mailbox with the
436
491
  # name +mailbox+ cannot be renamed to +newname+ for whatever
437
492
  # reason; for instance, because +mailbox+ does not exist, or
438
493
  # because there is already a mailbox with the name +newname+.
@@ -479,8 +534,8 @@ module Net
479
534
  # imap.create("foo/bar")
480
535
  # imap.create("foo/baz")
481
536
  # p imap.list("", "foo/%")
482
- # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
483
- # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
537
+ # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
538
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
484
539
  # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
485
540
  def list(refname, mailbox)
486
541
  synchronize do
@@ -489,6 +544,38 @@ module Net
489
544
  end
490
545
  end
491
546
 
547
+ # Sends a XLIST command, and returns a subset of names from
548
+ # the complete set of all names available to the client.
549
+ # +refname+ provides a context (for instance, a base directory
550
+ # in a directory-based mailbox hierarchy). +mailbox+ specifies
551
+ # a mailbox or (via wildcards) mailboxes under that context.
552
+ # Two wildcards may be used in +mailbox+: '*', which matches
553
+ # all characters *including* the hierarchy delimiter (for instance,
554
+ # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
555
+ # which matches all characters *except* the hierarchy delimiter.
556
+ #
557
+ # If +refname+ is empty, +mailbox+ is used directly to determine
558
+ # which mailboxes to match. If +mailbox+ is empty, the root
559
+ # name of +refname+ and the hierarchy delimiter are returned.
560
+ #
561
+ # The XLIST command is like the LIST command except that the flags
562
+ # returned refer to the function of the folder/mailbox, e.g. :Sent
563
+ #
564
+ # The return value is an array of +Net::IMAP::MailboxList+. For example:
565
+ #
566
+ # imap.create("foo/bar")
567
+ # imap.create("foo/baz")
568
+ # p imap.xlist("", "foo/%")
569
+ # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
570
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
571
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
572
+ def xlist(refname, mailbox)
573
+ synchronize do
574
+ send_command("XLIST", refname, mailbox)
575
+ return @responses.delete("XLIST")
576
+ end
577
+ end
578
+
492
579
  # Sends the GETQUOTAROOT command along with specified +mailbox+.
493
580
  # This command is generally available to both admin and user.
494
581
  # If mailbox exists, returns an array containing objects of
@@ -533,7 +620,7 @@ module Net
533
620
  # then that user will be stripped of any rights to that mailbox.
534
621
  # The IMAP ACL commands are described in [RFC-2086].
535
622
  def setacl(mailbox, user, rights)
536
- if rights.nil?
623
+ if rights.nil?
537
624
  send_command("SETACL", mailbox, user, "")
538
625
  else
539
626
  send_command("SETACL", mailbox, user, rights)
@@ -552,7 +639,7 @@ module Net
552
639
 
553
640
  # Sends a LSUB command, and returns a subset of names from the set
554
641
  # of names that the user has declared as being "active" or
555
- # "subscribed". +refname+ and +mailbox+ are interpreted as
642
+ # "subscribed". +refname+ and +mailbox+ are interpreted as
556
643
  # for #list().
557
644
  # The return value is an array of +Net::IMAP::MailboxList+.
558
645
  def lsub(refname, mailbox)
@@ -575,7 +662,7 @@ module Net
575
662
  # p imap.status("inbox", ["MESSAGES", "RECENT"])
576
663
  # #=> {"RECENT"=>0, "MESSAGES"=>44}
577
664
  #
578
- # A Net::IMAP::NoResponseError is raised if status values
665
+ # A Net::IMAP::NoResponseError is raised if status values
579
666
  # for +mailbox+ cannot be returned, for instance because it
580
667
  # does not exist.
581
668
  def status(mailbox, attr)
@@ -586,9 +673,9 @@ module Net
586
673
  end
587
674
 
588
675
  # Sends a APPEND command to append the +message+ to the end of
589
- # the +mailbox+. The optional +flags+ argument is an array of
676
+ # the +mailbox+. The optional +flags+ argument is an array of
590
677
  # flags to initially passing to the new message. The optional
591
- # +date_time+ argument specifies the creation time to assign to the
678
+ # +date_time+ argument specifies the creation time to assign to the
592
679
  # new message; it defaults to the current time.
593
680
  # For example:
594
681
  #
@@ -596,7 +683,7 @@ module Net
596
683
  # Subject: hello
597
684
  # From: shugo@ruby-lang.org
598
685
  # To: shugo@ruby-lang.org
599
- #
686
+ #
600
687
  # hello world
601
688
  # EOF
602
689
  #
@@ -615,7 +702,7 @@ module Net
615
702
 
616
703
  # Sends a CHECK command to request a checkpoint of the currently
617
704
  # selected mailbox. This performs implementation-specific
618
- # housekeeping, for instance, reconciling the mailbox's
705
+ # housekeeping, for instance, reconciling the mailbox's
619
706
  # in-memory and on-disk state.
620
707
  def check
621
708
  send_command("CHECK")
@@ -639,8 +726,8 @@ module Net
639
726
 
640
727
  # Sends a SEARCH command to search the mailbox for messages that
641
728
  # match the given searching criteria, and returns message sequence
642
- # numbers. +keys+ can either be a string holding the entire
643
- # search string, or a single-dimension array of search keywords and
729
+ # numbers. +keys+ can either be a string holding the entire
730
+ # search string, or a single-dimension array of search keywords and
644
731
  # arguments. The following are some common search criteria;
645
732
  # see [IMAP] section 6.4.4 for a full list.
646
733
  #
@@ -664,7 +751,7 @@ module Net
664
751
  #
665
752
  # OR <search-key> <search-key>:: "or" two search keys together.
666
753
  #
667
- # ON <date>:: messages with an internal date exactly equal to <date>,
754
+ # ON <date>:: messages with an internal date exactly equal to <date>,
668
755
  # which has a format similar to 8-Aug-2002.
669
756
  #
670
757
  # SINCE <date>:: messages with an internal date on or after <date>.
@@ -672,7 +759,7 @@ module Net
672
759
  # SUBJECT <string>:: messages with <string> in their subject.
673
760
  #
674
761
  # TO <string>:: messages with <string> in their TO field.
675
- #
762
+ #
676
763
  # For example:
677
764
  #
678
765
  # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
@@ -695,8 +782,8 @@ module Net
695
782
  # The return value is an array of Net::IMAP::FetchData. For example:
696
783
  #
697
784
  # p imap.fetch(6..8, "UID")
698
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
699
- # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
785
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
786
+ # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
700
787
  # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
701
788
  # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
702
789
  # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
@@ -719,9 +806,9 @@ module Net
719
806
  end
720
807
 
721
808
  # Sends a STORE command to alter data associated with messages
722
- # in the mailbox, in particular their flags. The +set+ parameter
723
- # is a number or an array of numbers or a Range object. Each number
724
- # is a message sequence number. +attr+ is the name of a data item
809
+ # in the mailbox, in particular their flags. The +set+ parameter
810
+ # is a number or an array of numbers or a Range object. Each number
811
+ # is a message sequence number. +attr+ is the name of a data item
725
812
  # to store: 'FLAGS' means to replace the message's flag list
726
813
  # with the provided one; '+FLAGS' means to add the provided flags;
727
814
  # and '-FLAGS' means to remove them. +flags+ is a list of flags.
@@ -729,8 +816,8 @@ module Net
729
816
  # The return value is an array of Net::IMAP::FetchData. For example:
730
817
  #
731
818
  # p imap.store(6..8, "+FLAGS", [:Deleted])
732
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
733
- # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
819
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
820
+ # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
734
821
  # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
735
822
  def store(set, attr, flags)
736
823
  return store_internal("STORE", set, attr, flags)
@@ -772,9 +859,9 @@ module Net
772
859
  return sort_internal("UID SORT", sort_keys, search_keys, charset)
773
860
  end
774
861
 
775
- # Adds a response handler. For example, to detect when
862
+ # Adds a response handler. For example, to detect when
776
863
  # the server sends us a new EXISTS response (which normally
777
- # indicates new messages being added to the mail box),
864
+ # indicates new messages being added to the mail box),
778
865
  # you could add the following handler after selecting the
779
866
  # mailbox.
780
867
  #
@@ -810,12 +897,55 @@ module Net
810
897
  return thread_internal("THREAD", algorithm, search_keys, charset)
811
898
  end
812
899
 
813
- # As for #thread(), but returns unique identifiers instead of
900
+ # As for #thread(), but returns unique identifiers instead of
814
901
  # message sequence numbers.
815
902
  def uid_thread(algorithm, search_keys, charset)
816
903
  return thread_internal("UID THREAD", algorithm, search_keys, charset)
817
904
  end
818
905
 
906
+ # Sends an IDLE command that waits for notifications of new or expunged
907
+ # messages. Yields responses from the server during the IDLE.
908
+ #
909
+ # Use #idle_done() to leave IDLE.
910
+ def idle(&response_handler)
911
+ raise LocalJumpError, "no block given" unless response_handler
912
+
913
+ response = nil
914
+
915
+ synchronize do
916
+ tag = Thread.current[:net_imap_tag] = generate_tag
917
+ put_string("#{tag} IDLE#{CRLF}")
918
+
919
+ begin
920
+ add_response_handler(response_handler)
921
+ @idle_done_cond = new_cond
922
+ @idle_done_cond.wait
923
+ @idle_done_cond = nil
924
+ if @receiver_thread_terminating
925
+ raise Net::IMAP::Error, "connection closed"
926
+ end
927
+ ensure
928
+ unless @receiver_thread_terminating
929
+ remove_response_handler(response_handler)
930
+ put_string("DONE#{CRLF}")
931
+ response = get_tagged_response(tag, "IDLE")
932
+ end
933
+ end
934
+ end
935
+
936
+ return response
937
+ end
938
+
939
+ # Leaves IDLE.
940
+ def idle_done
941
+ synchronize do
942
+ if @idle_done_cond.nil?
943
+ raise Net::IMAP::Error, "not during IDLE"
944
+ end
945
+ @idle_done_cond.signal
946
+ end
947
+ end
948
+
819
949
  # Decode a string from modified UTF-7 format to UTF-8.
820
950
  #
821
951
  # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
@@ -825,48 +955,64 @@ module Net
825
955
  # Net::IMAP does _not_ automatically encode and decode
826
956
  # mailbox names to and from utf7.
827
957
  def self.decode_utf7(s)
828
- return s.gsub(/&(.*?)-/n) {
829
- if $1.empty?
830
- "&"
958
+ return s.gsub(/&([^-]+)?-/n) {
959
+ if $1
960
+ ($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
831
961
  else
832
- base64 = $1.tr(",", "/")
833
- x = base64.length % 4
834
- if x > 0
835
- base64.concat("=" * (4 - x))
836
- end
837
- u16tou8(base64.unpack("m")[0])
962
+ "&"
838
963
  end
839
964
  }
840
965
  end
841
966
 
842
967
  # Encode a string from UTF-8 format to modified UTF-7.
843
968
  def self.encode_utf7(s)
844
- return s.gsub(/(&)|([^\x20-\x7e]+)/u) { |x|
969
+ return s.gsub(/(&)|[^\x20-\x7e]+/) {
845
970
  if $1
846
971
  "&-"
847
972
  else
848
- base64 = [u8tou16(x)].pack("m")
973
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m")
849
974
  "&" + base64.delete("=\n").tr("/", ",") + "-"
850
975
  end
851
- }
976
+ }.force_encoding("ASCII-8BIT")
977
+ end
978
+
979
+ # Formats +time+ as an IMAP-style date.
980
+ def self.format_date(time)
981
+ return time.strftime('%d-%b-%Y')
982
+ end
983
+
984
+ # Formats +time+ as an IMAP-style date-time.
985
+ def self.format_datetime(time)
986
+ return time.strftime('%d-%b-%Y %H:%M %z')
852
987
  end
853
988
 
854
989
  private
855
990
 
856
991
  CRLF = "\r\n" # :nodoc:
857
992
  PORT = 143 # :nodoc:
993
+ SSL_PORT = 993 # :nodoc:
858
994
 
859
995
  @@debug = false
860
996
  @@authenticators = {}
997
+ @@max_flag_count = 10000
861
998
 
999
+ # :call-seq:
1000
+ # Net::IMAP.new(host, options = {})
1001
+ #
862
1002
  # Creates a new Net::IMAP object and connects it to the specified
863
- # +port+ (143 by default) on the named +host+. If +usessl+ is true,
864
- # then an attempt will
865
- # be made to use SSL (now TLS) to connect to the server. For this
866
- # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
867
- # extensions need to be installed. The +certs+ parameter indicates
868
- # the path or file containing the CA cert of the server, and the
869
- # +verify+ parameter is for the OpenSSL verification callback.
1003
+ # +host+.
1004
+ #
1005
+ # +options+ is an option hash, each key of which is a symbol.
1006
+ #
1007
+ # The available options are:
1008
+ #
1009
+ # port:: port number (default value is 143 for imap, or 993 for imaps)
1010
+ # ssl:: if options[:ssl] is true, then an attempt will be made
1011
+ # to use SSL (now TLS) to connect to the server. For this to work
1012
+ # OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
1013
+ # be installed.
1014
+ # if options[:ssl] is a hash, it's passed to
1015
+ # OpenSSL::SSL::SSLContext#set_params as parameters.
870
1016
  #
871
1017
  # The most common errors are:
872
1018
  #
@@ -876,60 +1022,66 @@ module Net
876
1022
  # being dropped by an intervening firewall).
877
1023
  # Errno::ENETUNREACH:: there is no route to that network.
878
1024
  # SocketError:: hostname not known or other socket error.
879
- # Net::IMAP::ByeResponseError:: we connected to the host, but they
1025
+ # Net::IMAP::ByeResponseError:: we connected to the host, but they
880
1026
  # immediately said goodbye to us.
881
- def initialize(host, port = PORT, usessl = false, certs = nil, verify = false)
1027
+ def initialize(host, port_or_options = {},
1028
+ usessl = false, certs = nil, verify = true)
882
1029
  super()
883
1030
  @host = host
884
- @port = port
1031
+ begin
1032
+ options = port_or_options.to_hash
1033
+ rescue NoMethodError
1034
+ # for backward compatibility
1035
+ options = {}
1036
+ options[:port] = port_or_options
1037
+ if usessl
1038
+ options[:ssl] = create_ssl_params(certs, verify)
1039
+ end
1040
+ end
1041
+ @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
885
1042
  @tag_prefix = "RUBY"
886
1043
  @tagno = 0
887
1044
  @parser = ResponseParser.new
888
- @sock = TCPSocket.open(host, port)
889
- if usessl
890
- unless defined?(OpenSSL)
891
- raise "SSL extension not installed"
892
- end
1045
+ @sock = TCPSocket.open(@host, @port)
1046
+ if options[:ssl]
1047
+ start_tls_session(options[:ssl])
893
1048
  @usessl = true
894
-
895
- # verify the server.
896
- context = SSLContext::new()
897
- context.ca_file = certs if certs && FileTest::file?(certs)
898
- context.ca_path = certs if certs && FileTest::directory?(certs)
899
- context.verify_mode = VERIFY_PEER if verify
900
- if defined?(VerifyCallbackProc)
901
- context.verify_callback = VerifyCallbackProc
902
- end
903
- @sock = SSLSocket.new(@sock, context)
904
- @sock.sync_close = true
905
- @sock.connect # start ssl session.
906
- @sock.post_connection_check(@host) if verify
907
1049
  else
908
1050
  @usessl = false
909
1051
  end
910
1052
  @responses = Hash.new([].freeze)
911
1053
  @tagged_responses = {}
912
1054
  @response_handlers = []
913
- @response_arrival = new_cond
914
- @continuation_request = nil
1055
+ @tagged_response_arrival = new_cond
1056
+ @continuation_request_arrival = new_cond
1057
+ @idle_done_cond = nil
915
1058
  @logout_command_tag = nil
916
1059
  @debug_output_bol = true
917
1060
  @exception = nil
918
1061
 
919
1062
  @greeting = get_response
1063
+ if @greeting.nil?
1064
+ @sock.close
1065
+ raise Error, "connection closed"
1066
+ end
920
1067
  if @greeting.name == "BYE"
921
1068
  @sock.close
922
- raise ByeResponseError, @greeting.raw_data
1069
+ raise ByeResponseError, @greeting
923
1070
  end
924
1071
 
925
1072
  @client_thread = Thread.current
926
1073
  @receiver_thread = Thread.start {
927
- receive_responses
1074
+ begin
1075
+ receive_responses
1076
+ rescue Exception
1077
+ end
928
1078
  }
1079
+ @receiver_thread_terminating = false
929
1080
  end
930
1081
 
931
1082
  def receive_responses
932
- while true
1083
+ connection_closed = false
1084
+ until connection_closed
933
1085
  synchronize do
934
1086
  @exception = nil
935
1087
  end
@@ -937,7 +1089,7 @@ module Net
937
1089
  resp = get_response
938
1090
  rescue Exception => e
939
1091
  synchronize do
940
- @sock.close unless @sock.closed?
1092
+ @sock.close
941
1093
  @exception = e
942
1094
  end
943
1095
  break
@@ -953,7 +1105,7 @@ module Net
953
1105
  case resp
954
1106
  when TaggedResponse
955
1107
  @tagged_responses[resp.tag] = resp
956
- @response_arrival.broadcast
1108
+ @tagged_response_arrival.broadcast
957
1109
  if resp.tag == @logout_command_tag
958
1110
  return
959
1111
  end
@@ -965,13 +1117,11 @@ module Net
965
1117
  end
966
1118
  if resp.name == "BYE" && @logout_command_tag.nil?
967
1119
  @sock.close
968
- @exception = ByeResponseError.new(resp.raw_data)
969
- @response_arrival.broadcast
970
- return
1120
+ @exception = ByeResponseError.new(resp)
1121
+ connection_closed = true
971
1122
  end
972
1123
  when ContinuationRequest
973
- @continuation_request = resp
974
- @response_arrival.broadcast
1124
+ @continuation_request_arrival.signal
975
1125
  end
976
1126
  @response_handlers.each do |handler|
977
1127
  handler.call(resp)
@@ -980,30 +1130,32 @@ module Net
980
1130
  rescue Exception => e
981
1131
  @exception = e
982
1132
  synchronize do
983
- @response_arrival.broadcast
1133
+ @tagged_response_arrival.broadcast
1134
+ @continuation_request_arrival.broadcast
984
1135
  end
985
1136
  end
986
1137
  end
987
1138
  synchronize do
988
- @response_arrival.broadcast
1139
+ @receiver_thread_terminating = true
1140
+ @tagged_response_arrival.broadcast
1141
+ @continuation_request_arrival.broadcast
1142
+ if @idle_done_cond
1143
+ @idle_done_cond.signal
1144
+ end
989
1145
  end
990
1146
  end
991
1147
 
992
- def get_tagged_response(tag)
1148
+ def get_tagged_response(tag, cmd)
993
1149
  until @tagged_responses.key?(tag)
994
1150
  raise @exception if @exception
995
- @response_arrival.wait
1151
+ @tagged_response_arrival.wait
996
1152
  end
997
- return pick_up_tagged_response(tag)
998
- end
999
-
1000
- def pick_up_tagged_response(tag)
1001
1153
  resp = @tagged_responses.delete(tag)
1002
1154
  case resp.name
1003
1155
  when /\A(?:NO)\z/ni
1004
- raise NoResponseError, resp.data.text
1156
+ raise NoResponseError, resp
1005
1157
  when /\A(?:BAD)\z/ni
1006
- raise BadResponseError, resp.data.text
1158
+ raise BadResponseError, resp
1007
1159
  else
1008
1160
  return resp
1009
1161
  end
@@ -1038,7 +1190,10 @@ module Net
1038
1190
 
1039
1191
  def send_command(cmd, *args, &block)
1040
1192
  synchronize do
1041
- tag = Thread.current[:net_imap_tag] = generate_tag
1193
+ args.each do |i|
1194
+ validate_data(i)
1195
+ end
1196
+ tag = generate_tag
1042
1197
  put_string(tag + " " + cmd)
1043
1198
  args.each do |i|
1044
1199
  put_string(" ")
@@ -1052,7 +1207,7 @@ module Net
1052
1207
  add_response_handler(block)
1053
1208
  end
1054
1209
  begin
1055
- return get_tagged_response(tag)
1210
+ return get_tagged_response(tag, cmd)
1056
1211
  ensure
1057
1212
  if block
1058
1213
  remove_response_handler(block)
@@ -1065,7 +1220,7 @@ module Net
1065
1220
  @tagno += 1
1066
1221
  return format("%s%04d", @tag_prefix, @tagno)
1067
1222
  end
1068
-
1223
+
1069
1224
  def put_string(str)
1070
1225
  @sock.print(str)
1071
1226
  if @@debug
@@ -1081,6 +1236,25 @@ module Net
1081
1236
  end
1082
1237
  end
1083
1238
 
1239
+ def validate_data(data)
1240
+ case data
1241
+ when nil
1242
+ when String
1243
+ when Integer
1244
+ if data < 0 || data >= 4294967296
1245
+ raise DataFormatError, num.to_s
1246
+ end
1247
+ when Array
1248
+ data.each do |i|
1249
+ validate_data(i)
1250
+ end
1251
+ when Time
1252
+ when Symbol
1253
+ else
1254
+ data.validate
1255
+ end
1256
+ end
1257
+
1084
1258
  def send_data(data)
1085
1259
  case data
1086
1260
  when nil
@@ -1114,30 +1288,19 @@ module Net
1114
1288
  put_string(str)
1115
1289
  end
1116
1290
  end
1117
-
1291
+
1118
1292
  def send_quoted_string(str)
1119
1293
  put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
1120
1294
  end
1121
1295
 
1122
1296
  def send_literal(str)
1123
- put_string("{" + str.length.to_s + "}" + CRLF)
1124
- while @continuation_request.nil? &&
1125
- !@tagged_responses.key?(Thread.current[:net_imap_tag])
1126
- @response_arrival.wait
1127
- raise @exception if @exception
1128
- end
1129
- if @continuation_request.nil?
1130
- pick_up_tagged_response(Thread.current[:net_imap_tag])
1131
- raise ResponseError.new("expected continuation request")
1132
- end
1133
- @continuation_request = nil
1297
+ put_string("{" + str.bytesize.to_s + "}" + CRLF)
1298
+ @continuation_request_arrival.wait
1299
+ raise @exception if @exception
1134
1300
  put_string(str)
1135
1301
  end
1136
1302
 
1137
1303
  def send_number_data(num)
1138
- if num < 0 || num >= 4294967296
1139
- raise DataFormatError, num.to_s
1140
- end
1141
1304
  put_string(num.to_s)
1142
1305
  end
1143
1306
 
@@ -1252,130 +1415,56 @@ module Net
1252
1415
  end
1253
1416
  end
1254
1417
 
1255
- def self.u16tou8(s)
1256
- len = s.length
1257
- if len < 2
1258
- return ""
1259
- end
1260
- buf = ""
1261
- i = 0
1262
- while i < len
1263
- c = s[i] << 8 | s[i + 1]
1264
- i += 2
1265
- if c == 0xfeff
1266
- next
1267
- elsif c < 0x0080
1268
- buf.concat(c)
1269
- elsif c < 0x0800
1270
- b2 = c & 0x003f
1271
- b1 = c >> 6
1272
- buf.concat(b1 | 0xc0)
1273
- buf.concat(b2 | 0x80)
1274
- elsif c >= 0xdc00 && c < 0xe000
1275
- raise DataFormatError, "invalid surrogate detected"
1276
- elsif c >= 0xd800 && c < 0xdc00
1277
- if i + 2 > len
1278
- raise DataFormatError, "invalid surrogate detected"
1279
- end
1280
- low = s[i] << 8 | s[i + 1]
1281
- i += 2
1282
- if low < 0xdc00 || low > 0xdfff
1283
- raise DataFormatError, "invalid surrogate detected"
1284
- end
1285
- c = (((c & 0x03ff)) << 10 | (low & 0x03ff)) + 0x10000
1286
- b4 = c & 0x003f
1287
- b3 = (c >> 6) & 0x003f
1288
- b2 = (c >> 12) & 0x003f
1289
- b1 = c >> 18;
1290
- buf.concat(b1 | 0xf0)
1291
- buf.concat(b2 | 0x80)
1292
- buf.concat(b3 | 0x80)
1293
- buf.concat(b4 | 0x80)
1294
- else # 0x0800-0xffff
1295
- b3 = c & 0x003f
1296
- b2 = (c >> 6) & 0x003f
1297
- b1 = c >> 12
1298
- buf.concat(b1 | 0xe0)
1299
- buf.concat(b2 | 0x80)
1300
- buf.concat(b3 | 0x80)
1301
- end
1302
- end
1303
- return buf
1304
- end
1305
- private_class_method :u16tou8
1306
-
1307
- def self.u8tou16(s)
1308
- len = s.length
1309
- buf = ""
1310
- i = 0
1311
- while i < len
1312
- c = s[i]
1313
- if (c & 0x80) == 0
1314
- buf.concat(0x00)
1315
- buf.concat(c)
1316
- i += 1
1317
- elsif (c & 0xe0) == 0xc0 &&
1318
- len >= 2 &&
1319
- (s[i + 1] & 0xc0) == 0x80
1320
- if c == 0xc0 || c == 0xc1
1321
- raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
1322
- end
1323
- u = ((c & 0x1f) << 6) | (s[i + 1] & 0x3f)
1324
- buf.concat(u >> 8)
1325
- buf.concat(u & 0x00ff)
1326
- i += 2
1327
- elsif (c & 0xf0) == 0xe0 &&
1328
- i + 2 < len &&
1329
- (s[i + 1] & 0xc0) == 0x80 &&
1330
- (s[i + 2] & 0xc0) == 0x80
1331
- if c == 0xe0 && s[i + 1] < 0xa0
1332
- raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
1333
- end
1334
- u = ((c & 0x0f) << 12) | ((s[i + 1] & 0x3f) << 6) | (s[i + 2] & 0x3f)
1335
- # surrogate chars
1336
- if u >= 0xd800 && u <= 0xdfff
1337
- raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
1338
- end
1339
- buf.concat(u >> 8)
1340
- buf.concat(u & 0x00ff)
1341
- i += 3
1342
- elsif (c & 0xf8) == 0xf0 &&
1343
- i + 3 < len &&
1344
- (s[i + 1] & 0xc0) == 0x80 &&
1345
- (s[i + 2] & 0xc0) == 0x80 &&
1346
- (s[i + 3] & 0xc0) == 0x80
1347
- if c == 0xf0 && s[i + 1] < 0x90
1348
- raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
1349
- end
1350
- u = ((c & 0x07) << 18) | ((s[i + 1] & 0x3f) << 12) |
1351
- ((s[i + 2] & 0x3f) << 6) | (s[i + 3] & 0x3f)
1352
- if u < 0x10000
1353
- buf.concat(u >> 8)
1354
- buf.concat(u & 0x00ff)
1355
- elsif u < 0x110000
1356
- high = ((u - 0x10000) >> 10) | 0xd800
1357
- low = (u & 0x03ff) | 0xdc00
1358
- buf.concat(high >> 8)
1359
- buf.concat(high & 0x00ff)
1360
- buf.concat(low >> 8)
1361
- buf.concat(low & 0x00ff)
1362
- else
1363
- raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
1364
- end
1365
- i += 4
1366
- else
1367
- raise DataFormatError, format("illegal UTF-8 sequence (%02x)", c)
1418
+ def create_ssl_params(certs = nil, verify = true)
1419
+ params = {}
1420
+ if certs
1421
+ if File.file?(certs)
1422
+ params[:ca_file] = certs
1423
+ elsif File.directory?(certs)
1424
+ params[:ca_path] = certs
1368
1425
  end
1369
1426
  end
1370
- return buf
1427
+ if verify
1428
+ params[:verify_mode] = VERIFY_PEER
1429
+ else
1430
+ params[:verify_mode] = VERIFY_NONE
1431
+ end
1432
+ return params
1433
+ end
1434
+
1435
+ def start_tls_session(params = {})
1436
+ unless defined?(OpenSSL::SSL)
1437
+ raise "SSL extension not installed"
1438
+ end
1439
+ if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
1440
+ raise RuntimeError, "already using SSL"
1441
+ end
1442
+ begin
1443
+ params = params.to_hash
1444
+ rescue NoMethodError
1445
+ params = {}
1446
+ end
1447
+ context = SSLContext.new
1448
+ context.set_params(params)
1449
+ if defined?(VerifyCallbackProc)
1450
+ context.verify_callback = VerifyCallbackProc
1451
+ end
1452
+ @sock = SSLSocket.new(@sock, context)
1453
+ @sock.sync_close = true
1454
+ @sock.connect
1455
+ if context.verify_mode != VERIFY_NONE
1456
+ @sock.post_connection_check(@host)
1457
+ end
1371
1458
  end
1372
- private_class_method :u8tou16
1373
1459
 
1374
1460
  class RawData # :nodoc:
1375
1461
  def send_data(imap)
1376
1462
  imap.send(:put_string, @data)
1377
1463
  end
1378
1464
 
1465
+ def validate
1466
+ end
1467
+
1379
1468
  private
1380
1469
 
1381
1470
  def initialize(data)
@@ -1388,6 +1477,9 @@ module Net
1388
1477
  imap.send(:put_string, @data)
1389
1478
  end
1390
1479
 
1480
+ def validate
1481
+ end
1482
+
1391
1483
  private
1392
1484
 
1393
1485
  def initialize(data)
@@ -1400,6 +1492,9 @@ module Net
1400
1492
  imap.send(:send_quoted_string, @data)
1401
1493
  end
1402
1494
 
1495
+ def validate
1496
+ end
1497
+
1403
1498
  private
1404
1499
 
1405
1500
  def initialize(data)
@@ -1412,6 +1507,9 @@ module Net
1412
1507
  imap.send(:send_literal, @data)
1413
1508
  end
1414
1509
 
1510
+ def validate
1511
+ end
1512
+
1415
1513
  private
1416
1514
 
1417
1515
  def initialize(data)
@@ -1424,6 +1522,10 @@ module Net
1424
1522
  imap.send(:put_string, format_internal(@data))
1425
1523
  end
1426
1524
 
1525
+ def validate
1526
+ validate_internal(@data)
1527
+ end
1528
+
1427
1529
  private
1428
1530
 
1429
1531
  def initialize(data)
@@ -1435,7 +1537,6 @@ module Net
1435
1537
  when "*"
1436
1538
  return data
1437
1539
  when Integer
1438
- ensure_nz_number(data)
1439
1540
  if data == -1
1440
1541
  return "*"
1441
1542
  else
@@ -1449,6 +1550,23 @@ module Net
1449
1550
  when ThreadMember
1450
1551
  return data.seqno.to_s +
1451
1552
  ":" + data.children.collect {|i| format_internal(i).join(",")}
1553
+ end
1554
+ end
1555
+
1556
+ def validate_internal(data)
1557
+ case data
1558
+ when "*"
1559
+ when Integer
1560
+ ensure_nz_number(data)
1561
+ when Range
1562
+ when Array
1563
+ data.each do |i|
1564
+ validate_internal(i)
1565
+ end
1566
+ when ThreadMember
1567
+ data.children.each do |i|
1568
+ validate_internal(i)
1569
+ end
1452
1570
  else
1453
1571
  raise DataFormatError, data.inspect
1454
1572
  end
@@ -1464,109 +1582,109 @@ module Net
1464
1582
  end
1465
1583
 
1466
1584
  # Net::IMAP::ContinuationRequest represents command continuation requests.
1467
- #
1585
+ #
1468
1586
  # The command continuation request response is indicated by a "+" token
1469
1587
  # instead of a tag. This form of response indicates that the server is
1470
1588
  # ready to accept the continuation of a command from the client. The
1471
1589
  # remainder of this response is a line of text.
1472
- #
1590
+ #
1473
1591
  # continue_req ::= "+" SPACE (resp_text / base64)
1474
- #
1592
+ #
1475
1593
  # ==== Fields:
1476
- #
1594
+ #
1477
1595
  # data:: Returns the data (Net::IMAP::ResponseText).
1478
- #
1596
+ #
1479
1597
  # raw_data:: Returns the raw data string.
1480
1598
  ContinuationRequest = Struct.new(:data, :raw_data)
1481
1599
 
1482
1600
  # Net::IMAP::UntaggedResponse represents untagged responses.
1483
- #
1601
+ #
1484
1602
  # Data transmitted by the server to the client and status responses
1485
1603
  # that do not indicate command completion are prefixed with the token
1486
1604
  # "*", and are called untagged responses.
1487
- #
1605
+ #
1488
1606
  # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
1489
1607
  # mailbox_data / message_data / capability_data)
1490
- #
1608
+ #
1491
1609
  # ==== Fields:
1492
- #
1610
+ #
1493
1611
  # name:: Returns the name such as "FLAGS", "LIST", "FETCH"....
1494
- #
1612
+ #
1495
1613
  # data:: Returns the data such as an array of flag symbols,
1496
1614
  # a ((<Net::IMAP::MailboxList>)) object....
1497
- #
1615
+ #
1498
1616
  # raw_data:: Returns the raw data string.
1499
1617
  UntaggedResponse = Struct.new(:name, :data, :raw_data)
1500
-
1618
+
1501
1619
  # Net::IMAP::TaggedResponse represents tagged responses.
1502
- #
1620
+ #
1503
1621
  # The server completion result response indicates the success or
1504
1622
  # failure of the operation. It is tagged with the same tag as the
1505
1623
  # client command which began the operation.
1506
- #
1624
+ #
1507
1625
  # response_tagged ::= tag SPACE resp_cond_state CRLF
1508
- #
1626
+ #
1509
1627
  # tag ::= 1*<any ATOM_CHAR except "+">
1510
- #
1628
+ #
1511
1629
  # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1512
- #
1630
+ #
1513
1631
  # ==== Fields:
1514
- #
1632
+ #
1515
1633
  # tag:: Returns the tag.
1516
- #
1634
+ #
1517
1635
  # name:: Returns the name. the name is one of "OK", "NO", "BAD".
1518
- #
1636
+ #
1519
1637
  # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
1520
- #
1638
+ #
1521
1639
  # raw_data:: Returns the raw data string.
1522
1640
  #
1523
1641
  TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
1524
-
1642
+
1525
1643
  # Net::IMAP::ResponseText represents texts of responses.
1526
1644
  # The text may be prefixed by the response code.
1527
- #
1645
+ #
1528
1646
  # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
1529
1647
  # ;; text SHOULD NOT begin with "[" or "="
1530
- #
1648
+ #
1531
1649
  # ==== Fields:
1532
- #
1650
+ #
1533
1651
  # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
1534
- #
1652
+ #
1535
1653
  # text:: Returns the text.
1536
- #
1654
+ #
1537
1655
  ResponseText = Struct.new(:code, :text)
1538
1656
 
1539
- #
1657
+ #
1540
1658
  # Net::IMAP::ResponseCode represents response codes.
1541
- #
1659
+ #
1542
1660
  # resp_text_code ::= "ALERT" / "PARSE" /
1543
1661
  # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
1544
1662
  # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1545
1663
  # "UIDVALIDITY" SPACE nz_number /
1546
1664
  # "UNSEEN" SPACE nz_number /
1547
1665
  # atom [SPACE 1*<any TEXT_CHAR except "]">]
1548
- #
1666
+ #
1549
1667
  # ==== Fields:
1550
- #
1668
+ #
1551
1669
  # name:: Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY"....
1552
- #
1670
+ #
1553
1671
  # data:: Returns the data if it exists.
1554
1672
  #
1555
1673
  ResponseCode = Struct.new(:name, :data)
1556
1674
 
1557
1675
  # Net::IMAP::MailboxList represents contents of the LIST response.
1558
- #
1676
+ #
1559
1677
  # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
1560
1678
  # "\Noselect" / "\Unmarked" / flag_extension) ")"
1561
1679
  # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
1562
- #
1680
+ #
1563
1681
  # ==== Fields:
1564
- #
1682
+ #
1565
1683
  # attr:: Returns the name attributes. Each name attribute is a symbol
1566
1684
  # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
1567
- #
1685
+ #
1568
1686
  # delim:: Returns the hierarchy delimiter
1569
- #
1687
+ #
1570
1688
  # name:: Returns the mailbox name.
1571
1689
  #
1572
1690
  MailboxList = Struct.new(:attr, :delim, :name)
@@ -1575,78 +1693,78 @@ module Net
1575
1693
  # This object can also be a response to GETQUOTAROOT. In the syntax
1576
1694
  # specification below, the delimiter used with the "#" construct is a
1577
1695
  # single space (SPACE).
1578
- #
1696
+ #
1579
1697
  # quota_list ::= "(" #quota_resource ")"
1580
- #
1698
+ #
1581
1699
  # quota_resource ::= atom SPACE number SPACE number
1582
- #
1700
+ #
1583
1701
  # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
1584
- #
1702
+ #
1585
1703
  # ==== Fields:
1586
- #
1704
+ #
1587
1705
  # mailbox:: The mailbox with the associated quota.
1588
- #
1706
+ #
1589
1707
  # usage:: Current storage usage of mailbox.
1590
- #
1708
+ #
1591
1709
  # quota:: Quota limit imposed on mailbox.
1592
1710
  #
1593
1711
  MailboxQuota = Struct.new(:mailbox, :usage, :quota)
1594
1712
 
1595
1713
  # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
1596
1714
  # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
1597
- #
1715
+ #
1598
1716
  # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
1599
- #
1717
+ #
1600
1718
  # ==== Fields:
1601
- #
1719
+ #
1602
1720
  # mailbox:: The mailbox with the associated quota.
1603
- #
1721
+ #
1604
1722
  # quotaroots:: Zero or more quotaroots that effect the quota on the
1605
1723
  # specified mailbox.
1606
1724
  #
1607
1725
  MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
1608
1726
 
1609
1727
  # Net::IMAP::MailboxACLItem represents response from GETACL.
1610
- #
1728
+ #
1611
1729
  # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
1612
- #
1730
+ #
1613
1731
  # identifier ::= astring
1614
- #
1732
+ #
1615
1733
  # rights ::= astring
1616
- #
1734
+ #
1617
1735
  # ==== Fields:
1618
- #
1736
+ #
1619
1737
  # user:: Login name that has certain rights to the mailbox
1620
1738
  # that was specified with the getacl command.
1621
- #
1739
+ #
1622
1740
  # rights:: The access rights the indicated user has to the
1623
1741
  # mailbox.
1624
1742
  #
1625
- MailboxACLItem = Struct.new(:user, :rights)
1743
+ MailboxACLItem = Struct.new(:user, :rights, :mailbox)
1626
1744
 
1627
1745
  # Net::IMAP::StatusData represents contents of the STATUS response.
1628
- #
1746
+ #
1629
1747
  # ==== Fields:
1630
- #
1748
+ #
1631
1749
  # mailbox:: Returns the mailbox name.
1632
- #
1750
+ #
1633
1751
  # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
1634
1752
  # "UIDVALIDITY", "UNSEEN". Each value is a number.
1635
- #
1753
+ #
1636
1754
  StatusData = Struct.new(:mailbox, :attr)
1637
1755
 
1638
1756
  # Net::IMAP::FetchData represents contents of the FETCH response.
1639
- #
1757
+ #
1640
1758
  # ==== Fields:
1641
- #
1759
+ #
1642
1760
  # seqno:: Returns the message sequence number.
1643
1761
  # (Note: not the unique identifier, even for the UID command response.)
1644
- #
1762
+ #
1645
1763
  # attr:: Returns a hash. Each key is a data item name, and each value is
1646
1764
  # its value.
1647
- #
1765
+ #
1648
1766
  # The current data items are:
1649
- #
1767
+ #
1650
1768
  # [BODY]
1651
1769
  # A form of BODYSTRUCTURE without extension data.
1652
1770
  # [BODY[<section>]<<origin_octet>>]
@@ -1673,67 +1791,67 @@ module Net
1673
1791
  # Equivalent to BODY[TEXT].
1674
1792
  # [UID]
1675
1793
  # A number expressing the unique identifier of the message.
1676
- #
1794
+ #
1677
1795
  FetchData = Struct.new(:seqno, :attr)
1678
1796
 
1679
1797
  # Net::IMAP::Envelope represents envelope structures of messages.
1680
- #
1798
+ #
1681
1799
  # ==== Fields:
1682
- #
1800
+ #
1683
1801
  # date:: Returns a string that represents the date.
1684
- #
1802
+ #
1685
1803
  # subject:: Returns a string that represents the subject.
1686
- #
1804
+ #
1687
1805
  # from:: Returns an array of Net::IMAP::Address that represents the from.
1688
- #
1806
+ #
1689
1807
  # sender:: Returns an array of Net::IMAP::Address that represents the sender.
1690
- #
1808
+ #
1691
1809
  # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
1692
- #
1810
+ #
1693
1811
  # to:: Returns an array of Net::IMAP::Address that represents the to.
1694
- #
1812
+ #
1695
1813
  # cc:: Returns an array of Net::IMAP::Address that represents the cc.
1696
- #
1814
+ #
1697
1815
  # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
1698
- #
1816
+ #
1699
1817
  # in_reply_to:: Returns a string that represents the in-reply-to.
1700
- #
1818
+ #
1701
1819
  # message_id:: Returns a string that represents the message-id.
1702
- #
1820
+ #
1703
1821
  Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
1704
1822
  :to, :cc, :bcc, :in_reply_to, :message_id)
1705
1823
 
1706
- #
1824
+ #
1707
1825
  # Net::IMAP::Address represents electronic mail addresses.
1708
- #
1826
+ #
1709
1827
  # ==== Fields:
1710
- #
1828
+ #
1711
1829
  # name:: Returns the phrase from [RFC-822] mailbox.
1712
- #
1830
+ #
1713
1831
  # route:: Returns the route from [RFC-822] route-addr.
1714
- #
1832
+ #
1715
1833
  # mailbox:: nil indicates end of [RFC-822] group.
1716
1834
  # If non-nil and host is nil, returns [RFC-822] group name.
1717
1835
  # Otherwise, returns [RFC-822] local-part
1718
- #
1836
+ #
1719
1837
  # host:: nil indicates [RFC-822] group syntax.
1720
1838
  # Otherwise, returns [RFC-822] domain name.
1721
1839
  #
1722
1840
  Address = Struct.new(:name, :route, :mailbox, :host)
1723
1841
 
1724
- #
1842
+ #
1725
1843
  # Net::IMAP::ContentDisposition represents Content-Disposition fields.
1726
- #
1844
+ #
1727
1845
  # ==== Fields:
1728
- #
1846
+ #
1729
1847
  # dsp_type:: Returns the disposition type.
1730
- #
1848
+ #
1731
1849
  # param:: Returns a hash that represents parameters of the Content-Disposition
1732
1850
  # field.
1733
- #
1851
+ #
1734
1852
  ContentDisposition = Struct.new(:dsp_type, :param)
1735
1853
 
1736
- # Net::IMAP::ThreadMember represents a thread-node returned
1854
+ # Net::IMAP::ThreadMember represents a thread-node returned
1737
1855
  # by Net::IMAP#thread
1738
1856
  #
1739
1857
  # ==== Fields:
@@ -1746,37 +1864,37 @@ module Net
1746
1864
  ThreadMember = Struct.new(:seqno, :children)
1747
1865
 
1748
1866
  # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
1749
- #
1867
+ #
1750
1868
  # ==== Fields:
1751
- #
1869
+ #
1752
1870
  # media_type:: Returns the content media type name as defined in [MIME-IMB].
1753
- #
1871
+ #
1754
1872
  # subtype:: Returns the content subtype name as defined in [MIME-IMB].
1755
- #
1873
+ #
1756
1874
  # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
1757
- #
1875
+ #
1758
1876
  # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
1759
- #
1877
+ #
1760
1878
  # description:: Returns a string giving the content description as defined in
1761
1879
  # [MIME-IMB].
1762
- #
1880
+ #
1763
1881
  # encoding:: Returns a string giving the content transfer encoding as defined in
1764
1882
  # [MIME-IMB].
1765
- #
1883
+ #
1766
1884
  # size:: Returns a number giving the size of the body in octets.
1767
- #
1885
+ #
1768
1886
  # md5:: Returns a string giving the body MD5 value as defined in [MD5].
1769
- #
1887
+ #
1770
1888
  # disposition:: Returns a Net::IMAP::ContentDisposition object giving
1771
1889
  # the content disposition.
1772
- #
1890
+ #
1773
1891
  # language:: Returns a string or an array of strings giving the body
1774
1892
  # language value as defined in [LANGUAGE-TAGS].
1775
- #
1893
+ #
1776
1894
  # extension:: Returns extension data.
1777
- #
1895
+ #
1778
1896
  # multipart?:: Returns false.
1779
- #
1897
+ #
1780
1898
  class BodyTypeBasic < Struct.new(:media_type, :subtype,
1781
1899
  :param, :content_id,
1782
1900
  :description, :encoding, :size,
@@ -1787,7 +1905,7 @@ module Net
1787
1905
  end
1788
1906
 
1789
1907
  # Obsolete: use +subtype+ instead. Calling this will
1790
- # generate a warning message to +stderr+, then return
1908
+ # generate a warning message to +stderr+, then return
1791
1909
  # the value of +subtype+.
1792
1910
  def media_subtype
1793
1911
  $stderr.printf("warning: media_subtype is obsolete.\n")
@@ -1797,13 +1915,13 @@ module Net
1797
1915
  end
1798
1916
 
1799
1917
  # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
1800
- #
1918
+ #
1801
1919
  # ==== Fields:
1802
- #
1920
+ #
1803
1921
  # lines:: Returns the size of the body in text lines.
1804
- #
1922
+ #
1805
1923
  # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
1806
- #
1924
+ #
1807
1925
  class BodyTypeText < Struct.new(:media_type, :subtype,
1808
1926
  :param, :content_id,
1809
1927
  :description, :encoding, :size,
@@ -1815,7 +1933,7 @@ module Net
1815
1933
  end
1816
1934
 
1817
1935
  # Obsolete: use +subtype+ instead. Calling this will
1818
- # generate a warning message to +stderr+, then return
1936
+ # generate a warning message to +stderr+, then return
1819
1937
  # the value of +subtype+.
1820
1938
  def media_subtype
1821
1939
  $stderr.printf("warning: media_subtype is obsolete.\n")
@@ -1825,13 +1943,13 @@ module Net
1825
1943
  end
1826
1944
 
1827
1945
  # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
1828
- #
1946
+ #
1829
1947
  # ==== Fields:
1830
- #
1948
+ #
1831
1949
  # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
1832
- #
1950
+ #
1833
1951
  # body:: Returns an object giving the body structure.
1834
- #
1952
+ #
1835
1953
  # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
1836
1954
  #
1837
1955
  class BodyTypeMessage < Struct.new(:media_type, :subtype,
@@ -1845,7 +1963,7 @@ module Net
1845
1963
  end
1846
1964
 
1847
1965
  # Obsolete: use +subtype+ instead. Calling this will
1848
- # generate a warning message to +stderr+, then return
1966
+ # generate a warning message to +stderr+, then return
1849
1967
  # the value of +subtype+.
1850
1968
  def media_subtype
1851
1969
  $stderr.printf("warning: media_subtype is obsolete.\n")
@@ -1854,29 +1972,49 @@ module Net
1854
1972
  end
1855
1973
  end
1856
1974
 
1857
- # Net::IMAP::BodyTypeMultipart represents multipart body structures
1975
+ # Net::IMAP::BodyTypeAttachment represents attachment body structures
1976
+ # of messages.
1977
+ #
1978
+ # ==== Fields:
1979
+ #
1980
+ # media_type:: Returns the content media type name.
1981
+ #
1982
+ # subtype:: Returns +nil+.
1983
+ #
1984
+ # param:: Returns a hash that represents parameters.
1985
+ #
1986
+ # multipart?:: Returns false.
1987
+ #
1988
+ class BodyTypeAttachment < Struct.new(:media_type, :subtype,
1989
+ :param)
1990
+ def multipart?
1991
+ return false
1992
+ end
1993
+ end
1994
+
1995
+ # Net::IMAP::BodyTypeMultipart represents multipart body structures
1858
1996
  # of messages.
1859
- #
1997
+ #
1860
1998
  # ==== Fields:
1861
- #
1999
+ #
1862
2000
  # media_type:: Returns the content media type name as defined in [MIME-IMB].
1863
- #
2001
+ #
1864
2002
  # subtype:: Returns the content subtype name as defined in [MIME-IMB].
1865
- #
2003
+ #
1866
2004
  # parts:: Returns multiple parts.
1867
- #
2005
+ #
1868
2006
  # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
1869
- #
2007
+ #
1870
2008
  # disposition:: Returns a Net::IMAP::ContentDisposition object giving
1871
2009
  # the content disposition.
1872
- #
2010
+ #
1873
2011
  # language:: Returns a string or an array of strings giving the body
1874
2012
  # language value as defined in [LANGUAGE-TAGS].
1875
- #
2013
+ #
1876
2014
  # extension:: Returns extension data.
1877
- #
2015
+ #
1878
2016
  # multipart?:: Returns true.
1879
- #
2017
+ #
1880
2018
  class BodyTypeMultipart < Struct.new(:media_type, :subtype,
1881
2019
  :parts,
1882
2020
  :param, :disposition, :language,
@@ -1886,7 +2024,7 @@ module Net
1886
2024
  end
1887
2025
 
1888
2026
  # Obsolete: use +subtype+ instead. Calling this will
1889
- # generate a warning message to +stderr+, then return
2027
+ # generate a warning message to +stderr+, then return
1890
2028
  # the value of +subtype+.
1891
2029
  def media_subtype
1892
2030
  $stderr.printf("warning: media_subtype is obsolete.\n")
@@ -1895,7 +2033,23 @@ module Net
1895
2033
  end
1896
2034
  end
1897
2035
 
2036
+ class BodyTypeExtension < Struct.new(:media_type, :subtype,
2037
+ :params, :content_id,
2038
+ :description, :encoding, :size)
2039
+ def multipart?
2040
+ return false
2041
+ end
2042
+ end
2043
+
1898
2044
  class ResponseParser # :nodoc:
2045
+ def initialize
2046
+ @str = nil
2047
+ @pos = nil
2048
+ @lex_state = nil
2049
+ @token = nil
2050
+ @flag_symbols = {}
2051
+ end
2052
+
1899
2053
  def parse(str)
1900
2054
  @str = str
1901
2055
  @pos = 0
@@ -1932,9 +2086,9 @@ module Net
1932
2086
 
1933
2087
  BEG_REGEXP = /\G(?:\
1934
2088
  (?# 1: SPACE )( +)|\
1935
- (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
1936
- (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
1937
- (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
2089
+ (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*#{'"'}\\\[\]+])|\
2090
+ (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*#{'"'}\\\[\]+])|\
2091
+ (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*#{'"'}\\\[\]+]+)|\
1938
2092
  (?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
1939
2093
  (?# 6: LPAR )(\()|\
1940
2094
  (?# 7: RPAR )(\))|\
@@ -2002,7 +2156,7 @@ module Net
2002
2156
  return response_cond
2003
2157
  when /\A(?:FLAGS)\z/ni
2004
2158
  return flags_response
2005
- when /\A(?:LIST|LSUB)\z/ni
2159
+ when /\A(?:LIST|LSUB|XLIST)\z/ni
2006
2160
  return list_response
2007
2161
  when /\A(?:QUOTA)\z/ni
2008
2162
  return getquota_response
@@ -2053,12 +2207,12 @@ module Net
2053
2207
  when "FETCH"
2054
2208
  shift_token
2055
2209
  match(T_SPACE)
2056
- data = FetchData.new(n, msg_att)
2210
+ data = FetchData.new(n, msg_att(n))
2057
2211
  return UntaggedResponse.new(name, data, @str)
2058
2212
  end
2059
2213
  end
2060
2214
 
2061
- def msg_att
2215
+ def msg_att(n)
2062
2216
  match(T_LPAR)
2063
2217
  attr = {}
2064
2218
  while true
@@ -2069,7 +2223,7 @@ module Net
2069
2223
  break
2070
2224
  when T_SPACE
2071
2225
  shift_token
2072
- token = lookahead
2226
+ next
2073
2227
  end
2074
2228
  case token.value
2075
2229
  when /\A(?:ENVELOPE)\z/ni
@@ -2087,7 +2241,7 @@ module Net
2087
2241
  when /\A(?:UID)\z/ni
2088
2242
  name, val = uid_data
2089
2243
  else
2090
- parse_error("unknown attribute `%s'", token.value)
2244
+ parse_error("unknown attribute `%s' for {%d}", token.value, n)
2091
2245
  end
2092
2246
  attr[name] = val
2093
2247
  end
@@ -2154,6 +2308,11 @@ module Net
2154
2308
  def rfc822_text
2155
2309
  token = match(T_ATOM)
2156
2310
  name = token.value.upcase
2311
+ token = lookahead
2312
+ if token.symbol == T_LBRA
2313
+ shift_token
2314
+ match(T_RBRA)
2315
+ end
2157
2316
  match(T_SPACE)
2158
2317
  return name, nstring
2159
2318
  end
@@ -2211,6 +2370,8 @@ module Net
2211
2370
  return body_type_text
2212
2371
  when /\A(?:MESSAGE)\z/ni
2213
2372
  return body_type_msg
2373
+ when /\A(?:ATTACHMENT)\z/ni
2374
+ return body_type_attachment
2214
2375
  else
2215
2376
  return body_type_basic
2216
2377
  end
@@ -2249,6 +2410,29 @@ module Net
2249
2410
  mtype, msubtype = media_type
2250
2411
  match(T_SPACE)
2251
2412
  param, content_id, desc, enc, size = body_fields
2413
+
2414
+ token = lookahead
2415
+ if token.symbol == T_RPAR
2416
+ # If this is not message/rfc822, we shouldn't apply the RFC822
2417
+ # spec to it. We should handle anything other than
2418
+ # message/rfc822 using multipart extension data [rfc3501] (i.e.
2419
+ # the data itself won't be returned, we would have to retrieve it
2420
+ # with BODYSTRUCTURE instead of with BODY
2421
+
2422
+ # Also, sometimes a message/rfc822 is included as a large
2423
+ # attachment instead of having all of the other details
2424
+ # (e.g. attaching a .eml file to an email)
2425
+ if msubtype == "RFC822"
2426
+ return BodyTypeMessage.new(mtype, msubtype, param, content_id,
2427
+ desc, enc, size, nil, nil, nil, nil,
2428
+ nil, nil, nil)
2429
+ else
2430
+ return BodyTypeExtension.new(mtype, msubtype,
2431
+ param, content_id,
2432
+ desc, enc, size)
2433
+ end
2434
+ end
2435
+
2252
2436
  match(T_SPACE)
2253
2437
  env = envelope
2254
2438
  match(T_SPACE)
@@ -2263,6 +2447,13 @@ module Net
2263
2447
  md5, disposition, language, extension)
2264
2448
  end
2265
2449
 
2450
+ def body_type_attachment
2451
+ mtype = case_insensitive_string
2452
+ match(T_SPACE)
2453
+ param = body_fld_param
2454
+ return BodyTypeAttachment.new(mtype, nil, param)
2455
+ end
2456
+
2266
2457
  def body_type_mpart
2267
2458
  parts = []
2268
2459
  while true
@@ -2283,6 +2474,10 @@ module Net
2283
2474
 
2284
2475
  def media_type
2285
2476
  mtype = case_insensitive_string
2477
+ token = lookahead
2478
+ if token.symbol != T_SPACE
2479
+ return mtype, nil
2480
+ end
2286
2481
  match(T_SPACE)
2287
2482
  msubtype = case_insensitive_string
2288
2483
  return mtype, msubtype
@@ -2502,7 +2697,7 @@ module Net
2502
2697
  return '""'
2503
2698
  when /[\x80-\xff\r\n]/n
2504
2699
  # literal
2505
- return "{" + str.length.to_s + "}" + CRLF + str
2700
+ return "{" + str.bytesize.to_s + "}" + CRLF + str
2506
2701
  when /[(){ \x00-\x1f\x7f%*"\\]/n
2507
2702
  # quoted string
2508
2703
  return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
@@ -2627,8 +2822,7 @@ module Net
2627
2822
  user = astring
2628
2823
  match(T_SPACE)
2629
2824
  rights = astring
2630
- ##XXX data.push([user, rights])
2631
- data.push(MailboxACLItem.new(user, rights))
2825
+ data.push(MailboxACLItem.new(user, rights, mailbox))
2632
2826
  end
2633
2827
  end
2634
2828
  return UntaggedResponse.new(name, data, @str)
@@ -2648,8 +2842,9 @@ module Net
2648
2842
  break
2649
2843
  when T_SPACE
2650
2844
  shift_token
2845
+ else
2846
+ data.push(number)
2651
2847
  end
2652
- data.push(number)
2653
2848
  end
2654
2849
  else
2655
2850
  data = []
@@ -2687,35 +2882,35 @@ module Net
2687
2882
  def thread_branch(token)
2688
2883
  rootmember = nil
2689
2884
  lastmember = nil
2690
-
2885
+
2691
2886
  while true
2692
2887
  shift_token # ignore first T_LPAR
2693
2888
  token = lookahead
2694
-
2889
+
2695
2890
  case token.symbol
2696
2891
  when T_NUMBER
2697
2892
  # new member
2698
2893
  newmember = ThreadMember.new(number, [])
2699
2894
  if rootmember.nil?
2700
2895
  rootmember = newmember
2701
- else
2896
+ else
2702
2897
  lastmember.children << newmember
2703
- end
2898
+ end
2704
2899
  lastmember = newmember
2705
- when T_SPACE
2706
- # do nothing
2900
+ when T_SPACE
2901
+ # do nothing
2707
2902
  when T_LPAR
2708
2903
  if rootmember.nil?
2709
2904
  # dummy member
2710
2905
  lastmember = rootmember = ThreadMember.new(nil, [])
2711
- end
2712
-
2906
+ end
2907
+
2713
2908
  lastmember.children << thread_branch(token)
2714
2909
  when T_RPAR
2715
- break
2716
- end
2910
+ break
2911
+ end
2717
2912
  end
2718
-
2913
+
2719
2914
  return rootmember
2720
2915
  end
2721
2916
 
@@ -2758,6 +2953,7 @@ module Net
2758
2953
  break
2759
2954
  when T_SPACE
2760
2955
  shift_token
2956
+ next
2761
2957
  end
2762
2958
  data.push(atom.upcase)
2763
2959
  end
@@ -2906,7 +3102,16 @@ module Net
2906
3102
  if @str.index(/\(([^)]*)\)/ni, @pos)
2907
3103
  @pos = $~.end(0)
2908
3104
  return $1.scan(FLAG_REGEXP).collect { |flag, atom|
2909
- atom || flag.capitalize.intern
3105
+ if atom
3106
+ atom
3107
+ else
3108
+ symbol = flag.capitalize.untaint.intern
3109
+ @flag_symbols[symbol] = true
3110
+ if @flag_symbols.length > IMAP.max_flag_count
3111
+ raise FlagCountError, "number of flag symbols exceeded"
3112
+ end
3113
+ symbol
3114
+ end
2910
3115
  }
2911
3116
  else
2912
3117
  parse_error("invalid flag list")
@@ -3140,7 +3345,7 @@ module Net
3140
3345
  parse_error("unknown token - %s", $&.dump)
3141
3346
  end
3142
3347
  else
3143
- parse_error("illegal @lex_state - %s", @lex_state.inspect)
3348
+ parse_error("invalid @lex_state - %s", @lex_state.inspect)
3144
3349
  end
3145
3350
  end
3146
3351
 
@@ -3184,6 +3389,22 @@ module Net
3184
3389
  end
3185
3390
  add_authenticator "LOGIN", LoginAuthenticator
3186
3391
 
3392
+ # Authenticator for the "PLAIN" authentication type. See
3393
+ # #authenticate().
3394
+ class PlainAuthenticator
3395
+ def process(data)
3396
+ return "\0#{@user}\0#{@password}"
3397
+ end
3398
+
3399
+ private
3400
+
3401
+ def initialize(user, password)
3402
+ @user = user
3403
+ @password = password
3404
+ end
3405
+ end
3406
+ add_authenticator "PLAIN", PlainAuthenticator
3407
+
3187
3408
  # Authenticator for the "CRAM-MD5" authentication type. See
3188
3409
  # #authenticate().
3189
3410
  class CramMD5Authenticator
@@ -3207,8 +3428,8 @@ module Net
3207
3428
  k_ipad = key + "\0" * (64 - key.length)
3208
3429
  k_opad = key + "\0" * (64 - key.length)
3209
3430
  for i in 0..63
3210
- k_ipad[i] ^= 0x36
3211
- k_opad[i] ^= 0x5c
3431
+ k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
3432
+ k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
3212
3433
  end
3213
3434
 
3214
3435
  digest = Digest::MD5.digest(k_ipad + text)
@@ -3218,6 +3439,106 @@ module Net
3218
3439
  end
3219
3440
  add_authenticator "CRAM-MD5", CramMD5Authenticator
3220
3441
 
3442
+ # Authenticator for the "DIGEST-MD5" authentication type. See
3443
+ # #authenticate().
3444
+ class DigestMD5Authenticator
3445
+ def process(challenge)
3446
+ case @stage
3447
+ when STAGE_ONE
3448
+ @stage = STAGE_TWO
3449
+ sparams = {}
3450
+ c = StringScanner.new(challenge)
3451
+ while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
3452
+ k, v = c[1], c[2]
3453
+ if v =~ /^"(.*)"$/
3454
+ v = $1
3455
+ if v =~ /,/
3456
+ v = v.split(',')
3457
+ end
3458
+ end
3459
+ sparams[k] = v
3460
+ end
3461
+
3462
+ raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
3463
+ raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
3464
+
3465
+ response = {
3466
+ :nonce => sparams['nonce'],
3467
+ :username => @user,
3468
+ :realm => sparams['realm'],
3469
+ :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
3470
+ :'digest-uri' => 'imap/' + sparams['realm'],
3471
+ :qop => 'auth',
3472
+ :maxbuf => 65535,
3473
+ :nc => "%08d" % nc(sparams['nonce']),
3474
+ :charset => sparams['charset'],
3475
+ }
3476
+
3477
+ response[:authzid] = @authname unless @authname.nil?
3478
+
3479
+ # now, the real thing
3480
+ a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
3481
+
3482
+ a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
3483
+ a1 << ':' + response[:authzid] unless response[:authzid].nil?
3484
+
3485
+ a2 = "AUTHENTICATE:" + response[:'digest-uri']
3486
+ a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
3487
+
3488
+ response[:response] = Digest::MD5.hexdigest(
3489
+ [
3490
+ Digest::MD5.hexdigest(a1),
3491
+ response.values_at(:nonce, :nc, :cnonce, :qop),
3492
+ Digest::MD5.hexdigest(a2)
3493
+ ].join(':')
3494
+ )
3495
+
3496
+ return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
3497
+ when STAGE_TWO
3498
+ @stage = nil
3499
+ # if at the second stage, return an empty string
3500
+ if challenge =~ /rspauth=/
3501
+ return ''
3502
+ else
3503
+ raise ResponseParseError, challenge
3504
+ end
3505
+ else
3506
+ raise ResponseParseError, challenge
3507
+ end
3508
+ end
3509
+
3510
+ def initialize(user, password, authname = nil)
3511
+ @user, @password, @authname = user, password, authname
3512
+ @nc, @stage = {}, STAGE_ONE
3513
+ end
3514
+
3515
+ private
3516
+
3517
+ STAGE_ONE = :stage_one
3518
+ STAGE_TWO = :stage_two
3519
+
3520
+ def nc(nonce)
3521
+ if @nc.has_key? nonce
3522
+ @nc[nonce] = @nc[nonce] + 1
3523
+ else
3524
+ @nc[nonce] = 1
3525
+ end
3526
+ return @nc[nonce]
3527
+ end
3528
+
3529
+ # some responses need quoting
3530
+ def qdval(k, v)
3531
+ return if k.nil? or v.nil?
3532
+ if %w"username authzid realm nonce cnonce digest-uri qop".include? k
3533
+ v.gsub!(/([\\"])/, "\\\1")
3534
+ return '%s="%s"' % [k, v]
3535
+ else
3536
+ return '%s=%s' % [k, v]
3537
+ end
3538
+ end
3539
+ end
3540
+ add_authenticator "DIGEST-MD5", DigestMD5Authenticator
3541
+
3221
3542
  # Superclass of IMAP errors.
3222
3543
  class Error < StandardError
3223
3544
  end
@@ -3233,6 +3554,16 @@ module Net
3233
3554
  # Superclass of all errors used to encapsulate "fail" responses
3234
3555
  # from the server.
3235
3556
  class ResponseError < Error
3557
+
3558
+ # The response that caused this error
3559
+ attr_accessor :response
3560
+
3561
+ def initialize(response)
3562
+ @response = response
3563
+
3564
+ super @response.data.text
3565
+ end
3566
+
3236
3567
  end
3237
3568
 
3238
3569
  # Error raised upon a "NO" response from the server, indicating
@@ -3246,11 +3577,15 @@ module Net
3246
3577
  class BadResponseError < ResponseError
3247
3578
  end
3248
3579
 
3249
- # Error raised upon a "BYE" response from the server, indicating
3580
+ # Error raised upon a "BYE" response from the server, indicating
3250
3581
  # that the client is not being allowed to login, or has been timed
3251
3582
  # out due to inactivity.
3252
3583
  class ByeResponseError < ResponseError
3253
3584
  end
3585
+
3586
+ # Error raised when too many flags are interned to symbols.
3587
+ class FlagCountError < Error
3588
+ end
3254
3589
  end
3255
3590
  end
3256
3591
 
@@ -3263,27 +3598,44 @@ if __FILE__ == $0
3263
3598
  $user = ENV["USER"] || ENV["LOGNAME"]
3264
3599
  $auth = "login"
3265
3600
  $ssl = false
3601
+ $starttls = false
3266
3602
 
3267
3603
  def usage
3268
- $stderr.print <<EOF
3604
+ <<EOF
3269
3605
  usage: #{$0} [options] <host>
3270
3606
 
3271
3607
  --help print this message
3272
3608
  --port=PORT specifies port
3273
3609
  --user=USER specifies user
3274
3610
  --auth=AUTH specifies auth type
3611
+ --starttls use starttls
3275
3612
  --ssl use ssl
3276
3613
  EOF
3277
3614
  end
3278
3615
 
3616
+ begin
3617
+ require 'io/console'
3618
+ rescue LoadError
3619
+ def _noecho(&block)
3620
+ system("stty", "-echo")
3621
+ begin
3622
+ yield STDIN
3623
+ ensure
3624
+ system("stty", "echo")
3625
+ end
3626
+ end
3627
+ else
3628
+ def _noecho(&block)
3629
+ STDIN.noecho(&block)
3630
+ end
3631
+ end
3632
+
3279
3633
  def get_password
3280
3634
  print "password: "
3281
- system("stty", "-echo")
3282
3635
  begin
3283
- return gets.chop
3636
+ return _noecho(&:gets).chomp
3284
3637
  ensure
3285
- system("stty", "echo")
3286
- print "\n"
3638
+ puts
3287
3639
  end
3288
3640
  end
3289
3641
 
@@ -3302,6 +3654,7 @@ EOF
3302
3654
  ['--port', GetoptLong::REQUIRED_ARGUMENT],
3303
3655
  ['--user', GetoptLong::REQUIRED_ARGUMENT],
3304
3656
  ['--auth', GetoptLong::REQUIRED_ARGUMENT],
3657
+ ['--starttls', GetoptLong::NO_ARGUMENT],
3305
3658
  ['--ssl', GetoptLong::NO_ARGUMENT])
3306
3659
  begin
3307
3660
  parser.each_option do |name, arg|
@@ -3314,28 +3667,30 @@ EOF
3314
3667
  $auth = arg
3315
3668
  when "--ssl"
3316
3669
  $ssl = true
3670
+ when "--starttls"
3671
+ $starttls = true
3317
3672
  when "--debug"
3318
3673
  Net::IMAP.debug = true
3319
3674
  when "--help"
3320
3675
  usage
3321
- exit(1)
3676
+ exit
3322
3677
  end
3323
3678
  end
3324
3679
  rescue
3325
- usage
3326
- exit(1)
3680
+ abort usage
3327
3681
  end
3328
3682
 
3329
3683
  $host = ARGV.shift
3330
3684
  unless $host
3331
- usage
3332
- exit(1)
3685
+ abort usage
3333
3686
  end
3334
- $port ||= $ssl ? 993 : 143
3335
-
3336
- imap = Net::IMAP.new($host, $port, $ssl)
3687
+
3688
+ imap = Net::IMAP.new($host, :port => $port, :ssl => $ssl)
3337
3689
  begin
3338
- password = get_password
3690
+ imap.starttls if $starttls
3691
+ class << password = method(:get_password)
3692
+ alias to_str call
3693
+ end
3339
3694
  imap.authenticate($auth, $user, password)
3340
3695
  while true
3341
3696
  cmd, *args = get_command