Programming with Perl Modules: Chapter 4

The Mail and MIME Modules

Send Email with Mail::Mailer
Better Header Control with Mail::Send
Interface to SMTP with Net::SMTP
Read Email with Mail::POP3Client
Mail Helper Modules
Handling MIME Data
The MIME Modules

Electronic mail is arguably the most essential Internet application. Although there are many different mail clients, standards for mail delivery and retrieval simplify the process of writing a mail client to send and/or retrieve mail.

The Send Mail Transfer Protocol, or SMTP, is responsible for clients negotiating RCPT ("to") and FROM ("from") requests with the SMTP server, then sending data to the SMTP server, followed by an EOF.

The Post Office Protocol Version 3, or POP3, was designed to retrieve electronic mail from a server after a user has authenticated with a username and password. Authenticated users can retrieve any information about their mailboxes, including the number of messages, headers, and message bodies, and specific messages by entering the message number.

You'll find that many of the Mail modules satisfy a host of tasks you might have accomplished either with an external program or by writing a socket client (using the Socket module) that communicates with the SMTP or POP server. Although the Mail::Mailer and Mail::Send modules interact with external mail programs such as mail, mailx, or sendmail, we'll also introduce you to Net::SMTP, which talks to an SMTP server through a socket.

This chapter covers modules designed for sending and retrieving email, as well as "helper" modules for more rudimentary message handling. We also cover the MIME and encoding modules used to package data for mail transfer and translate data upon receipt.

Send Email with Mail::Mailer

The Mail::Mailer module interacts with external mail programs. When you "use" Mail::Mailer or create a new Mail::Mailer object, you can specify which mail program you want your program to talk to:

use Mail::Mailer qw(mail);
You can also specify the mail program that sends the mail:

use Mail::Mailer;
$type = 'sendmail';
$mailprog = Mail::Mailer->new($type);
$type is the mail program you'll be using. Once you've created a new object, you use the open() function to send the message headers to the mail program, where %headers is a hash of key/value pairs that represent the header type and the value of the header:

# mail headers to use in the message
%headers = (
    'To' => 'nvp@mail.somename.com',
    'From' => 'nvp@mail.somename.com',
    'Subject' => 'working?'
);
This code represents a set of headers where the recipient of the mail message is nvp@mail.somename.com, the mail was sent from nvp@mail.somename.com, and the subject of the mail message is "working?"

Once %headers or an array of scalars has been defined, this hash or array is passed to open():

$mailprog->open(\%headers);
The body of the message is then sent to the mail program:

print $mailprog "This is the message body.\n";
Now, close the program when the message is finished.

$mailprog->close;
Let's put it all together. A practical example of using Mail::Mailer would be a command-line-driven application that works much like the UNIX mail program, reading STDIN until EOF or mailing a file specified on the command line, like so:

mailprogram filename
The code for this program might look like:

#!/usr/local/bin/perl -w

use Mail::Mailer;
use strict; # or forever hold your peace

my($mailer, $body);
my(%headers);

# use sendmail for mailing
$mailer = Mail::Mailer->new('sendmail');

# mail headers to use in the message
%headers = (
    'To' => 'nvp@mail.somename.com', # the recipient
    'From' => 'nvp@mail.somename.com', # the sender
    'Subject' => 'working?' # the subject
);

# send headers to the mail program
$mailer->open(\%headers);

# undefine record separator, this is necessary 
# to assign <> to a scalar
undef $/;

# read <> for body of message
$body = <>;

# send $body to the mail program
print $mailer $body; 

# end session
$mailer->close;

Better Header Control with Mail::Send

Mail::Send is built on top of Mail::Mailer, which means that you can also choose the mail program that sends the mail. Mail::Mailer has implemented functions to(), cc(), bcc(), and subject() to replace the %headers hash used in Mail::Mailer.

Mail::Send uses the open() function to open the mail program for output; it is built on Mail::Mailer's new function, so:

# Start mailer and output headers
$fh = $msg->open('sendmail');
serves the same purpose as:

