File Coverage

blib/lib/Mail/Message/Field/Addresses.pm
Criterion Covered Total %
statement 117 126 92.8
branch 47 64 73.4
condition 12 22 54.5
subroutine 19 21 90.4
pod 11 12 91.6
total 206 245 84.0


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Message version 4.04.
2             # The POD got stripped from this file by OODoc version 3.06.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2026 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Message::Field::Addresses;{
13             our $VERSION = '4.04';
14             }
15              
16 28     28   1142 use parent 'Mail::Message::Field::Structured';
  28         61  
  28         239  
17              
18 28     28   2301 use strict;
  28         62  
  28         749  
19 28     28   133 use warnings;
  28         61  
  28         1871  
20              
21 28     28   289 use Log::Report 'mail-message', import => [ qw/__x error info warning/ ];
  28         62  
  28         227  
22              
23 28     28   23233 use Mail::Message::Field::AddrGroup ();
  28         112  
  28         926  
24 28     28   16629 use Mail::Message::Field::Address ();
  28         219  
  28         971  
25              
26 28     28   215 use List::Util qw/first/;
  28         58  
  28         2418  
27 28     28   247 use Scalar::Util qw/blessed/;
  28         65  
  28         94177  
28              
29             #--------------------
30              
31             # what is permitted for each field.
32              
33             my $address_list = +{ groups => 1, multi => 1 };
34             my $mailbox_list = +{ multi => 1 };
35             my $mailbox = +{ };
36              
37             my %accepted = ( # defaults to $address_list
38             from => $mailbox_list,
39             sender => $mailbox,
40             );
41              
42             sub init($)
43 36     36 0 117 { my ($self, $args) = @_;
44              
45 36         478 $self->{MMFF_groups} = [];
46              
47 36         145 my $def = lc $args->{name} =~ s/^resent\-//r;
48 36   66     232 $self->{MMFF_defaults} = $accepted{$def} || $address_list;
49              
50 36         71 my ($body, @body);
51 36 100       128 if($body = $args->{body})
52 35 50       165 { @body = ref $body eq 'ARRAY' ? @$body : ($body);
53 35 50       106 @body or return ();
54             }
55              
56 36 50 33     374 if(@body > 1 || ref $body[0])
57 0         0 { $self->addAddress($_) for @body;
58 0         0 delete $args->{body};
59             }
60              
61 36         270 $self->SUPER::init($args);
62             }
63              
64             #--------------------
65              
66             sub addAddress(@)
67 42     42 1 87 { my $self = shift;
68 42 50       135 my $email = blessed $_[0] ? shift : undef;
69 42         2376 my %args = @_;
70 42   50     141 my $group = delete $args{group} // '';
71              
72 42   33     126 $email //= Mail::Message::Field::Address->new(%args);
73              
74 42   66     132 my $set = $self->group($group) // $self->addGroup(name => $group);
75 42         458 $set->addAddress($email);
76 42         102 $email;
77             }
78              
79              
80             sub addGroup(@)
81 35     35 1 129 { my $self = shift;
82 35 50       272 my $group = @_ == 1 ? shift : Mail::Message::Field::AddrGroup->new(@_);
83 35         3192 push @{$self->{MMFF_groups}}, $group;
  35         110  
84 35         133 $group;
85             }
86              
87              
88             sub group($)
89 55     55 1 177 { my ($self, $name) = @_;
90 55   50     187 $name //= '';
91 55     30   382 first { lc($_->name) eq lc($name) } $self->groups;
  30         145  
92             }
93              
94              
95 94     94 1 2368 sub groups() { @{ $_[0]->{MMFF_groups}} }
  94         634  
