net-imap 0.2.1 → 0.2.2
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 +3 -5
- data/.gitignore +1 -0
- data/README.md +1 -1
- data/lib/net/imap.rb +149 -2702
- data/lib/net/imap/authenticators.rb +44 -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/command_data.rb +301 -0
- data/lib/net/imap/data_encoding.rb +47 -0
- data/lib/net/imap/flags.rb +76 -0
- data/lib/net/imap/response_data.rb +527 -0
- data/lib/net/imap/response_parser.rb +1530 -0
- metadata +12 -3
- data/Gemfile.lock +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ce3f04b1c49832a6e2b36c2b9b9a7a79223c297896aa7daeb7bf7fd0b54a7f6
|
4
|
+
data.tar.gz: 2ce12d9e40a529f638a4314f6c61ebf343a5341225fa4c2442800be339a32dea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8933d844581528c0b969e4e5256821a41cf7838f1fed493205c2447e3fd5578df6f9f1eb75f24cb14c8a227b3908218224c161da3a61a26dab2b66d819738d03
|
7
|
+
data.tar.gz: 9b37cf6d107aa5e4a5bd650faac5f4fb4f2a4d2283068bd1b39793db2a5a3b1e6dcc86e5b850efc4e008ddbe0a0632d367ad0fb473e1e7158633c09726dc7f25
|
data/.github/workflows/test.yml
CHANGED
@@ -7,7 +7,7 @@ jobs:
|
|
7
7
|
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
|
8
8
|
strategy:
|
9
9
|
matrix:
|
10
|
-
ruby: [ 3.0, 2.7, 2.5, head ]
|
10
|
+
ruby: [ '3.0', 2.7, 2.5, head ]
|
11
11
|
os: [ ubuntu-latest, macos-latest ]
|
12
12
|
experimental: [false]
|
13
13
|
include:
|
@@ -20,14 +20,12 @@ jobs:
|
|
20
20
|
runs-on: ${{ matrix.os }}
|
21
21
|
continue-on-error: ${{ matrix.experimental }}
|
22
22
|
steps:
|
23
|
-
- uses: actions/checkout@
|
23
|
+
- uses: actions/checkout@v2
|
24
24
|
- name: Set up Ruby
|
25
25
|
uses: ruby/setup-ruby@v1
|
26
26
|
with:
|
27
27
|
ruby-version: ${{ matrix.ruby }}
|
28
28
|
- name: Install dependencies
|
29
|
-
run:
|
30
|
-
gem install bundler --no-document
|
31
|
-
bundle install
|
29
|
+
run: bundle install
|
32
30
|
- name: Run test
|
33
31
|
run: rake test
|
data/.gitignore
CHANGED
data/README.md
CHANGED
data/lib/net/imap.rb
CHANGED
@@ -16,24 +16,29 @@
|
|
16
16
|
|
17
17
|
require "socket"
|
18
18
|
require "monitor"
|
19
|
-
require "digest/md5"
|
20
|
-
require "strscan"
|
21
19
|
require 'net/protocol'
|
22
20
|
begin
|
23
21
|
require "openssl"
|
24
22
|
rescue LoadError
|
25
23
|
end
|
26
24
|
|
25
|
+
require_relative "imap/command_data"
|
26
|
+
require_relative "imap/data_encoding"
|
27
|
+
require_relative "imap/flags"
|
28
|
+
require_relative "imap/response_data"
|
29
|
+
require_relative "imap/response_parser"
|
30
|
+
|
27
31
|
module Net
|
28
32
|
|
29
33
|
#
|
30
34
|
# Net::IMAP implements Internet Message Access Protocol (IMAP) client
|
31
|
-
# functionality. The protocol is described in
|
35
|
+
# functionality. The protocol is described in
|
36
|
+
# [IMAP[https://tools.ietf.org/html/rfc3501]].
|
32
37
|
#
|
33
38
|
# == IMAP Overview
|
34
39
|
#
|
35
|
-
# An IMAP client connects to a server, and then authenticates
|
36
|
-
# itself using either #authenticate
|
40
|
+
# An \IMAP client connects to a server, and then authenticates
|
41
|
+
# itself using either #authenticate or #login. Having
|
37
42
|
# authenticated itself, there is a range of commands
|
38
43
|
# available to it. Most work with mailboxes, which may be
|
39
44
|
# arranged in an hierarchical namespace, and each of which
|
@@ -43,8 +48,8 @@ module Net
|
|
43
48
|
# within a hierarchy of directories.
|
44
49
|
#
|
45
50
|
# To work on the messages within a mailbox, the client must
|
46
|
-
# first select that mailbox, using either #select
|
47
|
-
# read-only access) #examine
|
51
|
+
# first select that mailbox, using either #select or (for
|
52
|
+
# read-only access) #examine. Once the client has successfully
|
48
53
|
# selected a mailbox, they enter _selected_ state, and that
|
49
54
|
# mailbox becomes the _current_ mailbox, on which mail-item
|
50
55
|
# related commands implicitly operate.
|
@@ -65,7 +70,7 @@ module Net
|
|
65
70
|
# be assigned in ascending (but not necessarily sequential)
|
66
71
|
# order within a mailbox; this means that if a non-IMAP client
|
67
72
|
# rearranges the order of mailitems within a mailbox, the
|
68
|
-
# UIDs have to be reassigned. An IMAP client thus cannot
|
73
|
+
# UIDs have to be reassigned. An \IMAP client thus cannot
|
69
74
|
# rearrange message orders.
|
70
75
|
#
|
71
76
|
# == Examples of Usage
|
@@ -155,40 +160,61 @@ module Net
|
|
155
160
|
#
|
156
161
|
# == References
|
157
162
|
#
|
158
|
-
# [[IMAP]]
|
159
|
-
# M.
|
160
|
-
# RFC
|
163
|
+
# [[IMAP[https://tools.ietf.org/html/rfc3501]]]
|
164
|
+
# Crispin, M. "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
|
165
|
+
# RFC-3501[https://tools.ietf.org/html/rfc3501], March 2003. (Note:
|
166
|
+
# obsoletes RFC-2060[https://tools.ietf.org/html/rfc2060], December 1996.)
|
167
|
+
#
|
168
|
+
# [[LANGUAGE-TAGS[https://tools.ietf.org/html/rfc1766]]]
|
169
|
+
# Phillips, A. and Davis, M. "Tags for Identifying Languages",
|
170
|
+
# RFC-5646[https://tools.ietf.org/html/rfc5646], September 2009.
|
171
|
+
# (Note: obsoletes
|
172
|
+
# RFC-3066[https://tools.ietf.org/html/rfc3066], January 2001,
|
173
|
+
# RFC-4646[https://tools.ietf.org/html/rfc4646], September 2006, and
|
174
|
+
# RFC-1766[https://tools.ietf.org/html/rfc1766], March 1995.)
|
161
175
|
#
|
162
|
-
# [[
|
163
|
-
#
|
164
|
-
#
|
176
|
+
# [[MD5[https://tools.ietf.org/html/rfc1864]]]
|
177
|
+
# Myers, J. and M. Rose, "The Content-MD5 Header Field",
|
178
|
+
# RFC-1864[https://tools.ietf.org/html/rfc1864], October 1995.
|
165
179
|
#
|
166
|
-
# [[
|
167
|
-
#
|
168
|
-
#
|
180
|
+
# [[MIME-IMB[https://tools.ietf.org/html/rfc2045]]]
|
181
|
+
# Freed, N. and N. Borenstein, "MIME (Multipurpose Internet
|
182
|
+
# Mail Extensions) Part One: Format of Internet Message Bodies",
|
183
|
+
# RFC-2045[https://tools.ietf.org/html/rfc2045], November 1996.
|
169
184
|
#
|
170
|
-
# [[
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
185
|
+
# [[RFC-5322[https://tools.ietf.org/html/rfc5322]]]
|
186
|
+
# Resnick, P., "Internet Message Format",
|
187
|
+
# RFC-5322[https://tools.ietf.org/html/rfc5322], October 2008.
|
188
|
+
# (Note: obsoletes
|
189
|
+
# RFC-2822[https://tools.ietf.org/html/rfc2822], April 2001, and
|
190
|
+
# RFC-822[https://tools.ietf.org/html/rfc822], August 1982.)
|
174
191
|
#
|
175
|
-
# [[
|
176
|
-
#
|
177
|
-
#
|
192
|
+
# [[EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]]
|
193
|
+
# Myers, J., "IMAP4 QUOTA extension",
|
194
|
+
# RFC-2087[https://tools.ietf.org/html/rfc2087], January 1997.
|
178
195
|
#
|
179
|
-
# [[
|
180
|
-
#
|
196
|
+
# [[EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]]
|
197
|
+
# Gahrns, M. and Newman, C., "IMAP4 Namespace",
|
198
|
+
# RFC-2342[https://tools.ietf.org/html/rfc2342], May 1998.
|
181
199
|
#
|
182
|
-
# [[
|
183
|
-
#
|
200
|
+
# [[EXT-ID[https://tools.ietf.org/html/rfc2971]]]
|
201
|
+
# Showalter, T., "IMAP4 ID extension",
|
202
|
+
# RFC-2971[https://tools.ietf.org/html/rfc2971], October 2000.
|
184
203
|
#
|
185
|
-
# [[
|
186
|
-
#
|
187
|
-
#
|
204
|
+
# [[EXT-ACL[https://tools.ietf.org/html/rfc4314]]]
|
205
|
+
# Melnikov, A., "IMAP4 ACL extension",
|
206
|
+
# RFC-4314[https://tools.ietf.org/html/rfc4314], December 2005. (Note:
|
207
|
+
# obsoletes RFC-2086[https://tools.ietf.org/html/rfc2086], January 1997.)
|
188
208
|
#
|
189
|
-
# [[SORT-THREAD
|
190
|
-
# Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT
|
191
|
-
# Extensions",
|
209
|
+
# [[EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]]]
|
210
|
+
# Crispin, M. and Muchison, K., "INTERNET MESSAGE ACCESS PROTOCOL - SORT
|
211
|
+
# and THREAD Extensions", RFC-5256[https://tools.ietf.org/html/rfc5256],
|
212
|
+
# June 2008.
|
213
|
+
#
|
214
|
+
# [[EXT-MOVE[https://tools.ietf.org/html/rfc6851]]]
|
215
|
+
# Gulbrandsen, A. and Freed, N., "Internet Message Access Protocol (\IMAP) -
|
216
|
+
# MOVE Extension", RFC-6851[https://tools.ietf.org/html/rfc6851], January
|
217
|
+
# 2013.
|
192
218
|
#
|
193
219
|
# [[OSSL]]
|
194
220
|
# http://www.openssl.org
|
@@ -196,12 +222,12 @@ module Net
|
|
196
222
|
# [[RSSL]]
|
197
223
|
# http://savannah.gnu.org/projects/rubypki
|
198
224
|
#
|
199
|
-
# [[UTF7]]
|
225
|
+
# [[UTF7[https://tools.ietf.org/html/rfc2152]]]
|
200
226
|
# Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
|
201
|
-
# Unicode", RFC
|
227
|
+
# Unicode", RFC-2152[https://tools.ietf.org/html/rfc2152], May 1997.
|
202
228
|
#
|
203
229
|
class IMAP < Protocol
|
204
|
-
VERSION = "0.2.
|
230
|
+
VERSION = "0.2.2"
|
205
231
|
|
206
232
|
include MonitorMixin
|
207
233
|
if defined?(OpenSSL::SSL)
|
@@ -235,43 +261,6 @@ module Net
|
|
235
261
|
# The thread to receive exceptions.
|
236
262
|
attr_accessor :client_thread
|
237
263
|
|
238
|
-
# Flag indicating a message has been seen.
|
239
|
-
SEEN = :Seen
|
240
|
-
|
241
|
-
# Flag indicating a message has been answered.
|
242
|
-
ANSWERED = :Answered
|
243
|
-
|
244
|
-
# Flag indicating a message has been flagged for special or urgent
|
245
|
-
# attention.
|
246
|
-
FLAGGED = :Flagged
|
247
|
-
|
248
|
-
# Flag indicating a message has been marked for deletion. This
|
249
|
-
# will occur when the mailbox is closed or expunged.
|
250
|
-
DELETED = :Deleted
|
251
|
-
|
252
|
-
# Flag indicating a message is only a draft or work-in-progress version.
|
253
|
-
DRAFT = :Draft
|
254
|
-
|
255
|
-
# Flag indicating that the message is "recent," meaning that this
|
256
|
-
# session is the first session in which the client has been notified
|
257
|
-
# of this message.
|
258
|
-
RECENT = :Recent
|
259
|
-
|
260
|
-
# Flag indicating that a mailbox context name cannot contain
|
261
|
-
# children.
|
262
|
-
NOINFERIORS = :Noinferiors
|
263
|
-
|
264
|
-
# Flag indicating that a mailbox is not selected.
|
265
|
-
NOSELECT = :Noselect
|
266
|
-
|
267
|
-
# Flag indicating that a mailbox has been marked "interesting" by
|
268
|
-
# the server; this commonly indicates that the mailbox contains
|
269
|
-
# new messages.
|
270
|
-
MARKED = :Marked
|
271
|
-
|
272
|
-
# Flag indicating that the mailbox does not contains new messages.
|
273
|
-
UNMARKED = :Unmarked
|
274
|
-
|
275
264
|
# Returns the debug mode.
|
276
265
|
def self.debug
|
277
266
|
return @@debug
|
@@ -282,41 +271,6 @@ module Net
|
|
282
271
|
return @@debug = val
|
283
272
|
end
|
284
273
|
|
285
|
-
# Returns the max number of flags interned to symbols.
|
286
|
-
def self.max_flag_count
|
287
|
-
return @@max_flag_count
|
288
|
-
end
|
289
|
-
|
290
|
-
# Sets the max number of flags interned to symbols.
|
291
|
-
def self.max_flag_count=(count)
|
292
|
-
@@max_flag_count = count
|
293
|
-
end
|
294
|
-
|
295
|
-
# Adds an authenticator for Net::IMAP#authenticate. +auth_type+
|
296
|
-
# is the type of authentication this authenticator supports
|
297
|
-
# (for instance, "LOGIN"). The +authenticator+ is an object
|
298
|
-
# which defines a process() method to handle authentication with
|
299
|
-
# the server. See Net::IMAP::LoginAuthenticator,
|
300
|
-
# Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
|
301
|
-
# for examples.
|
302
|
-
#
|
303
|
-
#
|
304
|
-
# If +auth_type+ refers to an existing authenticator, it will be
|
305
|
-
# replaced by the new one.
|
306
|
-
def self.add_authenticator(auth_type, authenticator)
|
307
|
-
@@authenticators[auth_type] = authenticator
|
308
|
-
end
|
309
|
-
|
310
|
-
# Builds an authenticator for Net::IMAP#authenticate.
|
311
|
-
def self.authenticator(auth_type, *args)
|
312
|
-
auth_type = auth_type.upcase
|
313
|
-
unless @@authenticators.has_key?(auth_type)
|
314
|
-
raise ArgumentError,
|
315
|
-
format('unknown auth type - "%s"', auth_type)
|
316
|
-
end
|
317
|
-
@@authenticators[auth_type].new(*args)
|
318
|
-
end
|
319
|
-
|
320
274
|
# The default port for IMAP connections, port 143
|
321
275
|
def self.default_port
|
322
276
|
return PORT
|
@@ -394,7 +348,7 @@ module Net
|
|
394
348
|
# )
|
395
349
|
# end
|
396
350
|
#
|
397
|
-
# See
|
351
|
+
# See [EXT-ID[https://tools.ietf.org/html/rfc2971]] for field definitions.
|
398
352
|
def id(client_id=nil)
|
399
353
|
synchronize do
|
400
354
|
send_command("ID", ClientID.new(client_id))
|
@@ -441,7 +395,7 @@ module Net
|
|
441
395
|
#
|
442
396
|
# For both of these mechanisms, there should be two +args+: username
|
443
397
|
# and (cleartext) password. A server may not support one or the other
|
444
|
-
# of these mechanisms; check #capability
|
398
|
+
# of these mechanisms; check #capability for a capability of
|
445
399
|
# the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
|
446
400
|
#
|
447
401
|
# Authentication is done using the appropriate authenticator object:
|
@@ -467,8 +421,8 @@ module Net
|
|
467
421
|
|
468
422
|
# Sends a LOGIN command to identify the client and carries
|
469
423
|
# the plaintext +password+ authenticating this +user+. Note
|
470
|
-
# that, unlike calling #authenticate
|
471
|
-
# of "LOGIN", #login
|
424
|
+
# that, unlike calling #authenticate with an +auth_type+
|
425
|
+
# of "LOGIN", #login does *not* use the login authenticator.
|
472
426
|
#
|
473
427
|
# A Net::IMAP::NoResponseError is raised if authentication fails.
|
474
428
|
def login(user, password)
|
@@ -479,10 +433,10 @@ module Net
|
|
479
433
|
# in the +mailbox+ can be accessed.
|
480
434
|
#
|
481
435
|
# After you have selected a mailbox, you may retrieve the
|
482
|
-
# number of items in that mailbox from
|
483
|
-
# and the number of recent messages from
|
436
|
+
# number of items in that mailbox from +@responses["EXISTS"][-1]+,
|
437
|
+
# and the number of recent messages from +@responses["RECENT"][-1]+.
|
484
438
|
# Note that these values can change if new messages arrive
|
485
|
-
# during a session; see #add_response_handler
|
439
|
+
# during a session; see #add_response_handler for a way of
|
486
440
|
# detecting this event.
|
487
441
|
#
|
488
442
|
# A Net::IMAP::NoResponseError is raised if the mailbox does not
|
@@ -495,7 +449,7 @@ module Net
|
|
495
449
|
end
|
496
450
|
|
497
451
|
# Sends a EXAMINE command to select a +mailbox+ so that messages
|
498
|
-
# in the +mailbox+ can be accessed. Behaves the same as #select
|
452
|
+
# in the +mailbox+ can be accessed. Behaves the same as #select,
|
499
453
|
# except that the selected +mailbox+ is identified as read-only.
|
500
454
|
#
|
501
455
|
# A Net::IMAP::NoResponseError is raised if the mailbox does not
|
@@ -537,7 +491,7 @@ module Net
|
|
537
491
|
|
538
492
|
# Sends a SUBSCRIBE command to add the specified +mailbox+ name to
|
539
493
|
# the server's set of "active" or "subscribed" mailboxes as returned
|
540
|
-
# by #lsub
|
494
|
+
# by #lsub.
|
541
495
|
#
|
542
496
|
# A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
|
543
497
|
# subscribed to; for instance, because it does not exist.
|
@@ -584,15 +538,16 @@ module Net
|
|
584
538
|
end
|
585
539
|
end
|
586
540
|
|
587
|
-
# Sends a NAMESPACE command
|
588
|
-
#
|
589
|
-
#
|
541
|
+
# Sends a NAMESPACE command and returns the namespaces that are available.
|
542
|
+
# The NAMESPACE command allows a client to discover the prefixes of
|
543
|
+
# namespaces used by a server for personal mailboxes, other users'
|
590
544
|
# mailboxes, and shared mailboxes.
|
591
545
|
#
|
592
|
-
#
|
593
|
-
# it. Many popular IMAP servers are configured
|
594
|
-
# namespaces as `("" "/")`: no prefix and "/"
|
595
|
-
# common case, the naive client may not have
|
546
|
+
# The NAMESPACE extension predates [IMAP4rev1[https://tools.ietf.org/html/rfc2501]],
|
547
|
+
# so most IMAP servers support it. Many popular IMAP servers are configured
|
548
|
+
# with the default personal namespaces as `("" "/")`: no prefix and "/"
|
549
|
+
# hierarchy delimiter. In that common case, the naive client may not have
|
550
|
+
# any trouble naming mailboxes.
|
596
551
|
#
|
597
552
|
# But many servers are configured with the default personal namespace as
|
598
553
|
# e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "."
|
@@ -631,6 +586,8 @@ module Net
|
|
631
586
|
# imap.create(prefix + %w[path to my folder].join(delim))
|
632
587
|
# end
|
633
588
|
# end
|
589
|
+
#
|
590
|
+
# The NAMESPACE extension is described in [EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]
|
634
591
|
def namespace
|
635
592
|
synchronize do
|
636
593
|
send_command("NAMESPACE")
|
@@ -674,6 +631,8 @@ module Net
|
|
674
631
|
# This command is generally available to both admin and user.
|
675
632
|
# If this mailbox exists, it returns an array containing objects of type
|
676
633
|
# Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
|
634
|
+
#
|
635
|
+
# The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
|
677
636
|
def getquotaroot(mailbox)
|
678
637
|
synchronize do
|
679
638
|
send_command("GETQUOTAROOT", mailbox)
|
@@ -688,6 +647,8 @@ module Net
|
|
688
647
|
# If this mailbox exists, then an array containing a
|
689
648
|
# Net::IMAP::MailboxQuota object is returned. This
|
690
649
|
# command is generally only available to server admin.
|
650
|
+
#
|
651
|
+
# The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
|
691
652
|
def getquota(mailbox)
|
692
653
|
synchronize do
|
693
654
|
send_command("GETQUOTA", mailbox)
|
@@ -698,8 +659,9 @@ module Net
|
|
698
659
|
# Sends a SETQUOTA command along with the specified +mailbox+ and
|
699
660
|
# +quota+. If +quota+ is nil, then +quota+ will be unset for that
|
700
661
|
# mailbox. Typically one needs to be logged in as a server admin
|
701
|
-
# for this to work.
|
702
|
-
#
|
662
|
+
# for this to work.
|
663
|
+
#
|
664
|
+
# The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
|
703
665
|
def setquota(mailbox, quota)
|
704
666
|
if quota.nil?
|
705
667
|
data = '()'
|
@@ -712,7 +674,8 @@ module Net
|
|
712
674
|
# Sends the SETACL command along with +mailbox+, +user+ and the
|
713
675
|
# +rights+ that user is to have on that mailbox. If +rights+ is nil,
|
714
676
|
# then that user will be stripped of any rights to that mailbox.
|
715
|
-
#
|
677
|
+
#
|
678
|
+
# The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
|
716
679
|
def setacl(mailbox, user, rights)
|
717
680
|
if rights.nil?
|
718
681
|
send_command("SETACL", mailbox, user, "")
|
@@ -724,6 +687,8 @@ module Net
|
|
724
687
|
# Send the GETACL command along with a specified +mailbox+.
|
725
688
|
# If this mailbox exists, an array containing objects of
|
726
689
|
# Net::IMAP::MailboxACLItem will be returned.
|
690
|
+
#
|
691
|
+
# The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
|
727
692
|
def getacl(mailbox)
|
728
693
|
synchronize do
|
729
694
|
send_command("GETACL", mailbox)
|
@@ -734,7 +699,8 @@ module Net
|
|
734
699
|
# Sends a LSUB command, and returns a subset of names from the set
|
735
700
|
# of names that the user has declared as being "active" or
|
736
701
|
# "subscribed." +refname+ and +mailbox+ are interpreted as
|
737
|
-
# for #list
|
702
|
+
# for #list.
|
703
|
+
#
|
738
704
|
# The return value is an array of +Net::IMAP::MailboxList+.
|
739
705
|
def lsub(refname, mailbox)
|
740
706
|
synchronize do
|
@@ -862,7 +828,7 @@ module Net
|
|
862
828
|
return search_internal("SEARCH", keys, charset)
|
863
829
|
end
|
864
830
|
|
865
|
-
# Similar to #search
|
831
|
+
# Similar to #search, but returns unique identifiers.
|
866
832
|
def uid_search(keys, charset = nil)
|
867
833
|
return search_internal("UID SEARCH", keys, charset)
|
868
834
|
end
|
@@ -906,7 +872,7 @@ module Net
|
|
906
872
|
return fetch_internal("FETCH", set, attr, mod)
|
907
873
|
end
|
908
874
|
|
909
|
-
# Similar to #fetch
|
875
|
+
# Similar to #fetch, but +set+ contains unique identifiers.
|
910
876
|
def uid_fetch(set, attr, mod = nil)
|
911
877
|
return fetch_internal("UID FETCH", set, attr, mod)
|
912
878
|
end
|
@@ -929,7 +895,7 @@ module Net
|
|
929
895
|
return store_internal("STORE", set, attr, flags)
|
930
896
|
end
|
931
897
|
|
932
|
-
# Similar to #store
|
898
|
+
# Similar to #store, but +set+ contains unique identifiers.
|
933
899
|
def uid_store(set, attr, flags)
|
934
900
|
return store_internal("UID STORE", set, attr, flags)
|
935
901
|
end
|
@@ -942,7 +908,7 @@ module Net
|
|
942
908
|
copy_internal("COPY", set, mailbox)
|
943
909
|
end
|
944
910
|
|
945
|
-
# Similar to #copy
|
911
|
+
# Similar to #copy, but +set+ contains unique identifiers.
|
946
912
|
def uid_copy(set, mailbox)
|
947
913
|
copy_internal("UID COPY", set, mailbox)
|
948
914
|
end
|
@@ -951,12 +917,15 @@ module Net
|
|
951
917
|
# of the specified destination +mailbox+. The +set+ parameter is
|
952
918
|
# a number, an array of numbers, or a Range object. The number is
|
953
919
|
# a message sequence number.
|
954
|
-
#
|
920
|
+
#
|
921
|
+
# The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
|
955
922
|
def move(set, mailbox)
|
956
923
|
copy_internal("MOVE", set, mailbox)
|
957
924
|
end
|
958
925
|
|
959
|
-
# Similar to #move
|
926
|
+
# Similar to #move, but +set+ contains unique identifiers.
|
927
|
+
#
|
928
|
+
# The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
|
960
929
|
def uid_move(set, mailbox)
|
961
930
|
copy_internal("UID MOVE", set, mailbox)
|
962
931
|
end
|
@@ -969,12 +938,14 @@ module Net
|
|
969
938
|
# p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
|
970
939
|
# #=> [6, 7, 8, 1]
|
971
940
|
#
|
972
|
-
#
|
941
|
+
# The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
|
973
942
|
def sort(sort_keys, search_keys, charset)
|
974
943
|
return sort_internal("SORT", sort_keys, search_keys, charset)
|
975
944
|
end
|
976
945
|
|
977
|
-
# Similar to #sort
|
946
|
+
# Similar to #sort, but returns an array of unique identifiers.
|
947
|
+
#
|
948
|
+
# The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
|
978
949
|
def uid_sort(sort_keys, search_keys, charset)
|
979
950
|
return sort_internal("UID SORT", sort_keys, search_keys, charset)
|
980
951
|
end
|
@@ -1001,7 +972,7 @@ module Net
|
|
1001
972
|
@response_handlers.delete(handler)
|
1002
973
|
end
|
1003
974
|
|
1004
|
-
# Similar to #search
|
975
|
+
# Similar to #search, but returns message sequence numbers in threaded
|
1005
976
|
# format, as a Net::IMAP::ThreadMember tree. The supported algorithms
|
1006
977
|
# are:
|
1007
978
|
#
|
@@ -1010,16 +981,18 @@ module Net
|
|
1010
981
|
# REFERENCES:: split into threads by parent/child relationships determined
|
1011
982
|
# by which message is a reply to which.
|
1012
983
|
#
|
1013
|
-
# Unlike #search
|
984
|
+
# Unlike #search, +charset+ is a required argument. US-ASCII
|
1014
985
|
# and UTF-8 are sample values.
|
1015
986
|
#
|
1016
|
-
#
|
987
|
+
# The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
|
1017
988
|
def thread(algorithm, search_keys, charset)
|
1018
989
|
return thread_internal("THREAD", algorithm, search_keys, charset)
|
1019
990
|
end
|
1020
991
|
|
1021
|
-
# Similar to #thread
|
992
|
+
# Similar to #thread, but returns unique identifiers instead of
|
1022
993
|
# message sequence numbers.
|
994
|
+
#
|
995
|
+
# The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
|
1023
996
|
def uid_thread(algorithm, search_keys, charset)
|
1024
997
|
return thread_internal("UID THREAD", algorithm, search_keys, charset)
|
1025
998
|
end
|
@@ -1027,7 +1000,7 @@ module Net
|
|
1027
1000
|
# Sends an IDLE command that waits for notifications of new or expunged
|
1028
1001
|
# messages. Yields responses from the server during the IDLE.
|
1029
1002
|
#
|
1030
|
-
# Use #idle_done
|
1003
|
+
# Use #idle_done to leave IDLE.
|
1031
1004
|
#
|
1032
1005
|
# If +timeout+ is given, this method returns after +timeout+ seconds passed.
|
1033
1006
|
# +timeout+ can be used for keep-alive. For example, the following code
|
@@ -1077,46 +1050,6 @@ module Net
|
|
1077
1050
|
end
|
1078
1051
|
end
|
1079
1052
|
|
1080
|
-
# Decode a string from modified UTF-7 format to UTF-8.
|
1081
|
-
#
|
1082
|
-
# UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
|
1083
|
-
# slightly modified version of this to encode mailbox names
|
1084
|
-
# containing non-ASCII characters; see [IMAP] section 5.1.3.
|
1085
|
-
#
|
1086
|
-
# Net::IMAP does _not_ automatically encode and decode
|
1087
|
-
# mailbox names to and from UTF-7.
|
1088
|
-
def self.decode_utf7(s)
|
1089
|
-
return s.gsub(/&([^-]+)?-/n) {
|
1090
|
-
if $1
|
1091
|
-
($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
|
1092
|
-
else
|
1093
|
-
"&"
|
1094
|
-
end
|
1095
|
-
}
|
1096
|
-
end
|
1097
|
-
|
1098
|
-
# Encode a string from UTF-8 format to modified UTF-7.
|
1099
|
-
def self.encode_utf7(s)
|
1100
|
-
return s.gsub(/(&)|[^\x20-\x7e]+/) {
|
1101
|
-
if $1
|
1102
|
-
"&-"
|
1103
|
-
else
|
1104
|
-
base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
|
1105
|
-
"&" + base64.delete("=").tr("/", ",") + "-"
|
1106
|
-
end
|
1107
|
-
}.force_encoding("ASCII-8BIT")
|
1108
|
-
end
|
1109
|
-
|
1110
|
-
# Formats +time+ as an IMAP-style date.
|
1111
|
-
def self.format_date(time)
|
1112
|
-
return time.strftime('%d-%b-%Y')
|
1113
|
-
end
|
1114
|
-
|
1115
|
-
# Formats +time+ as an IMAP-style date-time.
|
1116
|
-
def self.format_datetime(time)
|
1117
|
-
return time.strftime('%d-%b-%Y %H:%M %z')
|
1118
|
-
end
|
1119
|
-
|
1120
1053
|
private
|
1121
1054
|
|
1122
1055
|
CRLF = "\r\n" # :nodoc:
|
@@ -1124,8 +1057,6 @@ module Net
|
|
1124
1057
|
SSL_PORT = 993 # :nodoc:
|
1125
1058
|
|
1126
1059
|
@@debug = false
|
1127
|
-
@@authenticators = {}
|
1128
|
-
@@max_flag_count = 10000
|
1129
1060
|
|
1130
1061
|
# :call-seq:
|
1131
1062
|
# Net::IMAP.new(host, options = {})
|
@@ -1138,11 +1069,11 @@ module Net
|
|
1138
1069
|
# The available options are:
|
1139
1070
|
#
|
1140
1071
|
# port:: Port number (default value is 143 for imap, or 993 for imaps)
|
1141
|
-
# ssl:: If options[:ssl] is true, then an attempt will be made
|
1072
|
+
# ssl:: If +options[:ssl]+ is true, then an attempt will be made
|
1142
1073
|
# to use SSL (now TLS) to connect to the server. For this to work
|
1143
1074
|
# OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
|
1144
1075
|
# be installed.
|
1145
|
-
# If options[:ssl] is a hash, it's passed to
|
1076
|
+
# If +options[:ssl]+ is a hash, it's passed to
|
1146
1077
|
# OpenSSL::SSL::SSLContext#set_params as parameters.
|
1147
1078
|
# open_timeout:: Seconds to wait until a connection is opened
|
1148
1079
|
# idle_response_timeout:: Seconds to wait until an IDLE response is received
|
@@ -1315,12 +1246,14 @@ module Net
|
|
1315
1246
|
end
|
1316
1247
|
resp = @tagged_responses.delete(tag)
|
1317
1248
|
case resp.name
|
1249
|
+
when /\A(?:OK)\z/ni
|
1250
|
+
return resp
|
1318
1251
|
when /\A(?:NO)\z/ni
|
1319
1252
|
raise NoResponseError, resp
|
1320
1253
|
when /\A(?:BAD)\z/ni
|
1321
1254
|
raise BadResponseError, resp
|
1322
1255
|
else
|
1323
|
-
|
1256
|
+
raise UnknownResponseError, resp
|
1324
1257
|
end
|
1325
1258
|
end
|
1326
1259
|
|
@@ -1399,114 +1332,6 @@ module Net
|
|
1399
1332
|
end
|
1400
1333
|
end
|
1401
1334
|
|
1402
|
-
def validate_data(data)
|
1403
|
-
case data
|
1404
|
-
when nil
|
1405
|
-
when String
|
1406
|
-
when Integer
|
1407
|
-
NumValidator.ensure_number(data)
|
1408
|
-
when Array
|
1409
|
-
if data[0] == 'CHANGEDSINCE'
|
1410
|
-
NumValidator.ensure_mod_sequence_value(data[1])
|
1411
|
-
else
|
1412
|
-
data.each do |i|
|
1413
|
-
validate_data(i)
|
1414
|
-
end
|
1415
|
-
end
|
1416
|
-
when Time
|
1417
|
-
when Symbol
|
1418
|
-
else
|
1419
|
-
data.validate
|
1420
|
-
end
|
1421
|
-
end
|
1422
|
-
|
1423
|
-
def send_data(data, tag = nil)
|
1424
|
-
case data
|
1425
|
-
when nil
|
1426
|
-
put_string("NIL")
|
1427
|
-
when String
|
1428
|
-
send_string_data(data, tag)
|
1429
|
-
when Integer
|
1430
|
-
send_number_data(data)
|
1431
|
-
when Array
|
1432
|
-
send_list_data(data, tag)
|
1433
|
-
when Time
|
1434
|
-
send_time_data(data)
|
1435
|
-
when Symbol
|
1436
|
-
send_symbol_data(data)
|
1437
|
-
else
|
1438
|
-
data.send_data(self, tag)
|
1439
|
-
end
|
1440
|
-
end
|
1441
|
-
|
1442
|
-
def send_string_data(str, tag = nil)
|
1443
|
-
case str
|
1444
|
-
when ""
|
1445
|
-
put_string('""')
|
1446
|
-
when /[\x80-\xff\r\n]/n
|
1447
|
-
# literal
|
1448
|
-
send_literal(str, tag)
|
1449
|
-
when /[(){ \x00-\x1f\x7f%*"\\]/n
|
1450
|
-
# quoted string
|
1451
|
-
send_quoted_string(str)
|
1452
|
-
else
|
1453
|
-
put_string(str)
|
1454
|
-
end
|
1455
|
-
end
|
1456
|
-
|
1457
|
-
def send_quoted_string(str)
|
1458
|
-
put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
|
1459
|
-
end
|
1460
|
-
|
1461
|
-
def send_literal(str, tag = nil)
|
1462
|
-
synchronize do
|
1463
|
-
put_string("{" + str.bytesize.to_s + "}" + CRLF)
|
1464
|
-
@continued_command_tag = tag
|
1465
|
-
@continuation_request_exception = nil
|
1466
|
-
begin
|
1467
|
-
@continuation_request_arrival.wait
|
1468
|
-
e = @continuation_request_exception || @exception
|
1469
|
-
raise e if e
|
1470
|
-
put_string(str)
|
1471
|
-
ensure
|
1472
|
-
@continued_command_tag = nil
|
1473
|
-
@continuation_request_exception = nil
|
1474
|
-
end
|
1475
|
-
end
|
1476
|
-
end
|
1477
|
-
|
1478
|
-
def send_number_data(num)
|
1479
|
-
put_string(num.to_s)
|
1480
|
-
end
|
1481
|
-
|
1482
|
-
def send_list_data(list, tag = nil)
|
1483
|
-
put_string("(")
|
1484
|
-
first = true
|
1485
|
-
list.each do |i|
|
1486
|
-
if first
|
1487
|
-
first = false
|
1488
|
-
else
|
1489
|
-
put_string(" ")
|
1490
|
-
end
|
1491
|
-
send_data(i, tag)
|
1492
|
-
end
|
1493
|
-
put_string(")")
|
1494
|
-
end
|
1495
|
-
|
1496
|
-
DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
|
1497
|
-
|
1498
|
-
def send_time_data(time)
|
1499
|
-
t = time.dup.gmtime
|
1500
|
-
s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
|
1501
|
-
t.day, DATE_MONTH[t.month - 1], t.year,
|
1502
|
-
t.hour, t.min, t.sec)
|
1503
|
-
put_string(s)
|
1504
|
-
end
|
1505
|
-
|
1506
|
-
def send_symbol_data(symbol)
|
1507
|
-
put_string("\\" + symbol.to_s)
|
1508
|
-
end
|
1509
|
-
|
1510
1335
|
def search_internal(cmd, keys, charset)
|
1511
1336
|
if keys.instance_of?(String)
|
1512
1337
|
keys = [RawData.new(keys)]
|
@@ -1637,190 +1462,6 @@ module Net
|
|
1637
1462
|
end
|
1638
1463
|
end
|
1639
1464
|
|
1640
|
-
class RawData # :nodoc:
|
1641
|
-
def send_data(imap, tag)
|
1642
|
-
imap.__send__(:put_string, @data)
|
1643
|
-
end
|
1644
|
-
|
1645
|
-
def validate
|
1646
|
-
end
|
1647
|
-
|
1648
|
-
private
|
1649
|
-
|
1650
|
-
def initialize(data)
|
1651
|
-
@data = data
|
1652
|
-
end
|
1653
|
-
end
|
1654
|
-
|
1655
|
-
class Atom # :nodoc:
|
1656
|
-
def send_data(imap, tag)
|
1657
|
-
imap.__send__(:put_string, @data)
|
1658
|
-
end
|
1659
|
-
|
1660
|
-
def validate
|
1661
|
-
end
|
1662
|
-
|
1663
|
-
private
|
1664
|
-
|
1665
|
-
def initialize(data)
|
1666
|
-
@data = data
|
1667
|
-
end
|
1668
|
-
end
|
1669
|
-
|
1670
|
-
class QuotedString # :nodoc:
|
1671
|
-
def send_data(imap, tag)
|
1672
|
-
imap.__send__(:send_quoted_string, @data)
|
1673
|
-
end
|
1674
|
-
|
1675
|
-
def validate
|
1676
|
-
end
|
1677
|
-
|
1678
|
-
private
|
1679
|
-
|
1680
|
-
def initialize(data)
|
1681
|
-
@data = data
|
1682
|
-
end
|
1683
|
-
end
|
1684
|
-
|
1685
|
-
class Literal # :nodoc:
|
1686
|
-
def send_data(imap, tag)
|
1687
|
-
imap.__send__(:send_literal, @data, tag)
|
1688
|
-
end
|
1689
|
-
|
1690
|
-
def validate
|
1691
|
-
end
|
1692
|
-
|
1693
|
-
private
|
1694
|
-
|
1695
|
-
def initialize(data)
|
1696
|
-
@data = data
|
1697
|
-
end
|
1698
|
-
end
|
1699
|
-
|
1700
|
-
class MessageSet # :nodoc:
|
1701
|
-
def send_data(imap, tag)
|
1702
|
-
imap.__send__(:put_string, format_internal(@data))
|
1703
|
-
end
|
1704
|
-
|
1705
|
-
def validate
|
1706
|
-
validate_internal(@data)
|
1707
|
-
end
|
1708
|
-
|
1709
|
-
private
|
1710
|
-
|
1711
|
-
def initialize(data)
|
1712
|
-
@data = data
|
1713
|
-
end
|
1714
|
-
|
1715
|
-
def format_internal(data)
|
1716
|
-
case data
|
1717
|
-
when "*"
|
1718
|
-
return data
|
1719
|
-
when Integer
|
1720
|
-
if data == -1
|
1721
|
-
return "*"
|
1722
|
-
else
|
1723
|
-
return data.to_s
|
1724
|
-
end
|
1725
|
-
when Range
|
1726
|
-
return format_internal(data.first) +
|
1727
|
-
":" + format_internal(data.last)
|
1728
|
-
when Array
|
1729
|
-
return data.collect {|i| format_internal(i)}.join(",")
|
1730
|
-
when ThreadMember
|
1731
|
-
return data.seqno.to_s +
|
1732
|
-
":" + data.children.collect {|i| format_internal(i).join(",")}
|
1733
|
-
end
|
1734
|
-
end
|
1735
|
-
|
1736
|
-
def validate_internal(data)
|
1737
|
-
case data
|
1738
|
-
when "*"
|
1739
|
-
when Integer
|
1740
|
-
NumValidator.ensure_nz_number(data)
|
1741
|
-
when Range
|
1742
|
-
when Array
|
1743
|
-
data.each do |i|
|
1744
|
-
validate_internal(i)
|
1745
|
-
end
|
1746
|
-
when ThreadMember
|
1747
|
-
data.children.each do |i|
|
1748
|
-
validate_internal(i)
|
1749
|
-
end
|
1750
|
-
else
|
1751
|
-
raise DataFormatError, data.inspect
|
1752
|
-
end
|
1753
|
-
end
|
1754
|
-
end
|
1755
|
-
|
1756
|
-
class ClientID # :nodoc:
|
1757
|
-
|
1758
|
-
def send_data(imap, tag)
|
1759
|
-
imap.__send__(:send_data, format_internal(@data), tag)
|
1760
|
-
end
|
1761
|
-
|
1762
|
-
def validate
|
1763
|
-
validate_internal(@data)
|
1764
|
-
end
|
1765
|
-
|
1766
|
-
private
|
1767
|
-
|
1768
|
-
def initialize(data)
|
1769
|
-
@data = data
|
1770
|
-
end
|
1771
|
-
|
1772
|
-
def validate_internal(client_id)
|
1773
|
-
client_id.to_h.each do |k,v|
|
1774
|
-
unless StringFormatter.valid_string?(k)
|
1775
|
-
raise DataFormatError, client_id.inspect
|
1776
|
-
end
|
1777
|
-
end
|
1778
|
-
rescue NoMethodError, TypeError # to_h failed
|
1779
|
-
raise DataFormatError, client_id.inspect
|
1780
|
-
end
|
1781
|
-
|
1782
|
-
def format_internal(client_id)
|
1783
|
-
return nil if client_id.nil?
|
1784
|
-
client_id.to_h.flat_map {|k,v|
|
1785
|
-
[StringFormatter.string(k), StringFormatter.nstring(v)]
|
1786
|
-
}
|
1787
|
-
end
|
1788
|
-
|
1789
|
-
end
|
1790
|
-
|
1791
|
-
module StringFormatter
|
1792
|
-
|
1793
|
-
LITERAL_REGEX = /[\x80-\xff\r\n]/n
|
1794
|
-
|
1795
|
-
module_function
|
1796
|
-
|
1797
|
-
# Allows symbols in addition to strings
|
1798
|
-
def valid_string?(str)
|
1799
|
-
str.is_a?(Symbol) || str.respond_to?(:to_str)
|
1800
|
-
end
|
1801
|
-
|
1802
|
-
# Allows nil, symbols, and strings
|
1803
|
-
def valid_nstring?(str)
|
1804
|
-
str.nil? || valid_string?(str)
|
1805
|
-
end
|
1806
|
-
|
1807
|
-
# coerces using +to_s+
|
1808
|
-
def string(str)
|
1809
|
-
str = str.to_s
|
1810
|
-
if str =~ LITERAL_REGEX
|
1811
|
-
Literal.new(str)
|
1812
|
-
else
|
1813
|
-
QuotedString.new(str)
|
1814
|
-
end
|
1815
|
-
end
|
1816
|
-
|
1817
|
-
# coerces non-nil using +to_s+
|
1818
|
-
def nstring(str)
|
1819
|
-
str.nil? ? nil : string(str)
|
1820
|
-
end
|
1821
|
-
|
1822
|
-
end
|
1823
|
-
|
1824
1465
|
# Common validators of number and nz_number types
|
1825
1466
|
module NumValidator # :nodoc
|
1826
1467
|
class << self
|
@@ -1877,2237 +1518,37 @@ module Net
|
|
1877
1518
|
end
|
1878
1519
|
end
|
1879
1520
|
|
1880
|
-
#
|
1881
|
-
|
1882
|
-
|
1883
|
-
# instead of a tag. This form of response indicates that the server is
|
1884
|
-
# ready to accept the continuation of a command from the client. The
|
1885
|
-
# remainder of this response is a line of text.
|
1886
|
-
#
|
1887
|
-
# continue_req ::= "+" SPACE (resp_text / base64)
|
1888
|
-
#
|
1889
|
-
# ==== Fields:
|
1890
|
-
#
|
1891
|
-
# data:: Returns the data (Net::IMAP::ResponseText).
|
1892
|
-
#
|
1893
|
-
# raw_data:: Returns the raw data string.
|
1894
|
-
ContinuationRequest = Struct.new(:data, :raw_data)
|
1895
|
-
|
1896
|
-
# Net::IMAP::UntaggedResponse represents untagged responses.
|
1897
|
-
#
|
1898
|
-
# Data transmitted by the server to the client and status responses
|
1899
|
-
# that do not indicate command completion are prefixed with the token
|
1900
|
-
# "*", and are called untagged responses.
|
1901
|
-
#
|
1902
|
-
# response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
|
1903
|
-
# mailbox_data / message_data / capability_data)
|
1904
|
-
#
|
1905
|
-
# ==== Fields:
|
1906
|
-
#
|
1907
|
-
# name:: Returns the name, such as "FLAGS", "LIST", or "FETCH".
|
1908
|
-
#
|
1909
|
-
# data:: Returns the data such as an array of flag symbols,
|
1910
|
-
# a ((<Net::IMAP::MailboxList>)) object.
|
1911
|
-
#
|
1912
|
-
# raw_data:: Returns the raw data string.
|
1913
|
-
UntaggedResponse = Struct.new(:name, :data, :raw_data)
|
1521
|
+
# Superclass of IMAP errors.
|
1522
|
+
class Error < StandardError
|
1523
|
+
end
|
1914
1524
|
|
1915
|
-
#
|
1916
|
-
|
1917
|
-
|
1918
|
-
# clients to close the connection.
|
1919
|
-
#
|
1920
|
-
# It matches no IMAP standard.
|
1921
|
-
#
|
1922
|
-
# ==== Fields:
|
1923
|
-
#
|
1924
|
-
# raw_data:: Returns the raw data string.
|
1925
|
-
IgnoredResponse = Struct.new(:raw_data)
|
1525
|
+
# Error raised when data is in the incorrect format.
|
1526
|
+
class DataFormatError < Error
|
1527
|
+
end
|
1926
1528
|
|
1927
|
-
#
|
1928
|
-
|
1929
|
-
|
1930
|
-
# failure of the operation. It is tagged with the same tag as the
|
1931
|
-
# client command which began the operation.
|
1932
|
-
#
|
1933
|
-
# response_tagged ::= tag SPACE resp_cond_state CRLF
|
1934
|
-
#
|
1935
|
-
# tag ::= 1*<any ATOM_CHAR except "+">
|
1936
|
-
#
|
1937
|
-
# resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
|
1938
|
-
#
|
1939
|
-
# ==== Fields:
|
1940
|
-
#
|
1941
|
-
# tag:: Returns the tag.
|
1942
|
-
#
|
1943
|
-
# name:: Returns the name, one of "OK", "NO", or "BAD".
|
1944
|
-
#
|
1945
|
-
# data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
|
1946
|
-
#
|
1947
|
-
# raw_data:: Returns the raw data string.
|
1948
|
-
#
|
1949
|
-
TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
|
1529
|
+
# Error raised when a response from the server is non-parseable.
|
1530
|
+
class ResponseParseError < Error
|
1531
|
+
end
|
1950
1532
|
|
1951
|
-
#
|
1952
|
-
#
|
1953
|
-
|
1954
|
-
# resp_text ::= ["[" resp-text-code "]" SP] text
|
1955
|
-
#
|
1956
|
-
# ==== Fields:
|
1957
|
-
#
|
1958
|
-
# code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
|
1959
|
-
#
|
1960
|
-
# text:: Returns the text.
|
1961
|
-
#
|
1962
|
-
ResponseText = Struct.new(:code, :text)
|
1533
|
+
# Superclass of all errors used to encapsulate "fail" responses
|
1534
|
+
# from the server.
|
1535
|
+
class ResponseError < Error
|
1963
1536
|
|
1964
|
-
|
1965
|
-
|
1966
|
-
# resp_text_code ::= "ALERT" /
|
1967
|
-
# "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
|
1968
|
-
# capability_data / "PARSE" /
|
1969
|
-
# "PERMANENTFLAGS" SP "("
|
1970
|
-
# [flag_perm *(SP flag_perm)] ")" /
|
1971
|
-
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
1972
|
-
# "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number /
|
1973
|
-
# "UNSEEN" SP nz_number /
|
1974
|
-
# atom [SP 1*<any TEXT-CHAR except "]">]
|
1975
|
-
#
|
1976
|
-
# ==== Fields:
|
1977
|
-
#
|
1978
|
-
# name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY".
|
1979
|
-
#
|
1980
|
-
# data:: Returns the data, if it exists.
|
1981
|
-
#
|
1982
|
-
ResponseCode = Struct.new(:name, :data)
|
1537
|
+
# The response that caused this error
|
1538
|
+
attr_accessor :response
|
1983
1539
|
|
1984
|
-
|
1985
|
-
|
1986
|
-
# mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
|
1987
|
-
# "\Noselect" / "\Unmarked" / flag_extension) ")"
|
1988
|
-
# SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
|
1989
|
-
#
|
1990
|
-
# ==== Fields:
|
1991
|
-
#
|
1992
|
-
# attr:: Returns the name attributes. Each name attribute is a symbol
|
1993
|
-
# capitalized by String#capitalize, such as :Noselect (not :NoSelect).
|
1994
|
-
#
|
1995
|
-
# delim:: Returns the hierarchy delimiter.
|
1996
|
-
#
|
1997
|
-
# name:: Returns the mailbox name.
|
1998
|
-
#
|
1999
|
-
MailboxList = Struct.new(:attr, :delim, :name)
|
1540
|
+
def initialize(response)
|
1541
|
+
@response = response
|
2000
1542
|
|
2001
|
-
|
2002
|
-
|
2003
|
-
# specification below, the delimiter used with the "#" construct is a
|
2004
|
-
# single space (SPACE).
|
2005
|
-
#
|
2006
|
-
# quota_list ::= "(" #quota_resource ")"
|
2007
|
-
#
|
2008
|
-
# quota_resource ::= atom SPACE number SPACE number
|
2009
|
-
#
|
2010
|
-
# quota_response ::= "QUOTA" SPACE astring SPACE quota_list
|
2011
|
-
#
|
2012
|
-
# ==== Fields:
|
2013
|
-
#
|
2014
|
-
# mailbox:: The mailbox with the associated quota.
|
2015
|
-
#
|
2016
|
-
# usage:: Current storage usage of the mailbox.
|
2017
|
-
#
|
2018
|
-
# quota:: Quota limit imposed on the mailbox.
|
2019
|
-
#
|
2020
|
-
MailboxQuota = Struct.new(:mailbox, :usage, :quota)
|
1543
|
+
super @response.data.text
|
1544
|
+
end
|
2021
1545
|
|
2022
|
-
|
2023
|
-
# response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
|
2024
|
-
#
|
2025
|
-
# quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
|
2026
|
-
#
|
2027
|
-
# ==== Fields:
|
2028
|
-
#
|
2029
|
-
# mailbox:: The mailbox with the associated quota.
|
2030
|
-
#
|
2031
|
-
# quotaroots:: Zero or more quotaroots that affect the quota on the
|
2032
|
-
# specified mailbox.
|
2033
|
-
#
|
2034
|
-
MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
|
1546
|
+
end
|
2035
1547
|
|
2036
|
-
#
|
2037
|
-
#
|
2038
|
-
|
2039
|
-
|
2040
|
-
# identifier ::= astring
|
2041
|
-
#
|
2042
|
-
# rights ::= astring
|
2043
|
-
#
|
2044
|
-
# ==== Fields:
|
2045
|
-
#
|
2046
|
-
# user:: Login name that has certain rights to the mailbox
|
2047
|
-
# that was specified with the getacl command.
|
2048
|
-
#
|
2049
|
-
# rights:: The access rights the indicated user has to the
|
2050
|
-
# mailbox.
|
2051
|
-
#
|
2052
|
-
MailboxACLItem = Struct.new(:user, :rights, :mailbox)
|
2053
|
-
|
2054
|
-
# Net::IMAP::Namespace represents a single [RFC-2342] namespace.
|
2055
|
-
#
|
2056
|
-
# Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> /
|
2057
|
-
# nil) *(Namespace_Response_Extension) ")" ) ")"
|
2058
|
-
#
|
2059
|
-
# Namespace_Response_Extension = SP string SP "(" string *(SP string)
|
2060
|
-
# ")"
|
2061
|
-
#
|
2062
|
-
# ==== Fields:
|
2063
|
-
#
|
2064
|
-
# prefix:: Returns the namespace prefix string.
|
2065
|
-
# delim:: Returns nil or the hierarchy delimiter character.
|
2066
|
-
# extensions:: Returns a hash of extension names to extension flag arrays.
|
2067
|
-
#
|
2068
|
-
Namespace = Struct.new(:prefix, :delim, :extensions)
|
2069
|
-
|
2070
|
-
# Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE.
|
2071
|
-
#
|
2072
|
-
# Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP
|
2073
|
-
# Namespace
|
2074
|
-
#
|
2075
|
-
# ; The first Namespace is the Personal Namespace(s)
|
2076
|
-
# ; The second Namespace is the Other Users' Namespace(s)
|
2077
|
-
# ; The third Namespace is the Shared Namespace(s)
|
2078
|
-
#
|
2079
|
-
# ==== Fields:
|
2080
|
-
#
|
2081
|
-
# personal:: Returns an array of Personal Net::IMAP::Namespace objects.
|
2082
|
-
# other:: Returns an array of Other Users' Net::IMAP::Namespace objects.
|
2083
|
-
# shared:: Returns an array of Shared Net::IMAP::Namespace objects.
|
2084
|
-
#
|
2085
|
-
Namespaces = Struct.new(:personal, :other, :shared)
|
2086
|
-
|
2087
|
-
# Net::IMAP::StatusData represents the contents of the STATUS response.
|
2088
|
-
#
|
2089
|
-
# ==== Fields:
|
2090
|
-
#
|
2091
|
-
# mailbox:: Returns the mailbox name.
|
2092
|
-
#
|
2093
|
-
# attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
|
2094
|
-
# "UIDVALIDITY", "UNSEEN". Each value is a number.
|
2095
|
-
#
|
2096
|
-
StatusData = Struct.new(:mailbox, :attr)
|
2097
|
-
|
2098
|
-
# Net::IMAP::FetchData represents the contents of the FETCH response.
|
2099
|
-
#
|
2100
|
-
# ==== Fields:
|
2101
|
-
#
|
2102
|
-
# seqno:: Returns the message sequence number.
|
2103
|
-
# (Note: not the unique identifier, even for the UID command response.)
|
2104
|
-
#
|
2105
|
-
# attr:: Returns a hash. Each key is a data item name, and each value is
|
2106
|
-
# its value.
|
2107
|
-
#
|
2108
|
-
# The current data items are:
|
2109
|
-
#
|
2110
|
-
# [BODY]
|
2111
|
-
# A form of BODYSTRUCTURE without extension data.
|
2112
|
-
# [BODY[<section>]<<origin_octet>>]
|
2113
|
-
# A string expressing the body contents of the specified section.
|
2114
|
-
# [BODYSTRUCTURE]
|
2115
|
-
# An object that describes the [MIME-IMB] body structure of a message.
|
2116
|
-
# See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
|
2117
|
-
# Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
|
2118
|
-
# [ENVELOPE]
|
2119
|
-
# A Net::IMAP::Envelope object that describes the envelope
|
2120
|
-
# structure of a message.
|
2121
|
-
# [FLAGS]
|
2122
|
-
# A array of flag symbols that are set for this message. Flag symbols
|
2123
|
-
# are capitalized by String#capitalize.
|
2124
|
-
# [INTERNALDATE]
|
2125
|
-
# A string representing the internal date of the message.
|
2126
|
-
# [RFC822]
|
2127
|
-
# Equivalent to BODY[].
|
2128
|
-
# [RFC822.HEADER]
|
2129
|
-
# Equivalent to BODY.PEEK[HEADER].
|
2130
|
-
# [RFC822.SIZE]
|
2131
|
-
# A number expressing the [RFC-822] size of the message.
|
2132
|
-
# [RFC822.TEXT]
|
2133
|
-
# Equivalent to BODY[TEXT].
|
2134
|
-
# [UID]
|
2135
|
-
# A number expressing the unique identifier of the message.
|
2136
|
-
#
|
2137
|
-
FetchData = Struct.new(:seqno, :attr)
|
2138
|
-
|
2139
|
-
# Net::IMAP::Envelope represents envelope structures of messages.
|
2140
|
-
#
|
2141
|
-
# ==== Fields:
|
2142
|
-
#
|
2143
|
-
# date:: Returns a string that represents the date.
|
2144
|
-
#
|
2145
|
-
# subject:: Returns a string that represents the subject.
|
2146
|
-
#
|
2147
|
-
# from:: Returns an array of Net::IMAP::Address that represents the from.
|
2148
|
-
#
|
2149
|
-
# sender:: Returns an array of Net::IMAP::Address that represents the sender.
|
2150
|
-
#
|
2151
|
-
# reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
|
2152
|
-
#
|
2153
|
-
# to:: Returns an array of Net::IMAP::Address that represents the to.
|
2154
|
-
#
|
2155
|
-
# cc:: Returns an array of Net::IMAP::Address that represents the cc.
|
2156
|
-
#
|
2157
|
-
# bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
|
2158
|
-
#
|
2159
|
-
# in_reply_to:: Returns a string that represents the in-reply-to.
|
2160
|
-
#
|
2161
|
-
# message_id:: Returns a string that represents the message-id.
|
2162
|
-
#
|
2163
|
-
Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
|
2164
|
-
:to, :cc, :bcc, :in_reply_to, :message_id)
|
2165
|
-
|
2166
|
-
#
|
2167
|
-
# Net::IMAP::Address represents electronic mail addresses.
|
2168
|
-
#
|
2169
|
-
# ==== Fields:
|
2170
|
-
#
|
2171
|
-
# name:: Returns the phrase from [RFC-822] mailbox.
|
2172
|
-
#
|
2173
|
-
# route:: Returns the route from [RFC-822] route-addr.
|
2174
|
-
#
|
2175
|
-
# mailbox:: nil indicates end of [RFC-822] group.
|
2176
|
-
# If non-nil and host is nil, returns [RFC-822] group name.
|
2177
|
-
# Otherwise, returns [RFC-822] local-part.
|
2178
|
-
#
|
2179
|
-
# host:: nil indicates [RFC-822] group syntax.
|
2180
|
-
# Otherwise, returns [RFC-822] domain name.
|
2181
|
-
#
|
2182
|
-
Address = Struct.new(:name, :route, :mailbox, :host)
|
2183
|
-
|
2184
|
-
#
|
2185
|
-
# Net::IMAP::ContentDisposition represents Content-Disposition fields.
|
2186
|
-
#
|
2187
|
-
# ==== Fields:
|
2188
|
-
#
|
2189
|
-
# dsp_type:: Returns the disposition type.
|
2190
|
-
#
|
2191
|
-
# param:: Returns a hash that represents parameters of the Content-Disposition
|
2192
|
-
# field.
|
2193
|
-
#
|
2194
|
-
ContentDisposition = Struct.new(:dsp_type, :param)
|
2195
|
-
|
2196
|
-
# Net::IMAP::ThreadMember represents a thread-node returned
|
2197
|
-
# by Net::IMAP#thread.
|
2198
|
-
#
|
2199
|
-
# ==== Fields:
|
2200
|
-
#
|
2201
|
-
# seqno:: The sequence number of this message.
|
2202
|
-
#
|
2203
|
-
# children:: An array of Net::IMAP::ThreadMember objects for mail
|
2204
|
-
# items that are children of this in the thread.
|
2205
|
-
#
|
2206
|
-
ThreadMember = Struct.new(:seqno, :children)
|
2207
|
-
|
2208
|
-
# Net::IMAP::BodyTypeBasic represents basic body structures of messages.
|
2209
|
-
#
|
2210
|
-
# ==== Fields:
|
2211
|
-
#
|
2212
|
-
# media_type:: Returns the content media type name as defined in [MIME-IMB].
|
2213
|
-
#
|
2214
|
-
# subtype:: Returns the content subtype name as defined in [MIME-IMB].
|
2215
|
-
#
|
2216
|
-
# param:: Returns a hash that represents parameters as defined in [MIME-IMB].
|
2217
|
-
#
|
2218
|
-
# content_id:: Returns a string giving the content id as defined in [MIME-IMB].
|
2219
|
-
#
|
2220
|
-
# description:: Returns a string giving the content description as defined in
|
2221
|
-
# [MIME-IMB].
|
2222
|
-
#
|
2223
|
-
# encoding:: Returns a string giving the content transfer encoding as defined in
|
2224
|
-
# [MIME-IMB].
|
2225
|
-
#
|
2226
|
-
# size:: Returns a number giving the size of the body in octets.
|
2227
|
-
#
|
2228
|
-
# md5:: Returns a string giving the body MD5 value as defined in [MD5].
|
2229
|
-
#
|
2230
|
-
# disposition:: Returns a Net::IMAP::ContentDisposition object giving
|
2231
|
-
# the content disposition.
|
2232
|
-
#
|
2233
|
-
# language:: Returns a string or an array of strings giving the body
|
2234
|
-
# language value as defined in [LANGUAGE-TAGS].
|
2235
|
-
#
|
2236
|
-
# extension:: Returns extension data.
|
2237
|
-
#
|
2238
|
-
# multipart?:: Returns false.
|
2239
|
-
#
|
2240
|
-
class BodyTypeBasic < Struct.new(:media_type, :subtype,
|
2241
|
-
:param, :content_id,
|
2242
|
-
:description, :encoding, :size,
|
2243
|
-
:md5, :disposition, :language,
|
2244
|
-
:extension)
|
2245
|
-
def multipart?
|
2246
|
-
return false
|
2247
|
-
end
|
2248
|
-
|
2249
|
-
# Obsolete: use +subtype+ instead. Calling this will
|
2250
|
-
# generate a warning message to +stderr+, then return
|
2251
|
-
# the value of +subtype+.
|
2252
|
-
def media_subtype
|
2253
|
-
warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
|
2254
|
-
return subtype
|
2255
|
-
end
|
2256
|
-
end
|
2257
|
-
|
2258
|
-
# Net::IMAP::BodyTypeText represents TEXT body structures of messages.
|
2259
|
-
#
|
2260
|
-
# ==== Fields:
|
2261
|
-
#
|
2262
|
-
# lines:: Returns the size of the body in text lines.
|
2263
|
-
#
|
2264
|
-
# And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
|
2265
|
-
#
|
2266
|
-
class BodyTypeText < Struct.new(:media_type, :subtype,
|
2267
|
-
:param, :content_id,
|
2268
|
-
:description, :encoding, :size,
|
2269
|
-
:lines,
|
2270
|
-
:md5, :disposition, :language,
|
2271
|
-
:extension)
|
2272
|
-
def multipart?
|
2273
|
-
return false
|
2274
|
-
end
|
2275
|
-
|
2276
|
-
# Obsolete: use +subtype+ instead. Calling this will
|
2277
|
-
# generate a warning message to +stderr+, then return
|
2278
|
-
# the value of +subtype+.
|
2279
|
-
def media_subtype
|
2280
|
-
warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
|
2281
|
-
return subtype
|
2282
|
-
end
|
2283
|
-
end
|
2284
|
-
|
2285
|
-
# Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
|
2286
|
-
#
|
2287
|
-
# ==== Fields:
|
2288
|
-
#
|
2289
|
-
# envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
|
2290
|
-
#
|
2291
|
-
# body:: Returns an object giving the body structure.
|
2292
|
-
#
|
2293
|
-
# And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
|
2294
|
-
#
|
2295
|
-
class BodyTypeMessage < Struct.new(:media_type, :subtype,
|
2296
|
-
:param, :content_id,
|
2297
|
-
:description, :encoding, :size,
|
2298
|
-
:envelope, :body, :lines,
|
2299
|
-
:md5, :disposition, :language,
|
2300
|
-
:extension)
|
2301
|
-
def multipart?
|
2302
|
-
return false
|
2303
|
-
end
|
2304
|
-
|
2305
|
-
# Obsolete: use +subtype+ instead. Calling this will
|
2306
|
-
# generate a warning message to +stderr+, then return
|
2307
|
-
# the value of +subtype+.
|
2308
|
-
def media_subtype
|
2309
|
-
warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
|
2310
|
-
return subtype
|
2311
|
-
end
|
2312
|
-
end
|
2313
|
-
|
2314
|
-
# Net::IMAP::BodyTypeAttachment represents attachment body structures
|
2315
|
-
# of messages.
|
2316
|
-
#
|
2317
|
-
# ==== Fields:
|
2318
|
-
#
|
2319
|
-
# media_type:: Returns the content media type name.
|
2320
|
-
#
|
2321
|
-
# subtype:: Returns +nil+.
|
2322
|
-
#
|
2323
|
-
# param:: Returns a hash that represents parameters.
|
2324
|
-
#
|
2325
|
-
# multipart?:: Returns false.
|
2326
|
-
#
|
2327
|
-
class BodyTypeAttachment < Struct.new(:media_type, :subtype,
|
2328
|
-
:param)
|
2329
|
-
def multipart?
|
2330
|
-
return false
|
2331
|
-
end
|
2332
|
-
end
|
2333
|
-
|
2334
|
-
# Net::IMAP::BodyTypeMultipart represents multipart body structures
|
2335
|
-
# of messages.
|
2336
|
-
#
|
2337
|
-
# ==== Fields:
|
2338
|
-
#
|
2339
|
-
# media_type:: Returns the content media type name as defined in [MIME-IMB].
|
2340
|
-
#
|
2341
|
-
# subtype:: Returns the content subtype name as defined in [MIME-IMB].
|
2342
|
-
#
|
2343
|
-
# parts:: Returns multiple parts.
|
2344
|
-
#
|
2345
|
-
# param:: Returns a hash that represents parameters as defined in [MIME-IMB].
|
2346
|
-
#
|
2347
|
-
# disposition:: Returns a Net::IMAP::ContentDisposition object giving
|
2348
|
-
# the content disposition.
|
2349
|
-
#
|
2350
|
-
# language:: Returns a string or an array of strings giving the body
|
2351
|
-
# language value as defined in [LANGUAGE-TAGS].
|
2352
|
-
#
|
2353
|
-
# extension:: Returns extension data.
|
2354
|
-
#
|
2355
|
-
# multipart?:: Returns true.
|
2356
|
-
#
|
2357
|
-
class BodyTypeMultipart < Struct.new(:media_type, :subtype,
|
2358
|
-
:parts,
|
2359
|
-
:param, :disposition, :language,
|
2360
|
-
:extension)
|
2361
|
-
def multipart?
|
2362
|
-
return true
|
2363
|
-
end
|
2364
|
-
|
2365
|
-
# Obsolete: use +subtype+ instead. Calling this will
|
2366
|
-
# generate a warning message to +stderr+, then return
|
2367
|
-
# the value of +subtype+.
|
2368
|
-
def media_subtype
|
2369
|
-
warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
|
2370
|
-
return subtype
|
2371
|
-
end
|
2372
|
-
end
|
2373
|
-
|
2374
|
-
class BodyTypeExtension < Struct.new(:media_type, :subtype,
|
2375
|
-
:params, :content_id,
|
2376
|
-
:description, :encoding, :size)
|
2377
|
-
def multipart?
|
2378
|
-
return false
|
2379
|
-
end
|
2380
|
-
end
|
2381
|
-
|
2382
|
-
class ResponseParser # :nodoc:
|
2383
|
-
def initialize
|
2384
|
-
@str = nil
|
2385
|
-
@pos = nil
|
2386
|
-
@lex_state = nil
|
2387
|
-
@token = nil
|
2388
|
-
@flag_symbols = {}
|
2389
|
-
end
|
2390
|
-
|
2391
|
-
def parse(str)
|
2392
|
-
@str = str
|
2393
|
-
@pos = 0
|
2394
|
-
@lex_state = EXPR_BEG
|
2395
|
-
@token = nil
|
2396
|
-
return response
|
2397
|
-
end
|
2398
|
-
|
2399
|
-
private
|
2400
|
-
|
2401
|
-
EXPR_BEG = :EXPR_BEG
|
2402
|
-
EXPR_DATA = :EXPR_DATA
|
2403
|
-
EXPR_TEXT = :EXPR_TEXT
|
2404
|
-
EXPR_RTEXT = :EXPR_RTEXT
|
2405
|
-
EXPR_CTEXT = :EXPR_CTEXT
|
2406
|
-
|
2407
|
-
T_SPACE = :SPACE
|
2408
|
-
T_NIL = :NIL
|
2409
|
-
T_NUMBER = :NUMBER
|
2410
|
-
T_ATOM = :ATOM
|
2411
|
-
T_QUOTED = :QUOTED
|
2412
|
-
T_LPAR = :LPAR
|
2413
|
-
T_RPAR = :RPAR
|
2414
|
-
T_BSLASH = :BSLASH
|
2415
|
-
T_STAR = :STAR
|
2416
|
-
T_LBRA = :LBRA
|
2417
|
-
T_RBRA = :RBRA
|
2418
|
-
T_LITERAL = :LITERAL
|
2419
|
-
T_PLUS = :PLUS
|
2420
|
-
T_PERCENT = :PERCENT
|
2421
|
-
T_CRLF = :CRLF
|
2422
|
-
T_EOF = :EOF
|
2423
|
-
T_TEXT = :TEXT
|
2424
|
-
|
2425
|
-
BEG_REGEXP = /\G(?:\
|
2426
|
-
(?# 1: SPACE )( +)|\
|
2427
|
-
(?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
|
2428
|
-
(?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
|
2429
|
-
(?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
|
2430
|
-
(?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
|
2431
|
-
(?# 6: LPAR )(\()|\
|
2432
|
-
(?# 7: RPAR )(\))|\
|
2433
|
-
(?# 8: BSLASH )(\\)|\
|
2434
|
-
(?# 9: STAR )(\*)|\
|
2435
|
-
(?# 10: LBRA )(\[)|\
|
2436
|
-
(?# 11: RBRA )(\])|\
|
2437
|
-
(?# 12: LITERAL )\{(\d+)\}\r\n|\
|
2438
|
-
(?# 13: PLUS )(\+)|\
|
2439
|
-
(?# 14: PERCENT )(%)|\
|
2440
|
-
(?# 15: CRLF )(\r\n)|\
|
2441
|
-
(?# 16: EOF )(\z))/ni
|
2442
|
-
|
2443
|
-
DATA_REGEXP = /\G(?:\
|
2444
|
-
(?# 1: SPACE )( )|\
|
2445
|
-
(?# 2: NIL )(NIL)|\
|
2446
|
-
(?# 3: NUMBER )(\d+)|\
|
2447
|
-
(?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
|
2448
|
-
(?# 5: LITERAL )\{(\d+)\}\r\n|\
|
2449
|
-
(?# 6: LPAR )(\()|\
|
2450
|
-
(?# 7: RPAR )(\)))/ni
|
2451
|
-
|
2452
|
-
TEXT_REGEXP = /\G(?:\
|
2453
|
-
(?# 1: TEXT )([^\x00\r\n]*))/ni
|
2454
|
-
|
2455
|
-
RTEXT_REGEXP = /\G(?:\
|
2456
|
-
(?# 1: LBRA )(\[)|\
|
2457
|
-
(?# 2: TEXT )([^\x00\r\n]*))/ni
|
2458
|
-
|
2459
|
-
CTEXT_REGEXP = /\G(?:\
|
2460
|
-
(?# 1: TEXT )([^\x00\r\n\]]*))/ni
|
2461
|
-
|
2462
|
-
Token = Struct.new(:symbol, :value)
|
2463
|
-
|
2464
|
-
def response
|
2465
|
-
token = lookahead
|
2466
|
-
case token.symbol
|
2467
|
-
when T_PLUS
|
2468
|
-
result = continue_req
|
2469
|
-
when T_STAR
|
2470
|
-
result = response_untagged
|
2471
|
-
else
|
2472
|
-
result = response_tagged
|
2473
|
-
end
|
2474
|
-
while lookahead.symbol == T_SPACE
|
2475
|
-
# Ignore trailing space for Microsoft Exchange Server
|
2476
|
-
shift_token
|
2477
|
-
end
|
2478
|
-
match(T_CRLF)
|
2479
|
-
match(T_EOF)
|
2480
|
-
return result
|
2481
|
-
end
|
2482
|
-
|
2483
|
-
def continue_req
|
2484
|
-
match(T_PLUS)
|
2485
|
-
token = lookahead
|
2486
|
-
if token.symbol == T_SPACE
|
2487
|
-
shift_token
|
2488
|
-
return ContinuationRequest.new(resp_text, @str)
|
2489
|
-
else
|
2490
|
-
return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
|
2491
|
-
end
|
2492
|
-
end
|
2493
|
-
|
2494
|
-
def response_untagged
|
2495
|
-
match(T_STAR)
|
2496
|
-
match(T_SPACE)
|
2497
|
-
token = lookahead
|
2498
|
-
if token.symbol == T_NUMBER
|
2499
|
-
return numeric_response
|
2500
|
-
elsif token.symbol == T_ATOM
|
2501
|
-
case token.value
|
2502
|
-
when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
|
2503
|
-
return response_cond
|
2504
|
-
when /\A(?:FLAGS)\z/ni
|
2505
|
-
return flags_response
|
2506
|
-
when /\A(?:ID)\z/ni
|
2507
|
-
return id_response
|
2508
|
-
when /\A(?:LIST|LSUB|XLIST)\z/ni
|
2509
|
-
return list_response
|
2510
|
-
when /\A(?:NAMESPACE)\z/ni
|
2511
|
-
return namespace_response
|
2512
|
-
when /\A(?:QUOTA)\z/ni
|
2513
|
-
return getquota_response
|
2514
|
-
when /\A(?:QUOTAROOT)\z/ni
|
2515
|
-
return getquotaroot_response
|
2516
|
-
when /\A(?:ACL)\z/ni
|
2517
|
-
return getacl_response
|
2518
|
-
when /\A(?:SEARCH|SORT)\z/ni
|
2519
|
-
return search_response
|
2520
|
-
when /\A(?:THREAD)\z/ni
|
2521
|
-
return thread_response
|
2522
|
-
when /\A(?:STATUS)\z/ni
|
2523
|
-
return status_response
|
2524
|
-
when /\A(?:CAPABILITY)\z/ni
|
2525
|
-
return capability_response
|
2526
|
-
when /\A(?:NOOP)\z/ni
|
2527
|
-
return ignored_response
|
2528
|
-
else
|
2529
|
-
return text_response
|
2530
|
-
end
|
2531
|
-
else
|
2532
|
-
parse_error("unexpected token %s", token.symbol)
|
2533
|
-
end
|
2534
|
-
end
|
2535
|
-
|
2536
|
-
def response_tagged
|
2537
|
-
tag = astring_chars
|
2538
|
-
match(T_SPACE)
|
2539
|
-
token = match(T_ATOM)
|
2540
|
-
name = token.value.upcase
|
2541
|
-
match(T_SPACE)
|
2542
|
-
return TaggedResponse.new(tag, name, resp_text, @str)
|
2543
|
-
end
|
2544
|
-
|
2545
|
-
def response_cond
|
2546
|
-
token = match(T_ATOM)
|
2547
|
-
name = token.value.upcase
|
2548
|
-
match(T_SPACE)
|
2549
|
-
return UntaggedResponse.new(name, resp_text, @str)
|
2550
|
-
end
|
2551
|
-
|
2552
|
-
def numeric_response
|
2553
|
-
n = number
|
2554
|
-
match(T_SPACE)
|
2555
|
-
token = match(T_ATOM)
|
2556
|
-
name = token.value.upcase
|
2557
|
-
case name
|
2558
|
-
when "EXISTS", "RECENT", "EXPUNGE"
|
2559
|
-
return UntaggedResponse.new(name, n, @str)
|
2560
|
-
when "FETCH"
|
2561
|
-
shift_token
|
2562
|
-
match(T_SPACE)
|
2563
|
-
data = FetchData.new(n, msg_att(n))
|
2564
|
-
return UntaggedResponse.new(name, data, @str)
|
2565
|
-
end
|
2566
|
-
end
|
2567
|
-
|
2568
|
-
def msg_att(n)
|
2569
|
-
match(T_LPAR)
|
2570
|
-
attr = {}
|
2571
|
-
while true
|
2572
|
-
token = lookahead
|
2573
|
-
case token.symbol
|
2574
|
-
when T_RPAR
|
2575
|
-
shift_token
|
2576
|
-
break
|
2577
|
-
when T_SPACE
|
2578
|
-
shift_token
|
2579
|
-
next
|
2580
|
-
end
|
2581
|
-
case token.value
|
2582
|
-
when /\A(?:ENVELOPE)\z/ni
|
2583
|
-
name, val = envelope_data
|
2584
|
-
when /\A(?:FLAGS)\z/ni
|
2585
|
-
name, val = flags_data
|
2586
|
-
when /\A(?:INTERNALDATE)\z/ni
|
2587
|
-
name, val = internaldate_data
|
2588
|
-
when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
|
2589
|
-
name, val = rfc822_text
|
2590
|
-
when /\A(?:RFC822\.SIZE)\z/ni
|
2591
|
-
name, val = rfc822_size
|
2592
|
-
when /\A(?:BODY(?:STRUCTURE)?)\z/ni
|
2593
|
-
name, val = body_data
|
2594
|
-
when /\A(?:UID)\z/ni
|
2595
|
-
name, val = uid_data
|
2596
|
-
when /\A(?:MODSEQ)\z/ni
|
2597
|
-
name, val = modseq_data
|
2598
|
-
else
|
2599
|
-
parse_error("unknown attribute `%s' for {%d}", token.value, n)
|
2600
|
-
end
|
2601
|
-
attr[name] = val
|
2602
|
-
end
|
2603
|
-
return attr
|
2604
|
-
end
|
2605
|
-
|
2606
|
-
def envelope_data
|
2607
|
-
token = match(T_ATOM)
|
2608
|
-
name = token.value.upcase
|
2609
|
-
match(T_SPACE)
|
2610
|
-
return name, envelope
|
2611
|
-
end
|
2612
|
-
|
2613
|
-
def envelope
|
2614
|
-
@lex_state = EXPR_DATA
|
2615
|
-
token = lookahead
|
2616
|
-
if token.symbol == T_NIL
|
2617
|
-
shift_token
|
2618
|
-
result = nil
|
2619
|
-
else
|
2620
|
-
match(T_LPAR)
|
2621
|
-
date = nstring
|
2622
|
-
match(T_SPACE)
|
2623
|
-
subject = nstring
|
2624
|
-
match(T_SPACE)
|
2625
|
-
from = address_list
|
2626
|
-
match(T_SPACE)
|
2627
|
-
sender = address_list
|
2628
|
-
match(T_SPACE)
|
2629
|
-
reply_to = address_list
|
2630
|
-
match(T_SPACE)
|
2631
|
-
to = address_list
|
2632
|
-
match(T_SPACE)
|
2633
|
-
cc = address_list
|
2634
|
-
match(T_SPACE)
|
2635
|
-
bcc = address_list
|
2636
|
-
match(T_SPACE)
|
2637
|
-
in_reply_to = nstring
|
2638
|
-
match(T_SPACE)
|
2639
|
-
message_id = nstring
|
2640
|
-
match(T_RPAR)
|
2641
|
-
result = Envelope.new(date, subject, from, sender, reply_to,
|
2642
|
-
to, cc, bcc, in_reply_to, message_id)
|
2643
|
-
end
|
2644
|
-
@lex_state = EXPR_BEG
|
2645
|
-
return result
|
2646
|
-
end
|
2647
|
-
|
2648
|
-
def flags_data
|
2649
|
-
token = match(T_ATOM)
|
2650
|
-
name = token.value.upcase
|
2651
|
-
match(T_SPACE)
|
2652
|
-
return name, flag_list
|
2653
|
-
end
|
2654
|
-
|
2655
|
-
def internaldate_data
|
2656
|
-
token = match(T_ATOM)
|
2657
|
-
name = token.value.upcase
|
2658
|
-
match(T_SPACE)
|
2659
|
-
token = match(T_QUOTED)
|
2660
|
-
return name, token.value
|
2661
|
-
end
|
2662
|
-
|
2663
|
-
def rfc822_text
|
2664
|
-
token = match(T_ATOM)
|
2665
|
-
name = token.value.upcase
|
2666
|
-
token = lookahead
|
2667
|
-
if token.symbol == T_LBRA
|
2668
|
-
shift_token
|
2669
|
-
match(T_RBRA)
|
2670
|
-
end
|
2671
|
-
match(T_SPACE)
|
2672
|
-
return name, nstring
|
2673
|
-
end
|
2674
|
-
|
2675
|
-
def rfc822_size
|
2676
|
-
token = match(T_ATOM)
|
2677
|
-
name = token.value.upcase
|
2678
|
-
match(T_SPACE)
|
2679
|
-
return name, number
|
2680
|
-
end
|
2681
|
-
|
2682
|
-
def body_data
|
2683
|
-
token = match(T_ATOM)
|
2684
|
-
name = token.value.upcase
|
2685
|
-
token = lookahead
|
2686
|
-
if token.symbol == T_SPACE
|
2687
|
-
shift_token
|
2688
|
-
return name, body
|
2689
|
-
end
|
2690
|
-
name.concat(section)
|
2691
|
-
token = lookahead
|
2692
|
-
if token.symbol == T_ATOM
|
2693
|
-
name.concat(token.value)
|
2694
|
-
shift_token
|
2695
|
-
end
|
2696
|
-
match(T_SPACE)
|
2697
|
-
data = nstring
|
2698
|
-
return name, data
|
2699
|
-
end
|
2700
|
-
|
2701
|
-
def body
|
2702
|
-
@lex_state = EXPR_DATA
|
2703
|
-
token = lookahead
|
2704
|
-
if token.symbol == T_NIL
|
2705
|
-
shift_token
|
2706
|
-
result = nil
|
2707
|
-
else
|
2708
|
-
match(T_LPAR)
|
2709
|
-
token = lookahead
|
2710
|
-
if token.symbol == T_LPAR
|
2711
|
-
result = body_type_mpart
|
2712
|
-
else
|
2713
|
-
result = body_type_1part
|
2714
|
-
end
|
2715
|
-
match(T_RPAR)
|
2716
|
-
end
|
2717
|
-
@lex_state = EXPR_BEG
|
2718
|
-
return result
|
2719
|
-
end
|
2720
|
-
|
2721
|
-
def body_type_1part
|
2722
|
-
token = lookahead
|
2723
|
-
case token.value
|
2724
|
-
when /\A(?:TEXT)\z/ni
|
2725
|
-
return body_type_text
|
2726
|
-
when /\A(?:MESSAGE)\z/ni
|
2727
|
-
return body_type_msg
|
2728
|
-
when /\A(?:ATTACHMENT)\z/ni
|
2729
|
-
return body_type_attachment
|
2730
|
-
when /\A(?:MIXED)\z/ni
|
2731
|
-
return body_type_mixed
|
2732
|
-
else
|
2733
|
-
return body_type_basic
|
2734
|
-
end
|
2735
|
-
end
|
2736
|
-
|
2737
|
-
def body_type_basic
|
2738
|
-
mtype, msubtype = media_type
|
2739
|
-
token = lookahead
|
2740
|
-
if token.symbol == T_RPAR
|
2741
|
-
return BodyTypeBasic.new(mtype, msubtype)
|
2742
|
-
end
|
2743
|
-
match(T_SPACE)
|
2744
|
-
param, content_id, desc, enc, size = body_fields
|
2745
|
-
md5, disposition, language, extension = body_ext_1part
|
2746
|
-
return BodyTypeBasic.new(mtype, msubtype,
|
2747
|
-
param, content_id,
|
2748
|
-
desc, enc, size,
|
2749
|
-
md5, disposition, language, extension)
|
2750
|
-
end
|
2751
|
-
|
2752
|
-
def body_type_text
|
2753
|
-
mtype, msubtype = media_type
|
2754
|
-
match(T_SPACE)
|
2755
|
-
param, content_id, desc, enc, size = body_fields
|
2756
|
-
match(T_SPACE)
|
2757
|
-
lines = number
|
2758
|
-
md5, disposition, language, extension = body_ext_1part
|
2759
|
-
return BodyTypeText.new(mtype, msubtype,
|
2760
|
-
param, content_id,
|
2761
|
-
desc, enc, size,
|
2762
|
-
lines,
|
2763
|
-
md5, disposition, language, extension)
|
2764
|
-
end
|
2765
|
-
|
2766
|
-
def body_type_msg
|
2767
|
-
mtype, msubtype = media_type
|
2768
|
-
match(T_SPACE)
|
2769
|
-
param, content_id, desc, enc, size = body_fields
|
2770
|
-
|
2771
|
-
token = lookahead
|
2772
|
-
if token.symbol == T_RPAR
|
2773
|
-
# If this is not message/rfc822, we shouldn't apply the RFC822
|
2774
|
-
# spec to it. We should handle anything other than
|
2775
|
-
# message/rfc822 using multipart extension data [rfc3501] (i.e.
|
2776
|
-
# the data itself won't be returned, we would have to retrieve it
|
2777
|
-
# with BODYSTRUCTURE instead of with BODY
|
2778
|
-
|
2779
|
-
# Also, sometimes a message/rfc822 is included as a large
|
2780
|
-
# attachment instead of having all of the other details
|
2781
|
-
# (e.g. attaching a .eml file to an email)
|
2782
|
-
if msubtype == "RFC822"
|
2783
|
-
return BodyTypeMessage.new(mtype, msubtype, param, content_id,
|
2784
|
-
desc, enc, size, nil, nil, nil, nil,
|
2785
|
-
nil, nil, nil)
|
2786
|
-
else
|
2787
|
-
return BodyTypeExtension.new(mtype, msubtype,
|
2788
|
-
param, content_id,
|
2789
|
-
desc, enc, size)
|
2790
|
-
end
|
2791
|
-
end
|
2792
|
-
|
2793
|
-
match(T_SPACE)
|
2794
|
-
env = envelope
|
2795
|
-
match(T_SPACE)
|
2796
|
-
b = body
|
2797
|
-
match(T_SPACE)
|
2798
|
-
lines = number
|
2799
|
-
md5, disposition, language, extension = body_ext_1part
|
2800
|
-
return BodyTypeMessage.new(mtype, msubtype,
|
2801
|
-
param, content_id,
|
2802
|
-
desc, enc, size,
|
2803
|
-
env, b, lines,
|
2804
|
-
md5, disposition, language, extension)
|
2805
|
-
end
|
2806
|
-
|
2807
|
-
def body_type_attachment
|
2808
|
-
mtype = case_insensitive_string
|
2809
|
-
match(T_SPACE)
|
2810
|
-
param = body_fld_param
|
2811
|
-
return BodyTypeAttachment.new(mtype, nil, param)
|
2812
|
-
end
|
2813
|
-
|
2814
|
-
def body_type_mixed
|
2815
|
-
mtype = "MULTIPART"
|
2816
|
-
msubtype = case_insensitive_string
|
2817
|
-
param, disposition, language, extension = body_ext_mpart
|
2818
|
-
return BodyTypeBasic.new(mtype, msubtype, param, nil, nil, nil, nil, nil, disposition, language, extension)
|
2819
|
-
end
|
2820
|
-
|
2821
|
-
def body_type_mpart
|
2822
|
-
parts = []
|
2823
|
-
while true
|
2824
|
-
token = lookahead
|
2825
|
-
if token.symbol == T_SPACE
|
2826
|
-
shift_token
|
2827
|
-
break
|
2828
|
-
end
|
2829
|
-
parts.push(body)
|
2830
|
-
end
|
2831
|
-
mtype = "MULTIPART"
|
2832
|
-
msubtype = case_insensitive_string
|
2833
|
-
param, disposition, language, extension = body_ext_mpart
|
2834
|
-
return BodyTypeMultipart.new(mtype, msubtype, parts,
|
2835
|
-
param, disposition, language,
|
2836
|
-
extension)
|
2837
|
-
end
|
2838
|
-
|
2839
|
-
def media_type
|
2840
|
-
mtype = case_insensitive_string
|
2841
|
-
token = lookahead
|
2842
|
-
if token.symbol != T_SPACE
|
2843
|
-
return mtype, nil
|
2844
|
-
end
|
2845
|
-
match(T_SPACE)
|
2846
|
-
msubtype = case_insensitive_string
|
2847
|
-
return mtype, msubtype
|
2848
|
-
end
|
2849
|
-
|
2850
|
-
def body_fields
|
2851
|
-
param = body_fld_param
|
2852
|
-
match(T_SPACE)
|
2853
|
-
content_id = nstring
|
2854
|
-
match(T_SPACE)
|
2855
|
-
desc = nstring
|
2856
|
-
match(T_SPACE)
|
2857
|
-
enc = case_insensitive_string
|
2858
|
-
match(T_SPACE)
|
2859
|
-
size = number
|
2860
|
-
return param, content_id, desc, enc, size
|
2861
|
-
end
|
2862
|
-
|
2863
|
-
def body_fld_param
|
2864
|
-
token = lookahead
|
2865
|
-
if token.symbol == T_NIL
|
2866
|
-
shift_token
|
2867
|
-
return nil
|
2868
|
-
end
|
2869
|
-
match(T_LPAR)
|
2870
|
-
param = {}
|
2871
|
-
while true
|
2872
|
-
token = lookahead
|
2873
|
-
case token.symbol
|
2874
|
-
when T_RPAR
|
2875
|
-
shift_token
|
2876
|
-
break
|
2877
|
-
when T_SPACE
|
2878
|
-
shift_token
|
2879
|
-
end
|
2880
|
-
name = case_insensitive_string
|
2881
|
-
match(T_SPACE)
|
2882
|
-
val = string
|
2883
|
-
param[name] = val
|
2884
|
-
end
|
2885
|
-
return param
|
2886
|
-
end
|
2887
|
-
|
2888
|
-
def body_ext_1part
|
2889
|
-
token = lookahead
|
2890
|
-
if token.symbol == T_SPACE
|
2891
|
-
shift_token
|
2892
|
-
else
|
2893
|
-
return nil
|
2894
|
-
end
|
2895
|
-
md5 = nstring
|
2896
|
-
|
2897
|
-
token = lookahead
|
2898
|
-
if token.symbol == T_SPACE
|
2899
|
-
shift_token
|
2900
|
-
else
|
2901
|
-
return md5
|
2902
|
-
end
|
2903
|
-
disposition = body_fld_dsp
|
2904
|
-
|
2905
|
-
token = lookahead
|
2906
|
-
if token.symbol == T_SPACE
|
2907
|
-
shift_token
|
2908
|
-
else
|
2909
|
-
return md5, disposition
|
2910
|
-
end
|
2911
|
-
language = body_fld_lang
|
2912
|
-
|
2913
|
-
token = lookahead
|
2914
|
-
if token.symbol == T_SPACE
|
2915
|
-
shift_token
|
2916
|
-
else
|
2917
|
-
return md5, disposition, language
|
2918
|
-
end
|
2919
|
-
|
2920
|
-
extension = body_extensions
|
2921
|
-
return md5, disposition, language, extension
|
2922
|
-
end
|
2923
|
-
|
2924
|
-
def body_ext_mpart
|
2925
|
-
token = lookahead
|
2926
|
-
if token.symbol == T_SPACE
|
2927
|
-
shift_token
|
2928
|
-
else
|
2929
|
-
return nil
|
2930
|
-
end
|
2931
|
-
param = body_fld_param
|
2932
|
-
|
2933
|
-
token = lookahead
|
2934
|
-
if token.symbol == T_SPACE
|
2935
|
-
shift_token
|
2936
|
-
else
|
2937
|
-
return param
|
2938
|
-
end
|
2939
|
-
disposition = body_fld_dsp
|
2940
|
-
|
2941
|
-
token = lookahead
|
2942
|
-
if token.symbol == T_SPACE
|
2943
|
-
shift_token
|
2944
|
-
else
|
2945
|
-
return param, disposition
|
2946
|
-
end
|
2947
|
-
language = body_fld_lang
|
2948
|
-
|
2949
|
-
token = lookahead
|
2950
|
-
if token.symbol == T_SPACE
|
2951
|
-
shift_token
|
2952
|
-
else
|
2953
|
-
return param, disposition, language
|
2954
|
-
end
|
2955
|
-
|
2956
|
-
extension = body_extensions
|
2957
|
-
return param, disposition, language, extension
|
2958
|
-
end
|
2959
|
-
|
2960
|
-
def body_fld_dsp
|
2961
|
-
token = lookahead
|
2962
|
-
if token.symbol == T_NIL
|
2963
|
-
shift_token
|
2964
|
-
return nil
|
2965
|
-
end
|
2966
|
-
match(T_LPAR)
|
2967
|
-
dsp_type = case_insensitive_string
|
2968
|
-
match(T_SPACE)
|
2969
|
-
param = body_fld_param
|
2970
|
-
match(T_RPAR)
|
2971
|
-
return ContentDisposition.new(dsp_type, param)
|
2972
|
-
end
|
2973
|
-
|
2974
|
-
def body_fld_lang
|
2975
|
-
token = lookahead
|
2976
|
-
if token.symbol == T_LPAR
|
2977
|
-
shift_token
|
2978
|
-
result = []
|
2979
|
-
while true
|
2980
|
-
token = lookahead
|
2981
|
-
case token.symbol
|
2982
|
-
when T_RPAR
|
2983
|
-
shift_token
|
2984
|
-
return result
|
2985
|
-
when T_SPACE
|
2986
|
-
shift_token
|
2987
|
-
end
|
2988
|
-
result.push(case_insensitive_string)
|
2989
|
-
end
|
2990
|
-
else
|
2991
|
-
lang = nstring
|
2992
|
-
if lang
|
2993
|
-
return lang.upcase
|
2994
|
-
else
|
2995
|
-
return lang
|
2996
|
-
end
|
2997
|
-
end
|
2998
|
-
end
|
2999
|
-
|
3000
|
-
def body_extensions
|
3001
|
-
result = []
|
3002
|
-
while true
|
3003
|
-
token = lookahead
|
3004
|
-
case token.symbol
|
3005
|
-
when T_RPAR
|
3006
|
-
return result
|
3007
|
-
when T_SPACE
|
3008
|
-
shift_token
|
3009
|
-
end
|
3010
|
-
result.push(body_extension)
|
3011
|
-
end
|
3012
|
-
end
|
3013
|
-
|
3014
|
-
def body_extension
|
3015
|
-
token = lookahead
|
3016
|
-
case token.symbol
|
3017
|
-
when T_LPAR
|
3018
|
-
shift_token
|
3019
|
-
result = body_extensions
|
3020
|
-
match(T_RPAR)
|
3021
|
-
return result
|
3022
|
-
when T_NUMBER
|
3023
|
-
return number
|
3024
|
-
else
|
3025
|
-
return nstring
|
3026
|
-
end
|
3027
|
-
end
|
3028
|
-
|
3029
|
-
def section
|
3030
|
-
str = String.new
|
3031
|
-
token = match(T_LBRA)
|
3032
|
-
str.concat(token.value)
|
3033
|
-
token = match(T_ATOM, T_NUMBER, T_RBRA)
|
3034
|
-
if token.symbol == T_RBRA
|
3035
|
-
str.concat(token.value)
|
3036
|
-
return str
|
3037
|
-
end
|
3038
|
-
str.concat(token.value)
|
3039
|
-
token = lookahead
|
3040
|
-
if token.symbol == T_SPACE
|
3041
|
-
shift_token
|
3042
|
-
str.concat(token.value)
|
3043
|
-
token = match(T_LPAR)
|
3044
|
-
str.concat(token.value)
|
3045
|
-
while true
|
3046
|
-
token = lookahead
|
3047
|
-
case token.symbol
|
3048
|
-
when T_RPAR
|
3049
|
-
str.concat(token.value)
|
3050
|
-
shift_token
|
3051
|
-
break
|
3052
|
-
when T_SPACE
|
3053
|
-
shift_token
|
3054
|
-
str.concat(token.value)
|
3055
|
-
end
|
3056
|
-
str.concat(format_string(astring))
|
3057
|
-
end
|
3058
|
-
end
|
3059
|
-
token = match(T_RBRA)
|
3060
|
-
str.concat(token.value)
|
3061
|
-
return str
|
3062
|
-
end
|
3063
|
-
|
3064
|
-
def format_string(str)
|
3065
|
-
case str
|
3066
|
-
when ""
|
3067
|
-
return '""'
|
3068
|
-
when /[\x80-\xff\r\n]/n
|
3069
|
-
# literal
|
3070
|
-
return "{" + str.bytesize.to_s + "}" + CRLF + str
|
3071
|
-
when /[(){ \x00-\x1f\x7f%*"\\]/n
|
3072
|
-
# quoted string
|
3073
|
-
return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
|
3074
|
-
else
|
3075
|
-
# atom
|
3076
|
-
return str
|
3077
|
-
end
|
3078
|
-
end
|
3079
|
-
|
3080
|
-
def uid_data
|
3081
|
-
token = match(T_ATOM)
|
3082
|
-
name = token.value.upcase
|
3083
|
-
match(T_SPACE)
|
3084
|
-
return name, number
|
3085
|
-
end
|
3086
|
-
|
3087
|
-
def modseq_data
|
3088
|
-
token = match(T_ATOM)
|
3089
|
-
name = token.value.upcase
|
3090
|
-
match(T_SPACE)
|
3091
|
-
match(T_LPAR)
|
3092
|
-
modseq = number
|
3093
|
-
match(T_RPAR)
|
3094
|
-
return name, modseq
|
3095
|
-
end
|
3096
|
-
|
3097
|
-
def ignored_response
|
3098
|
-
while lookahead.symbol != T_CRLF
|
3099
|
-
shift_token
|
3100
|
-
end
|
3101
|
-
return IgnoredResponse.new(@str)
|
3102
|
-
end
|
3103
|
-
|
3104
|
-
def text_response
|
3105
|
-
token = match(T_ATOM)
|
3106
|
-
name = token.value.upcase
|
3107
|
-
match(T_SPACE)
|
3108
|
-
return UntaggedResponse.new(name, text)
|
3109
|
-
end
|
3110
|
-
|
3111
|
-
def flags_response
|
3112
|
-
token = match(T_ATOM)
|
3113
|
-
name = token.value.upcase
|
3114
|
-
match(T_SPACE)
|
3115
|
-
return UntaggedResponse.new(name, flag_list, @str)
|
3116
|
-
end
|
3117
|
-
|
3118
|
-
def list_response
|
3119
|
-
token = match(T_ATOM)
|
3120
|
-
name = token.value.upcase
|
3121
|
-
match(T_SPACE)
|
3122
|
-
return UntaggedResponse.new(name, mailbox_list, @str)
|
3123
|
-
end
|
3124
|
-
|
3125
|
-
def mailbox_list
|
3126
|
-
attr = flag_list
|
3127
|
-
match(T_SPACE)
|
3128
|
-
token = match(T_QUOTED, T_NIL)
|
3129
|
-
if token.symbol == T_NIL
|
3130
|
-
delim = nil
|
3131
|
-
else
|
3132
|
-
delim = token.value
|
3133
|
-
end
|
3134
|
-
match(T_SPACE)
|
3135
|
-
name = astring
|
3136
|
-
return MailboxList.new(attr, delim, name)
|
3137
|
-
end
|
3138
|
-
|
3139
|
-
def getquota_response
|
3140
|
-
# If quota never established, get back
|
3141
|
-
# `NO Quota root does not exist'.
|
3142
|
-
# If quota removed, get `()' after the
|
3143
|
-
# folder spec with no mention of `STORAGE'.
|
3144
|
-
token = match(T_ATOM)
|
3145
|
-
name = token.value.upcase
|
3146
|
-
match(T_SPACE)
|
3147
|
-
mailbox = astring
|
3148
|
-
match(T_SPACE)
|
3149
|
-
match(T_LPAR)
|
3150
|
-
token = lookahead
|
3151
|
-
case token.symbol
|
3152
|
-
when T_RPAR
|
3153
|
-
shift_token
|
3154
|
-
data = MailboxQuota.new(mailbox, nil, nil)
|
3155
|
-
return UntaggedResponse.new(name, data, @str)
|
3156
|
-
when T_ATOM
|
3157
|
-
shift_token
|
3158
|
-
match(T_SPACE)
|
3159
|
-
token = match(T_NUMBER)
|
3160
|
-
usage = token.value
|
3161
|
-
match(T_SPACE)
|
3162
|
-
token = match(T_NUMBER)
|
3163
|
-
quota = token.value
|
3164
|
-
match(T_RPAR)
|
3165
|
-
data = MailboxQuota.new(mailbox, usage, quota)
|
3166
|
-
return UntaggedResponse.new(name, data, @str)
|
3167
|
-
else
|
3168
|
-
parse_error("unexpected token %s", token.symbol)
|
3169
|
-
end
|
3170
|
-
end
|
3171
|
-
|
3172
|
-
def getquotaroot_response
|
3173
|
-
# Similar to getquota, but only admin can use getquota.
|
3174
|
-
token = match(T_ATOM)
|
3175
|
-
name = token.value.upcase
|
3176
|
-
match(T_SPACE)
|
3177
|
-
mailbox = astring
|
3178
|
-
quotaroots = []
|
3179
|
-
while true
|
3180
|
-
token = lookahead
|
3181
|
-
break unless token.symbol == T_SPACE
|
3182
|
-
shift_token
|
3183
|
-
quotaroots.push(astring)
|
3184
|
-
end
|
3185
|
-
data = MailboxQuotaRoot.new(mailbox, quotaroots)
|
3186
|
-
return UntaggedResponse.new(name, data, @str)
|
3187
|
-
end
|
3188
|
-
|
3189
|
-
def getacl_response
|
3190
|
-
token = match(T_ATOM)
|
3191
|
-
name = token.value.upcase
|
3192
|
-
match(T_SPACE)
|
3193
|
-
mailbox = astring
|
3194
|
-
data = []
|
3195
|
-
token = lookahead
|
3196
|
-
if token.symbol == T_SPACE
|
3197
|
-
shift_token
|
3198
|
-
while true
|
3199
|
-
token = lookahead
|
3200
|
-
case token.symbol
|
3201
|
-
when T_CRLF
|
3202
|
-
break
|
3203
|
-
when T_SPACE
|
3204
|
-
shift_token
|
3205
|
-
end
|
3206
|
-
user = astring
|
3207
|
-
match(T_SPACE)
|
3208
|
-
rights = astring
|
3209
|
-
data.push(MailboxACLItem.new(user, rights, mailbox))
|
3210
|
-
end
|
3211
|
-
end
|
3212
|
-
return UntaggedResponse.new(name, data, @str)
|
3213
|
-
end
|
3214
|
-
|
3215
|
-
def search_response
|
3216
|
-
token = match(T_ATOM)
|
3217
|
-
name = token.value.upcase
|
3218
|
-
token = lookahead
|
3219
|
-
if token.symbol == T_SPACE
|
3220
|
-
shift_token
|
3221
|
-
data = []
|
3222
|
-
while true
|
3223
|
-
token = lookahead
|
3224
|
-
case token.symbol
|
3225
|
-
when T_CRLF
|
3226
|
-
break
|
3227
|
-
when T_SPACE
|
3228
|
-
shift_token
|
3229
|
-
when T_NUMBER
|
3230
|
-
data.push(number)
|
3231
|
-
when T_LPAR
|
3232
|
-
# TODO: include the MODSEQ value in a response
|
3233
|
-
shift_token
|
3234
|
-
match(T_ATOM)
|
3235
|
-
match(T_SPACE)
|
3236
|
-
match(T_NUMBER)
|
3237
|
-
match(T_RPAR)
|
3238
|
-
end
|
3239
|
-
end
|
3240
|
-
else
|
3241
|
-
data = []
|
3242
|
-
end
|
3243
|
-
return UntaggedResponse.new(name, data, @str)
|
3244
|
-
end
|
3245
|
-
|
3246
|
-
def thread_response
|
3247
|
-
token = match(T_ATOM)
|
3248
|
-
name = token.value.upcase
|
3249
|
-
token = lookahead
|
3250
|
-
|
3251
|
-
if token.symbol == T_SPACE
|
3252
|
-
threads = []
|
3253
|
-
|
3254
|
-
while true
|
3255
|
-
shift_token
|
3256
|
-
token = lookahead
|
3257
|
-
|
3258
|
-
case token.symbol
|
3259
|
-
when T_LPAR
|
3260
|
-
threads << thread_branch(token)
|
3261
|
-
when T_CRLF
|
3262
|
-
break
|
3263
|
-
end
|
3264
|
-
end
|
3265
|
-
else
|
3266
|
-
# no member
|
3267
|
-
threads = []
|
3268
|
-
end
|
3269
|
-
|
3270
|
-
return UntaggedResponse.new(name, threads, @str)
|
3271
|
-
end
|
3272
|
-
|
3273
|
-
def thread_branch(token)
|
3274
|
-
rootmember = nil
|
3275
|
-
lastmember = nil
|
3276
|
-
|
3277
|
-
while true
|
3278
|
-
shift_token # ignore first T_LPAR
|
3279
|
-
token = lookahead
|
3280
|
-
|
3281
|
-
case token.symbol
|
3282
|
-
when T_NUMBER
|
3283
|
-
# new member
|
3284
|
-
newmember = ThreadMember.new(number, [])
|
3285
|
-
if rootmember.nil?
|
3286
|
-
rootmember = newmember
|
3287
|
-
else
|
3288
|
-
lastmember.children << newmember
|
3289
|
-
end
|
3290
|
-
lastmember = newmember
|
3291
|
-
when T_SPACE
|
3292
|
-
# do nothing
|
3293
|
-
when T_LPAR
|
3294
|
-
if rootmember.nil?
|
3295
|
-
# dummy member
|
3296
|
-
lastmember = rootmember = ThreadMember.new(nil, [])
|
3297
|
-
end
|
3298
|
-
|
3299
|
-
lastmember.children << thread_branch(token)
|
3300
|
-
when T_RPAR
|
3301
|
-
break
|
3302
|
-
end
|
3303
|
-
end
|
3304
|
-
|
3305
|
-
return rootmember
|
3306
|
-
end
|
3307
|
-
|
3308
|
-
def status_response
|
3309
|
-
token = match(T_ATOM)
|
3310
|
-
name = token.value.upcase
|
3311
|
-
match(T_SPACE)
|
3312
|
-
mailbox = astring
|
3313
|
-
match(T_SPACE)
|
3314
|
-
match(T_LPAR)
|
3315
|
-
attr = {}
|
3316
|
-
while true
|
3317
|
-
token = lookahead
|
3318
|
-
case token.symbol
|
3319
|
-
when T_RPAR
|
3320
|
-
shift_token
|
3321
|
-
break
|
3322
|
-
when T_SPACE
|
3323
|
-
shift_token
|
3324
|
-
end
|
3325
|
-
token = match(T_ATOM)
|
3326
|
-
key = token.value.upcase
|
3327
|
-
match(T_SPACE)
|
3328
|
-
val = number
|
3329
|
-
attr[key] = val
|
3330
|
-
end
|
3331
|
-
data = StatusData.new(mailbox, attr)
|
3332
|
-
return UntaggedResponse.new(name, data, @str)
|
3333
|
-
end
|
3334
|
-
|
3335
|
-
def capability_response
|
3336
|
-
token = match(T_ATOM)
|
3337
|
-
name = token.value.upcase
|
3338
|
-
match(T_SPACE)
|
3339
|
-
UntaggedResponse.new(name, capability_data, @str)
|
3340
|
-
end
|
3341
|
-
|
3342
|
-
def capability_data
|
3343
|
-
data = []
|
3344
|
-
while true
|
3345
|
-
token = lookahead
|
3346
|
-
case token.symbol
|
3347
|
-
when T_CRLF, T_RBRA
|
3348
|
-
break
|
3349
|
-
when T_SPACE
|
3350
|
-
shift_token
|
3351
|
-
next
|
3352
|
-
end
|
3353
|
-
data.push(atom.upcase)
|
3354
|
-
end
|
3355
|
-
data
|
3356
|
-
end
|
3357
|
-
|
3358
|
-
def id_response
|
3359
|
-
token = match(T_ATOM)
|
3360
|
-
name = token.value.upcase
|
3361
|
-
match(T_SPACE)
|
3362
|
-
token = match(T_LPAR, T_NIL)
|
3363
|
-
if token.symbol == T_NIL
|
3364
|
-
return UntaggedResponse.new(name, nil, @str)
|
3365
|
-
else
|
3366
|
-
data = {}
|
3367
|
-
while true
|
3368
|
-
token = lookahead
|
3369
|
-
case token.symbol
|
3370
|
-
when T_RPAR
|
3371
|
-
shift_token
|
3372
|
-
break
|
3373
|
-
when T_SPACE
|
3374
|
-
shift_token
|
3375
|
-
next
|
3376
|
-
else
|
3377
|
-
key = string
|
3378
|
-
match(T_SPACE)
|
3379
|
-
val = nstring
|
3380
|
-
data[key] = val
|
3381
|
-
end
|
3382
|
-
end
|
3383
|
-
return UntaggedResponse.new(name, data, @str)
|
3384
|
-
end
|
3385
|
-
end
|
3386
|
-
|
3387
|
-
def namespace_response
|
3388
|
-
@lex_state = EXPR_DATA
|
3389
|
-
token = lookahead
|
3390
|
-
token = match(T_ATOM)
|
3391
|
-
name = token.value.upcase
|
3392
|
-
match(T_SPACE)
|
3393
|
-
personal = namespaces
|
3394
|
-
match(T_SPACE)
|
3395
|
-
other = namespaces
|
3396
|
-
match(T_SPACE)
|
3397
|
-
shared = namespaces
|
3398
|
-
@lex_state = EXPR_BEG
|
3399
|
-
data = Namespaces.new(personal, other, shared)
|
3400
|
-
return UntaggedResponse.new(name, data, @str)
|
3401
|
-
end
|
3402
|
-
|
3403
|
-
def namespaces
|
3404
|
-
token = lookahead
|
3405
|
-
# empty () is not allowed, so nil is functionally identical to empty.
|
3406
|
-
data = []
|
3407
|
-
if token.symbol == T_NIL
|
3408
|
-
shift_token
|
3409
|
-
else
|
3410
|
-
match(T_LPAR)
|
3411
|
-
loop do
|
3412
|
-
data << namespace
|
3413
|
-
break unless lookahead.symbol == T_SPACE
|
3414
|
-
shift_token
|
3415
|
-
end
|
3416
|
-
match(T_RPAR)
|
3417
|
-
end
|
3418
|
-
data
|
3419
|
-
end
|
3420
|
-
|
3421
|
-
def namespace
|
3422
|
-
match(T_LPAR)
|
3423
|
-
prefix = match(T_QUOTED, T_LITERAL).value
|
3424
|
-
match(T_SPACE)
|
3425
|
-
delimiter = string
|
3426
|
-
extensions = namespace_response_extensions
|
3427
|
-
match(T_RPAR)
|
3428
|
-
Namespace.new(prefix, delimiter, extensions)
|
3429
|
-
end
|
3430
|
-
|
3431
|
-
def namespace_response_extensions
|
3432
|
-
data = {}
|
3433
|
-
token = lookahead
|
3434
|
-
if token.symbol == T_SPACE
|
3435
|
-
shift_token
|
3436
|
-
name = match(T_QUOTED, T_LITERAL).value
|
3437
|
-
data[name] ||= []
|
3438
|
-
match(T_SPACE)
|
3439
|
-
match(T_LPAR)
|
3440
|
-
loop do
|
3441
|
-
data[name].push match(T_QUOTED, T_LITERAL).value
|
3442
|
-
break unless lookahead.symbol == T_SPACE
|
3443
|
-
shift_token
|
3444
|
-
end
|
3445
|
-
match(T_RPAR)
|
3446
|
-
end
|
3447
|
-
data
|
3448
|
-
end
|
3449
|
-
|
3450
|
-
# text = 1*TEXT-CHAR
|
3451
|
-
# TEXT-CHAR = <any CHAR except CR and LF>
|
3452
|
-
def text
|
3453
|
-
match(T_TEXT, lex_state: EXPR_TEXT).value
|
3454
|
-
end
|
3455
|
-
|
3456
|
-
# resp-text = ["[" resp-text-code "]" SP] text
|
3457
|
-
def resp_text
|
3458
|
-
token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT)
|
3459
|
-
case token.symbol
|
3460
|
-
when T_LBRA
|
3461
|
-
code = resp_text_code
|
3462
|
-
match(T_RBRA)
|
3463
|
-
accept_space # violating RFC
|
3464
|
-
ResponseText.new(code, text)
|
3465
|
-
when T_TEXT
|
3466
|
-
ResponseText.new(nil, token.value)
|
3467
|
-
end
|
3468
|
-
end
|
3469
|
-
|
3470
|
-
# See https://www.rfc-editor.org/errata/rfc3501
|
3471
|
-
#
|
3472
|
-
# resp-text-code = "ALERT" /
|
3473
|
-
# "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
|
3474
|
-
# capability-data / "PARSE" /
|
3475
|
-
# "PERMANENTFLAGS" SP "("
|
3476
|
-
# [flag-perm *(SP flag-perm)] ")" /
|
3477
|
-
# "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
|
3478
|
-
# "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
|
3479
|
-
# "UNSEEN" SP nz-number /
|
3480
|
-
# atom [SP 1*<any TEXT-CHAR except "]">]
|
3481
|
-
def resp_text_code
|
3482
|
-
token = match(T_ATOM)
|
3483
|
-
name = token.value.upcase
|
3484
|
-
case name
|
3485
|
-
when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
|
3486
|
-
result = ResponseCode.new(name, nil)
|
3487
|
-
when /\A(?:BADCHARSET)\z/n
|
3488
|
-
result = ResponseCode.new(name, charset_list)
|
3489
|
-
when /\A(?:CAPABILITY)\z/ni
|
3490
|
-
result = ResponseCode.new(name, capability_data)
|
3491
|
-
when /\A(?:PERMANENTFLAGS)\z/n
|
3492
|
-
match(T_SPACE)
|
3493
|
-
result = ResponseCode.new(name, flag_list)
|
3494
|
-
when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
|
3495
|
-
match(T_SPACE)
|
3496
|
-
result = ResponseCode.new(name, number)
|
3497
|
-
else
|
3498
|
-
token = lookahead
|
3499
|
-
if token.symbol == T_SPACE
|
3500
|
-
shift_token
|
3501
|
-
token = match(T_TEXT, lex_state: EXPR_CTEXT)
|
3502
|
-
result = ResponseCode.new(name, token.value)
|
3503
|
-
else
|
3504
|
-
result = ResponseCode.new(name, nil)
|
3505
|
-
end
|
3506
|
-
end
|
3507
|
-
return result
|
3508
|
-
end
|
3509
|
-
|
3510
|
-
def charset_list
|
3511
|
-
result = []
|
3512
|
-
if accept(T_SPACE)
|
3513
|
-
match(T_LPAR)
|
3514
|
-
result << charset
|
3515
|
-
while accept(T_SPACE)
|
3516
|
-
result << charset
|
3517
|
-
end
|
3518
|
-
match(T_RPAR)
|
3519
|
-
end
|
3520
|
-
result
|
3521
|
-
end
|
3522
|
-
|
3523
|
-
def address_list
|
3524
|
-
token = lookahead
|
3525
|
-
if token.symbol == T_NIL
|
3526
|
-
shift_token
|
3527
|
-
return nil
|
3528
|
-
else
|
3529
|
-
result = []
|
3530
|
-
match(T_LPAR)
|
3531
|
-
while true
|
3532
|
-
token = lookahead
|
3533
|
-
case token.symbol
|
3534
|
-
when T_RPAR
|
3535
|
-
shift_token
|
3536
|
-
break
|
3537
|
-
when T_SPACE
|
3538
|
-
shift_token
|
3539
|
-
end
|
3540
|
-
result.push(address)
|
3541
|
-
end
|
3542
|
-
return result
|
3543
|
-
end
|
3544
|
-
end
|
3545
|
-
|
3546
|
-
ADDRESS_REGEXP = /\G\
|
3547
|
-
(?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
3548
|
-
(?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
3549
|
-
(?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
|
3550
|
-
(?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
|
3551
|
-
\)/ni
|
3552
|
-
|
3553
|
-
def address
|
3554
|
-
match(T_LPAR)
|
3555
|
-
if @str.index(ADDRESS_REGEXP, @pos)
|
3556
|
-
# address does not include literal.
|
3557
|
-
@pos = $~.end(0)
|
3558
|
-
name = $1
|
3559
|
-
route = $2
|
3560
|
-
mailbox = $3
|
3561
|
-
host = $4
|
3562
|
-
for s in [name, route, mailbox, host]
|
3563
|
-
if s
|
3564
|
-
s.gsub!(/\\(["\\])/n, "\\1")
|
3565
|
-
end
|
3566
|
-
end
|
3567
|
-
else
|
3568
|
-
name = nstring
|
3569
|
-
match(T_SPACE)
|
3570
|
-
route = nstring
|
3571
|
-
match(T_SPACE)
|
3572
|
-
mailbox = nstring
|
3573
|
-
match(T_SPACE)
|
3574
|
-
host = nstring
|
3575
|
-
match(T_RPAR)
|
3576
|
-
end
|
3577
|
-
return Address.new(name, route, mailbox, host)
|
3578
|
-
end
|
3579
|
-
|
3580
|
-
FLAG_REGEXP = /\
|
3581
|
-
(?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
|
3582
|
-
(?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
|
3583
|
-
|
3584
|
-
def flag_list
|
3585
|
-
if @str.index(/\(([^)]*)\)/ni, @pos)
|
3586
|
-
@pos = $~.end(0)
|
3587
|
-
return $1.scan(FLAG_REGEXP).collect { |flag, atom|
|
3588
|
-
if atom
|
3589
|
-
atom
|
3590
|
-
else
|
3591
|
-
symbol = flag.capitalize.intern
|
3592
|
-
@flag_symbols[symbol] = true
|
3593
|
-
if @flag_symbols.length > IMAP.max_flag_count
|
3594
|
-
raise FlagCountError, "number of flag symbols exceeded"
|
3595
|
-
end
|
3596
|
-
symbol
|
3597
|
-
end
|
3598
|
-
}
|
3599
|
-
else
|
3600
|
-
parse_error("invalid flag list")
|
3601
|
-
end
|
3602
|
-
end
|
3603
|
-
|
3604
|
-
def nstring
|
3605
|
-
token = lookahead
|
3606
|
-
if token.symbol == T_NIL
|
3607
|
-
shift_token
|
3608
|
-
return nil
|
3609
|
-
else
|
3610
|
-
return string
|
3611
|
-
end
|
3612
|
-
end
|
3613
|
-
|
3614
|
-
def astring
|
3615
|
-
token = lookahead
|
3616
|
-
if string_token?(token)
|
3617
|
-
return string
|
3618
|
-
else
|
3619
|
-
return astring_chars
|
3620
|
-
end
|
3621
|
-
end
|
3622
|
-
|
3623
|
-
def string
|
3624
|
-
token = lookahead
|
3625
|
-
if token.symbol == T_NIL
|
3626
|
-
shift_token
|
3627
|
-
return nil
|
3628
|
-
end
|
3629
|
-
token = match(T_QUOTED, T_LITERAL)
|
3630
|
-
return token.value
|
3631
|
-
end
|
3632
|
-
|
3633
|
-
STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
|
3634
|
-
|
3635
|
-
def string_token?(token)
|
3636
|
-
return STRING_TOKENS.include?(token.symbol)
|
3637
|
-
end
|
3638
|
-
|
3639
|
-
def case_insensitive_string
|
3640
|
-
token = lookahead
|
3641
|
-
if token.symbol == T_NIL
|
3642
|
-
shift_token
|
3643
|
-
return nil
|
3644
|
-
end
|
3645
|
-
token = match(T_QUOTED, T_LITERAL)
|
3646
|
-
return token.value.upcase
|
3647
|
-
end
|
3648
|
-
|
3649
|
-
# atom = 1*ATOM-CHAR
|
3650
|
-
# ATOM-CHAR = <any CHAR except atom-specials>
|
3651
|
-
ATOM_TOKENS = [
|
3652
|
-
T_ATOM,
|
3653
|
-
T_NUMBER,
|
3654
|
-
T_NIL,
|
3655
|
-
T_LBRA,
|
3656
|
-
T_PLUS
|
3657
|
-
]
|
3658
|
-
|
3659
|
-
def atom
|
3660
|
-
-combine_adjacent(*ATOM_TOKENS)
|
3661
|
-
end
|
3662
|
-
|
3663
|
-
# ASTRING-CHAR = ATOM-CHAR / resp-specials
|
3664
|
-
# resp-specials = "]"
|
3665
|
-
ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA]
|
3666
|
-
|
3667
|
-
def astring_chars
|
3668
|
-
combine_adjacent(*ASTRING_CHARS_TOKENS)
|
3669
|
-
end
|
3670
|
-
|
3671
|
-
def combine_adjacent(*tokens)
|
3672
|
-
result = "".b
|
3673
|
-
while token = accept(*tokens)
|
3674
|
-
result << token.value
|
3675
|
-
end
|
3676
|
-
if result.empty?
|
3677
|
-
parse_error('unexpected token %s (expected %s)',
|
3678
|
-
lookahead.symbol, args.join(" or "))
|
3679
|
-
end
|
3680
|
-
result
|
3681
|
-
end
|
3682
|
-
|
3683
|
-
# See https://www.rfc-editor.org/errata/rfc3501
|
3684
|
-
#
|
3685
|
-
# charset = atom / quoted
|
3686
|
-
def charset
|
3687
|
-
if token = accept(T_QUOTED)
|
3688
|
-
token.value
|
3689
|
-
else
|
3690
|
-
atom
|
3691
|
-
end
|
3692
|
-
end
|
3693
|
-
|
3694
|
-
def number
|
3695
|
-
token = lookahead
|
3696
|
-
if token.symbol == T_NIL
|
3697
|
-
shift_token
|
3698
|
-
return nil
|
3699
|
-
end
|
3700
|
-
token = match(T_NUMBER)
|
3701
|
-
return token.value.to_i
|
3702
|
-
end
|
3703
|
-
|
3704
|
-
def nil_atom
|
3705
|
-
match(T_NIL)
|
3706
|
-
return nil
|
3707
|
-
end
|
3708
|
-
|
3709
|
-
SPACES_REGEXP = /\G */n
|
3710
|
-
|
3711
|
-
# This advances @pos directly so it's safe before changing @lex_state.
|
3712
|
-
def accept_space
|
3713
|
-
if @token
|
3714
|
-
shift_token if @token.symbol == T_SPACE
|
3715
|
-
elsif @str[@pos] == " "
|
3716
|
-
@pos += 1
|
3717
|
-
end
|
3718
|
-
end
|
3719
|
-
|
3720
|
-
# The RFC is very strict about this and usually we should be too.
|
3721
|
-
# But skipping spaces is usually a safe workaround for buggy servers.
|
3722
|
-
#
|
3723
|
-
# This advances @pos directly so it's safe before changing @lex_state.
|
3724
|
-
def accept_spaces
|
3725
|
-
shift_token if @token&.symbol == T_SPACE
|
3726
|
-
if @str.index(SPACES_REGEXP, @pos)
|
3727
|
-
@pos = $~.end(0)
|
3728
|
-
end
|
3729
|
-
end
|
3730
|
-
|
3731
|
-
def match(*args, lex_state: @lex_state)
|
3732
|
-
if @token && lex_state != @lex_state
|
3733
|
-
parse_error("invalid lex_state change to %s with unconsumed token",
|
3734
|
-
lex_state)
|
3735
|
-
end
|
3736
|
-
begin
|
3737
|
-
@lex_state, original_lex_state = lex_state, @lex_state
|
3738
|
-
token = lookahead
|
3739
|
-
unless args.include?(token.symbol)
|
3740
|
-
parse_error('unexpected token %s (expected %s)',
|
3741
|
-
token.symbol.id2name,
|
3742
|
-
args.collect {|i| i.id2name}.join(" or "))
|
3743
|
-
end
|
3744
|
-
shift_token
|
3745
|
-
return token
|
3746
|
-
ensure
|
3747
|
-
@lex_state = original_lex_state
|
3748
|
-
end
|
3749
|
-
end
|
3750
|
-
|
3751
|
-
# like match, but does not raise error on failure.
|
3752
|
-
#
|
3753
|
-
# returns and shifts token on successful match
|
3754
|
-
# returns nil and leaves @token unshifted on no match
|
3755
|
-
def accept(*args)
|
3756
|
-
token = lookahead
|
3757
|
-
if args.include?(token.symbol)
|
3758
|
-
shift_token
|
3759
|
-
token
|
3760
|
-
end
|
3761
|
-
end
|
3762
|
-
|
3763
|
-
def lookahead
|
3764
|
-
@token ||= next_token
|
3765
|
-
end
|
3766
|
-
|
3767
|
-
def shift_token
|
3768
|
-
@token = nil
|
3769
|
-
end
|
3770
|
-
|
3771
|
-
def next_token
|
3772
|
-
case @lex_state
|
3773
|
-
when EXPR_BEG
|
3774
|
-
if @str.index(BEG_REGEXP, @pos)
|
3775
|
-
@pos = $~.end(0)
|
3776
|
-
if $1
|
3777
|
-
return Token.new(T_SPACE, $+)
|
3778
|
-
elsif $2
|
3779
|
-
return Token.new(T_NIL, $+)
|
3780
|
-
elsif $3
|
3781
|
-
return Token.new(T_NUMBER, $+)
|
3782
|
-
elsif $4
|
3783
|
-
return Token.new(T_ATOM, $+)
|
3784
|
-
elsif $5
|
3785
|
-
return Token.new(T_QUOTED,
|
3786
|
-
$+.gsub(/\\(["\\])/n, "\\1"))
|
3787
|
-
elsif $6
|
3788
|
-
return Token.new(T_LPAR, $+)
|
3789
|
-
elsif $7
|
3790
|
-
return Token.new(T_RPAR, $+)
|
3791
|
-
elsif $8
|
3792
|
-
return Token.new(T_BSLASH, $+)
|
3793
|
-
elsif $9
|
3794
|
-
return Token.new(T_STAR, $+)
|
3795
|
-
elsif $10
|
3796
|
-
return Token.new(T_LBRA, $+)
|
3797
|
-
elsif $11
|
3798
|
-
return Token.new(T_RBRA, $+)
|
3799
|
-
elsif $12
|
3800
|
-
len = $+.to_i
|
3801
|
-
val = @str[@pos, len]
|
3802
|
-
@pos += len
|
3803
|
-
return Token.new(T_LITERAL, val)
|
3804
|
-
elsif $13
|
3805
|
-
return Token.new(T_PLUS, $+)
|
3806
|
-
elsif $14
|
3807
|
-
return Token.new(T_PERCENT, $+)
|
3808
|
-
elsif $15
|
3809
|
-
return Token.new(T_CRLF, $+)
|
3810
|
-
elsif $16
|
3811
|
-
return Token.new(T_EOF, $+)
|
3812
|
-
else
|
3813
|
-
parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
|
3814
|
-
end
|
3815
|
-
else
|
3816
|
-
@str.index(/\S*/n, @pos)
|
3817
|
-
parse_error("unknown token - %s", $&.dump)
|
3818
|
-
end
|
3819
|
-
when EXPR_DATA
|
3820
|
-
if @str.index(DATA_REGEXP, @pos)
|
3821
|
-
@pos = $~.end(0)
|
3822
|
-
if $1
|
3823
|
-
return Token.new(T_SPACE, $+)
|
3824
|
-
elsif $2
|
3825
|
-
return Token.new(T_NIL, $+)
|
3826
|
-
elsif $3
|
3827
|
-
return Token.new(T_NUMBER, $+)
|
3828
|
-
elsif $4
|
3829
|
-
return Token.new(T_QUOTED,
|
3830
|
-
$+.gsub(/\\(["\\])/n, "\\1"))
|
3831
|
-
elsif $5
|
3832
|
-
len = $+.to_i
|
3833
|
-
val = @str[@pos, len]
|
3834
|
-
@pos += len
|
3835
|
-
return Token.new(T_LITERAL, val)
|
3836
|
-
elsif $6
|
3837
|
-
return Token.new(T_LPAR, $+)
|
3838
|
-
elsif $7
|
3839
|
-
return Token.new(T_RPAR, $+)
|
3840
|
-
else
|
3841
|
-
parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
|
3842
|
-
end
|
3843
|
-
else
|
3844
|
-
@str.index(/\S*/n, @pos)
|
3845
|
-
parse_error("unknown token - %s", $&.dump)
|
3846
|
-
end
|
3847
|
-
when EXPR_TEXT
|
3848
|
-
if @str.index(TEXT_REGEXP, @pos)
|
3849
|
-
@pos = $~.end(0)
|
3850
|
-
if $1
|
3851
|
-
return Token.new(T_TEXT, $+)
|
3852
|
-
else
|
3853
|
-
parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
|
3854
|
-
end
|
3855
|
-
else
|
3856
|
-
@str.index(/\S*/n, @pos)
|
3857
|
-
parse_error("unknown token - %s", $&.dump)
|
3858
|
-
end
|
3859
|
-
when EXPR_RTEXT
|
3860
|
-
if @str.index(RTEXT_REGEXP, @pos)
|
3861
|
-
@pos = $~.end(0)
|
3862
|
-
if $1
|
3863
|
-
return Token.new(T_LBRA, $+)
|
3864
|
-
elsif $2
|
3865
|
-
return Token.new(T_TEXT, $+)
|
3866
|
-
else
|
3867
|
-
parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
|
3868
|
-
end
|
3869
|
-
else
|
3870
|
-
@str.index(/\S*/n, @pos)
|
3871
|
-
parse_error("unknown token - %s", $&.dump)
|
3872
|
-
end
|
3873
|
-
when EXPR_CTEXT
|
3874
|
-
if @str.index(CTEXT_REGEXP, @pos)
|
3875
|
-
@pos = $~.end(0)
|
3876
|
-
if $1
|
3877
|
-
return Token.new(T_TEXT, $+)
|
3878
|
-
else
|
3879
|
-
parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
|
3880
|
-
end
|
3881
|
-
else
|
3882
|
-
@str.index(/\S*/n, @pos) #/
|
3883
|
-
parse_error("unknown token - %s", $&.dump)
|
3884
|
-
end
|
3885
|
-
else
|
3886
|
-
parse_error("invalid @lex_state - %s", @lex_state.inspect)
|
3887
|
-
end
|
3888
|
-
end
|
3889
|
-
|
3890
|
-
def parse_error(fmt, *args)
|
3891
|
-
if IMAP.debug
|
3892
|
-
$stderr.printf("@str: %s\n", @str.dump)
|
3893
|
-
$stderr.printf("@pos: %d\n", @pos)
|
3894
|
-
$stderr.printf("@lex_state: %s\n", @lex_state)
|
3895
|
-
if @token
|
3896
|
-
$stderr.printf("@token.symbol: %s\n", @token.symbol)
|
3897
|
-
$stderr.printf("@token.value: %s\n", @token.value.inspect)
|
3898
|
-
end
|
3899
|
-
end
|
3900
|
-
raise ResponseParseError, format(fmt, *args)
|
3901
|
-
end
|
3902
|
-
end
|
3903
|
-
|
3904
|
-
# Authenticator for the "LOGIN" authentication type. See
|
3905
|
-
# #authenticate().
|
3906
|
-
class LoginAuthenticator
|
3907
|
-
def process(data)
|
3908
|
-
case @state
|
3909
|
-
when STATE_USER
|
3910
|
-
@state = STATE_PASSWORD
|
3911
|
-
return @user
|
3912
|
-
when STATE_PASSWORD
|
3913
|
-
return @password
|
3914
|
-
end
|
3915
|
-
end
|
3916
|
-
|
3917
|
-
private
|
3918
|
-
|
3919
|
-
STATE_USER = :USER
|
3920
|
-
STATE_PASSWORD = :PASSWORD
|
3921
|
-
|
3922
|
-
def initialize(user, password)
|
3923
|
-
@user = user
|
3924
|
-
@password = password
|
3925
|
-
@state = STATE_USER
|
3926
|
-
end
|
3927
|
-
end
|
3928
|
-
add_authenticator "LOGIN", LoginAuthenticator
|
3929
|
-
|
3930
|
-
# Authenticator for the "PLAIN" authentication type. See
|
3931
|
-
# #authenticate().
|
3932
|
-
class PlainAuthenticator
|
3933
|
-
def process(data)
|
3934
|
-
return "\0#{@user}\0#{@password}"
|
3935
|
-
end
|
3936
|
-
|
3937
|
-
private
|
3938
|
-
|
3939
|
-
def initialize(user, password)
|
3940
|
-
@user = user
|
3941
|
-
@password = password
|
3942
|
-
end
|
3943
|
-
end
|
3944
|
-
add_authenticator "PLAIN", PlainAuthenticator
|
3945
|
-
|
3946
|
-
# Authenticator for the "CRAM-MD5" authentication type. See
|
3947
|
-
# #authenticate().
|
3948
|
-
class CramMD5Authenticator
|
3949
|
-
def process(challenge)
|
3950
|
-
digest = hmac_md5(challenge, @password)
|
3951
|
-
return @user + " " + digest
|
3952
|
-
end
|
3953
|
-
|
3954
|
-
private
|
3955
|
-
|
3956
|
-
def initialize(user, password)
|
3957
|
-
@user = user
|
3958
|
-
@password = password
|
3959
|
-
end
|
3960
|
-
|
3961
|
-
def hmac_md5(text, key)
|
3962
|
-
if key.length > 64
|
3963
|
-
key = Digest::MD5.digest(key)
|
3964
|
-
end
|
3965
|
-
|
3966
|
-
k_ipad = key + "\0" * (64 - key.length)
|
3967
|
-
k_opad = key + "\0" * (64 - key.length)
|
3968
|
-
for i in 0..63
|
3969
|
-
k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
|
3970
|
-
k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
|
3971
|
-
end
|
3972
|
-
|
3973
|
-
digest = Digest::MD5.digest(k_ipad + text)
|
3974
|
-
|
3975
|
-
return Digest::MD5.hexdigest(k_opad + digest)
|
3976
|
-
end
|
3977
|
-
end
|
3978
|
-
add_authenticator "CRAM-MD5", CramMD5Authenticator
|
3979
|
-
|
3980
|
-
# Authenticator for the "DIGEST-MD5" authentication type. See
|
3981
|
-
# #authenticate().
|
3982
|
-
class DigestMD5Authenticator
|
3983
|
-
def process(challenge)
|
3984
|
-
case @stage
|
3985
|
-
when STAGE_ONE
|
3986
|
-
@stage = STAGE_TWO
|
3987
|
-
sparams = {}
|
3988
|
-
c = StringScanner.new(challenge)
|
3989
|
-
while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
|
3990
|
-
k, v = c[1], c[2]
|
3991
|
-
if v =~ /^"(.*)"$/
|
3992
|
-
v = $1
|
3993
|
-
if v =~ /,/
|
3994
|
-
v = v.split(',')
|
3995
|
-
end
|
3996
|
-
end
|
3997
|
-
sparams[k] = v
|
3998
|
-
end
|
3999
|
-
|
4000
|
-
raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
|
4001
|
-
raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
|
4002
|
-
|
4003
|
-
response = {
|
4004
|
-
:nonce => sparams['nonce'],
|
4005
|
-
:username => @user,
|
4006
|
-
:realm => sparams['realm'],
|
4007
|
-
:cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
|
4008
|
-
:'digest-uri' => 'imap/' + sparams['realm'],
|
4009
|
-
:qop => 'auth',
|
4010
|
-
:maxbuf => 65535,
|
4011
|
-
:nc => "%08d" % nc(sparams['nonce']),
|
4012
|
-
:charset => sparams['charset'],
|
4013
|
-
}
|
4014
|
-
|
4015
|
-
response[:authzid] = @authname unless @authname.nil?
|
4016
|
-
|
4017
|
-
# now, the real thing
|
4018
|
-
a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
|
4019
|
-
|
4020
|
-
a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
|
4021
|
-
a1 << ':' + response[:authzid] unless response[:authzid].nil?
|
4022
|
-
|
4023
|
-
a2 = "AUTHENTICATE:" + response[:'digest-uri']
|
4024
|
-
a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
|
4025
|
-
|
4026
|
-
response[:response] = Digest::MD5.hexdigest(
|
4027
|
-
[
|
4028
|
-
Digest::MD5.hexdigest(a1),
|
4029
|
-
response.values_at(:nonce, :nc, :cnonce, :qop),
|
4030
|
-
Digest::MD5.hexdigest(a2)
|
4031
|
-
].join(':')
|
4032
|
-
)
|
4033
|
-
|
4034
|
-
return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
|
4035
|
-
when STAGE_TWO
|
4036
|
-
@stage = nil
|
4037
|
-
# if at the second stage, return an empty string
|
4038
|
-
if challenge =~ /rspauth=/
|
4039
|
-
return ''
|
4040
|
-
else
|
4041
|
-
raise ResponseParseError, challenge
|
4042
|
-
end
|
4043
|
-
else
|
4044
|
-
raise ResponseParseError, challenge
|
4045
|
-
end
|
4046
|
-
end
|
4047
|
-
|
4048
|
-
def initialize(user, password, authname = nil)
|
4049
|
-
@user, @password, @authname = user, password, authname
|
4050
|
-
@nc, @stage = {}, STAGE_ONE
|
4051
|
-
end
|
4052
|
-
|
4053
|
-
private
|
4054
|
-
|
4055
|
-
STAGE_ONE = :stage_one
|
4056
|
-
STAGE_TWO = :stage_two
|
4057
|
-
|
4058
|
-
def nc(nonce)
|
4059
|
-
if @nc.has_key? nonce
|
4060
|
-
@nc[nonce] = @nc[nonce] + 1
|
4061
|
-
else
|
4062
|
-
@nc[nonce] = 1
|
4063
|
-
end
|
4064
|
-
return @nc[nonce]
|
4065
|
-
end
|
4066
|
-
|
4067
|
-
# some responses need quoting
|
4068
|
-
def qdval(k, v)
|
4069
|
-
return if k.nil? or v.nil?
|
4070
|
-
if %w"username authzid realm nonce cnonce digest-uri qop".include? k
|
4071
|
-
v.gsub!(/([\\"])/, "\\\1")
|
4072
|
-
return '%s="%s"' % [k, v]
|
4073
|
-
else
|
4074
|
-
return '%s=%s' % [k, v]
|
4075
|
-
end
|
4076
|
-
end
|
4077
|
-
end
|
4078
|
-
add_authenticator "DIGEST-MD5", DigestMD5Authenticator
|
4079
|
-
|
4080
|
-
# Superclass of IMAP errors.
|
4081
|
-
class Error < StandardError
|
4082
|
-
end
|
4083
|
-
|
4084
|
-
# Error raised when data is in the incorrect format.
|
4085
|
-
class DataFormatError < Error
|
4086
|
-
end
|
4087
|
-
|
4088
|
-
# Error raised when a response from the server is non-parseable.
|
4089
|
-
class ResponseParseError < Error
|
4090
|
-
end
|
4091
|
-
|
4092
|
-
# Superclass of all errors used to encapsulate "fail" responses
|
4093
|
-
# from the server.
|
4094
|
-
class ResponseError < Error
|
4095
|
-
|
4096
|
-
# The response that caused this error
|
4097
|
-
attr_accessor :response
|
4098
|
-
|
4099
|
-
def initialize(response)
|
4100
|
-
@response = response
|
4101
|
-
|
4102
|
-
super @response.data.text
|
4103
|
-
end
|
4104
|
-
|
4105
|
-
end
|
4106
|
-
|
4107
|
-
# Error raised upon a "NO" response from the server, indicating
|
4108
|
-
# that the client command could not be completed successfully.
|
4109
|
-
class NoResponseError < ResponseError
|
4110
|
-
end
|
1548
|
+
# Error raised upon a "NO" response from the server, indicating
|
1549
|
+
# that the client command could not be completed successfully.
|
1550
|
+
class NoResponseError < ResponseError
|
1551
|
+
end
|
4111
1552
|
|
4112
1553
|
# Error raised upon a "BAD" response from the server, indicating
|
4113
1554
|
# that the client command violated the IMAP protocol, or an internal
|
@@ -4121,6 +1562,10 @@ module Net
|
|
4121
1562
|
class ByeResponseError < ResponseError
|
4122
1563
|
end
|
4123
1564
|
|
1565
|
+
# Error raised upon an unknown response from the server.
|
1566
|
+
class UnknownResponseError < ResponseError
|
1567
|
+
end
|
1568
|
+
|
4124
1569
|
RESPONSE_ERRORS = Hash.new(ResponseError)
|
4125
1570
|
RESPONSE_ERRORS["NO"] = NoResponseError
|
4126
1571
|
RESPONSE_ERRORS["BAD"] = BadResponseError
|
@@ -4130,3 +1575,5 @@ module Net
|
|
4130
1575
|
end
|
4131
1576
|
end
|
4132
1577
|
end
|
1578
|
+
|
1579
|
+
require_relative "imap/authenticators"
|