net-imap 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

checksums.yaml 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"