File Coverage

blib/lib/Mail/Message/Field/Addresses.pm
Criterion Covered Total %
statement 115 126 91.2
branch 50 70 71.4
condition 12 20 60.0
subroutine 18 20 90.0
pod 11 12 91.6
total 206 248 83.0


line stmt bran cond sub pod time code
1             # Copyrights 2001-2023 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.03.
5             # This code is part of distribution Mail-Message. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Message::Field::Addresses;
10 22     22   798 use vars '$VERSION';
  22         52  
  22         1254  
11             $VERSION = '3.013';
12              
13 22     22   146 use base 'Mail::Message::Field::Structured';
  22         53  
  22         8399  
14              
15 22     22   163 use strict;
  22         50  
  22         455  
16 22     22   196 use warnings;
  22         73  
  22         619  
17              
18 22     22   10125 use Mail::Message::Field::AddrGroup;
  22         68  
  22         669  
19 22     22   9535 use Mail::Message::Field::Address;
  22         74  
  22         667  
20 22     22   146 use List::Util 'first';
  22         62  
  22         39208  
21              
22              
23             #------------------------------------------
24             # what is permitted for each field.
25              
26             my $address_list = {groups => 1, multi => 1};
27             my $mailbox_list = {multi => 1};
28             my $mailbox = {};
29              
30             my %accepted = # defaults to $address_list
31             ( from => $mailbox_list
32             , sender => $mailbox
33             );
34              
35             sub init($)
36 14     14 0 34 { my ($self, $args) = @_;
37              
38 14         150 $self->{MMFF_groups} = [];
39              
40 14         45 ( my $def = lc $args->{name} ) =~ s/^resent\-//;
41 14   66     71 $self->{MMFF_defaults} = $accepted{$def} || $address_list;
42              
43 14         25 my ($body, @body);
44 14 100       38 if($body = $args->{body})
45 13 50       46 { @body = ref $body eq 'ARRAY' ? @$body : ($body);
46 13 50       38 return () unless @body;
47             }
48              
49 14 50 33     68 if(@body > 1 || ref $body[0])
50 0         0 { $self->addAddress($_) foreach @body;
51 0         0 delete $args->{body};
52             }
53              
54 14 50       63 $self->SUPER::init($args) or return;
55 14         97 $self;
56             }
57              
58             #------------------------------------------
59              
60              
61             sub addAddress(@)
62 23     23 1 44 { my $self = shift;
63 23 50 33     106 my $email = @_ && ref $_[0] ? shift : undef;
64 23         85 my %args = @_;
65 23   100     85 my $group = delete $args{group} || '';
66              
67 23 50       51 $email = Mail::Message::Field::Address->new(%args)
68             unless defined $email;
69              
70 23   66     63 my $set = $self->group($group) || $self->addGroup(name => $group);
71 23         136 $set->addAddress($email);
72 23         47 $email;
73             }
74              
75              
76             sub addGroup(@)
77 13     13 1 50 { my $self = shift;
78 13 50       81 my $group = @_ == 1 ? shift
79             : Mail::Message::Field::AddrGroup->new(@_);
80              
81 13         936 push @{$self->{MMFF_groups}}, $group;
  13         36  
82 13         48 $group;
83             }
84              
85              
86             sub group($)
87 26     26 1 58 { my ($self, $name) = @_;
88 26 50       52 $name = '' unless defined $name;
89 26     23   170 first { lc($_->name) eq lc($name) } $self->groups;
  23         90  
90             }
91              
92              
93 43     43 1 1832 sub groups() { @{shift->{MMFF_groups}} }
  43         224  
94              
95              
96 0     0 1 0 sub groupNames() { map {$_->name} shift->groups }
  0         0  
97              
98              
99 9     9 1 2520 sub addresses() { map {$_->addresses} shift->groups }
  7         23  
