Mail::IMAPTalk - Perl extension for interacting with IMAP servers
use Mail::IMAPTalk;
$IMAP = Mail::IMAPTalk->new(
Server => $PopServer,
Username => 'foo',
Password => 'bar',
Uid => 1 )
|| die "Failed to connect/login to IMAP server";
# Append message to folder open(F, 'rfc822msg.txt'); $IMAP->append($FolderName, \*F); close(F);
# Select folder and get first unseen message
$IMAP->select($FolderName);
$MsgId = $IMAP->search('not', 'seen')->[0];
# Get message envelope and print some details
$MsgEV = $IMAP->fetch($MsgId, 'envelope')->{$MsgId}->{envelope};
print "From: " . $MsgEv->{From};
print "To: " . $MsgEv->{To};
print "Subject: " . $MsgEv->{Subject};
# Get message body structure
$MsgBS = $IMAP->fetch($MsgId, 'bodystructure')->{$MsgId}->{bodystructure};
# Find imap part number of text part of message
$MsgTxtHash = Mail::IMAPTalk::find_message($MsgBS);
$MsgPart = $MsgTxtHash->{plain}->{'IMAP-Partnum'};
# Retrieve message text body
$MsgTxt = $IMAP->fetch($MsgId, 'body['.$MsgPart.']')->{$MsgId}->{body};
$IMAP->Logout();
This module communicates with an IMAP server. Each IMAP server command is mapped to a method of this object.
Although other IMAP modules exist on CPAN, this is the only one that fully parses IMAP structures according to RFC2060, rather than using rough heuristics. As a result this module can handle complex multipart MIME messages, and returns common IMAP and MIME structures in convenient list and hash forms. It also allows large return values (eg. attachments on a message) to be read directly into a file, rather than into memory.
While the IMAP protocol does allow for asynchronous running of commands, this module is designed to be used in a synchronous manner. That is, you issue a command by calling a method, and the command will block until the appropriate response is returned. The method will then return the parsed results from the given command.
The object methods have been broken in several sections.
Mail::IMAPTalk class
Mail::IMAPTalk object to get work
done. They may be useful if you need to extend the class yourself. Note that
internal methods will always 'die' if they encounter any errors.
Mail::IMAPTalk object
to read/write data to/from the IMAP connection socket. The class does
it's own buffering so if you want to read/write to the IMAP socket, you
should use these functions.
All methods return undef on failure. There are four main modes of failure:
In each case, some readable form of error text is placed in $@, or you
can call the get_last_error() method. For commands which return
responses (eg fetch, getacl, etc), the result is returned. See each
command for details of the response result. For commands
with no response but which succeed (eg setacl, rename, etc) the result
'ok' is generally returned.
All methods which send data to the IMAP server (eg. fetch(), search(),
etc) have their arguments processed before they are sent. Arguments may be
specified in several ways:
Examples: # Password is automatically quoted to ``nasty%*\''passwd`` $IMAP->login(''joe``, 'nasty%*''passwd'); # Append $MsgTxt as string $IMAP->append(``inbox'', [ 'Literal', $MsgTxt ]) # Append MSGFILE contents as new message $IMAP->append(``inbox'', \*MSGFILE ])
These constants relate to the standard 4 states that an IMAP connection can
be in. They are passed and returned from the state() method. See RFC2060
for more details about IMAP connection states.
This is useful if you want to create an SSL socket connection using
IO::Socket::SSL and then pass in the connected socket to the new() call.
It's also useful in conjunction with the release_socket() method
described below for reusing the same socket beyond the lifetime of the IMAPTalk
object. See a description in the section release_socket() method for
more information.
You must have write flushing enabled for any
socket you pass in here so that commands will actually be sent,
and responses received, rather than just waiting and eventually
timing out. you can do this using the perl select() call and
$| variable as shown below.
my $ofh = select($Socket); $| = 1; select ($ofh);
Socket option, you can specify the IMAP state the
socket is currently in, namely one of 'Unconnected', 'Connected',
'Authenticated' or 'Selected'. This defaults to 'Connected' if not
supplied and the Socket option is supplied.
Socket
option, checks that a greeting line is supplied by the server
and reads the greeting line.
set_root_folder() with the value passed. If no value is supplied,
set_root_folder() is called with no value. See the set_root_folder()
method for more details.
set_root_folder() method.
set_root_folder()
method.
set_root_folder()
method.
$imap = Mail::IMAPTalk->new(
Server => 'foo.com',
Port => 143,
Username => 'joebloggs',
Password => 'mypassword',
Separator => '.',
RootFolder => 'inbox',
CaseInsensitive => 1)
|| die "Connection to foo.com failed. Reason: $@";
$imap = Mail::IMAPTalk->new(
Socket => $SSLSocket,
State => Mail::IMAPTalk::Authenticated,
Uid => 0)
|| die "Could not query on existing socket. Reason: $@";
Currently there is only plain text password login support. If someone can give me a hand implementing others (like DIGEST-MD5, CRAM-MD5, etc) please contact me (see details below)
if ($IMAP->capability()->{quota}) { ... }
To test if the server has the QUOTA capability. If you just want a list of capabilities, use the perl 'keys' function to get a list of keys from the returned hash reference.
Setting this affects all commands that take a folder argument. Basically if the foldername begins with root folder prefix (case sensitive or insensitive based on the second argument), it's left as is, otherwise the root folder prefix and seperator char are prefixed to the folder name.
Examples: # This is what cyrus uses $IMAP->set_root_folder('inbox', '.', 1, 'user');
# Selects 'Inbox' (because 'Inbox' eq 'inbox' case insensitive)
$IMAP->select('Inbox');
# Selects 'inbox.blah'
$IMAP->select('blah');
# Selects 'INBOX.fred' (because 'INBOX' eq 'inbox' case insensitive)
#IMAP->select('INBOX.fred'); # Selects 'INBOX.fred'
# Selects 'user.john' (because 'user' is alt root)
#IMAP->select('user.john'); # Selects 'user.john'
You should pass a filehandle here that any literal will be read into. To turn off literal reads into a file handle, pass a 0
Example: # Read rfc822 text of message 3 into file # (note that the file will have /r/n line terminators) open(F, ``>messagebody.txt''); $IMAP->literal_handle_control(\*F); $IMAP->fetch(3, 'rfc822'); $IMAP->literal_handle_control(0);
Other possible responses are alert, newname, parse, trycreate, appenduid.
Examples: # Select inbox and get list of permanent flags, uidnext and number # of message in the folder $IMAP->select('inbox'); my $NMessages = $IMAP->get_sub_result('exists'); my $PermanentFlags = $IMAP->get_sub_result('permanentflags'); my $UidNext = $IMAP->get_sub_result('uidnext');
Note: literals are never passed to the tracer.
OnFolderChange($Folder) - called if a message is
added to/deleted from a folder
Note: In all cases where a folder name is used,
the folder name is first manipulated according to the current root folder
prefix as described in set_root_folder().
select($FolderName, 1).
See select() for more details.
The standard access control configurations for cyrus are
Examples: # Get full access for user 'joe' on his own folder $IMAP->setacl('user.joe', 'joe', 'lrswipcda') || die ``IMAP error: $@''; # Remove write, insert, post, create, delete access for user 'andrew' $IMAP->setacl('user.joe', 'andrew', '-wipcd') || die ``IMAP error: $@''; # Add lookup, read, keep unseen information for user 'paul' $IMAP->setacl('user.joe', 'paul', '+lrs') || die ``IMAP error: $@'';
Examples:
my $Rights = $IMAP->getacl('user.joe') || die ``IMAP error : $@'';
$Rights = [
'joe', 'lrs',
'andrew', 'lrswipcda'
];
$IMAP->setacl('user.joe', 'joe', 'lrswipcda') || die "IMAP error : $@";
$IMAP->setacl('user.joe', 'andrew', '-wipcd') || die "IMAP error : $@";
$IMAP->setacl('user.joe', 'paul', '+lrs') || die "IMAP error : $@";
$Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrswipcd',
'andrew', 'lrs',
'paul', 'lrs'
];
Examples:
my $Rights = $IMAP->getacl('user.joe') || die ``IMAP error : $@'';
$Rights = [
'joe', 'lrswipcd',
'andrew', 'lrs',
'paul', 'lrs'
];
# Delete access information for user 'andrew'
$IMAP->deleteacl('user.joe', 'andrew') || die "IMAP error : $@";
$Rights = $IMAP->getacl('user.joe') || die "IMAP error : $@";
$Rights = [
'joe', 'lrswipcd',
'paul', 'lrs'
];
Examples: # Set maximum size of folder to 50M and 1000 messages $IMAP->setquota('user.joe', '(storage 50000)') || die ``IMAP error: $@''; $IMAP->setquota('user.john', '(messages 1000)') || die ``IMAP error: $@''; # Remove quotas $IMAP->setquota('user.joe', '()') || die ``IMAP error: $@'';
Note that this only returns the quota for a folder if it actually has had a quota set on it. It's possible that a parent folder might have a quota as well which affects sub-folders. Use the getquotaroot to find out if this is true.
Examples:
my $Result = $IMAP->getquota('user.joe') || die ``IMAP error: $@'';
$Result = [
'STORAGE', 31, 50000,
'MESSAGE', 5, 1000
];
Basically it's a hash reference. The 'quotaroot' item is the response which lists the root quotas that apply to the given folder. The first item is the folder name, and the remaining items are the quota root items. There is then a hash item for each quota root item. It's probably easiest to look at the example below.
Examples:
my $Result = $IMAP->getquotaroot('user.joe.blah') || die ``IMAP error: $@'';
$Result = {
'quotaroot' => [
'user.joe.blah', 'user.joe', ''
],
'user.joe' => [
'STORAGE', 31, 50000,
'MESSAGES', 5, 1000
],
'' => [
'MESSAGES', 3498, 100000
]
};
status() for getting
more information about messages in a folder.
The $StatusList is a bracketed list of folder items to obtain the status of. Can contain: messages, recent, uidnext, uidvalidity, unseen
The return value is a hash reference of lc(status item) => value
Example:
my $Res = $IMAP->status('inbox', '(MESSAGES UNSEEN)');
$Res = {
'messages' => 8,
'unseen' => 2
};
$MessageIds can be one of two forms:
Note that , separated lists and : separated ranges can be mixed, but to make sure a certain hack works, if a '*' is used, it must be the last character in the string.
join(',', ...)ed together.
Note: If the uid() state has been set to true, then all message ID's
must be message UIDs
$MessageItems can be one of, or a bracketed list of:
It would be a good idea to see RFC2060 for what all these means.
Examples:
my $Res = $IMAP->fetch('1:*', 'rfc822.size');
my $Res = $IMAP->fetch([1,2,3], '(bodystructure envelope)');
Return results:
The results returned by the IMAP server are parsed into a perl structure. See the section FETCH RESULTS for all the interesting details.
For some servers (cyrus at least), if you do a fetch on a message id which doesn't exist, you still get an OK response. I didn't feel this was really very useful so if no data was retrieved, undef is returned.
The $MessageData to append can either be a perl scalar containing the data, or a file handle to read the data from. In each case, the data must be in proper RFC822 format with \r\n line terminators.
Any optional fields not needed should be removed, not left blank.
Examples: # msg.txt should have \r\n line terminators open(F, ``msg.txt''); $IMAP->append('inbox', \*F);
my $MsgTxt =<<MSG; From: blah\@xyz.com To: whoever\@whereever.com .... MSG
$MsgTxt =~ s/\n/\015\012/g;
$IMAP->append('inbox', [ 'Literal', $MsgTxt ]);
Example:
my $Res = $IMAP->search('1:*', 'NOT', 'DELETED');
$Res = [ 1, 2, 5 ];
Example:
$IMAP->store('1:*', '+flags', '(\\deleted)');
$IMAP->store('1:*', '-flags.silent', '(\\read)');
It would probably be a good idea to look at the sort extension details at somewhere like : http://www.imap.org/papers/docs/sort-ext.html
Example:
my $Res = $IMAP->sort('(subject)', 'US-ASCII', 'NOT', 'DELETED');
$Res = [ 5, 2, 3, 1, 4 ];
capability()
of the server to see if it supports one or both of these.
Example:
my $Res = $IMAP->thread('REFERENCES', 'US-ASCII', 'NOT', 'DELETED');
$Res = [ [10, 15, 20], [11], [ [ 12, 16 ], [13, 17] ];
Example:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to find particular sub part
my $P12 = $IMAP->get_body_part($BS, '1.2');
$P12->{'IMAP->Partnum'} eq '1.2' || die "Unexpected IMAP part number";
Example:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to find message components
my $MC = $IMAP->find_message($BS);
$MC = { 'plain' => ... text body struct ref part ...,
'html' => ... html body struct ref part (if present) ... };
# Now get the text part of the message
my $MT = $IMAP->fetch(1, 'body[' . $MC->{plain}->{'IMAP-Part'} . ']');
Example:
# Fetch body structure
my $FR = $IMAP->fetch(1, 'bodystructure');
my $BS = $FR->{1}->{bodystructure};
# Parse further to get CID links
my $CL = $IMAP->build_cid_map($BS);
$CL = { '2958293123' => ... ref to body part ..., ... };
The 'fetch' operation is probably the most common thing you'll do with an IMAP connection. This operation allows you to retrieve information about a message or set of messages, including header fields, flags or parts of the message body.
Mail::IMAPTalk will always parse the results of a fetch call into a perl like
structure, though 'bodystructure', 'envelope' and 'uid' responses may
have additional parsing depending on the parse_mode state and the uid
state (see below).
For an example case, consider the following IMAP commands and responses (C is what the client sends, S is the server response).
C: a100 fetch 5,6 (flags rfc822.size uid) S: * 1 fetch (UID 1952 FLAGS (\recent \seen) RFC822.SIZE 1150) S: * 2 fetch (UID 1958 FLAGS (\recent) RFC822.SIZE 110) S: a100 OK Completed
The fetch command can be sent by calling:
my $Res = $IMAP->fetch('1:*', '(flags rfc822.size uid)');
The result in response will look like this:
$Res = {
1 => {
'uid' => 1952,
'flags' => [ '\\recent', '\\seen' ],
'rfc822.size' => 1150
},
2 => {
'uid' => 1958,
'flags' => [ '\\recent' ],
'rfc822.size' => 110
}
};
A couple of points to note:
In general, this is how all fetch responses are parsed when the parse_mode
is set to 0. There is one major difference however when the IMAP connection
is in 'uid' mode. In this case, the message IDs in the main hash are changed
to message UIDs, and the 'uid' entry in the inner hash is removed. So the
above example would become:
my $Res = $IMAP->fetch('1:*', '(flags rfc822.size)');
$Res = {
1952 => {
'flags' => [ '\\recent', '\\seen' ],
'rfc822.size' => 1150
},
1958 => {
'flags' => [ '\\recent' ],
'rfc822.size' => 110
}
};
When dealing with messages, we need to understand the MIME structure of the message, so we can work out what is the text body, what is attachments, etc. This is where the 'bodystructure' item from an IMAP server comes in.
C: a101 fetch 1 (bodystructure)
S: * 1 fetch (BODYSTRUCTURE ("TEXT" "PLAIN" NIL NIL NIL "QUOTED-PRINTABLE" 255 11 NIL ("INLINE" NIL) NIL))
S: a101 OK Completed
The fetch command can be sent by calling:
my $Res = $IMAP->fetch(1, 'bodystructure');
As expected, the resultant response would look like this:
$Res = {
1 => {
'bodystructure' => [
'TEXT', 'PLAIN', undef, undef, undef, 'QUOTED-PRINTABLE',
255, 11, UNDEF, [ 'INLINE', undef ], undef
]
}
};
However, if you set the parse_mode state to 1, then the result would be:
$Res = {
'1' => {
'bodystructure' => {
'MIME-Type' => 'text',
'MIME-Subtype' => 'plain',
'MIME-TxtType' => 'text/plain',
'Content-Type' => {},
'Content-ID' => undef,
'Content-Description' => undef,
'Content-Transfer-Encoding' => 'QUOTED-PRINTABLE',
'Size' => '3569',
'Lines' => '94',
'Content-MD5' => undef,
'Content-Disposition' => [
'INLINE',
undef
],
'Content-Language' => undef,
'Remainder' => [],
'IMAP-Partnum' => ''
}
}
};
A couple of points to note here:
In general, the following items are defined for all body structures
For all items EXCEPT those that have a MIME-Type of 'multipart', the following are defined
For items where MIME-Type is 'text', an extra field 'Lines' is defined.
For items where MIME-Type is 'message' and MIME-Subtype is 'rfc822', the extra fields 'Message-Envelope', 'Message-Bodystructure' and 'Message-Lines' are defined. The 'Message-Bodystructure' field is itself a hash references to an entire bodystructure hash with all the format information of the contained message. The 'Message-Envelope' field is a hash structure with the message header information. See the Envelope entry below.
For items where MIME-Type is 'multipart', an extra field 'MIME-Subparts' is defined. The 'MIME-Subparts' field is an array reference, with each item being a hash reference to an entire bodystructure hash with all the format information of each MIME sub-part.
For further processing, you can use the find_message() function. This will analyse the body structure and find which part corresponds to the main text/html message parts to display. You can also use the find_cid_parts() function to find CID links in an html message.
The envelope structure contains most of the addressing header fields from an email message. The following shows an example envelope fetch (the response from the IMAP server has been neatened up here)
C: a102 fetch 1 (envelope)
S: * 1 FETCH (ENVELOPE
("Tue, 7 Nov 2000 08:31:21 UT" # Date
"FW: another question" # Subject
(("John B" NIL "jb" "abc.com")) # From
(("John B" NIL "jb" "abc.com")) # Sender
(("John B" NIL "jb" "abc.com")) # Reply-To
(("Bob H" NIL "bh" "xyz.com") # To
("K Jones" NIL "kj" "lmn.com"))
NIL # Cc
NIL # Bcc
NIL # In-Reply-To
NIL) # Message-ID
)
S: a102 OK Completed
The fetch command can be sent by calling:
my $Res = $IMAP->fetch(1, 'envelope');
And you get the idea of what the resultant response would be. Again
if you change parse_mode to 1, you get a neat structure as follows:
$Res = {
'1' => {
'envelope' => {
'Date' => 'Tue, 7 Nov 2000 08:31:21 UT',
'Subject' => 'FW: another question',
'From' => '"John B" <jb@abc.com>',
'Sender' => '"John B" <jb@abc.com>',
'Reply-To' => '"John B" <jb@abc.com>',
'To' => '"Bob H" <bh@xyz.com>, "K Jones" <kj@lmn.com>',
'Cc' => '',
'Bcc' => '',
'In-Reply-To' => undef,
'Message-ID' => undef,
'From-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'Sender-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'Reply-To-Raw' => [ [ 'John B', undef, 'jb', 'abc.com' ] ],
'To-Raw' => [
[ 'Bob H', undef, 'bh', 'xyz.com' ],
[ 'K Jones', undef, 'kj', 'lmn.com' ],
],
'Cc-Raw' => [],
'Bcc-Raw' => [],
}
}
};
All the fields here are from straight from the email headers. See RFC822 for more details.
C123 uid fetch 1:* (flags) * 1 FETCH (FLAGS (\Seen) UID 1) * 2 FETCH (FLAGS (\Seen) UID 2) C123 OK Completed
Between the sending of the command and the 'OK Completed' response, we have to pick up all the untagged 'FETCH' response items so we would pass 'fetch' (always use lower case) as the $RespItems to extract.
If the next atom is:
literal_handle_control)In each case, after parsing the atom, it removes any trailing space separator,
and then returns the remainder of the line to $Self->{ReadLine} ready for the
next call to _next_atom().
_next_atom()
_imap_socket_read_line(), _imap_socket_read_bytes()
or _copy_imap_socket_to_handle() to read data from the buffer in
lines or bytes at a time.
The number of bytes specified ($NBytes) must be available on the IMAP socket, otherwise the function will 'die' with an error if it runs out of data.
If $NBytes is not specified (undef), the function will attempt to seek to the end of the file to find the size of the file.
=cut
sub _copy_handle_to_handle {
my ($Self, $InHandle, $OutHandle, $NBytes) = @_;
# If NBytes undef, seek to end to find total length
if (!defined $NBytes) {
$InHandle->seek(0, 2); # SEEK_END
$NBytes = $InHandle->tell();
$InHandle->seek(0, 0); # SEEK_SET
}
# Loop over in handle reading chunks at a time and writing to the out handle
my $Val;
while (my $NRead = $InHandle->read($Val, 8192)) {
if (!defined $NRead) {
die 'Error reading data from io handle.' . $@;
}
my $NWritten = 0;
while ($NWritten != $NRead) {
my $NWrite = $OutHandle->syswrite($Val, $NRead-$NWritten, $NWritten);
if (!defined $NWrite) {
die 'Error writing data to io handle.' . $@;
}
$NWritten += $NWrite;
}
}
# Done return 1; }
_copy_handle_to_handle() because we internally buffer the IMAP
socket so we can't just user it to copy from the socket handle, we
have to copy the contents of our buffer first.
The number of bytes specified must be available on the IMAP socket, otherwise the function will 'die' with an error if it runs out of data.
=cut
sub _copy_imap_socket_to_handle {
my ($Self, $OutHandle, $NBytes) = @_;
# Loop over socket reading chunks at a time and writing to the out handle
my $Val;
while ($NBytes) {
my $NToRead = ($NBytes > 16384 ? 16384 : $NBytes);
$Val = $Self->_imap_socket_read_bytes($NToRead);
my $NRead = length($Val);
if (length($Val) == 0) {
die 'Error reading data from socket.' . $@;
}
$NBytes -= $NRead;
my $NWritten = 0;
while ($NWritten != $NRead) {
my $NWrite = syswrite($OutHandle,$Val, $NRead-$NWritten, $NWritten);
if (!defined $NWrite) {
die 'Error writing data to io handle.' . $@;
}
$NWritten += $NWrite;
}
}
# Done return 1; }
=item I<_quote($String)>
Returns an IMAP quoted version of a string. This place ``...'' around the string, and replaces any internal `` with \''
=cut
sub _quote {
# Replace " and \ with \" and \\ and surround with "..."
my $Str = shift;
$Str =~ s/(["\\])/\\$1/g;
return '"' . $Str . '"';
}
set_root_prefix() call.
The $MessageIds parameter may take the following forms
This is used to parse an envelope structure returned from a fetch call.
See the documentation section 'FETCH RESULTS' for more information.
See the documentation section 'FETCH RESULTS' from more information
See the documentation section 'FETCH RESULTS' from more information
See the documentation section 'FETCH RESULTS' from more information
Rob Mueller <robm+imap@fastmail.fm>. Thanks to Jeremy Howard <j+daemonize@howard.fm> for socket code, support and documentation setup