diff options
author | French Language Team <french> | 2001-09-21 14:51:10 +0000 |
---|---|---|
committer | French Language Team <french> | 2001-09-21 14:51:10 +0000 |
commit | c41903268b445d1766fab81140fcd70bbff89b32 (patch) | |
tree | 59197619efe84ce82b63199c677e17e54872280c /Perl/MIME | |
parent | a5acac27e2214dad7f66db0eefcbf5b6bd03db18 (diff) |
MIME::Lite from libmime-lite-perl_2.117-1_all.deb (used to main the translators)
CVS version numbers
Perl/MIME/Lite.pm: INITIAL -> 1.1
Diffstat (limited to 'Perl/MIME')
-rw-r--r-- | Perl/MIME/Lite.pm | 3545 |
1 files changed, 3545 insertions, 0 deletions
diff --git a/Perl/MIME/Lite.pm b/Perl/MIME/Lite.pm new file mode 100644 index 00000000000..26903c3d202 --- /dev/null +++ b/Perl/MIME/Lite.pm @@ -0,0 +1,3545 @@ +package MIME::Lite; + + +=head1 NAME + +MIME::Lite - low-calorie MIME generator + + +=head1 SYNOPSIS + + use MIME::Lite; + +Create a single-part message: + + ### Create a new single-part message, to send a GIF file: + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'Helloooooo, nurse!', + Type =>'image/gif', + Encoding =>'base64', + Path =>'hellonurse.gif' + ); + +Create a multipart message (i.e., one with attachments): + + ### Create a new multipart message: + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'A message with 2 parts...', + Type =>'multipart/mixed' + ); + + ### Add parts (each "attach" has same arguments as "new"): + $msg->attach(Type =>'TEXT', + Data =>"Here's the GIF file you wanted" + ); + $msg->attach(Type =>'image/gif', + Path =>'aaa000123.gif', + Filename =>'logo.gif', + Disposition => 'attachment' + ); + +Output a message: + + ### Format as a string: + $str = $msg->as_string; + + ### Print to a filehandle (say, a "sendmail" stream): + $msg->print(\*SENDMAIL); + + +Send a message: + + ### Send in the "best" way (the default is to use "sendmail"): + $msg->send; + + + +=head1 DESCRIPTION + +In the never-ending quest for great taste with fewer calories, +we proudly present: I<MIME::Lite>. + +MIME::Lite is intended as a simple, standalone module for generating +(not parsing!) MIME messages... specifically, it allows you to +output a simple, decent single- or multi-part message with text or binary +attachments. It does not require that you have the Mail:: or MIME:: +modules installed. + +You can specify each message part as either the literal data itself (in +a scalar or array), or as a string which can be given to open() to get +a readable filehandle (e.g., "<filename" or "somecommand|"). + +You don't need to worry about encoding your message data: +this module will do that for you. It handles the 5 standard MIME encodings. + +If you need more sophisticated behavior, please get the MIME-tools +package instead. I will be more likely to add stuff to that toolkit +over this one. + + +=head1 EXAMPLES + +=head2 Create a simple message containing just text + + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'Helloooooo, nurse!', + Data =>"How's it goin', eh?" + ); + +=head2 Create a simple message containing just an image + + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'Helloooooo, nurse!', + Type =>'image/gif', + Encoding =>'base64', + Path =>'hellonurse.gif' + ); + + +=head2 Create a multipart message + + ### Create the multipart "container": + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'A message with 2 parts...', + Type =>'multipart/mixed' + ); + + ### Add the text message part: + ### (Note that "attach" has same arguments as "new"): + $msg->attach(Type =>'TEXT', + Data =>"Here's the GIF file you wanted" + ); + + ### Add the image part: + $msg->attach(Type =>'image/gif', + Path =>'aaa000123.gif', + Filename =>'logo.gif', + Disposition => 'attachment' + ); + + +=head2 Attach a GIF to a text message + +This will create a multipart message exactly as above, but using the +"attach to singlepart" hack: + + ### Start with a simple text message: + $msg = MIME::Lite->new( + From =>'me@myhost.com', + To =>'you@yourhost.com', + Cc =>'some@other.com, some@more.com', + Subject =>'A message with 2 parts...', + Type =>'TEXT', + Data =>"Here's the GIF file you wanted" + ); + + ### Attach a part... the make the message a multipart automatically: + $msg->attach(Type =>'image/gif', + Path =>'aaa000123.gif', + Filename =>'logo.gif' + ); + + +=head2 Attach a pre-prepared part to a message + + ### Create a standalone part: + $part = MIME::Lite->new( + Type =>'text/html', + Data =>'<H1>Hello</H1>', + ); + $part->attr('content-type.charset' => 'UTF8'); + $part->add('X-Comment' => 'A message for you'); + + ### Attach it to any message: + $msg->attach($part); + + +=head2 Print a message to a filehandle + + ### Write it to a filehandle: + $msg->print(\*STDOUT); + + ### Write just the header: + $msg->print_header(\*STDOUT); + + ### Write just the encoded body: + $msg->print_body(\*STDOUT); + + +=head2 Print a message into a string + + ### Get entire message as a string: + $str = $msg->as_string; + + ### Get just the header: + $str = $msg->header_as_string; + + ### Get just the encoded body: + $str = $msg->body_as_string; + + +=head2 Send a message + + ### Send in the "best" way (the default is to use "sendmail"): + $msg->send; + + +=head2 Send an HTML document... with images included! + + $msg = MIME::Lite->new( + To =>'you@yourhost.com', + Subject =>'HTML with in-line images!', + Type =>'multipart/related' + ); + $msg->attach(Type => 'text/html', + Data => qq{ <body> + Here's <i>my</i> image: + <img src="cid:myimage.gif"> + </body> } + ); + $msg->attach(Type => 'image/gif', + Id => 'myimage.gif', + Path => '/path/to/somefile.gif', + ); + $msg->send(); + + +=head2 Change how messages are sent + + ### Do something like this in your 'main': + if ($I_DONT_HAVE_SENDMAIL) { + MIME::Lite->send('smtp', "smtp.myisp.net", Timeout=>60); + } + + ### Now this will do the right thing: + $msg->send; ### will now use Net::SMTP as shown above + + + + + + +=head1 PUBLIC INTERFACE + +=head2 Global configuration + +To alter the way the entire module behaves, you have the following +methods/options: + +=over 4 + + +=item MIME::Lite->field_order() + +When used as a L<classmethod|/field_order>, this changes the default +order in which headers are output for I<all> messages. +However, please consider using the instance method variant instead, +so you won't stomp on other message senders in the same application. + + +=item MIME::Lite->quiet() + +This L<classmethod|/quiet> can be used to suppress/unsuppress +all warnings coming from this module. + + +=item MIME::Lite->send() + +When used as a L<classmethod|/send>, this can be used to specify +a different default mechanism for sending message. +The initial default is: + + MIME::Lite->send("sendmail", "/usr/lib/sendmail -t -oi -oem"); + +However, you should consider the similar but smarter and taint-safe variant: + + MIME::Lite->send("sendmail"); + +Or, for non-Unix users: + + MIME::Lite->send("smtp"); + + +=item $MIME::Lite::AUTO_CC + +If true, automatically send to the Cc/Bcc addresses for send_by_smtp(). +Default is B<true>. + + +=item $MIME::Lite::AUTO_CONTENT_TYPE + +If true, try to automatically choose the content type from the file name +in C<new()>/C<build()>. In other words, setting this true changes the +default C<Type> from C<"TEXT"> to C<"AUTO">. + +Default is B<false>, since we must maintain backwards-compatibility +with prior behavior. B<Please> consider keeping it false, +and just using Type 'AUTO' when you build() or attach(). + + +=item $MIME::Lite::AUTO_ENCODE + +If true, automatically choose the encoding from the content type. +Default is B<true>. + + +=item $MIME::Lite::AUTO_VERIFY + +If true, check paths to attachments right before printing, raising an exception +if any path is unreadable. +Default is B<true>. + + +=item $MIME::Lite::PARANOID + +If true, we won't attempt to use MIME::Base64, MIME::QuotedPrint, +or MIME::Types, even if they're available. +Default is B<false>. Please consider keeping it false, +and trusting these other packages to do the right thing. + + +=back + +=cut + +require 5.004; ### for /c modifier in m/\G.../gc modifier + +use Carp (); +use FileHandle; + +use strict; +use vars qw( + $AUTO_CC + $AUTO_CONTENT_TYPE + $AUTO_ENCODE + $AUTO_VERIFY + $PARANOID + $QUIET + $VANILLA + $VERSION + ); + + + +#============================== +#============================== +# +# GLOBALS, EXTERNAL/CONFIGURATION... + +### The package version, both in 1.23 style *and* usable by MakeMaker: +$VERSION = substr q$Revision$, 10; + +### Automatically interpret CC/BCC for SMTP: +$AUTO_CC = 1; + +### Automatically choose content type from file name: +$AUTO_CONTENT_TYPE = 0; + +### Automatically choose encoding from content type: +$AUTO_ENCODE = 1; + +### Check paths right before printing: +$AUTO_VERIFY = 1; + +### Set this true if you don't want to use MIME::Base64/QuotedPrint/Types: +$PARANOID = 0; + +### Don't warn me about dangerous activities: +$QUIET = undef; + +### Unsupported (for tester use): don't qualify boundary with time/pid: +$VANILLA = 0; + + +#============================== +#============================== +# +# GLOBALS, INTERNAL... + +### Find sendmail: +my $SENDMAIL = "/usr/lib/sendmail"; +(-x $SENDMAIL) or ($SENDMAIL = "/usr/sbin/sendmail"); +(-x $SENDMAIL) or ($SENDMAIL = "sendmail"); + +### Our sending facilities: +my $Sender = "sendmail"; +my %SenderArgs = ( + "sendmail" => ["$SENDMAIL -t -oi -oem"], + "smtp" => [], + "sub" => [], +); + +### Boundary counter: +my $BCount = 0; + +### Known Mail/MIME fields... these, plus some general forms like +### "x-*", are recognized by build(): +my %KnownField = map {$_=>1} +qw( + bcc cc comments date encrypted + from keywords message-id mime-version organization + received references reply-to return-path sender + subject to + + approved + ); + +### What external packages do we use for encoding? +my @Uses; + +### Header order: +my @FieldOrder; + +### See if we have File::Basename +my $HaveFileBasename = 0; +if (eval "require File::Basename") { # not affected by $PARANOID, core Perl + $HaveFileBasename = 1; + push @Uses, "F$File::Basename::VERSION"; +} + +### See if we have/want MIME::Types +my $HaveMimeTypes = 0; +if (!$PARANOID and eval "require MIME::Types") { + $HaveMimeTypes = 1; + push @Uses, "T$MIME::Types::VERSION"; +} + +#============================== +#============================== +# +# PRIVATE UTILITY FUNCTIONS... + +#------------------------------ +# +# fold STRING +# +# Make STRING safe as a field value. Remove leading/trailing whitespace, +# and make sure newlines are represented as newline+space + +sub fold { + my $str = shift; + $str =~ s/^\s*|\s*$//g; ### trim + $str =~ s/\n/\n /g; + $str; +} + +#------------------------------ +# +# gen_boundary +# +# Generate a new boundary to use. +# The unsupported $VANILLA is for test purposes only. + +sub gen_boundary { + return ("_----------=_".($VANILLA ? '' : int(time).$$).$BCount++); +} + +#------------------------------ +# +# known_field FIELDNAME +# +# Is this a recognized Mail/MIME field? + +sub known_field { + my $field = lc(shift); + $KnownField{$field} or ($field =~ m{^(content|resent|x)-.}); +} + +#------------------------------ +# +# is_mime_field FIELDNAME +# +# Is this a field I manage? + +sub is_mime_field { + $_[0] =~ /^(mime\-|content\-)/i; +} + +#------------------------------ +# +# extract_addrs STRING +# +# Split STRING into an array of email addresses: somewhat of a KLUDGE. +# +# Unless paranoid, we try to load the real code before supplying our own. + +my $ATOM = '[^ \000-\037()<>@,;:\134"\056\133\135]+'; +my $QSTR = '".*?"'; +my $WORD = '(?:' . $QSTR . '|' . $ATOM . ')'; +my $DOMAIN = '(?:' . $ATOM . '(?:' . '\\.' . $ATOM . ')*' . ')'; +my $LOCALPART = '(?:' . $WORD . '(?:' . '\\.' . $WORD . ')*' . ')'; +my $ADDR = '(?:' . $LOCALPART . '@' . $DOMAIN . ')'; +my $PHRASE = '(?:' . $WORD . ')+'; +my $SEP = "(?:^\\s*|\\s*,\\s*)"; ### before elems in a list + +sub my_extract_addrs { + my $str = shift; + my @addrs; + $str =~ s/\s/ /g; ### collapse whitespace + + pos($str) = 0; + while ($str !~ m{\G\s*\Z}gco) { + ### print STDERR "TACKLING: ".substr($str, pos($str))."\n"; + if ($str =~ m{\G$SEP$PHRASE\s*<\s*($ADDR)\s*>}gco) {push @addrs,$1} + elsif ($str =~ m{\G$SEP($ADDR)}gco) {push @addrs,$1} + elsif ($str =~ m{\G$SEP($ATOM)}gco) {push @addrs,$1} + else { + my $problem = substr($str, pos($str)); + die "can't extract address at <$problem> in <$str>\n"; + } + } + return @addrs; +} + +if (eval "require Mail::Address") { + push @Uses, "A$Mail::Address::VERSION"; + eval q{ + sub extract_addrs { + return map { $_->format } Mail::Address->parse($_[0]); + } + }; ### q +} +else { + eval q{ + sub extract_addrs { + return my_extract_addrs(@_); + } + }; ### q +} ### if + + + +#============================== +#============================== +# +# PRIVATE ENCODING FUNCTIONS... + +#------------------------------ +# +# encode_base64 STRING +# +# Encode the given string using BASE64. +# Unless paranoid, we try to load the real code before supplying our own. + +if (!$PARANOID and eval "require MIME::Base64") { + import MIME::Base64 qw(encode_base64); + push @Uses, "B$MIME::Base64::VERSION"; +} +else { + eval q{ +sub encode_base64 { + my $res = ""; + my $eol = "\n"; + + pos($_[0]) = 0; ### thanks, Andreas! + while ($_[0] =~ /(.{1,45})/gs) { + $res .= substr(pack('u', $1), 1); + chop($res); + } + $res =~ tr|` -_|AA-Za-z0-9+/|; + + ### Fix padding at the end: + my $padding = (3 - length($_[0]) % 3) % 3; + $res =~ s/.{$padding}$/'=' x $padding/e if $padding; + + ### Break encoded string into lines of no more than 76 characters each: + $res =~ s/(.{1,76})/$1$eol/g if (length $eol); + return $res; +} ### sub + } ### q +} ### if + +#------------------------------ +# +# encode_qp STRING +# +# Encode the given string, LINE BY LINE, using QUOTED-PRINTABLE. +# Stolen from MIME::QuotedPrint by Gisle Aas, with a slight bug fix: we +# break lines earlier. Notice that this seems not to work unless +# encoding line by line. +# +# Unless paranoid, we try to load the real code before supplying our own. + +if (!$PARANOID and eval "require MIME::QuotedPrint") { + import MIME::QuotedPrint qw(encode_qp); + push @Uses, "Q$MIME::QuotedPrint::VERSION"; +} +else { + eval q{ +sub encode_qp { + my $res = shift; + local($_); + $res =~ s/([^ \t\n!-<>-~])/sprintf("=%02X", ord($1))/eg; ### rule #2,#3 + $res =~ s/([ \t]+)$/ + join('', map { sprintf("=%02X", ord($_)) } + split('', $1) + )/egm; ### rule #3 (encode whitespace at eol) + + ### rule #5 (lines shorter than 76 chars, but can't break =XX escapes: + my $brokenlines = ""; + $brokenlines .= "$1=\n" while $res =~ s/^(.{70}([^=]{2})?)//; ### 70 was 74 + $brokenlines =~ s/=\n$// unless length $res; + "$brokenlines$res"; +} ### sub + } ### q +} ### if + + +#------------------------------ +# +# encode_8bit STRING +# +# Encode the given string using 8BIT. +# This breaks long lines into shorter ones. + +sub encode_8bit { + my $str = shift; + $str =~ s/^(.{990})/$1\n/mg; + $str; +} + +#------------------------------ +# +# encode_7bit STRING +# +# Encode the given string using 7BIT. +# This NO LONGER protects people through encoding. + +sub encode_7bit { + my $str = shift; + $str =~ s/[\x80-\xFF]//g; + $str =~ s/^(.{990})/$1\n/mg; + $str; +} + +#============================== +#============================== + +=head2 Construction + +=over 4 + +=cut + + +#------------------------------ + +=item new [PARAMHASH] + +I<Class method, constructor.> +Create a new message object. + +If any arguments are given, they are passed into C<build()>; otherwise, +just the empty object is created. + +=cut + +sub new { + my $class = shift; + + ### Create basic object: + my $self = { + Attrs => {}, ### MIME attributes + Header => [], ### explicit message headers + Parts => [], ### array of parts + }; + bless $self, $class; + + ### Build, if needed: + return (@_ ? $self->build(@_) : $self); +} + + +#------------------------------ + +=item attach PART + +=item attach PARAMHASH... + +I<Instance method.> +Add a new part to this message, and return the new part. + +If you supply a single PART argument, it will be regarded +as a MIME::Lite object to be attached. Otherwise, this +method assumes that you are giving in the pairs of a PARAMHASH +which will be sent into C<new()> to create the new part. + +One of the possibly-quite-useful hacks thrown into this is the +"attach-to-singlepart" hack: if you attempt to attach a part (let's +call it "part 1") to a message that doesn't have a content-type +of "multipart" or "message", the following happens: + +=over 4 + +=item * + +A new part (call it "part 0") is made. + +=item * + +The MIME attributes and data (but I<not> the other headers) +are cut from the "self" message, and pasted into "part 0". + +=item * + +The "self" is turned into a "multipart/mixed" message. + +=item * + +The new "part 0" is added to the "self", and I<then> "part 1" is added. + +=back + +One of the nice side-effects is that you can create a text message +and then add zero or more attachments to it, much in the same way +that a user agent like Netscape allows you to do. + +=cut + +sub attach { + my $self = shift; + + ### Create new part, if necessary: + my $part1 = ((@_ == 1) ? shift : ref($self)->new(Top=>0, @_)); + + ### Do the "attach-to-singlepart" hack: + if ($self->attr('content-type') !~ m{^(multipart|message)/}i) { + + ### Create part zero: + my $part0 = ref($self)->new; + + ### Cut MIME stuff from self, and paste into part zero: + foreach (qw(Attrs Data Path FH)) { + $part0->{$_} = $self->{$_}; delete($self->{$_}); + } + $part0->top_level(0); ### clear top-level attributes + + ### Make self a top-level multipart: + $self->{Attrs} ||= {}; ### reset + $self->attr('content-type' => 'multipart/mixed'); + $self->attr('content-type.boundary' => gen_boundary()); + $self->attr('content-transfer-encoding' => '7bit'); + $self->top_level(1); ### activate top-level attributes + + ### Add part 0: + push @{$self->{Parts}}, $part0; + } + + ### Add the new part: + push @{$self->{Parts}}, $part1; + $part1; +} + +#------------------------------ + +=item build [PARAMHASH] + +I<Class/instance method, initializer.> +Create (or initialize) a MIME message object. +Normally, you'll use the following keys in PARAMHASH: + + * Data, FH, or Path (either one of these, or none if multipart) + * Type (e.g., "image/jpeg") + * From, To, and Subject (if this is the "top level" of a message) + +The PARAMHASH can contain the following keys: + +=over 4 + +=item (fieldname) + +Any field you want placed in the message header, taken from the +standard list of header fields (you don't need to worry about case): + + Approved Encrypted Received Sender + Bcc From References Subject + Cc Keywords Reply-To To + Comments Message-ID Resent-* X-* + Content-* MIME-Version Return-Path + Date Organization + +To give experienced users some veto power, these fields will be set +I<after> the ones I set... so be careful: I<don't set any MIME fields> +(like C<Content-type>) unless you know what you're doing! + +To specify a fieldname that's I<not> in the above list, even one that's +identical to an option below, just give it with a trailing C<":">, +like C<"My-field:">. When in doubt, that I<always> signals a mail +field (and it sort of looks like one too). + +=item Data + +I<Alternative to "Path" or "FH".> +The actual message data. This may be a scalar or a ref to an array of +strings; if the latter, the message consists of a simple concatenation +of all the strings in the array. + +=item Datestamp + +I<Optional.> +If given true (or omitted), we force the creation of a C<Date:> field +stamped with the current date/time if this is a top-level message. +You may want this if using L<send_by_smtp()|/send_by_smtp>. +If you don't want this to be done, either provide your own Date +or explicitly set this to false. + +=item Disposition + +I<Optional.> +The content disposition, C<"inline"> or C<"attachment">. +The default is C<"inline">. + +=item Encoding + +I<Optional.> +The content transfer encoding that should be used to encode your data: + + Use encoding: | If your message contains: + ------------------------------------------------------------ + 7bit | Only 7-bit text, all lines <1000 characters + 8bit | 8-bit text, all lines <1000 characters + quoted-printable | 8-bit text or long lines (more reliable than "8bit") + base64 | Largely non-textual data: a GIF, a tar file, etc. + +The default is taken from the Type; generally it is "binary" (no +encoding) for text/*, message/*, and multipart/*, and "base64" for +everything else. A value of C<"binary"> is generally I<not> suitable +for sending anything but ASCII text files with lines under 1000 +characters, so consider using one of the other values instead. + +In the case of "7bit"/"8bit", long lines are automatically chopped to +legal length; in the case of "7bit", all 8-bit characters are +automatically I<removed>. This may not be what you want, so pick your +encoding well! For more info, see L<"A MIME PRIMER">. + +=item FH + +I<Alternative to "Data" or "Path".> +Filehandle containing the data, opened for reading. +See "ReadNow" also. + +=item Filename + +I<Optional.> +The name of the attachment. You can use this to supply a +recommended filename for the end-user who is saving the attachment +to disk. You only need this if the filename at the end of the +"Path" is inadequate, or if you're using "Data" instead of "Path". +You should I<not> put path information in here (e.g., no "/" +or "\" or ":" characters should be used). + +=item Id + +I<Optional.> +Same as setting "content-id". + +=item Length + +I<Optional.> +Set the content length explicitly. Normally, this header is automatically +computed, but only under certain circumstances (see L<"Limitations">). + +=item Path + +I<Alternative to "Data" or "FH".> +Path to a file containing the data... actually, it can be any open()able +expression. If it looks like a path, the last element will automatically +be treated as the filename. +See "ReadNow" also. + +=item ReadNow + +I<Optional, for use with "Path".> +If true, will open the path and slurp the contents into core now. +This is useful if the Path points to a command and you don't want +to run the command over and over if outputting the message several +times. B<Fatal exception> raised if the open fails. + +=item Top + +I<Optional.> +If defined, indicates whether or not this is a "top-level" MIME message. +The parts of a multipart message are I<not> top-level. +Default is true. + +=item Type + +I<Optional.> +The MIME content type, or one of these special values (case-sensitive): + + "TEXT" means "text/plain" + "BINARY" means "application/octet-stream" + "AUTO" means attempt to guess from the filename, falling back + to 'application/octet-stream'. This is good if you have + MIME::Types on your system and you have no idea what + file might be used for the attachment. + +The default is C<"TEXT">, but it will be C<"AUTO"> if you set +$AUTO_CONTENT_TYPE to true (sorry, but you have to enable +it explicitly, since we don't want to break code which depends +on the old behavior). + +=back + +A picture being worth 1000 words (which +is of course 2000 bytes, so it's probably more of an "icon" than a "picture", +but I digress...), here are some examples: + + $msg = MIME::Lite->build( + From => 'yelling@inter.com', + To => 'stocking@fish.net', + Subject => "Hi there!", + Type => 'TEXT', + Encoding => '7bit', + Data => "Just a quick note to say hi!"); + + $msg = MIME::Lite->build( + From => 'dorothy@emerald-city.oz', + To => 'gesundheit@edu.edu.edu', + Subject => "A gif for U" + Type => 'image/gif', + Path => "/home/httpd/logo.gif"); + + $msg = MIME::Lite->build( + From => 'laughing@all.of.us', + To => 'scarlett@fiddle.dee.de', + Subject => "A gzipp'ed tar file", + Type => 'x-gzip', + Path => "gzip < /usr/inc/somefile.tar |", + ReadNow => 1, + Filename => "somefile.tgz"); + +To show you what's really going on, that last example could also +have been written: + + $msg = new MIME::Lite; + $msg->build(Type => 'x-gzip', + Path => "gzip < /usr/inc/somefile.tar |", + ReadNow => 1, + Filename => "somefile.tgz"); + $msg->add(From => "laughing@all.of.us"); + $msg->add(To => "scarlett@fiddle.dee.de"); + $msg->add(Subject => "A gzipp'ed tar file"); + +=cut + +sub build { + my $self = shift; + my %params = @_; + my @params = @_; + my $key; + + ### Miko's note: reorganized to check for exactly one of Data, Path, or FH + (defined($params{Data})+defined($params{Path})+defined($params{FH}) <= 1) + or Carp::croak "supply exactly zero or one of (Data|Path|FH).\n"; + + ### Create new instance, if necessary: + ref($self) or $self = $self->new; + + + ### CONTENT-TYPE.... + ### + + ### Get content-type or content-type-macro: + my $type = ($params{Type} || ($AUTO_CONTENT_TYPE ? 'AUTO' : 'TEXT')); + + ### Interpret content-type-macros: + if ($type eq 'TEXT') { $type = 'text/plain'; } + elsif ($type eq 'BINARY') { $type = 'application/octet-stream' } + elsif ($type eq 'AUTO') { $type = $self->suggest_type($params{Path}); } + + ### We now have a content-type; set it: + $type = lc($type); + $self->attr('content-type' => $type); + + ### Get some basic attributes from the content type: + my $is_multipart = ($type =~ m{^(multipart)/}i); + + ### Add in the multipart boundary: + if ($is_multipart) { + my $boundary = gen_boundary(); + $self->attr('content-type.boundary' => $boundary); + } + + + ### CONTENT-ID... + ### + $self->attr('content-id' => $params{Id}) if defined($params{Id}); + + + ### DATA OR PATH... + ### Note that we must do this *after* we get the content type, + ### in case read_now() is invoked, since it needs the binmode(). + + ### Get data, as... + ### ...either literal data: + if (defined($params{Data})) { + $self->data($params{Data}); + } + ### ...or a path to data: + elsif (defined($params{Path})) { + $self->path($params{Path}); ### also sets filename + $self->read_now if $params{ReadNow}; + } + ### ...or a filehandle to data: + ### Miko's note: this part works much like the path routine just above, + elsif (defined($params{FH})) { + $self->fh($params{FH}); + $self->read_now if $params{ReadNow}; ### implement later + } + + + ### FILENAME... (added by Ian Smith <ian@safeway.dircon.co.uk> on 8/4/97) + ### Need this to make sure the filename is added. The Filename + ### attribute is ignored, otherwise. + if (defined($params{Filename})) { + $self->filename($params{Filename}); + } + + + ### CONTENT-TRANSFER-ENCODING... + ### + + ### Get it: + my $enc = ($params{Encoding} || + ($AUTO_ENCODE and $self->suggest_encoding($type)) || + 'binary'); + $self->attr('content-transfer-encoding' => lc($enc)); + + ### Sanity check: + if ($type =~ m{^(multipart|message)/}) { + ($enc =~ m{^(7bit|8bit|binary)\Z}) or + Carp::croak("illegal MIME: ". + "can't have encoding $enc with type $type\n"); + } + + ### CONTENT-DISPOSITION... + ### Default is inline for single, none for multis: + ### + my $disp = ($params{Disposition} or ($is_multipart ? undef : 'inline')); + $self->attr('content-disposition' => $disp); + + ### CONTENT-LENGTH... + ### + my $length; + if (exists($params{Length})) { ### given by caller: + $self->attr('content-length' => $params{Length}); + } + else { ### compute it ourselves + $self->get_length; + } + + ### Init the top-level fields: + my $is_top = defined($params{Top}) ? $params{Top} : 1; + $self->top_level($is_top); + + ### Datestamp if desired: + my $ds_wanted = $params{Datestamp}; + my $ds_defaulted = ($is_top and !exists($params{Datestamp})); + if (($ds_wanted or $ds_defaulted) and !exists($params{Date})) { + my ($u_wdy, $u_mon, $u_mdy, $u_time, $u_y4) = + split /\s+/, gmtime().""; ### should be non-locale-dependent + my $date = "$u_wdy, $u_mdy $u_mon $u_y4 $u_time UT"; + $self->add("date", $date); + } + + ### Set message headers: + my @paramz = @params; + my $field; + while (@paramz) { + my ($tag, $value) = (shift(@paramz), shift(@paramz)); + + ### Get tag, if a tag: + if ($tag =~ /^-(.*)/) { ### old style, backwards-compatibility + $field = lc($1); + } + elsif ($tag =~ /^(.*):$/) { ### new style + $field = lc($1); + } + elsif (known_field($field = lc($tag))) { ### known field + ### no-op + } + else { ### not a field: + next; + } + + ### Add it: + $self->add($field, $value); + } + + ### Done! + $self; +} + +=back + +=cut + + +#============================== +#============================== + +=head2 Setting/getting headers and attributes + +=over 4 + +=cut + +#------------------------------ +# +# top_level ONOFF +# +# Set/unset the top-level attributes and headers. +# This affects "MIME-Version" and "X-Mailer". + +sub top_level { + my ($self, $onoff) = @_; + if ($onoff) { + $self->attr('MIME-Version' => '1.0'); + my $uses = (@Uses ? ("(" . join("; ", @Uses) . ")") : ''); + $self->replace('X-Mailer' => "MIME::Lite $VERSION $uses") + unless $VANILLA; + } + else { + $self->attr('MIME-Version' => undef); + $self->delete('X-Mailer'); + } +} + +#------------------------------ + +=item add TAG,VALUE + +I<Instance method.> +Add field TAG with the given VALUE to the end of the header. +The TAG will be converted to all-lowercase, and the VALUE +will be made "safe" (returns will be given a trailing space). + +B<Beware:> any MIME fields you "add" will override any MIME +attributes I have when it comes time to output those fields. +Normally, you will use this method to add I<non-MIME> fields: + + $msg->add("Subject" => "Hi there!"); + +Giving VALUE as an arrayref will cause all those values to be added. +This is only useful for special multiple-valued fields like "Received": + + $msg->add("Received" => ["here", "there", "everywhere"] + +Giving VALUE as the empty string adds an invisible placeholder +to the header, which can be used to suppress the output of +the "Content-*" fields or the special "MIME-Version" field. +When suppressing fields, you should use replace() instead of add(): + + $msg->replace("Content-disposition" => ""); + +I<Note:> add() is probably going to be more efficient than C<replace()>, +so you're better off using it for most applications if you are +certain that you don't need to delete() the field first. + +I<Note:> the name comes from Mail::Header. + +=cut + +sub add { + my $self = shift; + my $tag = lc(shift); + my $value = shift; + + ### If a dangerous option, warn them: + Carp::carp "Explicitly setting a MIME header field ($tag) is dangerous:\n". + "use the attr() method instead.\n" + if (is_mime_field($tag) && !$QUIET); + + ### Get array of clean values: + my @vals = ((ref($value) and (ref($value) eq 'ARRAY')) + ? @{$value} + : ($value.'')); + map { s/\n/\n /g } @vals; + + ### Add them: + foreach (@vals) { + push @{$self->{Header}}, [$tag, $_]; + } +} + +#------------------------------ + +=item attr ATTR,[VALUE] + +I<Instance method.> +Set MIME attribute ATTR to the string VALUE. +ATTR is converted to all-lowercase. +This method is normally used to set/get MIME attributes: + + $msg->attr("content-type" => "text/html"); + $msg->attr("content-type.charset" => "US-ASCII"); + $msg->attr("content-type.name" => "homepage.html"); + +This would cause the final output to look something like this: + + Content-type: text/html; charset=US-ASCII; name="homepage.html" + +Note that the special empty sub-field tag indicates the anonymous +first sub-field. + +Giving VALUE as undefined will cause the contents of the named +subfield to be deleted. + +Supplying no VALUE argument just returns the attribute's value: + + $type = $msg->attr("content-type"); ### returns "text/html" + $name = $msg->attr("content-type.name"); ### returns "homepage.html" + +=cut + +sub attr { + my ($self, $attr, $value) = @_; + $attr = lc($attr); + + ### Break attribute name up: + my ($tag, $subtag) = split /\./, $attr; + defined($subtag) or $subtag = ''; + + ### Set or get? + if (@_ > 2) { ### set: + $self->{Attrs}{$tag} ||= {}; ### force hash + delete $self->{Attrs}{$tag}{$subtag}; ### delete first + if (defined($value)) { ### set... + $value =~ s/[\r\n]//g; ### make clean + $self->{Attrs}{$tag}{$subtag} = $value; + } + } + + ### Return current value: + $self->{Attrs}{$tag}{$subtag}; +} + +sub _safe_attr { + my ($self, $attr) = @_; + my $v = $self->attr($attr); + defined($v) ? $v : ''; +} + +#------------------------------ + +=item delete TAG + +I<Instance method.> +Delete field TAG with the given VALUE to the end of the header. +The TAG will be converted to all-lowercase. + + $msg->delete("Subject"); + +I<Note:> the name comes from Mail::Header. + +=cut + +sub delete { + my $self = shift; + my $tag = lc(shift); + + ### Delete from the header: + my $hdr = []; + my $field; + foreach $field (@{$self->{Header}}) { + push @$hdr, $field if ($field->[0] ne $tag); + } + $self->{Header} = $hdr; + $self; +} + + +#------------------------------ + +=item field_order FIELD,...FIELD + +I<Class/instance method.> +Change the order in which header fields are output for this object: + + $msg->field_order('from', 'to', 'content-type', 'subject'); + +When used as a class method, changes the default settings for +all objects: + + MIME::Lite->field_order('from', 'to', 'content-type', 'subject'); + +Case does not matter: all field names will be coerced to lowercase. +In either case, supply the empty array to restore the default ordering. + +=cut + +sub field_order { + my $self = shift; + if (ref($self)) { $self->{FieldOrder} = [ map { lc($_) } @_ ] } + else { @FieldOrder = map { lc($_) } @_ } +} + +#------------------------------ + +=item fields + +I<Instance method.> +Return the full header for the object, as a ref to an array +of C<[TAG, VALUE]> pairs, where each TAG is all-lowercase. +Note that any fields the user has explicitly set will override the +corresponding MIME fields that we would otherwise generate. +So, don't say... + + $msg->set("Content-type" => "text/html; charset=US-ASCII"); + +unless you want the above value to override the "Content-type" +MIME field that we would normally generate. + +I<Note:> I called this "fields" because the header() method of +Mail::Header returns something different, but similar enough to +be confusing. + +You can change the order of the fields: see L</field_order>. +You really shouldn't need to do this, but some people have to +deal with broken mailers. + +=cut + +sub fields { + my $self = shift; + my @fields; + + ### Get a lookup-hash of all *explicitly-given* fields: + my %explicit = map { $_->[0] => 1 } @{$self->{Header}}; + + ### Start with any MIME attributes not given explicitly: + my $tag; + foreach $tag (sort keys %{$self->{Attrs}}) { + + ### Skip if explicit: + next if ($explicit{$tag}); + + ### Skip if no subtags: + my @subtags = keys %{$self->{Attrs}{$tag}}; + @subtags or next; + + ### Create string: + my $value; + defined($value = $self->{Attrs}{$tag}{''}) or next; ### need default + foreach (sort @subtags) { + next if ($_ eq ''); + $value .= qq{; $_="$self->{Attrs}{$tag}{$_}"}; + } + + ### Add to running fields; + push @fields, [$tag, $value]; + } + + ### Add remaining fields (note that we duplicate the array for safety): + foreach (@{$self->{Header}}) { + push @fields, [@{$_}]; + } + + ### Final step: + ### If a suggested ordering was given, we "sort" by that ordering. + ### The idea is that we give each field a numeric rank, which is + ### (1000 * order(field)) + origposition. + my @order = @{$self->{FieldOrder} || []}; ### object-specific + @order or @order = @FieldOrder; ### no? maybe generic + if (@order) { ### either? + + ### Create hash mapping field names to 1-based rank: + my %rank = map {$order[$_] => (1+$_)} (0..$#order); + + ### Create parallel array to @fields, called @ranked. + ### It contains fields tagged with numbers like 2003, where the + ### 3 is the original 0-based position, and 2000 indicates that + ### we wanted ths type of field to go second. + my @ranked = map { + [ + ($_ + 1000*($rank{lc($fields[$_][0])} || (2+$#order))), + $fields[$_] + ] + } (0..$#fields); + # foreach (@ranked) { + # print STDERR "RANKED: $_->[0] $_->[1][0] $_->[1][1]\n"; + # } + + ### That was half the Schwartzian transform. Here's the rest: + @fields = map { $_->[1] } + sort { $a->[0] <=> $b->[0] } + @ranked; + } + + ### Done! + return \@fields; +} + + +#------------------------------ + +=item filename [FILENAME] + +I<Instance method.> +Set the filename which this data will be reported as. +This actually sets both "standard" attributes. + +With no argument, returns the filename as dictated by the +content-disposition. + +=cut + +sub filename { + my ($self, $filename) = @_; + if (@_ > 1) { + $self->attr('content-type.name' => $filename); + $self->attr('content-disposition.filename' => $filename); + } + $self->attr('content-disposition.filename'); +} + +#------------------------------ + +=item get TAG,[INDEX] + +I<Instance method.> +Get the contents of field TAG, which might have been set +with set() or replace(). Returns the text of the field. + + $ml->get('Subject', 0); + +If the optional 0-based INDEX is given, then we return the INDEX'th +occurence of field TAG. Otherwise, we look at the context: +In a scalar context, only the first (0th) occurence of the +field is returned; in an array context, I<all> occurences are returned. + +I<Warning:> this should only be used with non-MIME fields. +Behavior with MIME fields is TBD, and will raise an exception for now. + +=cut + +sub get { + my ($self, $tag, $index) = @_; + $tag = lc($tag); + Carp::croak "get: can't be used with MIME fields\n" if is_mime_field($tag); + + my @all = map { ($_->[0] eq $tag) ? $_->[1] : ()} @{$self->{Header}}; + (defined($index) ? $all[$index] : (wantarray ? @all : $all[0])); +} + +#------------------------------ + +=item get_length + +I<Instance method.> +Recompute the content length for the message I<if the process is trivial>, +setting the "content-length" attribute as a side-effect: + + $msg->get_length; + +Returns the length, or undefined if not set. + +I<Note:> the content length can be difficult to compute, since it +involves assembling the entire encoded body and taking the length +of it (which, in the case of multipart messages, means freezing +all the sub-parts, etc.). + +This method only sets the content length to a defined value if the +message is a singlepart with C<"binary"> encoding, I<and> the body is +available either in-core or as a simple file. Otherwise, the content +length is set to the undefined value. + +Since content-length is not a standard MIME field anyway (that's right, kids: +it's not in the MIME RFCs, it's an HTTP thing), this seems pretty fair. + +=cut + +#---- +# Miko's note: I wasn't quite sure how to handle this, so I waited to hear +# what you think. Given that the content-length isn't always required, +# and given the performance cost of calculating it from a file handle, +# I thought it might make more sense to add some some sort of computelength +# property. If computelength is false, then the length simply isn't +# computed. What do you think? +# +# Eryq's reply: I agree; for now, we can silently leave out the content-type. + +sub get_length { + my $self = shift; + + my $is_multipart = ($self->attr('content-type') =~ m{^multipart/}i); + my $enc = lc($self->attr('content-transfer-encoding') || 'binary'); + my $length; + if (!$is_multipart && ($enc eq "binary")){ ### might figure it out cheap: + if (defined($self->{Data})) { ### it's in core + $length = length($self->{Data}); + } + elsif (defined($self->{FH})) { ### it's in a filehandle + ### no-op: it's expensive, so don't bother + } + elsif (defined($self->{Path})) { ### it's a simple file! + $length = (-s $self->{Path}) if (-e $self->{Path}); + } + } + $self->attr('content-length' => $length); + return $length; +} + +#------------------------------ + +=item parts + +I<Instance method.> +Return the parts of this entity, and this entity only. +Returns empty array if this entity has no parts. + +This is B<not> recursive! Parts can have sub-parts; use +parts_DFS() to get everything. + +=cut + +sub parts { + my $self = shift; + @{$self->{Parts} || []}; +} + +#------------------------------ + +=item parts_DFS + +I<Instance method.> +Return the list of all MIME::Lite objects included in the entity, +starting with the entity itself, in depth-first-search order. +If this object has no parts, it alone will be returned. + +=cut + +sub parts_DFS { + my $self = shift; + return ($self, map { $_->parts_DFS } $self->parts); +} + +#------------------------------ + +=item preamble [TEXT] + +I<Instance method.> +Get/set the preamble string, assuming that this object has subparts. +Set it to undef for the default string. + +=cut + +sub preamble { + my $self = shift; + $self->{Preamble} = shift if @_; + $self->{Preamble}; +} + +#------------------------------ + +=item replace TAG,VALUE + +I<Instance method.> +Delete all occurences of fields named TAG, and add a new +field with the given VALUE. TAG is converted to all-lowercase. + +B<Beware> the special MIME fields (MIME-version, Content-*): +if you "replace" a MIME field, the replacement text will override +the I<actual> MIME attributes when it comes time to output that field. +So normally you use attr() to change MIME fields and add()/replace() to +change I<non-MIME> fields: + + $msg->replace("Subject" => "Hi there!"); + +Giving VALUE as the I<empty string> will effectively I<prevent> that +field from being output. This is the correct way to suppress +the special MIME fields: + + $msg->replace("Content-disposition" => ""); + +Giving VALUE as I<undefined> will just cause all explicit values +for TAG to be deleted, without having any new values added. + +I<Note:> the name of this method comes from Mail::Header. + +=cut + +sub replace { + my ($self, $tag, $value) = @_; + $self->delete($tag); + $self->add($tag, $value) if defined($value); +} + + +#------------------------------ + +=item scrub + +I<Instance method.> +B<This is Alpha code. If you use it, please let me know how it goes.> +Recursively goes through the "parts" tree of this message and tries +to find MIME attributes that can be removed. +With an array argument, removes exactly those attributes; e.g.: + + $msg->scrub(['content-disposition', 'content-length']); + +Is the same as recursively doing: + + $msg->replace('Content-disposition' => ''); + $msg->replace('Content-length' => ''); + +=cut + +sub scrub { + my ($self, @a) = @_; + my ($expl) = @a; + local $QUIET = 1; + + ### Scrub me: + if (!@a) { ### guess + + ### Scrub length always: + $self->replace('content-length', ''); + + ### Scrub disposition if no filename, or if content-type has same info: + if (!$self->_safe_attr('content-disposition.filename') || + $self->_safe_attr('content-type.name')) { + $self->replace('content-disposition', ''); + } + + ### Scrub encoding if effectively unencoded: + if ($self->_safe_attr('content-transfer-encoding') =~ + /^(7bit|8bit|binary)$/i) { + $self->replace('content-transfer-encoding', ''); + } + + ### Scrub charset if US-ASCII: + if ($self->_safe_attr('content-type.charset') =~ /^(us-ascii)/i) { + $self->attr('content-type.charset' => undef); + } + + ### TBD: this is not really right for message/digest: + if ((keys %{$self->{Attrs}{'content-type'}} == 1) and + ($self->_safe_attr('content-type') eq 'text/plain')) { + $self->replace('content-type', ''); + } + } + elsif ($expl and (ref($expl) eq 'ARRAY')) { + foreach (@{$expl}) { $self->replace($_, ''); } + } + + ### Scrub my kids: + foreach (@{$self->{Parts}}) { $_->scrub(@a); } +} + +=back + +=cut + + +#============================== +#============================== + +=head2 Setting/getting message data + +=over 4 + +=cut + +#------------------------------ + +=item binmode [OVERRIDE] + +I<Instance method.> +With no argument, returns whether or not it thinks that the data +(as given by the "Path" argument of C<build()>) should be read using +binmode() (for example, when C<read_now()> is invoked). + +The default behavior is that any content type other than +C<text/*> or C<message/*> is binmode'd; this should in general work fine. + +With a defined argument, this method sets an explicit "override" +value. An undefined argument unsets the override. +The new current value is returned. + +=cut + +sub binmode { + my $self = shift; + $self->{Binmode} = shift if (@_); ### argument? set override + return (defined($self->{Binmode}) + ? $self->{Binmode} + : ($self->attr("content-type") !~ m{^(text|message)/}i)); +} + +#------------------------------ + +=item data [DATA] + +I<Instance method.> +Get/set the literal DATA of the message. The DATA may be +either a scalar, or a reference to an array of scalars (which +will simply be joined). + +I<Warning:> setting the data causes the "content-length" attribute +to be recomputed (possibly to nothing). + +=cut + +sub data { + my $self = shift; + if (@_) { + $self->{Data} = ((ref($_[0]) eq 'ARRAY') ? join('', @{$_[0]}) : $_[0]); + $self->get_length; + } + $self->{Data}; +} + +#------------------------------ + +=item fh [FILEHANDLE] + +I<Instance method.> +Get/set the FILEHANDLE which contains the message data. + +Takes a filehandle as an input and stores it in the object. +This routine is similar to path(); one important difference is that +no attempt is made to set the content length. + +=cut + +sub fh { + my $self = shift; + $self->{FH} = shift if @_; + $self->{FH}; +} + +#------------------------------ + +=item path [PATH] + +I<Instance method.> +Get/set the PATH to the message data. + +I<Warning:> setting the path recomputes any existing "content-length" field, +and re-sets the "filename" (to the last element of the path if it +looks like a simple path, and to nothing if not). + +=cut + +sub path { + my $self = shift; + if (@_) { + + ### Set the path, and invalidate the content length: + $self->{Path} = shift; + + ### Re-set filename, extracting it from path if possible: + my $filename; + if ($self->{Path} and ($self->{Path} !~ /\|$/)) { ### non-shell path: + ($filename = $self->{Path}) =~ s/^<//; + + ### Consult File::Basename, maybe: + if ($HaveFileBasename) { + $filename = File::Basename::basename($filename); + } + else { + ($filename) = ($filename =~ m{([^\/]+)\Z}); + } + } + $self->filename($filename); + + ### Reset the length: + $self->get_length; + } + $self->{Path}; +} + +#------------------------------ + +=item resetfh [FILEHANDLE] + +I<Instance method.> +Set the current position of the filehandle back to the beginning. +Only applies if you used "FH" in build() or attach() for this message. + +Returns false if unable to reset the filehandle (since not all filehandles +are seekable). + +=cut + +#---- +# Miko's note: With the Data and Path, the same data could theoretically +# be reused. However, file handles need to be reset to be reused, +# so I added this routine. +# +# Eryq reply: beware... not all filehandles are seekable (think about STDIN)! + +sub resetfh { + my $self = shift; + seek($self->{FH},0,0); +} + +#------------------------------ + +=item read_now + +I<Instance method.> +Forces data from the path/filehandle (as specified by C<build()>) +to be read into core immediately, just as though you had given it +literally with the C<Data> keyword. + +Note that the in-core data will always be used if available. + +Be aware that everything is slurped into a giant scalar: you may not want +to use this if sending tar files! The benefit of I<not> reading in the data +is that very large files can be handled by this module if left on disk +until the message is output via C<print()> or C<print_body()>. + +=cut + +sub read_now { + my $self = shift; + local $/ = undef; + + if ($self->{FH}) { ### data from a filehandle: + my $chunk; + my @chunks; + CORE::binmode($self->{FH}) if $self->binmode; + while (read($self->{FH}, $chunk, 1024)) { + push @chunks, $chunk; + } + $self->{Data} = join '', @chunks; + } + elsif ($self->{Path}) { ### data from a path: + open SLURP, $self->{Path} or Carp::croak "open $self->{Path}: $!\n"; + CORE::binmode(SLURP) if $self->binmode; + $self->{Data} = <SLURP>; ### sssssssssssssslurp... + close SLURP; ### ...aaaaaaaaahhh! + } +} + +#------------------------------ + +=item sign PARAMHASH + +I<Instance method.> +Sign the message. This forces the message to be read into core, +after which the signature is appended to it. + +=over 4 + +=item Data + +As in C<build()>: the literal signature data. +Can be either a scalar or a ref to an array of scalars. + +=item Path + +As in C<build()>: the path to the file. + +=back + +If no arguments are given, the default is: + + Path => "$ENV{HOME}/.signature" + +The content-length is recomputed. + +=cut + +sub sign { + my $self = shift; + my %params = @_; + + ### Default: + @_ or $params{Path} = "$ENV{HOME}/.signature"; + + ### Force message in-core: + defined($self->{Data}) or $self->read_now; + + ### Load signature: + my $sig; + if (!defined($sig = $params{Data})) { ### not given explicitly: + local $/ = undef; + open SIG, $params{Path} or Carp::croak "open sig $params{Path}: $!\n"; + $sig = <SIG>; ### sssssssssssssslurp... + close SIG; ### ...aaaaaaaaahhh! + } + $sig = join('',@$sig) if (ref($sig) and (ref($sig) eq 'ARRAY')); + + ### Append, following Internet conventions: + $self->{Data} .= "\n-- \n$sig"; + + ### Re-compute length: + $self->get_length; + 1; +} + +#------------------------------ +# +# =item suggest_encoding CONTENTTYPE +# +# I<Class/instance method.> +# Based on the CONTENTTYPE, return a good suggested encoding. +# C<text> and C<message> types have their bodies scanned line-by-line +# for 8-bit characters and long lines; lack of either means that the +# message is 7bit-ok. Other types are chosen independent of their body: +# +# Major type: 7bit ok? Suggested encoding: +# ------------------------------------------------------------ +# text yes 7bit +# no quoted-printable +# unknown binary +# +# message yes 7bit +# no binary +# unknown binary +# +# multipart n/a binary (in case some parts are not ok) +# +# (other) n/a base64 +# +#=cut + +sub suggest_encoding { + my ($self, $ctype) = @_; + $ctype = lc($ctype); + + ### Consult MIME::Types, maybe: + if ($HaveMimeTypes) { + + ### Mappings contain [suffix,mimetype,encoding] + my @mappings = MIME::Types::by_mediatype($ctype); + if (scalar(@mappings)) { + ### Just pick the first one: + my ($suffix, $mimetype, $encoding) = @{$mappings[0]}; + if ($encoding && + $encoding =~/^(base64|binary|[78]bit|quoted-printable)$/i) { + return lc($encoding); ### sanity check + } + } + } + + ### If we got here, then MIME::Types was no help. + ### Extract major type: + my ($type) = split '/', $ctype; + if (($type eq 'text') || ($type eq 'message')) { ### scan message body? + return 'binary'; + } + else { + return ($type eq 'multipart') ? 'binary' : 'base64'; + } +} + +#------------------------------ +# +# =item suggest_type PATH +# +# I<Class/instance method.> +# Suggest the content-type for this attached path. +# We always fall back to "application/octet-stream" if no good guess +# can be made, so don't use this if you don't mean it! +# +sub suggest_type { + my ($self, $path) = @_; + + ### If there's no path, bail: + $path or return 'application/octet-stream'; + + ### Consult MIME::Types, maybe: + if ($HaveMimeTypes) { + # Mappings contain [mimetype,encoding]: + my @mappings = MIME::Types::by_suffix($path); + if (scalar(@mappings)) { + ### Just pick the first one: + my ($mimetype, $encoding) = @{$mappings[0]}; + if ($mimetype && $mimetype =~ /^\S+\/\S+$/) { + return $mimetype; ### sanity check + } + } + } + + ### If we got here, then MIME::Types was no help. + ### The correct thing to fall back to is the most-generic content type: + return 'application/octet-stream'; +} + +#------------------------------ + +=item verify_data + +I<Instance method.> +Verify that all "paths" to attached data exist, recursively. +It might be a good idea for you to do this before a print(), to +prevent accidental partial output if a file might be missing. +Raises exception if any path is not readable. + +=cut + +sub verify_data { + my $self = shift; + + ### Verify self: + my $path = $self->{Path}; + if ($path and ($path !~ /\|$/)) { ### non-shell path: + $path =~ s/^<//; + (-r $path) or die "$path: not readable\n"; + } + + ### Verify parts: + foreach my $part (@{$self->{Parts}}) { $part->verify_data } + 1; +} + +=back + +=cut + + +#============================== +#============================== + +=head2 Output + +=over 4 + +=cut + +#------------------------------ + +=item print [OUTHANDLE] + +I<Instance method.> +Print the message to the given output handle, or to the currently-selected +filehandle if none was given. + +All OUTHANDLE has to be is a filehandle (possibly a glob ref), or +any object that responds to a print() message. + +=cut + +sub print { + my ($self, $out) = @_; + + ### Coerce into a printable output handle: + $out = wrap MIME::Lite::IO_Handle $out; + + ### Output head, separator, and body: + $self->verify_data if $AUTO_VERIFY; ### prevents missing parts! + $out->print($self->header_as_string, "\n"); + $self->print_body($out); +} + +#------------------------------ +# +# print_for_smtp +# +# Instance method, private. +# Print, but filter out the topmost "Bcc" field. +# This is because qmail apparently doesn't do this for us! +# +sub print_for_smtp { + my ($self, $out) = @_; + + ### Coerce into a printable output handle: + $out = wrap MIME::Lite::IO_Handle $out; + + ### Create a safe head: + my @fields = grep { $_->[0] ne 'bcc' } @{$self->fields}; + my $header = $self->fields_as_string(\@fields); + + ### Output head, separator, and body: + $out->print($header, "\n"); + $self->print_body($out); +} + +#------------------------------ + +=item print_body [OUTHANDLE] + +I<Instance method.> +Print the body of a message to the given output handle, or to +the currently-selected filehandle if none was given. + +All OUTHANDLE has to be is a filehandle (possibly a glob ref), or +any object that responds to a print() message. + +B<Fatal exception> raised if unable to open any of the input files, +or if a part contains no data, or if an unsupported encoding is +encountered. + +=cut + +sub print_body { + my ($self, $out) = @_; + + ### Coerce into a printable output handle: + $out = wrap MIME::Lite::IO_Handle $out; + + ### Output either the body or the parts. + ### Notice that we key off of the content-type! We expect fewer + ### accidents that way, since the syntax will always match the MIME type. + my $type = $self->attr('content-type'); + if ($type =~ m{^multipart/}i) { + my $boundary = $self->attr('content-type.boundary'); + + ### Preamble: + $out->print(defined($self->{Preamble}) + ? $self->{Preamble} + : "This is a multi-part message in MIME format.\n"); + + ### Parts: + my $part; + foreach $part (@{$self->{Parts}}) { + $out->print("\n--$boundary\n"); + $part->print($out); + } + + ### Epilogue: + $out->print("\n--$boundary--\n\n"); + } + elsif ($type =~ m{^message/}) { + my @parts = @{$self->{Parts}}; + + ### It's a toss-up; try both data and parts: + if (@parts == 0) { $self->print_simple_body($out) } + elsif (@parts == 1) { $parts[0]->print($out) } + else { Carp::croak "can't handle message with >1 part\n"; } + } + else { + $self->print_simple_body($out); + } + 1; +} + +#------------------------------ +# +# print_simple_body [OUTHANDLE] +# +# I<Instance method, private.> +# Print the body of a simple singlepart message to the given +# output handle, or to the currently-selected filehandle if none +# was given. +# +# Note that if you want to print "the portion after +# the header", you don't want this method: you want +# L<print_body()|/print_body>. +# +# All OUTHANDLE has to be is a filehandle (possibly a glob ref), or +# any object that responds to a print() message. +# +# B<Fatal exception> raised if unable to open any of the input files, +# or if a part contains no data, or if an unsupported encoding is +# encountered. +# +sub print_simple_body { + my ($self, $out) = @_; + + ### Coerce into a printable output handle: + $out = wrap MIME::Lite::IO_Handle $out; + + ### Get content-transfer-encoding: + my $encoding = uc($self->attr('content-transfer-encoding')); + + ### Notice that we don't just attempt to slurp the data in from a file: + ### by processing files piecemeal, we still enable ourselves to prepare + ### very large MIME messages... + + ### Is the data in-core? If so, blit it out... + if (defined($self->{Data})) { + DATA: + { local $_ = $encoding; + + /^BINARY$/ and do { + $out->print($self->{Data}); + last DATA; + }; + /^8BIT$/ and do { + $out->print(encode_8bit($self->{Data})); + last DATA; + }; + /^7BIT$/ and do { + $out->print(encode_7bit($self->{Data})); + last DATA; + }; + /^QUOTED-PRINTABLE$/ and do { + ### UNTAINT since m//mg on tainted data loops forever: + my ($untainted) = ($self->{Data} =~ m/\A(.*)\Z/s); + + ### Encode it line by line: + while ($untainted =~ m{^(.*[\r\n]*)}mg) { + $out->print(encode_qp($1)); ### have to do it line by line... + } + last DATA; + }; + /^BASE64/ and do { + $out->print(encode_base64($self->{Data})); + last DATA; + }; + Carp::croak "unsupported encoding: `$_'\n"; + } + } + + ### Else, is the data in a file? If so, output piecemeal... + ### Miko's note: this routine pretty much works the same with a path + ### or a filehandle. the only difference in behaviour is that it does + ### not attempt to open anything if it already has a filehandle + elsif (defined($self->{Path}) || defined($self->{FH})) { + no strict 'refs'; ### in case FH is not an object + my $DATA; + + ### Open file if necessary: + if (defined($self->{Path})) { + $DATA = new FileHandle || Carp::croak "can't get new filehandle\n"; + $DATA->open("$self->{Path}") or + Carp::croak "open $self->{Path}: $!\n"; + } + else { + $DATA=$self->{FH}; + } + CORE::binmode($DATA) if $self->binmode; + + ### Encode piece by piece: + PATH: + { local $_ = $encoding; + + /^BINARY$/ and do { + $out->print($_) while read($DATA, $_, 2048); + last PATH; + }; + /^8BIT$/ and do { + $out->print(encode_8bit($_)) while (<$DATA>); + last PATH; + }; + /^7BIT$/ and do { + $out->print(encode_7bit($_)) while (<$DATA>); + last PATH; + }; + /^QUOTED-PRINTABLE$/ and do { + $out->print(encode_qp($_)) while (<$DATA>); + last PATH; + }; + /^BASE64$/ and do { + $out->print(encode_base64($_)) while (read($DATA, $_, 45)); + last PATH; + }; + Carp::croak "unsupported encoding: `$_'\n"; + } + + ### Close file: + close $DATA if defined($self->{Path}); + } + + else { + Carp::croak "no data in this part\n"; + } + 1; +} + +#------------------------------ + +=item print_header [OUTHANDLE] + +I<Instance method.> +Print the header of the message to the given output handle, +or to the currently-selected filehandle if none was given. + +All OUTHANDLE has to be is a filehandle (possibly a glob ref), or +any object that responds to a print() message. + +=cut + +sub print_header { + my ($self, $out) = @_; + + ### Coerce into a printable output handle: + $out = wrap MIME::Lite::IO_Handle $out; + + ### Output the header: + $out->print($self->header_as_string); + 1; +} + +#------------------------------ + +=item as_string + +I<Instance method.> +Return the entire message as a string, with a header and an encoded body. + +=cut + +sub as_string { + my $self = shift; + my $buf = []; + my $io = (wrap MIME::Lite::IO_ScalarArray $buf); + $self->print($io); + join '', @$buf; +} +*stringify = \&as_string; ### backwards compatibility +*stringify = \&as_string; ### ...twice to avoid warnings :) + +#------------------------------ + +=item body_as_string + +I<Instance method.> +Return the encoded body as a string. +This is the portion after the header and the blank line. + +I<Note:> actually prepares the body by "printing" to a scalar. +Proof that you can hand the C<print*()> methods any blessed object +that responds to a C<print()> message. + +=cut + +sub body_as_string { + my $self = shift; + my $buf = []; + my $io = (wrap MIME::Lite::IO_ScalarArray $buf); + $self->print_body($io); + join '', @$buf; +} +*stringify_body = \&body_as_string; ### backwards compatibility +*stringify_body = \&body_as_string; ### ...twice to avoid warnings :) + +#------------------------------ +# +# fields_as_string FIELDS +# +# PRIVATE! Return a stringified version of the given header +# fields, where FIELDS is an arrayref like that returned by fields(). +# +sub fields_as_string { + my ($self, $fields) = @_; + my @lines; + foreach (@$fields) { + my ($tag, $value) = @$_; + next if ($value eq ''); ### skip empties + $tag =~ s/\b([a-z])/uc($1)/ge; ### make pretty + $tag =~ s/^mime-/MIME-/ig; ### even prettier + push @lines, "$tag: $value\n"; + } + join '', @lines; +} + +#------------------------------ + +=item header_as_string + +I<Instance method.> +Return the header as a string. + +=cut + +sub header_as_string { + my $self = shift; + $self->fields_as_string($self->fields); +} +*stringify_header = \&header_as_string; ### backwards compatibility +*stringify_header = \&header_as_string; ### ...twice to avoid warnings :) + +=back + +=cut + + + +#============================== +#============================== + +=head2 Sending + +=over 4 + +=cut + +#------------------------------ + +=item send + +=item send HOW, HOWARGS... + +I<Class/instance method.> +This is the principal method for sending mail, and for configuring +how mail will be sent. + +I<As a class method> with a HOW argument and optional HOWARGS, it sets +the default sending mechanism that the no-argument instance method +will use. The HOW is a facility name (B<see below>), +and the HOWARGS is interpreted by the facilty. +The class method returns the previous HOW and HOWARGS as an array. + + MIME::Lite->send('sendmail', "d:\\programs\\sendmail.exe"); + ... + $msg = MIME::Lite->new(...); + $msg->send; + +I<As an instance method with arguments> +(a HOW argument and optional HOWARGS), sends the message in the +requested manner; e.g.: + + $msg->send('sendmail', "d:\\programs\\sendmail.exe"); + +I<As an instance method with no arguments,> sends the message by +the default mechanism set up by the class method. +Returns whatever the mail-handling routine returns: this should be true +on success, false/exception on error: + + $msg = MIME::Lite->new(From=>...); + $msg->send || die "you DON'T have mail!"; + +On Unix systems (at least), the default setting is equivalent to: + + MIME::Lite->send("sendmail", "/usr/lib/sendmail -t -oi -oem"); + +There are three facilities: + +=over 4 + +=item "sendmail", ARGS... + +Send a message by piping it into the "sendmail" command. +Uses the L<send_by_sendmail()|/send_by_sendmail> method, giving it the ARGS. +This usage implements (and deprecates) the C<sendmail()> method. + +=item "smtp", [HOSTNAME] + +Send a message by SMTP, using optional HOSTNAME as SMTP-sending host. +Uses the L<send_by_smtp()|/send_by_smtp> method. + +=item "sub", \&SUBREF, ARGS... + +Sends a message MSG by invoking the subroutine SUBREF of your choosing, +with MSG as the first argument, and ARGS following. + +=back + +I<For example:> let's say you're on an OS which lacks the usual Unix +"sendmail" facility, but you've installed something a lot like it, and +you need to configure your Perl script to use this "sendmail.exe" program. +Do this following in your script's setup: + + MIME::Lite->send('sendmail', "d:\\programs\\sendmail.exe"); + +Then, whenever you need to send a message $msg, just say: + + $msg->send; + +That's it. Now, if you ever move your script to a Unix box, all you +need to do is change that line in the setup and you're done. +All of your $msg-E<gt>send invocations will work as expected. + +=cut + +sub send { + my $self = shift; + + if (ref($self)) { ### instance method: + my ($method, @args); + if (@_) { ### args; use them just this once + $method = 'send_by_' . shift; + @args = @_; + } + else { ### no args; use defaults + $method = "send_by_$Sender"; + @args = @{$SenderArgs{$Sender} || []}; + } + $self->verify_data if $AUTO_VERIFY; ### prevents missing parts! + return $self->$method(@args); + } + else { ### class method: + if (@_) { + my @old = ($Sender, @{$SenderArgs{$Sender}}); + $Sender = shift; + $SenderArgs{$Sender} = [@_]; ### remaining args + return @old; + } + else { + Carp::croak "class method send must have HOW... arguments\n"; + } + } +} + +#------------------------------ + +=item send_by_sendmail SENDMAILCMD + +=item send_by_sendmail PARAM=>VALUE, ... + +I<Instance method.> +Send message via an external "sendmail" program +(this will probably only work out-of-the-box on Unix systems). + +Returns true on success, false or exception on error. + +You can specify the program and all its arguments by giving a single +string, SENDMAILCMD. Nothing fancy is done; the message is simply +piped in. + +However, if your needs are a little more advanced, you can specify +zero or more of the following PARAM/VALUE pairs; a Unix-style, +taint-safe "sendmail" command will be constructed for you: + +=over 4 + +=item Sendmail + +Full path to the program to use. +Default is "/usr/lib/sendmail". + +=item BaseArgs + +Ref to the basic array of arguments we start with. +Default is C<["-t", "-oi", "-oem"]>. + +=item SetSender + +Unless this is I<explicitly> given as false, we attempt to automatically +set the C<-f> argument to the first address that can be extracted from +the "From:" field of the message (if there is one). + +I<What is the -f, and why do we use it?> +Suppose we did I<not> use C<-f>, and you gave an explicit "From:" +field in your message: in this case, the sendmail "envelope" would +indicate the I<real> user your process was running under, as a way +of preventing mail forgery. Using the C<-f> switch causes the sender +to be set in the envelope as well. + +I<So when would I NOT want to use it?> +If sendmail doesn't regard you as a "trusted" user, it will permit +the C<-f> but also add an "X-Authentication-Warning" header to the message +to indicate a forged envelope. To avoid this, you can either +(1) have SetSender be false, or +(2) make yourself a trusted user by adding a C<T> configuration + command to your I<sendmail.cf> file + (e.g.: C<Teryq> if the script is running as user "eryq"). + +=item FromSender + +If defined, this is identical to setting SetSender to true, +except that instead of looking at the "From:" field we use +the address given by this option. +Thus: + + FromSender => 'me@myhost.com' + +=back + +=cut + +sub send_by_sendmail { + my $self = shift; + + if (@_ == 1) { ### Use the given command... + my $sendmailcmd = shift @_; + + ### Do it: + open SENDMAIL, "|$sendmailcmd" or Carp::croak "open |$sendmailcmd: $!\n"; + $self->print(\*SENDMAIL); + close SENDMAIL; + return (($? >> 8) ? undef : 1); + } + else { ### Build the command... + my %p = @_; + $p{Sendmail} ||= "/usr/lib/sendmail"; + + ### Start with the command and basic args: + my @cmd = ($p{Sendmail}, @{$p{BaseArgs} || ['-t', '-oi', '-oem']}); + + ### See if we are forcibly setting the sender: + $p{SetSender} = 1 if defined($p{FromSender}); + + ### Add the -f argument, unless we're explicitly told NOT to: + unless (exists($p{SetSender}) and !$p{SetSender}) { + my $from = $p{FromSender} || ($self->get('From'))[0]; + if ($from) { + my ($from_addr) = extract_addrs($from); + push @cmd, "-f$from_addr" if $from_addr; + } + } + + ### Open the command in a taint-safe fashion: + my $pid = open SENDMAIL, "|-"; + defined($pid) or die "open of pipe failed: $!\n"; + if (!$pid) { ### child + exec(@cmd) or die "can't exec $p{Sendmail}: $!\n"; + ### NOTREACHED + } + else { ### parent + $self->print(\*SENDMAIL); + close SENDMAIL || die "error closing $p{Sendmail}: $! (exit $?)\n"; + return 1; + } + } +} + +#------------------------------ + +=item send_by_smtp ARGS... + +I<Instance method.> +Send message via SMTP, using Net::SMTP. +The optional ARGS are sent into Net::SMTP::new(): usually, these are + + MAILHOST, OPTION=>VALUE, ... + +Note that the list of recipients is taken from the +"To", "Cc" and "Bcc" fields. + +Returns true on success, false or exception on error. + +=cut + +### Provided by Andrew McRae. Version 0.2 anm 09Sep97 +### Copyright 1997 Optimation New Zealand Ltd. +### May be modified/redistributed under the same terms as Perl. +# +sub send_by_smtp { + my ($self, @args) = @_; + + ### We need the "From:" and "To:" headers to pass to the SMTP mailer: + my $hdr = $self->fields(); + my $from = $self->get('From'); + my $to = $self->get('To'); + + ### Sanity check: + defined($to) or Carp::croak "send_by_smtp: missing 'To:' address\n"; + + ### Get the destinations as a simple array of addresses: + my @to_all = extract_addrs($to); + if ($AUTO_CC) { + foreach my $field (qw(Cc Bcc)) { + my $value = $self->get($field); + push @to_all, extract_addrs($value) if defined($value); + } + } + + ### Create SMTP client: + require Net::SMTP; + my $smtp = MIME::Lite::SMTP->new(@args) + or Carp::croak("Failed to connect to mail server: $!\n"); + $smtp->mail($from) + or Carp::croak("SMTP MAIL command failed: $!\n".$smtp->message."\n"); + $smtp->to(@to_all) + or Carp::croak("SMTP RCPT command failed: $!\n".$smtp->message."\n"); + $smtp->data() + or Carp::croak("SMTP DATA command failed: $!\n".$smtp->message."\n"); + + ### MIME::Lite can print() to anything with a print() method: + $self->print_for_smtp($smtp); + $smtp->dataend(); + $smtp->quit; + 1; +} + +#------------------------------ +# +# send_by_sub [\&SUBREF, [ARGS...]] +# +# I<Instance method, private.> +# Send the message via an anonymous subroutine. +# +sub send_by_sub { + my ($self, $subref, @args) = @_; + &$subref($self, @args); +} + +#------------------------------ + +=item sendmail COMMAND... + +I<Class method, DEPRECATED.> +Declare the sender to be "sendmail", and set up the "sendmail" command. +I<You should use send() instead.> + +=cut + +sub sendmail { + my $self = shift; + $self->send('sendmail', join(' ', @_)); +} + +=back + +=cut + + + +#============================== +#============================== + +=head2 Miscellaneous + +=over 4 + +=cut + +#------------------------------ + +=item quiet ONOFF + +I<Class method.> +Suppress/unsuppress all warnings coming from this module. + + MIME::Lite->quiet(1); ### I know what I'm doing + +I recommend that you include that comment as well. And while +you type it, say it out loud: if it doesn't feel right, then maybe +you should reconsider the whole line. C<;-)> + +=cut + +sub quiet { + my $class = shift; + $QUIET = shift if @_; + $QUIET; +} + +=back + +=cut + + + +#============================================================ + +package MIME::Lite::SMTP; + +#============================================================ +# This class just adds a print() method to Net::SMTP. +# Notice that we don't use/require it until it's needed! + +use strict; +use vars qw( @ISA ); +@ISA = qw(Net::SMTP); + +sub print { shift->datasend(@_) } + + + +#============================================================ + +package MIME::Lite::IO_Handle; + +#============================================================ + +### Wrap a non-object filehandle inside a blessed, printable interface: +### Does nothing if the given $fh is already a blessed object. +sub wrap { + my ($class, $fh) = @_; + no strict 'refs'; + + ### Get default, if necessary: + $fh or $fh = select; ### no filehandle means selected one + ref($fh) or $fh = \*$fh; ### scalar becomes a globref + + ### Stop right away if already a printable object: + return $fh if (ref($fh) and (ref($fh) ne 'GLOB')); + + ### Get and return a printable interface: + bless \$fh, $class; ### wrap it in a printable interface +} + +### Print: +sub print { + my $self = shift; + print {$$self} @_; +} + + +#============================================================ + +package MIME::Lite::IO_Scalar; + +#============================================================ + +### Wrap a scalar inside a blessed, printable interface: +sub wrap { + my ($class, $scalarref) = @_; + defined($scalarref) or $scalarref = \""; + bless $scalarref, $class; +} + +### Print: +sub print { + my $self = shift; + $$self .= join('', @_); + 1; +} + + +#============================================================ + +package MIME::Lite::IO_ScalarArray; + +#============================================================ + +### Wrap an array inside a blessed, printable interface: +sub wrap { + my ($class, $arrayref) = @_; + defined($arrayref) or $arrayref = []; + bless $arrayref, $class; +} + +### Print: +sub print { + my $self = shift; + push @$self, @_; + 1; +} + +1; +__END__ + + +#============================================================ + +=head1 NOTES + + +=head2 How do I prevent "Content" headers from showing up in my mail reader? + +Apparently, some people are using mail readers which display the MIME +headers like "Content-disposition", and they want MIME::Lite not +to generate them "because they look ugly". + +Sigh. + +Y'know, kids, those headers aren't just there for cosmetic purposes. +They help ensure that the message is I<understood> correctly by mail +readers. But okay, you asked for it, you got it... +here's how you can suppress the standard MIME headers. +Before you send the message, do this: + + $msg->scrub; + +You can scrub() any part of a multipart message independently; +just be aware that it works recursively. Before you scrub, +note the rules that I follow: + +=over 4 + +=item Content-type + +You can safely scrub the "content-type" attribute if, and only if, +the part is of type "text/plain" with charset "us-ascii". + +=item Content-transfer-encoding + +You can safely scrub the "content-transfer-encoding" attribute +if, and only if, the part uses "7bit", "8bit", or "binary" encoding. +You are far better off doing this if your lines are under 1000 +characters. Generally, that means you I<can> scrub it for plain +text, and you can I<not> scrub this for images, etc. + +=item Content-disposition + +You can safely scrub the "content-disposition" attribute +if you trust the mail reader to do the right thing when it decides +whether to show an attachment inline or as a link. Be aware +that scrubbing both the content-disposition and the content-type +means that there is no way to "recommend" a filename for the attachment! + +B<Note:> there are reports of brain-dead MUAs out there that +do the wrong thing if you I<provide> the content-disposition. +If your attachments keep showing up inline or vice-versa, +try scrubbing this attribute. + +=item Content-length + +You can always scrub "content-length" safely. + +=back + + +=head2 How do I give my attachment a [different] recommended filename? + +By using the Filename option (which is different from Path!): + + $msg->attach(Type => "image/gif", + Path => "/here/is/the/real/file.GIF", + Filename => "logo.gif"); + +You should I<not> put path information in the Filename. + + + + +=head2 Benign limitations + +This is "lite", after all... + +=over 4 + +=item * + +There's no parsing. Get MIME-tools if you need to parse MIME messages. + +=item * + +MIME::Lite messages are currently I<not> interchangeable with +either Mail::Internet or MIME::Entity objects. This is a completely +separate module. + +=item * + +A content-length field is only inserted if the encoding is binary, +the message is a singlepart, and all the document data is available +at C<build()> time by virtue of residing in a simple path, or in-core. +Since content-length is not a standard MIME field anyway (that's right, kids: +it's not in the MIME RFCs, it's an HTTP thing), this seems pretty fair. + +=item * + +MIME::Lite alone cannot help you lose weight. You must supplement +your use of MIME::Lite with a healthy diet and exercise. + +=back + + +=head2 Cheap and easy mailing + +I thought putting in a default "sendmail" invocation wasn't too bad an +idea, since a lot of Perlers are on UNIX systems. +The out-of-the-box configuration is: + + MIME::Lite->send('sendmail', "/usr/lib/sendmail -t -oi -oem"); + +By the way, these arguments to sendmail are: + + -t Scan message for To:, Cc:, Bcc:, etc. + + -oi Do NOT treat a single "." on a line as a message terminator. + As in, "-oi vey, it truncated my message... why?!" + + -oem On error, mail back the message (I assume to the + appropriate address, given in the header). + When mail returns, circle is complete. Jai Guru Deva -oem. + +Note that these are the same arguments you get if you configure to use +the smarter, taint-safe mailing: + + MIME::Lite->send('sendmail'); + +If you get "X-Authentication-Warning" headers from this, you can forgo +diddling with the envelope by instead specifying: + + MIME::Lite->send('sendmail', SetSender=>0); + +And, if you're not on a Unix system, or if you'd just rather send mail +some other way, there's always: + + MIME::Lite->send('smtp', "smtp.myisp.net"); + +Or you can set up your own subroutine to call. +In any case, check out the L<send()|/send> method. + + + +=head1 WARNINGS + +=head2 Good-vs-bad email addresses with send_by_smtp() + +If using L<send_by_smtp()|/send_by_smtp>, be aware that you are +forcing MIME::Lite to extract email addresses out of a possible list +provided in the C<To:>, C<Cc:>, and C<Bcc:> fields. This is tricky +stuff, and as such only the following sorts of addresses will work +reliably: + + username + full.name@some.host.com + "Name, Full" <full.name@some.host.com> + +This last form is discouraged because SMTP must be able to get +at the I<name> or I<name@domain> portion. + +B<Disclaimer:> +MIME::Lite was never intended to be a Mail User Agent, so please +don't expect a full implementation of RFC-822. Restrict yourself to +the common forms of Internet addresses described herein, and you should +be fine. If this is not feasible, then consider using MIME::Lite +to I<prepare> your message only, and using Net::SMTP explicitly to +I<send> your message. + + +=head2 Formatting of headers delayed until print() + +This class treats a MIME header in the most abstract sense, +as being a collection of high-level attributes. The actual +RFC-822-style header fields are not constructed until it's time +to actually print the darn thing. + + +=head2 Encoding of data delayed until print() + +When you specify message bodies +(in L<build()|/build> or L<attach()|/attach>) -- +whether by B<FH>, B<Data>, or B<Path> -- be warned that we don't +attempt to open files, read filehandles, or encode the data until +L<print()|/print> is invoked. + +In the past, this created some confusion for users of sendmail +who gave the wrong path to an attachment body, since enough of +the print() would succeed to get the initial part of the message out. +Nowadays, $AUTO_VERIFY is used to spot-check the Paths given before +the mail facility is employed. A whisker slower, but tons safer. + +Note that if you give a message body via FH, and try to print() +a message twice, the second print() will not do the right thing +unless you explicitly rewind the filehandle. + +You can get past these difficulties by using the B<ReadNow> option, +provided that you have enough memory to handle your messages. + + +=head2 MIME attributes are separate from header fields! + +B<Important:> the MIME attributes are stored and manipulated separately +from the message header fields; when it comes time to print the +header out, I<any explicitly-given header fields override the ones that +would be created from the MIME attributes.> That means that this: + + ### DANGER ### DANGER ### DANGER ### DANGER ### DANGER ### + $msg->add("Content-type", "text/html; charset=US-ASCII"); + +will set the exact C<"Content-type"> field in the header I write, +I<regardless of what the actual MIME attributes are.> + +I<This feature is for experienced users only,> as an escape hatch in case +the code that normally formats MIME header fields isn't doing what +you need. And, like any escape hatch, it's got an alarm on it: +MIME::Lite will warn you if you attempt to C<set()> or C<replace()> +any MIME header field. Use C<attr()> instead. + + +=head2 Beware of lines consisting of a single dot + +Julian Haight noted that MIME::Lite allows you to compose messages +with lines in the body consisting of a single ".". +This is true: it should be completely harmless so long as "sendmail" +is used with the -oi option (see L<"Cheap and easy mailing">). + +However, I don't know if using Net::SMTP to transfer such a message +is equally safe. Feedback is welcomed. + +My perspective: I don't want to magically diddle with a user's +message unless absolutely positively necessary. +Some users may want to send files with "." alone on a line; +my well-meaning tinkering could seriously harm them. + + +=head2 Infinite loops may mean tainted data! + +Stefan Sautter noticed a bug in 2.106 where a m//gc match was +failing due to tainted data, leading to an infinite loop inside +MIME::Lite. + +I am attempting to correct for this, but be advised that my fix will +silently untaint the data (given the context in which the problem +occurs, this should be benign: I've labelled the source code with +UNTAINT comments for the curious). + +So: don't depend on taint-checking to save you from outputting +tainted data in a message. + + +=head2 Don't tweak the global configuration + +Global configuration variables are bad, and should go away. +Until they do, please follow the hints with each setting +on how I<not> to change it. + + + + +=head1 A MIME PRIMER + +=head2 Content types + +The "Type" parameter of C<build()> is a I<content type>. +This is the actual type of data you are sending. +Generally this is a string of the form C<"majortype/minortype">. + +Here are the major MIME types. +A more-comprehensive listing may be found in RFC-2046. + +=over 4 + +=item application + +Data which does not fit in any of the other categories, particularly +data to be processed by some type of application program. +C<application/octet-stream>, C<application/gzip>, C<application/postscript>... + +=item audio + +Audio data. +C<audio/basic>... + +=item image + +Graphics data. +C<image/gif>, C<image/jpeg>... + +=item message + +A message, usually another mail or MIME message. +C<message/rfc822>... + +=item multipart + +A message containing other messages. +C<multipart/mixed>, C<multipart/alternative>... + +=item text + +Textual data, meant for humans to read. +C<text/plain>, C<text/html>... + +=item video + +Video or video+audio data. +C<video/mpeg>... + +=back + + +=head2 Content transfer encodings + +The "Encoding" parameter of C<build()>. +This is how the message body is packaged up for safe transit. + +Here are the 5 major MIME encodings. +A more-comprehensive listing may be found in RFC-2045. + +=over 4 + +=item 7bit + +Basically, no I<real> encoding is done. However, this label guarantees that no +8-bit characters are present, and that lines do not exceed 1000 characters +in length. + +=item 8bit + +Basically, no I<real> encoding is done. The message might contain 8-bit +characters, but this encoding guarantees that lines do not exceed 1000 +characters in length. + +=item binary + +No encoding is done at all. Message might contain 8-bit characters, +and lines might be longer than 1000 characters long. + +The most liberal, and the least likely to get through mail gateways. +Use sparingly, or (better yet) not at all. + +=item base64 + +Like "uuencode", but very well-defined. This is how you should send +essentially binary information (tar files, GIFs, JPEGs, etc.). + +=item quoted-printable + +Useful for encoding messages which are textual in nature, yet which contain +non-ASCII characters (e.g., Latin-1, Latin-2, or any other 8-bit alphabet). + +=back + + + +=head1 VERSION + +$Id$ + + +=head1 CHANGE LOG + +=over 4 + + +=item Version 2.117 (2001/08/20) + +The terms-of-use have been placed in the distribution file "COPYING". +Also, small documentation tweaks were made. + + +=item Version 2.116 (2001/08/17) + +Added long-overdue patch which makes the instance method form +of send() do the right thing when given HOW... arguments. +I<Thanks to Casey West for the patch.> + +=item Version 2.114 (2001/08/16) + +New special 'AUTO' content type in new()/build() tells MIME::Lite to +try and guess the type from file extension. To make use of +this, you'll want to install B<MIME::Types>. +The "AUTO" setting can be made the default default (instead of "TEXT") +if you set C<$AUTO_CONTENT_TYPE = 1, $PARANOID = 0>. +I<Thanks to> Ville SkyttE<#228> I<for these patches.> + +File::Basename is used if it is available. +I<Thanks to> Ville SkyttE<#228> I<for this patch.> + +SMTP failures (in send_by_smtp) now add the $smtp-E<gt>message to the +croak'ed exception, so if things go wrong, you get a better +idea of what and why. +I<Thanks to Thomas R. Wyant III for the patch.> + +Made a subtle change to C<as_string> which supposedly fixes a +failed MIME data.t test with Perl 5.004_04 on NT 4 sp6. +The problem might only exist in this old perl, but as the patch +author says, not everyone has climbed higher on the Perl ladder. +I<Thanks to John Gotts for the patch.> + +Added C<contrib> directory, with F<MailTool.pm>. +I<Thanks to Tom Wyant for this contribution.> + +Improved HTML documentation (notice the links to +the individual methods in the top menu). + +Corrected some mis-docs. + + +=item Version 2.111 (2001/04/03) + +Added long-overdue C<parts()> and C<parts_DFS()> methods. + + No instance method + For accessing the subparts? + That can't be right. D'OH! + +Added long-overdue auto-verify logic to C<print()> method. + +Added long-overdue C<preamble()> method for getting/setting +the preamble text. +I<Thanks to Jim Daigle for inspiring this.> + + +=item Version 2.108 (2001/03/30) + +New C<field_order()> allows you to set the header order, both on a +per-message basis, and package-wide. +I<Thanks to Thomas Stromberg for suggesting this.> + +Added code to try and divine "sendmail" path more intelligently. +I<Thanks to Slaven Rezic for the suggestion.> + + +=item Version 2.107 (2001/03/27) + +Fixed serious bug where tainted data with quoted-printable encoding +was causing infinite loops. The "fix" untaints the data in question, +which is not optimal, but it's probably benign in this case. +I<Thanks to Stefan Sautter for tracking this nasty little beast down.> +I<Thanks to Larry Geralds for a related patch.> + + "Doctor, O doctor: + it's painful when I do *this* --" + "Simple: don't *do* that." + +Fixed bugs where a non-local C<$_> was being modified... again! +Will I never learn? +I<Thanks to Maarten Koskamp for reporting this.> + + Dollar-underscore + can poison distant waters; + 'local' must it be. + +Fixed buglet in C<add()> where all value references were being treated +as arrayrefs, instead of as possibly-self-stringifying object refs. +Now you can send in an object ref as the 2nd argument. +I<Thanks to dLux for the bug report.> + + That ref is a string? + Operator overload + has ruined my day. + +Added "Approved" as an acceptable header field for C<new()>, as per RFC1036. +I<Thanks to Thomax for the suggestion regarding MIME-tools.> + +Small improvements to docs to make different uses of attach() +and various arguments clearer. +I<Thanks to Sven Rassman and Roland Walter for the suggestions.> + + +=item Version 2.106 (2000/11/21) + +Added Alpha version of scrub() to make it easy for people to suppress +the printing of unwanted MIME attributes (like Content-length). +I<Thanks to the many people who asked for this.> + +Headers with empty-strings for their values are no longer +printed. This seems sensible, and helps us implement scrub(). + + +=item Version 2.105 (2000/10/14) + +The regression-test failure was identified, and it was my fault. +Apparently some of the \-quoting in my "autoloaded" code was +making Perl 5.6 unhappy. For this nesting-related idiocy, +a nesting kaiku. +I<Thanks to Scott Schwartz for identifying the problem.> + + In a pattern, my + backslash-s dwells peacefully, + unambiguous -- + + but I embed it + in a double-quoted string + doubling the backslash -- + + interpolating + that same double-quoted string + in other patterns -- + + and, worlds within worlds, + I single-quote the function + to autoload it -- + + changing the meaning + of the backslash and the 's'; + and Five-Point-Six growls. + + +=item Version 2.104 (2000/09/28) + +Now attempts to load and use Mail::Address for parsing email +addresses I<before> falling back to our own method. +I<Thanks to numerous people for suggesting this.> + + Parsing addresses + is too damn hard. One last hope: + Let Graham Barr do it! + +For the curious, the version of Mail::Address appears +as the "A" number in the X-Mailer: + + X-Mailer: MIME::Lite 2.104 (A1.15; B2.09; Q2.03) + +Added B<FromSender> option to send_by_sendmail(). +I<Thanks to Bill Moseley for suggesting this feature.> + + +=item Version 2.101 (2000/06/06) + +Major revision to print_body() and body_as_string() so that +"body" really means "the part after the header", which is what most +people would want in this context. This is B<not> how it was used +1.x, where "body" only meant "the body of a simple singlepart". +Hopefully, this change will solve many problems and create very few ones. + +Added support for attaching a part to a "message/rfc822", treating +the "message" type as a multipart-like container. + +Now takes care not to include "Bcc:" in header when using send_by_smtp, +as a safety precaution against qmail's behavior. +I<Thanks to Tatsuhiko Miyagawa for identifying this problem.> + +Improved efficiency of many stringifying operations by using +string-arrays which are joined, instead of doing multiple appends +to a scalar. + +Cleaned up the "examples" directory. + + +=item Version 1.147 (2000/06/02) + +Fixed buglet where lack of Cc:/Bcc: was causing extract_addrs +to emit "undefined variable" warnings. Also, lack of a "To:" field +now causes a croak. +I<Thanks to David Mitchell for the bug report and suggested patch.> + + +=item Version 1.146 (2000/05/18) + +Fixed bug in parsing of addresses; please read the WARNINGS section +which describes recommended address formats for "To:", "Cc:", etc. +Also added automatic inclusion of a UT "Date:" at top level unless +explicitly told not to. +I<Thanks to Andy Jacobs for the bug report and the suggestion.> + +=item Version 1.145 (2000/05/06) + +Fixed bug in encode_7bit(): a lingering C</e> modifier was removed. +I<Thanks to Michael A. Chase for the patch.> + + +=item Version 1.142 (2000/05/02) + +Added new, taint-safe invocation of "sendmail", one which also +sets up the C<-f> option. Unfortunately, I couldn't make this automatic: +the change could have broken a lot of code out there which used +send_by_sendmail() with unusual "sendmail" variants. +So you'll have to configure "send" to use the new mechanism: + + MIME::Lite->send('sendmail'); ### no args! + +I<Thanks to Jeremy Howard for suggesting these features.> + + +=item Version 1.140 (2000/04/27) + +Fixed bug in support for "To", "Cc", and "Bcc" in send_by_smtp(): +multiple (comma-separated) addresses should now work fine. +We try real hard to extract addresses from the flat text strings. +I<Thanks to John Mason for motivating this change.> + +Added automatic verification that attached data files exist, +done immediately before the "send" action is invoked. +To turn this off, set $MIME::Lite::AUTO_VERIFY to false. + +=item Version 1.137 (2000/03/22) + +Added support for "Cc" and "Bcc" in send_by_smtp(). +To turn this off, set $MIME::Lite::AUTO_CC to false. +I<Thanks to Lucas Maneos for the patch, and tons of others for +the suggestion.> + +Chooses a better default content-transfer-encoding if the content-type +is "image/*", "audio/*", etc. +To turn this off, set $MIME::Lite::AUTO_ENCODE to false. +I<Thanks to many folks for the suggestion.> + +Fixed bug in QP-encoding where a non-local C<$_> was being modified. +I<Thanks to Jochen Stenzel for finding this very obscure bug!> + +Removed references to C<$`>, C<$'>, and C<$&> (bad variables +which slow things down). + +Added an example of how to send HTML files with enclosed in-line +images, per popular demand. + + +=item Version 1.133 (1999/04/17) + +Fixed bug in "Data" handling: arrayrefs were not being handled +properly. + + +=item Version 1.130 (1998/12/14) + +Added much larger and more-flexible send() facility. +I<Thanks to Andrew McRae (and Optimation New Zealand Ltd) +for the Net::SMTP interface. Additional thanks to the many folks +who requested this feature.> + +Added get() method for extracting basic attributes. + +New... "t" tests! + + +=item Version 1.124 (1998/11/13) + +Folded in filehandle (FH) support in build/attach. +I<Thanks to Miko O'Sullivan for the code.> + + +=item Version 1.122 (1998/01/19) + +MIME::Base64 and MIME::QuotedPrint are used if available. + +The 7bit encoding no longer does "escapes"; it merely strips 8-bit characters. + + +=item Version 1.121 (1997/04/08) + +Filename attribute is now no longer ignored by build(). +I<Thanks to Ian Smith for finding and patching this bug.> + + +=item Version 1.120 (1997/03/29) + +Efficiency hack to speed up MIME::Lite::IO_Scalar. +I<Thanks to David Aspinwall for the patch.> + + +=item Version 1.116 (1997/03/19) + +Small bug in our private copy of encode_base64() was patched. +I<Thanks to Andreas Koenig for pointing this out.> + +New, prettier way of specifying mail message headers in C<build()>. + +New quiet method to turn off warnings. + +Changed "stringify" methods to more-standard "as_string" methods. + + +=item Version 1.112 (1997/03/06) + +Added C<read_now()>, and C<binmode()> method for our non-Unix-using brethren: +file data is now read using binmode() if appropriate. +I<Thanks to Xiangzhou Wang for pointing out this bug.> + + +=item Version 1.110 (1997/03/06) + +Fixed bug in opening the data filehandle. + + +=item Version 1.102 (1997/03/01) + +Initial release. + + +=item Version 1.101 (1997/03/01) + +Baseline code. +Originally created: 11 December 1996. Ho ho ho. + +=back + + +=head1 TERMS AND CONDITIONS + +Copyright (c) 1997 by Eryq. +Copyright (c) 1998 by ZeeGee Software Inc. +All rights reserved. This program is free software; you can redistribute +it and/or modify it under the same terms as Perl itself. + +This software comes with B<NO WARRANTY> of any kind. +See the COPYING file in the distribution for details. + + +=head1 NUTRITIONAL INFORMATION + +For some reason, the US FDA says that this is now required by law +on any products that bear the name "Lite"... + + MIME::Lite | + ------------------------------------------------------------ + Serving size: | 1 module + Servings per container: | 1 + Calories: | 0 + Fat: | 0g + Saturated Fat: | 0g + +Warning: for consumption by hardware only! May produce +indigestion in humans if taken internally. + + +=head1 AUTHOR + +Eryq (F<eryq@zeegee.com>). +President, ZeeGee Software Inc. (F<http://www.zeegee.com>). + +Go to F<http://www.zeegee.com> for the latest downloads +and on-line documentation for this module. Enjoy. + +=cut + |