Spaces:
Running
Running
# Copyrights 1995-2018 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 2.02. | |
# This code is part of the bundle MailTools. Meta-POD processed with | |
# OODoc into POD and HTML manual-pages. See README.md for Copyright. | |
# Licensed under the same terms as Perl itself. | |
package Mail::Address; | |
use vars '$VERSION'; | |
$VERSION = '2.20'; | |
use strict; | |
use Carp; | |
# use locale; removed in version 1.78, because it causes taint problems | |
sub Version { our $VERSION } | |
# given a comment, attempt to extract a person's name | |
sub _extract_name | |
{ # This function can be called as method as well | |
my $self = @_ && ref $_[0] ? shift : undef; | |
local $_ = shift | |
or return ''; | |
# Using encodings, too hard. See Mail::Message::Field::Full. | |
return '' if m/\=\?.*?\?\=/; | |
# trim whitespace | |
s/^\s+//; | |
s/\s+$//; | |
s/\s+/ /; | |
# Disregard numeric names (e.g. [email protected]) | |
return "" if /^[\d ]+$/; | |
s/^\((.*)\)$/$1/; # remove outermost parenthesis | |
s/^"(.*)"$/$1/; # remove outer quotation marks | |
s/\(.*?\)//g; # remove minimal embedded comments | |
s/\\//g; # remove all escapes | |
s/^"(.*)"$/$1/; # remove internal quotation marks | |
s/^([^\s]+) ?, ?(.*)$/$2 $1/; # reverse "Last, First M." if applicable | |
s/,.*//; | |
# Change casing only when the name contains only upper or only | |
# lower cased characters. | |
unless( m/[A-Z]/ && m/[a-z]/ ) | |
{ # Set the case of the name to first char upper rest lower | |
s/\b(\w+)/\L\u$1/igo; # Upcase first letter on name | |
s/\bMc(\w)/Mc\u$1/igo; # Scottish names such as 'McLeod' | |
s/\bo'(\w)/O'\u$1/igo; # Irish names such as 'O'Malley, O'Reilly' | |
s/\b(x*(ix)?v*(iv)?i*)\b/\U$1/igo; # Roman numerals, eg 'Level III Support' | |
} | |
# some cleanup | |
s/\[[^\]]*\]//g; | |
s/(^[\s'"]+|[\s'"]+$)//g; | |
s/\s{2,}/ /g; | |
$_; | |
} | |
sub _tokenise | |
{ local $_ = join ',', @_; | |
my (@words,$snippet,$field); | |
s/\A\s+//; | |
s/[\r\n]+/ /g; | |
while ($_ ne '') | |
{ $field = ''; | |
if(s/^\s*\(/(/ ) # (...) | |
{ my $depth = 0; | |
PAREN: while(s/^(\(([^\(\)\\]|\\.)*)//) | |
{ $field .= $1; | |
$depth++; | |
while(s/^(([^\(\)\\]|\\.)*\)\s*)//) | |
{ $field .= $1; | |
last PAREN unless --$depth; | |
$field .= $1 if s/^(([^\(\)\\]|\\.)+)//; | |
} | |
} | |
carp "Unmatched () '$field' '$_'" | |
if $depth; | |
$field =~ s/\s+\Z//; | |
push @words, $field; | |
next; | |
} | |
if( s/^("(?:[^"\\]+|\\.)*")\s*// # "..." | |
|| s/^(\[(?:[^\]\\]+|\\.)*\])\s*// # [...] | |
|| s/^([^\s()<>\@,;:\\".[\]]+)\s*// | |
|| s/^([()<>\@,;:\\".[\]])\s*// | |
) | |
{ push @words, $1; | |
next; | |
} | |
croak "Unrecognised line: $_"; | |
} | |
push @words, ","; | |
\@words; | |
} | |
sub _find_next | |
{ my ($idx, $tokens, $len) = @_; | |
while($idx < $len) | |
{ my $c = $tokens->[$idx]; | |
return $c if $c eq ',' || $c eq ';' || $c eq '<'; | |
$idx++; | |
} | |
""; | |
} | |
sub _complete | |
{ my ($class, $phrase, $address, $comment) = @_; | |
@$phrase || @$comment || @$address | |
or return undef; | |
my $o = $class->new(join(" ",@$phrase), join("",@$address), join(" ",@$comment)); | |
@$phrase = @$address = @$comment = (); | |
$o; | |
} | |
#------------ | |
sub new(@) | |
{ my $class = shift; | |
bless [@_], $class; | |
} | |
sub parse(@) | |
{ my $class = shift; | |
my @line = grep {defined} @_; | |
my $line = join '', @line; | |
my (@phrase, @comment, @address, @objs); | |
my ($depth, $idx) = (0, 0); | |
my $tokens = _tokenise @line; | |
my $len = @$tokens; | |
my $next = _find_next $idx, $tokens, $len; | |
local $_; | |
for(my $idx = 0; $idx < $len; $idx++) | |
{ $_ = $tokens->[$idx]; | |
if(substr($_,0,1) eq '(') { push @comment, $_ } | |
elsif($_ eq '<') { $depth++ } | |
elsif($_ eq '>') { $depth-- if $depth } | |
elsif($_ eq ',' || $_ eq ';') | |
{ warn "Unmatched '<>' in $line" if $depth; | |
my $o = $class->_complete(\@phrase, \@address, \@comment); | |
push @objs, $o if defined $o; | |
$depth = 0; | |
$next = _find_next $idx+1, $tokens, $len; | |
} | |
elsif($depth) { push @address, $_ } | |
elsif($next eq '<') { push @phrase, $_ } | |
elsif( /^[.\@:;]$/ || !@address || $address[-1] =~ /^[.\@:;]$/ ) | |
{ push @address, $_ } | |
else | |
{ warn "Unmatched '<>' in $line" if $depth; | |
my $o = $class->_complete(\@phrase, \@address, \@comment); | |
push @objs, $o if defined $o; | |
$depth = 0; | |
push @address, $_; | |
} | |
} | |
@objs; | |
} | |
#------------ | |
sub phrase { shift->set_or_get(0, @_) } | |
sub address { shift->set_or_get(1, @_) } | |
sub comment { shift->set_or_get(2, @_) } | |
sub set_or_get($) | |
{ my ($self, $i) = (shift, shift); | |
@_ or return $self->[$i]; | |
my $val = $self->[$i]; | |
$self->[$i] = shift if @_; | |
$val; | |
} | |
my $atext = '[\-\w !#$%&\'*+/=?^`{|}~]'; | |
sub format | |
{ my @addrs; | |
foreach (@_) | |
{ my ($phrase, $email, $comment) = @$_; | |
my @addr; | |
if(defined $phrase && length $phrase) | |
{ push @addr | |
, $phrase =~ /^(?:\s*$atext\s*)+$/o ? $phrase | |
: $phrase =~ /(?<!\\)"/ ? $phrase | |
: qq("$phrase"); | |
push @addr, "<$email>" | |
if defined $email && length $email; | |
} | |
elsif(defined $email && length $email) | |
{ push @addr, $email; | |
} | |
if(defined $comment && $comment =~ /\S/) | |
{ $comment =~ s/^\s*\(?/(/; | |
$comment =~ s/\)?\s*$/)/; | |
} | |
push @addr, $comment | |
if defined $comment && length $comment; | |
push @addrs, join(" ", @addr) | |
if @addr; | |
} | |
join ", ", @addrs; | |
} | |
#------------ | |
sub name | |
{ my $self = shift; | |
my $phrase = $self->phrase; | |
my $addr = $self->address; | |
$phrase = $self->comment | |
unless defined $phrase && length $phrase; | |
my $name = $self->_extract_name($phrase); | |
# first.last@domain address | |
if($name eq '' && $addr =~ /([^\%\.\@_]+([\._][^\%\.\@_]+)+)[\@\%]/) | |
{ ($name = $1) =~ s/[\._]+/ /g; | |
$name = _extract_name $name; | |
} | |
if($name eq '' && $addr =~ m#/g=#i) # X400 style address | |
{ my ($f) = $addr =~ m#g=([^/]*)#i; | |
my ($l) = $addr =~ m#s=([^/]*)#i; | |
$name = _extract_name "$f $l"; | |
} | |
length $name ? $name : undef; | |
} | |
sub host | |
{ my $addr = shift->address || ''; | |
my $i = rindex $addr, '@'; | |
$i >= 0 ? substr($addr, $i+1) : undef; | |
} | |
sub user | |
{ my $addr = shift->address || ''; | |
my $i = rindex $addr, '@'; | |
$i >= 0 ? substr($addr,0,$i) : $addr; | |
} | |
1; | |