home *** CD-ROM | disk | FTP | other *** search
- package MIME::Body;
-
-
- =head1 NAME
-
- MIME::Body - the body of a 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...
-
-
- =head2 Obtaining bodies
-
- ### Get the bodyhandle of a MIME::Entity object:
- $body = $entity->bodyhandle;
-
- ### Create a body which stores data in a disk file:
- $body = new MIME::Body::File "/path/to/file";
-
- ### Create a body which stores data in an in-core array:
- $body = new MIME::Body::InCore \@strings;
-
-
- =head2 Opening, closing, and using IO handles
-
- ### Write data to the body:
- $IO = $body->open("w") || die "open body: $!";
- $IO->print($message);
- $IO->close || die "close I/O handle: $!";
-
- ### Read data from the body (in this case, line by line):
- $IO = $body->open("r") || die "open body: $!";
- while (defined($_ = $IO->getline)) {
- ### do stuff
- }
- $IO->close || die "close I/O handle: $!";
-
-
- =head2 Other I/O
-
- ### Dump the ENCODED body data to a filehandle:
- $body->print(\*STDOUT);
-
- ### Slurp all the UNENCODED data in, and put it in a scalar:
- $string = $body->as_string;
-
- ### Slurp all the UNENCODED data in, and put it in an array of lines:
- @lines = $body->as_lines;
-
-
- =head2 Working directly with paths to underlying files
-
- ### Where's the data?
- if (defined($body->path)) { ### data is on disk:
- print "data is stored externally, in ", $body->path;
- }
- else { ### data is in core:
- print "data is already in core, and is...\n", $body->as_string;
- }
-
- ### Get rid of anything on disk:
- $body->purge;
-
-
- =head1 DESCRIPTION
-
- MIME messages can be very long (e.g., tar files, MPEGs, etc.) or very
- short (short textual notes, as in ordinary mail). Long messages
- are best stored in files, while short ones are perhaps best stored
- in core.
-
- This class is an attempt to define a common interface for objects
- which contain message data, regardless of how the data is
- physically stored. The lifespan of a "body" object
- usually looks like this:
-
- =over 4
-
- =item 1.
-
- B<Body object is created by a MIME::Parser during parsing.>
- It's at this point that the actual MIME::Body subclass is chosen,
- and new() is invoked. (For example: if the body data is going to
- a file, then it is at this point that the class MIME::Body::File,
- and the filename, is chosen).
-
- =item 2.
-
- B<Data is written to the body> (usually by the MIME parser) like this:
- The body is opened for writing, via C<open("w")>. This will trash any
- previous contents, and return an "I/O handle" opened for writing.
- Data is written to this I/O handle, via print().
- Then the I/O handle is closed, via close().
-
- =item 3.
-
- B<Data is read from the body> (usually by the user application) like this:
- The body is opened for reading by a user application, via C<open("r")>.
- This will return an "I/O handle" opened for reading.
- Data is read from the I/O handle, via read(), getline(), or getlines().
- Then the I/O handle is closed, via close().
-
- =item 4.
-
- B<Body object is destructed.>
-
- =back
-
- You can write your own subclasses, as long as they follow the
- interface described below. Implementers of subclasses should assume
- that steps 2 and 3 may be repeated any number of times, and in
- different orders (e.g., 1-2-2-3-2-3-3-3-3-3-2-4).
-
- In any case, once a MIME::Body has been created, you ask to open it
- for reading or writing, which gets you an "i/o handle": you then use
- the same mechanisms for reading from or writing to that handle, no matter
- what class it is.
-
- Beware: unless you know for certain what kind of body you have, you
- should I<not> assume that the body has an underlying filehandle.
-
-
- =head1 PUBLIC INTERFACE
-
- =over 4
-
- =cut
-
-
- ### Pragmas:
- use strict;
- use vars qw($VERSION);
-
- ### System modules:
- use IO::Scalar;
- use Carp;
-
- ### The package version, both in 1.23 style *and* usable by MakeMaker:
- $VERSION = substr q$Revision: 5.403 $, 10;
-
-
- #------------------------------
-
- =item new ARGS...
-
- I<Class method, constructor.>
- Create a new body. Any ARGS are sent to init().
-
- =cut
-
- sub new {
- my $self = bless {}, shift;
- $self->init(@_);
- $self;
- }
-
- #------------------------------
-
- =item init ARGS...
-
- I<Instance method, abstract, initiallizer.>
- This is called automatically by C<new()>, with the arguments given
- to C<new()>. The arguments are optional, and entirely up to the
- subclass. The default method does nothing,
-
- =cut
-
- sub init { 1 }
-
- #------------------------------
-
- =item as_lines
-
- I<Instance method.>
- Return the contents of the body as an array of lines (each terminated
- by a newline, with the possible exception of the final one).
- Returns empty on failure (NB: indistinguishable from an empty body!).
-
- Note: the default method gets the data via
- repeated getline() calls; your subclass might wish to override this.
-
- =cut
-
- sub as_lines {
- my $self = shift;
- my @lines;
- my $io = $self->open("r") || return ();
- push @lines, $_ while (defined($_ = $io->getline()));
- $io->close;
- @lines;
- }
-
- #------------------------------
-
- =item as_string
-
- I<Instance method.>
- Return the body data as a string (slurping it into core if necessary).
- Best not to do this unless you're I<sure> that the body is reasonably small!
- Returns empty string for an empty body, and undef on failure.
-
- Note: the default method uses print(), which gets the data via
- repeated read() calls; your subclass might wish to override this.
-
- =cut
-
- sub as_string {
- my $self = shift;
- my $str = '';
- my $out = new IO::Scalar \$str;
- $self->print($out);
- return $str;
- }
- *data = \&as_string; ### silenty invoke preferred usage
-
-
- #------------------------------
-
- =item binmode [ONOFF]
-
- I<Instance method.>
- With argument, flags whether or not open() should return an I/O handle
- which has binmode() activated. With no argument, just returns the
- current value.
-
- =cut
-
- sub binmode {
- my ($self, $onoff) = @_;
- $self->{MB_Binmode} = $onoff if (@_ > 1);
- $self->{MB_Binmode};
- }
-
- #------------------------------
-
- =item dup
-
- I<Instance method.>
- Duplicate the bodyhandle.
-
- I<Beware:> external data in bodyhandles is I<not> copied to new files!
- Changing the data in one body's data file, or purging that body,
- I<will> affect its duplicate. Bodies with in-core data probably need
- not worry.
-
- =cut
-
- sub dup {
- my $self = shift;
- bless { %$self }, ref($self); ### shallow copy ok for ::File and ::Scalar
- }
-
- #------------------------------
-
- =item open READWRITE
-
- I<Instance method, abstract.>
- This should do whatever is necessary to open the body for either
- writing (if READWRITE is "w") or reading (if mode is "r").
-
- This method is expected to return an "I/O handle" object on success,
- and undef on error. An I/O handle can be any object that supports a
- small set of standard methods for reading/writing data.
- See the IO::Handle class for an example.
-
- =cut
-
- sub open {
- undef;
- }
-
- #------------------------------
-
- =item path [PATH]
-
- I<Instance method.>
- If you're storing the body data externally (e.g., in a disk file), you'll
- want to give applications the ability to get at that data, for cleanup.
- This method should return the path to the data, or undef if there is none.
-
- Where appropriate, the path I<should> be a simple string, like a filename.
- With argument, sets the PATH, which should be undef if there is none.
-
- =cut
-
- sub path {
- my $self = shift;
- $self->{MB_Path} = shift if @_;
- $self->{MB_Path};
- }
-
- #------------------------------
-
- =item print FILEHANDLE
-
- I<Instance method.>
- Output the body data to the given filehandle, or to the currently-selected
- one if none is given.
-
- =cut
-
- sub print {
- my ($self, $fh) = @_;
- my $nread;
-
- ### Get output filehandle, and ensure that it's a printable object:
- $fh = IO::Wrap::wraphandle($fh || select);
-
- ### Write it:
- my $buf = '';
- my $io = $self->open("r") || return undef;
- $fh->print($buf) while ($nread = $io->read($buf, 2048));
- $io->close;
- return defined($nread); ### how'd we do?
- }
-
- #------------------------------
-
- =item purge
-
- I<Instance method, abstract.>
- Remove any data which resides external to the program (e.g., in disk files).
- Immediately after a purge(), the path() should return undef to indicate
- that the external data is no longer available.
-
- =cut
-
- sub purge {
- 1;
- }
-
-
-
- =back
-
- =head1 SUBCLASSES
-
- The following built-in classes are provided:
-
- Body Stores body When open()ed,
- class: data in: returns:
- --------------------------------------------------------
- MIME::Body::File disk file IO::Handle
- MIME::Body::Scalar scalar IO::Scalar
- MIME::Body::InCore scalar array IO::ScalarArray
-
- =cut
-
-
- #------------------------------------------------------------
- package MIME::Body::File;
- #------------------------------------------------------------
-
- =head2 MIME::Body::File
-
- A body class that stores the data in a disk file.
- The I/O handle is a wrapped filehandle. Invoke the constructor as:
-
- $body = new MIME::Body::File "/path/to/file";
-
- In this case, the C<path()> method would return the given path,
- so you I<could> say:
-
- if (defined($body->path)) {
- open BODY, $body->path or die "open: $!";
- while (<BODY>) {
- ### do stuff
- }
- close BODY;
- }
-
- But you're best off not doing this.
-
- =cut
-
-
- ### Pragmas:
- use vars qw(@ISA);
- use strict;
-
- ### System modules:
- require FileHandle;
-
- ### Kit modules:
- use MIME::Tools qw(whine);
- use IO::Wrap;
-
- @ISA = qw(MIME::Body);
-
-
- #------------------------------
- # init PATH
- #------------------------------
- sub init {
- my ($self, $path) = @_;
- $self->path($path); ### use it as-is
- $self;
- }
-
- #------------------------------
- # open READWRITE
- #------------------------------
- sub open {
- my ($self, $mode) = @_;
- my $IO;
- my $path = $self->path;
- if ($mode eq 'w') { ### writing
- $IO = FileHandle->new(">$path") || die "write-open $path: $!";
- }
- elsif ($mode eq 'r') { ### reading
- $IO = FileHandle->new("<$path") || die "read-open $path: $!";
- }
- else {
- die "bad mode: '$mode'";
- }
- binmode($IO) if $self->binmode; ### set binary read/write mode?
- return (IO::Wrap::wraphandle($IO)); ### wrap if old FileHandle class
- }
-
- #------------------------------
- # purge
- #------------------------------
- # Unlink the path (and undefine it).
- #
- sub purge {
- my $self = shift;
- if (defined($self->path)) {
- unlink $self->path or whine "couldn't unlink ".$self->path.": $!";
- $self->path(undef);
- }
- 1;
- }
-
-
-
-
- #------------------------------------------------------------
- package MIME::Body::Scalar;
- #------------------------------------------------------------
-
- =head2 MIME::Body::Scalar
-
- A body class that stores the data in-core, in a simple scalar.
- Invoke the constructor as:
-
- $body = new MIME::Body::Scalar \$string;
-
- A single scalar argument sets the body to that value, exactly as though
- you'd opened for the body for writing, written the value,
- and closed the body again:
-
- $body = new MIME::Body::Scalar "Line 1\nLine 2\nLine 3";
-
- A single array reference sets the body to the result of joining all the
- elements of that array together:
-
- $body = new MIME::Body::Scalar ["Line 1\n",
- "Line 2\n",
- "Line 3"];
-
- Uses B<IO::Scalar> as the I/O handle.
-
- =cut
-
- use vars qw(@ISA);
- use strict;
-
- require FileHandle;
-
- use IO::Scalar;
- use Carp;
-
- @ISA = qw(MIME::Body);
-
-
- #------------------------------
- # init DATA
- #------------------------------
- sub init {
- my ($self, $data) = @_;
- $data = join('', @$data) if (ref($data) && (ref($data) eq 'ARRAY'));
- $self->{MBS_Data} = (defined($data) ? $data : '');
- $self;
- }
-
- #------------------------------
- # as_string
- #------------------------------
- sub as_string {
- shift->{MBS_Data};
- }
-
- #------------------------------
- # open READWRITE
- #------------------------------
- sub open {
- my ($self, $mode) = @_;
- $self->{MBS_Data} = '' if ($mode eq 'w'); ### writing
- return new IO::Scalar \($self->{MBS_Data});
- }
-
-
-
-
-
- #------------------------------------------------------------
- package MIME::Body::InCore;
- #------------------------------------------------------------
-
- =head2 MIME::Body::InCore
-
- A body class that stores the data in-core.
- Invoke the constructor as:
-
- $body = new MIME::Body::InCore \$string;
- $body = new MIME::Body::InCore $string;
- $body = new MIME::Body::InCore \@stringarray
-
- A simple scalar argument sets the body to that value, exactly as though
- you'd opened for the body for writing, written the value,
- and closed the body again:
-
- $body = new MIME::Body::InCore "Line 1\nLine 2\nLine 3";
-
- A single array reference sets the body to the concatenation of all
- scalars that it holds:
-
- $body = new MIME::Body::InCore ["Line 1\n",
- "Line 2\n",
- "Line 3"];
-
- Uses B<IO::ScalarArray> as the I/O handle.
-
- =cut
-
- use vars qw(@ISA);
- use strict;
-
- require FileHandle;
-
- use IO::ScalarArray;
- use Carp;
-
- @ISA = qw(MIME::Body);
-
-
- #------------------------------
- # init DATA
- #------------------------------
- sub init {
- my ($self, $data) = @_;
- if (!defined($data)) { ### nothing
- $self->{MBC_Data} = [];
- }
- elsif (!ref($data)) { ### simple scalar
- $self->{MBC_Data} = [ $data ];
- }
- elsif (ref($data) eq 'SCALAR') {
- $self->{MBC_Data} = [ $$data ];
- }
- elsif (ref($data) eq 'ARRAY') {
- $self->{MBC_Data} = $data;
- }
- else {
- croak "I can't handle DATA which is a ".ref($data)."\n";
- }
- $self;
- }
-
- #------------------------------
- # as_string
- #------------------------------
- sub as_string {
- my $self = shift;
- return join '', @{$self->{MBC_Data}};
- }
-
- #------------------------------
- # open READWRITE
- #------------------------------
- sub open {
- my ($self, $mode) = @_;
- $self->{MBC_Data} = [] if ($mode eq 'w'); ### writing
- return new IO::ScalarArray $self->{MBC_Data};
- }
-
-
- 1;
- __END__
-
-
- #------------------------------
-
- =head2 Defining your own subclasses
-
- So you're not happy with files and scalar-arrays?
- No problem: just define your own MIME::Body subclass, and make a subclass
- of MIME::Parser or MIME::ParserBase which returns an instance of your
- body class whenever appropriate in the C<new_body_for(head)> method.
-
- Your "body" class must inherit from MIME::Body (or some subclass of it),
- and it must either provide (or inherit the default for) the following
- methods...
-
- The default inherited method I<should suffice> for all these:
-
- new
- binmode [ONOFF]
- path
-
- The default inherited method I<may suffice> for these, but perhaps
- there's a better implementation for your subclass.
-
- init ARGS...
- as_lines
- as_string
- dup
- print
- purge
-
- The default inherited method I<will probably not suffice> for these:
-
- open
-
-
-
- =head1 NOTES
-
- One reason I didn't just use FileHandle or IO::Handle objects for message
- bodies was that I wanted a "body" object to be a form of completely
- encapsulated program-persistent storage; that is, I wanted users
- to be able to write code like this...
-
- ### Get body handle from this MIME message, and read its data:
- $body = $entity->bodyhandle;
- $IO = $body->open("r");
- while (defined($_ = $IO->getline)) {
- print STDOUT $_;
- }
- $IO->close;
-
- ...without requiring that they know anything more about how the
- $body object is actually storing its data (disk file, scalar variable,
- array variable, or whatever).
-
- Storing the body of each MIME message in a persistently-open
- IO::Handle was a possibility, but it seemed like a bad idea,
- considering that a single multipart MIME message could easily suck up
- all the available file descriptors on some systems. This risk increases
- if the user application is processing more than one MIME entity at a time.
-
-
-
- =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.
-
- Thanks to Achim Bohnet for suggesting that MIME::Parser not be restricted
- to the use of FileHandles.
-
-
-
- =head1 VERSION
-
- $Revision: 5.403 $ $Date: 2000/11/04 19:54:46 $
-
- =cut
-
- #------------------------------
- 1;
-
-