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 +4 -4
- data/.travis.yml +3 -2
- data/lib/rubysl/net/imap/imap.rb +767 -412
- data/lib/rubysl/net/imap/version.rb +1 -1
- data/rubysl-net-imap.gemspec +3 -1
- metadata +18 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3308e84d0e8024e4ece77eec112a7334dd2dbb2a
|
4
|
+
data.tar.gz: 8b643041df74e584d02daff83af3890bd5c3b9cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e67f4cb84f0d12d8b957dde9604e0c5baea70d9da4249dfc29230b2e3deff38b77c38d1fd7366ed5d01c80835359ca14c6a0d3f7f8dc830c85ade0531b37873
|
7
|
+
data.tar.gz: efac146ad3c2c8b579f2454f39b342aaa7130c83ef6da4e72473b1cd8dfb5bd3e483900eed0c5f549e0812dff90d86e47f1ef113cb98eb17f90e5a0314f3f267
|
data/.travis.yml
CHANGED
data/lib/rubysl/net/imap/imap.rb
CHANGED
@@ -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
|
277
|
-
# Net::IMAP::CramMD5Authenticator
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
|
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(/&(
|
829
|
-
if $1
|
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
|
-
|
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(/(&)|
|
969
|
+
return s.gsub(/(&)|[^\x20-\x7e]+/) {
|
845
970
|
if $1
|
846
971
|
"&-"
|
847
972
|
else
|
848
|
-
base64 = [
|
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
|
-
# +
|
864
|
-
#
|
865
|
-
#
|
866
|
-
#
|
867
|
-
#
|
868
|
-
#
|
869
|
-
#
|
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,
|
1027
|
+
def initialize(host, port_or_options = {},
|
1028
|
+
usessl = false, certs = nil, verify = true)
|
882
1029
|
super()
|
883
1030
|
@host = host
|
884
|
-
|
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
|
890
|
-
|
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
|
-
@
|
914
|
-
@
|
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
|
1069
|
+
raise ByeResponseError, @greeting
|
923
1070
|
end
|
924
1071
|
|
925
1072
|
@client_thread = Thread.current
|
926
1073
|
@receiver_thread = Thread.start {
|
927
|
-
|
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
|
-
|
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
|
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
|
-
@
|
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
|
969
|
-
|
970
|
-
return
|
1120
|
+
@exception = ByeResponseError.new(resp)
|
1121
|
+
connection_closed = true
|
971
1122
|
end
|
972
1123
|
when ContinuationRequest
|
973
|
-
@
|
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
|
-
@
|
1133
|
+
@tagged_response_arrival.broadcast
|
1134
|
+
@continuation_request_arrival.broadcast
|
984
1135
|
end
|
985
1136
|
end
|
986
1137
|
end
|
987
1138
|
synchronize do
|
988
|
-
@
|
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
|
-
@
|
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
|
1156
|
+
raise NoResponseError, resp
|
1005
1157
|
when /\A(?:BAD)\z/ni
|
1006
|
-
raise BadResponseError, resp
|
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
|
-
|
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.
|
1124
|
-
|
1125
|
-
|
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
|
1256
|
-
|
1257
|
-
if
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
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
|
-
|
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::
|
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
|
-
|
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.
|
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
|
-
|
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
|
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("
|
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]
|
3211
|
-
k_opad[i]
|
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
|
-
|
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.
|
3636
|
+
return _noecho(&:gets).chomp
|
3284
3637
|
ensure
|
3285
|
-
|
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
|
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
|
-
|
3335
|
-
|
3336
|
-
imap = Net::IMAP.new($host, $port, $ssl)
|
3687
|
+
|
3688
|
+
imap = Net::IMAP.new($host, :port => $port, :ssl => $ssl)
|
3337
3689
|
begin
|
3338
|
-
|
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
|