summaryrefslogtreecommitdiff
path: root/lib/net/imap.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/net/imap.rb')
-rw-r--r--lib/net/imap.rb2878
1 files changed, 0 insertions, 2878 deletions
diff --git a/lib/net/imap.rb b/lib/net/imap.rb
deleted file mode 100644
index e31b80a461..0000000000
--- a/lib/net/imap.rb
+++ /dev/null
@@ -1,2878 +0,0 @@
-=begin
-
-= 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.
-
-== Net::IMAP
-
-Net::IMAP implements Internet Message Access Protocol (IMAP) clients.
-(The protocol is described in ((<[IMAP]>)).)
-
-Net::IMAP supports multiple commands. 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.
-
-=== Super Class
-
-Object
-
-=== Class Methods
-
-: new(host, port = 143, usessl = false, certs = nil, verify = false)
- Creates a new Net::IMAP object and connects it to the specified
- port on the named host. If usessl 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]>))
- extension need to be installed. The certs parameter indicates
- the path or file containing the CA cert of the server, and the
- verify parameter is for the OpenSSL verification callback.
-
-: debug
- Returns the debug mode.
-
-: debug = val
- Sets the debug mode.
-
-: add_authenticator(auth_type, authenticator)
- Adds an authenticator for Net::IMAP#authenticate.
-
-=== Methods
-
-: greeting
- Returns an initial greeting response from the server.
-
-: responses
- Returns recorded untagged responses.
-
- ex).
- imap.select("inbox")
- p imap.responses["EXISTS"][-1]
- #=> 2
- p imap.responses["UIDVALIDITY"][-1]
- #=> 968263756
-
-: disconnect
- Disconnects from the server.
-
-: capability
- Sends a CAPABILITY command, and returns a listing of
- capabilities that the server supports.
-
-: noop
- Sends a NOOP command to the server. It does nothing.
-
-: logout
- Sends a LOGOUT command to inform the server that the client is
- done with the connection.
-
-: authenticate(auth_type, arg...)
- Sends an AUTEHNTICATE command to authenticate the client.
- The auth_type parameter is a string that represents
- the authentication mechanism to be used. Currently Net::IMAP
- supports "LOGIN" and "CRAM-MD5" for the auth_type.
-
- ex).
- imap.authenticate('LOGIN', user, password)
-
-: login(user, password)
- Sends a LOGIN command to identify the client and carries
- the plaintext password authenticating this user.
-
-: select(mailbox)
- Sends a SELECT command to select a mailbox so that messages
- in the mailbox can be accessed.
-
-: examine(mailbox)
- Sends a EXAMINE command to select a mailbox so that messages
- in the mailbox can be accessed. However, the selected mailbox
- is identified as read-only.
-
-: create(mailbox)
- Sends a CREATE command to create a new mailbox.
-
-: delete(mailbox)
- Sends a DELETE command to remove the mailbox.
-
-: rename(mailbox, newname)
- Sends a RENAME command to change the name of the mailbox to
- the newname.
-
-: subscribe(mailbox)
- Sends a SUBSCRIBE command to add the specified mailbox name to
- the server's set of "active" or "subscribed" mailboxes.
-
-: unsubscribe(mailbox)
- Sends a UNSUBSCRIBE command to remove the specified mailbox name
- from the server's set of "active" or "subscribed" mailboxes.
-
-: list(refname, mailbox)
- Sends a LIST command, and returns a subset of names from
- the complete set of all names available to the client.
- The return value is an array of ((<Net::IMAP::MailboxList>)).
-
- ex).
- 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">]
-
-: lsub(refname, mailbox)
- 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".
- The return value is an array of ((<Net::IMAP::MailboxList>)).
-
-: status(mailbox, attr)
- Sends a STATUS command, and returns the status of the indicated
- mailbox.
- The return value is a hash of attributes.
-
- ex).
- p imap.status("inbox", ["MESSAGES", "RECENT"])
- #=> {"RECENT"=>0, "MESSAGES"=>44}
-
-: append(mailbox, message, flags = nil, date_time = nil)
- Sends a APPEND command to append the message to the end of
- the mailbox.
-
- ex).
- 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
-
-: check
- Sends a CHECK command to request a checkpoint of the currently
- selected mailbox.
-
-: close
- 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.
-
-: expunge
- Sends a EXPUNGE command to permanently remove from the currently
- selected mailbox all messages that have the \Deleted flag set.
-
-: search(keys, charset = nil)
-: uid_search(keys, charset = nil)
- Sends a SEARCH command to search the mailbox for messages that
- match the given searching criteria, and returns message sequence
- numbers (search) or unique identifiers (uid_search).
-
- ex).
- p imap.search(["SUBJECT", "hello"])
- #=> [1, 6, 7, 8]
- p imap.search('SUBJECT "hello"')
- #=> [1, 6, 7, 8]
-
-: fetch(set, attr)
-: uid_fetch(set, attr)
- 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 (fetch) or a unique identifier (uid_fetch).
- The return value is an array of ((<Net::IMAP::FetchData>)).
-
- ex).
- 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
-
-: store(set, attr, flags)
-: uid_store(set, attr, flags)
- Sends a STORE command to alter 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 (store) or a unique identifier (uid_store).
- The return value is an array of ((<Net::IMAP::FetchData>)).
-
- ex).
- 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]}>]
-
-: copy(set, mailbox)
-: uid_copy(set, mailbox)
- 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 (copy) or a unique identifier (uid_copy).
-
-: sort(sort_keys, search_keys, charset)
-: uid_sort(sort_keys, search_keys, charset)
- Sends a SORT command to sort messages in the mailbox.
-
- ex).
- 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]
-
-: setquota(mailbox, quota)
- 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]>)).
-
-: getquota(mailbox)
- 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.
-
-: getquotaroot(mailbox)
- 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>)).
-
-: setacl(mailbox, user, rights)
- 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]>)).
-
-: getacl(mailbox)
- Send the GETACL command along with specified mailbox.
- If this mailbox exists, an array containing objects of
- ((<Net::IMAP::MailboxACLItem>)) will be returned.
-
-: add_response_handler(handler = Proc.new)
- Adds a response handler.
-
- ex).
- imap.add_response_handler do |resp|
- p resp
- end
-
-: remove_response_handler(handler)
- Removes the response handler.
-
-: response_handlers
- Returns all response handlers.
-
-== Net::IMAP::ContinuationRequest
-
-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)
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: data
- Returns the data (Net::IMAP::ResponseText).
-
-: raw_data
- Returns the raw data string.
-
-== Net::IMAP::UntaggedResponse
-
-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)
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: name
- Returns the name such as "FLAGS", "LIST", "FETCH"....
-
-: data
- Returns the data such as an array of flag symbols,
- a ((<Net::IMAP::MailboxList>)) object....
-
-: raw_data
- Returns the raw data string.
-
-== Net::IMAP::TaggedResponse
-
-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
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: tag
- Returns the tag.
-
-: name
- Returns the name. the name is one of "OK", "NO", "BAD".
-
-: data
- Returns the data. See ((<Net::IMAP::ResponseText>)).
-
-: raw_data
- Returns the raw data string.
-
-== Net::IMAP::ResponseText
-
-Net::IMAP::ResponseText represents texts of responses.
-The text may be prefixed by the response code.
-
- resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
- ;; text SHOULD NOT begin with "[" or "="
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: code
- Returns the response code. See ((<Net::IMAP::ResponseCode>)).
-
-: text
- Returns the text.
-
-== Net::IMAP::ResponseCode
-
-Net::IMAP::ResponseCode represents response codes.
-
- resp_text_code ::= "ALERT" / "PARSE" /
- "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
- "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
- "UIDVALIDITY" SPACE nz_number /
- "UNSEEN" SPACE nz_number /
- atom [SPACE 1*<any TEXT_CHAR except "]">]
-
-=== SuperClass
-
-Struct
-
-=== Methods
-
-: name
- Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY"....
-
-: data
- Returns the data if it exists.
-
-== Net::IMAP::MailboxList
-
-Net::IMAP::MailboxList represents contents of the LIST response.
-
- mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
- "\Noselect" / "\Unmarked" / flag_extension) ")"
- SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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.
-
-== Net::IMAP::MailboxQuota
-
-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
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: mailbox
- The mailbox with the associated quota.
-
-: usage
- Current storage usage of mailbox.
-
-: quota
- Quota limit imposed on mailbox.
-
-== Net::IMAP::MailboxQuotaRoot
-
-Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
-response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
-
- quotaroot_response
- ::= "QUOTAROOT" SPACE astring *(SPACE astring)
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: mailbox
- The mailbox with the associated quota.
-
-: quotaroots
- Zero or more quotaroots that effect the quota on the
- specified mailbox.
-
-== Net::IMAP::MailboxACLItem
-
-Net::IMAP::MailboxACLItem represents response from GETACL.
-
- acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE
- rights)
-
- identifier ::= astring
-
- rights ::= astring
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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.
-
-== Net::IMAP::StatusData
-
-Net::IMAP::StatusData represents contents of the STATUS response.
-
-=== Super Class
-
-Object
-
-=== Methods
-
-: mailbox
- Returns the mailbox name.
-
-: attr
- Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
- "UIDVALIDITY", "UNSEEN". Each value is a number.
-
-== Net::IMAP::FetchData
-
-Net::IMAP::FetchData represents contents of the FETCH response.
-
-=== Super Class
-
-Object
-
-=== Methods
-
-: 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.
-
-== Net::IMAP::Envelope
-
-Net::IMAP::Envelope represents envelope structures of messages.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: date
- Retunns a string that represents the date.
-
-: subject
- Retunns a string that represents the subject.
-
-: from
- Retunns an array of ((<Net::IMAP::Address>)) that represents the from.
-
-: sender
- Retunns an array of ((<Net::IMAP::Address>)) that represents the sender.
-
-: reply_to
- Retunns an array of ((<Net::IMAP::Address>)) that represents the reply-to.
-
-: to
- Retunns an array of ((<Net::IMAP::Address>)) that represents the to.
-
-: cc
- Retunns an array of ((<Net::IMAP::Address>)) that represents the cc.
-
-: bcc
- Retunns an array of ((<Net::IMAP::Address>)) that represents the bcc.
-
-: in_reply_to
- Retunns a string that represents the in-reply-to.
-
-: message_id
- Retunns a string that represents the message-id.
-
-== Net::IMAP::Address
-
-((<Net::IMAP::Address>)) represents electronic mail addresses.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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.
-
-== Net::IMAP::ContentDisposition
-
-Net::IMAP::ContentDisposition represents Content-Disposition fields.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: dsp_type
- Returns the disposition type.
-
-: param
- Returns a hash that represents parameters of the Content-Disposition
- field.
-
-== Net::IMAP::BodyTypeBasic
-
-Net::IMAP::BodyTypeBasic represents basic body structures of messages.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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.
-
-== Net::IMAP::BodyTypeText
-
-Net::IMAP::BodyTypeText represents TEXT body structures of messages.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: lines
- Returns the size of the body in text lines.
-
-And Net::IMAP::BodyTypeText has all methods of ((<Net::IMAP::BodyTypeBasic>)).
-
-== Net::IMAP::BodyTypeMessage
-
-Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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>)).
-
-== Net::IMAP::BodyTypeText
-
-=== Super Class
-
-Struct
-
-=== Methods
-
-: 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.
-
-== References
-
-: [IMAP]
- M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
- RFC 2060, December 1996.
-
-: [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.
-
-: [OSSL]
- http://www.openssl.org
-
-: [RSSL]
- http://savannah.gnu.org/projects/rubypki
-
-=end
-
-require "socket"
-require "monitor"
-require "digest/md5"
-begin
- require "openssl"
-rescue LoadError
-end
-
-module Net
- class IMAP
- include MonitorMixin
- if defined?(OpenSSL)
- include OpenSSL
- include SSL
- end
-
- attr_reader :greeting, :responses, :response_handlers
-
- SEEN = :Seen
- ANSWERED = :Answered
- FLAGGED = :Flagged
- DELETED = :Deleted
- DRAFT = :Draft
- RECENT = :Recent
-
- NOINFERIORS = :Noinferiors
- NOSELECT = :Noselect
- MARKED = :Marked
- UNMARKED = :Unmarked
-
- def self.debug
- return @@debug
- end
-
- def self.debug=(val)
- return @@debug = val
- end
-
- def self.add_authenticator(auth_type, authenticator)
- @@authenticators[auth_type] = authenticator
- end
-
- def disconnect
- @sock.shutdown unless @usessl
- @receiver_thread.join
- @sock.close
- end
-
- def capability
- synchronize do
- send_command("CAPABILITY")
- return @responses.delete("CAPABILITY")[-1]
- end
- end
-
- def noop
- send_command("NOOP")
- end
-
- def logout
- send_command("LOGOUT")
- end
-
- 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])
- send_data([data].pack("m").chomp)
- end
- end
- end
-
- def login(user, password)
- send_command("LOGIN", user, password)
- end
-
- def select(mailbox)
- synchronize do
- @responses.clear
- send_command("SELECT", mailbox)
- end
- end
-
- def examine(mailbox)
- synchronize do
- @responses.clear
- send_command("EXAMINE", mailbox)
- end
- end
-
- def create(mailbox)
- send_command("CREATE", mailbox)
- end
-
- def delete(mailbox)
- send_command("DELETE", mailbox)
- end
-
- def rename(mailbox, newname)
- send_command("RENAME", mailbox, newname)
- end
-
- def subscribe(mailbox)
- send_command("SUBSCRIBE", mailbox)
- end
-
- def unsubscribe(mailbox)
- send_command("UNSUBSCRIBE", mailbox)
- end
-
- def list(refname, mailbox)
- synchronize do
- send_command("LIST", refname, mailbox)
- return @responses.delete("LIST")
- end
- end
-
- def getquotaroot(mailbox)
- synchronize do
- send_command("GETQUOTAROOT", mailbox)
- result = []
- result.concat(@responses.delete("QUOTAROOT"))
- result.concat(@responses.delete("QUOTA"))
- return result
- end
- end
-
- def getquota(mailbox)
- synchronize do
- send_command("GETQUOTA", mailbox)
- return @responses.delete("QUOTA")
- end
- end
-
- # setquota(mailbox, nil) will unset quota.
- def setquota(mailbox, quota)
- if quota.nil?
- data = '()'
- else
- data = '(STORAGE ' + quota.to_s + ')'
- end
- send_command("SETQUOTA", mailbox, RawData.new(data))
- end
-
- # setacl(mailbox, user, nil) will remove rights.
- def setacl(mailbox, user, rights)
- if rights.nil?
- send_command("SETACL", mailbox, user, "")
- else
- send_command("SETACL", mailbox, user, rights)
- end
- end
-
- def getacl(mailbox)
- synchronize do
- send_command("GETACL", mailbox)
- return @responses.delete("ACL")[-1]
- end
- end
-
- def lsub(refname, mailbox)
- synchronize do
- send_command("LSUB", refname, mailbox)
- return @responses.delete("LSUB")
- end
- end
-
- def status(mailbox, attr)
- synchronize do
- send_command("STATUS", mailbox, attr)
- return @responses.delete("STATUS")[-1].attr
- end
- end
-
- 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
-
- def check
- send_command("CHECK")
- end
-
- def close
- send_command("CLOSE")
- end
-
- def expunge
- synchronize do
- send_command("EXPUNGE")
- return @responses.delete("EXPUNGE")
- end
- end
-
- def search(keys, charset = nil)
- return search_internal("SEARCH", keys, charset)
- end
-
- def uid_search(keys, charset = nil)
- return search_internal("UID SEARCH", keys, charset)
- end
-
- def fetch(set, attr)
- return fetch_internal("FETCH", set, attr)
- end
-
- def uid_fetch(set, attr)
- return fetch_internal("UID FETCH", set, attr)
- end
-
- def store(set, attr, flags)
- return store_internal("STORE", set, attr, flags)
- end
-
- def uid_store(set, attr, flags)
- return store_internal("UID STORE", set, attr, flags)
- end
-
- def copy(set, mailbox)
- copy_internal("COPY", set, mailbox)
- end
-
- def uid_copy(set, mailbox)
- copy_internal("UID COPY", set, mailbox)
- end
-
- def sort(sort_keys, search_keys, charset)
- return sort_internal("SORT", sort_keys, search_keys, charset)
- end
-
- def uid_sort(sort_keys, search_keys, charset)
- return sort_internal("UID SORT", sort_keys, search_keys, charset)
- end
-
- def add_response_handler(handler = Proc.new)
- @response_handlers.push(handler)
- end
-
- def remove_response_handler(handler)
- @response_handlers.delete(handler)
- end
-
- private
-
- CRLF = "\r\n"
- PORT = 143
-
- @@debug = false
- @@authenticators = {}
-
- def initialize(host, port = PORT, usessl = false, certs = nil, verify = false)
- super()
- @host = host
- @port = port
- @tag_prefix = "RUBY"
- @tagno = 0
- @parser = ResponseParser.new
- @sock = TCPSocket.open(host, port)
- if usessl
- unless defined?(OpenSSL)
- raise "SSL extension not installed"
- end
- @usessl = true
- @sock = SSLSocket.new(@sock)
-
- # verify the server.
- @sock.ca_file = certs if certs && FileTest::file?(certs)
- @sock.ca_path = certs if certs && FileTest::directory?(certs)
- @sock.verify_mode = VERIFY_PEER if verify
- @sock.verify_callback = VerifyCallbackProc if defined?(VerifyCallbackProc)
-
- @sock.connect # start ssl session.
- else
- @usessl = false
- end
- @responses = Hash.new([].freeze)
- @tagged_responses = {}
- @response_handlers = []
- @tag_arrival = new_cond
-
- @greeting = get_response
- if /\ABYE\z/ni =~ @greeting.name
- @sock.close
- raise ByeResponseError, resp[0]
- end
-
- @receiver_thread = Thread.start {
- receive_responses
- }
- end
-
- def receive_responses
- while resp = get_response
- synchronize do
- case resp
- when TaggedResponse
- @tagged_responses[resp.tag] = resp
- @tag_arrival.broadcast
- 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
- end
- @response_handlers.each do |handler|
- handler.call(resp)
- end
- end
- end
- end
-
- def get_tagged_response(tag, cmd)
- until @tagged_responses.key?(tag)
- @tag_arrival.wait
- end
- resp = @tagged_responses.delete(tag)
- case resp.name
- when /\A(?:NO)\z/ni
- raise NoResponseError, resp.data.text
- when /\A(?:BAD)\z/ni
- raise BadResponseError, resp.data.text
- else
- return resp
- end
- end
-
- def get_response
- buff = ""
- 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
- tag = generate_tag
- data = args.collect {|i| format_data(i)}.join(" ")
- if data.length > 0
- put_line(tag + " " + cmd + " " + data)
- else
- put_line(tag + " " + cmd)
- 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 send_data(*args)
- data = args.collect {|i| format_data(i)}.join(" ")
- put_line(data)
- end
-
- def put_line(line)
- line = line + CRLF
- @sock.print(line)
- if @@debug
- $stderr.print(line.gsub(/^/n, "C: "))
- end
- end
-
- def format_data(data)
- case data
- when nil
- return "NIL"
- when String
- return format_string(data)
- when Integer
- return format_number(data)
- when Array
- return format_list(data)
- when Time
- return format_time(data)
- when Symbol
- return format_symbol(data)
- else
- return data.format_data
- end
- end
-
- def format_string(str)
- case str
- when ""
- return '""'
- when /[\x80-\xff\r\n]/n
- # literal
- return "{" + str.length.to_s + "}" + CRLF + str
- when /[(){ \x00-\x1f\x7f%*"\\]/n
- # quoted string
- return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
- else
- # atom
- return str
- end
- end
-
- def format_number(num)
- if num < 0 || num >= 4294967296
- raise DataFormatError, num.to_s
- end
- return num.to_s
- end
-
- def format_list(list)
- contents = list.collect {|i| format_data(i)}.join(" ")
- return "(" + contents + ")"
- end
-
- DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
-
- def format_time(time)
- t = time.dup.gmtime
- return format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
- t.day, DATE_MONTH[t.month - 1], t.year,
- t.hour, t.min, t.sec)
- end
-
- def format_symbol(symbol)
- return "\\" + symbol.to_s
- 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)
- if attr.instance_of?(String)
- attr = RawData.new(attr)
- end
- synchronize do
- @responses.delete("FETCH")
- send_command(cmd, MessageSet.new(set), attr)
- 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 normalize_searching_criteria(keys)
- keys.collect! do |i|
- case i
- when -1, Range, Array
- MessageSet.new(i)
- else
- i
- end
- end
- end
-
- class RawData
- def format_data
- return @data
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Atom
- def format_data
- return @data
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class QuotedString
- def format_data
- return '"' + @data.gsub(/["\\]/n, "\\\\\\&") + '"'
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class Literal
- def format_data
- return "{" + @data.length.to_s + "}" + CRLF + @data
- end
-
- private
-
- def initialize(data)
- @data = data
- end
- end
-
- class MessageSet
- def format_data
- return format_internal(@data)
- end
-
- private
-
- def initialize(data)
- @data = data
- end
-
- def format_internal(data)
- case data
- when "*"
- return data
- when Integer
- ensure_nz_number(data)
- 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(",")
- else
- raise DataFormatError, data.inspect
- end
- end
-
- def ensure_nz_number(num)
- if num < -1 || num == 0 || num >= 4294967296
- raise DataFormatError, num.inspect
- end
- end
- end
-
- ContinuationRequest = Struct.new(:data, :raw_data)
- UntaggedResponse = Struct.new(:name, :data, :raw_data)
- TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
- ResponseText = Struct.new(:code, :text)
- ResponseCode = Struct.new(:name, :data)
- MailboxList = Struct.new(:attr, :delim, :name)
- MailboxQuota = Struct.new(:mailbox, :usage, :quota)
- MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
- MailboxACLItem = Struct.new(:user, :rights)
- StatusData = Struct.new(:mailbox, :attr)
- FetchData = Struct.new(:seqno, :attr)
- Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
- :to, :cc, :bcc, :in_reply_to, :message_id)
- Address = Struct.new(:name, :route, :mailbox, :host)
- ContentDisposition = Struct.new(:dsp_type, :param)
-
- class BodyTypeBasic < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- def media_subtype
- $stderr.printf("warning: media_subtype is obsolete.\n")
- $stderr.printf(" use subtype instead.\n")
- return subtype
- end
- end
-
- class BodyTypeText < Struct.new(:media_type, :subtype,
- :param, :content_id,
- :description, :encoding, :size,
- :lines,
- :md5, :disposition, :language,
- :extension)
- def multipart?
- return false
- end
-
- def media_subtype
- $stderr.printf("warning: media_subtype is obsolete.\n")
- $stderr.printf(" use subtype instead.\n")
- return subtype
- end
- end
-
- 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
-
- def media_subtype
- $stderr.printf("warning: media_subtype is obsolete.\n")
- $stderr.printf(" use subtype instead.\n")
- return subtype
- end
- end
-
- class BodyTypeMultipart < Struct.new(:media_type, :subtype,
- :parts,
- :param, :disposition, :language,
- :extension)
- def multipart?
- return true
- end
-
- def media_subtype
- $stderr.printf("warning: media_subtype is obsolete.\n")
- $stderr.printf(" use subtype instead.\n")
- return subtype
- end
- end
-
- class ResponseParser
- 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 )"((?:[^\x80-\xff\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 )"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)"|\
-(?# 5: LITERAL )\{(\d+)\}\r\n|\
-(?# 6: LPAR )(\()|\
-(?# 7: RPAR )(\)))/ni
-
- TEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\x80-\xff\r\n]*))/ni
-
- RTEXT_REGEXP = /\G(?:\
-(?# 1: LBRA )(\[)|\
-(?# 2: TEXT )([^\x00\x80-\xff\r\n]*))/ni
-
- CTEXT_REGEXP = /\G(?:\
-(?# 1: TEXT )([^\x00\x80-\xff\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
- match(T_CRLF)
- match(T_EOF)
- return result
- end
-
- def continue_req
- match(T_PLUS)
- match(T_SPACE)
- return ContinuationRequest.new(resp_text, @str)
- 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(?:LIST|LSUB)\z/ni
- return list_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(?:STATUS)\z/ni
- return status_response
- when /\A(?:CAPABILITY)\z/ni
- return capability_response
- else
- return text_response
- end
- else
- parse_error("unexpected token %s", token.symbol)
- end
- end
-
- def response_tagged
- tag = atom
- 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)
- return UntaggedResponse.new(name, data, @str)
- end
- end
-
- def msg_att
- match(T_LPAR)
- attr = {}
- while true
- token = lookahead
- case token.symbol
- when T_RPAR
- shift_token
- break
- when T_SPACE
- shift_token
- token = lookahead
- 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
- else
- parse_error("unknown attribute `%s'", token.value)
- 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
- 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)
- @lex_state = EXPR_BEG
- return Envelope.new(date, subject, from, sender, reply_to,
- to, cc, bcc, in_reply_to, message_id)
- 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
- 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
- match(T_LPAR)
- token = lookahead
- if token.symbol == T_LPAR
- result = body_type_mpart
- else
- result = body_type_1part
- end
- match(T_RPAR)
- @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
- else
- return body_type_basic
- end
- end
-
- def body_type_basic
- mtype, msubtype = media_type
- 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
- 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_mpart
- parts = []
- while true
- token = lookahead
- if token.symbol == T_SPACE
- shift_token
- break
- end
- parts.push(body)
- end
- mtype = "MULTIPART"
- msubtype = string.upcase
- param, disposition, language, extension = body_ext_mpart
- return BodyTypeMultipart.new(mtype, msubtype, parts,
- param, disposition, language,
- extension)
- end
-
- def media_type
- mtype = string.upcase
- match(T_SPACE)
- msubtype = string.upcase
- 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 = string.upcase
- 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 = string.upcase
- 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
- match(T_SPACE)
- 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 = string.upcase
- 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(string.upcase)
- 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 = ""
- 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.length.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 text_response
- token = match(T_ATOM)
- name = token.value.upcase
- match(T_SPACE)
- @lex_state = EXPR_TEXT
- token = match(T_TEXT)
- @lex_state = EXPR_BEG
- return UntaggedResponse.new(name, token.value)
- 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
- ##XXX data.push([user, rights])
- data.push(MailboxACLItem.new(user, rights))
- 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
- end
- data.push(number)
- end
- else
- data = []
- end
- return UntaggedResponse.new(name, data, @str)
- 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)
- data = []
- while true
- token = lookahead
- case token.symbol
- when T_CRLF
- break
- when T_SPACE
- shift_token
- end
- data.push(atom.upcase)
- end
- return UntaggedResponse.new(name, data, @str)
- end
-
- def resp_text
- @lex_state = EXPR_RTEXT
- token = lookahead
- if token.symbol == T_LBRA
- code = resp_text_code
- else
- code = nil
- end
- token = match(T_TEXT)
- @lex_state = EXPR_BEG
- return ResponseText.new(code, token.value)
- end
-
- def resp_text_code
- @lex_state = EXPR_BEG
- match(T_LBRA)
- token = match(T_ATOM)
- name = token.value.upcase
- case name
- when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE)\z/n
- result = ResponseCode.new(name, nil)
- 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
- match(T_SPACE)
- @lex_state = EXPR_CTEXT
- token = match(T_TEXT)
- @lex_state = EXPR_BEG
- result = ResponseCode.new(name, token.value)
- end
- match(T_RBRA)
- @lex_state = EXPR_RTEXT
- return 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
-
-# def flag_list
-# 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(flag)
-# end
-# return result
-# end
-
-# def flag
-# token = lookahead
-# if token.symbol == T_BSLASH
-# shift_token
-# token = lookahead
-# if token.symbol == T_STAR
-# shift_token
-# return token.value.intern
-# else
-# return atom.intern
-# end
-# else
-# return atom
-# end
-# 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|
- atom || flag.capitalize.intern
- }
- 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 atom
- end
- end
-
- def string
- token = match(T_QUOTED, T_LITERAL)
- return token.value
- end
-
- STRING_TOKENS = [T_QUOTED, T_LITERAL]
-
- def string_token?(token)
- return STRING_TOKENS.include?(token.symbol)
- end
-
- def atom
- result = ""
- while true
- token = lookahead
- if atom_token?(token)
- result.concat(token.value)
- shift_token
- else
- if result.empty?
- parse_error("unexpected token %s", token.symbol)
- else
- return result
- end
- end
- end
- end
-
- ATOM_TOKENS = [
- T_ATOM,
- T_NUMBER,
- T_NIL,
- T_LBRA,
- T_RBRA,
- T_PLUS
- ]
-
- def atom_token?(token)
- return ATOM_TOKENS.include?(token.symbol)
- end
-
- def number
- token = match(T_NUMBER)
- return token.value.to_i
- end
-
- def nil_atom
- match(T_NIL)
- return nil
- end
-
- def match(*args)
- 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
- end
-
- def lookahead
- unless @token
- @token = next_token
- end
- return @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] BEG_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("illegal @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.symbol
- $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
-
- class 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
- end
- add_authenticator "LOGIN", LoginAuthenticator
-
- class 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] ^= 0x36
- k_opad[i] ^= 0x5c
- end
-
- digest = Digest::MD5.digest(k_ipad + text)
-
- return Digest::MD5.hexdigest(k_opad + digest)
- end
- end
- add_authenticator "CRAM-MD5", CramMD5Authenticator
-
- class Error < StandardError
- end
-
- class DataFormatError < Error
- end
-
- class ResponseParseError < Error
- end
-
- class ResponseError < Error
- end
-
- class NoResponseError < ResponseError
- end
-
- class BadResponseError < ResponseError
- end
-
- class ByeResponseError < ResponseError
- end
- end
-end
-
-if __FILE__ == $0
- require "getoptlong"
-
- $stdout.sync = true
- $port = "imap2"
- $user = ENV["USER"] || ENV["LOGNAME"]
- $auth = "cram-md5"
-
- def usage
- $stderr.print <<EOF
-usage: #{$0} [options] <host>
-
- --help print this message
- --port=PORT specifies port
- --user=USER specifies user
- --auth=AUTH specifies auth type
-EOF
- end
-
- def get_password
- print "password: "
- system("stty", "-echo")
- begin
- return gets.chop
- ensure
- system("stty", "echo")
- print "\n"
- end
- end
-
- def get_command
- printf("%s@%s> ", $user, $host)
- if line = gets
- return line.strip.split(/\s+/)
- else
- return nil
- end
- end
-
- parser = GetoptLong.new
- parser.set_options(['--help', GetoptLong::NO_ARGUMENT],
- ['--port', GetoptLong::REQUIRED_ARGUMENT],
- ['--user', GetoptLong::REQUIRED_ARGUMENT],
- ['--auth', GetoptLong::REQUIRED_ARGUMENT])
- begin
- parser.each_option do |name, arg|
- case name
- when "--port"
- $port = arg
- when "--user"
- $user = arg
- when "--auth"
- $auth = arg
- when "--help"
- usage
- exit(1)
- end
- end
- rescue
- usage
- exit(1)
- end
-
- $host = ARGV.shift
- unless $host
- usage
- exit(1)
- end
-
- imap = Net::IMAP.new($host, $port)
- begin
- password = get_password
- imap.authenticate($auth, $user, password)
- while true
- cmd, *args = get_command
- break unless cmd
- begin
- case cmd
- when "list"
- for mbox in imap.list("", args[0] || "*")
- if mbox.attr.include?(Net::IMAP::NOSELECT)
- prefix = "!"
- elsif mbox.attr.include?(Net::IMAP::MARKED)
- prefix = "*"
- else
- prefix = " "
- end
- print prefix, mbox.name, "\n"
- end
- when "select"
- imap.select(args[0] || "inbox")
- print "ok\n"
- when "close"
- imap.close
- print "ok\n"
- when "summary"
- unless messages = imap.responses["EXISTS"][-1]
- puts "not selected"
- next
- end
- if messages > 0
- for data in imap.fetch(1..-1, ["ENVELOPE"])
- print data.seqno, ": ", data.attr["ENVELOPE"].subject, "\n"
- end
- else
- puts "no message"
- end
- when "fetch"
- if args[0]
- data = imap.fetch(args[0].to_i, ["RFC822.HEADER", "RFC822.TEXT"])[0]
- puts data.attr["RFC822.HEADER"]
- puts data.attr["RFC822.TEXT"]
- else
- puts "missing argument"
- end
- when "logout", "exit", "quit"
- break
- when "help", "?"
- print <<EOF
-list [pattern] list mailboxes
-select [mailbox] select mailbox
-close close mailbox
-summary display summary
-fetch [msgno] display message
-logout logout
-help, ? display help message
-EOF
- else
- print "unknown command: ", cmd, "\n"
- end
- rescue Net::IMAP::Error
- puts $!
- end
- end
- ensure
- imap.logout
- imap.disconnect
- end
-end