96              
97              
98 0     0 1 0 sub groupNames() { map $_->name, $_[0]->groups }
99              
100              
101 31     31 1 14186 sub addresses() { map $_->addresses, $_[0]->groups }
102              
103              
104             sub addAttribute($;@)
105 0     0 1 0 { my $self = shift;
106 0         0 error __x"no attributes for address fields.";
107             }
108              
109             #--------------------
110              
111             sub parse($)
112 35     35 1 95 { my ($self, $string) = @_;
113 35         91 my ($group, $email) = ('', undef);
114 35         359 $string =~ s/\s+/ /gs;
115              
116             ADDRESS:
117 35         73 while(1)
118 81         342 { (my $comment, $string) = $self->consumeComment($string);
119 81         195 my $start_length = length $string;
120              
121 81 100       287 if($string =~ s/^\s*\;//s ) { $group = ''; next ADDRESS } # end group
  3         8  
  3         10  
122 78 100       282 if($string =~ s/^\s*\,//s ) { next ADDRESS} # end address
  12         31  
123              
124 66         195 (my $email, $string) = $self->consumeAddress($string);
125 66 100       166 if(defined $email)
126             { # Pattern starts with e-mail address
127 13         58 ($comment, $string) = $self->consumeComment($string);
128 13 50       45 $email->comment($comment) if defined $comment;
129             }
130             else
131             { # Pattern not plain address
132 53         181 my $real_phrase = $string =~ m/^\s*\"/;
133 53         105 my @words;
134              
135             # In rfc2822 obs-phrase, we can have more than one word with
136             # comments inbetween.
137             WORD:
138 53         86 while(1)
139 90         334 { (my $word, $string) = $self->consumePhrase($string);
140 90 100       246 defined $word or last;
141              
142 50 50       185 push @words, $word if length $word;
143 50         173 ($comment, $string) = $self->consumeComment($string);
144              
145 50 100       225 if($string =~ s/^\s*\://s )
146 13         28 { $group = $word;
147             # even empty groups must appear
148 13 50       42 $self->addGroup(name => $group) unless $self->group($group);
149 13         95 next ADDRESS;
150             }
151             }
152 40 100       134 my $phrase = @words ? join ' ', @words : undef;
153              
154 40         74 my $angle;
155 40 100       277 if($string =~ s/^\s*\<([^>]*)\>//s) { $angle = $1 }
  31 50       108  
    100          
156             elsif($real_phrase)
157 0 0       0 { warning __x"ignoring addressless phrase '{phrase}'.", phrase => $1
158             if $string =~ s/^\s*\"(.*?)\r?\n//;
159 0         0 next ADDRESS;
160             }
161             elsif(defined $phrase)
162 4         17 { ($angle = $phrase) =~ s/\s+/./g;
163 4         10 undef $phrase;
164             }
165              
166 40         163 ($comment, $string) = $self->consumeComment($string);
167              
168             # remove obsoleted route info.
169 40 100       115 defined $angle or return 1;
170 35         84 $angle =~ s/^\@.*?\://;
171              
172 35         106 ($email, $angle) = $self->consumeAddress($angle, phrase => $phrase, comment => $comment);
173             }
174              
175 48 100       259 $self->addAddress($email, group => $group) if defined $email;
176 48 100       337 return 1 if $string =~ m/^\s*$/s;
177              
178             # Do not get stuck on illegal characters
179 18 50       57 last if $start_length == length $string;
180             }
181              
182 0         0 warning __x"illegal part in address field {name}: {part}.", name => $self->Name, part => $string;
183 0         0 0;
184             }
185              
186             sub produceBody()
187 4     4 1 9 { my $self = shift;
188 4         16 my @groups = sort { $a->name cmp $b->name } $self->groups;
  4         32  
189              
190 4 50       36 @groups or return '';
191 4 100       22 @groups > 1 or return $groups[0]->string;
192              
193 2 50 33     7 my $plain = $groups[0]->name eq '' && $groups[0]->addresses ? (shift @groups)->string.',' : '';
194 2         12 join ' ', $plain, (map $_->string, @groups);
195             }
196              
197              
198             sub consumeAddress($@)
199 101     101 1 304 { my ($self, $string, @options) = @_;
200              
201 101         276 my ($local, $shorter, $loccomment);
202 101 100       443 if($string =~ s/^\s*"((?:\\.|[^"])*)"\s*\@/@/)
203             { # local part is quoted-string rfc2822
204 1         5 ($local, $shorter) = ($1, $string);
205 1         6 $local =~ s/\\"/"/g;
206             }
207             else
208 100         309 { ($local, $shorter, $loccomment) = $self->consumeDotAtom($string);
209 100 100       398 $local =~ s/\s//g if defined $local;
210             }
211              
212 101 100 100     795 defined $local && $shorter =~ s/^\s*\@//
213             or return (undef, $string);
214              
215 42         143 (my $domain, $shorter, my $domcomment) = $self->consumeDomain($shorter);
216 42 50       114 defined $domain
217             or return (undef, $string);
218              
219             # loccomment and domcomment ignored
220 42         249 my $email = Mail::Message::Field::Address->new(username => $local, domain => $domain, @options);
221 42         468 ($email, $shorter);
222             }
223              
224              
225             sub consumeDomain($)
226 42     42 1 139 { my ($self, $string) = @_;
227              
228 42 50       125 return ($self->stripCFWS($1), $string)
229             if $string =~ s/\s*(\[(?:[^[]\\]*|\\.)*\])//;
230              
231 42         142 my ($atom, $rest, $comment) = $self->consumeDotAtom($string);
232 42 50       161 $atom =~ s/\s//g if defined $atom;
233 42         148 ($atom, $rest, $comment);
234             }
235              
236             #--------------------
237              
238             1;