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