| NetxAP documentation | Contained in the NetxAP distribution. |
Net::IMAP - A client interface to IMAP (Internet Message Access Protocol).
WARNING: This code is in alpha release. Expect the interface to change release to release.
use Net::IMAP;
Net::IMAP provides a perl interface to the client portion of IMAP
(Internet Message Access Protocol).
add more meat - describe overall design (commands, responses, callbacks, convenience routines
Creates a new Net::IMAP object, connects to $host on port 143,
performs some preliminary setup of the session, and returns a
reference to the object.
Once connected, it processes the connection banner sent by the server.
If the considers the session to be preauthenticated, new notes the
fact, allowing commands to be issued without logging in.
The method also issues a capability command, and notes the result.
If the server does support IMAP4rev1, the method closes the connection
and returns undef.
The client will use non-synchronizing literals if the server supports
the LITERAL+ extension (RFC2088) and the NonSyncLits options is
set to 1.
The following Net::xAP options are relevant to Net::IMAP:
Synchronous => 1NonSyncLits => 0Debug => 0InternetDraft => 0Net::IMAP also understands the following options, specific to the module:
EOL => 'lf'Controls what style of end-of-line processing to presented to the
end-programmer. The default, 'lf', assumes that the programemr
wants to fling messages terminated with bare LFs when invoking append,
and when fetching messages. In this case, the module will map to/from
CRLF accordingly.
If EOL is set to 'crlf', the assumption is that the programmer
wants messages, or portions of messages, to be terminated with CRLF.
It also assumes the programmer is providing messages terminated with
the string when invoking the append method, and will not provide an
EOL mapping.
There are numerous commands in the IMAP protocol. Each of these are
mapped to a corresponding method in the Net::IMAP module.
Some commands can only be issued in certain protocol states. Some commands alter the state of the session. These facts are indicated in the documentation for the individual command methods.
The following list enumerates the protocol states:
The client has not authenticated with the server. Most commands are unavailable in this state.
The client has authenticated with the server.
The client has opened a mailbox on the server.
Sends a noop command to the server. It is valid in any protocol state.
This method is useful for placating the auto-logout god, or for triggering pending unsolicited responses from the server.
The capability method retrieves the capabilities the IMAP server
supports. This method is valid in any protocol state.
The server sends a capability response back to the client.
If the response does not indicate support for the LITERAL+
extension, the NonSyncLits option is forced off.
Logs off of the server. This method is valid in any protocol state.
Logs into the server using a simple plaintext password. This method is only valid when the protocol is in the non-authenticated state.
If the server supports RFC2221 (IMAP4 Login Referrals), the completion response could include a referral. See RFC2221 for further information about login referrals.
If successful, the session state is changed to authenticated.
Logs into the server using the authentication mechanism specified in
$authtype. This method is only valid when the protocol is in the
non-authenticated state.
The IMAP authenticate command is the same as that documented in
RFC2222 (Simple Authentication and Security Layer (SASL)), despite the
fact that IMAP predates SASL.
If successful, the session state is changed to authenticated.
The following authentication mechanisms are currently supported:
'login'This is a variation on the simple login technique, except that the
information is transmitted in Base64. This does not provide any
additional security, but does allow clients to use authenticate.
'cram-md5'This implements the authentication mechanism defined in RFC2195 (IMAP/POP AUTHorize Extension for Simple Challenge/Response). It uses keyed MD5 to avoid sending the password over the wire.
'anonymous'This implements the authentication mechanism defined in RFC2245 (Anonymous SASL Mechanism). Anonymous IMAP access is intended to provide access to public mailboxes or newsgroups.
The method returns undef is $authtype specifies an unsupported
mechanism or if the server does not advertise support for the
mechanism. The has_authtype method can be used to see whether the
server supports a particular authentication mechanism.
In general, if the server supports a mechanism supported by
Net::IMAP, the authenticate command should be used instead of
the login method.
Opens the specified mailbox with the intention of performing reading and writing. This method is valid only when the session is in the authenticated or selected states.
If successful, the server sends several responses: flags,
exists, resent, as well as ok responses containing a
unseen, permanentflags, uidnext, and uidvalidity codes.
If also changes the session state to selected.
If server returns a no response containing a newname response
code, this means $mailbox does not exist but the server thinks this
is because the folder was renamed. In this case, try specifiying the
new folder name provided with the newname response code.
Opens the specified mailbox in read-only mode. This method is valid only when the session is in the authenticated or selected states.
Creates the specified mailbox. This method is valid only when the session is in the authenticated or selected states.
The optional $partition argument is only valid with the Cyrus IMAP
daemon. Refer to the section 'Specifying Partitions with "create"'
the doc/overview file for that package for further information.
This feature can only be used by administrators creating new
mailboxes. Other servers will probably reject the command if this
argument is used. The results are undefined if another server accepts
a second argument.
Deletes the specified mailbox. Returns undef if $mailbox is the
currently open mailbox. This method is valid only when the session is
in the authenticated or selected states.
Renames the mailbox specified in $oldmailbox to the name specified
in $newmailbox. This method is valid only when the session is in
the authenticated or selected states.
The optional $partition argument is only valid with the Cyrus IMAP
daemon. Refer to the section 'Specifying Partitions with "rename"'
the doc/overview file for that package for further information.
This feature can only be used by administrators. Other servers will
probably reject the command if this argument is used. The results are
undefined if another server accepts a third argument.
Subscribe to the specified $mailbox. Subscribing in IMAP is
subscribing in Usenet News, except that the server maintains the
subscription list. This method is valid only when the session is in
the authenticated or selected states.
Unsubscribe from the specified $mailbox. This method is valid only
when the session is in the authenticated or selected states.
Send an IMAP list command to the server. This method is valid only
when the session is in the authenticated or selected states.
Although IMAP folders do not need to be implemented as directories,
think of an IMAP reference name as a parameter given to a cd or
chdir command, prior to checking for folders matching
$mailbox_pattern.
The $mailbox_pattern parameter allows a couple wildcard characters
to list subsets of the mailboxes on the server.
*, matches zero or more characters at the specified location,
but does not match hierarchy delimiter characters.
$mailbox_pattern is a %, matching
levels of hierarchy are also returned. In other words: subfolders.This method will fail, returning undef, if $mailbox_pattern is
*. This behavior is not built into the IMAP protocol; it is wired
into Net::IMAP. Doing otherwise could be rude to both the client
and server machines. If you want to know why, imagine doing
list('#news.', '*') on a machine with a full news feed. The %
character should be used to build up a folder tree incrementally.
If successful, the server sends a series of list responses.
Please note that the $referencename is an IMAPism, not a Perl
reference. Also note that the wildcards usable in $mailbox_pattern
are specific to IMAP. Perl regexps are not usable here.
Sends an IMAP lsub command to the server. The lsub command is
similar to the list command, except that the server only returns
subscribed mailboxes. This method is valid only when the session is
in the authenticated or selected states.
The parameters are the same as those for the list method.
If successful, the server sends a series of lsub responses.
Retrieves status information for the specified $mailbox. This
method is valid only when the session is in the authenticated or
selected states.
Per RFC2060, the @statusattrs can contain any of the following
strings:
The number of messages in the mailbox.
The number of messages with the \recent flag set.
The UID expected to be assigned to the next mailbox appended to the mailbox. This requires some explanation. Rather than using this value for prefetching the next UID, it should be used to detect whether messages have been added to the mailbox. The value will not change until messages are appended to the mailbox.
The unique identifier validity value of the mailbox.
The number of messages without the \seen flag set.
This method will fail, returning undef if $mailbox is the
currently open mailbox.
If successful, the server sends one or more status responses.
The status operation can be rather expensive on some folder implementations, so clients should use this method sparingly.
Appends the email message specified in $message to the mailbox
specified in $mailbox. This method is valid only when the session
is in the authenticated or selected states.
In general, the email message should be a real RFC822 message,
although exceptions such as draft messages are reasonable in some
situations. Also note that the line terminators in $message need
to be CRLF.
The Flags option allows a set of flags to be specified for the
message when it is appended. Servers are not required to honor this,
but most, if not all, do so.
The Date option forces the internaldate to the specified value. If
$date is a string, the format of the string is dd-mmm-yyyy
hh:mm:ss [-+]zzzz, where dd is the day of the month (starting from
1), mmm is the three-character abbreviation for the month name,
yyyy is the 4-digit year, hh is the hour, mm is the minutes,
ss is the seconds, and [-+]zzzz is the numeric timezone offset.
This happens to be the same format returned by the internaldate
item from the fetch command. If $date is a list reference, it is
expected to contain two elements: a time integer and a timezone offset
string. The timezone string is expected to be formatted as
[-+]zzzz. These two values will be used to synthesize a string in
the format expected by the IMAP server. As with the Flags options,
servers are not required to honor the Date option, but most, if not
all, do so.
Note that the options are specified at the end of the list of method
arguments. This is due to the fact that it is possible to have a
$mailbox named Flags or Date. Processing the options at the
end of the argument list simplifies argument processing. The order of
the arguments will be changed if enough people complain.
If server returns a no response containing a trycreate response
code, this means $mailbox does not exist but the server thinks the
command would have succeeded if the an appropriate create command
was issued. On the other hand, failure with no trycreate response
code generally means that a create should not be attempted.
Ask the server to perform a checkpoint of its data. This method is valid only when the session is in the selected state.
While not always needed, this should be called if the client issues a large quantity of updates to a folder in an extended session.
Close the current mailbox. This method is valid only when the session is in the selected state.
If successful, the session state is changed to authenticated.
Delete messages marked for deletion. This method is valid only when the session is in the selected state.
If successful, the server sends a series of expunge responses.
It will return undef is the mailbox is marked read-only.
Searches the mailbox for messages matching the criteria contained in
@searchkeys. This method is valid only when the session is in the
selected state.
The @searchkeys list contains strings matching the format described
in Section 6.4.4 of RFC2060.
If successful, the server send zero or more search responses. Lack
of a search response means the server found no matches. Note that
the server can send the results of one search in multiple responses.
Retrieves data about a set of messages. This method is valid only when the session is in the selected state.
The $msgset parameter identifies the set of messages from which to
retrieve the items of interest. The notation accepted is similar to
that found in .newsrc files, except that : is used to specify
ranges, instead of -. Thus, to specify messages 1, 2, 3, 5, 7, 8,
9, the following string could be used: '1:3,5,7:9'. The character
* can be used to indicate the highest message number in the
mailbox. Thus, to specify the last 4 messages in an 8-message
mailbox, you can use '5-*'.
The following list enumerates the items that can be retrieved with
fetch. Refer to Section 6.4.5 of RFC2060 for a description of each
of these items.
Important: the response item returned for a body.peek is body.
Please note that the items returning messages, or portion of messages, return strings terminated with CRLF.
RFC2060 also defines several items that are actually macros for other sets of items:
A macro equivalent to ('flags', 'internaldate', 'rfc822.size', 'envelope').
A macro equivalent to ('flags', 'rfc822.size', 'envelope', 'body').
A macro equivalent to ('flags', 'internaldate', 'rfc822.size').
The all, full, and fast items are not intended to be used
with other items.
If successful, the server responses with one or more fetch
responses.
If the completion response from a fetch command is no, the
client should send a noop command, to force any pending expunge
responses from the server, and retry the fetch command with
$msgset adjusted accordingly.
Sets various attributes for the messages identified in $msgset.
This method is valid only when the session is in the selected state.
The $msgset parameter is described in the section describing fetch.
The $itemname can be one of the following:
Replaces the current flags with the flags specified in @storeattrflags.
Adds the flags specified in @storeattrflags to the current flags.
Removes the flags specified in @storeattrflags from the current
flags.
The $itemname can also have .silent appended, which causes the
server to not send back update responses for the messages.
If successful, and .silent is used used in $itemname, the server
response with a series of fetch responses reflecting the updates to
the specified messages.
If the completion response from a store command is no, the
client should send a noop command, to force any pending expunge
responses from the server, and retry the store command with
$msgset adjusted accordingly.
The @storeattrflags is a list of flag strings.
Copy the messages $msgset to the specified mailbox. This method is
valid only when the session is in the selected state.
The $msgset parameter is described in the section describing fetch.
If server returns a no response containing a trycreate response
code, this means $mailbox does not exist but the server thinks the
command would have succeeded if the an appropriate create command
was issued. On the other hand, failure with no trycreate response
code generally means that a create should not be attempted.
A variant of copy that uses UIDs in $msgset, instead of message
numbers. This method is valid only when the session is in the
selected state.
A variant of fetch that uses UIDs, instead of message numbers, in
$msgset and fetch responses. This method is valid only when the
session is in the selected state.
A variant of search that uses UIDs, instead of message numbers, in
$msgset and search responses. This method is valid only when
the session is in the selected state.
A variant of store that uses UIDs, instead of message numbers, in
$msgset and fetch responses. This method is valid only when the
session is in the selected state.
In addition to the core protocol methods, Net::IMAP provides
several methods for accessing various pieces of information.
Returns a boolean valud indicating whether the IMAP session is preauthenticated.
Returns the list of capabilities supported by the server, minus the authentication capabilities. The list is not guaranteed to be in any specific order.
Returns a boolean value indicating whether the server supports the specified capability.
Returns a list of authentication types supported by the server.
Returns a boolean value indicating whether the server supports the specified authentication type.
Returns the quantity of messages in the currently selected folder.
Returns the quantity of recent messages in the currently selected folder.
Returns the message number of the first unseen messages in the currently selected folder.
Returns the uidvalidity value for the currently selected folder.
This is useful for IMAP clients that cache data in persistent storage.
Cache data for a mailbox should only be considered valid if the
uidvalidity is the same for both cached data and the remote
mailbox. See Section 2.3.1.1 of RFC2060 for further details.
Returns the uidnext value for the currently selected folder.
Returns the list of permanent flags the server has identified for the currently open mailbox.
If a \* flag is present, the server allows new persistent keywords
to be created.
Returns a boolean value indicating whether the server considers
$flag to be a permanent flag.
Returns a list of the flags associated with the mailbox.
Returns a boolean value indicating whether the given $flag is defined for the mailbox.
Returns the name of the currently open mailbox. Returns undef if
no mailbox is currently open.
Returns a boolean value indicating whether the currently open mailbox is read-only.
The following methods are available if the server advertises support for RFC2342 (IMAP4 Namespace). Refer to that RFC for additional information.
Sends a namespace command to the server, if the server advertises
support for the extension extension.
The following methods are available if the server advertises support for RFC2086 (IMAP4 ACL Extension). Refer to that RFC for additional information.
Sets the access control list for $identifier on $mailbox
according to the rights contained in $modrights.
The $identifier typically identifies an account name, but can also
specify abstract entities, such as groups.
The format for $modrights is documented in RFC2086.
Retrieves the access control list for $mailbox.
Deletes all access control list entries for $identifier from
$mailbox.
List the rights available to $identifier for $mailbox.
List the rights the current user has for $mailbox.
The following methods are available if the server advertises support for RFC2087 (IMAP4 Quota Extension). Refer to that RFC for additional information.
Lists the resource usage and limits for $quotaroot.
Sets the resource limits for $quotaroot to @setquotalist.
Valid values for @setquotalist are server-dependant.
Lists the quota roots for $mailbox.
The following method is available if the server advertises support for RFC2359 (IMAP4 UIDPLUS Extension). Refer to that RFC for additional information.
A variant of expunge that allows the operation to be narrowed to
the messages with UIDs specified in $msgset.
The $msgset parameter is described in the section describing fetch.
Many of the command methods result in the server sending back response
data. Net::IMAP processes each response by parsing the data,
packages it in an appropriate object, and optionally calls a
programmer-defined callback for the response. This callback mechanism
is how programmers get access to the data retrieved from the server.
Assigns a programmer-defined code reference to the associated untagged
response. When an untagged response matching $item is received,
$coderef is called, with the IMAP object and the associated
response object passed as parameters.
The default callback for the ok, bye, bad, and no untagged
responses includes code to output the text from alert responses to
stderr, using carp. If you set your own callback for these
responses, be sure to code handle alert codes. Per Section 7.1 of
RFC2060, clients are required to clearly display alert messages to
users.
As mention in the previous section, responses are parsed and packaged into response objects, which are then passed to callbacks. Each type of response has a corresponding object class. This section describes the various response objects provided.
All of the class names itemized below are prefixed with Net::IMAP.
As a general rule, IMAP nil items are set to undef in the parsed
data, and IMAP parenthetical lists are converted to list references
(of one form or another). In addition, atoms, quoted strings, and
literals are presented as Perl strings.
The condition responses (ok, no, bad, bye, and preauth)
can include a response code. Refer to Section 7.1 in RFC2060 for a
description of each of the standard response codes.
This is the object class for completion responses.
Returns a boolean value indicating whether the response is tagged. In
the case of tagged completion responses, this value is always 1.
Returns a boolean value indicating whether the TRYCREATE response
code is present in the response. This can be used after a failed
append or copy command to determine whether the server thinks
the operation would succeed if a create was issued for the
associated mailbox.
This class is common to all untagged server responses.
Returns a string containing the tag associated with the response. In
the case of untagged responses, this is always *.
Returns a boolean value indicating whether the response is tagged.
Obviously, in the case of untagged responses, this value is always
0.
Returns a reference to the parent IMAP object.
This is a container for untagged ok responses from the server.
Returns a list reference containing response code elements in the
response. Returns undef if no response code is present.
Returns the name of the response. In the case of Ok, this returns
'ok'. This method is provided as a convenience for end-programmers
wanting to write one common subroutine for one or more of the
responses Ok, No, Bad, and Bye.
This is a container for untagged no responses from the server.
This is a container for untagged bad responses from the server.
This is a container for untagged bye responses from the server.
This is a container for expunge responses from the server.
The information returned by qty_messages is automatically updated
when expunge responses are received.
Returns the message number specified in the expunge response.
This is a container for capability responses.
Returns the list of capabilities supported by the server, minus the authentication capabilities. The list is not guaranteed to be in any specific order.
Returns a boolean value indicating whether the server supports the specified capability.
Returns a list of authentication types supported by the server.
Returns a boolean value indicating whether the server supports the specified authentication type.
This is a container for list responses.
Returns the name of the mailbox contained in the object.
Returns the hierarchy delimiter associated with the mailbox.
Returns a list of the flags associated with the mailbox.
Returns a boolean value indicating whether the given $flag is defined for the mailbox.
This is a container for lsub responses. It provides the same
interface as the Net::IMAP::List class.
This is a container for fetch responses.
Responses for partial fetches bear special mention. While both the
starting byte and quantity of bytes are specified when doing partial
fetches with the fetch command, the corresponding response will
only show the starting byte. In other words, the command
$imap->fetch(1, 'body[]<0.1024>' will, if successful,
result in a fetch response item of body[]<0> containing a
1024 octet value. To match a given response for a partial fetch, you
might need to use length to match it up with the corresponding item
specified in the fetch command.
Returns the message number identified in the response.
Returns the list of data item names contained in the response. The list is not guaranteed to be in any specific order.
Returns the data associated with the specified data item.
The following list enumerates the data types associated with each fetch item:
Net::IMAP::Envelope
Net::IMAP::BodyStructure
Net::IMAP::BodyStructure
Net::IMAP::Flags
Integer
Integer
String
This is a container for status responses.
Returns a string containing the mailbox the status information is associated with.
Returns the list of status items contains in the status response.
Returns the value of the $item status item.
This is a container for search responses.
Returns the list of message numbers contained in the response.
This is a container for flags responses.
Returns the list of flags contained in the response.
Returns a boolean value indicating whether the specified flag is contained in the response.
As a convenience, the information from the flags response is also
stored in the parent Net::IMAP object, and is available via
Net::IMAP versions of the flags and has_flags methods.
This is a container for exists responses.
Returns the quantity of messages in the currently selected mailbox.
This is information is also available in the qty_messages method in
the Net::IMAP class.
This is a container for recent responses.
Returns the number of messages with the \recent flag set.
This information is also available in the qty_recent method in the
Net::IMAP class.
This is a container for namespace responses.
With no argument specified, returns a list of personal namespaces. If
$namespace is specified, returns the delimiter character for the
specific personal namespace.
With no argument specified, returns a list of other users' namespaces.
If $namespace is specified, returns the delimiter character for the
specific other users' namespace.
This is a container for acl responses>
Returns the name of the mailbox associated with the given ACL data.
Returns a list of identifiers contained in the ACL data.
This is a container for listrights responses.
Returns the name of the mailbox associated with the given rights.
Returns a string containing the identifier associated with the rights.
Returns a string containing the rights contained in the response.
This is a container for myrights responses>
Returns the name of the mailbox associated with the given rights.
Returns a string containing the rights contained in the response.
This is a container for quota responses.
Returns a string containing the name of the quota root in the response.
Returns a list of the quotas contained in the response.
Returns the usage value associated with the given $quota. Returns
undef is the given $quota is not present in the response.
Returns the usage limit associated with the given $quota. Returns
undef is the given $quota is not present in the response.
This is a container for quotaroot responses.
Returns the name of the mailbox associated with the quotaroot data.
If called in an array context, returns the list of quotaroots associated with the mailbox. If called in a scalar context, returns a list reference.
A fetch response can be relatively complicated. This section
documents various classes and methods associated with the various
pieces of information available in fetch responses.
This is a container for bodystructure items in fetch responses.
Returns a string containing the MIME type of the message. This is the
left-hand portion of a MIME media type. For example, the type of
text/plain is text.
Returns a string containing the MIME subtype of the message. This is
the right-hand portion of a MIME media type. For example, the subtype
of text/plain is plain.
Returns a reference to a hash containing the key/value attribute pairs
in the Content-Type field.
If, for example, the Content-Type field was:
Content-Type: text/plain; charset=us-ascii
The hash would contain one entry the a key of charset, and a value
of us-ascii. The key is always forced to be lowercase, but the
case of the value is retained from the server.
Returns the disposition type in the Content-Disposition field.
Returns undef if no such field exists.
Returns a reference to a hash containing the key/value attributer
pairs in the Content-Disposition field. A reference to an empty
hash is returned if no such field exists, or if there are no
parameters in the field.
Returns a reference to a list of the language tags present in the
Content-Language field. Returns a reference to an empty hash if no
such field is present.
This is a container for BodyStructure objects that are multipart entities.
Returns a list reference of the body parts contained in the multipart entity.
This is a container for singlepart entities in BodyStructure and
Multipart objects.
Return a string containing the contents of the Content-ID field, if
one is present, otherwise returns undef.
Return a string containing the contents of the Content-Description
field, if one is present, otherwise returns undef.
Returns a string containing the contents of the
Content-Transfer-Encoding field. Returns undef if no such field
is in the entity.
Returns the number of octets in the entity.
If the MIME content type is message/rfc822 or the major type is
text, returns the number of lines in the entity, else returns undef.
If the MIME content type is message/rfc822, returns a
Net::IMAP::Envelope object, otherwise returns undef.
If the MIME content type is message/rfc822, returns a
Net::IMAP::BodyStructure object, otherwise returns undef.
Returns a string containing the contents of the Content-MD5 field.
Returns undef if no such field is in the entity.
This is a container for envelope data in fetch responses.
For those familiar with SMTP, this is not the same type envelope.
Rather, it is a composite structure containing key source,
destination, and reference information in the message. When retrieved
from the server, it is populated into a Net::IMAP::Envelope object.
The following methods are available.
Returns a string with the contents of the Date field.
Returns a string with the contents of the Subject field.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the From field.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the Sender field. If no Sender field is present in
the message, the server will default it to the contents of the From
field.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the Reply-To field. If no Reply-To field is present
in the message, the server will default it to the contents of the
From field.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the Tofield. Will return undef if no To field
exists in the message.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the Cc field. Will return undef if no Cc field
exists in the message.
Returns a list reference of Net::IMAP::Addr objects with the
contents of the Bcc field. Will return undef if no Bcc field
exists in the message.
Returns a string with the contents of the In-Reply-To field.
Returns undef if no such field is present in the message.
Returns a string with the contents of the Date field. Returns
undef if no such field is present in the message.
This is a container for address structures in Envelope objects.
Returns a string containing the phrase portion of the address, or
undef if no phrase is present.
Returns a string containing the route portion of the address, or
undef if no route information is present.
Returns a string containing the localpart portion of the address, or
undef if no localpart is present.
Returns a string containing the domain portion of the address, or
undef if no domain is present.
Returns a string representation of the contents of the object.
Minimal testing has been done against the various IMAP server
implementations. Refer to BUGS for known bugs/malfeatures.
Kevin Johnson <kjj@pobox.com>
Copyright (c) 1997-1999 Kevin Johnson <kjj@pobox.com>.
All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| NetxAP documentation | Contained in the NetxAP distribution. |
#!/usr/local/bin/perl # # Copyright (c) 1997-1999 Kevin Johnson <kjj@pobox.com>. # # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl # itself. # # $Id: IMAP.pm,v 1.2 1999/10/03 14:56:21 kjj Exp $ require 5.005; package Net::IMAP; use strict;
use Net::xAP; use Carp; use MIME::Base64; use Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex); use vars qw($VERSION @ISA $AUTOLOAD); $VERSION = "0.02"; @ISA = qw(Net::xAP); use constant ATOM => Net::xAP::ATOM; use constant ASTRING => Net::xAP::ASTRING; use constant PARENS => Net::xAP::PARENS; use constant SASLRESP => Net::xAP::SASLRESP;
use constant IMAP_STATE_NOT_AUTH => 1; use constant IMAP_STATE_AUTH => 2; use constant IMAP_STATE_SELECT => 4; use constant IMAP_STATE_ANY => 7; my %untagged_callbacks = ( 'ok' => [\&_default_aux_callback], 'bye' => [\&_default_aux_callback], 'bad' => [\&_default_aux_callback], 'no' => [\&_default_aux_callback], 'capability' => [undef], 'list' => [undef], 'lsub' => [undef], 'status' => [undef], 'search' => [undef], 'flags' => [undef], 'exists' => [undef], 'recent' => [undef], 'expunge' => [undef], 'fetch' => [undef], 'namespace' => [undef], 'acl' => [undef], 'listrights' => [undef], 'myrights' => [undef], 'quota' => [undef], 'quotaroot' => [undef], ); my %cmd_callbacks = ( 'noop' => [undef, IMAP_STATE_ANY], 'capability' => [undef, IMAP_STATE_ANY], 'logout' => [undef, IMAP_STATE_ANY], 'authenticate' => ['_login_cmd_callback', IMAP_STATE_NOT_AUTH], 'login' => ['_login_cmd_callback', IMAP_STATE_NOT_AUTH], 'select' => ['_select_cmd_callback', IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'examine' => ['_select_cmd_callback', IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'create' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'delete' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'rename' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'subscribe' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'list' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'lsub' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'status' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'append' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'check' => [undef, IMAP_STATE_SELECT], 'close' => ['_close_cmd_callback', IMAP_STATE_SELECT], 'expunge' => [undef, IMAP_STATE_SELECT], 'search' => [undef, IMAP_STATE_SELECT], 'fetch' => [undef, IMAP_STATE_SELECT], 'store' => [undef, IMAP_STATE_SELECT], 'copy' => [undef, IMAP_STATE_SELECT], 'uid copy' => [undef, IMAP_STATE_SELECT], 'uid fetch' => [undef, IMAP_STATE_SELECT], 'uid search' => [undef, IMAP_STATE_SELECT], 'uid store' => [undef, IMAP_STATE_SELECT], # Extension commands: 'namespace' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'setacl' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'getacl' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'deleteacl' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'listrights' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'myrights' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'getquota' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'setquota' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'getquotaroot' => [undef, IMAP_STATE_AUTH|IMAP_STATE_SELECT], 'uid expunge' => [undef, IMAP_STATE_SELECT], ); my %_system_flags = ( '\seen' => 1, '\answered' => 1, '\flagged' => 1, '\deleted' => 1, '\draft' => 1, '\recent' => 1, );
sub new { my $class = shift; my $type = ref($class) || $class; my $host = shift if @_ % 2; my %options = @_; my $self = Net::xAP->new($host, 'imap2(143)', Timeout => 10, %options) or return undef; bless $self, $class; $self->{Options}{EOL} ||= 'lf'; $self->{Options}{EOL} = lc($self->{Options}{EOL}); # force lower-case $self->{PreAuth} = 0; $self->{Banner} = undef; $self->{Capabilities} = (); $self->_init_mailbox; $self->{Disconnect} = 0; $self->{State} = IMAP_STATE_NOT_AUTH; $self->{ResponseCallback} = $self->imap_response_callback; STDERR->autoflush(1); $self->_get_banner or return undef; # the little back-flip here with the Synchronous option ensures that # the capability command is issued in Synchronous mode my $mode = $self->{Options}{Synchronous}; # save current sync mode $self->{Options}{Synchronous}++; # force sync mode on my $resp = $self->capability; $self->{Options}{Synchronous} = $mode; # restore previous sync mode if ($resp->status ne 'ok') { carp "capability command failed on initial connection"; $self->close_connection or carp "error closing connection: $!"; $! = 5; # *sigh* error reporting needs to be improved return undef; } return $self; } sub _init_mailbox { my $self = shift; $self->{Mailbox} = ''; $self->{MailboxStatus} = (); $self->{MailboxStatus}{'recent'} = 0; $self->{MailboxStatus}{'unseen'} = 0; $self->{MailboxStatus}{'exists'} = 0; $self->{MailboxStatus}{'uidvalidity'} = 0; $self->{MailboxStatus}{'uidnext'} = 0; $self->{MailboxStatus}{'flags'} = (); } sub debug_text { $_[2] =~ /^(\d+ LOGIN [^\s]+)/i ? "$1 ..." : $_[2] } sub _get_banner { my $self = shift; my $str = $self->getline; my $list = $self->parse_fields($str); return undef unless defined($list); if (($list->[0] eq '*') && ($list->[1] =~ /^preauth$/i)) { $self->{PreAuth}++; $self->{State} = IMAP_STATE_AUTH; } elsif (($list->[0] ne '*') || ($list->[1] !~ /^ok$/i)) { return undef; } my $supports_imap4rev1 = 0; for my $item (@{$list}) { $supports_imap4rev1++ if ($item =~ /^imap4rev1$/i); } unless ($supports_imap4rev1) { $self->close_connection; return undef; } $self->{Banner} = $list; return 1; } sub DESTROY { my $self = shift; } sub AUTOLOAD { my $self = shift; my $cmd = $AUTOLOAD; $cmd =~ s/^.*:://; carp("unknown command: $cmd"); return undef; } ###############################################################################
sub noop { $_[0]->imap_command('noop') }
sub capability { $_[0]->imap_command('capability') }
sub logout { $_[0]->{Disconnect}++; $_[0]->imap_command('logout'); }
sub login { $_[0]->imap_command('login', ASTRING, $_[1], ASTRING, $_[2]) }
my %auth_funcs = ( 'LOGIN' => \&authenticate_login, 'CRAM-MD5' => \&authenticate_cram, 'ANONYMOUS' => \&authenticate_anonymous, ); my @auth_strings; sub authenticate { my $authtype = uc($_[1]); return undef unless defined($auth_funcs{$authtype}); return undef unless defined($_[0]->has_authtype($authtype)); my $func = $auth_funcs{$authtype}; @auth_strings = @_[2..$#_]; $_[0]->imap_command('authenticate', ATOM, $authtype, SASLRESP, $func); } sub authenticate_login { my $i = shift; return undef unless defined($auth_strings[$i]); return encode_base64($auth_strings[$i], ''); } sub authenticate_cram { my $i = shift; my $challenge = shift; if ($i == 0) { $challenge = decode_base64($challenge); $challenge = hmac_md5_hex($challenge, $auth_strings[1]); $auth_strings[1] = undef; return(encode_base64("$auth_strings[0] $challenge", '')); } return undef; } sub authenticate_anonymous { my $i = shift; return(encode_base64(join(' ', @auth_strings), '')) if ($i == 0); return undef; }
sub select { $_[0]->{Mailbox} = $_[1]; my $ret = $_[0]->imap_command('select', ASTRING, _encode_mailbox($_[1])); $_[0]->{Mailbox} = '' unless defined($ret); return $ret; }
sub examine { $_[0]->imap_command('examine', ASTRING, _encode_mailbox($_[1])) }
sub create { my @args = (ASTRING, _encode_mailbox($_[1])); push @args, ATOM, $_[2] if (defined($_[2])); $_[0]->imap_command('create', @args); }
sub delete { return undef if ($_[0]->{Mailbox} eq $_[1]); $_[0]->imap_command('delete', ASTRING, _encode_mailbox($_[1])); }
sub rename { my @args = (ASTRING, _encode_mailbox($_[1]), ASTRING, _encode_mailbox($_[2])); push @args, ATOM, $_[3] if defined($_[3]); $_[0]->imap_command('rename', @args); }
sub subscribe { $_[0]->imap_command('subscribe', ASTRING, _encode_mailbox($_[1])) }
sub unsubscribe { $_[0]->imap_command('unsubscribe', ASTRING, _encode_mailbox($_[1])) }
sub list { return undef if ($_[2] eq '*'); $_[0]->imap_command('list', ASTRING, _encode_mailbox($_[1]), ASTRING, _encode_mailbox($_[2])); }
sub lsub { $_[0]->imap_command('lsub', ASTRING, _encode_mailbox($_[1]), ASTRING, _encode_mailbox($_[2])) }
sub status { my $self = shift; my $mailbox = shift; return undef if ($self->{Mailbox} eq $mailbox); $self->imap_command('status', ASTRING, _encode_mailbox($mailbox), PARENS, [@_]); }
sub append { my $self = shift; my $mailbox = shift; my $lit = shift; my %options = @_; my @args; push @args, ASTRING, _encode_mailbox($mailbox); if (defined($options{Flags})) { for my $flag (@{$options{Flags}}) { unless ($self->_valid_flag($flag)) { carp "$flag is not a system flag"; return undef; } } push @args, PARENS, [@{$options{Flags}}]; } if (defined($options{Date})) { my $date; if ((ref($options{Date}) eq 'ARRAY') && defined($options{Date}->[1])){ my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my @gmtime = gmtime($options{Date}->[0]); $date = sprintf("%02d-%2s-%4d %02d:%02d:%02d %s", $gmtime[3], $months[$gmtime[4]], $gmtime[5] + 1900, $gmtime[2], $gmtime[1], $gmtime[0], $options{Date}->[1]); } else { $date = $options{Date}; } if ($date !~ /^[ \d][\d]-[a-zA-Z]{3}-\d{4} \d\d:\d\d:\d\d [\+\-]\d{4}$/) { carp "invalid date value for append command"; return undef; } push @args, ATOM, "\"$date\""; } $lit =~ s/\n/\r\n/mg if ($self->{Options}{EOL} eq 'lf'); push @args, ASTRING, $lit; $self->imap_command('append', @args); }
sub check { $_[0]->imap_command('check') }
sub close { $_[0]->imap_command('close') }
sub expunge { return undef if $_[0]->is_readonly; $_[0]->imap_command('expunge'); }
sub search { my $self = shift; my @args; if ($_[0] =~ /^charset$/i) { shift; my $charset = shift; push @args, ATOM, 'charset', ASTRING, $charset; } push @args, map { (ATOM, $_) } @_; $self->imap_command('search', @args); }
sub fetch { my $self = shift; my $msgset = shift; my @args; if (scalar(@_) == 1) { push @args, ATOM, shift; } else { push @args, PARENS, [@_]; } $self->imap_command('fetch', ATOM, $msgset, @args); }
sub store { my $self = shift; my $msgset = shift; my $itemname = shift; for my $flag (@_) { unless ($self->_valid_flag($flag)) { carp "$flag is not a system flag"; return undef; } } $self->imap_command('store', ATOM, $msgset, ATOM, $itemname, PARENS, [@_]); }
sub copy { $_[0]->imap_command('copy', ATOM, $_[1], ASTRING, _encode_mailbox($_[2])) }
sub uid_copy { $_[0]->imap_command('uid copy', ATOM, $_[1], ASTRING, _encode_mailbox($_[2])) }
sub uid_fetch { my $self = shift; my $msgset = shift; my @args; if (scalar(@_) == 1) { push @args, ATOM, shift; } else { push @args, PARENS, [@_]; } $self->imap_command('uid fetch', ATOM, $msgset, @args); }
sub uid_search { my $self = shift; my @args; if ($_[0] =~ /^charset$/i) { shift; my $charset = shift; push @args, ATOM, 'charset', ASTRING, $charset; } push @args, map { (ATOM, $_) } @_; $self->imap_command('uid search', @args); }
sub uid_store { my $self = shift; my $msgset = shift; my $itemname = shift; for my $flag (@_) { unless ($self->_valid_flag($flag)) { carp "$flag is not a system flag"; return undef; } } $self->imap_command('uid store', ATOM, $msgset, ATOM, $itemname, PARENS, [@_]); } ###############################################################################
sub is_preauth { $_[0]->{PreAuth} }
sub banner { $_[0]->{Banner} }
sub capabilities { keys %{$_[0]->{Capabilities}} }
sub has_capability { defined($_[0]->{Capabilities}{uc($_[1])}) }
sub authtypes { keys %{$_[0]->{AuthTypes}} }
sub has_authtype { defined($_[0]->{AuthTypes}{uc($_[1])}) }
sub qty_messages { $_[0]->{MailboxStatus}{'exists'} }
sub qty_recent { $_[0]->{MailboxStatus}{'recent'} }
sub first_unseen { $_[0]->{MailboxStatus}{'unseen'} }
sub uidvalidity { $_[0]->{MailboxStatus}{'uidvalidity'} }
sub uidnext { $_[0]->{MailboxStatus}{'uidnext'} }
sub permanentflags { keys %{$_[0]->{MailboxStatus}{'permanentflags'}} }
sub is_permanentflag { defined($_[0]->{MailboxStatus}{'permanentflags'}{lc($_[1])}); }
sub flags { keys %{$_[0]->{MailboxStatus}{'flags'}} }
sub has_flag { defined($_[0]->{MailboxStatus}{'flags'}{lc($_[1])}) }
sub mailbox { $_[0]->{Mailbox} }
sub is_readonly { $_[0]->{ReadOnly} } #------------------------------------------------------------------------------ sub _encode_mailbox { my $str = $_[0]; $str =~ s/&/&-/g; return $str; } sub _decode_mailbox { my $str = $_[0]; $str =~ s/&-/&/g; return $str; } ###############################################################################
sub namespace { my $self = shift; return undef unless $self->has_capability('NAMESPACE'); $self->imap_command('namespace'); } ###############################################################################
sub setacl { my $self = shift; return undef unless $self->has_capability('ACL'); $self->imap_command('setacl', ASTRING, _encode_mailbox($_[0]), ASTRING, $_[1], ASTRING, $_[2]); }
sub getacl { my $self = shift; return undef unless $self->has_capability('ACL'); $self->imap_command('getacl', ASTRING, _encode_mailbox($_[0])); }
sub deleteacl { my $self = shift; return undef unless $self->has_capability('ACL'); $self->imap_command('deleteacl', ASTRING, _encode_mailbox($_[0]), ASTRING, $_[1]); }
sub listrights { my $self = shift; return undef unless $self->has_capability('ACL'); $self->imap_command('listrights', ASTRING, _encode_mailbox($_[0]), ASTRING, $_[1]); }
sub myrights { my $self = shift; return undef unless $self->has_capability('ACL'); $self->imap_command('myrights', ASTRING, _encode_mailbox($_[0])); } ###############################################################################
sub getquota { my $self = shift; return undef unless $self->has_capability('QUOTA'); $self->imap_command('getquota', ASTRING, $_[0]); }
sub setquota { my $self = shift; my $quotaroot = shift; return undef unless $self->has_capability('QUOTA'); $self->imap_command('setquota', ASTRING, $quotaroot, PARENS, [@_]); }
sub getquotaroot { return undef unless $_[0]->has_capability('QUOTA'); $_[0]->imap_command('getquotaroot', ASTRING, _encode_mailbox($_[1])); } ###############################################################################
sub uid_expunge { return undef unless $_[0]->has_capability('UIDPLUS'); $_[0]->imap_command('uid expunge', ATOM, $_[1]); } ############################################################################### sub imap_command { my $self = shift; if (!defined($cmd_callbacks{$_[0]})) { carp("unknown imap command: $_[0]"); return undef; } unless ($cmd_callbacks{$_[0]}->[1] & $self->{State}) { carp("invalid state for issuing $_[0] command"); return undef } $self->command($self->imap_cmd_callback($_[0]), @_); } ###############################################################################
sub set_untagged_callback { my $self = shift; my $item = shift; my $funcref = shift; return undef unless defined($untagged_callbacks{$item}); $untagged_callbacks{$item}->[0] = $funcref; } #------------------------------------------------------------------------------ sub imap_cmd_callback { my $self = shift; my $cmd = shift; return sub { my $resp = shift; return unless (defined($cmd_callbacks{$cmd}) && defined($cmd_callbacks{$cmd}->[0])); my $func = $cmd_callbacks{$cmd}->[0]; return $self->$func($resp); } } sub imap_response_callback { my $self = shift; # my $seq = $self->next_sequence; return sub { my $response = shift; my ($tag, $rest) = split(/\s/, $response, 2); if ($tag eq '*') { return $self->_imap_process_untagged_response($rest); } elsif ($tag =~ /^\d+$/) { return $self->_imap_process_tagged_response($tag, $rest); } else { croak("gack! server returned bogus tag: [$tag]"); } } } sub _imap_process_untagged_response { my $self = shift; my $str = shift; my @args; my $num; my ($cmd, $rest) = split(/\s/, $str, 2); if ($cmd =~ /^\d+$/) { push @args, $cmd; ($cmd, $rest) = split(/\s/, $rest, 2); } push @args, $rest if defined($rest); $cmd = lc($cmd); if (defined($untagged_callbacks{$cmd})) { my $class = "Net::IMAP::" . ucfirst(lc($cmd)); my $ret = $class->new($self, @args); # trigger a user callback, maybe - user callback is passed $self # and the object created by the internal callback if (defined($ret)) { if (defined($untagged_callbacks{$cmd}->[0])) { &{$untagged_callbacks{$cmd}->[0]}($self, $ret); } $self->debug_print(0, "untagged resp callback returned $ret") if $self->debug; } else { carp("untagged resp callback returned undef"); } return undef; } else { carp("received unknown response from server: [$cmd]\n"); } } sub _imap_process_tagged_response { my $self = shift; my $tag = shift; my $str = shift; my $resp = Net::IMAP::Response->new; my ($cond, $text) = split(/\s/, $str, 2); my $resp_code = undef; if (substr($text, 0, 1) eq '[') { ($resp_code, $text) = _extract_resp_code($text); } $resp->{Sequence} = $tag; $resp->{Status} = lc($cond); $resp->{StatusCode} = $resp_code; $resp->{Text} = $text; if ($self->{Disconnect}) { $self->close_connection or carp "error closing connection: $!"; } return $resp; } ############################################################################### sub _select_cmd_callback { my $self = shift; my $resp = shift; if ($resp->status eq 'ok') { $self->{State} = IMAP_STATE_SELECT; my $status = $resp->status_code; $self->{ReadOnly} = (defined($status) && ($status->[0] eq 'read-only')); } else { $self->{State} = IMAP_STATE_AUTH; $self->{Mailbox} = ''; } } sub _login_cmd_callback { $_[0]->{State} = IMAP_STATE_AUTH if ($_[1]->status eq 'ok'); } sub _close_cmd_callback { if ($_[1]->status eq 'ok') { $_[0]->_init_mailbox; $_[0]->{State} = IMAP_STATE_AUTH; } } #------------------------------------------------------------------------------ sub _default_aux_callback { my $self = shift; my $resp = shift; my $code = $resp->code; if (defined($code) && ($code->[0] eq 'alert')) { carp "Alert: ", $resp->text, "\n"; } } ############################################################################### sub _valid_flag { ((substr($_[1], 0, 1) ne "\\") || defined($_system_flags{lc($_[1])})) } ############################################################################### sub _extract_resp_code { my $line = shift; $line =~ m{ \[ ([^\]]+) # response code \] (?: \s (.*) # remainder of response line )? $ }x; my $resp_code = $1; my $rest = $2; my $resp_code_list = Net::xAP->parse_fields($resp_code); $resp_code_list->[0] = lc($resp_code_list->[0]); return($resp_code_list, $rest); } ############################################################################### # use Data::Dumper; # sub _dump_internals { print STDERR "----\n", Dumper($_[0]), "----\n" } ###############################################################################
package Net::IMAP::Response; use vars qw(@ISA); @ISA = qw(Net::xAP::Response); sub is_tagged { 1 }
sub has_trycreate { my $status_code = $_[0]->status_code; return (defined($status_code) && (lc($status_code->[0]) eq 'trycreate')); } ###############################################################################
package Net::IMAP::UntaggedResponse; sub tag { '*' } sub is_tagged { 0 } sub parent { $_[0]->{Parent} } #------------------------------------------------------------------------------ package Net::IMAP::Cond; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); use Carp; sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my $resp_code = undef; if (substr($str, 0, 1) eq '[') { ($resp_code, $str) = Net::IMAP::_extract_resp_code($str); } $self->{RespCode} = $resp_code; $self->{Text} = $str; carp "Alert: $str\n" if (defined($resp_code) && $resp_code->[0] eq 'alert'); if (($self->name eq 'bye') && !$self->parent->{Disconnect}) { # a logout command wasn't issued, so it's probably the result of # an autologout timer expiring $self->parent->close_connection or carp "error closing connection: $!"; } return $self; } sub code { $_[0]->{RespCode} } sub name { undef } #------------------------------------------------------------------------------
package Net::IMAP::Ok; use vars qw(@ISA); @ISA = qw(Net::IMAP::Cond); sub name { 'ok' }; #------------------------------------------------------------------------------
package Net::IMAP::No; use vars qw(@ISA); @ISA = qw(Net::IMAP::Cond); sub name { 'no' }; #------------------------------------------------------------------------------
package Net::IMAP::Bad; use vars qw(@ISA); @ISA = qw(Net::IMAP::Cond); sub name { 'bad' }; #------------------------------------------------------------------------------
package Net::IMAP::Bye; use vars qw(@ISA); @ISA = qw(Net::IMAP::Cond); sub name { 'bye' }; ###############################################################################
package Net::IMAP::Expunge; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'expunge' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; $self->{Msgnum} = $str; return $self; } sub msgnum { $_->{Msgnum} } ###############################################################################
package Net::IMAP::Capability; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'capability' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; undef $self->{Parent}{Capabilities}; # needs to be repopulated each time undef $self->{Parent}{AuthTypes}; # needs to be repopulated each time for my $cap (split(/\s/, $str)) { $cap = uc($cap); $self->{Parent}{Capabilities}{$cap}++; $self->{Parent}{AuthTypes}{$1}++ if $cap =~ /^AUTH=(.*)$/; $self->{Capabilities}{$cap}++; $self->{AuthTypes}{$1}++ if $cap =~ /^AUTH=(.*)$/; } # force the non-synchronous literals option off if the server # doesn't support it $self->{Parent}{Options}{NonSyncLits} = 0 unless defined($self->{Parent}{Capabilities}{'LITERAL+'}); return $self; } sub capabilities { keys %{$_[0]->{Capabilities}} } sub has_capability { defined($_[0]->{Capabilities}{uc($_[1])}) } sub authtypes { keys %{$_[0]->{AuthTypes}} } sub has_authtype { defined($_[0]->{AuthTypes}{uc($_[1])}) } ###############################################################################
package Net::IMAP::List; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'list' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my $fields = Net::xAP->parse_fields($str); for my $flag (@{$fields->[0]}) { $self->{Flags}{lc($flag)}++; } $self->{Delim} = $fields->[1]; $self->{Mailbox} = Net::IMAP::_decode_mailbox($fields->[2]); return $self; } sub mailbox { $_[0]->{Mailbox} } sub delimiter { $_[0]->{Delim} } sub flags { keys %{$_[0]->{Flags}} } sub has_flag { defined($_[0]->{Flags}{lc($_[1])}) } #------------------------------------------------------------------------------
package Net::IMAP::Lsub; use vars qw(@ISA); @ISA = qw(Net::IMAP::List); sub name { 'lsub' } ###############################################################################
package Net::IMAP::Fetch; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'fetch' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $msgnum = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; $self->{Msgnum} = $msgnum; my %hash = @{Net::xAP->parse_fields($str)->[0]}; for my $key (keys %hash) { my $lckey = lc($key); print "$lckey $hash{$key}\n"; if ($lckey eq 'envelope') { $self->{Items}{$lckey} = Net::IMAP::Envelope->new($hash{$key}); } elsif (($lckey eq 'bodystructure') || ($lckey eq 'body')) { $self->{Items}{$lckey} = Net::IMAP::BodyStructure->new($hash{$key}); } elsif ($lckey eq 'flags') { $self->{Items}{$lckey} = Net::IMAP::Flags->new($parent); for my $flag (@{$hash{$key}}) { $self->{Items}{$lckey}{Flags}{lc($flag)}++; } } else { if ($self->{Parent}{Options}{EOL} eq 'lf') { if ((substr($lckey, 0, 5) eq 'body[') || ($lckey eq 'rfc822') || ($lckey eq 'rfc822.header') || ($lckey eq 'rfc822.text')) { $hash{$key} =~ s/\r\n/\n/mg; } } $self->{Items}{$lckey} = $hash{$key}; } } return $self; } sub msgnum { $_[0]->{Msgnum} } sub items { keys %{$_[0]->{Items}} } sub item { $_[0]->{Items}{lc($_[1])} } ###############################################################################
package Net::IMAP::Status; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'status' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my $fields = Net::xAP->parse_fields($str); $self->{Mailbox} = Net::IMAP::_decode_mailbox($fields->[0]); my %hash = @{$fields->[1]}; for my $key (keys %hash) { $self->{Items}{lc($key)} = $hash{$key}; } return $self; } sub mailbox { $_[0]->{Mailbox} } sub items { keys %{$_[0]->{Items}} } sub item { $_[0]->{Items}{lc($_[1])} } ###############################################################################
package Net::IMAP::Search; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'search' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; for my $item (split(/\s/, $str)) { $self->{Msgnums}{$item}++; } return $self; } sub msgnums { keys %{$_[0]->{Msgnums}} } ###############################################################################
package Net::IMAP::Flags; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'flags' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; if (defined($str)) { for my $flag (@{Net::xAP->parse_fields($str)->[0]}) { $self->{Flags}{lc($flag)}++; $self->{Parent}{MailboxStatus}{'flags'}{lc($flag)}++; } } return $self; } sub flags { keys %{$_[0]->{Flags}} } sub has_flag { defined($_[0]->{Flags}{lc($_[1])}) } ###############################################################################
package Net::IMAP::Exists; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'exists' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; $self->{Parent}{MailboxStatus}{'exists'} = $str; $self->{Value} = $str; return $self; } sub exists { $_[0]->{Value} } ###############################################################################
package Net::IMAP::Recent; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'recent' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; $self->{Parent}{MailboxStatus}{'recent'} = $str; $self->{Value} = $str; return $self; } sub recent { $_[0]->{Value} } ###############################################################################
package Net::IMAP::Namespace; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'namespace' } my @namespace_types = qw(personal other_users shared); sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my $fields = Net::xAP->parse_fields($str); for my $n (0 .. 2) { my $field = $fields->[$n]; for my $item (@{$field}) { $item->[1] = '' if (lc($item->[1]) eq 'nil'); $self->{Namespaces}{$namespace_types[$n]}{$item->[0]} = $item->[1]; } } return $self; } sub personal { return $_[0]->{Namespaces}{'personal'}{lc($_[1])} if (defined($_[1])); keys %{$_[0]->{Namespaces}{'personal'}}; } sub other_users { return $_[0]->{Namespaces}{'other_users'}{lc($_[1])} if (defined($_[1])); keys %{$_[0]->{Namespaces}{'other_users'}}; } sub shared { return $_[0]->{Namespaces}{'shared'}{lc($_[1])} if (defined($_[1])); keys %{$_[0]->{Namespaces}{'shared'}}; } ###############################################################################
package Net::IMAP::Acl; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'acl' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my @fields = @{Net::xAP->parse_fields($str)}; $self->{Mailbox} = shift(@fields); my %hash = @fields; for my $key (keys %hash) { $self->{Identifiers}{lc{$key}} = $hash{$key}; } return $self; } sub mailbox { $_[0]->{Mailbox} } sub identifiers { keys %{$_[0]->{Identifiers}} } sub identifier { $_[0]->{Identifiers}{lc($_[1])} } ###############################################################################
package Net::IMAP::Listrights; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'listrights' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my @fields = @{Net::xAP->parse_fields($str)}; $self->{Mailbox} = shift(@fields); $self->{Identifier} = shift(@fields); $self->{Rights} = [@fields]; return $self; } sub mailbox { $_[0]->{Mailbox} } sub identifier { $_[0]->{Identifier} } sub rights { (wantarray) ? @{$_[0]->{Rights}} : $_[0]->{Rights} } ###############################################################################
package Net::IMAP::Myrights; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'myrights' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my $fields = Net::xAP->parse_fields($str); $self->{Mailbox} = $fields->[0]; $self->{Rights} = $fields->[1]; return $self; } sub mailbox { $_[0]->{Mailbox} } sub rights { $_[0]->{Rights} } ###############################################################################
package Net::IMAP::Quota; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'quota' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my @fields = @{Net::xAP->parse_fields($str)}; $self->{QuotaRoot} = shift(@fields); while (@fields) { my ($resource, $usage, $limit) = splice(@fields, 0, 3); $self->{Quota}{lc($resource)} = [$usage, $limit]; } return $self; } sub quotaroot { $_[0]->{QuotaRoot} } sub quotas { keys %{$_[0]->{Quotas}} } sub usage { $_[0]->{Quotas}{lc($_[1])}->[0] } sub limit { $_[0]->{Quotas}{lc($_[1])}->[1] } ###############################################################################
package Net::IMAP::Quotaroot; use vars qw(@ISA); @ISA = qw(Net::IMAP::UntaggedResponse); sub name { 'quotaroot' } sub new { my $class = shift; my $type = ref($class) || $class; my $parent = shift; my $str = shift; my $self = {}; bless $self, $class; $self->{Parent} = $parent; my @fields = @{Net::xAP->parse_fields($str)}; $self->{Mailbox} = shift(@fields); $self->{Quotaroots} = [@fields]; return $self; } sub mailbox { $_[0]->{Mailbox} } sub quotaroots { (wantarray) ? @{$_[0]->{Quotaroots}} : $_[0]->{Quotaroots} } ###############################################################################
package Net::IMAP::FetchData; sub new { my $class = shift; my $type = ref($class) || $class; my $self = []; if (defined($_[0])) { push(@{$self}, map { (lc($_) eq 'nil') ? undef : Net::xAP->dequote($_) } @{$_[0]}); } bless $self, $class; } ###############################################################################
package Net::IMAP::BodyStructure; sub new { my $class = shift; my $type = ref($class) || $class; my $data = shift; return Net::IMAP::Multipart->new($data) if (ref($data->[0]) eq 'ARRAY'); return Net::IMAP::Bodypart->new($data); } sub subtype { $_[0]->{Subtype} } sub parameters { $_[0]->{Parms} } sub disposition { $_[0]->{Disp} } sub disp_parameters { $_[0]->{DispParms} } sub language { $_[0]->{Lang} } sub _parse_parms { my $self = shift; my $data = shift; if (ref($data) eq 'ARRAY') { my @parms = @{$data}; while (@parms) { my ($key, $value) = splice(@parms, 0, 2); $self->{Parms}{lc($key)} = $value; } } } sub _parse_disp { my $self = shift; my $data = shift; $self->{Disp} = lc($data); if (ref($data) eq 'ARRAY') { if (lc($data->[1]) ne 'nil') { my @parms = @{$data->[1]}; while (@parms) { my ($key, $value) = splice(@parms, 0, 2); $self->{DispParms}{lc($key)} = $value; } } } } sub _parse_lang { my $self = shift; my $data = shift; $data = lc($data); if ($data ne 'nil') { if (ref($data) eq 'ARRAY') { $self->{Lang} = [map { lc($_) } @{$data}]; } else { $self->{Lang} = [lc($data)]; } } } #------------------------------------------------------------------------------
package Net::IMAP::Multipart; use vars qw(@ISA); @ISA = qw(Net::IMAP::BodyStructure); sub new { my $class = shift; my $type = ref($class) || $class; my $data = shift; my $self = {}; bless $self, $class; $self->{Parts} = []; my $i = 0; for my $item (@{$data}) { last if (ref($item) ne 'ARRAY'); if (ref($item->[0]) eq 'ARRAY') { push @{$self->{Parts}}, Net::IMAP::Multipart->new($item); } else { push @{$self->{Parts}}, Net::IMAP::Bodypart->new($item); } $i++; } $self->{Subtype} = lc(Net::xAP->dequote($data->[$i++])); $self->{Parms} = {}; $self->{Disp} = undef; $self->{DispParms} = {}; $self->{Lang} = undef; if (defined($data->[$i])) { $self->_parse_parms($data->[$i++]); if (defined($data->[$i])) { $self->_parse_disp($data->[$i++]); if (defined($data->[$i])) { $self->_parse_lang($data->[$i++]); if (defined($data->[$i])) { carp("Note: bodystructure contains unknown extension fields\n"); } } } } return $self; } sub type { 'multipart' } sub parts { $_[0]->{Parts} } #------------------------------------------------------------------------------
package Net::IMAP::Bodypart; use vars qw(@ISA); @ISA = qw(Net::IMAP::BodyStructure); use Carp; sub new { my $class = shift; my $type = ref($class) || $class; my $data = shift; my $self = {}; bless $self, $class; my $i = 0; $self->{Type} = lc(Net::xAP->dequote($data->[$i++])); $self->{Subtype} = lc(Net::xAP->dequote($data->[$i++])); $self->{Parms} = {}; $self->_parse_parms($data->[$i++]); $self->{Id} = Net::xAP->dequote($data->[$i++]); $self->{Description} = Net::xAP->dequote($data->[$i++]); $self->{Encoding} = lc(Net::xAP->dequote($data->[$i++])); $self->{Size} = $data->[$i++]; if (($self->{Type} eq 'message') && ($self->{Subtype} eq 'rfc822')) { $self->{Envelope} = Net::IMAP::Envelope->new($data->[$i++]); $self->{Bodystructure} = Net::IMAP::BodyStructure->new($data->[$i++]); $self->{Lines} = $data->[$i++]; } elsif ($self->{Type} eq 'text') { $self->{Lines} = $data->[$i++]; } $self->{Envelope} ||= undef; $self->{BodyStructure} ||= undef; $self->{Lines} ||= undef; if (defined($data->[$i])) { $self->{MD5} = Net::xAP->dequote($data->[$i++]); if (defined($data->[$i])) { $self->_parse_disp($data->[$i++]); if (defined($data->[$i])) { $self->_parse_lang($data->[$i++]); if (defined($data->[$i])) { carp("Note: bodystructure contains unknown extension fields\n"); } } } } $self->{MD5} ||= undef; $self->{Disp} ||= undef; $self->{DispParms} ||= {}; $self->{Lang} ||= undef; return $self; } sub type { $_[0]->{Type} } sub id { $_[0]->{Id} } sub description { $_[0]->{Description} } sub encoding { $_[0]->{Encoding} } sub size { $_[0]->{Size} } sub lines { $_[0]->{Lines} } # message/rfc822 and text/* sub envelope { $_[0]->{Envelope} } # message/rfc822 sub bodystructure { $_[0]->{Bodystructure} } # message/rfc822 sub md5 { $_[0]->{MD5} } ###############################################################################
package Net::IMAP::Envelope; use vars qw(@ISA); @ISA = qw(Net::IMAP::FetchData); sub new { my $class = shift; my $type = ref($class) || $class; my $data = shift; my $self = Net::IMAP::FetchData->new or return undef; bless $self, $class; $self->[0] = Net::xAP->dequote($data->[0]); $self->[1] = Net::xAP->dequote($data->[1]); for my $i (2 .. 7) { if (lc($data->[$i]) eq 'nil') { $self->[$i] = undef; next; } push @{$self->[$i]}, map { Net::IMAP::Addr->new($_) } @{$data->[$i]}; } $self->[8] = Net::xAP->dequote($data->[8]); $self->[9] = Net::xAP->dequote($data->[9]); return $self; } sub date { $_[0]->[0] } sub subject { $_[0]->[1] } sub from { $_[0]->[2] } sub sender { $_[0]->[3] } sub reply_to { $_[0]->[4] } sub to { $_[0]->[5] } sub cc { $_[0]->[6] } sub bcc { $_[0]->[7] } sub in_reply_to { $_[0]->[8] } sub message_id { $_[0]->[9] } #------------------------------------------------------------------------------
package Net::IMAP::Addr; use vars qw(@ISA); @ISA = qw(Net::IMAP::FetchData); sub phrase { $_[0]->[0] } sub route { $_[0]->[1] } sub localpart { $_[0]->[2] } sub domain { $_[0]->[3] } sub as_string { my $self = shift; my $str; my $domain = $self->domain; my $localpart = $self->localpart; my $route = $self->route; my $phrase = $self->phrase; return undef if (!defined($domain)); # part of a group list return undef if (!defined($localpart)); $str = "$localpart\@$domain"; if (defined($route) || defined($phrase)) { $str = "$route:$str" if defined($route); $str = "<$str>"; # route-addrs and phrases need <> $str = "$phrase $str" if defined($phrase); } return $str; } ###############################################################################
1;