net-imap 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3eefc3b1cd7e0363c5ce0a07a3849b903248a1ab716a6c025dd15dcfd2af30c3
4
- data.tar.gz: 0ff1ba4529e6d462e9bccd4417997582e8d3fac60ed4e60bbc270c135db115a7
3
+ metadata.gz: 5ce3f04b1c49832a6e2b36c2b9b9a7a79223c297896aa7daeb7bf7fd0b54a7f6
4
+ data.tar.gz: 2ce12d9e40a529f638a4314f6c61ebf343a5341225fa4c2442800be339a32dea
5
5
  SHA512:
6
- metadata.gz: 847ccde0966c5dd4e633c0cfd5ce894ad08a7c97dc6598377d169e609770c36093f9ba612a87749755ee56df24d6cde5caae54c4ea93dd4caf37b0a467cc01be
7
- data.tar.gz: 314766dc692163b3b9b9770d884154357bf956df8f3047bb6ce85c6c07b587cfca616c4a4677855013a33b31b5e43a8267dee7077689266b539afe459c8b467a
6
+ metadata.gz: 8933d844581528c0b969e4e5256821a41cf7838f1fed493205c2447e3fd5578df6f9f1eb75f24cb14c8a227b3908218224c161da3a61a26dab2b66d819738d03
7
+ data.tar.gz: 9b37cf6d107aa5e4a5bd650faac5f4fb4f2a4d2283068bd1b39793db2a5a3b1e6dcc86e5b850efc4e008ddbe0a0632d367ad0fb473e1e7158633c09726dc7f25
@@ -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@master
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
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /Gemfile.lock
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Net::IMAP
2
2
 
3
3
  Net::IMAP implements Internet Message Access Protocol (IMAP) client
4
- functionality. The protocol is described in [IMAP].
4
+ functionality. The protocol is described in [IMAP](https://tools.ietf.org/html/rfc3501).
5
5
 
6
6
  ## Installation
7
7
 
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 [IMAP].
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() or #login(). Having
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() or (for
47
- # read-only access) #examine(). Once the client has successfully
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. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
160
- # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501)
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
- # [[LANGUAGE-TAGS]]
163
- # Alvestrand, H., "Tags for the Identification of
164
- # Languages", RFC 1766, March 1995.
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
- # [[MD5]]
167
- # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC
168
- # 1864, October 1995.
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
- # [[MIME-IMB]]
171
- # Freed, N., and N. Borenstein, "MIME (Multipurpose Internet
172
- # Mail Extensions) Part One: Format of Internet Message Bodies", RFC
173
- # 2045, November 1996.
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
- # [[RFC-822]]
176
- # Crocker, D., "Standard for the Format of ARPA Internet Text
177
- # Messages", STD 11, RFC 822, University of Delaware, August 1982.
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
- # [[RFC-2087]]
180
- # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997.
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
- # [[RFC-2086]]
183
- # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997.
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
- # [[RFC-2195]]
186
- # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension
187
- # for Simple Challenge/Response", RFC 2195, September 1997.
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-EXT]]
190
- # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD
191
- # Extensions", draft-ietf-imapext-sort, May 2003.
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 2152, May 1997.
227
+ # Unicode", RFC-2152[https://tools.ietf.org/html/rfc2152], May 1997.
202
228
  #
203
229
  class IMAP < Protocol
204
- VERSION = "0.2.1"
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 RFC 2971, Section 3.3, for defined fields.
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() for a capability of
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() with an +auth_type+
471
- # of "LOGIN", #login() does *not* use the login authenticator.
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 @responses["EXISTS"][-1],
483
- # and the number of recent messages from @responses["RECENT"][-1].
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() for a way of
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 [RFC2342] and returns the namespaces that are
588
- # available. The NAMESPACE command allows a client to discover the prefixes
589
- # of namespaces used by a server for personal mailboxes, other users'
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
- # This extension predates IMAP4rev1 (RFC3501), so most IMAP servers support
593
- # it. Many popular IMAP servers are configured with the default personal
594
- # namespaces as `("" "/")`: no prefix and "/" hierarchy delimiter. In that
595
- # common case, the naive client may not have any trouble naming mailboxes.
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. The IMAP quota commands are described in
702
- # [RFC-2087].
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
- # The IMAP ACL commands are described in [RFC-2086].
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(), but returns unique identifiers.
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(), but +set+ contains unique identifiers.
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(), but +set+ contains unique identifiers.
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(), but +set+ contains unique identifiers.
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
- # The IMAP MOVE extension is described in [RFC-6851].
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(), but +set+ contains unique identifiers.
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
- # See [SORT-THREAD-EXT] for more details.
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(), but returns an array of unique identifiers.
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(), but returns message sequence numbers in threaded
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(), +charset+ is a required argument. US-ASCII
984
+ # Unlike #search, +charset+ is a required argument. US-ASCII
1014
985
  # and UTF-8 are sample values.
1015
986
  #
1016
- # See [SORT-THREAD-EXT] for more details.
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(), but returns unique identifiers instead of
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() to leave IDLE.
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
- return resp
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
- # Net::IMAP::ContinuationRequest represents command continuation requests.
1881
- #
1882
- # The command continuation request response is indicated by a "+" token
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
- # Net::IMAP::IgnoredResponse represents intentionaly ignored responses.
1916
- #
1917
- # This includes untagged response "NOOP" sent by eg. Zimbra to avoid some
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
- # Net::IMAP::TaggedResponse represents tagged responses.
1928
- #
1929
- # The server completion result response indicates the success or
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
- # Net::IMAP::ResponseText represents texts of responses.
1952
- # The text may be prefixed by the response code.
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
- # Net::IMAP::ResponseCode represents response codes.
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
- # Net::IMAP::MailboxList represents contents of the LIST response.
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
- # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
2002
- # This object can also be a response to GETQUOTAROOT. In the syntax
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
- # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
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
- # Net::IMAP::MailboxACLItem represents the response from GETACL.
2037
- #
2038
- # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
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"