diff options
-rw-r--r-- | lib/feed2imap/imap.rb | 2 | ||||
-rw-r--r-- | lib/feed2imap/rubyimap.rb | 3601 |
2 files changed, 1 insertions, 3602 deletions
diff --git a/lib/feed2imap/imap.rb b/lib/feed2imap/imap.rb index 4c54456..2e796f6 100644 --- a/lib/feed2imap/imap.rb +++ b/lib/feed2imap/imap.rb @@ -18,7 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =end # Imap connection handling -require 'feed2imap/rubyimap' +require 'net/imap' begin require 'openssl' rescue LoadError diff --git a/lib/feed2imap/rubyimap.rb b/lib/feed2imap/rubyimap.rb deleted file mode 100644 index 43af035..0000000 --- a/lib/feed2imap/rubyimap.rb +++ /dev/null @@ -1,3601 +0,0 @@ -# File fetched from -# http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/net/imap.rb?view=log -# Current rev: 27336 -############################################################################ -# -# = 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 "digest/md5" -require "strscan" -begin - require "openssl" -rescue LoadError -end - -module Net - - # - # Net::IMAP implements Internet Message Access Protocol (IMAP) client - # functionality. The protocol is described in [IMAP]. - # - # == 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 a 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 mail box - # from 1 up to the number of items in the mail box. If new - # message arrives during a session, it receives a sequence - # number equal to the new size of the mail box. 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 cannot thus - # 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 exists; 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 our 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]] - # M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1", - # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501) - # - # [[LANGUAGE-TAGS]] - # Alvestrand, H., "Tags for the Identification of - # Languages", RFC 1766, March 1995. - # - # [[MD5]] - # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC - # 1864, October 1995. - # - # [[MIME-IMB]] - # Freed, N., and N. Borenstein, "MIME (Multipurpose Internet - # Mail Extensions) Part One: Format of Internet Message Bodies", RFC - # 2045, November 1996. - # - # [[RFC-822]] - # Crocker, D., "Standard for the Format of ARPA Internet Text - # Messages", STD 11, RFC 822, University of Delaware, August 1982. - # - # [[RFC-2087]] - # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997. - # - # [[RFC-2086]] - # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997. - # - # [[RFC-2195]] - # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension - # for Simple Challenge/Response", RFC 2195, September 1997. - # - # [[SORT-THREAD-EXT]] - # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD - # Extensions", draft-ietf-imapext-sort, May 2003. - # - # [[OSSL]] - # http://www.openssl.org - # - # [[RSSL]] - # http://savannah.gnu.org/projects/rubypki - # - # [[UTF7]] - # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of - # Unicode", RFC 2152, May 1997. - # - class IMAP - include MonitorMixin - if defined?(OpenSSL) - 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 - - # The thread to receive exceptions. - attr_accessor :client_thread - - # Flag indicating a message has been seen - SEEN = :Seen - - # Flag indicating a message has been answered - ANSWERED = :Answered - - # Flag indicating a message has been flagged for special or urgent - # attention - FLAGGED = :Flagged - - # Flag indicating a message has been marked for deletion. This - # will occur when the mailbox is closed or expunged. - DELETED = :Deleted - - # Flag indicating a message is only a draft or work-in-progress version. - DRAFT = :Draft - - # 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 - - # Flag indicating that a mailbox context name cannot contain - # children. - NOINFERIORS = :Noinferiors - - # Flag indicating that a mailbox is not selected. - NOSELECT = :Noselect - - # Flag indicating that a mailbox has been marked "interesting" by - # the server; this commonly indicates that the mailbox contains - # new messages. - MARKED = :Marked - - # Flag indicating that the mailbox does not contains new messages. - UNMARKED = :Unmarked - - # Returns the debug mode. - def self.debug - return @@debug - end - - # Sets the debug mode. - def self.debug=(val) - return @@debug = val - end - - # 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 - - # Adds an authenticator for Net::IMAP#authenticate. +auth_type+ - # is the type of authentication this authenticator supports - # (for instance, "LOGIN"). The +authenticator+ is an object - # which defines a process() method to handle authentication with - # the server. See 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 self.add_authenticator(auth_type, authenticator) - @@authenticators[auth_type] = authenticator - end - - # Disconnects from the server. - def disconnect - 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. - end - @receiver_thread.join - @sock.close - 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 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 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 these mechanisms, there should be two +args+: username - # and (cleartext) password. A server may not support one or 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 @@authenticators 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) - auth_type = auth_type.upcase - unless @@authenticators.has_key?(auth_type) - raise ArgumentError, - format('unknown auth type - "%s"', auth_type) - end - authenticator = @@authenticators[auth_type].new(*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("m").gsub(/\n/, "") - 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 the GETQUOTAROOT command along with specified +mailbox+. - # This command is generally available to both admin and user. - # If mailbox exists, returns an array containing objects of - # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota. - 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 generally is only available to server admin. - 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 server admin - # for this to work. The IMAP quota commands are described in - # [RFC-2087]. - 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 IMAP ACL commands are described in [RFC-2086]. - 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 specified +mailbox+. - # If this mailbox exists, an array containing objects of - # Net::IMAP::MailboxACLItem will be returned. - 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 that - # we are request the status of. 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 to initially passing 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 - - # As for #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 an array of - # numbers or a Range object. The number is a message sequence - # number. +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. 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) - return fetch_internal("FETCH", set, attr) - end - - # As for #fetch(), but +set+ contains unique identifiers. - def uid_fetch(set, attr) - return fetch_internal("UID FETCH", set, attr) - end - - # Sends a STORE command to alter data associated with messages - # in the mailbox, in particular their flags. The +set+ parameter - # is a number or 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' means to replace the message's flag list - # with the provided one; '+FLAGS' means to add the provided flags; - # and '-FLAGS' means to 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 - - # As for #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 or 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 - - # As for #copy(), but +set+ contains unique identifiers. - def uid_copy(set, mailbox) - copy_internal("UID COPY", 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] - # - # See [SORT-THREAD-EXT] for more details. - def sort(sort_keys, search_keys, charset) - return sort_internal("SORT", sort_keys, search_keys, charset) - end - - # As for #sort(), but returns an array of unique identifiers. - 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 us a new EXISTS response (which normally - # indicates new messages being added to the mail box), - # you could 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 = Proc.new) - @response_handlers.push(handler) - end - - # Removes the response handler. - def remove_response_handler(handler) - @response_handlers.delete(handler) - end - - # As for #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. - # - # See [SORT-THREAD-EXT] for more details. - def thread(algorithm, search_keys, charset) - return thread_internal("THREAD", algorithm, search_keys, charset) - end - - # As for #thread(), but returns unique identifiers instead of - # message sequence numbers. - 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. - def idle(&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 - @idle_done_cond = nil - ensure - remove_response_handler(response_handler) - put_string("DONE#{CRLF}") - response = get_tagged_response(tag, "IDLE") - 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 - - # 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 utf7. - def self.decode_utf7(s) - return s.gsub(/&(.*?)-/n) { - if $1.empty? - "&" - else - base64 = $1.tr(",", "/") - x = base64.length % 4 - if x > 0 - base64.concat("=" * (4 - x)) - end - base64.unpack("m")[0].unpack("n*").pack("U*") - end - }.force_encoding("UTF-8") - end - - # Encode a string from UTF-8 format to modified UTF-7. - def self.encode_utf7(s) - return s.gsub(/(&)|([^\x20-\x7e]+)/u) { - if $1 - "&-" - else - base64 = [$&.unpack("U*").pack("n*")].pack("m") - "&" + base64.delete("=\n").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 - - private - - CRLF = "\r\n" # :nodoc: - PORT = 143 # :nodoc: - SSL_PORT = 993 # :nodoc: - - @@debug = false - @@authenticators = {} - @@max_flag_count = 10000 - - # 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. - # - # 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:: we connected to the host, but they - # immediately said goodbye to us. - 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] ? |