rubysl-net-imap 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|