# use sendmail for mailing
$mailer = Mail::Mailer->new('sendmail)';
This code tells Mail::Send to use sendmail as the mail program. Here's a rewritten example of the above, which takes command-line arguments for subject and recipient, but still reads from <>:

mailprogram -t <recipient> -subj <subject> 
<filename>
The program is written as:

#!/usr/local/bin/perl -w

use strict; # or I'm coming to your house to sell you some knives
use Getopt::Long;
my($recip, $subj, $fh, $msg, $body); # for strict

require Mail::Send;

# $recip is the variable for the -t switch
# $subj is the variable for the -subj switch
# both switches are required or output a usage message
GetOptions('t=s', \$recip, 'subj=s', \$subj);

# we have both switches
if($recip && $subj) {
    $msg = Mail::Send->new();

    # who the message is for
    $msg->to($recip);

    # the subject of the message
    $msg->subject($subj);

    # Start mailer and output headers
    $fh = $msg->open('sendmail');

    # undefine record separator so scalar can be
    # assigned to <> instead of an array
    undef $/;

    # read <>
    $body = <>; 

    # send the body of the message to the mailer
    print $fh $body;

    # complete the message and send it
    $fh->close;

# something's missing.  give usage message and exit.
} else {
    &usage();
    exit(0);
}

# the usage message
sub usage {
    print("Usage: $0 -t <recipient> -subj 
<subject> <filename>\n");
}
Mail::Send also provides the set() and CODE>add() functions, which assign a value to a header tag and append a value to a header tag. The set() function takes two arguments: a header tag and a value. The set() function is used like this:

$msg->set($scalar, @array); 
Therefore, to address a message to nvp@mail.somename.com:

$msg->set('To', 'nvp@mail.somename.com');
The above sets the To header to nvp@mail.somename.com; however, the following sets the To header to postmaster@mail.somename.com and nvp@mail.somename.com, because they represent an array of values.

$msg->set('To', ('nvp@mail.somename.com', 'postmaster@mail.somename.com'));
The set() function can't be used to add multiple values to a header value, like:

$msg->set('To', 'nvp@mail.somename.com');
$msg->set('To', 'someone@their.mailaddress.com');
The set() function doesn't append information from one call to another; the previous case only addresses mail to someone@their.mailaddress.com. The add function is used for this purpose. For example:

$msg->add('To', 'nvp@mail.somename.com');
$msg->add('To', 'someone@their.mailaddress.com');
This example addresses mail to both parties.

Rewriting the Mail::Send example shown above to use set() and add() subject and recipient fields would be represented as follows:

# @ recip from GetOptions('t=s', \@recip);
$msg->add('To', @recip);
 
# $subj from GetOptions('subj=s', \$subj);
$msg->set('Subject', $subj);
Putting it all together, mail can be sent to multiple recipients using Mail::Send with the following example:

#!/usr/local/bin/perl -w

# strict checking is our friend.  although it's not good
# to use one's friends, it's always good to use strict. :-)
use strict; 
use Getopt::Long;
my(@recip, $subj, $fh, $msg, $body); # for strict
require Mail::Send;

# @recip is an array of recipients - at least one recipient
# is required.  $subj is the subject of the message (this is
# also required)
GetOptions('t=s', \@recip, 'subj=s', \$subj);

# both switches have been entered
if((scalar(@recip) > 0) && $subj) {
# new Mail::Send object
$msg = Mail::Send->new(); 

# Add recipients to the list
$msg->add('To', @recip);

# Set the subject of the message
$msg->set('Subject', $subj);

# Start mailer and output headers; 
# in this case, use sendmail.
$fh = $msg->open('sendmail');

# undefine record separator so we can use a scalar
undef $/;

# read <> into $body
$body = <>;

# send the message body to the mailer
print $fh $body;

# complete the message and send it
$fh->close;
} else {
    usage();
}

sub usage {
    print("Usage: $0 -t <recipient> -subj <subject> 
<filename>\n");
    exit(0);
}

Interface to SMTP with Net::SMTP

Net::SMTP, a subclass of Net::Cmd and IO::Socket::INET, was written by Graham Barr (gbarr@ti.com) and is included with the libnet distribution. Net::SMTP implements an interface to the SMTP and ESMTP protocols, which send mail by talking to an SMTP server (through a socket) as described in RFC 821.

When would you want to use Net::SMTP instead of sending mail with an external program? Since socket communications through sockets don't involve spawning an external program, your programs won't suffer from the overhead associated with running an extra process. Talking to SMTP is convenient in cases of sending a volume of messages through electronic mail. Naturally, your server must have an SMTP server running or a remote mailhost must allow you to talk to it; otherwise you won't be able to use this module. If your server (or a remote host) doesn't have an SMTP server running, you'll be forced to use an external program to send your email with Mail::Mailer or Mail::Send; such is the case with home computers, which don't generally run their own SMTP server.

The SMTP Protocol and the SMTP Session

The SMTP protocol defines a set of commands a client sends to an SMTP server. The SMTP server is generally bound to port 25 of a mailhost. This section walks you through an SMTP session by showing you how the requests and responses are negotiated between the client and the server.

When a client negotiates an SMTP session with a server, the server tells the client that it's listening:

Trying 127.0.0.1 ...
Connected to mailhost.somename.com.
Escape character is '^]'.
220-mailhost.somename.com Spammers Beware Sendmail 8.6.13/8.6.11 
ready at Thu, 11 Sep 1997 21:07:36 -0400
220 ESMTP spoken here
The server returns a 220 message if everything is OK. If there is no server running on port 25, you'd receive an error message that your connection to port 25 was refused.

After you've connected to port 25 of the server, you introduce yourself to the server by issuing a HELO command. The HELO command accepts one parameter, your hostname; HELO defaults to your remote hostname if you don't specify a hostname.[1] Successful HELO commands receive a 250 response. Let's send a HELO command to the server:

HELO
250 mail.somename.com Hello some-remote-host.com [127.0.0.1], pleased 
to meet you
Once you've been greeted by the server, you'll have to tell the server who the sender of the message is by sending the MAIL command. The MAIL command takes the string From: user@hostname as an argument. If the MAIL command is successful, the server sends a 250 response:

MAIL From: realuser@realhost.com
250 realuser@realhost.com... Sender ok 
Then tell the server who you're sending the message to with RCPT. If the RCPT command is successful, the server sends a 250 response:

RCPT To: nospam@rid-spam-now.com
250 nospam@rid-spam-now.com ... Recipient ok
Now you're ready to send the body of your message to the server. The DATA command tells the server that all the data until the . on a line by itself will be received as the body of the email message.

DATA
354 Enter mail, end with "." on a line by itself
Subject: Hi, just thought you'd be interested ...

Hi, sorry to bother you, but I figured that you wouldn't mind
sparing a moment of your time to read about something disgusting,
explicit, or downright irritating blah blah blah ...

Please reply to my email address with the Subject: REMOVE so we
know that your email address is valid and can send you more
junk mail in the future.

.
250 VAA09505 Message accepted for delivery
Once the message has been accepted for delivery (with a 250 response), you can exit the SMTP session with the QUIT command, which returns 221 on success:

QUIT
221 mail.somename.com closing connection
Connection closed by foreign host.

The Net::SMTP Interface to SMTP

The Net::SMTP methods are named after the SMTP command they perform. This section introduces you to these methods and walks you through a complete SMTP session using Net::SMTP.

Create a new object with new()

Before you can negotiate an SMTP session with a mailhost, you must create a new object with new(). All SMTP commands are accessed through this object. The new() method takes the hostname of the mail server and options for the SMTP connection (see below) as arguments. The other options are passed to new() as a hash (name/value pairs).

Here are the options that can be passed to new():

Hello

Sends a HELO command to the SMTP server and requires a string that represents your domain; if not present, Hello guesses your domain.

Timeout

Time after which the client stops trying to establish a connection with the SMTP server; the program then exits. The default is 120 seconds.

Debug

Turns on debugging information that tells you what's transpiring with your connection, requests, and responses. The value 1 enables debug mode.

Here's how you'd create a new Net::SMTP object for a mailhost, $mailhost, that times out in 30 seconds if a connection can't be made with the SMTP server and outputs debugging information:

$mailhost = 'mail.someplace.com';
$smtp = Net::SMTP->new($mailhost,
                Hello => $mailhost,
                Timeout => 30,
                Debug   => 1,
);
The following Net::SMTP methods each return a value if successful, and undef otherwise.

domain()

This method returns the domain of the remote SMTP server.

hello()

This method sends an EHLO to the mail server or HELO if EHLO fails. This method executes automatically when you create a Net::SMTP object, so you shouldn't have to do it manually.

$smtp->hello('my-maildomain-com');
mail()

This method sends a MAIL command to the server. Takes an address as the argument:

$smtp->mail('me@my-domain.com');
mail() initiates the message sending process.

recipient()

The current message should be sent to all the specified recipients. As defined in the RFC, each address is a separate command that is sent to the server.

@mailto = ('she@her.com', 'him@his.com');

# send a separate To: line for each recipient
for(@mailto) { $smtp->recipient($_); }
to()

This method is interchangeable with recipient.

data()

This method starts sending the body of the current message. There are a number of ways to send data to the server with Net::SMTP, including a list or a reference to a list. The data must end with a period (.); the function returns true if accepted.

datasend()

This method sends extra header information and the body of the message to the server.

dataend()

This method sends a .\r\n to the server telling it that input has finished and to send the message.

Here's an example that sends a list to datasend():

@list_data = (1..10);

$smtp->data();
$smtp->datasend(@list_data);
$smtp->dataend();
help()

If your server has implemented the HELP command, you can pass a subject to help and have the help text returned to you.

$subject = 'HELO';
$help_text = $smtp->help($subject);
print $help_text."\n" if $help_text;
quit()

This method sends the QUIT command to the remote SMTP server and closes the socket connection.

Let's put it all together. Here's an example that sends a message to the webmaster of your favorite web site called www.my-favorite-site.com:

#!/usr/local/bin/perl -w
    
use Net::SMTP;
use strict;
my($mailhost, $sender, $recipient);

# mailhost, sender, and recipient declarations    
$mailhost = 'smtp.my-mailhost.com';
$sender = 'me@mail.somesite.com';
$recipient = 'webmaster@www.my-favorite-site.com';

# constructor
$smtp = Net::SMTP->new($mailhost);

# our sender and recipient
$smtp->mail($sender);
$smtp->to($recipient);

# start sending the DATA command to the server
$smtp->data();

# now, send the data to the server
$smtp->datasend(<<END);
To: $recipient
Subject: how are you enjoying your log?

Mine's pretty woody.

Love,
$sender

END

# stop sending data to the server
$smtp->dataend();

# now, quit the connection
$smtp->quit;

Read Email with Mail::POP3Client

Many networks have machines dedicated to sending and receiving electronic mail. Since users might hold accounts on foo.bar.com, and mail is sent to pop-server.bar.com, there must be a means to transfer this mail from the "post office machine" to the host that the user works on every day. The Post Office Protocol, or POP, negotiates this mail transfer from machine A to machine B.

The machine that runs POP has a server bound to port 109 or 110 (or others) depending on the machine's configuration. When a user wants to retrieve his or her mail, the user's mail client connects to the POP server and authenticates the user with a login name and password. After the user is authenticated, the user can list, read, and delete messages from the POP server.

This section introduces you to the important aspects of Mail::POP3Client and offers some examples using this module in the context of the command line. In Chapter 14, Examples, we also show an application using Mail::POP3Client to read electronic mail from a web page.

The Mail::POP3Client module simplifies the process of "talking POP" by implementing a number of functions to login, parse, and read mail messages held on the POP server. POP "vitals," such as login name (required), password (required), POP host, port, and debugging flag are passed to the constructor when a new POP3Client object is created. For example:

#!/usr/local/bin/perl -w

use Mail::POP3Client;

$pop = Mail::POP3Client->new("login", # required
                            "password", #required
                            "pophost.your.domain", # not required
                            port, # default is 110
                            debug_flag); # any positive integer

Counting Messages

The Count() function represents the number of messages in the mailbox: if authentication fails, Count() returns -1; otherwise, it returns 0 or a positive integer indicating the number of messages in the mailbox. Once authenticated, a user can list the headers of the messages in their mailbox using the function in conjunction with the Count() function, as shown in the following example:

#!/usr/local/bin/perl -w

use strict; # i will eat it very quick i will eat it using strict
use Mail::POP3Client;

my($pop, $num_mesg, $i);

$pop = Mail::POP3Client->new("nvp",
                            "xxxxxx",
                            "mail.somename.com",
                            110,
                            1);

        # How many messages do we have?
$num_mesg = $pop->Count;
print("You have ".$num_mesg." new message(s).\n");

        # Now, output the headers for all the messages.
for ($i = 1; $i <= $num_mesg; $i++) {
    print $pop->Head($i), "\n";
}
You can also use a regular expression to parse the headers to show wanted information, like sender and/or subject of each mail message:

#!/usr/local/bin/perl -w

use strict; # be there or be square
use Mail::POP3Client;

my($pop, $num_mesg, $i);

$pop = Mail::POP3Client->new("nvp", # required
                            "xxxxxx", # required
                            "mail.somename.com", # pop host
                            110, # port (an integer)
                            1); # debug flag (an integer)

$num_mesg = $pop->Count;
print("You have ".$num_mesg." new message(s).\n");

for ($i = 1; $i <= $pop->Count; $i++) {
    foreach ($pop->Head($i)) {
               # output from and subject
        # if matched by regexp
        print $_." " if /^(From|Subject)/;
   }
           # now, send a trailing \n for grins
    print "\n";
}
The above code outputs:

~/ORA/PRK/Chapters/Mail/scripts> ./ex1_7.pl
POP3: POPStat at ./ex1_7.pl line 8
You have 1 new message(s).
TOP 1 0
+OK Top of message follows
From: "Nathan V. Patwardhan" <nvp> Subject: pop test

Getting and Setting the Host and Port

The Host() function returns or sets the current POP host. For example:

$obj->Host;
returns the current POP host and sets the new POP host to new-pop.bar.com.

$new_host = 'new-pop.bar.com';
$obj->Host($new_host);
The Port() function works like Host(), returning or setting the current port the POP server is bound to:

$obj->Port;
This returns the current port the POP server is bound to and sets the new port to 7000.

$new_port = 7000;
$obj->Port($new_port);

Retrieving the Message Body

Naturally, you'll want to read more than the headers of your mail messages, so Mail::POP3Client contains the Body(), HeadAndBody(), and Retrieve() functions. The Body() function outputs the body of the message, and both HeadAndBody() and Retrieve() output both the head and body of the message. Given the above example, an entire message can be output with the following code:

#!/usr/local/bin/perl -w

use strict; # those who are late will not be served fruit cup.
use Mail::POP3Client;

my($pop, $num_mesg, $i);
$pop = Mail::POP3Client->new("nvp", # required
                            "xxxxxx", # required
                            "mail.somename.com", # pop host
                            110, # port (an integer)
                            0); # debug flag (an integer)

$num_mesg = $pop->Count;
print("You have ".$num_mesg." new message(s).\n");

for ($i = 1; $i <= $pop->Count; $i++) {
    foreach ($pop->Retrieve($i)) { # show the message, $i
        print $_,"\n"; # add a \n to end of each line
    }
    print "\n";
}
This example outputs the message:

~/ORA/PRK/Chapters/Mail/scripts> ./ex1_8.pl
You have 1 new message(s).
Received: (from nvp@localhost)
        by mail.somename.com (8.8.5/8.8.5) id AAA03248
        for nvp; Mon, 9 Jun 1997 00:11:09 -0500 (EST)
Date: Mon, 9 Jun 1997 00:11:09 -0500 (EST)
From: "Nathan V. Patwardhan" <nvp>
Message-Id: <199706090511.AAA03248@mail.somename.com>
To: nvp
Subject: pop test
Status: RO
this is a pop test

Deleting and Undeleting Messages

Messages can be deleted from the POP mailbox with the Delete() function. Delete temporarily marks messages for deletion until the QUIT command is received, at which time the messages are permanently removed. Delete() takes one argument, the number of the message to delete:

$pop->Delete(1);
This deletes the first mail message.

Like most mail programs, Mail::POP3Client can undelete messages marked for deletion before the program is terminated and the changes saved. The Reset() function takes one argument, representing the number of the message to undelete:

$pop->Reset(1);
Reset() unmarks the first mail message from pending deletion upon program termination.

Checking the Connection

Most programs that require a user to log in will time out after a given period of time for security and resource reasons. Unattended programs are a welcome find to individuals wanting to cause harm to the system, and they are also taxing on the system, because the program is running idle. The Alive() function checks to see if the connection to the POP server is still open; it returns true if the connection is good, or false if the connection is closed. For example:

#!/usr/local/bin/perl -w

use strict; # be there or be square
use Mail::POP3Client;

my($pop, $is_alive);
$pop = Mail::POP3Client->new("nvp", # required
                            "xxxxx", # required
                            "mail.somename.com", # pop host
                            110, # port (an integer)
                            1); # debug flag (an integer)

$is_alive = $pop->Alive; # Alive() returns 1 if connected
if($is_alive == 1) {
    print("You are currently connected to: ".$pop->Host."\n");
} else {
    print("Error: disconnected!\n");
}
The above code causes the POP server to respond (if connected):

~/ORA/PRK/Chapters/Mail/scripts> ./ex1_10.pl
POP3: POPStat at ./ex1_10.pl line 8
You are currently connected to: mail.somename.com

Explicitly Opening and Closing Connections

POP connections can be explicitly opened and closed with Login() and Close(). Close() takes no arguments and closes the connection to the POP server; here's a test using Close():

$pop->Close;
if($pop->Alive == 1) {
    print "Connected.\n";
} else {
    print "Not connected.\n";
}

Mail Helper Modules

The Mail helper modules covered in this chapter are Mail::Folder, Mail::Internet, and Mail::Address. We call them helper modules because they perform less complex tasks than core Mail modules like Mail::Send or Mail::POP3Client, yet come in quite handy when working with email messages.

Handle Folders with Mail::Folder

Once you've begun downloading and reading your email from a POP server, you might want to save or categorize your messages into folders which allow you to add, delete, save, and move messages easily.

Mail::Folder was written by Kevin Johnson (kjj@pobox.com) as an object-oriented, folder-independent interface to email folders that supports mbox, maildir, emaul, and NNTP mailbox formats. Since mbox is a standard mailbox format supported by a number of UNIX mail clients, the examples in this section demonstrate Mail::Folder's mbox (Mail::Folder::Mbox) capabilities. You can find examples for other mail folder formats in Chapter 19 of the Module Reference Guide, Volume 2.[2]

You'll need to create a Mail::Folder object before you can use the module. Mail::Folder uses the new() method as a constructor. The new() method takes three arguments: the type of the folder you'll be using, the name of the folder, and a list of options described below. If you specify the name of the folder, Mail::Folder automatically opens the folder with the open() method.

The options to the new() constructor for Mail::Folder are:

Create

The folder is created if it doesn't already exist.

Content-Length

The Content-length header is created or updated by append_message() and update_message().

DotLock

Uses .lock style folder locking with the proper folder interface (mbox is the only interface that currently uses this).

Flock

Uses flock-style folder locking with the proper folder interface (mbox is the only interface that currently uses this).

NFSLock

Deals with NFS-style file locking with the proper folder interface and the NFS server in question.

Timeout

The folder interface overrides the default value for timing out. This is particularly useful for folder interfaces that involve network communications. Time is specified in seconds.

DefaultFolderType

Auto-detects folder type if AUTODETECT has been set (neat!).

You can create a Mail::Folder object that reads the file /home/nvp/Mail/nvp in mbox format with the following code:

$ftype = 'mbox';
$foldername = '/home/nvp/Mail/nvp';
$fldr_obj = new($ftype, $foldername [, %options]);
Let's look at the key methods you'll need to get started with Mail::Folder.

open()

If you haven't specified a folder name in the constructor, you'll need to call the open() method, which takes the folder name as an argument. open() populates the internal data structures with information it gathers from the mail folders. readonly is set if the folder is determined to be read-only.

$folder_obj->open($foldername);
get_message()

Once you've opened a mail folder, you'll want to read the messages. The get_message() method takes a message number as an argument and returns 0 for failure.

$num = 24;
$folder_obj->get_message($num);
get_mime_message()

Mail::Folder also parses messages with MIME content (see the section, "The MIME Modules," later in this chapter). The get_mime_message() method calls get_message_file() to get a file to parse, creates a MIME::Parser object, and reads the content into MIME::Entity. get_mime_message() takes the same options as MIME::Entity.

get_message_file()

This method returns a filename instead of a Mail::Internet object reference and works well with the MIME tools package.

$num = 2;
$folder_obj->get_message_file($num);
get_header()

The get_header() method extracts a message header and takes one argument, the message number.

$num = 45;
$folder_obj->get_header($num);
get_mime_header()

This method works much like get_header(), but calls MIME::Head instead. It takes one argument, the message number.

$num = 392;
$folder_obj->get_mime_header($num);
append_message()

The append_message() method adds a message to a folder and takes a reference to a Mail::Internet object as an argument.

$folder_obj->append_message($mi_ref);
refile()

This method moves messages between folders and takes a message number and folder reference as arguments.

$folder_obj->refile($num, $fldr_ref);
delete_message()

The delete_message() method takes a list of messages to be marked for deletion. Messages won't actually be deleted until sync is called.

@all = (1, 2, 3, 5);
$folder_obj->delete(@all);
undelete_message()

This method unmarks a list of messages that have been marked for deletion. Like delete_message(), it takes a list as an argument:

@unmark(1, 3, 4, 6);
$folder_obj->undelete_message(@unmark);
message_list()

This method returns a list of the message numbers in the folder (in no particular order).

print $folder_obj->message_list."\n";
qty()

This method returns the number of messages in the folder.

print "There are ".$folder_obj->qty." 
messages in your folder\n";

Handle Messages with Mail::Internet

Mail::Internet implements a number of helpful functions to manipulate a mail message, including body(), print_header(), and head().

Mail::Internet is built on top of Mail::Header, which parses the header of an electronic mail message. Mail::Internet inherits the Mail::Header constructor style that requires a file descriptor or reference to an array be used. For example:

@lines = <STDIN>;
$mi_obj = new Mail::Internet([@lines]);
reads a mail message from STDIN (using a reference to an array). The following example reads a mail message from a filehandle, FILE.

open(FILE, "/home/nvp/Mail/nvp");
$mi_obj = new Mail::Internet(\*FILE);
close(FILE);
The print_header() function outputs the header of a message to a file descriptor; the default is STDOUT.

open(FILE, "/home/nvp/Mail/nvp");
$mi_obj = new Mail::Internet(\*FILE);
close(FILE);
$mi_obj->print_header(\*STDOUT);
The above example outputs:

From nvp Mon Jun  9 00:11:10 1997
Received: (from nvp@localhost) by mail.somename.com (8.8/8.8) id
    AAA03248 for nvp; Mon, 9 Jun 1997 00:11:09 -0500 (EST)
Date: Mon, 9 Jun 1997 00:11:09 -0500 (EST)
From: "Nathan V. Patwardhan" <nvp>
Message-Id: <199706090511.AAA03248@mail.somename.com>
To: nvp
Subject: pop test
X-Status:
X-Uid: 1
Status: RO
where print_body() also takes a file descriptor as an argument, but only outputs the body of the message, whereas the print() function outputs an entire message.

Parse Email Addresses with Mail::Address

Mail::Address was written by Graham Barr (gbarr@ti.com). This module parses RFC 822-compliant email addresses with the form:

"Full Name or Phrase" <username@host> (Comment Area)
For example, under RFC 822, an address might be represented as:

"Nathan V. Patwardhan" <nvp@mail.somename.com> (No Comment)
or

"Nathan V. Patwardhan" <nvp@mail.somename.com>
The Mail::Address constructor parses an email address into three parts based on the categories shown above:

$addr = Mail::Address->new("Full Name or Phrase",
                           "username@host",
                           "(Comment Area)");
Mail::Address also outputs portions of the email address with the functions phrase(), address(), comment(), format(), name(), host(), and user(). The phrase(), address(), and comment() functions represent the first, second, and third entities that were passed to the Mail::Address constructor, where the phrase() function when used as follows:

print $addr->phrase();
outputs:

Nathan V. Patwardhan
The address() function when used like:

print $addr->address();
outputs:

nvp@mail.somename.com
And the comment() function when used like:

print $addr->comment();
outputs:

No Comment
A real electronic mail address can be "unmangled," or parsed from its user@somehost.com format, with the user() and host() functions. The user() function removes everything after and including @, and host() removes everything before and including @. Using the previous example of nvp@mail.somename.com, the following line:

print $addr->user;
outputs:

nvp
And the following line using the host() function:

print $addr->host;
outputs:

nvp@mail.somename.com
If you create a Perl script called parsefrom.pl using MIME::Head and Mail::Address to parse the From line of an incoming mail message, and put a statement like:

 |/path/parseform.pl your_username 
in your .forward file, your script passes the contents of the From line into Mail::Address, and parses a mail address for each incoming mail message. Here's an idea of how parseform.pl might be written:

#!/usr/local/bin/perl -w

use MIME::Head; # used to parse the message header
use Mail::Address; # used to parse the sender's address
use strict; # the hills are alive

my($header, $bar, $foo);

$header = new MIME::Head;
$header->read(\*STDIN); # read message from STDIN
$bar = $header->get('From', 0); # grab the header containing 'From'

# If the address doesn't match the form
# PHRASE ADDRESS (COMMENT), use the first regexp which won't
# try to match (COMMENT).  Otherwise, grab everything.
if(....) {
#match address type 1
} else {
#match address type 2
}

Handling MIME Data

Nontext file types like executables, pictures, and file archives can be sent through electronic mail using MIME, or Multipurpose Internet Mail Extensions. MIME messages are sent in multiple parts separated by boundaries, with specific stamps representing the contents of the boundary. (MIME-encoded messages can also be referred to as multipart messages.)

Although many mail readers are able to parse and decode the contents of MIME messages, others, like RMAIL and elm, require an external program to parse and display the contents of a MIME-encoded mail message. There are several different packages that handle MIME message interpretation, but we'll concentrate on the metamail package, and specifically the mailcap file. The mailcap file is read by metamail to determine how entities of multipart mail messages or nontext attachments are handled.

For more information on sending mail with MIME attachments, see the discussion of Base64-encoding later in this chapter.

Format of the mailcap File

The mailcap file is generally found in /etc or /usr/local/etc, but can be located anywhere, providing that metamail knows where to find it. The MAILCAPS environment variable can be set to point to the location of the mailcap file if it is different than the default.

mailcap entries are divided into four fields, separated by semicolons. The four fields are:

  • The Content-type of the entry

  • A command to execute when the Content-type is encountered

  • An optional flag that is passed to the command if necessary

  • An optional entry description, denoted with a description tag

A mailcap entry might look like:

application/x-perl; ptkat %s; nonummy
With a description tag, the above entry could be illustrated like:

application/x-perl; ptkat %s; nonummy; \
description="pTk applications that should not be."
The above entry represents a valid Content-type as specified in RFC 822, application/x-perl. When metamail encounters the application/x-perl Content-type, ptkat executes with one argument as well as the nonummy flag, which tells ptkat not to suppress anything being sent to STDOUT.

mailcap entries preceded with a # are considered comments and are skipped by metamail; blank lines are also ignored. Multiline entries can be written, providing they are denoted with a backslash (\). Many editors automatically insert a backslash at the end of any line that exceeds x characters.

Using the Mail::Cap Module

Mail::Cap follows the convention defined in RFC 1524, pointing to a default mailcap file or the one specified in the MAILCAPS environment variable. You can create a new Mail::Cap object with the default mailcap file as follows:

$mailcap = Mail::Cap->new();
You can also create a new Mail::Cap object with a custom, or local, mailcap file. For example:

# use the mailcap file stored in /path called local_mailcap
$my_mailcap_file = '/path/local_mailcap';
$mailcap = Mail::Cap->new($my_mailcap_file);
Mail::Cap has implemented four functions to handle messages being analyzed by the mailcap file: view(), compose(), edit(), and print(). Each function takes two arguments: a Content-type and the name of a file to be operated on per the directives in the mailcap file.

The view() function takes the two parameters (Content-type and filename) and executes the external programs specified in the second and third fields of the mailcap file; edit(), compose(), and print() work in a similar fashion.

Here's a simple program that takes a command-line argument and handles the data from $ARGV[0]:

#!/usr/local/bin/perl -w

use Mail::Cap;

$capfile = '/home/nvp/mailcap';
$mcap = Mail::Cap->new($capfile);
$mcap->view('image/jpg', $ARGV[0]);
This program spawns the external viewer corresponding to the image/jpg Content-type, xloadimage. It is extremely limited, however; if the file specified by $ARGV[0] isn't of type image/jpg, the program fails, and the file won't be displayed.

The mime.types File

Mail readers aren't the only ones that handle MIME data. Web servers such as Apache have the responsibility of telling Web clients what kind of data they are sending, and for that they use an external file, generally called mime.types. The mime.types file lists Content-types with the file extensions associated with that type of file, such as .jpeg or .jpg for a JPEG image, or .html for an HTML file. An external file is used, because it's easier to add new entries to a file than recompile the program each time an entry is added.

Like the mailcap file, the mime.types file contains a number of RFC 822-compliant Content-types, but the mime.types file generally doesn't contain information about external programs to be executed when a given Content-type is encountered; that decision is made on the browser's end. The mime.types file is used to create an association between a file's extension and Content-type. The server conveys the Content-type to the browser, and the browser takes it from there.

Here's a command-line-based program that parses a file's extension (using File::Basename), and searches a mime.types file for a matching extension. If an extension is found, the Content-type of the document is returned and passed to the Mail::Cap view() function so the document can be viewed correctly.

#!/usr/local/bin/perl -w

use File::Basename; # for the file's extension
use Getopt::Long; # for the command-line arguments
use Mail::Cap; # to parse the mailcap file

# you're not leaving this table until you've eaten all of your peas
use strict; 

# remember, we're using strict
my($infile, $capfile, $file_ext, $mimetype, $mailcap);

# program must be executed like: programname -file inputfile
# if not, output a usage message
GetOptions('file=s', \$infile);
$infile or usage();

# specific capfile.  without declaring this, and creating an
# object like: $mailcap = new Mail::Cap, Mail::Cap will expect
# that you have an existing mailcap file (in /etc/mailcap or such),
# or have defined an environment variable MAILCAPS
$capfile = 'mailcap';

# From File::Basename, grab the extension of the file (field 2).
# This extension will be passed to lookup_type-by_extension, which 
# returns a Content-type if the extension matches an entry in the
# mime.types file
($file_ext) = (fileparse($infile, '\..*'))[2];
$mailcap = Mail::Cap->new($capfile);
$mimetype = lookup_type_by_extension($file_ext);
$mailcap->view($mimetype, $infile);

sub lookup_type_by_extension {
    my($ext) = @_;
    my($ctype, @ftype, @found);
    my($m_types) = 'mime.types';

    open(TFILE, $m_types) or die("error opening $m_types: $!");
    while(<TFILE>) {
        # remove newlines
        chop;

        # get the content-type and extensions (file types) of
        # mime.types entries by splitting each line of the file
        # on the tab character
        ($ctype, @ftype) = split(/\t/, $_, 2);

        # check to see if extension appears in @ftype array
        # yes Virginia, I've been reading perlfaq4  :-)
        my %found = ();
        for(@ftype) { $found{$_} = 1; }

        # if extension exists, there will be an element in @found,
        # so scalar(@found) will be greater than zero. Return the
        # content-type of the mime.types entry
        return $ctype if $found{$ext};
    }
    close(TFILE);

}
sub usage {
    print("Usage: $0 -file <filename>\n");
    exit(0); # failure
       }

The MIME Modules

This section introduces you to the creation of, mailing, parsing, reading, and decoding multipart MIME messages using the MIME modules. We'll cover:

  • MIME:Base64, the heart of the MIME modules, which handles encoding and decoding MIME data

  • MIME::Lite, a simple yet powerful module for creating simple MIME attachments

  • MIME::Entity, a more robust module for creating MIME attachments

  • MIME::Parser, which can parse MIME messages

Encoding and Decoding with MIME::Base64

As described previously, MIME makes it possible to send non-ASCII files via email. The way it performs this magic is via base64 encoding, which is then packed into a maximum of 76-character lines, an appropriate length for email. At the heart of the MIME modules is MIME::Base64, which implements the encode() and decode() methods that handle streams of information to be base64-encoded or -decoded.

Here's what the base64 algorithm does:

  1. Reads data into a variable (in this case, we're using a scalar, $_) and defines an end-of-line character. We'll need to add this to the 76th character of each encoded line.

    $end_of_line = "\n" unless defined $end_of_line;
    
  2. Iterates through $_ and encodes lines using substr() and pack() with the u attribute to uuencode the lines, appending each line to a scalar, $res, then chopping the last character from each line.

    while ($_[0] =~ /(.{1,45})/gs) {
         $encoded .= substr(pack('u', $1), 1);
         chop($res);
    }
    
  3. After uuencoding all the data, transforms certain characters using tr/// to comply with the MIME specification. Also adjusts the padding of each line. The tr and padding statements represent the actual base64-encoded string.

    # for 'smart' base64 'uu'encoding
    $encoded =~ tr|` -_|AA-Za-z0-9+/|;
    
    # fix padding at the end
    my $padding = (3 - length($_[0]) % 3) % 3;
    $encoded =~ s/.{$padding}$/'=' x $padding/e if $padding;
    
  4. Now, using a regular expression, breaks the string into lines containing no more than 76 characters.

    if (length $end_of_line) {
         $encoded =~ s/(.{1,76})/$1$end_of_line/g;
    }
    
  5. Finally, returns the base64-encoded string.

    $encoded
    
    What results is a file that looks like gibberish when not viewed with software capable of parsing base64-encoded content. MIME and the base64 encoding scheme are an ingenious way of transmitting multiple segments of mixed data between individuals who are using software capable of decoding these messages.

Here's an example using MIME::Base64 that encodes a clear text string.

#!/usr/local/bin/perl -w

### Script: test.b64

use MIME::Base64 ();

        ### clear text string
$to_encode = "There once was the good Duke of Earl,\n".
             "who used to sit home and hack Perl.";

        ### output the clear text string
print("Clear String: \n". $to_encode ."\n");         

        ### Now, encode the clear text string, $to_encode
$encoded = MIME::Base64::encode($to_encode);

        ### Let's output the encoded string
print("\nEncoded: \n$encoded\n");

        ### decode the clear text string
$decoded = MIME::Base64::decode($encoded); 

        ### If all has gone okay, we're right back to where
        ### we started. Output the decoded string.
print("\nDecoded: \n".
                $decoded
                ."\n"); 
Naturally, base64-encoded information can also be decoded from an input file. Here's an example that does just that.

#!/usr/local/bin/perl -w

### Script name: test3.b64

use MIME::Base64 ();

$input = 'output.mime'; ### input file
$output_file = 'from_mime.gif'; ### output file

print("Base64-encoded file: $input\n");
print("Output image: $output_file\n");

open(INPUT, "$input") or die("Input file $input error: $!");
open(OUTPUT, ">$output_file") or die("Output file $output_file error: $!");

         ### Again, DON'T chop() lines from the input file. This will
         ### break the base64-encoding scheme for the input file and
         ### your output file will get corrupted!
while($line = <INPUT>) { 
    $decoded = MIME::Base64::decode($line); ### output decoded line
    print OUTPUT $decoded;

}

close(OUTPUT);
close(INPUT);

Handling Multipart Mixed Messages

Now that you've been introduced to how information is base64-encoded, you might wonder why we've referred to MIME-encoded messages as multipart or mixed messages. The answer is that each document has a format or type that is considered when sending multipart messages. For example, you might send someone an electronic mail message containing ASCII text and a picture of yourself in GIF format. Since the mail message contains more than one type of content, it's referred to as containing multipart or multipart/mixed content.

When you add content to a multipart message, you're asking your mail program to map a Content-type to the extension of the filename you've included. If your mail program can't determine the Content-type of one of the files you've attached, most programs will use a default Content-type.

So, how's an ordinary mail message different from a multipart mail message?

Normal mail headers contain the following information: From, Date, Subject, and To. If I were to send myself a plaintext (nonencoded) mail message, with the subject "Here's looking at you, Nate," the headers would look like:

From nvp Wed Mar  5 20:34:43 1997
Received: (from nvp@localhost) by mail.somename.com (8.7.5/8.7.3) 
id UAA00505 for nvp; Wed, 5 Mar 1997 20:34:42 -0500 (EST)
Date: Wed, 5 Mar 1997 20:34:42 -0500 (EST)
From: "Nathan V. Patwardhan" <nvp>
Message-Id: <199703060134.UAA00505@mail.somename.com>
To: nvp
Subject: Here's looking at you, Nate
Status: RO
Multipart messages differentiate between content by using boundaries or dashed lines and character markers, which represent the type of content appearing between the boundaries. According to the RFC documents that deal with MIME messages, electronic mail messages that contain multiple types of content are bound like the following:

  1. First, send a MIME header that announces our version of MIME.

    MIME-Version: 1.0
    
  2. Next, send the Content-type and boundary for the document. Since our mail message will contain several different types of content, we'll use the multipart/mixed Content-type and define a boundary that will separate all the different parts we'll attaching to our message. The RFC documents state that a message's boundary must be preceded by two dashes for each of the message's boundaries, and that the message's ending boundary must also contain two trailing dashes, so we'll start with the boundary shown below.

    Content-Type: multipart/mixed; boundary = "--someboundaryhere"
    
  3. Now, start adding content to our multipart message, which follows a line of text that reads like the following:

    This is a multipart message in MIME format.
    
  4. Each section of content that appears in a multipart message is denoted by the boundary shown above and formatted with the Content-Type, filename, Content-Transfer-Encoding, and Content-Disposition of the attachment included in the section.

    --someboundaryhere
    Content-Type: format/type; name="filenamehere"
    Content-Transfer-Encoding: base64
    Content-Disposition: inline; filename="filenamehere"
    
  5. Finally, we'll end the multipart message by sending an ending boundary denoted by the two trailing slashes described in the RFC.

    --someboundaryhere--
    
Here's some code that assembles a multipart message, including boundaries. Although its message assembly routine isn't very robust, you'll get an idea of how to use the MIME modules to construct multipart mail messages.

#!/usr/local/bin/perl -w

# we'll need this to check filenames
use File::Basename;
# needed for Base64 encoding
use MIME::Base64;

# the mail program we'll be using for sending
$mailprog = '/usr/lib/sendmail';

# the subject of our mail message
$subject = 'This is a test message in MIME format';

# who'll be receiving our mail message
$recip = 'nvp@ora.com';

# okay, let's generate a boundary for each section
# of the multipart mail message
$bndry = encode_boundary($subject);

open(MAILER, "|".$mailprog." -t ".$recip)
     or die("error opening $mailprog: $!");
print MAILER "To: $recip\n";
print MAILER "X-Mailer: $0/1.0\n";
print MAILER "MIME-Version: 1.0\n";
print MAILER "Content-Type: multipart/mixed;".
          "boundary=\"".$bndry."\"\n\n";
print MAILER "This is a multi-part message in MIME format.\n";

# now, let's do something with each @ARGV member if it's a file
# other entries will be treated as body text in the message
for(@ARGV) {
    # if it's a file, let's encode the contents and
    # send them to the mailer
    if(-e $_) {
        undef $/;
        open(IN, $_) or die("no: $!"); $enc = <IN>; close(IN);
         $encoded = MIME::Base64::encode($enc);
        print $encoded."\n";
        print MAILER<<TYPE;
$bndry
Content-Type: application/octet-stream; name="$_"
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="$_"

$encoded
TYPE
} else {
# otherwise, we'll send the contents to
# the mailer as-is
print MAILER<<TEXT;
$bndry
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

$_
TEXT
}
}

# okay, we're done sending the parts.
# let's send the ending boundary
print MAILER $bndry.'--'."\n\n";
close(MAILER);

# subroutine to encode a boundary. There are *much* better ways
# to do this, but this subroutine is offered only for illustration
sub encode_boundary {
    my $boundary = shift;
    $boundary =~ s/(\s+)//g;
    $boundary = '------------'.substr(pack('u', $boundary), 0, 24);
    return $boundary;
}

# ok, if the entity name has an extension, we'll attach it as a file
# if it doesn't have an extension, we'll attach it as-is
sub do_part {
    my $part = shift;
    my($ext) = fileparse($part);

    unless(defined($ext)) {
        return undef;
    } else {
        return $ext;
    }
}
Although functional, the above example is lengthy and painful in the context of writing code from scratch versus using Perl modules. Thankfully, there are a number of modules that simplify the process of sending mail messages with attachments.

Create Simple Attachments with MIME::Lite

Since the essence of this book has been to write applications with Perl modules instead of reinventing the wheel, the rest of this chapter focuses on some interesting uses of the MIME modules to generate and parse multipart messages. We'll start from the ground up, with MIME::Lite, developed by eryq@enteract.com.

Let's start with MIME::Lite because of the lightweight nature of this module and straightforward API details that make it easy to get started writing applications with MIME::Lite. In fact, you can send email with an attachment using MIME::Lite in three steps:

  1. Use this module.

    use MIME::Lite;
    
  2. Create a MIME::Lite object and pass it the necessary header information so that the file(s) can be included.

    $object = MIME::Lite->new(
        From     =>'nvp@somemail.com',
        To       =>'recipient@theirdomain.com',
        Cc       =>'do you want to copy anyone?',
        Subject  =>'What\'s your favorite subject',
        Type     =>'mime type here',
        Encoding =>'encoding type here - base64 for binaries',
        Path     =>'path and name of file here');
    
  3. Send the message. You can represent the message as a string with as_string and send $show_string to STDOUT:

    $show_string = $object->as_string;
    print $show_string."\n";
    
    Or, send the message through a filehandle:

    $object->print(\*SENDMAIL);
    
    Assuming you're on a UNIX system, you can send the message directly to sendmail without using a filehandle:

    $object->send;
    
    As you noticed from the example above, it's not difficult to send an email message containing one attachment with MIME::Lite. But what about multipart messages?

    MIME::Lite uses the attach() method to add parts to a multipart message. Since you aren't able to add multiple parts to the MIME::Lite constructor, like so,

    # WRONG! - DON'T DO THIS
    
    $message = MIME::Lite->new(
        From     => 'nvp@somemailhost.com',
        To       => 'recipient@someotherhost.com',
        Subject  => 'Stop hitting me!',
        Type     => 'multipart/mixed',
        Type     => 'image/gif',
        Encoding => 'base64',
        Path     => 'some_useless_animated_and_blinking.gif',
        Type     => 'image/jpeg',
        Encoding => 'base64',
        Path     => 'another_senseless.jpg');
    
    you must add a multipart/mixed type to the constructor, then add each part with the attach() method. For example, you can create a multipart message with an attached GIF, JPEG, and body text with the following steps.

  4. First, tell MIME::Lite that it's going to be adding parts to a message by setting the Content-type to multipart/mixed.

    $message = MIME::Lite->new(
        From    => 'nvp@somemailhost.com',
        To      => 'recipient@sybil-has-multiple-parts-too.com',
        Subject => 'GIF and JPEG for you!',
        Type    => 'multipart/mixed');
    
  5. Now, use attach to add the first attachment to the mail message, a GIF.

    attach $message
        Type     => 'image/gif',
        Path     => '/the/path/less/traveled',
        Filename => 'some_gif_to_add.gif';
    
  6. Next, use attach to add another attachment to the mail message, a JPEG.

    attach $message
        Type     =>'image/jpeg',
        Path     =>'/the/path/slightly/less/traveled',
        Filename =>'some_jpeg_to_add.jpg';
    
  7. After you've finished adding parts to the message, send the contents of the message to STDOUT. This is helpful if you want to debug the contents of your message before sending it to someone. For example, it would be embarrassing to accidentally send something bawdy to a person who didn't appreciate bawdy content.

    $check_message_for_smut = $msg->as_string;
    print $check_message_for_smut."\n";
    
  8. Finally, after you've ensured that your message is syntactically and "morally" correct, you can send it. Since you're probably running a UNIX flavor that supports the send() method, we'll use that:

    $message->send;
    
    Other MIME::Lite methods that come in handy when debugging email messages are header_as_string() and body_as_string(). Like their method names indicate, header_as_string() outputs the header of an email message created with MIME::Lite, and body_as_string() outputs the body of an email message created with MIME::Lite. Both are used like as_string():

    # Show us your body
    $body_not_bawdy = $message->body_as_string;
    print $body_not_bawdy."\n";
    
    # Show us your header
    $header_how_are_you = $message->header_as_string;
    print $header_how_are_you."\n";
    
    Instead of using as_string(), body_as_string(), or header_as_string(), you can use print(FILEHANDLE), print_body(FILEHANDLE), or print_header(FILEHANDLE). For example, if you wanted to output the contents of an email message to a file before sending the email, you could do the following:

    $outfile = '/path/output_file_name';
    open(OUTFILE, ">".$outfile)
        or die("output file $outfile error: $!");
    $message->print(\*OUTFILE);
    close(OUTFILE);
    

Header Hacking with MIME::Lite

Since you might choose to use MIME::Lite as part of an email application you're developing, you'll want the capability to add and remove items from the headers of messages you'll be sending. Let's review the contents of the object, $message, we've been using for the examples in this section; $message contains:

$message = MIME::Lite->new(
    From    => 'nvp@somemailhost.com',
    To      => 'recipient@sybil-has-multiple-parts-too.com',
    Subject => 'GIF and JPEG for you!',
    Type    => 'multipart/mixed');
Let's say you realized you wanted to CC this message to other users, but you forgot to do so when you initialized $message. MIME::Lite's add() method allows you to add/modify members of the message hash you initialized when you created the object, $message. The add() method works like this:

$object->add(Header-Type => 'value');
To add a CC header (for multiple recipients) to $message, type:

$message->add(CC => 'user@host1.com, user@host2.com');
Bear in mind that hashes use a name/value pair that assigns one value to one name unless you're using hashes of hashes. This means you must be careful with the add() method, as add()ing a header that already exists means you'll overwrite the old value with the new. You might opt to use the replace() method instead, where:

$object->replace(Header-Type => 'new-header-value');
As you might have noticed, there are limitations to what you can do with MIME::Lite (it's light and fluffy, after all). MIME::Lite doesn't handle reading from filehandles, nor can it efficiently parse members of the message headers hash that was populated when you created a new MIME::Lite object.

The remainder of this chapter discusses the modules distributed with the MIME-tools package. The MIME-tools package was also written by Eryq (eryq@enteract.com), and features a MIME header parser and multipart message generator similar to that in MIME::Lite. You'll want to use the MIME-tools package for any application you'll write that needs to both parse and create multipart MIME messages as well as manipulate mail header fields and their respective values.

More Robust Attachments with MIME::Entity

The MIME::Entity module works much like MIME::Lite. When you create a new MIME::Entity object, you're creating a hash of header/value pairs as you did in MIME::Lite. Instead of using new for creating a new object, MIME::Entity uses the build method that works in a similar fashion.

Remember the $message object we used in the MIME::Lite examples? Let's re-write it so it can be used with MIME::Entity.

  1. First, you'll notice that new has been replaced with build. Since we'll be building a multipart message, we'll need the build method to get us started. You'll also notice that the message's type (multipart/mixed) doesn't appear as a named parameter like From, To, Subject, and Type. If you don't create your $message object properly, you'll get an error message that looks something like the following.

    can't build entity: no body, and not multipart! at 
    /usr/local/lib/perl5/site_perl/MIME/Entity.pm line xxx.
    
    Your MIME::Entity constructor should look like this:

    my $message = MIME::Entity->build(
        Type     =>"multipart/mixed",
        -From    => 'nvp@somemailhost.com',
        -To      => 'recipient@parts-are-parts.com',
        -Subject => 'GIF and JPEG for you!',
        -Type    => 'multipart/mixed');
    
  2. Now it's time to attach content to the message. You'll be using the attach method as you did with MIME::Lite.

    attach $message
        Path => '/path/to/content',
        Type => 'whatever/whatever',
        Encoding => 'base64';
    
    For example, you could attach a GIF to your message with:

    attach $message
        Path => '/home/nvp/some_nutty_image.gif',
        Type => 'image/gif',
        Encoding => 'base64';
    
  3. After you've added the content you wanted, you can send the mail message. Unlike MIME::Lite, MIME::Entity doesn't implement a send method, so you'll need to open an external m ail program to send your mail. The print method takes a filehandle as an argument, so you can open a pipe to your mail program in a filehandle and print the contents of the MIME message using the print method.

    open(MAILER, "|mailprogram params")
        or die("mailer error: $!");
    $message->print(\*MAILER);
    close(MAILER);
    
  4. Let's put it all together in this example.

    #!/usr/local/bin/perl -w
    
    use MIME::Entity;
    
    # the mail program of choice
    $mailprog = '/usr/lib/sendmail';
    
    # who the mail is from, to, and the subject of this message
    $from = 'wwwmailer@mimetest.com';
    $to = 'nvp@mail.somename.com';
    $subject = 'Looks like a runner!';
    
    # files to attach
    $text_attach = '/home/nvp/.cshrc';
    $img_attach = '/home/nvp/Perl/PRK/MIME/scripts/mygif.gif'; 
    
    # Text to place at the end of the message (after the other attachments)
    $message = 'This is the end of the message.';
    
    # Create the object, and set up the mail headers:
    $message = MIME::Entity->build(
        Type     =>"multipart/mixed",
        -From    => "$from",
        -To      => "$to",
        -Subject => "$subject");
    
    # Attachment #1: a simple text document: 
    attach $message  Path=>"$text_attach";
    
    # Attachment #2: a GIF file:
    attach $message  Path        => "$img_attach",
        Type        => "image/gif",
        Encoding    => "base64";
    
    # Attachment #3: some literal text:
    attach $message  Data=>$message;
    
    # Send it!
    open(MAIL, "|$mailprog -t $to") 
        or die("mail open $mailprog error: $!");
    $message->print(\*MAIL);
    close(MAIL);
    

Parsing MIME Messages with MIME::Parser

If you don't have a news or mail reader that supports MIME, you can save the MIME-encoded message as is and use MIME::Parser. With MIME::Parser, you create a new MIME::Parser object, a directory where both extracted content and prefixes for the extracted content are deposited. Then you tell MIME::Parser to read from a filehandle and handle the information accordingly. Let's examine the MIME::Parser's methods you'll need to parse multipart messages:

  1. The new() method, the constructor, is needed to create a MIME::Parser object.

    $object = new MIME::Parser;
    
  2. Next, the output_dir() method tells MIME::Parser where you'll be depositing the entities you've parsed from the multipart message.

    $output_to_dir = '/path/to/put/files';
    $object->output_dir($output_to_dir);
    
  3. Now, use the output_prefix() method that tells MIME::Parser to append a certain prefix to the entities it extracts from the multipart message. If you choose not to add a prefix to the output files, the filenames default to the names listed in the boundary sections of the multipart message.

    $default_prefix = 'entity_oh_my_goodness';
    $object->output_prefix($default_prefix);
    
  4. You'll need to read an input file containing the multipart message to be parsed. MIME::Parser's read() method reads from a filehandle and assigns the data to a scalar.

    open(INPUT, $input) 
        or die("Input $input error: $!");
    my $entity = $object->read(\*INPUT) 
        or die "couldn't parse MIME stream";
    close(INPUT);
    
  5. When you're ready to parse and output entities from the file, you'll need the output_to_core() method, which outputs the entities of a MIME message to the filesystem.

    $object->output_to_core;
    
    MIME::Parser can run silently. That is, you don't have to read what MIME::Parser tells you has been decoded. Although you should use any and all capabilities to debug your program's output, you don't have to use MIME::Parser's dump_skeleton() method that outputs something similar to:

    Content-type: multipart/mixed
    Body-file: NONE
    Subject: Mail message with MIME attachment.
    Num-parts: 2
    --
        Content-type: text/plain
        Body-file: /tmp/get_mime.pl-12113-1.doc
        --
        Content-type: image/gif
        Body-file: /tmp/tiffany.gif
        --
    
Here's an example that decodes a multipart/mixed message containing a GIF and some text:

#!/usr/local/bin/perl -w

# for our MIME parsing needs
use MIME::Parser;

# for our filename/path parsing needs
use File::Basename;

# please use me - i like it!
use strict;

# where to put the decoded parts
my $output_path = '/tmp';

# derive the base filenames of extracted parts from
# the name of the script. uses basename method of
# File::Basename.yippee.
my ($parsed) = (basename($0))[0];

# our input file, a multipart message
my $input_file = '/tmp/mime.head';

my $parser = MIME::Parser->new();

# output directory for parsed files
$parser->output_dir($output_path);

# Basenames for parsed files
$parser->output_prefix($parsed);

# KEY PART: Now, write the entities (parsed from boundaries) 
# to the filesystem
$parser->output_to_core();

# Parse the input file: (which can be @ARGV if you want it to be!)
open(INPUT, "$input_file") or die("Input error: $!");
my $entity = $parser->read(\*INPUT) 
    or die "couldn't parse MIME stream";
close(INPUT);

# Tell us about the MIME entities!  You can suppress
# this if you don't want output for the sake of debugging
$entity->dump_skeleton;          # for debugging 

[1] Some SMTP servers do name lookups that disconnect requests at port 25 if the remote hostname can't be resolved. This practice is used to thwart evil commercial email programs that send junk mail by disguising themselves as fictitious hosts.

[2] Mail::Folders enables you to read, write, add, save, and delete messages from your existing mail folders reliably as of Mail::Folder Version 0.06 and Perl Version 5.004_01.

Return to Table of Contents

Copyright 1998, O'Reilly & Associates, Inc.