summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2021-05-26 15:48:36 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2021-05-27 14:42:11 +0900
commitd5bc6b23370908f19d1a4a19ead56e61444f0974 (patch)
tree765f16079da2b841691c85244733520a8c699561
parente49c998d1e41737016c8afb7e1b22797018caebb (diff)
Promote net-imap to the bundled gems
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/4530
-rw-r--r--doc/maintainers.rdoc6
-rw-r--r--doc/standard_library.rdoc2
-rw-r--r--gems/bundled_gems1
-rw-r--r--lib/net/imap.rb1573
-rw-r--r--lib/net/imap/authenticators.rb44
-rw-r--r--lib/net/imap/authenticators/cram_md5.rb49
-rw-r--r--lib/net/imap/authenticators/digest_md5.rb111
-rw-r--r--lib/net/imap/authenticators/login.rb43
-rw-r--r--lib/net/imap/authenticators/plain.rb41
-rw-r--r--lib/net/imap/command_data.rb301
-rw-r--r--lib/net/imap/data_encoding.rb47
-rw-r--r--lib/net/imap/flags.rb76
-rw-r--r--lib/net/imap/net-imap.gemspec37
-rw-r--r--lib/net/imap/response_data.rb527
-rw-r--r--lib/net/imap/response_parser.rb1530
-rwxr-xr-xmisc/expand_tabs.rb1
-rw-r--r--test/net/imap/test_imap.rb856
-rw-r--r--test/net/imap/test_imap_authenticators.rb23
-rw-r--r--test/net/imap/test_imap_data_encoding.rb46
-rw-r--r--test/net/imap/test_imap_response_parser.rb389
-rw-r--r--tool/sync_default_gems.rb7
21 files changed, 4 insertions, 5706 deletions
diff --git a/doc/maintainers.rdoc b/doc/maintainers.rdoc
index 965e9e8870..429a690a55 100644
--- a/doc/maintainers.rdoc
+++ b/doc/maintainers.rdoc
@@ -158,10 +158,6 @@ Yukihiro Matsumoto (matz)
NARUSE, Yui (naruse)
https://github.com/ruby/net-http
https://rubygems.org/gems/net-http
-[lib/net/imap.rb]
- Shugo Maeda (shugo)
- https://github.com/ruby/net-imap
- https://rubygems.org/gems/net-imap
[lib/net/pop.rb]
_unmaintained_
https://github.com/ruby/net-pop
@@ -389,6 +385,8 @@ Yukihiro Matsumoto (matz)
https://github.com/ruby/rss
[net-ftp]
https://github.com/ruby/net-ftp
+[net-imap]
+ https://github.com/ruby/net-imap
[matrix]
https://github.com/ruby/matrix
[prime]
diff --git a/doc/standard_library.rdoc b/doc/standard_library.rdoc
index 2c23c10deb..9bdcf79f78 100644
--- a/doc/standard_library.rdoc
+++ b/doc/standard_library.rdoc
@@ -47,7 +47,6 @@ OptionParser:: Ruby-oriented class for command-line option analysis
Logger:: Provides a simple logging utility for outputting messages
Mutex_m:: Mixin to extend objects to be handled like a Mutex
Net::HTTP:: HTTP client api for Ruby
-Net::IMAP:: Ruby client api for Internet Message Access Protocol
Net::POP3:: Ruby client library for POP3
Net::SMTP:: Simple Mail Transfer Protocol client library for Ruby
Observable:: Provides a mechanism for publish/subscribe pattern in Ruby
@@ -109,6 +108,7 @@ Test::Unit:: A compatibility layer for MiniTest
REXML:: An XML toolkit for Ruby
RSS:: Family of libraries that support various formats of XML "feeds"
Net::FTP:: Support for the File Transfer Protocol
+Net::IMAP:: Ruby client api for Internet Message Access Protocol
Matrix:: Represents a mathematical matrix.
Prime:: Prime numbers and factorization library
RBS:: RBS is a language to describe the structure of Ruby programs
diff --git a/gems/bundled_gems b/gems/bundled_gems
index e944d21f59..3da66a49ae 100644
--- a/gems/bundled_gems
+++ b/gems/bundled_gems
@@ -6,6 +6,7 @@ test-unit 3.4.1 https://github.com/test-unit/test-unit 3.4.1
rexml 3.2.5 https://github.com/ruby/rexml
rss 0.2.9 https://github.com/ruby/rss 0.2.9
net-ftp 0.1.2 https://github.com/ruby/net-ftp
+net-imap 0.2.1 https://github.com/ruby/net-imap
matrix 0.4.1 https://github.com/ruby/matrix
prime 0.1.2 https://github.com/ruby/prime
typeprof 0.14.1 https://github.com/ruby/typeprof
diff --git a/lib/net/imap.rb b/lib/net/imap.rb
deleted file mode 100644
index fe5fc14266..0000000000
--- a/lib/net/imap.rb
+++ /dev/null
@@ -1,1573 +0,0 @@
-# frozen_string_literal: true
-#
-# = net/imap.rb
-#
-# Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org>
-#
-# This library is distributed under the terms of the Ruby license.
-# You can freely distribute/modify this library.
-#
-# Documentation: Shugo Maeda, with RDoc conversion and overview by William
-# Webber.
-#
-# See Net::IMAP for documentation.
-#
-
-
-require "socket"
-require "monitor"
-require 'net/protocol'
-begin
- require "openssl"
-rescue LoadError
-end
-
-require_relative "imap/command_data"
-require_relative "imap/data_encoding"
-require_relative "imap/flags"
-require_relative "imap/response_data"
-require_relative "imap/response_parser"
-
-module Net
-
- #
- # Net::IMAP implements Internet Message Access Protocol (IMAP) client
- # functionality. The protocol is described in
- # [IMAP[https://tools.ietf.org/html/rfc3501]].
- #
- # == IMAP Overview
- #
- # An \IMAP client connects to a server, and then authenticates
- # itself using either #authenticate or #login. Having
- # authenticated itself, there is a range of commands
- # available to it. Most work with mailboxes, which may be
- # arranged in an hierarchical namespace, and each of which
- # contains zero or more messages. How this is implemented on
- # the server is implementation-dependent; on a UNIX server, it
- # will frequently be implemented as files in mailbox format
- # within a hierarchy of directories.
- #
- # To work on the messages within a mailbox, the client must
- # first select that mailbox, using either #select or (for
- # read-only access) #examine. Once the client has successfully
- # selected a mailbox, they enter _selected_ state, and that
- # mailbox becomes the _current_ mailbox, on which mail-item
- # related commands implicitly operate.
- #
- # Messages have two sorts of identifiers: message sequence
- # numbers and UIDs.
- #
- # Message sequence numbers number messages within a mailbox
- # from 1 up to the number of items in the mailbox. If a new
- # message arrives during a session, it receives a sequence
- # number equal to the new size of the mailbox. If messages
- # are expunged from the mailbox, remaining messages have their
- # sequence numbers "shuffled down" to fill the gaps.
- #
- # UIDs, on the other hand, are permanently guaranteed not to
- # identify another message within the same mailbox, even if
- # the existing message is deleted. UIDs are required to
- # be assigned in ascending (but not necessarily sequential)
- # order within a mailbox; this means that if a non-IMAP client
- # rearranges the order of mailitems within a mailbox, the
- # UIDs have to be reassigned. An \IMAP client thus cannot
- # rearrange message orders.
- #
- # == Examples of Usage
- #
- # === List sender and subject of all recent messages in the default mailbox
- #
- # imap = Net::IMAP.new('mail.example.com')
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
- # imap.examine('INBOX')
- # imap.search(["RECENT"]).each do |message_id|
- # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
- # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
- # end
- #
- # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
- #
- # imap = Net::IMAP.new('mail.example.com')
- # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
- # imap.select('Mail/sent-mail')
- # if not imap.list('Mail/', 'sent-apr03')
- # imap.create('Mail/sent-apr03')
- # end
- # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
- # imap.copy(message_id, "Mail/sent-apr03")
- # imap.store(message_id, "+FLAGS", [:Deleted])
- # end
- # imap.expunge
- #
- # == Thread Safety
- #
- # Net::IMAP supports concurrent threads. For example,
- #
- # imap = Net::IMAP.new("imap.foo.net", "imap2")
- # imap.authenticate("cram-md5", "bar", "password")
- # imap.select("inbox")
- # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
- # search_result = imap.search(["BODY", "hello"])
- # fetch_result = fetch_thread.value
- # imap.disconnect
- #
- # This script invokes the FETCH command and the SEARCH command concurrently.
- #
- # == Errors
- #
- # An IMAP server can send three different types of responses to indicate
- # failure:
- #
- # NO:: the attempted command could not be successfully completed. For
- # instance, the username/password used for logging in are incorrect;
- # the selected mailbox does not exist; etc.
- #
- # BAD:: the request from the client does not follow the server's
- # understanding of the IMAP protocol. This includes attempting
- # commands from the wrong client state; for instance, attempting
- # to perform a SEARCH command without having SELECTed a current
- # mailbox. It can also signal an internal server
- # failure (such as a disk crash) has occurred.
- #
- # BYE:: the server is saying goodbye. This can be part of a normal
- # logout sequence, and can be used as part of a login sequence
- # to indicate that the server is (for some reason) unwilling
- # to accept your connection. As a response to any other command,
- # it indicates either that the server is shutting down, or that
- # the server is timing out the client connection due to inactivity.
- #
- # These three error response are represented by the errors
- # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and
- # Net::IMAP::ByeResponseError, all of which are subclasses of
- # Net::IMAP::ResponseError. Essentially, all methods that involve
- # sending a request to the server can generate one of these errors.
- # Only the most pertinent instances have been documented below.
- #
- # Because the IMAP class uses Sockets for communication, its methods
- # are also susceptible to the various errors that can occur when
- # working with sockets. These are generally represented as
- # Errno errors. For instance, any method that involves sending a
- # request to the server and/or receiving a response from it could
- # raise an Errno::EPIPE error if the network connection unexpectedly
- # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2),
- # and associated man pages.
- #
- # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
- # is found to be in an incorrect format (for instance, when converting
- # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
- # thrown if a server response is non-parseable.
- #
- #
- # == References
- #
- # [[IMAP[https://tools.ietf.org/html/rfc3501]]]
- # Crispin, M. "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1",
- # RFC-3501[https://tools.ietf.org/html/rfc3501], March 2003. (Note:
- # obsoletes RFC-2060[https://tools.ietf.org/html/rfc2060], December 1996.)
- #
- # [[LANGUAGE-TAGS[https://tools.ietf.org/html/rfc1766]]]
- # Phillips, A. and Davis, M. "Tags for Identifying Languages",
- # RFC-5646[https://tools.ietf.org/html/rfc5646], September 2009.
- # (Note: obsoletes
- # RFC-3066[https://tools.ietf.org/html/rfc3066], January 2001,
- # RFC-4646[https://tools.ietf.org/html/rfc4646], September 2006, and
- # RFC-1766[https://tools.ietf.org/html/rfc1766], March 1995.)
- #
- # [[MD5[https://tools.ietf.org/html/rfc1864]]]
- # Myers, J. and M. Rose, "The Content-MD5 Header Field",
- # RFC-1864[https://tools.ietf.org/html/rfc1864], October 1995.
- #
- # [[MIME-IMB[https://tools.ietf.org/html/rfc2045]]]
- # Freed, N. and N. Borenstein, "MIME (Multipurpose Internet
- # Mail Extensions) Part One: Format of Internet Message Bodies",
- # RFC-2045[https://tools.ietf.org/html/rfc2045], November 1996.
- #
- # [[RFC-5322[https://tools.ietf.org/html/rfc5322]]]
- # Resnick, P., "Internet Message Format",
- # RFC-5322[https://tools.ietf.org/html/rfc5322], October 2008.
- # (Note: obsoletes
- # RFC-2822[https://tools.ietf.org/html/rfc2822], April 2001, and
- # RFC-822[https://tools.ietf.org/html/rfc822], August 1982.)
- #
- # [[EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]]
- # Myers, J., "IMAP4 QUOTA extension",
- # RFC-2087[https://tools.ietf.org/html/rfc2087], January 1997.
- #
- # [[EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]]
- # Gahrns, M. and Newman, C., "IMAP4 Namespace",
- # RFC-2342[https://tools.ietf.org/html/rfc2342], May 1998.
- #
- # [[EXT-ID[https://tools.ietf.org/html/rfc2971]]]
- # Showalter, T., "IMAP4 ID extension",
- # RFC-2971[https://tools.ietf.org/html/rfc2971], October 2000.
- #
- # [[EXT-ACL[https://tools.ietf.org/html/rfc4314]]]
- # Melnikov, A., "IMAP4 ACL extension",
- # RFC-4314[https://tools.ietf.org/html/rfc4314], December 2005. (Note:
- # obsoletes RFC-2086[https://tools.ietf.org/html/rfc2086], January 1997.)
- #
- # [[EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]]]
- # Crispin, M. and Muchison, K., "INTERNET MESSAGE ACCESS PROTOCOL - SORT
- # and THREAD Extensions", RFC-5256[https://tools.ietf.org/html/rfc5256],
- # June 2008.
- #
- # [[EXT-MOVE[https://tools.ietf.org/html/rfc6851]]]
- # Gulbrandsen, A. and Freed, N., "Internet Message Access Protocol (\IMAP) -
- # MOVE Extension", RFC-6851[https://tools.ietf.org/html/rfc6851], January
- # 2013.
- #
- # [[OSSL]]
- # http://www.openssl.org
- #
- # [[RSSL]]
- # http://savannah.gnu.org/projects/rubypki
- #
- # [[UTF7[https://tools.ietf.org/html/rfc2152]]]
- # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
- # Unicode", RFC-2152[https://tools.ietf.org/html/rfc2152], May 1997.
- #
- class IMAP < Protocol
- VERSION = "0.2.1"
-
- include MonitorMixin
- if defined?(OpenSSL::SSL)
- include OpenSSL
- include SSL
- end
-
- # Returns an initial greeting response from the server.
- attr_reader :greeting
-
- # Returns recorded untagged responses. For example:
- #
- # imap.select("inbox")
- # p imap.responses["EXISTS"][-1]
- # #=> 2
- # p imap.responses["UIDVALIDITY"][-1]
- # #=> 968263756
- attr_reader :responses
-
- # Returns all response handlers.
- attr_reader :response_handlers
-
- # Seconds to wait until a connection is opened.
- # If the IMAP object cannot open a connection within this time,
- # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
- attr_reader :open_timeout
-
- # Seconds to wait until an IDLE response is received.
- attr_reader :idle_response_timeout
-
- # The thread to receive exceptions.
- attr_accessor :client_thread
-
- # Returns the debug mode.
- def self.debug
- return @@debug
- end
-
- # Sets the debug mode.
- def self.debug=(val)
- return @@debug = val
- end
-
- # The default port for IMAP connections, port 143
- def self.default_port
- return PORT
- end
-
- # The default port for IMAPS connections, port 993
- def self.default_tls_port
- return SSL_PORT
- end
-
- class << self
- alias default_imap_port default_port
- alias default_imaps_port default_tls_port
- alias default_ssl_port default_tls_port
- end
-
- # Disconnects from the server.
- def disconnect
- return if disconnected?
- begin
- begin
- # try to call SSL::SSLSocket#io.
- @sock.io.shutdown
- rescue NoMethodError
- # @sock is not an SSL::SSLSocket.
- @sock.shutdown
- end
- rescue Errno::ENOTCONN
- # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
- rescue Exception => e
- @receiver_thread.raise(e)
- end
- @receiver_thread.join
- synchronize do
- @sock.close
- end
- raise e if e
- end
-
- # Returns true if disconnected from the server.
- def disconnected?
- return @sock.closed?
- end
-
- # Sends a CAPABILITY command, and returns an array of
- # capabilities that the server supports. Each capability
- # is a string. See [IMAP] for a list of possible
- # capabilities.
- #
- # Note that the Net::IMAP class does not modify its
- # behaviour according to the capabilities of the server;
- # it is up to the user of the class to ensure that
- # a certain capability is supported by a server before
- # using it.
- def capability
- synchronize do
- send_command("CAPABILITY")
- return @responses.delete("CAPABILITY")[-1]
- end
- end
-
- # Sends an ID command, and returns a hash of the server's
- # response, or nil if the server does not identify itself.
- #
- # Note that the user should first check if the server supports the ID
- # capability. For example:
- #
- # capabilities = imap.capability
- # if capabilities.include?("ID")
- # id = imap.id(
- # name: "my IMAP client (ruby)",
- # version: MyIMAP::VERSION,
- # "support-url": "mailto:bugs@example.com",
- # os: RbConfig::CONFIG["host_os"],
- # )
- # end
- #
- # See [EXT-ID[https://tools.ietf.org/html/rfc2971]] for field definitions.
- def id(client_id=nil)
- synchronize do
- send_command("ID", ClientID.new(client_id))
- @responses.delete("ID")&.last
- end
- end
-
- # Sends a NOOP command to the server. It does nothing.
- def noop
- send_command("NOOP")
- end
-
- # Sends a LOGOUT command to inform the server that the client is
- # done with the connection.
- def logout
- send_command("LOGOUT")
- end
-
- # Sends a STARTTLS command to start TLS session.
- def starttls(options = {}, verify = true)
- send_command("STARTTLS") do |resp|
- if resp.kind_of?(TaggedResponse) && resp.name == "OK"
- begin
- # for backward compatibility
- certs = options.to_str
- options = create_ssl_params(certs, verify)
- rescue NoMethodError
- end
- start_tls_session(options)
- end
- end
- end
-
- # Sends an AUTHENTICATE command to authenticate the client.
- # The +auth_type+ parameter is a string that represents
- # the authentication mechanism to be used. Currently Net::IMAP
- # supports the authentication mechanisms:
- #
- # LOGIN:: login using cleartext user and password.
- # CRAM-MD5:: login with cleartext user and encrypted password
- # (see [RFC-2195] for a full description). This
- # mechanism requires that the server have the user's
- # password stored in clear-text password.
- #
- # For both of these mechanisms, there should be two +args+: username
- # and (cleartext) password. A server may not support one or the other
- # of these mechanisms; check #capability for a capability of
- # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
- #
- # Authentication is done using the appropriate authenticator object:
- # see +add_authenticator+ for more information on plugging in your own
- # authenticator.
- #
- # For example:
- #
- # imap.authenticate('LOGIN', user, password)
- #
- # A Net::IMAP::NoResponseError is raised if authentication fails.
- def authenticate(auth_type, *args)
- authenticator = self.class.authenticator(auth_type, *args)
- send_command("AUTHENTICATE", auth_type) do |resp|
- if resp.instance_of?(ContinuationRequest)
- data = authenticator.process(resp.data.text.unpack("m")[0])
- s = [data].pack("m0")
- send_string_data(s)
- put_string(CRLF)
- end
- end
- end
-
- # Sends a LOGIN command to identify the client and carries
- # the plaintext +password+ authenticating this +user+. Note
- # that, unlike calling #authenticate with an +auth_type+
- # of "LOGIN", #login does *not* use the login authenticator.
- #
- # A Net::IMAP::NoResponseError is raised if authentication fails.
- def login(user, password)
- send_command("LOGIN", user, password)
- end
-
- # Sends a SELECT command to select a +mailbox+ so that messages
- # in the +mailbox+ can be accessed.
- #
- # After you have selected a mailbox, you may retrieve the
- # number of items in that mailbox from +@responses["EXISTS"][-1]+,
- # and the number of recent messages from +@responses["RECENT"][-1]+.
- # Note that these values can change if new messages arrive
- # during a session; see #add_response_handler for a way of
- # detecting this event.
- #
- # A Net::IMAP::NoResponseError is raised if the mailbox does not
- # exist or is for some reason non-selectable.
- def select(mailbox)
- synchronize do
- @responses.clear
- send_command("SELECT", mailbox)
- end
- end
-
- # Sends a EXAMINE command to select a +mailbox+ so that messages
- # in the +mailbox+ can be accessed. Behaves the same as #select,
- # except that the selected +mailbox+ is identified as read-only.
- #
- # A Net::IMAP::NoResponseError is raised if the mailbox does not
- # exist or is for some reason non-examinable.
- def examine(mailbox)
- synchronize do
- @responses.clear
- send_command("EXAMINE", mailbox)
- end
- end
-
- # Sends a CREATE command to create a new +mailbox+.
- #
- # A Net::IMAP::NoResponseError is raised if a mailbox with that name
- # cannot be created.
- def create(mailbox)
- send_command("CREATE", mailbox)
- end
-
- # Sends a DELETE command to remove the +mailbox+.
- #
- # A Net::IMAP::NoResponseError is raised if a mailbox with that name
- # cannot be deleted, either because it does not exist or because the
- # client does not have permission to delete it.
- def delete(mailbox)
- send_command("DELETE", mailbox)
- end
-
- # Sends a RENAME command to change the name of the +mailbox+ to
- # +newname+.
- #
- # A Net::IMAP::NoResponseError is raised if a mailbox with the
- # name +mailbox+ cannot be renamed to +newname+ for whatever
- # reason; for instance, because +mailbox+ does not exist, or
- # because there is already a mailbox with the name +newname+.
- def rename(mailbox, newname)
- send_command("RENAME", mailbox, newname)
- end
-
- # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
- # the server's set of "active" or "subscribed" mailboxes as returned
- # by #lsub.
- #
- # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
- # subscribed to; for instance, because it does not exist.
- def subscribe(mailbox)
- send_command("SUBSCRIBE", mailbox)
- end
-
- # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name
- # from the server's set of "active" or "subscribed" mailboxes.
- #
- # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
- # unsubscribed from; for instance, because the client is not currently
- # subscribed to it.
- def unsubscribe(mailbox)
- send_command("UNSUBSCRIBE", mailbox)
- end
-
- # Sends a LIST command, and returns a subset of names from
- # the complete set of all names available to the client.
- # +refname+ provides a context (for instance, a base directory
- # in a directory-based mailbox hierarchy). +mailbox+ specifies
- # a mailbox or (via wildcards) mailboxes under that context.
- # Two wildcards may be used in +mailbox+: '*', which matches
- # all characters *including* the hierarchy delimiter (for instance,
- # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
- # which matches all characters *except* the hierarchy delimiter.
- #
- # If +refname+ is empty, +mailbox+ is used directly to determine
- # which mailboxes to match. If +mailbox+ is empty, the root
- # name of +refname+ and the hierarchy delimiter are returned.
- #
- # The return value is an array of +Net::IMAP::MailboxList+. For example:
- #
- # imap.create("foo/bar")
- # imap.create("foo/baz")
- # p imap.list("", "foo/%")
- # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
- # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
- # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
- def list(refname, mailbox)
- synchronize do
- send_command("LIST", refname, mailbox)
- return @responses.delete("LIST")
- end
- end
-
- # Sends a NAMESPACE command and returns the namespaces that are available.
- # The NAMESPACE command allows a client to discover the prefixes of
- # namespaces used by a server for personal mailboxes, other users'
- # mailboxes, and shared mailboxes.
- #
- # The NAMESPACE extension predates [IMAP4rev1[https://tools.ietf.org/html/rfc2501]],
- # so most IMAP servers support it. Many popular IMAP servers are configured
- # with the default personal namespaces as `("" "/")`: no prefix and "/"
- # hierarchy delimiter. In that common case, the naive client may not have
- # any trouble naming mailboxes.
- #
- # But many servers are configured with the default personal namespace as
- # e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "."
- # as the hierarchy delimiter. If the client does not check for this, but
- # naively assumes it can use the same folder names for all servers, then
- # folder creation (and listing, moving, etc) can lead to errors.
- #
- # From RFC2342:
- #
- # Although typically a server will support only a single Personal
- # Namespace, and a single Other User's Namespace, circumstances exist
- # where there MAY be multiples of these, and a client MUST be prepared
- # for them. If a client is configured such that it is required to create
- # a certain mailbox, there can be circumstances where it is unclear which
- # Personal Namespaces it should create the mailbox in. In these
- # situations a client SHOULD let the user select which namespaces to
- # create the mailbox in.
- #
- # The user of this method should first check if the server supports the
- # NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+
- # object which has +personal+, +other+, and +shared+ fields, each an array
- # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the
- # server responds with nil.
- #
- # For example:
- #
- # capabilities = imap.capability
- # if capabilities.include?("NAMESPACE")
- # namespaces = imap.namespace
- # if namespace = namespaces.personal.first
- # prefix = namespace.prefix # e.g. "" or "INBOX."
- # delim = namespace.delim # e.g. "/" or "."
- # # personal folders should use the prefix and delimiter
- # imap.create(prefix + "foo")
- # imap.create(prefix + "bar")
- # imap.create(prefix + %w[path to my folder].join(delim))
- # end
- # end
- #
- # The NAMESPACE extension is described in [EXT-NAMESPACE[https://tools.ietf.org/html/rfc2342]]
- def namespace
- synchronize do
- send_command("NAMESPACE")
- return @responses.delete("NAMESPACE")[-1]
- end
- end
-
- # Sends a XLIST command, and returns a subset of names from
- # the complete set of all names available to the client.
- # +refname+ provides a context (for instance, a base directory
- # in a directory-based mailbox hierarchy). +mailbox+ specifies
- # a mailbox or (via wildcards) mailboxes under that context.
- # Two wildcards may be used in +mailbox+: '*', which matches
- # all characters *including* the hierarchy delimiter (for instance,
- # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
- # which matches all characters *except* the hierarchy delimiter.
- #
- # If +refname+ is empty, +mailbox+ is used directly to determine
- # which mailboxes to match. If +mailbox+ is empty, the root
- # name of +refname+ and the hierarchy delimiter are returned.
- #
- # The XLIST command is like the LIST command except that the flags
- # returned refer to the function of the folder/mailbox, e.g. :Sent
- #
- # The return value is an array of +Net::IMAP::MailboxList+. For example:
- #
- # imap.create("foo/bar")
- # imap.create("foo/baz")
- # p imap.xlist("", "foo/%")
- # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
- # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
- # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
- def xlist(refname, mailbox)
- synchronize do
- send_command("XLIST", refname, mailbox)
- return @responses.delete("XLIST")
- end
- end
-
- # Sends the GETQUOTAROOT command along with the specified +mailbox+.
- # This command is generally available to both admin and user.
- # If this mailbox exists, it returns an array containing objects of type
- # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
- #
- # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
- def getquotaroot(mailbox)
- synchronize do
- send_command("GETQUOTAROOT", mailbox)
- result = []
- result.concat(@responses.delete("QUOTAROOT"))
- result.concat(@responses.delete("QUOTA"))
- return result
- end
- end
-
- # Sends the GETQUOTA command along with specified +mailbox+.
- # If this mailbox exists, then an array containing a
- # Net::IMAP::MailboxQuota object is returned. This
- # command is generally only available to server admin.
- #
- # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
- def getquota(mailbox)
- synchronize do
- send_command("GETQUOTA", mailbox)
- return @responses.delete("QUOTA")
- end
- end
-
- # Sends a SETQUOTA command along with the specified +mailbox+ and
- # +quota+. If +quota+ is nil, then +quota+ will be unset for that
- # mailbox. Typically one needs to be logged in as a server admin
- # for this to work.
- #
- # The QUOTA extension is described in [EXT-QUOTA[https://tools.ietf.org/html/rfc2087]]
- def setquota(mailbox, quota)
- if quota.nil?
- data = '()'
- else
- data = '(STORAGE ' + quota.to_s + ')'
- end
- send_command("SETQUOTA", mailbox, RawData.new(data))
- end
-
- # Sends the SETACL command along with +mailbox+, +user+ and the
- # +rights+ that user is to have on that mailbox. If +rights+ is nil,
- # then that user will be stripped of any rights to that mailbox.
- #
- # The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
- def setacl(mailbox, user, rights)
- if rights.nil?
- send_command("SETACL", mailbox, user, "")
- else
- send_command("SETACL", mailbox, user, rights)
- end
- end
-
- # Send the GETACL command along with a specified +mailbox+.
- # If this mailbox exists, an array containing objects of
- # Net::IMAP::MailboxACLItem will be returned.
- #
- # The ACL extension is described in [EXT-ACL[https://tools.ietf.org/html/rfc4314]]
- def getacl(mailbox)
- synchronize do
- send_command("GETACL", mailbox)
- return @responses.delete("ACL")[-1]
- end
- end
-
- # Sends a LSUB command, and returns a subset of names from the set
- # of names that the user has declared as being "active" or
- # "subscribed." +refname+ and +mailbox+ are interpreted as
- # for #list.
- #
- # The return value is an array of +Net::IMAP::MailboxList+.
- def lsub(refname, mailbox)
- synchronize do
- send_command("LSUB", refname, mailbox)
- return @responses.delete("LSUB")
- end
- end
-
- # Sends a STATUS command, and returns the status of the indicated
- # +mailbox+. +attr+ is a list of one or more attributes whose
- # statuses are to be requested. Supported attributes include:
- #
- # MESSAGES:: the number of messages in the mailbox.
- # RECENT:: the number of recent messages in the mailbox.
- # UNSEEN:: the number of unseen messages in the mailbox.
- #
- # The return value is a hash of attributes. For example:
- #
- # p imap.status("inbox", ["MESSAGES", "RECENT"])
- # #=> {"RECENT"=>0, "MESSAGES"=>44}
- #
- # A Net::IMAP::NoResponseError is raised if status values
- # for +mailbox+ cannot be returned; for instance, because it
- # does not exist.
- def status(mailbox, attr)
- synchronize do
- send_command("STATUS", mailbox, attr)
- return @responses.delete("STATUS")[-1].attr
- end
- end
-
- # Sends a APPEND command to append the +message+ to the end of
- # the +mailbox+. The optional +flags+ argument is an array of
- # flags initially passed to the new message. The optional
- # +date_time+ argument specifies the creation time to assign to the
- # new message; it defaults to the current time.
- # For example:
- #
- # imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
- # Subject: hello
- # From: shugo@ruby-lang.org
- # To: shugo@ruby-lang.org
- #
- # hello world
- # EOF
- #
- # A Net::IMAP::NoResponseError is raised if the mailbox does
- # not exist (it is not created automatically), or if the flags,
- # date_time, or message arguments contain errors.
- def append(mailbox, message, flags = nil, date_time = nil)
- args = []
- if flags
- args.push(flags)
- end
- args.push(date_time) if date_time
- args.push(Literal.new(message))
- send_command("APPEND", mailbox, *args)
- end
-
- # Sends a CHECK command to request a checkpoint of the currently
- # selected mailbox. This performs implementation-specific
- # housekeeping; for instance, reconciling the mailbox's
- # in-memory and on-disk state.
- def check
- send_command("CHECK")
- end
-
- # Sends a CLOSE command to close the currently selected mailbox.
- # The CLOSE command permanently removes from the mailbox all
- # messages that have the \Deleted flag set.
- def close
- send_command("CLOSE")
- end
-
- # Sends a EXPUNGE command to permanently remove from the currently
- # selected mailbox all messages that have the \Deleted flag set.
- def expunge
- synchronize do
- send_command("EXPUNGE")
- return @responses.delete("EXPUNGE")
- end
- end
-
- # Sends a SEARCH command to search the mailbox for messages that
- # match the given searching criteria, and returns message sequence
- # numbers. +keys+ can either be a string holding the entire
- # search string, or a single-dimension array of search keywords and
- # arguments. The following are some common search criteria;
- # see [IMAP] section 6.4.4 for a full list.
- #
- # <message set>:: a set of message sequence numbers. ',' indicates
- # an interval, ':' indicates a range. For instance,
- # '2,10:12,15' means "2,10,11,12,15".
- #
- # BEFORE <date>:: messages with an internal date strictly before
- # <date>. The date argument has a format similar
- # to 8-Aug-2002.
- #
- # BODY <string>:: messages that contain <string> within their body.
- #
- # CC <string>:: messages containing <string> in their CC field.
- #
- # FROM <string>:: messages that contain <string> in their FROM field.
- #
- # NEW:: messages with the \Recent, but not the \Seen, flag set.
- #
- # NOT <search-key>:: negate the following search key.
- #
- # OR <search-key> <search-key>:: "or" two search keys together.
- #
- # ON <date>:: messages with an internal date exactly equal to <date>,
- # which has a format similar to 8-Aug-2002.
- #
- # SINCE <date>:: messages with an internal date on or after <date>.
- #
- # SUBJECT <string>:: messages with <string> in their subject.
- #
- # TO <string>:: messages with <string> in their TO field.
- #
- # For example:
- #
- # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
- # #=> [1, 6, 7, 8]
- def search(keys, charset = nil)
- return search_internal("SEARCH", keys, charset)
- end
-
- # Similar to #search, but returns unique identifiers.
- def uid_search(keys, charset = nil)
- return search_internal("UID SEARCH", keys, charset)
- end
-
- # Sends a FETCH command to retrieve data associated with a message
- # in the mailbox.
- #
- # The +set+ parameter is a number or a range between two numbers,
- # or an array of those. The number is a message sequence number,
- # where -1 represents a '*' for use in range notation like 100..-1
- # being interpreted as '100:*'. Beware that the +exclude_end?+
- # property of a Range object is ignored, and the contents of a
- # range are independent of the order of the range endpoints as per
- # the protocol specification, so 1...5, 5..1 and 5...1 are all
- # equivalent to 1..5.
- #
- # +attr+ is a list of attributes to fetch; see the documentation
- # for Net::IMAP::FetchData for a list of valid attributes.
- #
- # The return value is an array of Net::IMAP::FetchData or nil
- # (instead of an empty array) if there is no matching message.
- #
- # For example:
- #
- # p imap.fetch(6..8, "UID")
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
- # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
- # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
- # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
- # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
- # p data.seqno
- # #=> 6
- # p data.attr["RFC822.SIZE"]
- # #=> 611
- # p data.attr["INTERNALDATE"]
- # #=> "12-Oct-2000 22:40:59 +0900"
- # p data.attr["UID"]
- # #=> 98
- def fetch(set, attr, mod = nil)
- return fetch_internal("FETCH", set, attr, mod)
- end
-
- # Similar to #fetch, but +set+ contains unique identifiers.
- def uid_fetch(set, attr, mod = nil)
- return fetch_internal("UID FETCH", set, attr, mod)
- end
-
- # Sends a STORE command to alter data associated with messages
- # in the mailbox, in particular their flags. The +set+ parameter
- # is a number, an array of numbers, or a Range object. Each number
- # is a message sequence number. +attr+ is the name of a data item
- # to store: 'FLAGS' will replace the message's flag list
- # with the provided one, '+FLAGS' will add the provided flags,
- # and '-FLAGS' will remove them. +flags+ is a list of flags.
- #
- # The return value is an array of Net::IMAP::FetchData. For example:
- #
- # p imap.store(6..8, "+FLAGS", [:Deleted])
- # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
- # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
- # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
- def store(set, attr, flags)
- return store_internal("STORE", set, attr, flags)
- end
-
- # Similar to #store, but +set+ contains unique identifiers.
- def uid_store(set, attr, flags)
- return store_internal("UID STORE", set, attr, flags)
- end
-
- # Sends a COPY command to copy the specified message(s) to the end
- # of the specified destination +mailbox+. The +set+ parameter is
- # a number, an array of numbers, or a Range object. The number is
- # a message sequence number.
- def copy(set, mailbox)
- copy_internal("COPY", set, mailbox)
- end
-
- # Similar to #copy, but +set+ contains unique identifiers.
- def uid_copy(set, mailbox)
- copy_internal("UID COPY", set, mailbox)
- end
-
- # Sends a MOVE command to move the specified message(s) to the end
- # of the specified destination +mailbox+. The +set+ parameter is
- # a number, an array of numbers, or a Range object. The number is
- # a message sequence number.
- #
- # The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
- def move(set, mailbox)
- copy_internal("MOVE", set, mailbox)
- end
-
- # Similar to #move, but +set+ contains unique identifiers.
- #
- # The MOVE extension is described in [EXT-MOVE[https://tools.ietf.org/html/rfc6851]].
- def uid_move(set, mailbox)
- copy_internal("UID MOVE", set, mailbox)
- end
-
- # Sends a SORT command to sort messages in the mailbox.
- # Returns an array of message sequence numbers. For example:
- #
- # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
- # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
- # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
- # #=> [6, 7, 8, 1]
- #
- # The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
- def sort(sort_keys, search_keys, charset)
- return sort_internal("SORT", sort_keys, search_keys, charset)
- end
-
- # Similar to #sort, but returns an array of unique identifiers.
- #
- # The SORT extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
- def uid_sort(sort_keys, search_keys, charset)
- return sort_internal("UID SORT", sort_keys, search_keys, charset)
- end
-
- # Adds a response handler. For example, to detect when
- # the server sends a new EXISTS response (which normally
- # indicates new messages being added to the mailbox),
- # add the following handler after selecting the
- # mailbox:
- #
- # imap.add_response_handler { |resp|
- # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
- # puts "Mailbox now has #{resp.data} messages"
- # end
- # }
- #
- def add_response_handler(handler = nil, &block)
- raise ArgumentError, "two Procs are passed" if handler && block
- @response_handlers.push(block || handler)
- end
-
- # Removes the response handler.
- def remove_response_handler(handler)
- @response_handlers.delete(handler)
- end
-
- # Similar to #search, but returns message sequence numbers in threaded
- # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
- # are:
- #
- # ORDEREDSUBJECT:: split into single-level threads according to subject,
- # ordered by date.
- # REFERENCES:: split into threads by parent/child relationships determined
- # by which message is a reply to which.
- #
- # Unlike #search, +charset+ is a required argument. US-ASCII
- # and UTF-8 are sample values.
- #
- # The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
- def thread(algorithm, search_keys, charset)
- return thread_internal("THREAD", algorithm, search_keys, charset)
- end
-
- # Similar to #thread, but returns unique identifiers instead of
- # message sequence numbers.
- #
- # The THREAD extension is described in [EXT-SORT-THREAD[https://tools.ietf.org/html/rfc5256]].
- def uid_thread(algorithm, search_keys, charset)
- return thread_internal("UID THREAD", algorithm, search_keys, charset)
- end
-
- # Sends an IDLE command that waits for notifications of new or expunged
- # messages. Yields responses from the server during the IDLE.
- #
- # Use #idle_done to leave IDLE.
- #
- # If +timeout+ is given, this method returns after +timeout+ seconds passed.
- # +timeout+ can be used for keep-alive. For example, the following code
- # checks the connection for each 60 seconds.
- #
- # loop do
- # imap.idle(60) do |res|
- # ...
- # end
- # end
- def idle(timeout = nil, &response_handler)
- raise LocalJumpError, "no block given" unless response_handler
-
- response = nil
-
- synchronize do
- tag = Thread.current[:net_imap_tag] = generate_tag
- put_string("#{tag} IDLE#{CRLF}")
-
- begin
- add_response_handler(&response_handler)
- @idle_done_cond = new_cond
- @idle_done_cond.wait(timeout)
- @idle_done_cond = nil
- if @receiver_thread_terminating
- raise @exception || Net::IMAP::Error.new("connection closed")
- end
- ensure
- unless @receiver_thread_terminating
- remove_response_handler(response_handler)
- put_string("DONE#{CRLF}")
- response = get_tagged_response(tag, "IDLE", @idle_response_timeout)
- end
- end
- end
-
- return response
- end
-
- # Leaves IDLE.
- def idle_done
- synchronize do
- if @idle_done_cond.nil?
- raise Net::IMAP::Error, "not during IDLE"
- end
- @idle_done_cond.signal
- end
- end
-
- private
-
- CRLF = "\r\n" # :nodoc:
- PORT = 143 # :nodoc:
- SSL_PORT = 993 # :nodoc:
-
- @@debug = false
-
- # :call-seq:
- # Net::IMAP.new(host, options = {})
- #
- # Creates a new Net::IMAP object and connects it to the specified
- # +host+.
- #
- # +options+ is an option hash, each key of which is a symbol.
- #
- # The available options are:
- #
- # port:: Port number (default value is 143 for imap, or 993 for imaps)
- # ssl:: If +options[:ssl]+ is true, then an attempt will be made
- # to use SSL (now TLS) to connect to the server. For this to work
- # OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
- # be installed.
- # If +options[:ssl]+ is a hash, it's passed to
- # OpenSSL::SSL::SSLContext#set_params as parameters.
- # open_timeout:: Seconds to wait until a connection is opened
- # idle_response_timeout:: Seconds to wait until an IDLE response is received
- #
- # The most common errors are:
- #
- # Errno::ECONNREFUSED:: Connection refused by +host+ or an intervening
- # firewall.
- # Errno::ETIMEDOUT:: Connection timed out (possibly due to packets
- # being dropped by an intervening firewall).
- # Errno::ENETUNREACH:: There is no route to that network.
- # SocketError:: Hostname not known or other socket error.
- # Net::IMAP::ByeResponseError:: The connected to the host was successful, but
- # it immediately said goodbye.
- def initialize(host, port_or_options = {},
- usessl = false, certs = nil, verify = true)
- super()
- @host = host
- begin
- options = port_or_options.to_hash
- rescue NoMethodError
- # for backward compatibility
- options = {}
- options[:port] = port_or_options
- if usessl
- options[:ssl] = create_ssl_params(certs, verify)
- end
- end
- @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
- @tag_prefix = "RUBY"
- @tagno = 0
- @open_timeout = options[:open_timeout] || 30
- @idle_response_timeout = options[:idle_response_timeout] || 5
- @parser = ResponseParser.new
- @sock = tcp_socket(@host, @port)
- begin
- if options[:ssl]
- start_tls_session(options[:ssl])
- @usessl = true
- else
- @usessl = false
- end
- @responses = Hash.new([].freeze)
- @tagged_responses = {}
- @response_handlers = []
- @tagged_response_arrival = new_cond
- @continued_command_tag = nil
- @continuation_request_arrival = new_cond
- @continuation_request_exception = nil
- @idle_done_cond = nil
- @logout_command_tag = nil
- @debug_output_bol = true
- @exception = nil
-
- @greeting = get_response
- if @greeting.nil?
- raise Error, "connection closed"
- end
- if @greeting.name == "BYE"
- raise ByeResponseError, @greeting
- end
-
- @client_thread = Thread.current
- @receiver_thread = Thread.start {
- begin
- receive_responses
- rescue Exception
- end
- }
- @receiver_thread_terminating = false
- rescue Exception
- @sock.close
- raise
- end
- end
-
- def tcp_socket(host, port)
- s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
- s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
- s
- rescue Errno::ETIMEDOUT
- raise Net::OpenTimeout, "Timeout to open TCP connection to " +
- "#{host}:#{port} (exceeds #{@open_timeout} seconds)"
- end
-
- def receive_responses
- connection_closed = false
- until connection_closed
- synchronize do
- @exception = nil
- end
- begin
- resp = get_response
- rescue Exception => e
- synchronize do
- @sock.close
- @exception = e
- end
- break
- end
- unless resp
- synchronize do
- @exception = EOFError.new("end of file reached")
- end
- break
- end
- begin
- synchronize do
- case resp
- when TaggedResponse
- @tagged_responses[resp.tag] = resp
- @tagged_response_arrival.broadcast
- case resp.tag
- when @logout_command_tag
- return
- when @continued_command_tag
- @continuation_request_exception =
- RESPONSE_ERRORS[resp.name].new(resp)
- @continuation_request_arrival.signal
- end
- when UntaggedResponse
- record_response(resp.name, resp.data)
- if resp.data.instance_of?(ResponseText) &&
- (code = resp.data.code)
- record_response(code.name, code.data)
- end
- if resp.name == "BYE" && @logout_command_tag.nil?
- @sock.close
- @exception = ByeResponseError.new(resp)
- connection_closed = true
- end
- when ContinuationRequest
- @continuation_request_arrival.signal
- end
- @response_handlers.each do |handler|
- handler.call(resp)
- end
- end
- rescue Exception => e
- @exception = e
- synchronize do
- @tagged_response_arrival.broadcast
- @continuation_request_arrival.broadcast
- end
- end
- end
- synchronize do
- @receiver_thread_terminating = true
- @tagged_response_arrival.broadcast
- @continuation_request_arrival.broadcast
- if @idle_done_cond
- @idle_done_cond.signal
- end
- end
- end
-
- def get_tagged_response(tag, cmd, timeout = nil)
- if timeout
- deadline = Time.now + timeout
- end
- until @tagged_responses.key?(tag)
- raise @exception if @exception
- if timeout
- timeout = deadline - Time.now
- if timeout <= 0
- return nil
- end
- end
- @tagged_response_arrival.wait(timeout)
- end
- resp = @tagged_responses.delete(tag)
- case resp.name
- when /\A(?:NO)\z/ni
- raise NoResponseError, resp
- when /\A(?:BAD)\z/ni
- raise BadResponseError, resp
- else
- return resp
- end
- end
-
- def get_response
- buff = String.new
- while true
- s = @sock.gets(CRLF)
- break unless s
- buff.concat(s)
- if /\{(\d+)\}\r\n/n =~ s
- s = @sock.read($1.to_i)
- buff.concat(s)
- else
- break
- end
- end
- return nil if buff.length == 0
- if @@debug
- $stderr.print(buff.gsub(/^/n, "S: "))
- end
- return @parser.parse(buff)
- end
-
- def record_response(name, data)
- unless @responses.has_key?(name)
- @responses[name] = []
- end
- @responses[name].push(data)
- end
-
- def send_command(cmd, *args, &block)
- synchronize do
- args.each do |i|
- validate_data(i)
- end
- tag = generate_tag
- put_string(tag + " " + cmd)
- args.each do |i|
- put_string(" ")
- send_data(i, tag)
- end
- put_string(CRLF)
- if cmd == "LOGOUT"
- @logout_command_tag = tag
- end
- if block
- add_response_handler(&block)
- end
- begin
- return get_tagged_response(tag, cmd)
- ensure
- if block
- remove_response_handler(block)
- end
- end
- end
- end
-
- def generate_tag
- @tagno += 1
- return format("%s%04d", @tag_prefix, @tagno)
- end
-
- def put_string(str)
- @sock.print(str)
- if @@debug
- if @debug_output_bol
- $stderr.print("C: ")
- end
- $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
- if /\r\n\z/n.match(str)
- @debug_output_bol = true
- else
- @debug_output_bol = false
- end
- end
- end
-
- def search_internal(cmd, keys, charset)
- if keys.instance_of?(String)
- keys = [RawData.new(keys)]
- else
- normalize_searching_criteria(keys)
- end
- synchronize do
- if charset
- send_command(cmd, "CHARSET", charset, *keys)
- else
- send_command(cmd, *keys)
- end
- return @responses.delete("SEARCH")[-1]
- end
- end
-
- def fetch_internal(cmd, set, attr, mod = nil)
- case attr
- when String then
- attr = RawData.new(attr)
- when Array then
- attr = attr.map { |arg|
- arg.is_a?(String) ? RawData.new(arg) : arg
- }
- end
-
- synchronize do
- @responses.delete("FETCH")
- if mod
- send_command(cmd, MessageSet.new(set), attr, mod)
- else
- send_command(cmd, MessageSet.new(set), attr)
- end
- return @responses.delete("FETCH")
- end
- end
-
- def store_internal(cmd, set, attr, flags)
- if attr.instance_of?(String)
- attr = RawData.new(attr)
- end
- synchronize do
- @responses.delete("FETCH")
- send_command(cmd, MessageSet.new(set), attr, flags)
- return @responses.delete("FETCH")
- end
- end
-
- def copy_internal(cmd, set, mailbox)
- send_command(cmd, MessageSet.new(set), mailbox)
- end
-
- def sort_internal(cmd, sort_keys, search_keys, charset)
- if search_keys.instance_of?(String)
- search_keys = [RawData.new(search_keys)]
- else
- normalize_searching_criteria(search_keys)
- end
- normalize_searching_criteria(search_keys)
- synchronize do
- send_command(cmd, sort_keys, charset, *search_keys)
- return @responses.delete("SORT")[-1]
- end
- end
-
- def thread_internal(cmd, algorithm, search_keys, charset)
- if search_keys.instance_of?(String)
- search_keys = [RawData.new(search_keys)]
- else
- normalize_searching_criteria(search_keys)
- end
- normalize_searching_criteria(search_keys)
- send_command(cmd, algorithm, charset, *search_keys)
- return @responses.delete("THREAD")[-1]
- end
-
- def normalize_searching_criteria(keys)
- keys.collect! do |i|
- case i
- when -1, Range, Array
- MessageSet.new(i)
- else
- i
- end
- end
- end
-
- def create_ssl_params(certs = nil, verify = true)
- params = {}
- if certs
- if File.file?(certs)
- params[:ca_file] = certs
- elsif File.directory?(certs)
- params[:ca_path] = certs
- end
- end
- if verify
- params[:verify_mode] = VERIFY_PEER
- else
- params[:verify_mode] = VERIFY_NONE
- end
- return params
- end
-
- def start_tls_session(params = {})
- unless defined?(OpenSSL::SSL)
- raise "SSL extension not installed"
- end
- if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
- raise RuntimeError, "already using SSL"
- end
- begin
- params = params.to_hash
- rescue NoMethodError
- params = {}
- end
- context = SSLContext.new
- context.set_params(params)
- if defined?(VerifyCallbackProc)
- context.verify_callback = VerifyCallbackProc
- end
- @sock = SSLSocket.new(@sock, context)
- @sock.sync_close = true
- @sock.hostname = @host if @sock.respond_to? :hostname=
- ssl_socket_connect(@sock, @open_timeout)
- if context.verify_mode != VERIFY_NONE
- @sock.post_connection_check(@host)
- end
- end
-
- # Common validators of number and nz_number types
- module NumValidator # :nodoc
- class << self
- # Check is passed argument valid 'number' in RFC 3501 terminology
- def valid_number?(num)
- # [RFC 3501]
- # number = 1*DIGIT
- # ; Unsigned 32-bit integer
- # ; (0 <= n < 4,294,967,296)
- num >= 0 && num < 4294967296
- end
-
- # Check is passed argument valid 'nz_number' in RFC 3501 terminology
- def valid_nz_number?(num)
- # [RFC 3501]
- # nz-number = digit-nz *DIGIT
- # ; Non-zero unsigned 32-bit integer
- # ; (0 < n < 4,294,967,296)
- num != 0 && valid_number?(num)
- end
-
- # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology
- def valid_mod_sequence_value?(num)
- # mod-sequence-value = 1*DIGIT
- # ; Positive unsigned 64-bit integer
- # ; (mod-sequence)
- # ; (1 <= n < 18,446,744,073,709,551,615)
- num >= 1 && num < 18446744073709551615
- end
-
- # Ensure argument is 'number' or raise DataFormatError
- def ensure_number(num)
- return if valid_number?(num)
-
- msg = "number must be unsigned 32-bit integer: #{num}"
- raise DataFormatError, msg
- end
-
- # Ensure argument is 'nz_number' or raise DataFormatError
- def ensure_nz_number(num)
- return if valid_nz_number?(num)
-
- msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}"
- raise DataFormatError, msg
- end
-
- # Ensure argument is 'mod_sequence_value' or raise DataFormatError
- def ensure_mod_sequence_value(num)
- return if valid_mod_sequence_value?(num)
-
- msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}"
- raise DataFormatError, msg
- end
- end
- end
-
- # Superclass of IMAP errors.
- class Error < StandardError
- end
-
- # Error raised when data is in the incorrect format.
- class DataFormatError < Error
- end
-
- # Error raised when a response from the server is non-parseable.
- class ResponseParseError < Error
- end
-
- # Superclass of all errors used to encapsulate "fail" responses
- # from the server.
- class ResponseError < Error
-
- # The response that caused this error
- attr_accessor :response
-
- def initialize(response)
- @response = response
-
- super @response.data.text
- end
-
- end
-
- # Error raised upon a "NO" response from the server, indicating
- # that the client command could not be completed successfully.
- class NoResponseError < ResponseError
- end
-
- # Error raised upon a "BAD" response from the server, indicating
- # that the client command violated the IMAP protocol, or an internal
- # server failure has occurred.
- class BadResponseError < ResponseError
- end
-
- # Error raised upon a "BYE" response from the server, indicating
- # that the client is not being allowed to login, or has been timed
- # out due to inactivity.
- class ByeResponseError < ResponseError
- end
-
- RESPONSE_ERRORS = Hash.new(ResponseError)
- RESPONSE_ERRORS["NO"] = NoResponseError
- RESPONSE_ERRORS["BAD"] = BadResponseError
-
- # Error raised when too many flags are interned to symbols.
- class FlagCountError < Error
- end
- end
-end
-
-require_relative "imap/authenticators"
diff --git a/lib/net/imap/authenticators.rb b/lib/net/imap/authenticators.rb
deleted file mode 100644
index b5dded35a5..0000000000
--- a/lib/net/imap/authenticators.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-# Registry for SASL authenticators used by Net::IMAP.
-module Net::IMAP::Authenticators
-
- # Adds an authenticator for use with Net::IMAP#authenticate. +auth_type+ is the
- # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
- # supported by +authenticator+ (for instance, "+PLAIN+"). The +authenticator+
- # is an object which defines a +#process+ method to handle authentication with
- # the server. See Net::IMAP::PlainAuthenticator, Net::IMAP::LoginAuthenticator,
- # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator for
- # examples.
- #
- # If +auth_type+ refers to an existing authenticator, it will be
- # replaced by the new one.
- def add_authenticator(auth_type, authenticator)
- authenticators[auth_type] = authenticator
- end
-
- # Builds an authenticator for Net::IMAP#authenticate. +args+ will be passed
- # directly to the chosen authenticator's +#initialize+.
- def authenticator(auth_type, *args)
- auth_type = auth_type.upcase
- unless authenticators.has_key?(auth_type)
- raise ArgumentError,
- format('unknown auth type - "%s"', auth_type)
- end
- authenticators[auth_type].new(*args)
- end
-
- private
-
- def authenticators
- @authenticators ||= {}
- end
-
-end
-
-Net::IMAP.extend Net::IMAP::Authenticators
-
-require_relative "authenticators/login"
-require_relative "authenticators/plain"
-require_relative "authenticators/cram_md5"
-require_relative "authenticators/digest_md5"
diff --git a/lib/net/imap/authenticators/cram_md5.rb b/lib/net/imap/authenticators/cram_md5.rb
deleted file mode 100644
index 0930c5ac34..0000000000
--- a/lib/net/imap/authenticators/cram_md5.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require "digest/md5"
-
-# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in
-# RFC2195[https://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate.
-#
-# == Deprecated
-#
-# +CRAM-MD5+ is obsolete and insecure. It is included for compatibility with
-# existing servers.
-# {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html]
-# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead.
-#
-# Additionally, RFC8314[https://tools.ietf.org/html/rfc8314] discourage the use
-# of cleartext and recommends TLS version 1.2 or greater be used for all
-# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
-class Net::IMAP::CramMD5Authenticator
- def process(challenge)
- digest = hmac_md5(challenge, @password)
- return @user + " " + digest
- end
-
- private
-
- def initialize(user, password)
- @user = user
- @password = password
- end
-
- def hmac_md5(text, key)
- if key.length > 64
- key = Digest::MD5.digest(key)
- end
-
- k_ipad = key + "\0" * (64 - key.length)
- k_opad = key + "\0" * (64 - key.length)
- for i in 0..63
- k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr
- k_opad[i] = (k_opad[i].ord ^ 0x5c).chr
- end
-
- digest = Digest::MD5.digest(k_ipad + text)
-
- return Digest::MD5.hexdigest(k_opad + digest)
- end
-
- Net::IMAP.add_authenticator "PLAIN", self
-end
diff --git a/lib/net/imap/authenticators/digest_md5.rb b/lib/net/imap/authenticators/digest_md5.rb
deleted file mode 100644
index 19e1a460c8..0000000000
--- a/lib/net/imap/authenticators/digest_md5.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-require "digest/md5"
-require "strscan"
-
-# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
-# in RFC2831(https://tools.ietf.org/html/rfc2831). See Net::IMAP#authenticate.
-#
-# == Deprecated
-#
-# "+DIGEST-MD5+" has been deprecated by
-# {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on for
-# security. It is included for compatibility with existing servers.
-class Net::IMAP::DigestMD5Authenticator
- def process(challenge)
- case @stage
- when STAGE_ONE
- @stage = STAGE_TWO
- sparams = {}
- c = StringScanner.new(challenge)
- while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
- k, v = c[1], c[2]
- if v =~ /^"(.*)"$/
- v = $1
- if v =~ /,/
- v = v.split(',')
- end
- end
- sparams[k] = v
- end
-
- raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.size == 0
- raise Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")
-
- response = {
- :nonce => sparams['nonce'],
- :username => @user,
- :realm => sparams['realm'],
- :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
- :'digest-uri' => 'imap/' + sparams['realm'],
- :qop => 'auth',
- :maxbuf => 65535,
- :nc => "%08d" % nc(sparams['nonce']),
- :charset => sparams['charset'],
- }
-
- response[:authzid] = @authname unless @authname.nil?
-
- # now, the real thing
- a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
-
- a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':')
- a1 << ':' + response[:authzid] unless response[:authzid].nil?
-
- a2 = "AUTHENTICATE:" + response[:'digest-uri']
- a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/
-
- response[:response] = Digest::MD5.hexdigest(
- [
- Digest::MD5.hexdigest(a1),
- response.values_at(:nonce, :nc, :cnonce, :qop),
- Digest::MD5.hexdigest(a2)
- ].join(':')
- )
-
- return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',')
- when STAGE_TWO
- @stage = nil
- # if at the second stage, return an empty string
- if challenge =~ /rspauth=/
- return ''
- else
- raise ResponseParseError, challenge
- end
- else
- raise ResponseParseError, challenge
- end
- end
-
- def initialize(user, password, authname = nil)
- @user, @password, @authname = user, password, authname
- @nc, @stage = {}, STAGE_ONE
- end
-
- private
-
- STAGE_ONE = :stage_one
- STAGE_TWO = :stage_two
-
- def nc(nonce)
- if @nc.has_key? nonce
- @nc[nonce] = @nc[nonce] + 1
- else
- @nc[nonce] = 1
- end
- return @nc[nonce]
- end
-
- # some responses need quoting
- def qdval(k, v)
- return if k.nil? or v.nil?
- if %w"username authzid realm nonce cnonce digest-uri qop".include? k
- v.gsub!(/([\\"])/, "\\\1")
- return '%s="%s"' % [k, v]
- else
- return '%s=%s' % [k, v]
- end
- end
-
- Net::IMAP.add_authenticator "DIGEST-MD5", self
-end
diff --git a/lib/net/imap/authenticators/login.rb b/lib/net/imap/authenticators/login.rb
deleted file mode 100644
index e1afebc323..0000000000
--- a/lib/net/imap/authenticators/login.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-# Authenticator for the "+LOGIN+" SASL mechanism. See Net::IMAP#authenticate.
-#
-# +LOGIN+ authentication sends the password in cleartext.
-# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
-# cleartext authentication until after TLS has been negotiated.
-# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
-# greater be used for all traffic, and deprecate cleartext access ASAP. +LOGIN+
-# can be secured by TLS encryption.
-#
-# == Deprecated
-#
-# The {SASL mechanisms
-# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
-# marks "LOGIN" as obsoleted in favor of "PLAIN". It is included here for
-# compatibility with existing servers. See
-# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login]
-# for both specification and deprecation.
-class Net::IMAP::LoginAuthenticator
- def process(data)
- case @state
- when STATE_USER
- @state = STATE_PASSWORD
- return @user
- when STATE_PASSWORD
- return @password
- end
- end
-
- private
-
- STATE_USER = :USER
- STATE_PASSWORD = :PASSWORD
-
- def initialize(user, password)
- @user = user
- @password = password
- @state = STATE_USER
- end
-
- Net::IMAP.add_authenticator "LOGIN", self
-end
diff --git a/lib/net/imap/authenticators/plain.rb b/lib/net/imap/authenticators/plain.rb
deleted file mode 100644
index a9d46c920e..0000000000
--- a/lib/net/imap/authenticators/plain.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-# Authenticator for the "+PLAIN+" SASL mechanism, specified in
-# RFC4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate.
-#
-# +PLAIN+ authentication sends the password in cleartext.
-# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
-# cleartext authentication until after TLS has been negotiated.
-# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
-# greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+
-# can be secured by TLS encryption.
-class Net::IMAP::PlainAuthenticator
-
- def process(data)
- return "#@authzid\0#@username\0#@password"
- end
-
- # :nodoc:
- NULL = -"\0".b
-
- private
-
- # +username+ is the authentication identity, the identity whose +password+ is
- # used. +username+ is referred to as +authcid+ by
- # RFC4616[https://tools.ietf.org/html/rfc4616].
- #
- # +authzid+ is the authorization identity (identity to act as). It can
- # usually be left blank. When +authzid+ is left blank (nil or empty string)
- # the server will derive an identity from the credentials and use that as the
- # authorization identity.
- def initialize(username, password, authzid: nil)
- raise ArgumentError, "username contains NULL" if username&.include?(NULL)
- raise ArgumentError, "password contains NULL" if password&.include?(NULL)
- raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
- @username = username
- @password = password
- @authzid = authzid
- end
-
- Net::IMAP.add_authenticator "PLAIN", self
-end
diff --git a/lib/net/imap/command_data.rb b/lib/net/imap/command_data.rb
deleted file mode 100644
index d52390c933..0000000000
--- a/lib/net/imap/command_data.rb
+++ /dev/null
@@ -1,301 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- private
-
- def validate_data(data)
- case data
- when nil
- when String
- when Integer
- NumValidator.ensure_number(data)
- when Array
- if data[0] == 'CHANGEDSINCE'
- NumValidator.ensure_mod_sequence_value(data[1])
- else
- data.each do |i|
- validate_data(i)
- end
- end
- when Time
- when Symbol
- else
- data.validate
- end
- end
-
- def send_data(data, tag = nil)
- case data
- when nil
- put_string("NIL")
- when String
- send_string_data(data, tag)
- when Integer
- send_number_data(data)
- when Array
- send_list_data(data, tag)
- when Time
- send_time_data(data)
- when Symbol
- send_symbol_data(data)
- else
- data.send_data(self, tag)
- end
- end
-
- def send_string_data(str, tag = nil)
- case str
- when ""
- put_string('""')
- when /[\x80-\xff\r\n]/n
- # literal
- send_literal(str, tag)
- when /[(){ \x00-\x1f\x7f%*"\\]/n
- # quoted string
- send_quoted_string(str)
- else
- put_string(str)
- end
- end
-
- def send_quoted_string(str)
- put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
- end
-
- def send_literal(str, tag = nil)
- synchronize do
- put_string("{" + str.bytesize.to_s + "}" + CRLF)
- @continued_command_tag = tag
- @continuation_request_exception = nil
- begin
- @continuation_request_arrival.wait
- e = @continuation_request_exception || @exception
- raise e if e
- put_string(str)
- ensure
- @continued_command_tag = nil
- @continuation_request_exception = nil
- end
- end
- end
-
- def send_number_data(num)
- put_string(num.to_s)
- end
-
- def send_list_data(list, tag = nil)
- put_string("(")
- first = true
- list.each do |i|
- if first
- first = false
- else
- put_string(" ")
- end
- send_data(i, tag)
- end
- put_string(")")
- end
-
- DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
-
- def send_time_data(time)
- t = time.dup.gmtime
- s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
- t.day, DATE_MONTH[t.month - 1], t.year,
- t.hour, t.min, t.sec)
- put_string(s)
- end
-
- def send_symbol_data(symbol)
- put_string("\\" + symbol.to_s)
- end
-
- class RawData # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Atom # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class QuotedString # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:send_quoted_string, @data)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Literal # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:send_literal, @data, tag)
- end
-
- def validate
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class MessageSet # :nodoc:
- def send_data(imap, tag)
- imap.__send__(:put_string, format_internal(@data))
- end
-
- def validate
- validate_internal(@data)
- end
-
- private
-
- def initialize(data)
- @data = data
- end
-
- def format_internal(data)
- case data
- when "*"
- return data
- when Integer
- if data == -1
- return "*"
- else
- return data.to_s
- end
- when Range
- return format_internal(data.first) +
- ":" + format_internal(data.last)
- when Array
- return data.collect {|i| format_internal(i)}.join(",")
- when ThreadMember
- return data.seqno.to_s +
- ":" + data.children.collect {|i| format_internal(i).join(",")}
- end
- end
-
- def validate_internal(data)
- case data
- when "*"
- when Integer
- NumValidator.ensure_nz_number(data)
- when Range
- when Array
- data.each do |i|
- validate_internal(i)
- end
- when ThreadMember
- data.children.each do |i|
- validate_internal(i)
- end
- else
- raise DataFormatError, data.inspect
- end
- end
- end
-
- class ClientID # :nodoc:
-
- def send_data(imap, tag)
- imap.__send__(:send_data, format_internal(@data), tag)
- end
-
- def validate
- validate_internal(@data)
- end
-
- private
-
- def initialize(data)
- @data = data
- end
-
- def validate_internal(client_id)
- client_id.to_h.each do |k,v|
- unless StringFormatter.valid_string?(k)
- raise DataFormatError, client_id.inspect
- end
- end
- rescue NoMethodError, TypeError # to_h failed
- raise DataFormatError, client_id.inspect
- end
-
- def format_internal(client_id)
- return nil if client_id.nil?
- client_id.to_h.flat_map {|k,v|
- [StringFormatter.string(k), StringFormatter.nstring(v)]
- }
- end
-
- end
-
- module StringFormatter
-
- LITERAL_REGEX = /[\x80-\xff\r\n]/n
-
- module_function
-
- # Allows symbols in addition to strings
- def valid_string?(str)
- str.is_a?(Symbol) || str.respond_to?(:to_str)
- end
-
- # Allows nil, symbols, and strings
- def valid_nstring?(str)
- str.nil? || valid_string?(str)
- end
-
- # coerces using +to_s+
- def string(str)
- str = str.to_s
- if str =~ LITERAL_REGEX
- Literal.new(str)
- else
- QuotedString.new(str)
- end
- end
-
- # coerces non-nil using +to_s+
- def nstring(str)
- str.nil? ? nil : string(str)
- end
-
- end
-
- end
-end
diff --git a/lib/net/imap/data_encoding.rb b/lib/net/imap/data_encoding.rb
deleted file mode 100644
index d8449f582c..0000000000
--- a/lib/net/imap/data_encoding.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # Decode a string from modified UTF-7 format to UTF-8.
- #
- # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
- # slightly modified version of this to encode mailbox names
- # containing non-ASCII characters; see [IMAP] section 5.1.3.
- #
- # Net::IMAP does _not_ automatically encode and decode
- # mailbox names to and from UTF-7.
- def self.decode_utf7(s)
- return s.gsub(/&([^-]+)?-/n) {
- if $1
- ($1.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE)
- else
- "&"
- end
- }
- end
-
- # Encode a string from UTF-8 format to modified UTF-7.
- def self.encode_utf7(s)
- return s.gsub(/(&)|[^\x20-\x7e]+/) {
- if $1
- "&-"
- else
- base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
- "&" + base64.delete("=").tr("/", ",") + "-"
- end
- }.force_encoding("ASCII-8BIT")
- end
-
- # Formats +time+ as an IMAP-style date.
- def self.format_date(time)
- return time.strftime('%d-%b-%Y')
- end
-
- # Formats +time+ as an IMAP-style date-time.
- def self.format_datetime(time)
- return time.strftime('%d-%b-%Y %H:%M %z')
- end
-
- end
-end
diff --git a/lib/net/imap/flags.rb b/lib/net/imap/flags.rb
deleted file mode 100644
index b3d98c09b2..0000000000
--- a/lib/net/imap/flags.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # :category: Message Flags
- #
- # Flag indicating a message has been seen.
- SEEN = :Seen
-
- # :category: Message Flags
- #
- # Flag indicating a message has been answered.
- ANSWERED = :Answered
-
- # :category: Message Flags
- #
- # Flag indicating a message has been flagged for special or urgent
- # attention.
- FLAGGED = :Flagged
-
- # :category: Message Flags
- #
- # Flag indicating a message has been marked for deletion. This
- # will occur when the mailbox is closed or expunged.
- DELETED = :Deleted
-
- # :category: Message Flags
- #
- # Flag indicating a message is only a draft or work-in-progress version.
- DRAFT = :Draft
-
- # :category: Message Flags
- #
- # Flag indicating that the message is "recent," meaning that this
- # session is the first session in which the client has been notified
- # of this message.
- RECENT = :Recent
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox context name cannot contain
- # children.
- NOINFERIORS = :Noinferiors
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox is not selected.
- NOSELECT = :Noselect
-
- # :category: Mailbox Flags
- #
- # Flag indicating that a mailbox has been marked "interesting" by
- # the server; this commonly indicates that the mailbox contains
- # new messages.
- MARKED = :Marked
-
- # :category: Mailbox Flags
- #
- # Flag indicating that the mailbox does not contains new messages.
- UNMARKED = :Unmarked
-
- @@max_flag_count = 10000
-
- # Returns the max number of flags interned to symbols.
- def self.max_flag_count
- return @@max_flag_count
- end
-
- # Sets the max number of flags interned to symbols.
- def self.max_flag_count=(count)
- @@max_flag_count = count
- end
-
- end
-end
diff --git a/lib/net/imap/net-imap.gemspec b/lib/net/imap/net-imap.gemspec
deleted file mode 100644
index 9a6e0acf2d..0000000000
--- a/lib/net/imap/net-imap.gemspec
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
- end rescue nil
-end
-
-Gem::Specification.new do |spec|
- spec.name = name
- spec.version = version
- spec.authors = ["Shugo Maeda"]
- spec.email = ["shugo@ruby-lang.org"]
-
- spec.summary = %q{Ruby client api for Internet Message Access Protocol}
- spec.description = %q{Ruby client api for Internet Message Access Protocol}
- spec.homepage = "https://github.com/ruby/net-imap"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
- spec.licenses = ["Ruby", "BSD-2-Clause"]
-
- spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = spec.homepage
-
- # Specify which files should be added to the gem when it is released.
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- end
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ["lib"]
-
- spec.add_dependency "net-protocol"
- spec.add_dependency "digest"
- spec.add_dependency "strscan"
-end
diff --git a/lib/net/imap/response_data.rb b/lib/net/imap/response_data.rb
deleted file mode 100644
index f0175286fe..0000000000
--- a/lib/net/imap/response_data.rb
+++ /dev/null
@@ -1,527 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- # Net::IMAP::ContinuationRequest represents command continuation requests.
- #
- # The command continuation request response is indicated by a "+" token
- # instead of a tag. This form of response indicates that the server is
- # ready to accept the continuation of a command from the client. The
- # remainder of this response is a line of text.
- #
- # continue_req ::= "+" SPACE (resp_text / base64)
- #
- # ==== Fields:
- #
- # data:: Returns the data (Net::IMAP::ResponseText).
- #
- # raw_data:: Returns the raw data string.
- class ContinuationRequest < Struct.new(:data, :raw_data)
- end
-
- # Net::IMAP::UntaggedResponse represents untagged responses.
- #
- # Data transmitted by the server to the client and status responses
- # that do not indicate command completion are prefixed with the token
- # "*", and are called untagged responses.
- #
- # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
- # mailbox_data / message_data / capability_data)
- #
- # ==== Fields:
- #
- # name:: Returns the name, such as "FLAGS", "LIST", or "FETCH".
- #
- # data:: Returns the data such as an array of flag symbols,
- # a ((<Net::IMAP::MailboxList>)) object.
- #
- # raw_data:: Returns the raw data string.
- class UntaggedResponse < Struct.new(:name, :data, :raw_data)
- end
-
- # Net::IMAP::IgnoredResponse represents intentionally ignored responses.
- #
- # This includes untagged response "NOOP" sent by eg. Zimbra to avoid some
- # clients to close the connection.
- #
- # It matches no IMAP standard.
- #
- # ==== Fields:
- #
- # raw_data:: Returns the raw data string.
- class IgnoredResponse < Struct.new(:raw_data)
- end
-
- # Net::IMAP::TaggedResponse represents tagged responses.
- #
- # The server completion result response indicates the success or
- # failure of the operation. It is tagged with the same tag as the
- # client command which began the operation.
- #
- # response_tagged ::= tag SPACE resp_cond_state CRLF
- #
- # tag ::= 1*<any ATOM_CHAR except "+">
- #
- # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
- #
- # ==== Fields:
- #
- # tag:: Returns the tag.
- #
- # name:: Returns the name, one of "OK", "NO", or "BAD".
- #
- # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
- #
- # raw_data:: Returns the raw data string.
- #
- class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data)
- end
-
- # Net::IMAP::ResponseText represents texts of responses.
- # The text may be prefixed by the response code.
- #
- # resp_text ::= ["[" resp-text-code "]" SP] text
- #
- # ==== Fields:
- #
- # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
- #
- # text:: Returns the text.
- #
- class ResponseText < Struct.new(:code, :text)
- end
-
- # Net::IMAP::ResponseCode represents response codes.
- #
- # resp_text_code ::= "ALERT" /
- # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
- # capability_data / "PARSE" /
- # "PERMANENTFLAGS" SP "("
- # [flag_perm *(SP flag_perm)] ")" /
- # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
- # "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number /
- # "UNSEEN" SP nz_number /
- # atom [SP 1*<any TEXT-CHAR except "]">]
- #
- # ==== Fields:
- #
- # name:: Returns the name, such as "ALERT", "PERMANENTFLAGS", or "UIDVALIDITY".
- #
- # data:: Returns the data, if it exists.
- #
- class ResponseCode < Struct.new(:name, :data)
- end
-
- # Net::IMAP::MailboxList represents contents of the LIST response.
- #
- # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
- # "\Noselect" / "\Unmarked" / flag_extension) ")"
- # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
- #
- # ==== Fields:
- #
- # attr:: Returns the name attributes. Each name attribute is a symbol
- # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
- #
- # delim:: Returns the hierarchy delimiter.
- #
- # name:: Returns the mailbox name.
- #
- class MailboxList < Struct.new(:attr, :delim, :name)
- end
-
- # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
- # This object can also be a response to GETQUOTAROOT. In the syntax
- # specification below, the delimiter used with the "#" construct is a
- # single space (SPACE).
- #
- # quota_list ::= "(" #quota_resource ")"
- #
- # quota_resource ::= atom SPACE number SPACE number
- #
- # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
- #
- # ==== Fields:
- #
- # mailbox:: The mailbox with the associated quota.
- #
- # usage:: Current storage usage of the mailbox.
- #
- # quota:: Quota limit imposed on the mailbox.
- #
- class MailboxQuota < Struct.new(:mailbox, :usage, :quota)
- end
-
- # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
- # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
- #
- # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
- #
- # ==== Fields:
- #
- # mailbox:: The mailbox with the associated quota.
- #
- # quotaroots:: Zero or more quotaroots that affect the quota on the
- # specified mailbox.
- #
- class MailboxQuotaRoot < Struct.new(:mailbox, :quotaroots)
- end
-
- # Net::IMAP::MailboxACLItem represents the response from GETACL.
- #
- # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
- #
- # identifier ::= astring
- #
- # rights ::= astring
- #
- # ==== Fields:
- #
- # user:: Login name that has certain rights to the mailbox
- # that was specified with the getacl command.
- #
- # rights:: The access rights the indicated user has to the
- # mailbox.
- #
- class MailboxACLItem < Struct.new(:user, :rights, :mailbox)
- end
-
- # Net::IMAP::Namespace represents a single [RFC-2342] namespace.
- #
- # Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> /
- # nil) *(Namespace_Response_Extension) ")" ) ")"
- #
- # Namespace_Response_Extension = SP string SP "(" string *(SP string)
- # ")"
- #
- # ==== Fields:
- #
- # prefix:: Returns the namespace prefix string.
- # delim:: Returns nil or the hierarchy delimiter character.
- # extensions:: Returns a hash of extension names to extension flag arrays.
- #
- class Namespace < Struct.new(:prefix, :delim, :extensions)
- end
-
- # Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE.
- #
- # Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP
- # Namespace
- #
- # ; The first Namespace is the Personal Namespace(s)
- # ; The second Namespace is the Other Users' Namespace(s)
- # ; The third Namespace is the Shared Namespace(s)
- #
- # ==== Fields:
- #
- # personal:: Returns an array of Personal Net::IMAP::Namespace objects.
- # other:: Returns an array of Other Users' Net::IMAP::Namespace objects.
- # shared:: Returns an array of Shared Net::IMAP::Namespace objects.
- #
- class Namespaces < Struct.new(:personal, :other, :shared)
- end
-
- # Net::IMAP::StatusData represents the contents of the STATUS response.
- #
- # ==== Fields:
- #
- # mailbox:: Returns the mailbox name.
- #
- # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
- # "UIDVALIDITY", "UNSEEN". Each value is a number.
- #
- class StatusData < Struct.new(:mailbox, :attr)
- end
-
- # Net::IMAP::FetchData represents the contents of the FETCH response.
- #
- # ==== Fields:
- #
- # seqno:: Returns the message sequence number.
- # (Note: not the unique identifier, even for the UID command response.)
- #
- # attr:: Returns a hash. Each key is a data item name, and each value is
- # its value.
- #
- # The current data items are:
- #
- # [BODY]
- # A form of BODYSTRUCTURE without extension data.
- # [BODY[<section>]<<origin_octet>>]
- # A string expressing the body contents of the specified section.
- # [BODYSTRUCTURE]
- # An object that describes the [MIME-IMB] body structure of a message.
- # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
- # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
- # [ENVELOPE]
- # A Net::IMAP::Envelope object that describes the envelope
- # structure of a message.
- # [FLAGS]
- # A array of flag symbols that are set for this message. Flag symbols
- # are capitalized by String#capitalize.
- # [INTERNALDATE]
- # A string representing the internal date of the message.
- # [RFC822]
- # Equivalent to +BODY[]+.
- # [RFC822.HEADER]
- # Equivalent to +BODY.PEEK[HEADER]+.
- # [RFC822.SIZE]
- # A number expressing the [RFC-822] size of the message.
- # [RFC822.TEXT]
- # Equivalent to +BODY[TEXT]+.
- # [UID]
- # A number expressing the unique identifier of the message.
- #
- class FetchData < Struct.new(:seqno, :attr)
- end
-
- # Net::IMAP::Envelope represents envelope structures of messages.
- #
- # ==== Fields:
- #
- # date:: Returns a string that represents the date.
- #
- # subject:: Returns a string that represents the subject.
- #
- # from:: Returns an array of Net::IMAP::Address that represents the from.
- #
- # sender:: Returns an array of Net::IMAP::Address that represents the sender.
- #
- # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
- #
- # to:: Returns an array of Net::IMAP::Address that represents the to.
- #
- # cc:: Returns an array of Net::IMAP::Address that represents the cc.
- #
- # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
- #
- # in_reply_to:: Returns a string that represents the in-reply-to.
- #
- # message_id:: Returns a string that represents the message-id.
- #
- class Envelope < Struct.new(:date, :subject, :from, :sender, :reply_to,
- :to, :cc, :bcc, :in_reply_to, :message_id)
- end
-
- #
- # Net::IMAP::Address represents electronic mail addresses.
- #
- # ==== Fields:
- #
- # name:: Returns the phrase from [RFC-822] mailbox.
- #
- # route:: Returns the route from [RFC-822] route-addr.
- #
- # mailbox:: nil indicates end of [RFC-822] group.
- # If non-nil and host is nil, returns [RFC-822] group name.
- # Otherwise, returns [RFC-822] local-part.
- #
- # host:: nil indicates [RFC-822] group syntax.
- # Otherwise, returns [RFC-822] domain name.
- #
- class Address < Struct.new(:name, :route, :mailbox, :host)
- end
-
- #
- # Net::IMAP::ContentDisposition represents Content-Disposition fields.
- #
- # ==== Fields:
- #
- # dsp_type:: Returns the disposition type.
- #
- # param:: Returns a hash that represents parameters of the Content-Disposition
- # field.
- #
- class ContentDisposition < Struct.new(:dsp_type, :param)
- end
-
- # Net::IMAP::ThreadMember represents a thread-node returned
- # by Net::IMAP#thread.
- #
- # ==== Fields:
- #
- # seqno:: The sequence number of this message.
- #
- # children:: An array of Net::IMAP::ThreadMember objects for mail
- # items that are children of this in the thread.
- #
- class ThreadMember < Struct.new(:seqno, :children)
- end
-
- # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
- #
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
- #
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
- #
- # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
- #
- # description:: Returns a string giving the content description as defined in
- # [MIME-IMB].
- #
- # encoding:: Returns a string giving the content transfer encoding as defined in
- # [MIME-IMB].
- #
- # size:: Returns a number giving the size of the body in octets.
- #
- # md5:: Returns a string giving the body MD5 value as defined in [MD5].
- #
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
- # the content disposition.
- #
- # language:: Returns a string or an array of strings giving the body
- # language value as defined in [LANGUAGE-TAGS].
- #
- # extension:: Returns extension data.
- #
- # multipart?:: Returns false.
- #
- class BodyTypeBasic < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
- #
- # ==== Fields:
- #
- # lines:: Returns the size of the body in text lines.
- #
- # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
- #
- class BodyTypeText < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :lines,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
- #
- # ==== Fields:
- #
- # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
- #
- # body:: Returns an object giving the body structure.
- #
- # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
- #
- class BodyTypeMessage < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :envelope, :body, :lines,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- # Net::IMAP::BodyTypeAttachment represents attachment body structures
- # of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name.
- #
- # subtype:: Returns +nil+.
- #
- # param:: Returns a hash that represents parameters.
- #
- # multipart?:: Returns false.
- #
- class BodyTypeAttachment < Struct.new(:media_type, :subtype,
- :param)
- def multipart?
- return false
- end
- end
-
- # Net::IMAP::BodyTypeMultipart represents multipart body structures
- # of messages.
- #
- # ==== Fields:
- #
- # media_type:: Returns the content media type name as defined in [MIME-IMB].
- #
- # subtype:: Returns the content subtype name as defined in [MIME-IMB].
- #
- # parts:: Returns multiple parts.
- #
- # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
- #
- # disposition:: Returns a Net::IMAP::ContentDisposition object giving
- # the content disposition.
- #
- # language:: Returns a string or an array of strings giving the body
- # language value as defined in [LANGUAGE-TAGS].
- #
- # extension:: Returns extension data.
- #
- # multipart?:: Returns true.
- #
- class BodyTypeMultipart < Struct.new(:media_type, :subtype,
- :parts,
- :param, :disposition, :language,
- :extension)
- def multipart?
- return true
- end
-
- # Obsolete: use +subtype+ instead. Calling this will
- # generate a warning message to +stderr+, then return
- # the value of +subtype+.
- def media_subtype
- warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1)
- return subtype
- end
- end
-
- class BodyTypeExtension < Struct.new(:media_type, :subtype,
- :params, :content_id,
- :description, :encoding, :size)
- def multipart?
- return false
- end
- end
-
- end
-end
diff --git a/lib/net/imap/response_parser.rb b/lib/net/imap/response_parser.rb
deleted file mode 100644
index 1e051327c6..0000000000
--- a/lib/net/imap/response_parser.rb
+++ /dev/null
@@ -1,1530 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- class IMAP < Protocol
-
- class ResponseParser # :nodoc:
- def initialize
- @str = nil
- @pos = nil
- @lex_state = nil
- @token = nil
- @flag_symbols = {}
- end
-
- def parse(str)
- @str = str
- @pos = 0
- @lex_state = EXPR_BEG
- @token = nil
- return response
- end
-
- private
-
- EXPR_BEG = :EXPR_BEG
- EXPR_DATA = :EXPR_DATA
- EXPR_TEXT = :EXPR_TEXT
- EXPR_RTEXT = :EXPR_RTEXT
- EXPR_CTEXT = :EXPR_CTEXT
-
- T_SPACE = :SPACE
- T_NIL = :NIL
- T_NUMBER = :NUMBER
- T_ATOM = :ATOM
- T_QUOTED = :QUOTED
- T_LPAR = :LPAR
- T_RPAR = :RPAR
- T_BSLASH = :BSLASH
- T_STAR = :STAR
- T_LBRA = :LBRA
- T_RBRA = :RBRA
- T_LITERAL = :LITERAL
- T_PLUS = :PLUS
- T_PERCENT = :PERCENT
- T_CRLF = :CRLF
- T_EOF = :EOF
- T_TEXT = :TEXT
-
- BEG_REGEXP = /\G(?:\
-(?# 1: SPACE )( +)|\
-(?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
-(?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
-(?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
-(?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
-(?# 6: LPAR )(\()|\
-(?# 7: RPAR )(\))|\
-(?# 8: BSLASH )(\\)|\
-(?# 9: STAR )(\*)|\
-(?# 10: LBRA )(\[)|\
-(?# 11: RBRA )(\])|\
-(?# 12: LITERAL )\{(\d+)\}\r\n|\
-(?# 13: PLUS )(\+)|\
-(?# 14: PERCENT )(%)|\
-(?# 15: CRLF )(\r\n)|\
-(?# 16: EOF )(\z))/ni
-
- DATA_REGEXP = /\G(?:\
-(?# 1: SPACE )( )|\
-(?# 2: NIL )(NIL)|\
-(?# 3: NUMBER )(\d+)|\
-(?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
-(?# 5: LITERAL )\{(\d+)\}\r\n|\
-(?# 6: LPAR )(\()|\
-(?# 7: RPAR )(\)))/ni
-
- TEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\r\n]*))/ni
-
- RTEXT_REGEXP = /\G(?:\
-(?# 1: LBRA )(\[)|\
-(?# 2: TEXT )([^\x00\r\n]*))/ni
-
- CTEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\r\n\]]*))/ni
-
- Token = Struct.new(:symbol, :value)
-
- def response
- token = lookahead
- case token.symbol
- when T_PLUS
- result = continue_req
- when T_STAR
- result = response_untagged
- else
- result = response_tagged
- end
- while lookahead.symbol == T_SPACE
- # Ignore trailing space for Microsoft Exchange Server
- shift_token
- end
- match(T_CRLF)
- match(T_EOF)
- return result
- end
-
- def continue_req
- match(T_PLUS)
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- return ContinuationRequest.new(resp_text, @str)
- else
- return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
- end
- end
-
- def response_untagged
- match(T_STAR)
- match(T_SPACE)
- token = lookahead
- if token.symbol == T_NUMBER
- return numeric_response
- elsif token.symbol == T_ATOM
- case token.value
- when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
- return response_cond
- when /\A(?:FLAGS)\z/ni
- return flags_response
- when /\A(?:ID)\z/ni
- return id_response
- when /\A(?:LIST|LSUB|XLIST)\z/ni
- return list_response
- when /\A(?:NAMESPACE)\z/ni
- return namespace_response
- when /\A(?:QUOTA)\z/ni
- return getquota_response
- when /\A(?:QUOTAROOT)\z/ni
- return getquotaroot_response
- when /\A(?:ACL)\z/ni
- return getacl_response
- when /\A(?:SEARCH|SORT)\z/ni
- return search_response
- when /\A(?:THREAD)\z/ni
- return thread_response
- when /\A(?:STATUS)\z/ni
- return status_response
- when /\A(?:CAPABILITY)\z/ni
- return capability_response
- when /\A(?:NOOP)\z/ni
- return ignored_response
- else
- return text_response
- end
- else
- parse_error("unexpected token %s", token.symbol)
- end
- end
-
- def response_tagged
- tag = astring_chars
- match(T_SPACE)
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return TaggedResponse.new(tag, name, resp_text, @str)
- end
-
- def response_cond
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, resp_text, @str)
- end
-
- def numeric_response
- n = number
- match(T_SPACE)
- token = match(T_ATOM)
- name = token.value.upcase
- case name
- when "EXISTS", "RECENT", "EXPUNGE"
- return UntaggedResponse.new(name, n, @str)
- when "FETCH"
- shift_token
- match(T_SPACE)
- data = FetchData.new(n, msg_att(n))
- return UntaggedResponse.new(name, data, @str)
- end
- end
-
- def msg_att(n)
- match(T_LPAR)
- attr = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- next
- end
- case token.value
- when /\A(?:ENVELOPE)\z/ni
- name, val = envelope_data
- when /\A(?:FLAGS)\z/ni
- name, val = flags_data
- when /\A(?:INTERNALDATE)\z/ni
- name, val = internaldate_data
- when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
- name, val = rfc822_text
- when /\A(?:RFC822\.SIZE)\z/ni
- name, val = rfc822_size
- when /\A(?:BODY(?:STRUCTURE)?)\z/ni
- name, val = body_data
- when /\A(?:UID)\z/ni
- name, val = uid_data
- when /\A(?:MODSEQ)\z/ni
- name, val = modseq_data
- else
- parse_error("unknown attribute `%s' for {%d}", token.value, n)
- end
- attr[name] = val
- end
- return attr
- end
-
- def envelope_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, envelope
- end
-
- def envelope
- @lex_state = EXPR_DATA
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- result = nil
- else
- match(T_LPAR)
- date = nstring
- match(T_SPACE)
- subject = nstring
- match(T_SPACE)
- from = address_list
- match(T_SPACE)
- sender = address_list
- match(T_SPACE)
- reply_to = address_list
- match(T_SPACE)
- to = address_list
- match(T_SPACE)
- cc = address_list
- match(T_SPACE)
- bcc = address_list
- match(T_SPACE)
- in_reply_to = nstring
- match(T_SPACE)
- message_id = nstring
- match(T_RPAR)
- result = Envelope.new(date, subject, from, sender, reply_to,
- to, cc, bcc, in_reply_to, message_id)
- end
- @lex_state = EXPR_BEG
- return result
- end
-
- def flags_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, flag_list
- end
-
- def internaldate_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- token = match(T_QUOTED)
- return name, token.value
- end
-
- def rfc822_text
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_LBRA
- shift_token
- match(T_RBRA)
- end
- match(T_SPACE)
- return name, nstring
- end
-
- def rfc822_size
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, number
- end
-
- def body_data
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- return name, body
- end
- name.concat(section)
- token = lookahead
- if token.symbol == T_ATOM
- name.concat(token.value)
- shift_token
- end
- match(T_SPACE)
- data = nstring
- return name, data
- end
-
- def body
- @lex_state = EXPR_DATA
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- result = nil
- else
- match(T_LPAR)
- token = lookahead
- if token.symbol == T_LPAR
- result = body_type_mpart
- else
- result = body_type_1part
- end
- match(T_RPAR)
- end
- @lex_state = EXPR_BEG
- return result
- end
-
- def body_type_1part
- token = lookahead
- case token.value
- when /\A(?:TEXT)\z/ni
- return body_type_text
- when /\A(?:MESSAGE)\z/ni
- return body_type_msg
- when /\A(?:ATTACHMENT)\z/ni
- return body_type_attachment
- when /\A(?:MIXED)\z/ni
- return body_type_mixed
- else
- return body_type_basic
- end
- end
-
- def body_type_basic
- mtype, msubtype = media_type
- token = lookahead
- if token.symbol == T_RPAR
- return BodyTypeBasic.new(mtype, msubtype)
- end
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeBasic.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- md5, disposition, language, extension)
- end
-
- def body_type_text
- mtype, msubtype = media_type
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
- match(T_SPACE)
- lines = number
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeText.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- lines,
- md5, disposition, language, extension)
- end
-
- def body_type_msg
- mtype, msubtype = media_type
- match(T_SPACE)
- param, content_id, desc, enc, size = body_fields
-
- token = lookahead
- if token.symbol == T_RPAR
- # If this is not message/rfc822, we shouldn't apply the RFC822
- # spec to it. We should handle anything other than
- # message/rfc822 using multipart extension data [rfc3501] (i.e.
- # the data itself won't be returned, we would have to retrieve it
- # with BODYSTRUCTURE instead of with BODY
-
- # Also, sometimes a message/rfc822 is included as a large
- # attachment instead of having all of the other details
- # (e.g. attaching a .eml file to an email)
- if msubtype == "RFC822"
- return BodyTypeMessage.new(mtype, msubtype, param, content_id,
- desc, enc, size, nil, nil, nil, nil,
- nil, nil, nil)
- else
- return BodyTypeExtension.new(mtype, msubtype,
- param, content_id,
- desc, enc, size)
- end
- end
-
- match(T_SPACE)
- env = envelope
- match(T_SPACE)
- b = body
- match(T_SPACE)
- lines = number
- md5, disposition, language, extension = body_ext_1part
- return BodyTypeMessage.new(mtype, msubtype,
- param, content_id,
- desc, enc, size,
- env, b, lines,
- md5, disposition, language, extension)
- end
-
- def body_type_attachment
- mtype = case_insensitive_string
- match(T_SPACE)
- param = body_fld_param
- return BodyTypeAttachment.new(mtype, nil, param)
- end
-
- def body_type_mixed
- mtype = "MULTIPART"
- msubtype = case_insensitive_string
- param, disposition, language, extension = body_ext_mpart
- return BodyTypeBasic.new(mtype, msubtype, param, nil, nil, nil, nil, nil, disposition, language, extension)
- end
-
- def body_type_mpart
- parts = []
- while true
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- break
- end
- parts.push(body)
- end
- mtype = "MULTIPART"
- msubtype = case_insensitive_string
- param, disposition, language, extension = body_ext_mpart
- return BodyTypeMultipart.new(mtype, msubtype, parts,
- param, disposition, language,
- extension)
- end
-
- def media_type
- mtype = case_insensitive_string
- token = lookahead
- if token.symbol != T_SPACE
- return mtype, nil
- end
- match(T_SPACE)
- msubtype = case_insensitive_string
- return mtype, msubtype
- end
-
- def body_fields
- param = body_fld_param
- match(T_SPACE)
- content_id = nstring
- match(T_SPACE)
- desc = nstring
- match(T_SPACE)
- enc = case_insensitive_string
- match(T_SPACE)
- size = number
- return param, content_id, desc, enc, size
- end
-
- def body_fld_param
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- match(T_LPAR)
- param = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- name = case_insensitive_string
- match(T_SPACE)
- val = string
- param[name] = val
- end
- return param
- end
-
- def body_ext_1part
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return nil
- end
- md5 = nstring
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5
- end
- disposition = body_fld_dsp
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5, disposition
- end
- language = body_fld_lang
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return md5, disposition, language
- end
-
- extension = body_extensions
- return md5, disposition, language, extension
- end
-
- def body_ext_mpart
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return nil
- end
- param = body_fld_param
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param
- end
- disposition = body_fld_dsp
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param, disposition
- end
- language = body_fld_lang
-
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- else
- return param, disposition, language
- end
-
- extension = body_extensions
- return param, disposition, language, extension
- end
-
- def body_fld_dsp
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- match(T_LPAR)
- dsp_type = case_insensitive_string
- match(T_SPACE)
- param = body_fld_param
- match(T_RPAR)
- return ContentDisposition.new(dsp_type, param)
- end
-
- def body_fld_lang
- token = lookahead
- if token.symbol == T_LPAR
- shift_token
- result = []
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- return result
- when T_SPACE
- shift_token
- end
- result.push(case_insensitive_string)
- end
- else
- lang = nstring
- if lang
- return lang.upcase
- else
- return lang
- end
- end
- end
-
- def body_extensions
- result = []
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- return result
- when T_SPACE
- shift_token
- end
- result.push(body_extension)
- end
- end
-
- def body_extension
- token = lookahead
- case token.symbol
- when T_LPAR
- shift_token
- result = body_extensions
- match(T_RPAR)
- return result
- when T_NUMBER
- return number
- else
- return nstring
- end
- end
-
- def section
- str = String.new
- token = match(T_LBRA)
- str.concat(token.value)
- token = match(T_ATOM, T_NUMBER, T_RBRA)
- if token.symbol == T_RBRA
- str.concat(token.value)
- return str
- end
- str.concat(token.value)
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- str.concat(token.value)
- token = match(T_LPAR)
- str.concat(token.value)
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- str.concat(token.value)
- shift_token
- break
- when T_SPACE
- shift_token
- str.concat(token.value)
- end
- str.concat(format_string(astring))
- end
- end
- token = match(T_RBRA)
- str.concat(token.value)
- return str
- end
-
- def format_string(str)
- case str
- when ""
- return '""'
- when /[\x80-\xff\r\n]/n
- # literal
- return "{" + str.bytesize.to_s + "}" + CRLF + str
- when /[(){ \x00-\x1f\x7f%*"\\]/n
- # quoted string
- return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
- else
- # atom
- return str
- end
- end
-
- def uid_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return name, number
- end
-
- def modseq_data
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- match(T_LPAR)
- modseq = number
- match(T_RPAR)
- return name, modseq
- end
-
- def ignored_response
- while lookahead.symbol != T_CRLF
- shift_token
- end
- return IgnoredResponse.new(@str)
- end
-
- def text_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, text)
- end
-
- def flags_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, flag_list, @str)
- end
-
- def list_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- return UntaggedResponse.new(name, mailbox_list, @str)
- end
-
- def mailbox_list
- attr = flag_list
- match(T_SPACE)
- token = match(T_QUOTED, T_NIL)
- if token.symbol == T_NIL
- delim = nil
- else
- delim = token.value
- end
- match(T_SPACE)
- name = astring
- return MailboxList.new(attr, delim, name)
- end
-
- def getquota_response
- # If quota never established, get back
- # `NO Quota root does not exist'.
- # If quota removed, get `()' after the
- # folder spec with no mention of `STORAGE'.
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- match(T_SPACE)
- match(T_LPAR)
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- data = MailboxQuota.new(mailbox, nil, nil)
- return UntaggedResponse.new(name, data, @str)
- when T_ATOM
- shift_token
- match(T_SPACE)
- token = match(T_NUMBER)
- usage = token.value
- match(T_SPACE)
- token = match(T_NUMBER)
- quota = token.value
- match(T_RPAR)
- data = MailboxQuota.new(mailbox, usage, quota)
- return UntaggedResponse.new(name, data, @str)
- else
- parse_error("unexpected token %s", token.symbol)
- end
- end
-
- def getquotaroot_response
- # Similar to getquota, but only admin can use getquota.
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- quotaroots = []
- while true
- token = lookahead
- break unless token.symbol == T_SPACE
- shift_token
- quotaroots.push(astring)
- end
- data = MailboxQuotaRoot.new(mailbox, quotaroots)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def getacl_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- data = []
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- while true
- token = lookahead
- case token.symbol
- when T_CRLF
- break
- when T_SPACE
- shift_token
- end
- user = astring
- match(T_SPACE)
- rights = astring
- data.push(MailboxACLItem.new(user, rights, mailbox))
- end
- end
- return UntaggedResponse.new(name, data, @str)
- end
-
- def search_response
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- data = []
- while true
- token = lookahead
- case token.symbol
- when T_CRLF
- break
- when T_SPACE
- shift_token
- when T_NUMBER
- data.push(number)
- when T_LPAR
- # TODO: include the MODSEQ value in a response
- shift_token
- match(T_ATOM)
- match(T_SPACE)
- match(T_NUMBER)
- match(T_RPAR)
- end
- end
- else
- data = []
- end
- return UntaggedResponse.new(name, data, @str)
- end
-
- def thread_response
- token = match(T_ATOM)
- name = token.value.upcase
- token = lookahead
-
- if token.symbol == T_SPACE
- threads = []
-
- while true
- shift_token
- token = lookahead
-
- case token.symbol
- when T_LPAR
- threads << thread_branch(token)
- when T_CRLF
- break
- end
- end
- else
- # no member
- threads = []
- end
-
- return UntaggedResponse.new(name, threads, @str)
- end
-
- def thread_branch(token)
- rootmember = nil
- lastmember = nil
-
- while true
- shift_token # ignore first T_LPAR
- token = lookahead
-
- case token.symbol
- when T_NUMBER
- # new member
- newmember = ThreadMember.new(number, [])
- if rootmember.nil?
- rootmember = newmember
- else
- lastmember.children << newmember
- end
- lastmember = newmember
- when T_SPACE
- # do nothing
- when T_LPAR
- if rootmember.nil?
- # dummy member
- lastmember = rootmember = ThreadMember.new(nil, [])
- end
-
- lastmember.children << thread_branch(token)
- when T_RPAR
- break
- end
- end
-
- return rootmember
- end
-
- def status_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- mailbox = astring
- match(T_SPACE)
- match(T_LPAR)
- attr = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- token = match(T_ATOM)
- key = token.value.upcase
- match(T_SPACE)
- val = number
- attr[key] = val
- end
- data = StatusData.new(mailbox, attr)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def capability_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- UntaggedResponse.new(name, capability_data, @str)
- end
-
- def capability_data
- data = []
- while true
- token = lookahead
- case token.symbol
- when T_CRLF, T_RBRA
- break
- when T_SPACE
- shift_token
- next
- end
- data.push(atom.upcase)
- end
- data
- end
-
- def id_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- token = match(T_LPAR, T_NIL)
- if token.symbol == T_NIL
- return UntaggedResponse.new(name, nil, @str)
- else
- data = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- next
- else
- key = string
- match(T_SPACE)
- val = nstring
- data[key] = val
- end
- end
- return UntaggedResponse.new(name, data, @str)
- end
- end
-
- def namespace_response
- @lex_state = EXPR_DATA
- token = lookahead
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- personal = namespaces
- match(T_SPACE)
- other = namespaces
- match(T_SPACE)
- shared = namespaces
- @lex_state = EXPR_BEG
- data = Namespaces.new(personal, other, shared)
- return UntaggedResponse.new(name, data, @str)
- end
-
- def namespaces
- token = lookahead
- # empty () is not allowed, so nil is functionally identical to empty.
- data = []
- if token.symbol == T_NIL
- shift_token
- else
- match(T_LPAR)
- loop do
- data << namespace
- break unless lookahead.symbol == T_SPACE
- shift_token
- end
- match(T_RPAR)
- end
- data
- end
-
- def namespace
- match(T_LPAR)
- prefix = match(T_QUOTED, T_LITERAL).value
- match(T_SPACE)
- delimiter = string
- extensions = namespace_response_extensions
- match(T_RPAR)
- Namespace.new(prefix, delimiter, extensions)
- end
-
- def namespace_response_extensions
- data = {}
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- name = match(T_QUOTED, T_LITERAL).value
- data[name] ||= []
- match(T_SPACE)
- match(T_LPAR)
- loop do
- data[name].push match(T_QUOTED, T_LITERAL).value
- break unless lookahead.symbol == T_SPACE
- shift_token
- end
- match(T_RPAR)
- end
- data
- end
-
- # text = 1*TEXT-CHAR
- # TEXT-CHAR = <any CHAR except CR and LF>
- def text
- match(T_TEXT, lex_state: EXPR_TEXT).value
- end
-
- # resp-text = ["[" resp-text-code "]" SP] text
- def resp_text
- token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT)
- case token.symbol
- when T_LBRA
- code = resp_text_code
- match(T_RBRA)
- accept_space # violating RFC
- ResponseText.new(code, text)
- when T_TEXT
- ResponseText.new(nil, token.value)
- end
- end
-
- # See https://www.rfc-editor.org/errata/rfc3501
- #
- # resp-text-code = "ALERT" /
- # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
- # capability-data / "PARSE" /
- # "PERMANENTFLAGS" SP "("
- # [flag-perm *(SP flag-perm)] ")" /
- # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
- # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
- # "UNSEEN" SP nz-number /
- # atom [SP 1*<any TEXT-CHAR except "]">]
- def resp_text_code
- token = match(T_ATOM)
- name = token.value.upcase
- case name
- when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
- result = ResponseCode.new(name, nil)
- when /\A(?:BADCHARSET)\z/n
- result = ResponseCode.new(name, charset_list)
- when /\A(?:CAPABILITY)\z/ni
- result = ResponseCode.new(name, capability_data)
- when /\A(?:PERMANENTFLAGS)\z/n
- match(T_SPACE)
- result = ResponseCode.new(name, flag_list)
- when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
- match(T_SPACE)
- result = ResponseCode.new(name, number)
- else
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- token = match(T_TEXT, lex_state: EXPR_CTEXT)
- result = ResponseCode.new(name, token.value)
- else
- result = ResponseCode.new(name, nil)
- end
- end
- return result
- end
-
- def charset_list
- result = []
- if accept(T_SPACE)
- match(T_LPAR)
- result << charset
- while accept(T_SPACE)
- result << charset
- end
- match(T_RPAR)
- end
- result
- end
-
- def address_list
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- else
- result = []
- match(T_LPAR)
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- end
- result.push(address)
- end
- return result
- end
- end
-
- ADDRESS_REGEXP = /\G\
-(?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
-(?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
-\)/ni
-
- def address
- match(T_LPAR)
- if @str.index(ADDRESS_REGEXP, @pos)
- # address does not include literal.
- @pos = $~.end(0)
- name = $1
- route = $2
- mailbox = $3
- host = $4
- for s in [name, route, mailbox, host]
- if s
- s.gsub!(/\\(["\\])/n, "\\1")
- end
- end
- else
- name = nstring
- match(T_SPACE)
- route = nstring
- match(T_SPACE)
- mailbox = nstring
- match(T_SPACE)
- host = nstring
- match(T_RPAR)
- end
- return Address.new(name, route, mailbox, host)
- end
-
- FLAG_REGEXP = /\
-(?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
-(?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
-
- def flag_list
- if @str.index(/\(([^)]*)\)/ni, @pos)
- @pos = $~.end(0)
- return $1.scan(FLAG_REGEXP).collect { |flag, atom|
- if atom
- atom
- else
- symbol = flag.capitalize.intern
- @flag_symbols[symbol] = true
- if @flag_symbols.length > IMAP.max_flag_count
- raise FlagCountError, "number of flag symbols exceeded"
- end
- symbol
- end
- }
- else
- parse_error("invalid flag list")
- end
- end
-
- def nstring
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- else
- return string
- end
- end
-
- def astring
- token = lookahead
- if string_token?(token)
- return string
- else
- return astring_chars
- end
- end
-
- def string
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_QUOTED, T_LITERAL)
- return token.value
- end
-
- STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
-
- def string_token?(token)
- return STRING_TOKENS.include?(token.symbol)
- end
-
- def case_insensitive_string
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_QUOTED, T_LITERAL)
- return token.value.upcase
- end
-
- # atom = 1*ATOM-CHAR
- # ATOM-CHAR = <any CHAR except atom-specials>
- ATOM_TOKENS = [
- T_ATOM,
- T_NUMBER,
- T_NIL,
- T_LBRA,
- T_PLUS
- ]
-
- def atom
- -combine_adjacent(*ATOM_TOKENS)
- end
-
- # ASTRING-CHAR = ATOM-CHAR / resp-specials
- # resp-specials = "]"
- ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA]
-
- def astring_chars
- combine_adjacent(*ASTRING_CHARS_TOKENS)
- end
-
- def combine_adjacent(*tokens)
- result = "".b
- while token = accept(*tokens)
- result << token.value
- end
- if result.empty?
- parse_error('unexpected token %s (expected %s)',
- lookahead.symbol, args.join(" or "))
- end
- result
- end
-
- # See https://www.rfc-editor.org/errata/rfc3501
- #
- # charset = atom / quoted
- def charset
- if token = accept(T_QUOTED)
- token.value
- else
- atom
- end
- end
-
- def number
- token = lookahead
- if token.symbol == T_NIL
- shift_token
- return nil
- end
- token = match(T_NUMBER)
- return token.value.to_i
- end
-
- def nil_atom
- match(T_NIL)
- return nil
- end
-
- SPACES_REGEXP = /\G */n
-
- # This advances @pos directly so it's safe before changing @lex_state.
- def accept_space
- if @token
- shift_token if @token.symbol == T_SPACE
- elsif @str[@pos] == " "
- @pos += 1
- end
- end
-
- # The RFC is very strict about this and usually we should be too.
- # But skipping spaces is usually a safe workaround for buggy servers.
- #
- # This advances @pos directly so it's safe before changing @lex_state.
- def accept_spaces
- shift_token if @token&.symbol == T_SPACE
- if @str.index(SPACES_REGEXP, @pos)
- @pos = $~.end(0)
- end
- end
-
- def match(*args, lex_state: @lex_state)
- if @token && lex_state != @lex_state
- parse_error("invalid lex_state change to %s with unconsumed token",
- lex_state)
- end
- begin
- @lex_state, original_lex_state = lex_state, @lex_state
- token = lookahead
- unless args.include?(token.symbol)
- parse_error('unexpected token %s (expected %s)',
- token.symbol.id2name,
- args.collect {|i| i.id2name}.join(" or "))
- end
- shift_token
- return token
- ensure
- @lex_state = original_lex_state
- end
- end
-
- # like match, but does not raise error on failure.
- #
- # returns and shifts token on successful match
- # returns nil and leaves @token unshifted on no match
- def accept(*args)
- token = lookahead
- if args.include?(token.symbol)
- shift_token
- token
- end
- end
-
- def lookahead
- @token ||= next_token
- end
-
- def shift_token
- @token = nil
- end
-
- def next_token
- case @lex_state
- when EXPR_BEG
- if @str.index(BEG_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_SPACE, $+)
- elsif $2
- return Token.new(T_NIL, $+)
- elsif $3
- return Token.new(T_NUMBER, $+)
- elsif $4
- return Token.new(T_ATOM, $+)
- elsif $5
- return Token.new(T_QUOTED,
- $+.gsub(/\\(["\\])/n, "\\1"))
- elsif $6
- return Token.new(T_LPAR, $+)
- elsif $7
- return Token.new(T_RPAR, $+)
- elsif $8
- return Token.new(T_BSLASH, $+)
- elsif $9
- return Token.new(T_STAR, $+)
- elsif $10
- return Token.new(T_LBRA, $+)
- elsif $11
- return Token.new(T_RBRA, $+)
- elsif $12
- len = $+.to_i
- val = @str[@pos, len]
- @pos += len
- return Token.new(T_LITERAL, val)
- elsif $13
- return Token.new(T_PLUS, $+)
- elsif $14
- return Token.new(T_PERCENT, $+)
- elsif $15
- return Token.new(T_CRLF, $+)
- elsif $16
- return Token.new(T_EOF, $+)
- else
- parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_DATA
- if @str.index(DATA_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_SPACE, $+)
- elsif $2
- return Token.new(T_NIL, $+)
- elsif $3
- return Token.new(T_NUMBER, $+)
- elsif $4
- return Token.new(T_QUOTED,
- $+.gsub(/\\(["\\])/n, "\\1"))
- elsif $5
- len = $+.to_i
- val = @str[@pos, len]
- @pos += len
- return Token.new(T_LITERAL, val)
- elsif $6
- return Token.new(T_LPAR, $+)
- elsif $7
- return Token.new(T_RPAR, $+)
- else
- parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_TEXT
- if @str.index(TEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_RTEXT
- if @str.index(RTEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_LBRA, $+)
- elsif $2
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos)
- parse_error("unknown token - %s", $&.dump)
- end
- when EXPR_CTEXT
- if @str.index(CTEXT_REGEXP, @pos)
- @pos = $~.end(0)
- if $1
- return Token.new(T_TEXT, $+)
- else
- parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
- end
- else
- @str.index(/\S*/n, @pos) #/
- parse_error("unknown token - %s", $&.dump)
- end
- else
- parse_error("invalid @lex_state - %s", @lex_state.inspect)
- end
- end
-
- def parse_error(fmt, *args)
- if IMAP.debug
- $stderr.printf("@str: %s\n", @str.dump)
- $stderr.printf("@pos: %d\n", @pos)
- $stderr.printf("@lex_state: %s\n", @lex_state)
- if @token
- $stderr.printf("@token.symbol: %s\n", @token.symbol)
- $stderr.printf("@token.value: %s\n", @token.value.inspect)
- end
- end
- raise ResponseParseError, format(fmt, *args)
- end
- end
-
- end
-
-end
diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb
index 3c5842b9a0..d89d9ab635 100755
--- a/misc/expand_tabs.rb
+++ b/misc/expand_tabs.rb
@@ -80,7 +80,6 @@ DEFAULT_GEM_LIBS = %w[
logger
mutex_m
net-http
- net-imap
net-pop
net-protocol
net-smtp
diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb
deleted file mode 100644
index 46008b0531..0000000000
--- a/test/net/imap/test_imap.rb
+++ /dev/null
@@ -1,856 +0,0 @@
-# frozen_string_literal: true
-
-require "net/imap"
-require "test/unit"
-
-class IMAPTest < Test::Unit::TestCase
- CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__)
- SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__)
- SERVER_CERT = File.expand_path("../fixtures/server.crt", __dir__)
-
- def setup
- @do_not_reverse_lookup = Socket.do_not_reverse_lookup
- Socket.do_not_reverse_lookup = true
- @threads = []
- end
-
- def teardown
- if !@threads.empty?
- assert_join_threads(@threads)
- end
- ensure
- Socket.do_not_reverse_lookup = @do_not_reverse_lookup
- end
-
- if defined?(OpenSSL::SSL::SSLError)
- def test_imaps_unknown_ca
- assert_raise(OpenSSL::SSL::SSLError) do
- imaps_test do |port|
- begin
- Net::IMAP.new("localhost",
- :port => port,
- :ssl => true)
- rescue SystemCallError
- skip $!
- end
- end
- end
- end
-
- def test_imaps_with_ca_file
- assert_nothing_raised do
- imaps_test do |port|
- begin
- Net::IMAP.new("localhost",
- :port => port,
- :ssl => { :ca_file => CA_FILE })
- rescue SystemCallError
- skip $!
- end
- end
- end
- end
-
- def test_imaps_verify_none
- assert_nothing_raised do
- imaps_test do |port|
- Net::IMAP.new(server_addr,
- :port => port,
- :ssl => { :verify_mode => OpenSSL::SSL::VERIFY_NONE })
- end
- end
- end
-
- def test_imaps_post_connection_check
- assert_raise(OpenSSL::SSL::SSLError) do
- imaps_test do |port|
- # server_addr is different from the hostname in the certificate,
- # so the following code should raise a SSLError.
- Net::IMAP.new(server_addr,
- :port => port,
- :ssl => { :ca_file => CA_FILE })
- end
- end
- end
- end
-
- if defined?(OpenSSL::SSL)
- def test_starttls
- imap = nil
- starttls_test do |port|
- imap = Net::IMAP.new("localhost", :port => port)
- imap.starttls(:ca_file => CA_FILE)
- imap
- end
- rescue SystemCallError
- skip $!
- ensure
- if imap && !imap.disconnected?
- imap.disconnect
- end
- end
- end
-
- def start_server
- th = Thread.new do
- yield
- end
- @threads << th
- sleep 0.1 until th.stop?
- end
-
- def test_unexpected_eof
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
-# sock.print("* BYE terminating connection\r\n")
-# sock.print("RUBY0001 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- assert_raise(EOFError) do
- imap.logout
- end
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_idle
- server = create_tcp_server
- port = server.addr[1]
- requests = []
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- sock.print("+ idling\r\n")
- sock.print("* 3 EXISTS\r\n")
- sock.print("* 2 EXPUNGE\r\n")
- requests.push(sock.gets)
- sock.print("RUBY0001 OK IDLE terminated\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
-
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- responses = []
- imap.idle do |res|
- responses.push(res)
- if res.name == "EXPUNGE"
- imap.idle_done
- end
- end
- assert_equal(3, responses.length)
- assert_instance_of(Net::IMAP::ContinuationRequest, responses[0])
- assert_equal("EXISTS", responses[1].name)
- assert_equal(3, responses[1].data)
- assert_equal("EXPUNGE", responses[2].name)
- assert_equal(2, responses[2].data)
- assert_equal(2, requests.length)
- assert_equal("RUBY0001 IDLE\r\n", requests[0])
- assert_equal("DONE\r\n", requests[1])
- imap.logout
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_exception_during_idle
- server = create_tcp_server
- port = server.addr[1]
- requests = []
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- sock.print("+ idling\r\n")
- sock.print("* 3 EXISTS\r\n")
- sock.print("* 2 EXPUNGE\r\n")
- requests.push(sock.gets)
- sock.print("RUBY0001 OK IDLE terminated\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- begin
- th = Thread.current
- m = Monitor.new
- in_idle = false
- exception_raised = false
- c = m.new_cond
- raiser = Thread.start do
- m.synchronize do
- until in_idle
- c.wait(0.1)
- end
- end
- th.raise(Interrupt)
- m.synchronize do
- exception_raised = true
- c.signal
- end
- end
- @threads << raiser
- imap.idle do |res|
- m.synchronize do
- in_idle = true
- c.signal
- until exception_raised
- c.wait(0.1)
- end
- end
- end
- rescue Interrupt
- end
- assert_equal(2, requests.length)
- assert_equal("RUBY0001 IDLE\r\n", requests[0])
- assert_equal("DONE\r\n", requests[1])
- imap.logout
- ensure
- imap.disconnect if imap
- raiser.kill unless in_idle
- end
- end
-
- def test_idle_done_not_during_idle
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sleep 0.1
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- assert_raise(Net::IMAP::Error) do
- imap.idle_done
- end
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_idle_timeout
- server = create_tcp_server
- port = server.addr[1]
- requests = []
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- sock.print("+ idling\r\n")
- sock.print("* 3 EXISTS\r\n")
- sock.print("* 2 EXPUNGE\r\n")
- requests.push(sock.gets)
- sock.print("RUBY0001 OK IDLE terminated\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
-
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- responses = []
- Thread.pass
- imap.idle(0.2) do |res|
- responses.push(res)
- end
- # There is no guarantee that this thread has received all the responses,
- # so check the response length.
- if responses.length > 0
- assert_instance_of(Net::IMAP::ContinuationRequest, responses[0])
- if responses.length > 1
- assert_equal("EXISTS", responses[1].name)
- assert_equal(3, responses[1].data)
- if responses.length > 2
- assert_equal("EXPUNGE", responses[2].name)
- assert_equal(2, responses[2].data)
- end
- end
- end
- # Also, there is no guarantee that the server thread has stored
- # all the requests into the array, so check the length.
- if requests.length > 0
- assert_equal("RUBY0001 IDLE\r\n", requests[0])
- if requests.length > 1
- assert_equal("DONE\r\n", requests[1])
- end
- end
- imap.logout
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_unexpected_bye
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK Gimap ready for requests from 75.101.246.151 33if2752585qyk.26\r\n")
- sock.gets
- sock.print("* BYE System Error 33if2752585qyk.26\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- assert_raise(Net::IMAP::ByeResponseError) do
- imap.login("user", "password")
- end
- end
- end
-
- def test_exception_during_shutdown
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0001 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- imap.instance_eval do
- def @sock.shutdown(*args)
- super
- ensure
- raise "error"
- end
- end
- imap.logout
- ensure
- assert_raise(RuntimeError) do
- imap.disconnect
- end
- end
- end
-
- def test_connection_closed_during_idle
- server = create_tcp_server
- port = server.addr[1]
- requests = []
- sock = nil
- threads = []
- started = false
- threads << Thread.start do
- started = true
- begin
- sock = server.accept
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- sock.print("+ idling\r\n")
- rescue IOError # sock is closed by another thread
- ensure
- server.close
- end
- end
- sleep 0.1 until started
- threads << Thread.start do
- imap = Net::IMAP.new(server_addr, :port => port)
- begin
- m = Monitor.new
- in_idle = false
- closed = false
- c = m.new_cond
- threads << Thread.start do
- m.synchronize do
- until in_idle
- c.wait(0.1)
- end
- end
- sock.close
- m.synchronize do
- closed = true
- c.signal
- end
- end
- assert_raise(EOFError) do
- imap.idle do |res|
- m.synchronize do
- in_idle = true
- c.signal
- until closed
- c.wait(0.1)
- end
- end
- end
- end
- assert_equal(1, requests.length)
- assert_equal("RUBY0001 IDLE\r\n", requests[0])
- ensure
- imap.disconnect if imap
- end
- end
- assert_join_threads(threads)
- ensure
- if sock && !sock.closed?
- sock.close
- end
- end
-
- def test_connection_closed_without_greeting
- server = create_tcp_server
- port = server.addr[1]
- h = {
- server: server,
- port: port,
- server_created: {
- server: server.inspect,
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- }
- net_imap = Class.new(Net::IMAP) do
- @@h = h
- def tcp_socket(host, port)
- @@h[:in_tcp_socket] = {
- host: host,
- port: port,
- server: @@h[:server].inspect,
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- #super
- s = Socket.tcp(host, port, :connect_timeout => @open_timeout)
- @@h[:in_tcp_socket_2] = {
- s: s.inspect,
- local_address: s.local_address,
- remote_address: s.remote_address,
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true)
- s
- end
- end
- start_server do
- begin
- h[:in_start_server_before_accept] = {
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- sock = server.accept
- h[:in_start_server] = {
- sock_addr: sock.addr,
- sock_peeraddr: sock.peeraddr,
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- sockets: ObjectSpace.each_object(BasicSocket).map{|s| [s.inspect, connect_address: (s.connect_address rescue nil).inspect, local_address: (s.local_address rescue nil).inspect, remote_address: (s.remote_address rescue nil).inspect] },
- }
- sock.close
- h[:in_start_server_sock_closed] = {
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- ensure
- server.close
- end
- end
- assert_raise(Net::IMAP::Error) do
- #Net::IMAP.new(server_addr, :port => port)
- if true
- net_imap.new(server_addr, :port => port)
- else
- # for testing debug print
- begin
- net_imap.new(server_addr, :port => port)
- rescue Net::IMAP::Error
- raise Errno::EINVAL
- end
- end
- rescue SystemCallError => e # for debug on OpenCSW
- h[:in_rescue] = {
- e: e,
- server_addr: server_addr,
- t: Process.clock_gettime(Process::CLOCK_MONOTONIC),
- }
- require 'pp'
- raise(PP.pp(h, +''))
- end
- end
-
- def test_default_port
- assert_equal(143, Net::IMAP.default_port)
- assert_equal(143, Net::IMAP.default_imap_port)
- assert_equal(993, Net::IMAP.default_tls_port)
- assert_equal(993, Net::IMAP.default_ssl_port)
- assert_equal(993, Net::IMAP.default_imaps_port)
- end
-
- def test_send_invalid_number
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
- sock.print("RUBY0001 OK TEST completed\r\n")
- sock.gets
- sock.print("RUBY0002 OK TEST completed\r\n")
- sock.gets
- sock.print("RUBY0003 OK TEST completed\r\n")
- sock.gets
- sock.print("RUBY0004 OK TEST completed\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0005 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- assert_raise(Net::IMAP::DataFormatError) do
- imap.__send__(:send_command, "TEST", -1)
- end
- imap.__send__(:send_command, "TEST", 0)
- imap.__send__(:send_command, "TEST", 4294967295)
- assert_raise(Net::IMAP::DataFormatError) do
- imap.__send__(:send_command, "TEST", 4294967296)
- end
- assert_raise(Net::IMAP::DataFormatError) do
- imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(-1))
- end
- assert_raise(Net::IMAP::DataFormatError) do
- imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(0))
- end
- imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(1))
- imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295))
- assert_raise(Net::IMAP::DataFormatError) do
- imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296))
- end
- imap.logout
- ensure
- imap.disconnect
- end
- end
-
- def test_send_literal
- server = create_tcp_server
- port = server.addr[1]
- requests = []
- literal = nil
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- line = sock.gets
- requests.push(line)
- size = line.slice(/{(\d+)}\r\n/, 1).to_i
- sock.print("+ Ready for literal data\r\n")
- literal = sock.read(size)
- requests.push(sock.gets)
- sock.print("RUBY0001 OK TEST completed\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- imap.__send__(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b])
- assert_equal(2, requests.length)
- assert_equal("RUBY0001 TEST ({4}\r\n", requests[0])
- assert_equal("\xDE\xAD\xBE\xEF".b, literal)
- assert_equal(")\r\n", requests[1])
- imap.logout
- ensure
- imap.disconnect
- end
- end
-
- def test_disconnect
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0001 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- imap.logout
- imap.disconnect
- assert_equal(true, imap.disconnected?)
- imap.disconnect
- assert_equal(true, imap.disconnected?)
- ensure
- imap.disconnect if imap && !imap.disconnected?
- end
- end
-
- def test_append
- server = create_tcp_server
- port = server.addr[1]
- mail = <<EOF.gsub(/\n/, "\r\n")
-From: shugo@example.com
-To: matz@example.com
-Subject: hello
-
-hello world
-EOF
- requests = []
- received_mail = nil
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- line = sock.gets
- requests.push(line)
- size = line.slice(/{(\d+)}\r\n/, 1).to_i
- sock.print("+ Ready for literal data\r\n")
- received_mail = sock.read(size)
- sock.gets
- sock.print("RUBY0001 OK APPEND completed\r\n")
- requests.push(sock.gets)
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
-
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- imap.append("INBOX", mail)
- assert_equal(1, requests.length)
- assert_equal("RUBY0001 APPEND INBOX {#{mail.size}}\r\n", requests[0])
- assert_equal(mail, received_mail)
- imap.logout
- assert_equal(2, requests.length)
- assert_equal("RUBY0002 LOGOUT\r\n", requests[1])
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_append_fail
- server = create_tcp_server
- port = server.addr[1]
- mail = <<EOF.gsub(/\n/, "\r\n")
-From: shugo@example.com
-To: matz@example.com
-Subject: hello
-
-hello world
-EOF
- requests = []
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- sock.print("RUBY0001 NO Mailbox doesn't exist\r\n")
- requests.push(sock.gets)
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
-
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- assert_raise(Net::IMAP::NoResponseError) do
- imap.append("INBOX", mail)
- end
- assert_equal(1, requests.length)
- assert_equal("RUBY0001 APPEND INBOX {#{mail.size}}\r\n", requests[0])
- imap.logout
- assert_equal(2, requests.length)
- assert_equal("RUBY0002 LOGOUT\r\n", requests[1])
- ensure
- imap.disconnect if imap
- end
- end
-
- def test_id
- server = create_tcp_server
- port = server.addr[1]
- requests = Queue.new
- server_id = {"name" => "test server", "version" => "v0.1.0"}
- server_id_str = '("name" "test server" "version" "v0.1.0")'
- @threads << Thread.start do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- requests.push(sock.gets)
- # RFC 2971 very clearly states (in section 3.2):
- # "a server MUST send a tagged ID response to an ID command."
- # And yet... some servers report ID capability but won't the response.
- sock.print("RUBY0001 OK ID completed\r\n")
- requests.push(sock.gets)
- sock.print("* ID #{server_id_str}\r\n")
- sock.print("RUBY0002 OK ID completed\r\n")
- requests.push(sock.gets)
- sock.print("* ID #{server_id_str}\r\n")
- sock.print("RUBY0003 OK ID completed\r\n")
- requests.push(sock.gets)
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0004 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
-
- begin
- imap = Net::IMAP.new(server_addr, :port => port)
- resp = imap.id
- assert_equal(nil, resp)
- assert_equal("RUBY0001 ID NIL\r\n", requests.pop)
- resp = imap.id({})
- assert_equal(server_id, resp)
- assert_equal("RUBY0002 ID ()\r\n", requests.pop)
- resp = imap.id("name" => "test client", "version" => "latest")
- assert_equal(server_id, resp)
- assert_equal("RUBY0003 ID (\"name\" \"test client\" \"version\" \"latest\")\r\n",
- requests.pop)
- imap.logout
- assert_equal("RUBY0004 LOGOUT\r\n", requests.pop)
- ensure
- imap.disconnect if imap
- end
- end
-
- private
-
- def imaps_test
- server = create_tcp_server
- port = server.addr[1]
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.ca_file = CA_FILE
- ctx.key = File.open(SERVER_KEY) { |f|
- OpenSSL::PKey::RSA.new(f)
- }
- ctx.cert = File.open(SERVER_CERT) { |f|
- OpenSSL::X509::Certificate.new(f)
- }
- ssl_server = OpenSSL::SSL::SSLServer.new(server, ctx)
- started = false
- ths = Thread.start do
- Thread.current.report_on_exception = false # always join-ed
- begin
- started = true
- sock = ssl_server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0001 OK LOGOUT completed\r\n")
- ensure
- sock.close
- end
- rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
- end
- end
- sleep 0.1 until started
- begin
- begin
- imap = yield(port)
- imap.logout
- ensure
- imap.disconnect if imap
- end
- ensure
- ssl_server.close
- ths.join
- end
- end
-
- def starttls_test
- server = create_tcp_server
- port = server.addr[1]
- start_server do
- sock = server.accept
- begin
- sock.print("* OK test server\r\n")
- sock.gets
- sock.print("RUBY0001 OK completed\r\n")
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.ca_file = CA_FILE
- ctx.key = File.open(SERVER_KEY) { |f|
- OpenSSL::PKey::RSA.new(f)
- }
- ctx.cert = File.open(SERVER_CERT) { |f|
- OpenSSL::X509::Certificate.new(f)
- }
- sock = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- sock.sync_close = true
- sock.accept
- sock.gets
- sock.print("* BYE terminating connection\r\n")
- sock.print("RUBY0002 OK LOGOUT completed\r\n")
- ensure
- sock.close
- server.close
- end
- end
- begin
- imap = yield(port)
- imap.logout if !imap.disconnected?
- ensure
- imap.disconnect if imap && !imap.disconnected?
- end
- end
-
- def create_tcp_server
- return TCPServer.new(server_addr, 0)
- end
-
- def server_addr
- Addrinfo.tcp("localhost", 0).ip_address
- end
-end
diff --git a/test/net/imap/test_imap_authenticators.rb b/test/net/imap/test_imap_authenticators.rb
deleted file mode 100644
index 0c7a0a325d..0000000000
--- a/test/net/imap/test_imap_authenticators.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require "net/imap"
-require "test/unit"
-
-class IMAPAuthenticatorsTest < Test::Unit::TestCase
-
- PLAIN = Net::IMAP::PlainAuthenticator
-
- def test_plain
- assert_equal("\0authc\0passwd",
- PLAIN.new("authc", "passwd").process(nil))
- assert_equal("authz\0user\0pass",
- PLAIN.new("user", "pass", authzid: "authz").process(nil))
- end
-
- def test_plain_no_null_chars
- assert_raise(ArgumentError) { PLAIN.new("bad\0user", "pass") }
- assert_raise(ArgumentError) { PLAIN.new("user", "bad\0pass") }
- assert_raise(ArgumentError) { PLAIN.new("u", "p", authzid: "bad\0authz") }
- end
-
-end
diff --git a/test/net/imap/test_imap_data_encoding.rb b/test/net/imap/test_imap_data_encoding.rb
deleted file mode 100644
index 2ca1c1822b..0000000000
--- a/test/net/imap/test_imap_data_encoding.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require "net/imap"
-require "test/unit"
-
-class IMAPDataEncodingTest < Test::Unit::TestCase
-
- def test_encode_utf7
- assert_equal("foo", Net::IMAP.encode_utf7("foo"))
- assert_equal("&-", Net::IMAP.encode_utf7("&"))
-
- utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8")
- s = Net::IMAP.encode_utf7(utf8)
- assert_equal("&,yH,Iv8j-", s)
- s = Net::IMAP.encode_utf7("foo&#{utf8}-bar".encode("EUC-JP"))
- assert_equal("foo&-&,yH,Iv8j--bar", s)
-
- utf8 = "\343\201\202&".dup.force_encoding("UTF-8")
- s = Net::IMAP.encode_utf7(utf8)
- assert_equal("&MEI-&-", s)
- s = Net::IMAP.encode_utf7(utf8.encode("EUC-JP"))
- assert_equal("&MEI-&-", s)
- end
-
- def test_decode_utf7
- assert_equal("&", Net::IMAP.decode_utf7("&-"))
- assert_equal("&-", Net::IMAP.decode_utf7("&--"))
-
- s = Net::IMAP.decode_utf7("&,yH,Iv8j-")
- utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8")
- assert_equal(utf8, s)
- end
-
- def test_format_date
- time = Time.mktime(2009, 7, 24)
- s = Net::IMAP.format_date(time)
- assert_equal("24-Jul-2009", s)
- end
-
- def test_format_datetime
- time = Time.mktime(2009, 7, 24, 1, 23, 45)
- s = Net::IMAP.format_datetime(time)
- assert_match(/\A24-Jul-2009 01:23 [+\-]\d{4}\z/, s)
- end
-
-end
diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb
deleted file mode 100644
index 5b519edeff..0000000000
--- a/test/net/imap/test_imap_response_parser.rb
+++ /dev/null
@@ -1,389 +0,0 @@
-# frozen_string_literal: true
-
-require "net/imap"
-require "test/unit"
-
-class IMAPResponseParserTest < Test::Unit::TestCase
- def setup
- @do_not_reverse_lookup = Socket.do_not_reverse_lookup
- Socket.do_not_reverse_lookup = true
- if Net::IMAP.respond_to?(:max_flag_count)
- @max_flag_count = Net::IMAP.max_flag_count
- Net::IMAP.max_flag_count = 3
- end
- end
-
- def teardown
- Socket.do_not_reverse_lookup = @do_not_reverse_lookup
- if Net::IMAP.respond_to?(:max_flag_count)
- Net::IMAP.max_flag_count = @max_flag_count
- end
- end
-
- def test_flag_list_too_many_flags
- parser = Net::IMAP::ResponseParser.new
- assert_nothing_raised do
- 3.times do |i|
- parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* LIST (\\Foo#{i}) "." "INBOX"
-EOF
- end
- end
- assert_raise(Net::IMAP::FlagCountError) do
- parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* LIST (\\Foo3) "." "INBOX"
-EOF
- end
- end
-
- def test_flag_list_many_same_flags
- parser = Net::IMAP::ResponseParser.new
- assert_nothing_raised do
- 100.times do
- parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* LIST (\\Foo) "." "INBOX"
-EOF
- end
- end
- end
-
- def test_flag_xlist_inbox
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* XLIST (\\Inbox) "." "INBOX"
-EOF
- assert_equal [:Inbox], response.data.attr
- end
-
- def test_resp_text_code
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* OK [CLOSED] Previous mailbox closed.
-EOF
- assert_equal "CLOSED", response.data.code.name
- end
-
- def test_search_response
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* SEARCH
-EOF
- assert_equal [], response.data
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* SEARCH 1
-EOF
- assert_equal [1], response.data
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* SEARCH 1 2 3
-EOF
- assert_equal [1, 2, 3], response.data
- end
-
- def test_search_response_of_yahoo
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* SEARCH 1\s
-EOF
- assert_equal [1], response.data
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* SEARCH 1 2 3\s
-EOF
- assert_equal [1, 2, 3], response.data
- end
-
- def test_msg_att_extra_space
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 1 FETCH (UID 92285)
-EOF
- assert_equal 92285, response.data.attr["UID"]
-
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 1 FETCH (UID 92285 )
-EOF
- assert_equal 92285, response.data.attr["UID"]
- end
-
- def test_msg_att_parse_error
- parser = Net::IMAP::ResponseParser.new
- e = assert_raise(Net::IMAP::ResponseParseError) {
- parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 123 FETCH (UNKNOWN 92285)
-EOF
- }
- assert_match(/ for \{123\}/, e.message)
- end
-
- def test_msg_att_rfc822_text
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 123 FETCH (RFC822 {5}
-foo
-)
-EOF
- assert_equal("foo\r\n", response.data.attr["RFC822"])
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 123 FETCH (RFC822[] {5}
-foo
-)
-EOF
- assert_equal("foo\r\n", response.data.attr["RFC822"])
- end
-
- # [Bug #6397] [ruby-core:44849]
- def test_body_type_attachment
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 980 FETCH (UID 2862 BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 416 21 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 1493 32 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "Boundary_(ID_IaecgfnXwG5bn3x8lIeGIQ)") NIL NIL)("MESSAGE" "RFC822" ("NAME" "Fw_ ____ _____ ____.eml") NIL NIL "7BIT" 1980088 NIL ("ATTACHMENT" ("FILENAME" "Fw_ ____ _____ ____.eml")) NIL) "MIXED" ("BOUNDARY" "Boundary_(ID_eDdLc/j0mBIzIlR191pHjA)") NIL NIL))
-EOF
- assert_equal("Fw_ ____ _____ ____.eml",
- response.data.attr["BODYSTRUCTURE"].parts[1].body.param["FILENAME"])
- end
-
- def assert_parseable(s)
- parser = Net::IMAP::ResponseParser.new
- parser.parse(s.gsub(/\n/, "\r\n"))
- end
-
- # [Bug #7146]
- def test_msg_delivery_status
- # This was part of a larger response that caused crashes, but this was the
- # minimal test case to demonstrate it
- assert_parseable <<EOF
-* 4902 FETCH (BODY (("MESSAGE" "DELIVERY-STATUS" NIL NIL NIL "7BIT" 324) "REPORT"))
-EOF
- end
-
- # [Bug #7147]
- def test_msg_with_message_rfc822_attachment
- assert_parseable <<EOF
-* 5441 FETCH (BODY ((("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 69 1)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 455 12) "ALTERNATIVE")("MESSAGE" "RFC822" ("NAME" "ATT00026.eml") NIL NIL "7BIT" 4079755) "MIXED"))
-EOF
- end
-
- # [Bug #7153]
- def test_msg_body_mixed
- assert_parseable <<EOF
-* 1038 FETCH (BODY ("MIXED"))
-EOF
- end
-
- # [Bug #8167]
- def test_msg_delivery_status_with_extra_data
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* 29021 FETCH (RFC822.SIZE 3162 UID 113622 RFC822.HEADER {1155}
-Return-path: <>
-Envelope-to: info@xxxxxxxx.si
-Delivery-date: Tue, 26 Mar 2013 12:42:58 +0100
-Received: from mail by xxxx.xxxxxxxxxxx.net with spam-scanned (Exim 4.76)
- id 1UKSHI-000Cwl-AR
- for info@xxxxxxxx.si; Tue, 26 Mar 2013 12:42:58 +0100
-X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on xxxx.xxxxxxxxxxx.net
-X-Spam-Level: **
-X-Spam-Status: No, score=2.1 required=7.0 tests=DKIM_ADSP_NXDOMAIN,RDNS_NONE
- autolearn=no version=3.3.1
-Received: from [xx.xxx.xxx.xx] (port=56890 helo=xxxxxx.localdomain)
- by xxxx.xxxxxxxxxxx.net with esmtp (Exim 4.76)
- id 1UKSHI-000Cwi-9j
- for info@xxxxxxxx.si; Tue, 26 Mar 2013 12:42:56 +0100
-Received: by xxxxxx.localdomain (Postfix)
- id 72725BEA64A; Tue, 26 Mar 2013 12:42:55 +0100 (CET)
-Date: Tue, 26 Mar 2013 12:42:55 +0100 (CET)
-From: MAILER-DAEMON@xxxxxx.localdomain (Mail Delivery System)
-Subject: Undelivered Mail Returned to Sender
-To: info@xxxxxxxx.si
-Auto-Submitted: auto-replied
-MIME-Version: 1.0
-Content-Type: multipart/report; report-type=delivery-status;
- boundary="27797BEA649.1364298175/xxxxxx.localdomain"
-Message-Id: <20130326114255.72725BEA64A@xxxxxx.localdomain>
-
- BODYSTRUCTURE (("text" "plain" ("charset" "us-ascii") NIL "Notification" "7bit" 510 14 NIL NIL NIL NIL)("message" "delivery-status" NIL NIL "Delivery report" "7bit" 410 NIL NIL NIL NIL)("text" "rfc822-headers" ("charset" "us-ascii") NIL "Undelivered Message Headers" "7bit" 612 15 NIL NIL NIL NIL) "report" ("report-type" "delivery-status" "boundary" "27797BEA649.1364298175/xxxxxx.localdomain") NIL NIL NIL))
-EOF
- delivery_status = response.data.attr["BODYSTRUCTURE"].parts[1]
- assert_equal("MESSAGE", delivery_status.media_type)
- assert_equal("DELIVERY-STATUS", delivery_status.subtype)
- assert_equal(nil, delivery_status.param)
- assert_equal(nil, delivery_status.content_id)
- assert_equal("Delivery report", delivery_status.description)
- assert_equal("7BIT", delivery_status.encoding)
- assert_equal(410, delivery_status.size)
- end
-
- # [Bug #8281]
- def test_acl
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse(<<EOF.gsub(/\n/, "\r\n"))
-* ACL "INBOX/share" "imshare2copy1366146467@xxxxxxxxxxxxxxxxxx.com" lrswickxteda
-EOF
- assert_equal("ACL", response.name)
- assert_equal(1, response.data.length)
- assert_equal("INBOX/share", response.data[0].mailbox)
- assert_equal("imshare2copy1366146467@xxxxxxxxxxxxxxxxxx.com",
- response.data[0].user)
- assert_equal("lrswickxteda", response.data[0].rights)
- end
-
- # [Bug #8415]
- def test_capability
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN\r\n")
- assert_equal("CAPABILITY", response.name)
- assert_equal("AUTH=PLAIN", response.data.last)
- response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN \r\n")
- assert_equal("CAPABILITY", response.name)
- assert_equal("AUTH=PLAIN", response.data.last)
- response = parser.parse("* OK [CAPABILITY IMAP4rev1 SASL-IR 1234 NIL THIS+THAT + AUTH=PLAIN ID] IMAP4rev1 Hello\r\n")
- assert_equal("OK", response.name)
- assert_equal("IMAP4rev1 Hello", response.data.text)
- code = response.data.code
- assert_equal("CAPABILITY", code.name)
- assert_equal(
- ["IMAP4REV1", "SASL-IR", "1234", "NIL", "THIS+THAT", "+", "AUTH=PLAIN", "ID"],
- code.data
- )
- end
-
- def test_id
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* ID NIL\r\n")
- assert_equal("ID", response.name)
- assert_equal(nil, response.data)
- response = parser.parse("* ID (\"name\" \"GImap\" \"vendor\" \"Google, Inc.\" \"support-url\" NIL)\r\n")
- assert_equal("ID", response.name)
- assert_equal("GImap", response.data["name"])
- assert_equal("Google, Inc.", response.data["vendor"])
- assert_equal(nil, response.data.fetch("support-url"))
- end
-
- def test_mixed_boundary
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* 2688 FETCH (UID 179161 BODYSTRUCTURE (" \
- "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"iso-8859-1\") NIL NIL \"QUOTED-PRINTABLE\" 200 4 NIL NIL NIL)" \
- "(\"MESSAGE\" \"DELIVERY-STATUS\" NIL NIL NIL \"7BIT\" 318 NIL NIL NIL)" \
- "(\"MESSAGE\" \"RFC822\" NIL NIL NIL \"7BIT\" 2177" \
- " (\"Tue, 11 May 2010 18:28:16 -0400\" \"Re: Welcome letter\" (" \
- "(\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \
- "((\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \
- "((\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \
- "((\"Doretha\" NIL \"doretha.info\" \"xxxxxxxx.si\")) " \
- "NIL NIL " \
- "\"<AC1D15E06EA82F47BDE18E851CC32F330717704E@localdomain>\" " \
- "\"<AANLkTikKMev1I73L2E7XLjRs67IHrEkb23f7ZPmD4S_9@localdomain>\")" \
- " (\"MIXED\" (\"BOUNDARY\" \"000e0cd29212e3e06a0486590ae2\") NIL NIL)" \
- " 37 NIL NIL NIL)" \
- " \"REPORT\" (\"BOUNDARY\" \"16DuG.4XbaNOvCi.9ggvq.8Ipnyp3\" \"REPORT-TYPE\" \"delivery-status\") NIL NIL))\r\n")
- empty_part = response.data.attr['BODYSTRUCTURE'].parts[2]
- assert_equal(empty_part.lines, 37)
- assert_equal(empty_part.body.media_type, 'MULTIPART')
- assert_equal(empty_part.body.subtype, 'MIXED')
- assert_equal(empty_part.body.param['BOUNDARY'], '000e0cd29212e3e06a0486590ae2')
- end
-
- # [Bug #10112]
- def test_search_modseq
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* SEARCH 87216 87221 (MODSEQ 7667567)\r\n")
- assert_equal("SEARCH", response.name)
- assert_equal([87216, 87221], response.data)
- end
-
- # [Bug #11128]
- def test_body_ext_mpart_without_lang
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* 4 FETCH (BODY (((\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"7bit\" 257 9 NIL NIL NIL NIL)(\"text\" \"html\" (\"charset\" \"utf-8\") NIL NIL \"quoted-printable\" 655 9 NIL NIL NIL NIL) \"alternative\" (\"boundary\" \"001a1137a5047848dd05157ddaa1\") NIL)(\"application\" \"pdf\" (\"name\" \"test.xml\" \"x-apple-part-url\" \"9D00D9A2-98AB-4EFB-85BA-FB255F8BF3D7\") NIL NIL \"base64\" 4383638 NIL (\"attachment\" (\"filename\" \"test.xml\")) NIL NIL) \"mixed\" (\"boundary\" \"001a1137a5047848e405157ddaa3\") NIL))\r\n")
- assert_equal("FETCH", response.name)
- body = response.data.attr["BODY"]
- assert_equal(nil, body.parts[0].disposition)
- assert_equal(nil, body.parts[0].language)
- assert_equal("ATTACHMENT", body.parts[1].disposition.dsp_type)
- assert_equal("test.xml", body.parts[1].disposition.param["FILENAME"])
- assert_equal(nil, body.parts[1].language)
- end
-
- # [Bug #13649]
- def test_status
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1234)\r\n")
- assert_equal("STATUS", response.name)
- assert_equal("INBOX", response.data.mailbox)
- assert_equal(1234, response.data.attr["UIDVALIDITY"])
- response = parser.parse("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1234) \r\n")
- assert_equal("STATUS", response.name)
- assert_equal("INBOX", response.data.mailbox)
- assert_equal(1234, response.data.attr["UIDVALIDITY"])
- end
-
- # [Bug #10119]
- def test_msg_att_modseq_data
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("* 1 FETCH (FLAGS (\Seen) MODSEQ (12345) UID 5)\r\n")
- assert_equal(12345, response.data.attr["MODSEQ"])
- end
-
- def test_msg_rfc3501_response_text_with_T_LBRA
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("RUBY0004 OK [READ-WRITE] [Gmail]/Sent Mail selected. (Success)\r\n")
- assert_equal("RUBY0004", response.tag)
- assert_equal("READ-WRITE", response.data.code.name)
- assert_equal("[Gmail]/Sent Mail selected. (Success)", response.data.text)
- end
-
- def test_msg_rfc3501_response_text_with_BADCHARSET_astrings
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("t BAD [BADCHARSET (US-ASCII \"[astring with brackets]\")] unsupported charset foo.\r\n")
- assert_equal("t", response.tag)
- assert_equal("unsupported charset foo.", response.data.text)
- assert_equal("BADCHARSET", response.data.code.name)
- end
-
- def test_continuation_request_without_response_text
- parser = Net::IMAP::ResponseParser.new
- response = parser.parse("+\r\n")
- assert_instance_of(Net::IMAP::ContinuationRequest, response)
- assert_equal(nil, response.data.code)
- assert_equal("", response.data.text)
- end
-
- def test_ignored_response
- parser = Net::IMAP::ResponseParser.new
- response = nil
- assert_nothing_raised do
- response = parser.parse("* NOOP\r\n")
- end
- assert_instance_of(Net::IMAP::IgnoredResponse, response)
- end
-
- def test_namespace
- parser = Net::IMAP::ResponseParser.new
- # RFC2342 Example 5.1
- response = parser.parse(%Q{* NAMESPACE (("" "/")) NIL NIL\r\n})
- assert_equal("NAMESPACE", response.name)
- assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal)
- assert_equal([], response.data.other)
- assert_equal([], response.data.shared)
- # RFC2342 Example 5.4
- response = parser.parse(%Q{* NAMESPACE (("" "/")) (("~" "/")) (("#shared/" "/")} +
- %Q{ ("#public/" "/") ("#ftp/" "/") ("#news." "."))\r\n})
- assert_equal("NAMESPACE", response.name)
- assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal)
- assert_equal([Net::IMAP::Namespace.new("~", "/", {})], response.data.other)
- assert_equal(
- [
- Net::IMAP::Namespace.new("#shared/", "/", {}),
- Net::IMAP::Namespace.new("#public/", "/", {}),
- Net::IMAP::Namespace.new("#ftp/", "/", {}),
- Net::IMAP::Namespace.new("#news.", ".", {}),
- ],
- response.data.shared
- )
- # RFC2342 Example 5.6
- response = parser.parse(%Q{* NAMESPACE (("" "/") ("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2"))) NIL NIL\r\n})
- assert_equal("NAMESPACE", response.name)
- namespace = response.data.personal.last
- assert_equal("#mh/", namespace.prefix)
- assert_equal("/", namespace.delim)
- assert_equal({"X-PARAM" => ["FLAG1", "FLAG2"]}, namespace.extensions)
- end
-end
diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb
index 677b55a35c..2059ceb1ef 100644
--- a/tool/sync_default_gems.rb
+++ b/tool/sync_default_gems.rb
@@ -51,7 +51,6 @@ REPOSITORIES = {
tmpdir: "ruby/tmpdir",
English: "ruby/English",
"net-protocol": "ruby/net-protocol",
- "net-imap": "ruby/net-imap",
"net-http": "ruby/net-http",
bigdecimal: "ruby/bigdecimal",
optparse: "ruby/optparse",
@@ -260,12 +259,6 @@ def sync_default_gems(gem)
cp_r("#{upstream}/lib/net/protocol.rb", "lib/net")
cp_r("#{upstream}/test/net/protocol", "test/net")
cp_r("#{upstream}/net-protocol.gemspec", "lib/net")
- when "net-imap"
- rm_rf(%w[lib/net/imap.rb lib/net/imap test/net/imap])
- cp_r("#{upstream}/lib/net/imap.rb", "lib/net")
- cp_r("#{upstream}/lib/net/imap", "lib/net")
- cp_r("#{upstream}/test/net/imap", "test/net")
- cp_r("#{upstream}/net-imap.gemspec", "lib/net/imap")
when "net-http"
rm_rf(%w[lib/net/http.rb lib/net/http test/net/http])
cp_r("#{upstream}/lib/net/http.rb", "lib/net")