# Copyrights 2001-2007 by Mark Overmeer.
# For other contributors see ChangeLog.
# See the manual pages for details on the licensing terms.
# Pod stripped from pm file by OODoc 1.03.
use strict;
use warnings;
package Mail::Message::Field::Full;
use vars '$VERSION';
$VERSION = '2.079';
use base 'Mail::Message::Field';
use utf8;
use Encode ();
use MIME::QuotedPrint ();
use Storable 'dclone';
use Mail::Message::Field::Structured;
use Mail::Message::Field::Unstructured;
use Mail::Message::Field::Addresses;
use Mail::Message::Field::URIs;
my $atext = q[a-zA-Z0-9!#\$%&'*+\-\/=?^_`{|}~]; # from RFC
use overload '""' => sub { shift->decodedBody };
#------------------------------------------
my %implementation;
BEGIN {
$implementation{$_} = 'Addresses' foreach
qw/from to sender cc bcc reply-to envelope-to
resent-from resent-to resent-cc resent-bcc resent-reply-to
resent-sender
x-beenthere errors-to mail-follow-up x-loop delivered-to
original-sender x-original-sender/;
$implementation{$_} = 'URIs' foreach
qw/list-help list-post list-subscribe list-unsubscribe list-archive
list-owner/;
$implementation{$_} = 'Structured' foreach
qw/content-disposition content-type/;
# $implementation{$_} = 'Date' foreach
# qw/date resent-date/;
}
sub new($;$$@)
{ my $class = shift;
my $name = shift;
my $body = @_ % 2 ? shift : undef;
my %args = @_;
$body = delete $args{body} if defined $args{body};
unless(defined $body)
{ (my $n, $body) = split /\s*\:\s*/s, $name, 2;
$name = $n if defined $body;
}
return $class->SUPER::new(%args, name => $name, body => $body)
if $class ne __PACKAGE__;
# Look for best class to suit this field
my $myclass = 'Mail::Message::Field::'
. ($implementation{lc $name} || 'Unstructured');
$myclass->SUPER::new(%args, name => $name, body => $body);
}
sub init($)
{ my ($self, $args) = @_;
$self->SUPER::init($args);
$self->{MMFF_name} = $args->{name};
my $body = $args->{body};
if(!defined $body || !length $body || ref $body) { ; } # no body yet
elsif(index($body, "\n") >= 0)
{ $self->foldedBody($body) } # body is already folded
else { $self->unfoldedBody($body) } # body must be folded
$self;
}
sub clone() { dclone(shift) }
sub name() { lc shift->{MMFF_name}}
sub Name() { shift->{MMFF_name}}
sub folded()
{ my $self = shift;
return $self->{MMFF_name}.':'.$self->foldedBody
unless wantarray;
my @lines = $self->foldedBody;
my $first = $self->{MMFF_name}. ':'. shift @lines;
($first, @lines);
}
sub unfoldedBody($;$)
{ my ($self, $body) = (shift, shift);
if(defined $body)
{ $self->foldedBody(scalar $self->fold($self->{MMFF_name}, $body));
return $body;
}
$body = $self->foldedBody;
$body =~ s/^ //;
$body =~ s/\n//g;
$body;
}
sub foldedBody($)
{ my ($self, $body) = @_;
if(@_==2)
{ $self->parse($body);
$body =~ s/^\s*/ /;
$self->{MMFF_body} = $body;
}
elsif(defined($body = $self->{MMFF_body})) { ; }
else
{ # Create a new folded body from the parts.
$self->{MMFF_body} = $body
= $self->fold($self->{MMFF_name}, $self->produceBody);
}
wantarray ? (split /^/, $body) : $body;
}
#------------------------------------------
sub from($@)
{ my ($class, $field) = (shift, shift);
defined $field ? $class->new($field->Name, $field->foldedBody, @_) : ();
}
#------------------------------------------
sub decodedBody()
{ my $self = shift;
$self->decode($self->unfoldedBody, @_);
}
#------------------------------------------
sub createComment($@)
{ my ($thing, $comment) = (shift, shift);
$comment = $thing->encode($comment, @_)
if @_; # encoding required...
# Correct dangling parenthesis
local $_ = $comment; # work with a copy
s#\\[()]#xx#g; # remove escaped parens
s#[^()]#x#g; # remove other chars
while( s#\(([^()]*)\)#x$1x# ) {;} # remove pairs of parens
substr($comment, CORE::length($_), 0, '\\')
while s#[()][^()]*$##; # add escape before remaining parens
$comment =~ s#\\+$##; # backslash at end confuses
"($comment)";
}
sub createPhrase($)
{ my $self = shift;
local $_ = shift;
$_ = $self->encode($_, @_)
if @_; # encoding required...
if( m/[^$atext]/ )
{ s#\\#\\\\#g;
s#"#\\"#g;
$_ = qq["$_"];
}
$_;
}
sub beautify() { shift }
#------------------------------------------
sub encode($@)
{ my ($self, $utf8, %args) = @_;
my ($charset, $lang, $encoding);
if($charset = $args{charset})
{ $self->log(WARNING => "Illegal character in charset '$charset'")
if $charset =~ m/[\x00-\ ()<>@,;:"\/[\]?.=\\]/;
}
else { $charset = 'us-ascii' }
if($lang = $args{language})
{ $self->log(WARNING => "Illegal character in language '$lang'")
if $lang =~ m/[\x00-\ ()<>@,;:"\/[\]?.=\\]/;
}
if($encoding = $args{encoding})
{ unless($encoding =~ m/^[bBqQ]$/ )
{ $self->log(WARNING => "Illegal encoding '$encoding', used 'q'");
$encoding = 'q';
}
}
else { $encoding = 'q' }
my $encoded = Encode::encode($charset, $utf8, 0);
no utf8;
my $pre = '=?'. $charset. ($lang ? '*'.$lang : '') .'?'.$encoding.'?';
my $ready = '';
if(lc $encoding eq 'q')
{ # Quoted printable encoding
my $qp = $encoded;
$qp =~ s#([\x00-\x1F=\x7F-\xFF])#sprintf "=%02X", ord $1#ge;
return $qp # string only contains us-ascii?
if !$args{force} && $qp eq $utf8;
$qp =~ s#([_\?])#sprintf "=%02X", ord $1#ge;
$qp =~ s/ /_/g;
my $take = 70 - CORE::length($pre);
while(CORE::length($qp) > $take+1)
{ $qp =~ s#^(.{$take}.?.?[^=][^=])## or warn $qp;
$ready .= "$pre$1?= ";
}
$ready .= "$pre$qp?=" if CORE::length($qp);
}
else
{ # base64 encoding
require MIME::Base64;
my $maxchars = int((74-CORE::length($pre))/4) *4;
my $bq = MIME::Base64::encode_base64($encoded);
$bq =~ s/\s*//gs;
while(CORE::length($bq) > $maxchars)
{ $ready .= $pre . substr($bq, 0, $maxchars, '') . '?= ';
}
$ready .= "$pre$bq?=";
}
$ready;
}
sub _decoder($$$)
{ my ($charset, $encoding, $encoded) = @_;
$charset =~ s/\*[^*]+$//; # string language, not used
$charset ||= 'us-ascii';
my $decoded;
if(lc($encoding) eq 'q')
{ # Quoted-printable encoded
$encoded =~ s/_/ /g;
$decoded = MIME::QuotedPrint::decode_qp($encoded);
}
elsif(lc($encoding) eq 'b')
{ # Base64 encoded
require MIME::Base64;
$decoded = MIME::Base64::decode_base64($encoded);
}
else
{ # unknown encodings ignored
return $encoded;
}
Encode::decode($charset, $decoded, 0);
}
sub decode($@)
{ my ($self, $encoded, %args) = @_;
if(defined $args{is_text} ? $args{is_text} : 1)
{ # in text, blanks between encoding must be removed, but otherwise kept :(
# dirty trick to get this done: add an explicit blank.
$encoded =~ s/\?\=\s(?!\s*\=\?|$)/_?= /gs;
}
$encoded =~ s/\=\?([^?\s]*)\?([^?\s]*)\?([^?\s]*)\?\=\s*/_decoder($1,$2,$3)/gse;
$encoded;
}
#------------------------------------------
sub parse($) { shift }
sub consumePhrase($)
{ my ($thing, $string) = @_;
if($string =~ s/^\s*\" ((?:[^"\r\n\\]*|\\.)*) (?:\"|\s*$)//x )
{ (my $phrase = $1) =~ s/\\\"/"/g;
return ($phrase, $string);
}
if($string =~ s/^\s*([$atext\ \t.]+)//o )
{ (my $phrase = $1) =~ s/\s+$//;
return CORE::length($phrase) ? ($phrase, $string) : (undef, $_[1]);
}
(undef, $string);
}
sub consumeComment($)
{ my ($thing, $string) = @_;
return (undef, $string)
unless $string =~ s/^\s*\(((?:[^)\\]+|\\.)*)\)//;
my $comment = $1;
while(1)
{ (my $count = $comment) =~ s/\\./xx/g;
last if $count =~ tr/(// == $count =~ tr/)//;
return (undef, $_[1])
unless $string =~ s/^((?:[^)\\]+|\\.)*)\)//;
$comment .= ')'.$1;
}
$comment =~ s/\\([()])/$1/g;
($comment, $string);
}
sub consumeDotAtom($)
{ my ($self, $string) = @_;
my ($atom, $comment);
while(1)
{ (my $c, $string) = $self->consumeComment($string);
if(defined $c) { $comment .= $c; next }
last unless $string =~ s/^\s*([$atext]+(?:\.[$atext]+)*)//o;
$atom .= $1;
}
($atom, $string, $comment);
}
sub produceBody() { $_[0]->{MMFF_body} }
#------------------------------------------
1;
syntax highlighted by Code2HTML, v. 0.9.1