home *** CD-ROM | disk | FTP | other *** search
- package MIME::Entity;
-
-
- =head1 NAME
-
- MIME::Entity - class for parsed-and-decoded MIME message
-
-
- =head1 SYNOPSIS
-
- Before reading further, you should see L<MIME::Tools> to make sure that
- you understand where this module fits into the grand scheme of things.
- Go on, do it now. I'll wait.
-
- Ready? Ok...
-
- ### Create an entity:
- $top = MIME::Entity->build(From => 'me@myhost.com',
- To => 'you@yourhost.com',
- Subject => "Hello, nurse!",
- Data => \@my_message);
-
- ### Attach stuff to it:
- $top->attach(Path => $gif_path,
- Type => "image/gif",
- Encoding => "base64");
-
- ### Sign it:
- $top->sign;
-
- ### Output it:
- $top->print(\*STDOUT);
-
-
- =head1 DESCRIPTION
-
- A subclass of B<Mail::Internet>.
-
- This package provides a class for representing MIME message entities,
- as specified in RFC 1521, I<Multipurpose Internet Mail Extensions>.
-
-
- =head1 EXAMPLES
-
- =head2 Construction examples
-
- Create a document for an ordinary 7-bit ASCII text file (lots of
- stuff is defaulted for us):
-
- $ent = MIME::Entity->build(Path=>"english-msg.txt");
-
- Create a document for a text file with 8-bit (Latin-1) characters:
-
- $ent = MIME::Entity->build(Path =>"french-msg.txt",
- Encoding =>"quoted-printable",
- From =>'jean.luc@inria.fr',
- Subject =>"C'est bon!");
-
- Create a document for a GIF file (the description is completely optional;
- note that we have to specify content-type and encoding since they're
- not the default values):
-
- $ent = MIME::Entity->build(Description => "A pretty picture",
- Path => "./docs/mime-sm.gif",
- Type => "image/gif",
- Encoding => "base64");
-
- Create a document that you already have the text for, using "Data":
-
- $ent = MIME::Entity->build(Type => "text/plain",
- Encoding => "quoted-printable",
- Data => ["First line.\n",
- "Second line.\n",
- "Last line.\n"]);
-
- Create a multipart message, with the entire structure given
- explicitly:
-
- ### Create the top-level, and set up the mail headers:
- $top = MIME::Entity->build(Type => "multipart/mixed",
- From => 'me@myhost.com',
- To => 'you@yourhost.com',
- Subject => "Hello, nurse!");
-
- ### Attachment #1: a simple text document:
- $top->attach(Path=>"./testin/short.txt");
-
- ### Attachment #2: a GIF file:
- $top->attach(Path => "./docs/mime-sm.gif",
- Type => "image/gif",
- Encoding => "base64");
-
- ### Attachment #3: text we'll create with text we have on-hand:
- $top->attach(Data => $contents);
-
- Suppose you don't know ahead of time that you'll have attachments?
- No problem: you can "attach" to singleparts as well:
-
- $top = MIME::Entity->build(From => 'me@myhost.com',
- To => 'you@yourhost.com',
- Subject => "Hello, nurse!",
- Data => \@my_message);
- if ($GIF_path) {
- $top->attach(Path => $GIF_path,
- Type => 'image/gif');
- }
-
- Copy an entity (headers, parts... everything but external body data):
-
- my $deepcopy = $top->dup;
-
-
-
- =head2 Access examples
-
- ### Get the head, a MIME::Head:
- $head = $ent->head;
-
- ### Get the body, as a MIME::Body;
- $bodyh = $ent->bodyhandle;
-
- ### Get the intended MIME type (as declared in the header):
- $type = $ent->mime_type;
-
- ### Get the effective MIME type (in case decoding failed):
- $eff_type = $ent->effective_type;
-
- ### Get preamble, parts, and epilogue:
- $preamble = $ent->preamble; ### ref to array of lines
- $num_parts = $ent->parts;
- $first_part = $ent->parts(0); ### an entity
- $epilogue = $ent->epilogue; ### ref to array of lines
-
-
- =head2 Manipulation examples
-
- Muck about with the body data:
-
- ### Read the (unencoded) body data:
- if ($io = $ent->open("r")) {
- while (defined($_ = $io->getline)) { print $_ }
- $io->close;
- }
-
- ### Write the (unencoded) body data:
- if ($io = $ent->open("w")) {
- foreach (@lines) { $io->print($_) }
- $io->close;
- }
-
- ### Delete the files for any external (on-disk) data:
- $ent->purge;
-
- Muck about with the signature:
-
- ### Sign it (automatically removes any existing signature):
- $top->sign(File=>"$ENV{HOME}/.signature");
-
- ### Remove any signature within 15 lines of the end:
- $top->remove_sig(15);
-
- Muck about with the headers:
-
- ### Compute content-lengths for singleparts based on bodies:
- ### (Do this right before you print!)
- $entity->sync_headers(Length=>'COMPUTE');
-
- Muck about with the structure:
-
- ### If a 0- or 1-part multipart, collapse to a singlepart:
- $top->make_singlepart;
-
- ### If a singlepart, inflate to a multipart with 1 part:
- $top->make_multipart;
-
- Delete parts:
-
- ### Delete some parts of a multipart message:
- my @keep = grep { keep_part($_) } $msg->parts;
- $msg->parts(\@keep);
-
-
- =head2 Output examples
-
- Print to filehandles:
-
- ### Print the entire message:
- $top->print(\*STDOUT);
-
- ### Print just the header:
- $top->print_header(\*STDOUT);
-
- ### Print just the (encoded) body... includes parts as well!
- $top->print_body(\*STDOUT);
-
- Stringify... note that C<stringify_xx> can also be written C<xx_as_string>;
- the methods are synonymous, and neither form will be deprecated:
-
- ### Stringify the entire message:
- print $top->stringify; ### or $top->as_string
-
- ### Stringify just the header:
- print $top->stringify_header; ### or $top->header_as_string
-
- ### Stringify just the (encoded) body... includes parts as well!
- print $top->stringify_body; ### or $top->body_as_string
-
- Debug:
-
- ### Output debugging info:
- $entity->dump_skeleton(\*STDERR);
-
-
-
- =head1 PUBLIC INTERFACE
-
- =cut
-
- #------------------------------
-
- ### Pragmas:
- use vars qw(@ISA $VERSION);
- use strict;
-
- ### System modules:
- use FileHandle;
- use Carp;
-
- ### Other modules:
- use Mail::Internet 1.28 ();
- use Mail::Field 1.05 ();
-
- ### Kit modules:
- use MIME::Tools qw(:config :msgs :utils);
- use MIME::Head;
- use MIME::Body;
- use MIME::Decoder;
- use IO::Scalar;
- use IO::Wrap;
- use IO::Lines;
-
- @ISA = qw(Mail::Internet);
-
-
- #------------------------------
- #
- # Globals...
- #
- #------------------------------
-
- ### The package version, both in 1.23 style *and* usable by MakeMaker:
- $VERSION = substr q$Revision: 5.404 $, 10;
-
- ### Boundary counter:
- my $BCount = 0;
-
- ### Standard "Content-" MIME fields, for scrub():
- my $StandardFields = 'Description|Disposition|Id|Type|Transfer-Encoding';
-
- ### 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
- );
-
- ### Fallback preamble and epilogue:
- my $DefPreamble = [ "This is a multi-part message in MIME format...\n" ];
- my $DefEpilogue = [ ];
-
-
- #==============================
- #
- # Utilities, private
- #
-
- #------------------------------
- #
- # 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)-.});
- }
-
- #------------------------------
- #
- # make_boundary
- #
- # Return a unique boundary string.
- # This is used both internally and by MIME::ParserBase, but it is NOT in
- # the public interface! Do not use it!
- #
- # We generate one containing a "=_", as RFC1521 suggests.
- #
- sub make_boundary {
- return "----------=_".scalar(time)."-$$-".$BCount++;
- }
-
-
-
-
-
-
- #==============================
-
- =head2 Construction
-
- =over 4
-
- =cut
-
-
- #------------------------------
-
- =item new [SOURCE]
-
- I<Class method.>
- Create a new, empty MIME entity.
- Basically, this uses the Mail::Internet constructor...
-
- If SOURCE is an ARRAYREF, it is assumed to be an array of lines
- that will be used to create both the header and an in-core body.
-
- Else, if SOURCE is defined, it is assumed to be a filehandle
- from which the header and in-core body is to be read.
-
- B<Note:> in either case, the body will not be I<parsed:> merely read!
-
- =cut
-
- sub new {
- my $class = shift;
- my $self = $class->Mail::Internet::new(@_); ### inherited
- $self->{ME_Parts} = []; ### no parts extracted
- $self;
- }
-
-
- ###------------------------------
-
- =item add_part ENTITY, [OFFSET]
-
- I<Instance method.>
- Assuming we are a multipart message, add a body part (a MIME::Entity)
- to the array of body parts. Returns the part that was just added.
-
- If OFFSET is positive, the new part is added at that offset from the
- beginning of the array of parts. If it is negative, it counts from
- the end of the array. (An INDEX of -1 will place the new part at the
- very end of the array, -2 will place it as the penultimate item in the
- array, etc.) If OFFSET is not given, the new part is added to the end
- of the array.
- I<Thanks to Jason L Tibbitts III for providing support for OFFSET.>
-
- B<Warning:> in general, you only want to attach parts to entities
- with a content-type of C<multipart/*>).
-
- =cut
-
- sub add_part {
- my ($self, $part, $index) = @_;
- defined($index) or $index = -1;
-
- ### Make $index count from the end if negative:
- $index = $#{$self->{ME_Parts}} + 2 + $index if ($index < 0);
- splice(@{$self->{ME_Parts}}, $index, 0, $part);
- $part;
- }
-
- #------------------------------
-
- =item attach PARAMHASH
-
- I<Instance method.>
- The real quick-and-easy way to create multipart messages.
- The PARAMHASH is used to C<build> a new entity; this method is
- basically equivalent to:
-
- $entity->add_part(ref($entity)->build(PARAMHASH, Top=>0));
-
- B<Note:> normally, you attach to multipart entities; however, if you
- attach something to a singlepart (like attaching a GIF to a text
- message), the singlepart will be coerced into a multipart automatically.
-
- =cut
-
- sub attach {
- my $self = shift;
- $self->make_multipart;
- $self->add_part(ref($self)->build(@_, Top=>0));
- }
-
- #------------------------------
-
- =item build PARAMHASH
-
- I<Class/instance method.>
- A quick-and-easy catch-all way to create an entity. Use it like this
- to build a "normal" single-part entity:
-
- $ent = MIME::Entity->build(Type => "image/gif",
- Encoding => "base64",
- Path => "/path/to/xyz12345.gif",
- Filename => "saveme.gif",
- Disposition => "attachment");
-
- And like this to build a "multipart" entity:
-
- $ent = MIME::Entity->build(Type => "multipart/mixed",
- Boundary => "---1234567");
-
- A minimal MIME header will be created. If you want to add or modify
- any header fields afterwards, you can of course do so via the underlying
- head object... but hey, there's now a prettier syntax!
-
- $ent = MIME::Entity->build(Type =>"multipart/mixed",
- From => $myaddr,
- Subject => "Hi!",
- 'X-Certified' => ['SINED',
- 'SEELED',
- 'DELIVERED']);
-
- Normally, an C<X-Mailer> header field is output which contains this
- toolkit's name and version (plus this module's RCS version).
- This will allow any bad MIME we generate to be traced back to us.
- You can of course overwrite that header with your own:
-
- $ent = MIME::Entity->build(Type => "multipart/mixed",
- 'X-Mailer' => "myprog 1.1");
-
- Or remove it entirely:
-
- $ent = MIME::Entity->build(Type => "multipart/mixed",
- 'X-Mailer' => undef);
-
- OK, enough hype. The parameters are:
-
- =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):
-
- Bcc Encrypted Received Sender
- Cc From References Subject
- Comments Keywords Reply-To To
- Content-* Message-ID Resent-* X-*
- Date MIME-Version Return-Path
- 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 Boundary
-
- I<Multipart entities only. Optional.>
- The boundary string. As per RFC-1521, it must consist only
- of the characters C<[0-9a-zA-Z'()+_,-./:=?]> and space (you'll be
- warned, and your boundary will be ignored, if this is not the case).
- If you omit this, a random string will be chosen... which is probably
- safer.
-
- =item Charset
-
- I<Optional.>
- The character set.
-
- =item Data
-
- I<Single-part entities only. Optional.>
- An alternative to Path (q.v.): the actual data, either as a scalar
- or an array reference (whose elements are joined together to make
- the actual scalar). The body is opened on the data using
- MIME::Body::InCore.
-
- =item Description
-
- I<Optional.>
- The text of the content-description.
- If you don't specify it, the field is not put in the header.
-
- =item Disposition
-
- I<Optional.>
- The basic content-disposition (C<"attachment"> or C<"inline">).
- If you don't specify it, it defaults to "inline" for backwards
- compatibility. I<Thanks to Kurt Freytag for suggesting this feature.>
-
- =item Encoding
-
- I<Optional.>
- The content-transfer-encoding.
- If you don't specify it, a reasonable default is put in.
- You can also give the special value '-SUGGEST', to have it chosen for
- you in a heavy-duty fashion which scans the data itself.
-
- =item Filename
-
- I<Single-part entities only. Optional.>
- The recommended filename. Overrides any name extracted from C<Path>.
- The information is stored both the deprecated (content-type) and
- preferred (content-disposition) locations. If you explicitly want to
- I<avoid> a recommended filename (even when Path is used), supply this
- as empty or undef.
-
- =item Id
-
- I<Optional.>
- Set the content-id.
-
- =item Path
-
- I<Single-part entities only. Optional.>
- The path to the file to attach. The body is opened on that file
- using MIME::Body::File.
-
- =item Top
-
- I<Optional.>
- Is this a top-level entity? If so, it must sport a MIME-Version.
- The default is true. (NB: look at how C<attach()> uses it.)
-
- =item Type
-
- I<Optional.>
- The basic content-type (C<"text/plain">, etc.).
- If you don't specify it, it defaults to C<"text/plain">
- as per RFC-1521. I<Do yourself a favor: put it in.>
-
- =back
-
- =cut
-
- sub build {
- my ($self, @paramlist) = @_;
- my %params = @paramlist;
- my ($field, $filename, $boundary);
-
- ### Create a new entity, if needed:
- ref($self) or $self = $self->new;
-
-
- ### GET INFO...
-
- ### Get sundry field:
- my $type = $params{Type} || 'text/plain';
- my $charset = $params{Charset};
- my $is_multipart = ($type =~ m{^multipart/}i);
- my $encoding = $params{Encoding} || '';
- my $desc = $params{Description};
- my $top = exists($params{Top}) ? $params{Top} : 1;
- my $disposition = $params{Disposition} || 'inline';
- my $id = $params{Id};
-
- ### Get recommended filename, allowing explicit no-value value:
- my ($path_fname) = (($params{Path}||'') =~ m{([^/]+)\Z});
- $filename = (exists($params{Filename}) ? $params{Filename} : $path_fname);
- $filename = undef if (defined($filename) and $filename eq '');
-
- ### Type-check sanity:
- if ($type =~ m{^(multipart|message)/}) {
- ($encoding =~ /^(|7bit|8bit|binary|-suggest)$/i)
- or croak "can't have encoding $encoding for message type $type!";
- }
-
- ### Multipart or not? Do sanity check and fixup:
- if ($is_multipart) { ### multipart...
-
- ### Get any supplied boundary, and check it:
- if (defined($boundary = $params{Boundary})) { ### they gave us one...
- if ($boundary eq '') {
- whine "empty string not a legal boundary: I'm ignoring it";
- $boundary = undef;
- }
- elsif ($boundary =~ m{[^0-9a-zA-Z_\'\(\)\+\,\.\/\:\=\?\- ]}) {
- whine "boundary ignored: illegal characters ($boundary)";
- $boundary = undef;
- }
- }
-
- ### If we have to roll our own boundary, do so:
- defined($boundary) or $boundary = make_boundary();
- }
- else { ### single part...
- ### Create body:
- if ($params{Path}) {
- $self->bodyhandle(new MIME::Body::File $params{Path});
- }
- elsif (defined($params{Data})) {
- $self->bodyhandle(new MIME::Body::InCore $params{Data});
- }
- else {
- die "can't build entity: no body, and not multipart\n";
- }
-
- ### Check whether we need to binmode(): [Steve Kilbane]
- $self->bodyhandle->binmode(1) unless textual_type($type);
- }
-
-
- ### MAKE HEAD...
-
- ### Create head:
- my $head = new MIME::Head;
- $self->head($head);
- $head->modify(1);
-
- ### Add content-type field:
- $field = new Mail::Field 'Content_type'; ### not a typo :-(
- $field->type($type);
- $field->charset($charset) if $charset;
- $field->name($filename) if defined($filename);
- $field->boundary($boundary) if defined($boundary);
- $head->replace('Content-type', $field->stringify);
-
- ### Now that both body and content-type are available, we can suggest
- ### content-transfer-encoding (if desired);
- if (!$encoding) {
- $encoding = $self->suggest_encoding_lite;
- }
- elsif (lc($encoding) eq '-suggest') {
- $encoding = $self->suggest_encoding;
- }
-
- ### Add content-disposition field (if not multipart):
- unless ($is_multipart) {
- $field = new Mail::Field 'Content_disposition'; ### not a typo :-(
- $field->type($disposition);
- $field->filename($filename) if defined($filename);
- $head->replace('Content-disposition', $field->stringify);
- }
-
- ### Add other MIME fields:
- $head->replace('Content-transfer-encoding', $encoding) if $encoding;
- $head->replace('Content-description', $desc) if $desc;
- $head->replace('Content-id', $id) if defined($id);
- $head->replace('MIME-Version', '1.0') if $top;
-
- ### Add the X-Mailer field, if top level (use default value if not given):
- $top and $head->replace('X-Mailer',
- "MIME-tools ".(0+MIME::Tools->version).
- " (Entity " .(0+$VERSION).")");
-
- ### Add remaining user-specified fields, if any:
- while (@paramlist) {
- my ($tag, $value) = (shift @paramlist, shift @paramlist);
-
- ### Get fieldname, if that's what it is:
- if ($tag =~ /^-(.*)/s) { $tag = lc($1) } ### old style, b.c.
- elsif ($tag =~ /(.*):$/s ) { $tag = lc($1) } ### new style
- elsif (known_field(lc($tag))) { 1 } ### known field
- else { next; } ### not a field
-
- ### Clear head, get list of values, and add them:
- $head->delete($tag);
- foreach $value (ref($value) ? @$value : ($value)) {
- (defined($value) && ($value ne '')) or next;
- $head->add($tag, $value);
- }
- }
-
- ### Done!
- $self;
- }
-
- #------------------------------
-
- =item dup
-
- I<Instance method.>
- Duplicate the entity. Does a deep, recursive copy, I<but beware:>
- external data in bodyhandles is I<not> copied to new files!
- Changing the data in one entity's data file, or purging that entity,
- I<will> affect its duplicate. Entities with in-core data probably need
- not worry.
-
- =cut
-
- sub dup {
- my $self = shift;
- local($_);
-
- ### Self (this will also dup the header):
- my $dup = bless $self->SUPER::dup(), ref($self);
-
- ### Any simple inst vars:
- foreach (keys %$self) {$dup->{$_} = $self->{$_} unless ref($self->{$_})};
-
- ### Bodyhandle:
- $dup->bodyhandle($self->bodyhandle ? $self->bodyhandle->dup : undef);
-
- ### Preamble and epilogue:
- foreach (qw(ME_Preamble ME_Epilogue)) {
- $dup->{$_} = [@{$self->{$_}}] if $self->{$_};
- }
-
- ### Parts:
- $dup->{ME_Parts} = [];
- foreach (@{$self->{ME_Parts}}) { push @{$dup->{ME_Parts}}, $_->dup }
-
- ### Done!
- $dup;
- }
-
- =back
-
- =cut
-
-
-
-
-
- #==============================
-
- =head2 Access
-
- =over 4
-
- =cut
-
-
- #------------------------------
-
- =item body [VALUE]
-
- I<Instance method.>
- Get the I<encoded> (transport-ready) body, as an array of lines.
- This is a read-only data structure: changing its contents will have
- no effect. Its contents are identical to what is printed by
- L<print_body()|/print_body>.
-
- Provided for compatibility with Mail::Internet, so that methods
- like C<smtpsend()> will work. Note however that if VALUE is given,
- a fatal exception is thrown, since you cannot use this method to
- I<set> the lines of the encoded message.
-
- If you want the raw (unencoded) body data, use the L<bodyhandle()|/bodyhandle>
- method to get and use a MIME::Body. The content-type of the entity
- will tell you whether that body is best read as text (via getline())
- or raw data (via read()).
-
- =cut
-
- sub body {
- my ($self, $value) = @_;
- if (@_ > 1) { ### setting body line(s)...
- croak "you cannot use body() to set the encoded contents\n";
- }
- else { ### getting body lines...
- my $lines = [];
- my $lh = IO::Lines->new($lines);
- $self->print_body($lh);
- return $lines;
- }
- }
-
- #------------------------------
-
- =item bodyhandle [VALUE]
-
- I<Instance method.>
- Get or set an abstract object representing the body of the message.
- The body holds the decoded message data.
-
- B<Note that not all entities have bodies!>
- An entity will have either a body or parts: not both.
- This method will I<only> return an object if this entity can
- have a body; otherwise, it will return undefined.
- Whether-or-not a given entity can have a body is determined by
- (1) its content type, and (2) whether-or-not the parser was told to
- extract nested messages:
-
- Type: | Extract nested? | bodyhandle() | parts()
- -----------------------------------------------------------------------
- multipart/* | - | undef | 0 or more MIME::Entity
- message/* | true | undef | 0 or 1 MIME::Entity
- message/* | false | MIME::Body | empty list
- (other) | - | MIME::Body | empty list
-
- If C<VALUE> I<is not> given, the current bodyhandle is returned,
- or undef if the entity cannot have a body.
-
- If C<VALUE> I<is> given, the bodyhandle is set to the new value,
- and the previous value is returned.
-
- See L</parts> for more info.
-
- =cut
-
- sub bodyhandle {
- my ($self, $newvalue) = @_;
- my $value = $self->{ME_Bodyhandle};
- $self->{ME_Bodyhandle} = $newvalue if (@_ > 1);
- $value;
- }
-
- #------------------------------
-
- =item effective_type [MIMETYPE]
-
- I<Instance method.>
- Set/get the I<effective> MIME type of this entity. This is I<usually>
- identical to the actual (or defaulted) MIME type, but in some cases
- it differs. For example, from RFC-2045:
-
- Any entity with an unrecognized Content-Transfer-Encoding must be
- treated as if it has a Content-Type of "application/octet-stream",
- regardless of what the Content-Type header field actually says.
-
- Why? because if we can't decode the message, then we have to take
- the bytes as-is, in their (unrecognized) encoded form. So the
- message ceases to be a "text/foobar" and becomes a bunch of undecipherable
- bytes -- in other words, an "application/octet-stream".
-
- Such an entity, if parsed, would have its effective_type() set to
- C<"application/octet_stream">, although the mime_type() and the contents
- of the header would remain the same.
-
- If there is no effective type, the method just returns what
- mime_type() would.
-
- B<Warning:> the effective type is "sticky"; once set, that effective_type()
- will always be returned even if the conditions that necessitated setting
- the effective type become no longer true.
-
- =cut
-
- sub effective_type {
- my $self = shift;
- $self->{ME_EffType} = shift if @_;
- return ($self->{ME_EffType} ? lc($self->{ME_EffType}) : $self->mime_type);
- }
-
-
- #------------------------------
-
- =item epilogue [LINES]
-
- I<Instance method.>
- Get/set the text of the epilogue, as an array of newline-terminated LINES.
- Returns a reference to the array of lines, or undef if no epilogue exists.
-
- If there is a epilogue, it is output when printing this entity; otherwise,
- a default epilogue is used. Setting the epilogue to undef (not []!) causes
- it to fallback to the default.
-
- =cut
-
- sub epilogue {
- my ($self, $lines) = @_;
- $self->{ME_Epilogue} = $lines if @_ > 1;
- $self->{ME_Epilogue};
- }
-
- #------------------------------
-
- =item head [VALUE]
-
- I<Instance method.>
- Get/set the head.
-
- If there is no VALUE given, returns the current head. If none
- exists, an empty instance of MIME::Head is created, set, and returned.
-
- B<Note:> This is a patch over a problem in Mail::Internet, which doesn't
- provide a method for setting the head to some given object.
-
- =cut
-
- sub head {
- my ($self, $value) = @_;
- (@_ > 1) and $self->{'mail_inet_head'} = $value;
- $self->{'mail_inet_head'} ||= new MIME::Head; ### KLUDGE!
- }
-
- #------------------------------
-
- =item is_multipart
-
- I<Instance method.>
- Does this entity's effective MIME type indicate that it's a multipart entity?
- Returns undef (false) if the answer couldn't be determined, 0 (false)
- if it was determined to be false, and true otherwise.
- Note that this says nothing about whether or not parts were extracted.
-
- NOTE: we switched to effective_type so that multiparts with
- bad or missing boundaries could be coerced to an effective type
- of C<application/x-unparseable-multipart>.
-
-
- =cut
-
- sub is_multipart {
- my $self = shift;
- $self->head or return undef; ### no head, so no MIME type!
- my ($type, $subtype) = split('/', $self->effective_type);
- (($type eq 'multipart') ? 1 : 0);
- }
-
- #------------------------------
-
- =item mime_type
-
- I<Instance method.>
- A purely-for-convenience method. This simply relays the request to the
- associated MIME::Head object.
- If there is no head, returns undef in a scalar context and
- the empty array in a list context.
-
- B<Before you use this,> consider using effective_type() instead,
- especially if you obtained the entity from a MIME::Parser.
-
- =cut
-
- sub mime_type {
- my $self = shift;
- $self->head or return (wantarray ? () : undef);
- $self->head->mime_type;
- }
-
- #------------------------------
-
- =item open READWRITE
-
- I<Instance method.>
- A purely-for-convenience method. This simply relays the request to the
- associated MIME::Body object (see MIME::Body::open()).
- READWRITE is either 'r' (open for read) or 'w' (open for write).
-
- If there is no body, returns false.
-
- =cut
-
- sub open {
- my $self = shift;
- $self->bodyhandle and $self->bodyhandle->open(@_);
- }
-
- #------------------------------
-
- =item parts
-
- =item parts INDEX
-
- =item parts ARRAYREF
-
- I<Instance method.>
- Return the MIME::Entity objects which are the sub parts of this
- entity (if any).
-
- I<If no argument is given,> returns the array of all sub parts,
- returning the empty array if there are none (e.g., if this is a single
- part message, or a degenerate multipart). In a scalar context, this
- returns you the number of parts.
-
- I<If an integer INDEX is given,> return the INDEXed part,
- or undef if it doesn't exist.
-
- I<If an ARRAYREF to an array of parts is given,> then this method I<sets>
- the parts to a copy of that array, and returns the parts. This can
- be used to delete parts, as follows:
-
- ### Delete some parts of a multipart message:
- $msg->parts([ grep { keep_part($_) } $msg->parts ]);
-
-
- B<Note:> for multipart messages, the preamble and epilogue are I<not>
- considered parts. If you need them, use the C<preamble()> and C<epilogue()>
- methods.
-
- B<Note:> there are ways of parsing with a MIME::Parser which cause
- certain message parts (such as those of type C<message/rfc822>)
- to be "reparsed" into pseudo-multipart entities. You should read the
- documentation for those options carefully: it I<is> possible for
- a diddled entity to not be multipart, but still have parts attached to it!
-
- See L</bodyhandle> for a discussion of parts vs. bodies.
-
- =cut
-
- sub parts {
- my $self = shift;
- ref($_[0]) and return @{$self->{ME_Parts} = [@{$_[0]}]}; ### set the parts
- (@_ ? $self->{ME_Parts}[$_[0]] : @{$self->{ME_Parts}});
- }
-
- #------------------------------
-
- =item parts_DFS
-
- I<Instance method.>
- Return the list of all MIME::Entity objects included in the entity,
- starting with the entity itself, in depth-first-search order.
- If the entity has no parts, it alone will be returned.
-
- I<Thanks to Xavier Armengou for suggesting this method.>
-
- =cut
-
- sub parts_DFS {
- my $self = shift;
- return ($self, map { $_->parts_DFS } $self->parts);
- }
-
- #------------------------------
-
- =item preamble [LINES]
-
- I<Instance method.>
- Get/set the text of the preamble, as an array of newline-terminated LINES.
- Returns a reference to the array of lines, or undef if no preamble exists
- (e.g., if this is a single-part entity).
-
- If there is a preamble, it is output when printing this entity; otherwise,
- a default preamble is used. Setting the preamble to undef (not []!) causes
- it to fallback to the default.
-
- =cut
-
- sub preamble {
- my ($self, $lines) = @_;
- $self->{ME_Preamble} = $lines if @_ > 1;
- $self->{ME_Preamble};
- }
-
-
-
-
-
- =back
-
- =cut
-
-
-
-
- #==============================
-
- =head2 Manipulation
-
- =over 4
-
- =cut
-
- #------------------------------
-
- =item make_multipart [SUBTYPE], OPTSHASH...
-
- I<Instance method.>
- Force the entity to be a multipart, if it isn't already.
- We do this by replacing the original [singlepart] entity with a new
- multipart that has the same non-MIME headers ("From", "Subject", etc.),
- but all-new MIME headers ("Content-type", etc.). We then create
- a copy of the original singlepart, I<strip out> the non-MIME headers
- from that, and make it a part of the new multipart. So this:
-
- From: me
- To: you
- Content-type: text/plain
- Content-length: 12
-
- Hello there!
-
- Becomes something like this:
-
- From: me
- To: you
- Content-type: multipart/mixed; boundary="----abc----"
-
- ------abc----
- Content-type: text/plain
- Content-length: 12
-
- Hello there!
- ------abc------
-
- The actual type of the new top-level multipart will be "multipart/SUBTYPE"
- (default SUBTYPE is "mixed").
-
- Returns 'DONE' if we really did inflate a singlepart to a multipart.
- Returns 'ALREADY' (and does nothing) if entity is I<already> multipart
- and Force was not chosen.
-
- If OPTSHASH contains Force=>1, then we I<always> bump the top-level's
- content and content-headers down to a subpart of this entity, even if
- this entity is already a multipart. This is apparently of use to
- people who are tweaking messages after parsing them.
-
- =cut
-
- sub make_multipart {
- my ($self, $subtype, %opts) = @_;
- my $tag;
- $subtype ||= 'mixed';
- my $force = $opts{Force};
-
- ### Trap for simple case: already a multipart?
- return 'ALREADY' if ($self->is_multipart and !$force);
-
- ### Rip out our guts, and spew them into our future part:
- my $part = bless {%$self}, ref($self); ### part is a shallow copy
- %$self = (); ### lobotomize ourselves!
- $self->head($part->head->dup); ### dup the header
-
- ### Remove content headers from top-level, and set it up as a multipart:
- foreach $tag (grep {/^content-/i} $self->head->tags) {
- $self->head->delete($tag);
- }
- $self->head->mime_attr('Content-type' => "multipart/$subtype");
- $self->head->mime_attr('Content-type.boundary' => make_boundary());
-
- ### Remove NON-content headers from the part:
- foreach $tag (grep {!/^content-/i} $part->head->tags) {
- $part->head->delete($tag);
- }
-
- ### Add the [sole] part:
- $self->{ME_Parts} = [];
- $self->add_part($part);
- 'DONE';
- }
-
- #------------------------------
-
- =item make_singlepart
-
- I<Instance method.>
- If the entity is a multipart message with one part, this tries hard to
- rewrite it as a singlepart, by replacing the content (and content headers)
- of the top level with those of the part. Also crunches 0-part multiparts
- into singleparts.
-
- Returns 'DONE' if we really did collapse a multipart to a singlepart.
- Returns 'ALREADY' (and does nothing) if entity is already a singlepart.
- Returns '0' (and does nothing) if it can't be made into a singlepart.
-
- =cut
-
- sub make_singlepart {
- my $self = shift;
-
- ### Trap for simple cases:
- return 'ALREADY' if !$self->is_multipart; ### already a singlepart?
- return '0' if ($self->parts > 1); ### can this even be done?
-
- ### Do it:
- if ($self->parts == 1) { ### one part
- my $part = $self->parts(0);
- my $tag;
-
- ### Get rid of all our existing content info:
- foreach $tag (grep {/^content-/i} $self->head->tags) {
- $self->head->delete($tag);
- }
-
- ### Populate ourselves with any content info from the part:
- foreach $tag (grep {/^content-/i} $part->head->tags) {
- foreach ($part->head->get($tag)) { $self->head->add($tag, $_) }
- }
-
- ### Save reconstructed header, replace our guts, and restore header:
- my $new_head = $self->head;
- %$self = %$part; ### shallow copy is ok!
- $self->head($new_head);
-
- ### One more thing: the part *may* have been a multi with 0 or 1 parts!
- return $self->make_singlepart(@_) if $self->is_multipart;
- }
- else { ### no parts!
- $self->head->mime_attr('Content-type'=>'text/plain'); ### simple
- }
- 'DONE';
- }
-
- #------------------------------
-
- =item purge
-
- I<Instance method.>
- Recursively purge (e.g., unlink) all external (e.g., on-disk) body parts
- in this message. See MIME::Body::purge() for details.
-
- B<Note:> this does I<not> delete the directories that those body parts
- are contained in; only the actual message data files are deleted.
- This is because some parsers may be customized to create intermediate
- directories while others are not, and it's impossible for this class
- to know what directories are safe to remove. Only your application
- program truly knows that.
-
- B<If you really want to "clean everything up",> one good way is to
- use C<MIME::Parser::file_under()>, and then do this before parsing
- your next message:
-
- $parser->filer->purge();
-
- I wouldn't attempt to read those body files after you do this, for
- obvious reasons. As of MIME-tools 4.x, each body's path I<is> undefined
- after this operation. I warned you I might do this; truly I did.
-
- I<Thanks to Jason L. Tibbitts III for suggesting this method.>
-
- =cut
-
- sub purge {
- my $self = shift;
- $self->bodyhandle and $self->bodyhandle->purge; ### purge me
- foreach ($self->parts) { $_->purge } ### recurse
- 1;
- }
-
- #------------------------------
- #
- # _do_remove_sig
- #
- # Private. Remove a signature within NLINES lines from the end of BODY.
- # The signature must be flagged by a line containing only "-- ".
-
- sub _do_remove_sig {
- my ($body, $nlines) = @_;
- $nlines ||= 10;
- my $i = 0;
-
- my $line = int(@$body) || return;
- while ($i++ < $nlines and $line--) {
- if ($body->[$line] =~ /\A--[ \040][\r\n]+\Z/) {
- $#{$body} = $line-1;
- return;
- }
- }
- }
-
- #------------------------------
-
- =item remove_sig [NLINES]
-
- I<Instance method, override.>
- Attempts to remove a user's signature from the body of a message.
-
- It does this by looking for a line matching C</^-- $/> within the last
- C<NLINES> of the message. If found then that line and all lines after
- it will be removed. If C<NLINES> is not given, a default value of 10
- will be used. This would be of most use in auto-reply scripts.
-
- For MIME entity, this method is reasonably cautious: it will only
- attempt to un-sign a message with a content-type of C<text/*>.
-
- If you send remove_sig() to a multipart entity, it will relay it to
- the first part (the others usually being the "attachments").
-
- B<Warning:> currently slurps the whole message-part into core as an
- array of lines, so you probably don't want to use this on extremely
- long messages.
-
- Returns truth on success, false on error.
-
- =cut
-
- sub remove_sig {
- my $self = shift;
- my $nlines = shift;
-
- ### Handle multiparts:
- $self->is_multipart and return $self->{ME_Parts}[0]->remove_sig(@_);
-
- ### Refuse non-textual unless forced:
- textual_type($self->head->mime_type)
- or return error "I won't un-sign a non-text message unless I'm forced";
-
- ### Get body data, as an array of newline-terminated lines:
- $self->bodyhandle or return undef;
- my @body = $self->bodyhandle->as_lines;
-
- ### Nuke sig:
- _do_remove_sig(\@body, $nlines);
-
- ### Output data back into body:
- my $io = $self->bodyhandle->open("w");
- foreach (@body) { $io->print($_) }; ### body data
- $io->close;
-
- ### Done!
- 1;
- }
-
- #------------------------------
-
- =item sign PARAMHASH
-
- I<Instance method, override.>
- Append a signature to the message. The params are:
-
- =over 4
-
- =item Attach
-
- Instead of appending the text, add it to the message as an attachment.
- The disposition will be C<inline>, and the description will indicate
- that it is a signature. The default behavior is to append the signature
- to the text of the message (or the text of its first part if multipart).
- I<MIME-specific; new in this subclass.>
-
- =item File
-
- Use the contents of this file as the signature.
- Fatal error if it can't be read.
- I<As per superclass method.>
-
- =item Force
-
- Sign it even if the content-type isn't C<text/*>. Useful for
- non-standard types like C<x-foobar>, but be careful!
- I<MIME-specific; new in this subclass.>
-
- =item Remove
-
- Normally, we attempt to strip out any existing signature.
- If true, this gives us the NLINES parameter of the remove_sig call.
- If zero but defined, tells us I<not> to remove any existing signature.
- If undefined, removal is done with the default of 10 lines.
- I<New in this subclass.>
-
- =item Signature
-
- Use this text as the signature. You can supply it as either
- a scalar, or as a ref to an array of newline-terminated scalars.
- I<As per superclass method.>
-
- =back
-
- For MIME messages, this method is reasonably cautious: it will only
- attempt to sign a message with a content-type of C<text/*>, unless
- C<Force> is specified.
-
- If you send this message to a multipart entity, it will relay it to
- the first part (the others usually being the "attachments").
-
- B<Warning:> currently slurps the whole message-part into core as an
- array of lines, so you probably don't want to use this on extremely
- long messages.
-
- Returns true on success, false otherwise.
-
- =cut
-
- sub sign {
- my $self = shift;
- my %params = @_;
- my $io;
-
- ### If multipart and not attaching, try to sign our first part:
- if ($self->is_multipart and !$params{Attach}) {
- return $self->parts(0)->sign(@_);
- }
-
- ### Get signature:
- my $sig;
- if (defined($sig = $params{Signature})) { ### scalar or array
- $sig = (ref($sig) ? join('', @$sig) : $sig);
- }
- elsif ($params{File}) { ### file contents
- CORE::open SIG, $params{File} or croak "can't open $params{File}: $!";
- $sig = join('', SIG->getlines);
- close SIG;
- }
- else {
- croak "no signature given!";
- }
-
- ### Add signature to message as appropriate:
- if ($params{Attach}) { ### Attach .sig as new part...
- return $self->attach(Type => 'text/plain',
- Description => 'Signature',
- Disposition => 'inline',
- Encoding => '-SUGGEST',
- Data => $sig);
- }
- else { ### Add text of .sig to body data...
-
- ### Refuse non-textual unless forced:
- ($self->head->mime_type =~ m{text/}i or $params{Force}) or
- return error "I won't sign a non-text message unless I'm forced";
-
- ### Get body data, as an array of newline-terminated lines:
- $self->bodyhandle or return undef;
- my @body = $self->bodyhandle->as_lines;
-
- ### Nuke any existing sig?
- if (!defined($params{Remove}) || ($params{Remove} > 0)) {
- _do_remove_sig(\@body, $params{Remove});
- }
-
- ### Output data back into body, followed by signature:
- my $line;
- $io = $self->open("w");
- foreach $line (@body) { $io->print($line) }; ### body data
- (($body[-1]||'') =~ /\n\Z/) or $io->print("\n"); ### ensure final \n
- $io->print("-- \n$sig"); ### separator + sig
- $io->close;
- return 1; ### done!
- }
- }
-
- #------------------------------
-
- =item suggest_encoding
-
- I<Instance method.>
- Based on the effective content type, 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
- text no quoted-printable
- message yes 7bit
- message no binary
- multipart * binary (in case some parts are bad)
- image, etc... * base64
-
- =cut
-
- ### TO DO: resolve encodings of nested entities (possibly in sync_headers).
-
- sub suggest_encoding {
- my $self = shift;
-
- my ($type) = split '/', $self->effective_type;
- if (($type eq 'text') || ($type eq 'message')) { ### scan message body
- $self->bodyhandle || return ($self->parts ? 'binary' : '7bit');
- my ($IO, $unclean);
- if ($IO = $self->bodyhandle->open("r")) {
-
- ### Scan message for 7bit-cleanliness:
- while (defined($_ = $IO->getline)) {
- last if ($unclean = ((length($_) > 999) or /[\200-\377]/));
- }
-
- ### Return '7bit' if clean; try and encode if not...
- ### Note that encodings are not permitted for messages!
- return ($unclean
- ? (($type eq 'message') ? 'binary' : 'quoted-printable')
- : '7bit');
- }
- }
- else {
- return ($type eq 'multipart') ? 'binary' : 'base64';
- }
- }
-
- sub suggest_encoding_lite {
- my $self = shift;
- my ($type) = split '/', $self->effective_type;
- return (($type =~ /^(text|message|multipart)$/) ? 'binary' : 'base64');
- }
-
- #------------------------------
-
- =item sync_headers OPTIONS
-
- I<Instance method.>
- This method does a variety of activities which ensure that
- the MIME headers of an entity "tree" are in-synch with the body parts
- they describe. It can be as expensive an operation as printing
- if it involves pre-encoding the body parts; however, the aim is to
- produce fairly clean MIME. B<You will usually only need to invoke
- this if processing and re-sending MIME from an outside source.>
-
- The OPTIONS is a hash, which describes what is to be done.
-
- =over 4
-
-
- =item Length
-
- One of the "official unofficial" MIME fields is "Content-Length".
- Normally, one doesn't care a whit about this field; however, if
- you are preparing output destined for HTTP, you may. The value of
- this option dictates what will be done:
-
- B<COMPUTE> means to set a C<Content-Length> field for every non-multipart
- part in the entity, and to blank that field out for every multipart
- part in the entity.
-
- B<ERASE> means that C<Content-Length> fields will all
- be blanked out. This is fast, painless, and safe.
-
- B<Any false value> (the default) means to take no action.
-
-
- =item Nonstandard
-
- Any header field beginning with "Content-" is, according to the RFC,
- a MIME field. However, some are non-standard, and may cause problems
- with certain MIME readers which interpret them in different ways.
-
- B<ERASE> means that all such fields will be blanked out. This is
- done I<before> the B<Length> option (q.v.) is examined and acted upon.
-
- B<Any false value> (the default) means to take no action.
-
-
- =back
-
- Returns a true value if everything went okay, a false value otherwise.
-
- =cut
-
- sub sync_headers {
- my $self = shift;
- my $opts = ((int(@_) % 2 == 0) ? {@_} : shift);
- my $ENCBODY; ### keep it around until done!
-
- ### Get options:
- my $o_nonstandard = ($opts->{Nonstandard} || 0);
- my $o_length = ($opts->{Length} || 0);
-
- ### Get head:
- my $head = $self->head;
-
- ### What to do with "nonstandard" MIME fields?
- if ($o_nonstandard eq 'ERASE') { ### Erase them...
- my $tag;
- foreach $tag ($head->tags()) {
- if (($tag =~ /\AContent-/i) &&
- ($tag !~ /\AContent-$StandardFields\Z/io)) {
- $head->delete($tag);
- }
- }
- }
-
- ### What to do with the "Content-Length" MIME field?
- if ($o_length eq 'COMPUTE') { ### Compute the content length...
- my $content_length = '';
-
- ### We don't have content-lengths in multiparts...
- if ($self->is_multipart) { ### multipart...
- $head->delete('Content-length');
- }
- else { ### singlepart...
-
- ### Get the encoded body, if we don't have it already:
- unless ($ENCBODY) {
- $ENCBODY = tmpopen() || die "can't open tmpfile";
- $self->print_body($ENCBODY); ### write encoded to tmpfile
- }
-
- ### Analyse it:
- $ENCBODY->seek(0,2); ### fast-forward
- $content_length = $ENCBODY->tell; ### get encoded length
- $ENCBODY->seek(0,0); ### rewind
-
- ### Remember:
- $self->head->replace('Content-length', $content_length);
- }
- }
- elsif ($o_length eq 'ERASE') { ### Erase the content-length...
- $head->delete('Content-length');
- }
-
- ### Done with everything for us!
- undef($ENCBODY);
-
- ### Recurse:
- my $part;
- foreach $part ($self->parts) { $part->sync_headers($opts) or return undef }
- 1;
- }
-
- #------------------------------
-
- =item tidy_body
-
- I<Instance method, override.>
- Currently unimplemented for MIME messages. Does nothing, returns false.
-
- =cut
-
- sub tidy_body {
- usage "MIME::Entity::tidy_body currently does nothing";
- 0;
- }
-
- =back
-
- =cut
-
-
-
-
-
- #==============================
-
- =head2 Output
-
- =over 4
-
- =cut
-
- #------------------------------
-
- =item dump_skeleton [FILEHANDLE]
-
- I<Instance method.>
- Dump the skeleton of the entity to the given FILEHANDLE, or
- to the currently-selected one if none given.
-
- Each entity is output with an appropriate indentation level,
- the following selection of attributes:
-
- Content-type: multipart/mixed
- Effective-type: multipart/mixed
- Body-file: NONE
- Subject: Hey there!
- Num-parts: 2
-
- This is really just useful for debugging purposes; I make no guarantees
- about the consistency of the output format over time.
-
- =cut
-
- sub dump_skeleton {
- my ($self, $fh, $indent) = @_;
- $fh or $fh = select;
- defined($indent) or $indent = 0;
- my $ind = ' ' x $indent;
- my $part;
- no strict 'refs';
-
-
- ### The content type:
- print $fh $ind,"Content-type: ", ($self->mime_type||'UNKNOWN'),"\n";
- print $fh $ind,"Effective-type: ", ($self->effective_type||'UNKNOWN'),"\n";
-
- ### The name of the file containing the body (if any!):
- my $path = ($self->bodyhandle ? $self->bodyhandle->path : undef);
- print $fh $ind, "Body-file: ", ($path || 'NONE'), "\n";
-
- ### The recommended file name (thanks to Allen Campbell):
- my $filename = $self->head->recommended_filename;
- print $fh $ind, "Recommended-filename: ", $filename, "\n" if ($filename);
-
- ### The subject (note: already a newline if 2.x!)
- my $subj = $self->head->get('subject',0);
- defined($subj) or $subj = '';
- chomp($subj);
- print $fh $ind, "Subject: $subj\n" if $subj;
-
- ### The parts:
- my @parts = $self->parts;
- print $fh $ind, "Num-parts: ", int(@parts), "\n" if @parts;
- print $fh $ind, "--\n";
- foreach $part (@parts) {
- $part->dump_skeleton($fh, $indent+1);
- }
- }
-
- #------------------------------
-
- =item print [OUTSTREAM]
-
- I<Instance method, override.>
- Print the entity to the given OUTSTREAM, or to the currently-selected
- filehandle if none given. OUTSTREAM can be a filehandle, or any object
- that reponds to a print() message.
-
- The entity is output as a valid MIME stream! This means that the
- header is always output first, and the body data (if any) will be
- encoded if the header says that it should be.
- For example, your output may look like this:
-
- Subject: Greetings
- Content-transfer-encoding: base64
-
- SGkgdGhlcmUhCkJ5ZSB0aGVyZSEK
-
- I<If this entity has MIME type "multipart/*",>
- the preamble, parts, and epilogue are all output with appropriate
- boundaries separating each.
- Any bodyhandle is ignored:
-
- Content-type: multipart/mixed; boundary="*----*"
- Content-transfer-encoding: 7bit
-
- [Preamble]
- --*----*
- [Entity: Part 0]
- --*----*
- [Entity: Part 1]
- --*----*--
- [Epilogue]
-
- I<If this entity has a single-part MIME type with no attached parts,>
- then we're looking at a normal singlepart entity: the body is output
- according to the encoding specified by the header.
- If no body exists, a warning is output and the body is treated as empty:
-
- Content-type: image/gif
- Content-transfer-encoding: base64
-
- [Encoded body]
-
- I<If this entity has a single-part MIME type but it also has parts,>
- then we're probably looking at a "re-parsed" singlepart, usually one
- of type C<message/*> (you can get entities like this if you set the
- C<parse_nested_messages(NEST)> option on the parser to true).
- In this case, the parts are output with single blank lines separating each,
- and any bodyhandle is ignored:
-
- Content-type: message/rfc822
- Content-transfer-encoding: 7bit
-
- [Entity: Part 0]
-
- [Entity: Part 1]
-
- In all cases, when outputting a "part" of the entity, this method
- is invoked recursively.
-
- B<Note:> the output is very likely I<not> going to be identical
- to any input you parsed to get this entity. If you're building
- some sort of email handler, it's up to you to save this information.
-
- =cut
-
- use Symbol;
- sub print {
- my ($self, $out) = @_;
- $out = select if @_ < 2;
- $out = Symbol::qualify($out,scalar(caller)) unless ref($out);
- $out = wraphandle($out); ### get a printable output
-
- $self->print_header($out); ### the header
- $out->print("\n");
- $self->print_body($out); ### the "stuff after the header"
- }
-
- #------------------------------
-
- =item print_body [OUTSTREAM]
-
- I<Instance method, override.>
- Print the body of the entity to the given OUTSTREAM, or to the
- currently-selected filehandle if none given. OUTSTREAM can be a
- filehandle, or any object that reponds to a print() message.
-
- The body is output for inclusion in a valid MIME stream; this means
- that the body data will be encoded if the header says that it should be.
-
- B<Note:> by "body", we mean "the stuff following the header".
- A printed multipart body includes the printed representations of its subparts.
-
- B<Note:> The body is I<stored> in an un-encoded form; however, the idea is that
- the transfer encoding is used to determine how it should be I<output.>
- This means that the C<print()> method is always guaranteed to get you
- a sendmail-ready stream whose body is consistent with its head.
- If you want the I<raw body data> to be output, you can either read it from
- the bodyhandle yourself, or use:
-
- $ent->bodyhandle->print($outstream);
-
- which uses read() calls to extract the information, and thus will
- work with both text and binary bodies.
-
- B<Warning:> Please supply an OUTSTREAM. This override method differs
- from Mail::Internet's behavior, which outputs to the STDOUT if no
- filehandle is given: this may lead to confusion.
-
- =cut
-
- sub print_body {
- my ($self, $out) = @_;
- $out = wraphandle($out || select); ### get a printable output
- my ($type) = split '/', lc($self->mime_type); ### handle by MIME type
-
- ### Multipart...
- if ($type eq 'multipart') {
- my $boundary = $self->head->multipart_boundary;
-
- ### Preamble:
- my $preamble = join('', @{ $self->preamble || $DefPreamble });
- $out->print("$preamble\n") if ($preamble ne '');
-
- ### Parts:
- my $part;
- foreach $part ($self->parts) {
- $out->print("--$boundary\n");
- $part->print($out);
- $out->print("\n"); ### needed for next delim/close
- }
- $out->print("--$boundary--\n");
-
- ### Epilogue:
- my $epilogue = join('', @{ $self->epilogue || $DefEpilogue });
- if ($epilogue ne '') {
- $out->print($epilogue);
- $out->print("\n") if ($epilogue !~ /\n\Z/); ### be nice
- }
- }
-
- ### Singlepart type with parts...
- ### This makes $ent->print handle message/rfc822 bodies
- ### when parse_nested_messages('NEST') is on [idea by Marc Rouleau].
- elsif ($self->parts) {
- my $need_sep = 0;
- my $part;
- foreach $part ($self->parts) {
- $out->print("\n\n") if $need_sep++;
- $part->print($out);
- }
- }
-
- ### Singlepart type, or no parts: output body...
- else {
- $self->bodyhandle ? $self->print_bodyhandle($out)
- : whine "missing body; treated as empty";
- }
- 1;
- }
-
- #------------------------------
- #
- # print_bodyhandle
- #
- # Instance method, unpublicized. Print just the bodyhandle, *encoded*.
- #
- # WARNING: $self->print_bodyhandle() != $self->bodyhandle->print()!
- # The former encodes, and the latter does not!
- #
- sub print_bodyhandle {
- my ($self, $out) = @_;
- $out = wraphandle($out || select); ### get a printable output
-
- ### Get the encoding, defaulting to "binary" if unsupported:
- my $encoding = ($self->head->mime_encoding || 'binary');
- my $decoder = best MIME::Decoder $encoding;
- $decoder->head($self->head); ### associate with head, if any
-
- ### Output the body:
- my $IO = $self->open("r") || die "open body: $!";
- $decoder->encode($IO, $out) || return error "encoding failed";
- $IO->close;
- 1;
- }
-
- #------------------------------
-
- =item print_header [OUTSTREAM]
-
- I<Instance method, inherited.>
- Output the header to the given OUTSTREAM. You really should supply
- the OUTSTREAM.
-
- =cut
-
- ### Inherited.
-
- #------------------------------
-
- =item stringify
-
- I<Instance method.>
- Return the entity as a string, exactly as C<print> would print it.
- The body will be encoded as necessary, and will contain any subparts.
- You can also use C<as_string()>.
-
- =cut
-
- sub stringify {
- my $str = '';
- shift->print(new IO::Scalar \$str);
- $str;
- }
- sub as_string { shift->stringify }; ### silent BC
-
- #------------------------------
-
- =item stringify_body
-
- I<Instance method.>
- Return the I<encoded> message body as a string, exactly as C<print_body>
- would print it. You can also use C<body_as_string()>.
-
- If you want the I<unencoded> body, and you are dealing with a
- singlepart message (like a "text/plain"), use C<bodyhandle()> instead:
-
- if ($ent->bodyhandle) {
- $unencoded_data = $ent->bodyhandle->as_string;
- }
- else {
- ### this message has no body data (but it might have parts!)
- }
-
- =cut
-
- sub stringify_body {
- my $str = '';
- shift->print_body(new IO::Scalar \$str);
- $str;
- }
- sub body_as_string { shift->stringify_body }
-
- #------------------------------
- #
- # stringify_bodyhandle
- #
- # Instance method, unpublicized. Stringify just the bodyhandle.
-
- sub stringify_bodyhandle {
- my $str = '';
- shift->print_bodyhandle(new IO::Scalar \$str);
- $str;
- }
- sub bodyhandle_as_string { shift->stringify_bodyhandle }
-
- #------------------------------
-
- =item stringify_header
-
- I<Instance method.>
- Return the header as a string, exactly as C<print_header> would print it.
- You can also use C<header_as_string()>.
-
- =cut
-
- sub stringify_header {
- shift->head->stringify;
- }
- sub header_as_string { shift->stringify_header }
-
-
-
- __END__
- 1;
-
- #------------------------------
-
- =back
-
- =head1 NOTES
-
- =head2 Under the hood
-
- A B<MIME::Entity> is composed of the following elements:
-
- =over 4
-
- =item *
-
- A I<head>, which is a reference to a MIME::Head object
- containing the header information.
-
- =item *
-
- A I<bodyhandle>, which is a reference to a MIME::Body object
- containing the decoded body data. This is only defined if
- the message is a "singlepart" type:
-
- application/*
- audio/*
- image/*
- text/*
- video/*
-
- =item *
-
- An array of I<parts>, where each part is a MIME::Entity object.
- The number of parts will only be nonzero if the content-type
- is I<not> one of the "singlepart" types:
-
- message/* (should have exactly one part)
- multipart/* (should have one or more parts)
-
-
- =back
-
-
-
- =head2 The "two-body problem"
-
- MIME::Entity and Mail::Internet see message bodies differently,
- and this can cause confusion and some inconvenience. Sadly, I can't
- change the behavior of MIME::Entity without breaking lots of code already
- out there. But let's open up the floor for a few questions...
-
- =over 4
-
- =item What is the difference between a "message" and an "entity"?
-
- A B<message> is the actual data being sent or received; usually
- this means a stream of newline-terminated lines.
- An B<entity> is the representation of a message as an object.
-
- This means that you get a "message" when you print an "entity"
- I<to> a filehandle, and you get an "entity" when you parse a message
- I<from> a filehandle.
-
-
- =item What is a message body?
-
- B<Mail::Internet:>
- The portion of the printed message after the header.
-
- B<MIME::Entity:>
- The portion of the printed message after the header.
-
-
- =item How is a message body stored in an entity?
-
- B<Mail::Internet:>
- As an array of lines.
-
- B<MIME::Entity:>
- It depends on the content-type of the message.
- For "container" types (C<multipart/*>, C<message/*>), we store the
- contained entities as an array of "parts", accessed via the C<parts()>
- method, where each part is a complete MIME::Entity.
- For "singlepart" types (C<text/*>, C<image/*>, etc.), the unencoded
- body data is referenced via a MIME::Body object, accessed via
- the C<bodyhandle()> method:
-
- bodyhandle() parts()
- Content-type: returns: returns:
- ------------------------------------------------------------
- application/* MIME::Body empty
- audio/* MIME::Body empty
- image/* MIME::Body empty
- message/* undef MIME::Entity list (usually 1)
- multipart/* undef MIME::Entity list (usually >0)
- text/* MIME::Body empty
- video/* MIME::Body empty
- x-*/* MIME::Body empty
-
- As a special case, C<message/*> is currently ambiguous: depending
- on the parser, a C<message/*> might be treated as a singlepart,
- with a MIME::Body and no parts. Use bodyhandle() as the final
- arbiter.
-
-
- =item What does the body() method return?
-
- B<Mail::Internet:>
- As an array of lines, ready for sending.
-
- B<MIME::Entity:>
- As an array of lines, ready for sending.
-
-
- =item If an entity has a body, does it have a soul as well?
-
- The soul does not exist in a corporeal sense, the way the body does;
- it is not a solid [Perl] object. Rather, it is a virtual object
- which is only visible when you print() an entity to a file... in other
- words, the "soul" it is all that is left after the body is DESTROY'ed.
-
-
- =item What's the best way to get at the body data?
-
- B<Mail::Internet:>
- Use the body() method.
-
- B<MIME::Entity:>
- Depends on what you want... the I<encoded> data (as it is
- transported), or the I<unencoded> data? Keep reading...
-
-
- =item How do I get the "encoded" body data?
-
- B<Mail::Internet:>
- Use the body() method.
-
- B<MIME::Entity:>
- Use the body() method. You can also use:
-
- $entity->print_body()
- $entity->stringify_body() ### a.k.a. $entity->body_as_string()
-
-
- =item How do I get the "unencoded" body data?
-
- B<Mail::Internet:>
- Use the body() method.
-
- B<MIME::Entity:>
- Use the I<bodyhandle()> method!
- If bodyhandle() method returns true, then that value is a
- L<MIME::Body|MIME::Body> which can be used to access the data via
- its open() method. If bodyhandle() method returns an undefined value,
- then the entity is probably a "container" that has no real body data of
- its own (e.g., a "multipart" message): in this case, you should access
- the components via the parts() method. Like this:
-
- if ($bh = $entity->bodyhandle) {
- $io = $bh->open;
- ...access unencoded data via $io->getline or $io->read...
- $io->close;
- }
- else {
- foreach my $part (@parts) {
- ...do something with the part...
- }
- }
-
- You can also use:
-
- if ($bh = $entity->bodyhandle) {
- $unencoded_data = $bh->as_string;
- }
- else {
- ...do stuff with the parts...
- }
-
-
- =item What does the body() method return?
-
- B<Mail::Internet:>
- The transport-encoded message body, as an array of lines.
-
- B<MIME::Entity:>
- The transport-encoded message body, as an array of lines.
-
-
- =item What does print_body() print?
-
- B<Mail::Internet:>
- Exactly what body() would return to you.
-
- B<MIME::Entity:>
- Exactly what body() would return to you.
-
-
- =item Say I have an entity which might be either singlepart or multipart.
- How do I print out just "the stuff after the header"?
-
- B<Mail::Internet:>
- Use print_body().
-
- B<MIME::Entity:>
- Use print_body().
-
-
- =item Why is MIME::Entity so different from Mail::Internet?
-
- Because MIME streams are expected to have non-textual data...
- possibly, quite a lot of it, such as a tar file.
-
- Because MIME messages can consist of multiple parts, which are most-easily
- manipulated as MIME::Entity objects themselves.
-
- Because in the simpler world of Mail::Internet, the data of a message
- and its printed representation are I<identical>... and in the MIME
- world, they're not.
-
- Because parsing multipart bodies on-the-fly, or formatting multipart
- bodies for output, is a non-trivial task.
-
-
- =item This is confusing. Can the two classes be made more compatible?
-
- Not easily; their implementations are necessarily quite different.
- Mail::Internet is a simple, efficient way of dealing with a "black box"
- mail message... one whose internal data you don't care much about.
- MIME::Entity, in contrast, cares I<very much> about the message contents:
- that's its job!
-
- =back
-
-
-
- =head2 Design issues
-
- =over 4
-
- =item Some things just can't be ignored
-
- In multipart messages, the I<"preamble"> is the portion that precedes
- the first encapsulation boundary, and the I<"epilogue"> is the portion
- that follows the last encapsulation boundary.
-
- According to RFC-1521:
-
- There appears to be room for additional information prior
- to the first encapsulation boundary and following the final
- boundary. These areas should generally be left blank, and
- implementations must ignore anything that appears before the
- first boundary or after the last one.
-
- NOTE: These "preamble" and "epilogue" areas are generally
- not used because of the lack of proper typing of these parts
- and the lack of clear semantics for handling these areas at
- gateways, particularly X.400 gateways. However, rather than
- leaving the preamble area blank, many MIME implementations
- have found this to be a convenient place to insert an
- explanatory note for recipients who read the message with
- pre-MIME software, since such notes will be ignored by
- MIME-compliant software.
-
- In the world of standards-and-practices, that's the standard.
- Now for the practice:
-
- I<Some "MIME" mailers may incorrectly put a "part" in the preamble>.
- Since we have to parse over the stuff I<anyway>, in the future I
- I<may> allow the parser option of creating special MIME::Entity objects
- for the preamble and epilogue, with bogus MIME::Head objects.
-
- For now, though, we're MIME-compliant, so I probably won't change
- how we work.
-
- =back
-
-
- =head1 AUTHOR
-
- Eryq (F<eryq@zeegee.com>), ZeeGee Software Inc (F<http://www.zeegee.com>).
-
- All rights reserved. This program is free software; you can redistribute
- it and/or modify it under the same terms as Perl itself.
-
-
- =head1 VERSION
-
- $Revision: 5.404 $ $Date: 2000/11/06 11:58:53 $
-
- =cut
-
- #------------------------------
- 1;
-