100              
101              
102             sub addAttribute($;@)
103 0     0 1 0 { my $self = shift;
104 0         0 $self->log(ERROR => 'No attributes for address fields.');
105 0         0 $self;
106             }
107              
108             #------------------------------------------
109              
110              
111             sub parse($)
112 13     13 1 28 { my ($self, $string) = @_;
113 13         26 my ($group, $email) = ('', undef);
114 13         156 $string =~ s/\s+/ /gs;
115              
116             ADDRESS:
117 13         23 while(1)
118 47         167 { (my $comment, $string) = $self->consumeComment($string);
119 47         89 my $start_length = length $string;
120              
121 47 100       134 if($string =~ s/^\s*\;//s ) { $group = ''; next ADDRESS } # end group
  3         6  
  3         6  
122 44 100       144 if($string =~ s/^\s*\,//s ) { next ADDRESS} # end address
  12         33  
123              
124 32         74 (my $email, $string) = $self->consumeAddress($string);
125 32 100       77 if(defined $email)
126             { # Pattern starts with e-mail address
127 5         20 ($comment, $string) = $self->consumeComment($string);
128 5 50       16 $email->comment($comment) if defined $comment;
129             }
130             else
131             { # Pattern not plain address
132 27         76 my $real_phrase = $string =~ m/^\s*\"/;
133 27         75 my @words;
134              
135             # In rfc2822 obs-phrase, we can have more than one word with
136             # comments inbetween.
137             WORD:
138 27         42 while(1)
139 47         129 { (my $word, $string) = $self->consumePhrase($string);
140 47 100       116 defined $word or last;
141              
142 23 50       66 push @words, $word if length $word;
143 23         63 ($comment, $string) = $self->consumeComment($string);
144              
145 23 100       84 if($string =~ s/^\s*\://s )
146 3         7 { $group = $word;
147             # even empty groups must appear
148 3 50       7 $self->addGroup(name => $group) unless $self->group($group);
149 3         15 next ADDRESS;
150             }
151             }
152 24 100       71 my $phrase = @words ? join ' ', @words : undef;
153              
154 24         40 my $angle;
155 24 100       122 if($string =~ s/^\s*\<([^>]*)\>//s) { $angle = $1 }
  20 50       49  
    100          
156             elsif($real_phrase)
157 0 0       0 { $self->log(ERROR => "Ignore unrelated phrase `$1'")
158             if $string =~ s/^\s*\"(.*?)\r?\n//;
159 0         0 next ADDRESS;
160             }
161             elsif(defined $phrase)
162 1         4 { ($angle = $phrase) =~ s/\s+/./g;
163 1         3 undef $phrase;
164             }
165              
166 24         65 ($comment, $string) = $self->consumeComment($string);
167              
168             # remove obsoleted route info.
169 24 100       78 return 1 unless defined $angle;
170 21         38 $angle =~ s/^\@.*?\://;
171              
172 21         51 ($email, $angle) = $self->consumeAddress($angle
173             , phrase => $phrase, comment => $comment);
174             }
175              
176 26 100       105 $self->addAddress($email, group => $group) if defined $email;
177 26 100       212 return 1 if $string =~ m/^\s*$/s;
178              
179             # Do not get stuck on illegal characters
180 16 50       49 last if $start_length == length $string;
181             }
182              
183 0         0 $self->log(WARNING => 'Illegal part in address field '.$self->Name. ": $string\n");
184              
185 0         0 0;
186             }
187              
188             sub produceBody()
189 4     4 1 10 { my @groups = sort {$a->name cmp $b->name} shift->groups;
  4         25  
190              
191 4 50       31 @groups or return '';
192 4 100       20 @groups > 1 or return $groups[0]->string;
193              
194 2 50 33     9 my $plain
195             = $groups[0]->name eq '' && $groups[0]->addresses
196             ? (shift @groups)->string.','
197             : '';
198              
199 2         9 join ' ', $plain, (map $_->string, @groups);
200             }
201              
202              
203             sub consumeAddress($@)
204 53     53 1 125 { my ($self, $string, @options) = @_;
205              
206 53         79 my ($local, $shorter, $loccomment);
207 53 100       241 if($string =~ s/^\s*"((?:\\.|[^"])*)"\s*\@/@/)
208             { # local part is quoted-string rfc2822
209 1         7 ($local, $shorter) = ($1, $string);
210 1         6 $local =~ s/\\"/"/g;
211             }
212             else
213 52         152 { ($local, $shorter, $loccomment) = $self->consumeDotAtom($string);
214 52 100       148 $local =~ s/\s//g if defined $local;
215             }
216              
217 53 100 100     291 defined $local && $shorter =~ s/^\s*\@//
218             or return (undef, $string);
219            
220 23         73 (my $domain, $shorter, my $domcomment) = $self->consumeDomain($shorter);
221 23 50       72 defined $domain
222             or return (undef, $string);
223              
224             # loccomment and domcomment ignored
225 23         111 my $email = Mail::Message::Field::Address
226             ->new(username => $local, domain => $domain, @options);
227              
228 23         190 ($email, $shorter);
229             }
230              
231              
232             sub consumeDomain($)
233 23     23 1 49 { my ($self, $string) = @_;
234              
235 23 50       61 return ($self->stripCFWS($1), $string)
236             if $string =~ s/\s*(\[(?:[^[]\\]*|\\.)*\])//;
237              
238 23         58 my ($atom, $rest, $comment) = $self->consumeDotAtom($string);
239 23 50       69 $atom =~ s/\s//g if defined $atom;
240 23         68 ($atom, $rest, $comment);
241             }
242              
243             #------------------------------------------
244              
245              
246             1;