net-imap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3784632306c42023af1f1a84943663b706e7c77f4dab4fdfca3834d2baafe5ba
4
+ data.tar.gz: dc819ede4c44751748d6bae1c555e83727d4eea16a8d78a8e9902d3e16a02605
5
+ SHA512:
6
+ metadata.gz: ee02ea21fdb00057ba18fe6fb11735288ae508b36ba19d2d074ec50a0301177a1f6e2be97b4e87e95f13426d4e60f3598c2ea4c9d391344d8548c163da767909
7
+ data.tar.gz: 9fa3c929a93599f6dc26ebde519a3093d5ba2eea74a08365b61fa8583e3fb46d848ecad9605e948e266f024645a8324babe4b24ba3bbc39567d52d9512637779
@@ -0,0 +1,24 @@
1
+ name: ubuntu
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
+ strategy:
9
+ matrix:
10
+ ruby: [ 2.7, 2.6, 2.5, head ]
11
+ os: [ ubuntu-latest, macos-latest ]
12
+ runs-on: ${{ matrix.os }}
13
+ steps:
14
+ - uses: actions/checkout@master
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: Install dependencies
20
+ run: |
21
+ gem install bundler --no-document
22
+ bundle install
23
+ - name: Run test
24
+ run: rake test
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "test-unit"
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ net-imap (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ power_assert (1.1.5)
10
+ rake (13.0.1)
11
+ test-unit (3.3.5)
12
+ power_assert
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ net-imap!
19
+ rake
20
+ test-unit
21
+
22
+ BUNDLED WITH
23
+ 2.1.4
@@ -0,0 +1,61 @@
1
+ # Net::IMAP
2
+
3
+ Net::IMAP implements Internet Message Access Protocol (IMAP) client
4
+ functionality. The protocol is described in [IMAP].
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'net-imap'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install net-imap
21
+
22
+ ## Usage
23
+
24
+ ### List sender and subject of all recent messages in the default mailbox
25
+
26
+ ```ruby
27
+ imap = Net::IMAP.new('mail.example.com')
28
+ imap.authenticate('LOGIN', 'joe_user', 'joes_password')
29
+ imap.examine('INBOX')
30
+ imap.search(["RECENT"]).each do |message_id|
31
+ envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
32
+ puts "#{envelope.from[0].name}: \t#{envelope.subject}"
33
+ end
34
+ ```
35
+
36
+ ### Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
37
+
38
+ ```ruby
39
+ imap = Net::IMAP.new('mail.example.com')
40
+ imap.authenticate('LOGIN', 'joe_user', 'joes_password')
41
+ imap.select('Mail/sent-mail')
42
+ if not imap.list('Mail/', 'sent-apr03')
43
+ imap.create('Mail/sent-apr03')
44
+ end
45
+ imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
46
+ imap.copy(message_id, "Mail/sent-apr03")
47
+ imap.store(message_id, "+FLAGS", [:Deleted])
48
+ end
49
+ imap.expunge
50
+ ```
51
+
52
+ ## Development
53
+
54
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
55
+
56
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
57
+
58
+ ## Contributing
59
+
60
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-imap.
61
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test/lib"
6
+ t.ruby_opts << "-rhelper"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "net/imap"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3728 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # = net/imap.rb
4
+ #
5
+ # Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org>
6
+ #
7
+ # This library is distributed under the terms of the Ruby license.
8
+ # You can freely distribute/modify this library.
9
+ #
10
+ # Documentation: Shugo Maeda, with RDoc conversion and overview by William
11
+ # Webber.
12
+ #
13
+ # See Net::IMAP for documentation.
14
+ #
15
+
16
+
17
+ require "socket"
18
+ require "monitor"
19
+ require "digest/md5"
20
+ require "strscan"
21
+ require 'net/protocol'
22
+ begin
23
+ require "openssl"
24
+ rescue LoadError
25
+ end
26
+
27
+ module Net
28
+
29
+ #
30
+ # Net::IMAP implements Internet Message Access Protocol (IMAP) client
31
+ # functionality. The protocol is described in [IMAP].
32
+ #
33
+ # == IMAP Overview
34
+ #
35
+ # An IMAP client connects to a server, and then authenticates
36
+ # itself using either #authenticate() or #login(). Having
37
+ # authenticated itself, there is a range of commands
38
+ # available to it. Most work with mailboxes, which may be
39
+ # arranged in an hierarchical namespace, and each of which
40
+ # contains zero or more messages. How this is implemented on
41
+ # the server is implementation-dependent; on a UNIX server, it
42
+ # will frequently be implemented as files in mailbox format
43
+ # within a hierarchy of directories.
44
+ #
45
+ # 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
48
+ # selected a mailbox, they enter _selected_ state, and that
49
+ # mailbox becomes the _current_ mailbox, on which mail-item
50
+ # related commands implicitly operate.
51
+ #
52
+ # Messages have two sorts of identifiers: message sequence
53
+ # numbers and UIDs.
54
+ #
55
+ # Message sequence numbers number messages within a mailbox
56
+ # from 1 up to the number of items in the mailbox. If a new
57
+ # message arrives during a session, it receives a sequence
58
+ # number equal to the new size of the mailbox. If messages
59
+ # are expunged from the mailbox, remaining messages have their
60
+ # sequence numbers "shuffled down" to fill the gaps.
61
+ #
62
+ # UIDs, on the other hand, are permanently guaranteed not to
63
+ # identify another message within the same mailbox, even if
64
+ # the existing message is deleted. UIDs are required to
65
+ # be assigned in ascending (but not necessarily sequential)
66
+ # order within a mailbox; this means that if a non-IMAP client
67
+ # rearranges the order of mailitems within a mailbox, the
68
+ # UIDs have to be reassigned. An IMAP client thus cannot
69
+ # rearrange message orders.
70
+ #
71
+ # == Examples of Usage
72
+ #
73
+ # === List sender and subject of all recent messages in the default mailbox
74
+ #
75
+ # imap = Net::IMAP.new('mail.example.com')
76
+ # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
77
+ # imap.examine('INBOX')
78
+ # imap.search(["RECENT"]).each do |message_id|
79
+ # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
80
+ # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
81
+ # end
82
+ #
83
+ # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
84
+ #
85
+ # imap = Net::IMAP.new('mail.example.com')
86
+ # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
87
+ # imap.select('Mail/sent-mail')
88
+ # if not imap.list('Mail/', 'sent-apr03')
89
+ # imap.create('Mail/sent-apr03')
90
+ # end
91
+ # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
92
+ # imap.copy(message_id, "Mail/sent-apr03")
93
+ # imap.store(message_id, "+FLAGS", [:Deleted])
94
+ # end
95
+ # imap.expunge
96
+ #
97
+ # == Thread Safety
98
+ #
99
+ # Net::IMAP supports concurrent threads. For example,
100
+ #
101
+ # imap = Net::IMAP.new("imap.foo.net", "imap2")
102
+ # imap.authenticate("cram-md5", "bar", "password")
103
+ # imap.select("inbox")
104
+ # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
105
+ # search_result = imap.search(["BODY", "hello"])
106
+ # fetch_result = fetch_thread.value
107
+ # imap.disconnect
108
+ #
109
+ # This script invokes the FETCH command and the SEARCH command concurrently.
110
+ #
111
+ # == Errors
112
+ #
113
+ # An IMAP server can send three different types of responses to indicate
114
+ # failure:
115
+ #
116
+ # NO:: the attempted command could not be successfully completed. For
117
+ # instance, the username/password used for logging in are incorrect;
118
+ # the selected mailbox does not exist; etc.
119
+ #
120
+ # BAD:: the request from the client does not follow the server's
121
+ # understanding of the IMAP protocol. This includes attempting
122
+ # commands from the wrong client state; for instance, attempting
123
+ # to perform a SEARCH command without having SELECTed a current
124
+ # mailbox. It can also signal an internal server
125
+ # failure (such as a disk crash) has occurred.
126
+ #
127
+ # BYE:: the server is saying goodbye. This can be part of a normal
128
+ # logout sequence, and can be used as part of a login sequence
129
+ # to indicate that the server is (for some reason) unwilling
130
+ # to accept your connection. As a response to any other command,
131
+ # it indicates either that the server is shutting down, or that
132
+ # the server is timing out the client connection due to inactivity.
133
+ #
134
+ # These three error response are represented by the errors
135
+ # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and
136
+ # Net::IMAP::ByeResponseError, all of which are subclasses of
137
+ # Net::IMAP::ResponseError. Essentially, all methods that involve
138
+ # sending a request to the server can generate one of these errors.
139
+ # Only the most pertinent instances have been documented below.
140
+ #
141
+ # Because the IMAP class uses Sockets for communication, its methods
142
+ # are also susceptible to the various errors that can occur when
143
+ # working with sockets. These are generally represented as
144
+ # Errno errors. For instance, any method that involves sending a
145
+ # request to the server and/or receiving a response from it could
146
+ # raise an Errno::EPIPE error if the network connection unexpectedly
147
+ # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2),
148
+ # and associated man pages.
149
+ #
150
+ # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
151
+ # is found to be in an incorrect format (for instance, when converting
152
+ # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
153
+ # thrown if a server response is non-parseable.
154
+ #
155
+ #
156
+ # == References
157
+ #
158
+ # [[IMAP]]
159
+ # M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
160
+ # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501)
161
+ #
162
+ # [[LANGUAGE-TAGS]]
163
+ # Alvestrand, H., "Tags for the Identification of
164
+ # Languages", RFC 1766, March 1995.
165
+ #
166
+ # [[MD5]]
167
+ # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC
168
+ # 1864, October 1995.
169
+ #
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.
174
+ #
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.
178
+ #
179
+ # [[RFC-2087]]
180
+ # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997.
181
+ #
182
+ # [[RFC-2086]]
183
+ # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997.
184
+ #
185
+ # [[RFC-2195]]
186
+ # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension
187
+ # for Simple Challenge/Response", RFC 2195, September 1997.
188
+ #
189
+ # [[SORT-THREAD-EXT]]
190
+ # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD
191
+ # Extensions", draft-ietf-imapext-sort, May 2003.
192
+ #
193
+ # [[OSSL]]
194
+ # http://www.openssl.org
195
+ #
196
+ # [[RSSL]]
197
+ # http://savannah.gnu.org/projects/rubypki
198
+ #
199
+ # [[UTF7]]
200
+ # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
201
+ # Unicode", RFC 2152, May 1997.
202
+ #
203
+ class IMAP < Protocol
204
+ include MonitorMixin
205
+ if defined?(OpenSSL::SSL)
206
+ include OpenSSL
207
+ include SSL
208
+ end
209
+
210
+ # Returns an initial greeting response from the server.
211
+ attr_reader :greeting
212
+
213
+ # Returns recorded untagged responses. For example:
214
+ #
215
+ # imap.select("inbox")
216
+ # p imap.responses["EXISTS"][-1]
217
+ # #=> 2
218
+ # p imap.responses["UIDVALIDITY"][-1]
219
+ # #=> 968263756
220
+ attr_reader :responses
221
+
222
+ # Returns all response handlers.
223
+ attr_reader :response_handlers
224
+
225
+ # Seconds to wait until a connection is opened.
226
+ # If the IMAP object cannot open a connection within this time,
227
+ # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
228
+ attr_reader :open_timeout
229
+
230
+ # The thread to receive exceptions.
231
+ attr_accessor :client_thread
232
+
233
+ # Flag indicating a message has been seen.
234
+ SEEN = :Seen
235
+
236
+ # Flag indicating a message has been answered.
237
+ ANSWERED = :Answered
238
+
239
+ # Flag indicating a message has been flagged for special or urgent
240
+ # attention.
241
+ FLAGGED = :Flagged
242
+
243
+ # Flag indicating a message has been marked for deletion. This
244
+ # will occur when the mailbox is closed or expunged.
245
+ DELETED = :Deleted
246
+
247
+ # Flag indicating a message is only a draft or work-in-progress version.
248
+ DRAFT = :Draft
249
+
250
+ # Flag indicating that the message is "recent," meaning that this
251
+ # session is the first session in which the client has been notified
252
+ # of this message.
253
+ RECENT = :Recent
254
+
255
+ # Flag indicating that a mailbox context name cannot contain
256
+ # children.
257
+ NOINFERIORS = :Noinferiors
258
+
259
+ # Flag indicating that a mailbox is not selected.
260
+ NOSELECT = :Noselect
261
+
262
+ # Flag indicating that a mailbox has been marked "interesting" by
263
+ # the server; this commonly indicates that the mailbox contains
264
+ # new messages.
265
+ MARKED = :Marked
266
+
267
+ # Flag indicating that the mailbox does not contains new messages.
268
+ UNMARKED = :Unmarked
269
+
270
+ # Returns the debug mode.
271
+ def self.debug
272
+ return @@debug
273
+ end
274
+
275
+ # Sets the debug mode.
276
+ def self.debug=(val)
277
+ return @@debug = val
278
+ end
279
+
280
+ # Returns the max number of flags interned to symbols.
281
+ def self.max_flag_count
282
+ return @@max_flag_count
283
+ end
284
+
285
+ # Sets the max number of flags interned to symbols.
286
+ def self.max_flag_count=(count)
287
+ @@max_flag_count = count
288
+ end
289
+
290
+ # Adds an authenticator for Net::IMAP#authenticate. +auth_type+
291
+ # is the type of authentication this authenticator supports
292
+ # (for instance, "LOGIN"). The +authenticator+ is an object
293
+ # which defines a process() method to handle authentication with
294
+ # the server. See Net::IMAP::LoginAuthenticator,
295
+ # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
296
+ # for examples.
297
+ #
298
+ #
299
+ # If +auth_type+ refers to an existing authenticator, it will be
300
+ # replaced by the new one.
301
+ def self.add_authenticator(auth_type, authenticator)
302
+ @@authenticators[auth_type] = authenticator
303
+ end
304
+
305
+ # The default port for IMAP connections, port 143
306
+ def self.default_port
307
+ return PORT
308
+ end
309
+
310
+ # The default port for IMAPS connections, port 993
311
+ def self.default_tls_port
312
+ return SSL_PORT
313
+ end
314
+
315
+ class << self
316
+ alias default_imap_port default_port
317
+ alias default_imaps_port default_tls_port
318
+ alias default_ssl_port default_tls_port
319
+ end
320
+
321
+ # Disconnects from the server.
322
+ def disconnect
323
+ return if disconnected?
324
+ begin
325
+ begin
326
+ # try to call SSL::SSLSocket#io.
327
+ @sock.io.shutdown
328
+ rescue NoMethodError
329
+ # @sock is not an SSL::SSLSocket.
330
+ @sock.shutdown
331
+ end
332
+ rescue Errno::ENOTCONN
333
+ # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
334
+ rescue Exception => e
335
+ @receiver_thread.raise(e)
336
+ end
337
+ @receiver_thread.join
338
+ synchronize do
339
+ @sock.close
340
+ end
341
+ raise e if e
342
+ end
343
+
344
+ # Returns true if disconnected from the server.
345
+ def disconnected?
346
+ return @sock.closed?
347
+ end
348
+
349
+ # Sends a CAPABILITY command, and returns an array of
350
+ # capabilities that the server supports. Each capability
351
+ # is a string. See [IMAP] for a list of possible
352
+ # capabilities.
353
+ #
354
+ # Note that the Net::IMAP class does not modify its
355
+ # behaviour according to the capabilities of the server;
356
+ # it is up to the user of the class to ensure that
357
+ # a certain capability is supported by a server before
358
+ # using it.
359
+ def capability
360
+ synchronize do
361
+ send_command("CAPABILITY")
362
+ return @responses.delete("CAPABILITY")[-1]
363
+ end
364
+ end
365
+
366
+ # Sends a NOOP command to the server. It does nothing.
367
+ def noop
368
+ send_command("NOOP")
369
+ end
370
+
371
+ # Sends a LOGOUT command to inform the server that the client is
372
+ # done with the connection.
373
+ def logout
374
+ send_command("LOGOUT")
375
+ end
376
+
377
+ # Sends a STARTTLS command to start TLS session.
378
+ def starttls(options = {}, verify = true)
379
+ send_command("STARTTLS") do |resp|
380
+ if resp.kind_of?(TaggedResponse) && resp.name == "OK"
381
+ begin
382
+ # for backward compatibility
383
+ certs = options.to_str
384
+ options = create_ssl_params(certs, verify)
385
+ rescue NoMethodError
386
+ end
387
+ start_tls_session(options)
388
+ end
389
+ end
390
+ end
391
+
392
+ # Sends an AUTHENTICATE command to authenticate the client.
393
+ # The +auth_type+ parameter is a string that represents
394
+ # the authentication mechanism to be used. Currently Net::IMAP
395
+ # supports the authentication mechanisms:
396
+ #
397
+ # LOGIN:: login using cleartext user and password.
398
+ # CRAM-MD5:: login with cleartext user and encrypted password
399
+ # (see [RFC-2195] for a full description). This
400
+ # mechanism requires that the server have the user's
401
+ # password stored in clear-text password.
402
+ #
403
+ # For both of these mechanisms, there should be two +args+: username
404
+ # and (cleartext) password. A server may not support one or the other
405
+ # of these mechanisms; check #capability() for a capability of
406
+ # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
407
+ #
408
+ # Authentication is done using the appropriate authenticator object:
409
+ # see @@authenticators for more information on plugging in your own
410
+ # authenticator.
411
+ #
412
+ # For example:
413
+ #
414
+ # imap.authenticate('LOGIN', user, password)
415
+ #
416
+ # A Net::IMAP::NoResponseError is raised if authentication fails.
417
+ def authenticate(auth_type, *args)
418
+ auth_type = auth_type.upcase
419
+ unless @@authenticators.has_key?(auth_type)
420
+ raise ArgumentError,
421
+ format('unknown auth type - "%s"', auth_type)
422
+ end
423
+ authenticator = @@authenticators[auth_type].new(*args)
424
+ send_command("AUTHENTICATE", auth_type) do |resp|
425
+ if resp.instance_of?(ContinuationRequest)
426
+ data = authenticator.process(resp.data.text.unpack("m")[0])
427
+ s = [data].pack("m0")
428
+ send_string_data(s)
429
+ put_string(CRLF)
430
+ end
431
+ end
432
+ end
433
+
434
+ # Sends a LOGIN command to identify the client and carries
435
+ # the plaintext +password+ authenticating this +user+. Note
436
+ # that, unlike calling #authenticate() with an +auth_type+
437
+ # of "LOGIN", #login() does *not* use the login authenticator.
438
+ #
439
+ # A Net::IMAP::NoResponseError is raised if authentication fails.
440
+ def login(user, password)
441
+ send_command("LOGIN", user, password)
442
+ end
443
+
444
+ # Sends a SELECT command to select a +mailbox+ so that messages
445
+ # in the +mailbox+ can be accessed.
446
+ #
447
+ # After you have selected a mailbox, you may retrieve the
448
+ # number of items in that mailbox from @responses["EXISTS"][-1],
449
+ # and the number of recent messages from @responses["RECENT"][-1].
450
+ # Note that these values can change if new messages arrive
451
+ # during a session; see #add_response_handler() for a way of
452
+ # detecting this event.
453
+ #
454
+ # A Net::IMAP::NoResponseError is raised if the mailbox does not
455
+ # exist or is for some reason non-selectable.
456
+ def select(mailbox)
457
+ synchronize do
458
+ @responses.clear
459
+ send_command("SELECT", mailbox)
460
+ end
461
+ end
462
+
463
+ # Sends a EXAMINE command to select a +mailbox+ so that messages
464
+ # in the +mailbox+ can be accessed. Behaves the same as #select(),
465
+ # except that the selected +mailbox+ is identified as read-only.
466
+ #
467
+ # A Net::IMAP::NoResponseError is raised if the mailbox does not
468
+ # exist or is for some reason non-examinable.
469
+ def examine(mailbox)
470
+ synchronize do
471
+ @responses.clear
472
+ send_command("EXAMINE", mailbox)
473
+ end
474
+ end
475
+
476
+ # Sends a CREATE command to create a new +mailbox+.
477
+ #
478
+ # A Net::IMAP::NoResponseError is raised if a mailbox with that name
479
+ # cannot be created.
480
+ def create(mailbox)
481
+ send_command("CREATE", mailbox)
482
+ end
483
+
484
+ # Sends a DELETE command to remove the +mailbox+.
485
+ #
486
+ # A Net::IMAP::NoResponseError is raised if a mailbox with that name
487
+ # cannot be deleted, either because it does not exist or because the
488
+ # client does not have permission to delete it.
489
+ def delete(mailbox)
490
+ send_command("DELETE", mailbox)
491
+ end
492
+
493
+ # Sends a RENAME command to change the name of the +mailbox+ to
494
+ # +newname+.
495
+ #
496
+ # A Net::IMAP::NoResponseError is raised if a mailbox with the
497
+ # name +mailbox+ cannot be renamed to +newname+ for whatever
498
+ # reason; for instance, because +mailbox+ does not exist, or
499
+ # because there is already a mailbox with the name +newname+.
500
+ def rename(mailbox, newname)
501
+ send_command("RENAME", mailbox, newname)
502
+ end
503
+
504
+ # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
505
+ # the server's set of "active" or "subscribed" mailboxes as returned
506
+ # by #lsub().
507
+ #
508
+ # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
509
+ # subscribed to; for instance, because it does not exist.
510
+ def subscribe(mailbox)
511
+ send_command("SUBSCRIBE", mailbox)
512
+ end
513
+
514
+ # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name
515
+ # from the server's set of "active" or "subscribed" mailboxes.
516
+ #
517
+ # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
518
+ # unsubscribed from; for instance, because the client is not currently
519
+ # subscribed to it.
520
+ def unsubscribe(mailbox)
521
+ send_command("UNSUBSCRIBE", mailbox)
522
+ end
523
+
524
+ # Sends a LIST command, and returns a subset of names from
525
+ # the complete set of all names available to the client.
526
+ # +refname+ provides a context (for instance, a base directory
527
+ # in a directory-based mailbox hierarchy). +mailbox+ specifies
528
+ # a mailbox or (via wildcards) mailboxes under that context.
529
+ # Two wildcards may be used in +mailbox+: '*', which matches
530
+ # all characters *including* the hierarchy delimiter (for instance,
531
+ # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
532
+ # which matches all characters *except* the hierarchy delimiter.
533
+ #
534
+ # If +refname+ is empty, +mailbox+ is used directly to determine
535
+ # which mailboxes to match. If +mailbox+ is empty, the root
536
+ # name of +refname+ and the hierarchy delimiter are returned.
537
+ #
538
+ # The return value is an array of +Net::IMAP::MailboxList+. For example:
539
+ #
540
+ # imap.create("foo/bar")
541
+ # imap.create("foo/baz")
542
+ # p imap.list("", "foo/%")
543
+ # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
544
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
545
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
546
+ def list(refname, mailbox)
547
+ synchronize do
548
+ send_command("LIST", refname, mailbox)
549
+ return @responses.delete("LIST")
550
+ end
551
+ end
552
+
553
+ # Sends a XLIST command, and returns a subset of names from
554
+ # the complete set of all names available to the client.
555
+ # +refname+ provides a context (for instance, a base directory
556
+ # in a directory-based mailbox hierarchy). +mailbox+ specifies
557
+ # a mailbox or (via wildcards) mailboxes under that context.
558
+ # Two wildcards may be used in +mailbox+: '*', which matches
559
+ # all characters *including* the hierarchy delimiter (for instance,
560
+ # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
561
+ # which matches all characters *except* the hierarchy delimiter.
562
+ #
563
+ # If +refname+ is empty, +mailbox+ is used directly to determine
564
+ # which mailboxes to match. If +mailbox+ is empty, the root
565
+ # name of +refname+ and the hierarchy delimiter are returned.
566
+ #
567
+ # The XLIST command is like the LIST command except that the flags
568
+ # returned refer to the function of the folder/mailbox, e.g. :Sent
569
+ #
570
+ # The return value is an array of +Net::IMAP::MailboxList+. For example:
571
+ #
572
+ # imap.create("foo/bar")
573
+ # imap.create("foo/baz")
574
+ # p imap.xlist("", "foo/%")
575
+ # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
576
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
577
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
578
+ def xlist(refname, mailbox)
579
+ synchronize do
580
+ send_command("XLIST", refname, mailbox)
581
+ return @responses.delete("XLIST")
582
+ end
583
+ end
584
+
585
+ # Sends the GETQUOTAROOT command along with the specified +mailbox+.
586
+ # This command is generally available to both admin and user.
587
+ # If this mailbox exists, it returns an array containing objects of type
588
+ # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
589
+ def getquotaroot(mailbox)
590
+ synchronize do
591
+ send_command("GETQUOTAROOT", mailbox)
592
+ result = []
593
+ result.concat(@responses.delete("QUOTAROOT"))
594
+ result.concat(@responses.delete("QUOTA"))
595
+ return result
596
+ end
597
+ end
598
+
599
+ # Sends the GETQUOTA command along with specified +mailbox+.
600
+ # If this mailbox exists, then an array containing a
601
+ # Net::IMAP::MailboxQuota object is returned. This
602
+ # command is generally only available to server admin.
603
+ def getquota(mailbox)
604
+ synchronize do
605
+ send_command("GETQUOTA", mailbox)
606
+ return @responses.delete("QUOTA")
607
+ end
608
+ end
609
+
610
+ # Sends a SETQUOTA command along with the specified +mailbox+ and
611
+ # +quota+. If +quota+ is nil, then +quota+ will be unset for that
612
+ # mailbox. Typically one needs to be logged in as a server admin
613
+ # for this to work. The IMAP quota commands are described in
614
+ # [RFC-2087].
615
+ def setquota(mailbox, quota)
616
+ if quota.nil?
617
+ data = '()'
618
+ else
619
+ data = '(STORAGE ' + quota.to_s + ')'
620
+ end
621
+ send_command("SETQUOTA", mailbox, RawData.new(data))
622
+ end
623
+
624
+ # Sends the SETACL command along with +mailbox+, +user+ and the
625
+ # +rights+ that user is to have on that mailbox. If +rights+ is nil,
626
+ # then that user will be stripped of any rights to that mailbox.
627
+ # The IMAP ACL commands are described in [RFC-2086].
628
+ def setacl(mailbox, user, rights)
629
+ if rights.nil?
630
+ send_command("SETACL", mailbox, user, "")
631
+ else
632
+ send_command("SETACL", mailbox, user, rights)
633
+ end
634
+ end
635
+
636
+ # Send the GETACL command along with a specified +mailbox+.
637
+ # If this mailbox exists, an array containing objects of
638
+ # Net::IMAP::MailboxACLItem will be returned.
639
+ def getacl(mailbox)
640
+ synchronize do
641
+ send_command("GETACL", mailbox)
642
+ return @responses.delete("ACL")[-1]
643
+ end
644
+ end
645
+
646
+ # Sends a LSUB command, and returns a subset of names from the set
647
+ # of names that the user has declared as being "active" or
648
+ # "subscribed." +refname+ and +mailbox+ are interpreted as
649
+ # for #list().
650
+ # The return value is an array of +Net::IMAP::MailboxList+.
651
+ def lsub(refname, mailbox)
652
+ synchronize do
653
+ send_command("LSUB", refname, mailbox)
654
+ return @responses.delete("LSUB")
655
+ end
656
+ end
657
+
658
+ # Sends a STATUS command, and returns the status of the indicated
659
+ # +mailbox+. +attr+ is a list of one or more attributes whose
660
+ # statuses are to be requested. Supported attributes include:
661
+ #
662
+ # MESSAGES:: the number of messages in the mailbox.
663
+ # RECENT:: the number of recent messages in the mailbox.
664
+ # UNSEEN:: the number of unseen messages in the mailbox.
665
+ #
666
+ # The return value is a hash of attributes. For example:
667
+ #
668
+ # p imap.status("inbox", ["MESSAGES", "RECENT"])
669
+ # #=> {"RECENT"=>0, "MESSAGES"=>44}
670
+ #
671
+ # A Net::IMAP::NoResponseError is raised if status values
672
+ # for +mailbox+ cannot be returned; for instance, because it
673
+ # does not exist.
674
+ def status(mailbox, attr)
675
+ synchronize do
676
+ send_command("STATUS", mailbox, attr)
677
+ return @responses.delete("STATUS")[-1].attr
678
+ end
679
+ end
680
+
681
+ # Sends a APPEND command to append the +message+ to the end of
682
+ # the +mailbox+. The optional +flags+ argument is an array of
683
+ # flags initially passed to the new message. The optional
684
+ # +date_time+ argument specifies the creation time to assign to the
685
+ # new message; it defaults to the current time.
686
+ # For example:
687
+ #
688
+ # imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
689
+ # Subject: hello
690
+ # From: shugo@ruby-lang.org
691
+ # To: shugo@ruby-lang.org
692
+ #
693
+ # hello world
694
+ # EOF
695
+ #
696
+ # A Net::IMAP::NoResponseError is raised if the mailbox does
697
+ # not exist (it is not created automatically), or if the flags,
698
+ # date_time, or message arguments contain errors.
699
+ def append(mailbox, message, flags = nil, date_time = nil)
700
+ args = []
701
+ if flags
702
+ args.push(flags)
703
+ end
704
+ args.push(date_time) if date_time
705
+ args.push(Literal.new(message))
706
+ send_command("APPEND", mailbox, *args)
707
+ end
708
+
709
+ # Sends a CHECK command to request a checkpoint of the currently
710
+ # selected mailbox. This performs implementation-specific
711
+ # housekeeping; for instance, reconciling the mailbox's
712
+ # in-memory and on-disk state.
713
+ def check
714
+ send_command("CHECK")
715
+ end
716
+
717
+ # Sends a CLOSE command to close the currently selected mailbox.
718
+ # The CLOSE command permanently removes from the mailbox all
719
+ # messages that have the \Deleted flag set.
720
+ def close
721
+ send_command("CLOSE")
722
+ end
723
+
724
+ # Sends a EXPUNGE command to permanently remove from the currently
725
+ # selected mailbox all messages that have the \Deleted flag set.
726
+ def expunge
727
+ synchronize do
728
+ send_command("EXPUNGE")
729
+ return @responses.delete("EXPUNGE")
730
+ end
731
+ end
732
+
733
+ # Sends a SEARCH command to search the mailbox for messages that
734
+ # match the given searching criteria, and returns message sequence
735
+ # numbers. +keys+ can either be a string holding the entire
736
+ # search string, or a single-dimension array of search keywords and
737
+ # arguments. The following are some common search criteria;
738
+ # see [IMAP] section 6.4.4 for a full list.
739
+ #
740
+ # <message set>:: a set of message sequence numbers. ',' indicates
741
+ # an interval, ':' indicates a range. For instance,
742
+ # '2,10:12,15' means "2,10,11,12,15".
743
+ #
744
+ # BEFORE <date>:: messages with an internal date strictly before
745
+ # <date>. The date argument has a format similar
746
+ # to 8-Aug-2002.
747
+ #
748
+ # BODY <string>:: messages that contain <string> within their body.
749
+ #
750
+ # CC <string>:: messages containing <string> in their CC field.
751
+ #
752
+ # FROM <string>:: messages that contain <string> in their FROM field.
753
+ #
754
+ # NEW:: messages with the \Recent, but not the \Seen, flag set.
755
+ #
756
+ # NOT <search-key>:: negate the following search key.
757
+ #
758
+ # OR <search-key> <search-key>:: "or" two search keys together.
759
+ #
760
+ # ON <date>:: messages with an internal date exactly equal to <date>,
761
+ # which has a format similar to 8-Aug-2002.
762
+ #
763
+ # SINCE <date>:: messages with an internal date on or after <date>.
764
+ #
765
+ # SUBJECT <string>:: messages with <string> in their subject.
766
+ #
767
+ # TO <string>:: messages with <string> in their TO field.
768
+ #
769
+ # For example:
770
+ #
771
+ # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
772
+ # #=> [1, 6, 7, 8]
773
+ def search(keys, charset = nil)
774
+ return search_internal("SEARCH", keys, charset)
775
+ end
776
+
777
+ # Similar to #search(), but returns unique identifiers.
778
+ def uid_search(keys, charset = nil)
779
+ return search_internal("UID SEARCH", keys, charset)
780
+ end
781
+
782
+ # Sends a FETCH command to retrieve data associated with a message
783
+ # in the mailbox.
784
+ #
785
+ # The +set+ parameter is a number or a range between two numbers,
786
+ # or an array of those. The number is a message sequence number,
787
+ # where -1 represents a '*' for use in range notation like 100..-1
788
+ # being interpreted as '100:*'. Beware that the +exclude_end?+
789
+ # property of a Range object is ignored, and the contents of a
790
+ # range are independent of the order of the range endpoints as per
791
+ # the protocol specification, so 1...5, 5..1 and 5...1 are all
792
+ # equivalent to 1..5.
793
+ #
794
+ # +attr+ is a list of attributes to fetch; see the documentation
795
+ # for Net::IMAP::FetchData for a list of valid attributes.
796
+ #
797
+ # The return value is an array of Net::IMAP::FetchData or nil
798
+ # (instead of an empty array) if there is no matching message.
799
+ #
800
+ # For example:
801
+ #
802
+ # p imap.fetch(6..8, "UID")
803
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
804
+ # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
805
+ # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
806
+ # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
807
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
808
+ # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
809
+ # p data.seqno
810
+ # #=> 6
811
+ # p data.attr["RFC822.SIZE"]
812
+ # #=> 611
813
+ # p data.attr["INTERNALDATE"]
814
+ # #=> "12-Oct-2000 22:40:59 +0900"
815
+ # p data.attr["UID"]
816
+ # #=> 98
817
+ def fetch(set, attr, mod = nil)
818
+ return fetch_internal("FETCH", set, attr, mod)
819
+ end
820
+
821
+ # Similar to #fetch(), but +set+ contains unique identifiers.
822
+ def uid_fetch(set, attr, mod = nil)
823
+ return fetch_internal("UID FETCH", set, attr, mod)
824
+ end
825
+
826
+ # Sends a STORE command to alter data associated with messages
827
+ # in the mailbox, in particular their flags. The +set+ parameter
828
+ # is a number, an array of numbers, or a Range object. Each number
829
+ # is a message sequence number. +attr+ is the name of a data item
830
+ # to store: 'FLAGS' will replace the message's flag list
831
+ # with the provided one, '+FLAGS' will add the provided flags,
832
+ # and '-FLAGS' will remove them. +flags+ is a list of flags.
833
+ #
834
+ # The return value is an array of Net::IMAP::FetchData. For example:
835
+ #
836
+ # p imap.store(6..8, "+FLAGS", [:Deleted])
837
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
838
+ # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
839
+ # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
840
+ def store(set, attr, flags)
841
+ return store_internal("STORE", set, attr, flags)
842
+ end
843
+
844
+ # Similar to #store(), but +set+ contains unique identifiers.
845
+ def uid_store(set, attr, flags)
846
+ return store_internal("UID STORE", set, attr, flags)
847
+ end
848
+
849
+ # Sends a COPY command to copy the specified message(s) to the end
850
+ # of the specified destination +mailbox+. The +set+ parameter is
851
+ # a number, an array of numbers, or a Range object. The number is
852
+ # a message sequence number.
853
+ def copy(set, mailbox)
854
+ copy_internal("COPY", set, mailbox)
855
+ end
856
+
857
+ # Similar to #copy(), but +set+ contains unique identifiers.
858
+ def uid_copy(set, mailbox)
859
+ copy_internal("UID COPY", set, mailbox)
860
+ end
861
+
862
+ # Sends a MOVE command to move the specified message(s) to the end
863
+ # of the specified destination +mailbox+. The +set+ parameter is
864
+ # a number, an array of numbers, or a Range object. The number is
865
+ # a message sequence number.
866
+ # The IMAP MOVE extension is described in [RFC-6851].
867
+ def move(set, mailbox)
868
+ copy_internal("MOVE", set, mailbox)
869
+ end
870
+
871
+ # Similar to #move(), but +set+ contains unique identifiers.
872
+ def uid_move(set, mailbox)
873
+ copy_internal("UID MOVE", set, mailbox)
874
+ end
875
+
876
+ # Sends a SORT command to sort messages in the mailbox.
877
+ # Returns an array of message sequence numbers. For example:
878
+ #
879
+ # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
880
+ # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
881
+ # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
882
+ # #=> [6, 7, 8, 1]
883
+ #
884
+ # See [SORT-THREAD-EXT] for more details.
885
+ def sort(sort_keys, search_keys, charset)
886
+ return sort_internal("SORT", sort_keys, search_keys, charset)
887
+ end
888
+
889
+ # Similar to #sort(), but returns an array of unique identifiers.
890
+ def uid_sort(sort_keys, search_keys, charset)
891
+ return sort_internal("UID SORT", sort_keys, search_keys, charset)
892
+ end
893
+
894
+ # Adds a response handler. For example, to detect when
895
+ # the server sends a new EXISTS response (which normally
896
+ # indicates new messages being added to the mailbox),
897
+ # add the following handler after selecting the
898
+ # mailbox:
899
+ #
900
+ # imap.add_response_handler { |resp|
901
+ # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
902
+ # puts "Mailbox now has #{resp.data} messages"
903
+ # end
904
+ # }
905
+ #
906
+ def add_response_handler(handler = nil, &block)
907
+ raise ArgumentError, "two Procs are passed" if handler && block
908
+ @response_handlers.push(block || handler)
909
+ end
910
+
911
+ # Removes the response handler.
912
+ def remove_response_handler(handler)
913
+ @response_handlers.delete(handler)
914
+ end
915
+
916
+ # Similar to #search(), but returns message sequence numbers in threaded
917
+ # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
918
+ # are:
919
+ #
920
+ # ORDEREDSUBJECT:: split into single-level threads according to subject,
921
+ # ordered by date.
922
+ # REFERENCES:: split into threads by parent/child relationships determined
923
+ # by which message is a reply to which.
924
+ #
925
+ # Unlike #search(), +charset+ is a required argument. US-ASCII
926
+ # and UTF-8 are sample values.
927
+ #
928
+ # See [SORT-THREAD-EXT] for more details.
929
+ def thread(algorithm, search_keys, charset)
930
+ return thread_internal("THREAD", algorithm, search_keys, charset)
931
+ end
932
+
933
+ # Similar to #thread(), but returns unique identifiers instead of
934
+ # message sequence numbers.
935
+ def uid_thread(algorithm, search_keys, charset)
936
+ return thread_internal("UID THREAD", algorithm, search_keys, charset)
937
+ end
938
+
939
+ # Sends an IDLE command that waits for notifications of new or expunged
940
+ # messages. Yields responses from the server during the IDLE.
941
+ #
942
+ # Use #idle_done() to leave IDLE.
943
+ #
944
+ # If +timeout+ is given, this method returns after +timeout+ seconds passed.
945
+ # +timeout+ can be used for keep-alive. For example, the following code
946
+ # checks the connection for each 60 seconds.
947
+ #
948
+ # loop do
949
+ # imap.idle(60) do |res|
950
+ # ...
951
+ # end
952
+ # end
953
+ def idle(timeout = nil, &response_handler)
954
+ raise LocalJumpError, "no block given" unless response_handler
955
+
956
+ response = nil
957
+
958
+ synchronize do
959
+ tag = Thread.current[:net_imap_tag] = generate_tag
960
+ put_string("#{tag} IDLE#{CRLF}")
961
+
962
+ begin
963
+ add_response_handler(&response_handler)
964
+ @idle_done_cond = new_cond
965
+ @idle_done_cond.wait(timeout)
966
+ @idle_done_cond = nil
967
+ if @receiver_thread_terminating
968
+ raise @exception || Net::IMAP::Error.new("connection closed")
969
+ end
970
+ ensure
971
+ unless @receiver_thread_terminating
972
+ remove_response_handler(response_handler)
973
+ put_string("DONE#{CRLF}")
974
+ response = get_tagged_response(tag, "IDLE")
975
+ end
976
+ end
977
+ end
978
+
979
+ return response
980
+ end
981
+
982
+ # Leaves IDLE.
983
+ def idle_done
984
+ synchronize do
985
+ if @idle_done_cond.nil?
986
+ raise Net::IMAP::Error, "not during IDLE"
987
+ end
988
+ @idle_done_cond.signal
989
+ end
990
+ end
991
+
992
+ # Decode a string from modified UTF-7 format to UTF-8.
993
+ #
994
+ # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
995
+ # slightly modified version of this to encode mailbox names
996
+ # containing non-ASCII characters; see [IMAP] section 5.1.3.
997
+ #
998
+ # Net::IMAP does _not_ automatically encode and decode
999
+ # mailbox names to and from UTF-7.
1000
+ def self.decode_utf7(s)
1001
+ return s.gsub(/&([^-]+)?-/n) {
1002
+ if $1
1003
+ ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
1004
+ else
1005
+ "&"
1006
+ end
1007
+ }
1008
+ end
1009
+
1010
+ # Encode a string from UTF-8 format to modified UTF-7.
1011
+ def self.encode_utf7(s)
1012
+ return s.gsub(/(&)|[^\x20-\x7e]+/) {
1013
+ if $1
1014
+ "&-"
1015
+ else
1016
+ base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
1017
+ "&" + base64.delete("=").tr("/", ",") + "-"
1018
+ end
1019
+ }.force_encoding("ASCII-8BIT")
1020
+ end
1021
+
1022
+ # Formats +time+ as an IMAP-style date.
1023
+ def self.format_date(time)
1024
+ return time.strftime('%d-%b-%Y')
1025
+ end
1026
+
1027
+ # Formats +time+ as an IMAP-style date-time.
1028
+ def self.format_datetime(time)
1029
+ return time.strftime('%d-%b-%Y %H:%M %z')
1030
+ end
1031
+
1032
+ private
1033
+
1034
+ CRLF = "\r\n" # :nodoc:
1035
+ PORT = 143 # :nodoc:
1036
+ SSL_PORT = 993 # :nodoc:
1037
+
1038
+ @@debug = false
1039
+ @@authenticators = {}
1040
+ @@max_flag_count = 10000
1041
+
1042
+ # :call-seq:
1043
+ # Net::IMAP.new(host, options = {})
1044
+ #
1045
+ # Creates a new Net::IMAP object and connects it to the specified
1046
+ # +host+.
1047
+ #
1048
+ # +options+ is an option hash, each key of which is a symbol.
1049
+ #
1050
+ # The available options are:
1051
+ #
1052
+ # port:: Port number (default value is 143 for imap, or 993 for imaps)
1053
+ # ssl:: If options[:ssl] is true, then an attempt will be made
1054
+ # to use SSL (now TLS) to connect to the server. For this to work
1055
+ # OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
1056
+ # be installed.
1057
+ # If options[:ssl] is a hash, it's passed to
1058
+ # OpenSSL::SSL::SSLContext#set_params as parameters.
1059
+ # open_timeout:: Seconds to wait until a connection is opened
1060
+ #
1061
+ # The most common errors are:
1062
+ #
1063
+ # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
1064
+ # firewall.
1065
+ # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
1066
+ # being dropped by an intervening firewall).
1067
+ # Errno::ENETUNREACH:: There is no route to that network.
1068
+ # SocketError:: Hostname not known or other socket error.
1069
+ # Net::IMAP::ByeResponseError:: The connected to the host was successful, but
1070
+ # it immediately said goodbye.
1071
+ def initialize(host, port_or_options = {},
1072
+ usessl = false, certs = nil, verify = true)
1073
+ super()
1074
+ @host = host
1075
+ begin
1076
+ options = port_or_options.to_hash
1077
+ rescue NoMethodError
1078
+ # for backward compatibility
1079
+ options = {}
1080
+ options[:port] = port_or_options
1081
+ if usessl
1082
+ options[:ssl] = create_ssl_params(certs, verify)
1083
+ end
1084
+ end
1085
+ @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
1086
+ @tag_prefix = "RUBY"
1087
+ @tagno = 0
1088
+ @open_timeout = options[:open_timeout] || 30
1089
+ @parser = ResponseParser.new
1090
+ @sock = tcp_socket(@host, @port)
1091
+ begin
1092
+ if options[:ssl]
1093
+ start_tls_session(options[:ssl])
1094
+ @usessl = true
1095
+ else
1096
+ @usessl = false
1097
+ end
1098
+ @responses = Hash.new([].freeze)
1099
+ @tagged_responses = {}
1100
+ @response_handlers = []
1101
+ @tagged_response_arrival = new_cond
1102
+ @continued_command_tag = nil
1103
+ @continuation_request_arrival = new_cond
1104
+ @continuation_request_exception = nil
1105
+ @idle_done_cond = nil
1106
+ @logout_command_tag = nil
1107
+ @debug_output_bol = true
1108
+ @exception = nil
1109
+
1110
+ @greeting = get_response
1111
+ if @greeting.nil?
1112
+ raise Error, "connection closed"
1113
+ end
1114
+ if @greeting.name == "BYE"
1115
+ raise ByeResponseError, @greeting
1116
+ end
1117
+
1118
+ @client_thread = Thread.current
1119
+ @receiver_thread = Thread.start {
1120
+ begin
1121
+ receive_responses
1122
+ rescue Exception
1123
+ end
1124
+ }
1125
+ @receiver_thread_terminating = false
1126
+ rescue Exception
1127
+ @sock.close
1128
+ raise
1129
+ end
1130
+ end
1131
+
1132
+ def tcp_socket(host, port)
1133
+ s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
1134
+ s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
1135
+ s
1136
+ rescue Errno::ETIMEDOUT
1137
+ raise Net::OpenTimeout, "Timeout to open TCP connection to " +
1138
+ "#{host}:#{port} (exceeds #{@open_timeout} seconds)"
1139
+ end
1140
+
1141
+ def receive_responses
1142
+ connection_closed = false
1143
+ until connection_closed
1144
+ synchronize do
1145
+ @exception = nil
1146
+ end
1147
+ begin
1148
+ resp = get_response
1149
+ rescue Exception => e
1150
+ synchronize do
1151
+ @sock.close
1152
+ @exception = e
1153
+ end
1154
+ break
1155
+ end
1156
+ unless resp
1157
+ synchronize do
1158
+ @exception = EOFError.new("end of file reached")
1159
+ end
1160
+ break
1161
+ end
1162
+ begin
1163
+ synchronize do
1164
+ case resp
1165
+ when TaggedResponse
1166
+ @tagged_responses[resp.tag] = resp
1167
+ @tagged_response_arrival.broadcast
1168
+ case resp.tag
1169
+ when @logout_command_tag
1170
+ return
1171
+ when @continued_command_tag
1172
+ @continuation_request_exception =
1173
+ RESPONSE_ERRORS[resp.name].new(resp)
1174
+ @continuation_request_arrival.signal
1175
+ end
1176
+ when UntaggedResponse
1177
+ record_response(resp.name, resp.data)
1178
+ if resp.data.instance_of?(ResponseText) &&
1179
+ (code = resp.data.code)
1180
+ record_response(code.name, code.data)
1181
+ end
1182
+ if resp.name == "BYE" && @logout_command_tag.nil?
1183
+ @sock.close
1184
+ @exception = ByeResponseError.new(resp)
1185
+ connection_closed = true
1186
+ end
1187
+ when ContinuationRequest
1188
+ @continuation_request_arrival.signal
1189
+ end
1190
+ @response_handlers.each do |handler|
1191
+ handler.call(resp)
1192
+ end
1193
+ end
1194
+ rescue Exception => e
1195
+ @exception = e
1196
+ synchronize do
1197
+ @tagged_response_arrival.broadcast
1198
+ @continuation_request_arrival.broadcast
1199
+ end
1200
+ end
1201
+ end
1202
+ synchronize do
1203
+ @receiver_thread_terminating = true
1204
+ @tagged_response_arrival.broadcast
1205
+ @continuation_request_arrival.broadcast
1206
+ if @idle_done_cond
1207
+ @idle_done_cond.signal
1208
+ end
1209
+ end
1210
+ end
1211
+
1212
+ def get_tagged_response(tag, cmd)
1213
+ until @tagged_responses.key?(tag)
1214
+ raise @exception if @exception
1215
+ @tagged_response_arrival.wait
1216
+ end
1217
+ resp = @tagged_responses.delete(tag)
1218
+ case resp.name
1219
+ when /\A(?:NO)\z/ni
1220
+ raise NoResponseError, resp
1221
+ when /\A(?:BAD)\z/ni
1222
+ raise BadResponseError, resp
1223
+ else
1224
+ return resp
1225
+ end
1226
+ end
1227
+
1228
+ def get_response
1229
+ buff = String.new
1230
+ while true
1231
+ s = @sock.gets(CRLF)
1232
+ break unless s
1233
+ buff.concat(s)
1234
+ if /\{(\d+)\}\r\n/n =~ s
1235
+ s = @sock.read($1.to_i)
1236
+ buff.concat(s)
1237
+ else
1238
+ break
1239
+ end
1240
+ end
1241
+ return nil if buff.length == 0
1242
+ if @@debug
1243
+ $stderr.print(buff.gsub(/^/n, "S: "))
1244
+ end
1245
+ return @parser.parse(buff)
1246
+ end
1247
+
1248
+ def record_response(name, data)
1249
+ unless @responses.has_key?(name)
1250
+ @responses[name] = []
1251
+ end
1252
+ @responses[name].push(data)
1253
+ end
1254
+
1255
+ def send_command(cmd, *args, &block)
1256
+ synchronize do
1257
+ args.each do |i|
1258
+ validate_data(i)
1259
+ end
1260
+ tag = generate_tag
1261
+ put_string(tag + " " + cmd)
1262
+ args.each do |i|
1263
+ put_string(" ")
1264
+ send_data(i, tag)
1265
+ end
1266
+ put_string(CRLF)
1267
+ if cmd == "LOGOUT"
1268
+ @logout_command_tag = tag
1269
+ end
1270
+ if block
1271
+ add_response_handler(&block)
1272
+ end
1273
+ begin
1274
+ return get_tagged_response(tag, cmd)
1275
+ ensure
1276
+ if block
1277
+ remove_response_handler(block)
1278
+ end
1279
+ end
1280
+ end
1281
+ end
1282
+
1283
+ def generate_tag
1284
+ @tagno += 1
1285
+ return format("%s%04d", @tag_prefix, @tagno)
1286
+ end
1287
+
1288
+ def put_string(str)
1289
+ @sock.print(str)
1290
+ if @@debug
1291
+ if @debug_output_bol
1292
+ $stderr.print("C: ")
1293
+ end
1294
+ $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
1295
+ if /\r\n\z/n.match(str)
1296
+ @debug_output_bol = true
1297
+ else
1298
+ @debug_output_bol = false
1299
+ end
1300
+ end
1301
+ end
1302
+
1303
+ def validate_data(data)
1304
+ case data
1305
+ when nil
1306
+ when String
1307
+ when Integer
1308
+ NumValidator.ensure_number(data)
1309
+ when Array
1310
+ if data[0] == 'CHANGEDSINCE'
1311
+ NumValidator.ensure_mod_sequence_value(data[1])
1312
+ else
1313
+ data.each do |i|
1314
+ validate_data(i)
1315
+ end
1316
+ end
1317
+ when Time
1318
+ when Symbol
1319
+ else
1320
+ data.validate
1321
+ end
1322
+ end
1323
+
1324
+ def send_data(data, tag = nil)
1325
+ case data
1326
+ when nil
1327
+ put_string("NIL")
1328
+ when String
1329
+ send_string_data(data, tag)
1330
+ when Integer
1331
+ send_number_data(data)
1332
+ when Array
1333
+ send_list_data(data, tag)
1334
+ when Time
1335
+ send_time_data(data)
1336
+ when Symbol
1337
+ send_symbol_data(data)
1338
+ else
1339
+ data.send_data(self, tag)
1340
+ end
1341
+ end
1342
+
1343
+ def send_string_data(str, tag = nil)
1344
+ case str
1345
+ when ""
1346
+ put_string('""')
1347
+ when /[\x80-\xff\r\n]/n
1348
+ # literal
1349
+ send_literal(str, tag)
1350
+ when /[(){ \x00-\x1f\x7f%*"\\]/n
1351
+ # quoted string
1352
+ send_quoted_string(str)
1353
+ else
1354
+ put_string(str)
1355
+ end
1356
+ end
1357
+
1358
+ def send_quoted_string(str)
1359
+ put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
1360
+ end
1361
+
1362
+ def send_literal(str, tag = nil)
1363
+ synchronize do
1364
+ put_string("{" + str.bytesize.to_s + "}" + CRLF)
1365
+ @continued_command_tag = tag
1366
+ @continuation_request_exception = nil
1367
+ begin
1368
+ @continuation_request_arrival.wait
1369
+ e = @continuation_request_exception || @exception
1370
+ raise e if e
1371
+ put_string(str)
1372
+ ensure
1373
+ @continued_command_tag = nil
1374
+ @continuation_request_exception = nil
1375
+ end
1376
+ end
1377
+ end
1378
+
1379
+ def send_number_data(num)
1380
+ put_string(num.to_s)
1381
+ end
1382
+
1383
+ def send_list_data(list, tag = nil)
1384
+ put_string("(")
1385
+ first = true
1386
+ list.each do |i|
1387
+ if first
1388
+ first = false
1389
+ else
1390
+ put_string(" ")
1391
+ end
1392
+ send_data(i, tag)
1393
+ end
1394
+ put_string(")")
1395
+ end
1396
+
1397
+ DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
1398
+
1399
+ def send_time_data(time)
1400
+ t = time.dup.gmtime
1401
+ s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
1402
+ t.day, DATE_MONTH[t.month - 1], t.year,
1403
+ t.hour, t.min, t.sec)
1404
+ put_string(s)
1405
+ end
1406
+
1407
+ def send_symbol_data(symbol)
1408
+ put_string("\\" + symbol.to_s)
1409
+ end
1410
+
1411
+ def search_internal(cmd, keys, charset)
1412
+ if keys.instance_of?(String)
1413
+ keys = [RawData.new(keys)]
1414
+ else
1415
+ normalize_searching_criteria(keys)
1416
+ end
1417
+ synchronize do
1418
+ if charset
1419
+ send_command(cmd, "CHARSET", charset, *keys)
1420
+ else
1421
+ send_command(cmd, *keys)
1422
+ end
1423
+ return @responses.delete("SEARCH")[-1]
1424
+ end
1425
+ end
1426
+
1427
+ def fetch_internal(cmd, set, attr, mod = nil)
1428
+ case attr
1429
+ when String then
1430
+ attr = RawData.new(attr)
1431
+ when Array then
1432
+ attr = attr.map { |arg|
1433
+ arg.is_a?(String) ? RawData.new(arg) : arg
1434
+ }
1435
+ end
1436
+
1437
+ synchronize do
1438
+ @responses.delete("FETCH")
1439
+ if mod
1440
+ send_command(cmd, MessageSet.new(set), attr, mod)
1441
+ else
1442
+ send_command(cmd, MessageSet.new(set), attr)
1443
+ end
1444
+ return @responses.delete("FETCH")
1445
+ end
1446
+ end
1447
+
1448
+ def store_internal(cmd, set, attr, flags)
1449
+ if attr.instance_of?(String)
1450
+ attr = RawData.new(attr)
1451
+ end
1452
+ synchronize do
1453
+ @responses.delete("FETCH")
1454
+ send_command(cmd, MessageSet.new(set), attr, flags)
1455
+ return @responses.delete("FETCH")
1456
+ end
1457
+ end
1458
+
1459
+ def copy_internal(cmd, set, mailbox)
1460
+ send_command(cmd, MessageSet.new(set), mailbox)
1461
+ end
1462
+
1463
+ def sort_internal(cmd, sort_keys, search_keys, charset)
1464
+ if search_keys.instance_of?(String)
1465
+ search_keys = [RawData.new(search_keys)]
1466
+ else
1467
+ normalize_searching_criteria(search_keys)
1468
+ end
1469
+ normalize_searching_criteria(search_keys)
1470
+ synchronize do
1471
+ send_command(cmd, sort_keys, charset, *search_keys)
1472
+ return @responses.delete("SORT")[-1]
1473
+ end
1474
+ end
1475
+
1476
+ def thread_internal(cmd, algorithm, search_keys, charset)
1477
+ if search_keys.instance_of?(String)
1478
+ search_keys = [RawData.new(search_keys)]
1479
+ else
1480
+ normalize_searching_criteria(search_keys)
1481
+ end
1482
+ normalize_searching_criteria(search_keys)
1483
+ send_command(cmd, algorithm, charset, *search_keys)
1484
+ return @responses.delete("THREAD")[-1]
1485
+ end
1486
+
1487
+ def normalize_searching_criteria(keys)
1488
+ keys.collect! do |i|
1489
+ case i
1490
+ when -1, Range, Array
1491
+ MessageSet.new(i)
1492
+ else
1493
+ i
1494
+ end
1495
+ end
1496
+ end
1497
+
1498
+ def create_ssl_params(certs = nil, verify = true)
1499
+ params = {}
1500
+ if certs
1501
+ if File.file?(certs)
1502
+ params[:ca_file] = certs
1503
+ elsif File.directory?(certs)
1504
+ params[:ca_path] = certs
1505
+ end
1506
+ end
1507
+ if verify
1508
+ params[:verify_mode] = VERIFY_PEER
1509
+ else
1510
+ params[:verify_mode] = VERIFY_NONE
1511
+ end
1512
+ return params
1513
+ end
1514
+
1515
+ def start_tls_session(params = {})
1516
+ unless defined?(OpenSSL::SSL)
1517
+ raise "SSL extension not installed"
1518
+ end
1519
+ if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
1520
+ raise RuntimeError, "already using SSL"
1521
+ end
1522
+ begin
1523
+ params = params.to_hash
1524
+ rescue NoMethodError
1525
+ params = {}
1526
+ end
1527
+ context = SSLContext.new
1528
+ context.set_params(params)
1529
+ if defined?(VerifyCallbackProc)
1530
+ context.verify_callback = VerifyCallbackProc
1531
+ end
1532
+ @sock = SSLSocket.new(@sock, context)
1533
+ @sock.sync_close = true
1534
+ @sock.hostname = @host if @sock.respond_to? :hostname=
1535
+ ssl_socket_connect(@sock, @open_timeout)
1536
+ if context.verify_mode != VERIFY_NONE
1537
+ @sock.post_connection_check(@host)
1538
+ end
1539
+ end
1540
+
1541
+ class RawData # :nodoc:
1542
+ def send_data(imap, tag)
1543
+ imap.send(:put_string, @data)
1544
+ end
1545
+
1546
+ def validate
1547
+ end
1548
+
1549
+ private
1550
+
1551
+ def initialize(data)
1552
+ @data = data
1553
+ end
1554
+ end
1555
+
1556
+ class Atom # :nodoc:
1557
+ def send_data(imap, tag)
1558
+ imap.send(:put_string, @data)
1559
+ end
1560
+
1561
+ def validate
1562
+ end
1563
+
1564
+ private
1565
+
1566
+ def initialize(data)
1567
+ @data = data
1568
+ end
1569
+ end
1570
+
1571
+ class QuotedString # :nodoc:
1572
+ def send_data(imap, tag)
1573
+ imap.send(:send_quoted_string, @data)
1574
+ end
1575
+
1576
+ def validate
1577
+ end
1578
+
1579
+ private
1580
+
1581
+ def initialize(data)
1582
+ @data = data
1583
+ end
1584
+ end
1585
+
1586
+ class Literal # :nodoc:
1587
+ def send_data(imap, tag)
1588
+ imap.send(:send_literal, @data, tag)
1589
+ end
1590
+
1591
+ def validate
1592
+ end
1593
+
1594
+ private
1595
+
1596
+ def initialize(data)
1597
+ @data = data
1598
+ end
1599
+ end
1600
+
1601
+ class MessageSet # :nodoc:
1602
+ def send_data(imap, tag)
1603
+ imap.send(:put_string, format_internal(@data))
1604
+ end
1605
+
1606
+ def validate
1607
+ validate_internal(@data)
1608
+ end
1609
+
1610
+ private
1611
+
1612
+ def initialize(data)
1613
+ @data = data
1614
+ end
1615
+
1616
+ def format_internal(data)
1617
+ case data
1618
+ when "*"
1619
+ return data
1620
+ when Integer
1621
+ if data == -1
1622
+ return "*"
1623
+ else
1624
+ return data.to_s
1625
+ end
1626
+ when Range
1627
+ return format_internal(data.first) +
1628
+ ":" + format_internal(data.last)
1629
+ when Array
1630
+ return data.collect {|i| format_internal(i)}.join(",")
1631
+ when ThreadMember
1632
+ return data.seqno.to_s +
1633
+ ":" + data.children.collect {|i| format_internal(i).join(",")}
1634
+ end
1635
+ end
1636
+
1637
+ def validate_internal(data)
1638
+ case data
1639
+ when "*"
1640
+ when Integer
1641
+ NumValidator.ensure_nz_number(data)
1642
+ when Range
1643
+ when Array
1644
+ data.each do |i|
1645
+ validate_internal(i)
1646
+ end
1647
+ when ThreadMember
1648
+ data.children.each do |i|
1649
+ validate_internal(i)
1650
+ end
1651
+ else
1652
+ raise DataFormatError, data.inspect
1653
+ end
1654
+ end
1655
+ end
1656
+
1657
+ # Common validators of number and nz_number types
1658
+ module NumValidator # :nodoc
1659
+ class << self
1660
+ # Check is passed argument valid 'number' in RFC 3501 terminology
1661
+ def valid_number?(num)
1662
+ # [RFC 3501]
1663
+ # number = 1*DIGIT
1664
+ # ; Unsigned 32-bit integer
1665
+ # ; (0 <= n < 4,294,967,296)
1666
+ num >= 0 && num < 4294967296
1667
+ end
1668
+
1669
+ # Check is passed argument valid 'nz_number' in RFC 3501 terminology
1670
+ def valid_nz_number?(num)
1671
+ # [RFC 3501]
1672
+ # nz-number = digit-nz *DIGIT
1673
+ # ; Non-zero unsigned 32-bit integer
1674
+ # ; (0 < n < 4,294,967,296)
1675
+ num != 0 && valid_number?(num)
1676
+ end
1677
+
1678
+ # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology
1679
+ def valid_mod_sequence_value?(num)
1680
+ # mod-sequence-value = 1*DIGIT
1681
+ # ; Positive unsigned 64-bit integer
1682
+ # ; (mod-sequence)
1683
+ # ; (1 <= n < 18,446,744,073,709,551,615)
1684
+ num >= 1 && num < 18446744073709551615
1685
+ end
1686
+
1687
+ # Ensure argument is 'number' or raise DataFormatError
1688
+ def ensure_number(num)
1689
+ return if valid_number?(num)
1690
+
1691
+ msg = "number must be unsigned 32-bit integer: #{num}"
1692
+ raise DataFormatError, msg
1693
+ end
1694
+
1695
+ # Ensure argument is 'nz_number' or raise DataFormatError
1696
+ def ensure_nz_number(num)
1697
+ return if valid_nz_number?(num)
1698
+
1699
+ msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
1700
+ raise DataFormatError, msg
1701
+ end
1702
+
1703
+ # Ensure argument is 'mod_sequence_value' or raise DataFormatError
1704
+ def ensure_mod_sequence_value(num)
1705
+ return if valid_mod_sequence_value?(num)
1706
+
1707
+ msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
1708
+ raise DataFormatError, msg
1709
+ end
1710
+ end
1711
+ end
1712
+
1713
+ # Net::IMAP::ContinuationRequest represents command continuation requests.
1714
+ #
1715
+ # The command continuation request response is indicated by a "+" token
1716
+ # instead of a tag. This form of response indicates that the server is
1717
+ # ready to accept the continuation of a command from the client. The
1718
+ # remainder of this response is a line of text.
1719
+ #
1720
+ # continue_req ::= "+" SPACE (resp_text / base64)
1721
+ #
1722
+ # ==== Fields:
1723
+ #
1724
+ # data:: Returns the data (Net::IMAP::ResponseText).
1725
+ #
1726
+ # raw_data:: Returns the raw data string.
1727
+ ContinuationRequest = Struct.new(:data, :raw_data)
1728
+
1729
+ # Net::IMAP::UntaggedResponse represents untagged responses.
1730
+ #
1731
+ # Data transmitted by the server to the client and status responses
1732
+ # that do not indicate command completion are prefixed with the token
1733
+ # "*", and are called untagged responses.
1734
+ #
1735
+ # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
1736
+ # mailbox_data / message_data / capability_data)
1737
+ #
1738
+ # ==== Fields:
1739
+ #
1740
+ # name:: Returns the name, such as "FLAGS", "LIST", or "FETCH".
1741
+ #
1742
+ # data:: Returns the data such as an array of flag symbols,
1743
+ # a ((<Net::IMAP::MailboxList>)) object.
1744
+ #
1745
+ # raw_data:: Returns the raw data string.
1746
+ UntaggedResponse = Struct.new(:name, :data, :raw_data)
1747
+
1748
+ # Net::IMAP::TaggedResponse represents tagged responses.
1749
+ #
1750
+ # The server completion result response indicates the success or
1751
+ # failure of the operation. It is tagged with the same tag as the
1752
+ # client command which began the operation.
1753
+ #
1754
+ # response_tagged ::= tag SPACE resp_cond_state CRLF
1755
+ #
1756
+ # tag ::= 1*<any ATOM_CHAR except "+">
1757
+ #
1758
+ # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1759
+ #
1760
+ # ==== Fields:
1761
+ #
1762
+ # tag:: Returns the tag.
1763
+ #
1764
+ # name:: Returns the name, one of "OK", "NO", or "BAD".
1765
+ #
1766
+ # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
1767
+ #
1768
+ # raw_data:: Returns the raw data string.
1769
+ #
1770
+ TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
1771
+
1772
+ # Net::IMAP::ResponseText represents texts of responses.
1773
+ # The text may be prefixed by the response code.
1774
+ #
1775
+ # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
1776
+ # ;; text SHOULD NOT begin with "[" or "="
1777
+ #
1778
+ # ==== Fields:
1779
+ #
1780
+ # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
1781
+ #
1782
+ # text:: Returns the text.
1783
+ #
1784
+ ResponseText = Struct.new(:code, :text)
1785
+
1786
+ # Net::IMAP::ResponseCode represents response codes.
1787
+ #
1788
+ # resp_text_code ::= "ALERT" / "PARSE" /
1789
+ # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
1790
+ # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1791
+ # "UIDVALIDITY" SPACE nz_number /
1792
+ # "UNSEEN" SPACE nz_number /
1793
+ # atom [SPACE 1*<any TEXT_CHAR except "]">]
1794
+ #
1795
+ # ==== Fields:
1796
+ #
1797
+ # name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY".
1798
+ #
1799
+ # data:: Returns the data, if it exists.
1800
+ #
1801
+ ResponseCode = Struct.new(:name, :data)
1802
+
1803
+ # Net::IMAP::MailboxList represents contents of the LIST response.
1804
+ #
1805
+ # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
1806
+ # "\Noselect" / "\Unmarked" / flag_extension) ")"
1807
+ # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
1808
+ #
1809
+ # ==== Fields:
1810
+ #
1811
+ # attr:: Returns the name attributes. Each name attribute is a symbol
1812
+ # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
1813
+ #
1814
+ # delim:: Returns the hierarchy delimiter.
1815
+ #
1816
+ # name:: Returns the mailbox name.
1817
+ #
1818
+ MailboxList = Struct.new(:attr, :delim, :name)
1819
+
1820
+ # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
1821
+ # This object can also be a response to GETQUOTAROOT. In the syntax
1822
+ # specification below, the delimiter used with the "#" construct is a
1823
+ # single space (SPACE).
1824
+ #
1825
+ # quota_list ::= "(" #quota_resource ")"
1826
+ #
1827
+ # quota_resource ::= atom SPACE number SPACE number
1828
+ #
1829
+ # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
1830
+ #
1831
+ # ==== Fields:
1832
+ #
1833
+ # mailbox:: The mailbox with the associated quota.
1834
+ #
1835
+ # usage:: Current storage usage of the mailbox.
1836
+ #
1837
+ # quota:: Quota limit imposed on the mailbox.
1838
+ #
1839
+ MailboxQuota = Struct.new(:mailbox, :usage, :quota)
1840
+
1841
+ # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
1842
+ # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
1843
+ #
1844
+ # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
1845
+ #
1846
+ # ==== Fields:
1847
+ #
1848
+ # mailbox:: The mailbox with the associated quota.
1849
+ #
1850
+ # quotaroots:: Zero or more quotaroots that affect the quota on the
1851
+ # specified mailbox.
1852
+ #
1853
+ MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
1854
+
1855
+ # Net::IMAP::MailboxACLItem represents the response from GETACL.
1856
+ #
1857
+ # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
1858
+ #
1859
+ # identifier ::= astring
1860
+ #
1861
+ # rights ::= astring
1862
+ #
1863
+ # ==== Fields:
1864
+ #
1865
+ # user:: Login name that has certain rights to the mailbox
1866
+ # that was specified with the getacl command.
1867
+ #
1868
+ # rights:: The access rights the indicated user has to the
1869
+ # mailbox.
1870
+ #
1871
+ MailboxACLItem = Struct.new(:user, :rights, :mailbox)
1872
+
1873
+ # Net::IMAP::StatusData represents the contents of the STATUS response.
1874
+ #
1875
+ # ==== Fields:
1876
+ #
1877
+ # mailbox:: Returns the mailbox name.
1878
+ #
1879
+ # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
1880
+ # "UIDVALIDITY", "UNSEEN". Each value is a number.
1881
+ #
1882
+ StatusData = Struct.new(:mailbox, :attr)
1883
+
1884
+ # Net::IMAP::FetchData represents the contents of the FETCH response.
1885
+ #
1886
+ # ==== Fields:
1887
+ #
1888
+ # seqno:: Returns the message sequence number.
1889
+ # (Note: not the unique identifier, even for the UID command response.)
1890
+ #
1891
+ # attr:: Returns a hash. Each key is a data item name, and each value is
1892
+ # its value.
1893
+ #
1894
+ # The current data items are:
1895
+ #
1896
+ # [BODY]
1897
+ # A form of BODYSTRUCTURE without extension data.
1898
+ # [BODY[<section>]<<origin_octet>>]
1899
+ # A string expressing the body contents of the specified section.
1900
+ # [BODYSTRUCTURE]
1901
+ # An object that describes the [MIME-IMB] body structure of a message.
1902
+ # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
1903
+ # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
1904
+ # [ENVELOPE]
1905
+ # A Net::IMAP::Envelope object that describes the envelope
1906
+ # structure of a message.
1907
+ # [FLAGS]
1908
+ # A array of flag symbols that are set for this message. Flag symbols
1909
+ # are capitalized by String#capitalize.
1910
+ # [INTERNALDATE]
1911
+ # A string representing the internal date of the message.
1912
+ # [RFC822]
1913
+ # Equivalent to BODY[].
1914
+ # [RFC822.HEADER]
1915
+ # Equivalent to BODY.PEEK[HEADER].
1916
+ # [RFC822.SIZE]
1917
+ # A number expressing the [RFC-822] size of the message.
1918
+ # [RFC822.TEXT]
1919
+ # Equivalent to BODY[TEXT].
1920
+ # [UID]
1921
+ # A number expressing the unique identifier of the message.
1922
+ #
1923
+ FetchData = Struct.new(:seqno, :attr)
1924
+
1925
+ # Net::IMAP::Envelope represents envelope structures of messages.
1926
+ #
1927
+ # ==== Fields:
1928
+ #
1929
+ # date:: Returns a string that represents the date.
1930
+ #
1931
+ # subject:: Returns a string that represents the subject.
1932
+ #
1933
+ # from:: Returns an array of Net::IMAP::Address that represents the from.
1934
+ #
1935
+ # sender:: Returns an array of Net::IMAP::Address that represents the sender.
1936
+ #
1937
+ # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
1938
+ #
1939
+ # to:: Returns an array of Net::IMAP::Address that represents the to.
1940
+ #
1941
+ # cc:: Returns an array of Net::IMAP::Address that represents the cc.
1942
+ #
1943
+ # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
1944
+ #
1945
+ # in_reply_to:: Returns a string that represents the in-reply-to.
1946
+ #
1947
+ # message_id:: Returns a string that represents the message-id.
1948
+ #
1949
+ Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
1950
+ :to, :cc, :bcc, :in_reply_to, :message_id)
1951
+
1952
+ #
1953
+ # Net::IMAP::Address represents electronic mail addresses.
1954
+ #
1955
+ # ==== Fields:
1956
+ #
1957
+ # name:: Returns the phrase from [RFC-822] mailbox.
1958
+ #
1959
+ # route:: Returns the route from [RFC-822] route-addr.
1960
+ #
1961
+ # mailbox:: nil indicates end of [RFC-822] group.
1962
+ # If non-nil and host is nil, returns [RFC-822] group name.
1963
+ # Otherwise, returns [RFC-822] local-part.
1964
+ #
1965
+ # host:: nil indicates [RFC-822] group syntax.
1966
+ # Otherwise, returns [RFC-822] domain name.
1967
+ #
1968
+ Address = Struct.new(:name, :route, :mailbox, :host)
1969
+
1970
+ #
1971
+ # Net::IMAP::ContentDisposition represents Content-Disposition fields.
1972
+ #
1973
+ # ==== Fields:
1974
+ #
1975
+ # dsp_type:: Returns the disposition type.
1976
+ #
1977
+ # param:: Returns a hash that represents parameters of the Content-Disposition
1978
+ # field.
1979
+ #
1980
+ ContentDisposition = Struct.new(:dsp_type, :param)
1981
+
1982
+ # Net::IMAP::ThreadMember represents a thread-node returned
1983
+ # by Net::IMAP#thread.
1984
+ #
1985
+ # ==== Fields:
1986
+ #
1987
+ # seqno:: The sequence number of this message.
1988
+ #
1989
+ # children:: An array of Net::IMAP::ThreadMember objects for mail
1990
+ # items that are children of this in the thread.
1991
+ #
1992
+ ThreadMember = Struct.new(:seqno, :children)
1993
+
1994
+ # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
1995
+ #
1996
+ # ==== Fields:
1997
+ #
1998
+ # media_type:: Returns the content media type name as defined in [MIME-IMB].
1999
+ #
2000
+ # subtype:: Returns the content subtype name as defined in [MIME-IMB].
2001
+ #
2002
+ # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
2003
+ #
2004
+ # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
2005
+ #
2006
+ # description:: Returns a string giving the content description as defined in
2007
+ # [MIME-IMB].
2008
+ #
2009
+ # encoding:: Returns a string giving the content transfer encoding as defined in
2010
+ # [MIME-IMB].
2011
+ #
2012
+ # size:: Returns a number giving the size of the body in octets.
2013
+ #
2014
+ # md5:: Returns a string giving the body MD5 value as defined in [MD5].
2015
+ #
2016
+ # disposition:: Returns a Net::IMAP::ContentDisposition object giving
2017
+ # the content disposition.
2018
+ #
2019
+ # language:: Returns a string or an array of strings giving the body
2020
+ # language value as defined in [LANGUAGE-TAGS].
2021
+ #
2022
+ # extension:: Returns extension data.
2023
+ #
2024
+ # multipart?:: Returns false.
2025
+ #
2026
+ class BodyTypeBasic < Struct.new(:media_type, :subtype,
2027
+ :param, :content_id,
2028
+ :description, :encoding, :size,
2029
+ :md5, :disposition, :language,
2030
+ :extension)
2031
+ def multipart?
2032
+ return false
2033
+ end
2034
+
2035
+ # Obsolete: use +subtype+ instead. Calling this will
2036
+ # generate a warning message to +stderr+, then return
2037
+ # the value of +subtype+.
2038
+ def media_subtype
2039
+ warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2040
+ return subtype
2041
+ end
2042
+ end
2043
+
2044
+ # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
2045
+ #
2046
+ # ==== Fields:
2047
+ #
2048
+ # lines:: Returns the size of the body in text lines.
2049
+ #
2050
+ # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
2051
+ #
2052
+ class BodyTypeText < Struct.new(:media_type, :subtype,
2053
+ :param, :content_id,
2054
+ :description, :encoding, :size,
2055
+ :lines,
2056
+ :md5, :disposition, :language,
2057
+ :extension)
2058
+ def multipart?
2059
+ return false
2060
+ end
2061
+
2062
+ # Obsolete: use +subtype+ instead. Calling this will
2063
+ # generate a warning message to +stderr+, then return
2064
+ # the value of +subtype+.
2065
+ def media_subtype
2066
+ warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2067
+ return subtype
2068
+ end
2069
+ end
2070
+
2071
+ # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
2072
+ #
2073
+ # ==== Fields:
2074
+ #
2075
+ # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
2076
+ #
2077
+ # body:: Returns an object giving the body structure.
2078
+ #
2079
+ # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
2080
+ #
2081
+ class BodyTypeMessage < Struct.new(:media_type, :subtype,
2082
+ :param, :content_id,
2083
+ :description, :encoding, :size,
2084
+ :envelope, :body, :lines,
2085
+ :md5, :disposition, :language,
2086
+ :extension)
2087
+ def multipart?
2088
+ return false
2089
+ end
2090
+
2091
+ # Obsolete: use +subtype+ instead. Calling this will
2092
+ # generate a warning message to +stderr+, then return
2093
+ # the value of +subtype+.
2094
+ def media_subtype
2095
+ warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2096
+ return subtype
2097
+ end
2098
+ end
2099
+
2100
+ # Net::IMAP::BodyTypeAttachment represents attachment body structures
2101
+ # of messages.
2102
+ #
2103
+ # ==== Fields:
2104
+ #
2105
+ # media_type:: Returns the content media type name.
2106
+ #
2107
+ # subtype:: Returns +nil+.
2108
+ #
2109
+ # param:: Returns a hash that represents parameters.
2110
+ #
2111
+ # multipart?:: Returns false.
2112
+ #
2113
+ class BodyTypeAttachment < Struct.new(:media_type, :subtype,
2114
+ :param)
2115
+ def multipart?
2116
+ return false
2117
+ end
2118
+ end
2119
+
2120
+ # Net::IMAP::BodyTypeMultipart represents multipart body structures
2121
+ # of messages.
2122
+ #
2123
+ # ==== Fields:
2124
+ #
2125
+ # media_type:: Returns the content media type name as defined in [MIME-IMB].
2126
+ #
2127
+ # subtype:: Returns the content subtype name as defined in [MIME-IMB].
2128
+ #
2129
+ # parts:: Returns multiple parts.
2130
+ #
2131
+ # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
2132
+ #
2133
+ # disposition:: Returns a Net::IMAP::ContentDisposition object giving
2134
+ # the content disposition.
2135
+ #
2136
+ # language:: Returns a string or an array of strings giving the body
2137
+ # language value as defined in [LANGUAGE-TAGS].
2138
+ #
2139
+ # extension:: Returns extension data.
2140
+ #
2141
+ # multipart?:: Returns true.
2142
+ #
2143
+ class BodyTypeMultipart < Struct.new(:media_type, :subtype,
2144
+ :parts,
2145
+ :param, :disposition, :language,
2146
+ :extension)
2147
+ def multipart?
2148
+ return true
2149
+ end
2150
+
2151
+ # Obsolete: use +subtype+ instead. Calling this will
2152
+ # generate a warning message to +stderr+, then return
2153
+ # the value of +subtype+.
2154
+ def media_subtype
2155
+ warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
2156
+ return subtype
2157
+ end
2158
+ end
2159
+
2160
+ class BodyTypeExtension < Struct.new(:media_type, :subtype,
2161
+ :params, :content_id,
2162
+ :description, :encoding, :size)
2163
+ def multipart?
2164
+ return false
2165
+ end
2166
+ end
2167
+
2168
+ class ResponseParser # :nodoc:
2169
+ def initialize
2170
+ @str = nil
2171
+ @pos = nil
2172
+ @lex_state = nil
2173
+ @token = nil
2174
+ @flag_symbols = {}
2175
+ end
2176
+
2177
+ def parse(str)
2178
+ @str = str
2179
+ @pos = 0
2180
+ @lex_state = EXPR_BEG
2181
+ @token = nil
2182
+ return response
2183
+ end
2184
+
2185
+ private
2186
+
2187
+ EXPR_BEG = :EXPR_BEG
2188
+ EXPR_DATA = :EXPR_DATA
2189
+ EXPR_TEXT = :EXPR_TEXT
2190
+ EXPR_RTEXT = :EXPR_RTEXT
2191
+ EXPR_CTEXT = :EXPR_CTEXT
2192
+
2193
+ T_SPACE = :SPACE
2194
+ T_NIL = :NIL
2195
+ T_NUMBER = :NUMBER
2196
+ T_ATOM = :ATOM
2197
+ T_QUOTED = :QUOTED
2198
+ T_LPAR = :LPAR
2199
+ T_RPAR = :RPAR
2200
+ T_BSLASH = :BSLASH
2201
+ T_STAR = :STAR
2202
+ T_LBRA = :LBRA
2203
+ T_RBRA = :RBRA
2204
+ T_LITERAL = :LITERAL
2205
+ T_PLUS = :PLUS
2206
+ T_PERCENT = :PERCENT
2207
+ T_CRLF = :CRLF
2208
+ T_EOF = :EOF
2209
+ T_TEXT = :TEXT
2210
+
2211
+ BEG_REGEXP = /\G(?:\
2212
+ (?# 1: SPACE )( +)|\
2213
+ (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
2214
+ (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
2215
+ (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
2216
+ (?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
2217
+ (?# 6: LPAR )(\()|\
2218
+ (?# 7: RPAR )(\))|\
2219
+ (?# 8: BSLASH )(\\)|\
2220
+ (?# 9: STAR )(\*)|\
2221
+ (?# 10: LBRA )(\[)|\
2222
+ (?# 11: RBRA )(\])|\
2223
+ (?# 12: LITERAL )\{(\d+)\}\r\n|\
2224
+ (?# 13: PLUS )(\+)|\
2225
+ (?# 14: PERCENT )(%)|\
2226
+ (?# 15: CRLF )(\r\n)|\
2227
+ (?# 16: EOF )(\z))/ni
2228
+
2229
+ DATA_REGEXP = /\G(?:\
2230
+ (?# 1: SPACE )( )|\
2231
+ (?# 2: NIL )(NIL)|\
2232
+ (?# 3: NUMBER )(\d+)|\
2233
+ (?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
2234
+ (?# 5: LITERAL )\{(\d+)\}\r\n|\
2235
+ (?# 6: LPAR )(\()|\
2236
+ (?# 7: RPAR )(\)))/ni
2237
+
2238
+ TEXT_REGEXP = /\G(?:\
2239
+ (?# 1: TEXT )([^\x00\r\n]*))/ni
2240
+
2241
+ RTEXT_REGEXP = /\G(?:\
2242
+ (?# 1: LBRA )(\[)|\
2243
+ (?# 2: TEXT )([^\x00\r\n]*))/ni
2244
+
2245
+ CTEXT_REGEXP = /\G(?:\
2246
+ (?# 1: TEXT )([^\x00\r\n\]]*))/ni
2247
+
2248
+ Token = Struct.new(:symbol, :value)
2249
+
2250
+ def response
2251
+ token = lookahead
2252
+ case token.symbol
2253
+ when T_PLUS
2254
+ result = continue_req
2255
+ when T_STAR
2256
+ result = response_untagged
2257
+ else
2258
+ result = response_tagged
2259
+ end
2260
+ while lookahead.symbol == T_SPACE
2261
+ # Ignore trailing space for Microsoft Exchange Server
2262