File Coverage

blib/lib/Mail/Message/Field.pm
Criterion Covered Total %
statement 191 212 90.0
branch 85 110 77.2
condition 26 33 78.7
subroutine 39 45 86.6
pod 28 29 96.5
total 369 429 86.0


line stmt bran cond sub pod time code
1             # Copyrights 2001-2022 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;
10 50     50   1056 use vars '$VERSION';
  50         85  
  50         5417  
11             $VERSION = '3.012';
12              
13 50     50   284 use base 'Mail::Reporter';
  50         1422  
  50         10707  
14              
15 50     50   307 use strict;
  50         110  
  50         985  
16 50     50   218 use warnings;
  50         1464  
  50         1226  
17              
18 50     50   1492 use Carp;
  50         106  
  50         2714  
19 50     50   22929 use Mail::Address;
  50         106639  
  50         1702  
20 50     50   23830 use Date::Format 'strftime';
  50         350795  
  50         3646  
21 50     50   8502 use IO::Handle;
  50         84885  
  50         8322  
22              
23             our %_structured; # not to be used directly: call isStructured!
24             my $default_wrap_length = 78;
25              
26              
27             use overload
28 377     377   978 qq("") => sub { $_[0]->unfoldedBody }
29 2 50   2   26 , '0+' => sub { $_[0]->toInt || 0 }
30 2299     2299   4946 , bool => sub {1}
31 142     142   8671 , cmp => sub { $_[0]->unfoldedBody cmp "$_[1]" }
32 10 100   10   174 , '<=>' => sub { $_[2] ? $_[1] <=> $_[0]->toInt : $_[0]->toInt <=> $_[1] }
33 50     50   15302 , fallback => 1;
  50         12385  
  50         937  
34              
35             #------------------------------------------
36              
37              
38             sub new(@)
39 599     599 1 2550 { my $class = shift;
40 599 100       1358 if($class eq __PACKAGE__) # bootstrap
41 468         2579 { require Mail::Message::Field::Fast;
42 468         1390 return Mail::Message::Field::Fast->new(@_);
43             }
44 131         549 $class->SUPER::new(@_);
45             }
46              
47              
48              
49             #------------------------------------------
50              
51              
52 161     161 1 354 sub length { length shift->folded }
53              
54              
55             BEGIN {
56 50     50   11803 %_structured = map { (lc($_) => 1) }
  1350         136885  
57             qw/To Cc Bcc From Date Reply-To Sender
58             Resent-Date Resent-From Resent-Sender Resent-To Return-Path
59             List-Help List-Post List-Unsubscribe Mailing-List
60             Received References Message-ID In-Reply-To
61             Content-Type Content-Disposition Content-ID
62             Delivered-To
63             MIME-Version
64             Precedence
65             Status/;
66             }
67              
68             sub isStructured(;$)
69 697 50   697 1 1947 { my $name = ref $_[0] ? shift->name : $_[1];
70 697         2538 exists $_structured{lc $name};
71             }
72              
73              
74             sub print(;$)
75 21     21 1 33 { my $self = shift;
76 21   33     47 my $fh = shift || select;
77 21         110 $fh->print(scalar $self->folded);
78             }
79              
80              
81 27     27 0 1444 sub toString(;$) {shift->string(@_)}
82             sub string(;$)
83 93     93 1 8426 { my $self = shift;
84 93 100       282 return $self->folded unless @_;
85              
86 5   33     15 my $wrap = shift || $default_wrap_length;
87 5         13 my $name = $self->Name;
88 5         14 my @lines = $self->fold($name, $self->unfoldedBody, $wrap);
89 5         16 $lines[0] = $name . ':' . $lines[0];
90 5 50       23 wantarray ? @lines : join('', @lines);
91             }
92              
93              
94             sub toDisclose()
95 0     0 1 0 { shift->name !~ m!^(?: (?:x-)?status
96             | (?:resent-)?bcc
97             | Content-Length
98             | x-spam-
99             ) $!x;
100             }
101              
102              
103 122     122 1 309 sub nrLines() { my @l = shift->foldedBody; scalar @l }
  122         461  
104              
105              
106             *size = \&length;
107              
108             #------------------------------------------
109              
110              
111             # attempt to change the case of a tag to that required by RFC822. That
112             # being all characters are lowercase except the first of each
113             # word. Also if the word is an `acronym' then all characters are
114             # uppercase. We, rather arbitrarily, decide that a word is an acronym
115             # if it does not contain a vowel and isn't the well-known 'Cc' or
116             # 'Bcc' headers.
117              
118             my %wf_lookup
119             = qw/mime MIME ldap LDAP soap SOAP swe SWE
120             bcc Bcc cc Cc id ID/;
121              
122             sub wellformedName(;$)
123 27     27 1 3409 { my $thing = shift;
124 27 100       75 my $name = @_ ? shift : $thing->name;
125              
126             join '-',
127 27 100       80 map { $wf_lookup{lc $_} || ( /[aeiouyAEIOUY]/ ? ucfirst lc : uc ) }
  42 100       274  
128             split /\-/, $name, -1;
129             }
130              
131             #------------------------------------------
132              
133              
134 0     0 1 0 sub folded { shift->notImplemented }
135              
136              
137             sub body()
138 665     665 1 4730 { my $self = shift;
139 665         1556 my $body = $self->unfoldedBody;
140 665 100       1521 return $body unless $self->isStructured;
141              
142 467         3105 my ($first) = $body =~ m/^((?:"[^"]*"|'[^']*'|[^;])*)/;
143 467         972 $first =~ s/\s+$//;
144 467         1930 $first;
145             }
146              
147              
148 0     0 1 0 sub foldedBody { shift->notImplemented }
149              
150              
151 0     0 1 0 sub unfoldedBody { shift->notImplemented }
152              
153              
154             sub stripCFWS($)
155 55     55 1 11774 { my $thing = shift;
156              
157             # get (folded) data
158 55 100       122 my $string = @_ ? shift : $thing->foldedBody;
159              
160             # remove comments
161 55         69 my $r = '';
162 55         68 my $in_dquotes = 0;
163 55         58 my $open_paren = 0;
164              
165 55         291 my @s = split m/([()"])/, $string;
166 55         125 while(@s)
167 472         548 { my $s = shift @s;
168              
169 472 100 100     1695 if(CORE::length($r)&& substr($r, -1) eq "\\") { $r .= $s }
  4 100 100     9  
    100 100        
    100          
    100          
170 40         55 elsif($s eq '"') { $in_dquotes = not $in_dquotes; $r .= $s }
  40         59  
171 80         129 elsif($s eq '(' && !$in_dquotes) { $open_paren++ }
172 80         118 elsif($s eq ')' && !$in_dquotes) { $open_paren-- }
173             elsif($open_paren) {} # in comment
174 156         293 else { $r .= $s }
175             }
176              
177             # beautify and unfold at the same time
178 55         97 for($r)
179 55         238 { s/\s+/ /gs;
180 55         162 s/\s+$//;
181 55         126 s/^\s+//;
182             }
183              
184 55         154 $r;
185             }
186              
187             #------------------------------------------
188              
189              
190             sub comment(;$)
191 31     31 1 57 { my $self = shift;
192 31 100       55 return undef unless $self->isStructured;
193              
194 27         64 my $body = $self->unfoldedBody;
195              
196 27 100       59 if(@_)
197 2         5 { my $comment = shift;
198 2         5 $body =~ s/\s*\;.*//;
199 2 50 33     13 $body .= "; $comment" if defined $comment && CORE::length($comment);
200 2         7 $self->unfoldedBody($body);
201 2         6 return $comment;
202             }
203            
204 25 100       165 $body =~ s/.*?\;\s*// ? $body : '';
205             }
206              
207 10     10 1 24 sub content() { shift->unfoldedBody } # Compatibility
208              
209              
210             sub attribute($;$)
211 359     359 1 802 { my ($self, $attr) = (shift, shift);
212 359         798 my $body = $self->unfoldedBody;
213              
214 359 100       890 unless(@_)
215             { # only get a value
216 250 100       5760 if($body =~ m/\b$attr\s*\=\s*
217             ( "( (?> [^\\"]+|\\. )* )"
218             | ([^";\s]*)
219             )/xi)
220 165         609 { (my $val = $+) =~ s/\\(.)/$1/g;
221 165         699 return $val;
222             }
223 85         782 return undef;
224             }
225              
226             # set the value
227 109         229 my $value = shift;
228 109 50       307 unless(defined $value) # remove attribute
229 0         0 { for($body)
230 0 0       0 { s/\b$attr\s*=\s*"(?>[^\\"]|\\.)*"//i
231             or s/\b$attr\s*=\s*[;\s]*//i;
232             }
233 0         0 $self->unfoldedBody($body);
234 0         0 return undef;
235             }
236              
237 109         282 (my $quoted = $value) =~ s/(["\\])/\\$1/g;
238              
239 109         219 for($body)
240             { s/\b$attr\s*=\s*"(?>[^\\"]|\\.){0,1000}"/$attr="$quoted"/i
241             or s/\b$attr\s*=\s*[^;\s]*/$attr="$quoted"/i
242 109 100 100     2758 or do { $_ .= qq(; $attr="$quoted") }
  84         374  
243             }
244              
245 109         366 $self->unfoldedBody($body);
246 109         274 $value;
247             }
248              
249             #------------------------------------------
250              
251              
252             sub attributes()
253 1     1 1 3 { my $self = shift;
254 1         4 my $body = $self->unfoldedBody;
255              
256 1         2 my @attrs;
257 1         12 while($body =~ m/\b(\w+)\s*\=\s*
258             ( "( (?: [^"]|\\" )* )"
259             | '( (?: [^']|\\' )* )'
260             | ([^;\s]*)
261             )
262             /xig)
263 3         15 { push @attrs, $1 => $+;
264             }
265              
266 1         5 @attrs;
267             }
268              
269             #------------------------------------------
270              
271              
272             sub toInt()
273 12     12 1 19 { my $self = shift;
274 12 50       22 return $1 if $self->body =~ m/^\s*(\d+)\s*$/;
275              
276 0         0 $self->log(WARNING => "Field content is not numerical: ". $self->toString);
277              
278 0         0 return undef;
279             }
280              
281             #------------------------------------------
282              
283              
284             my @weekday = qw/Sun Mon Tue Wed Thu Fri Sat Sun/;
285             my @month = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
286              
287             sub toDate(@)
288 16     16 1 33 { my $class = shift;
289 16 0       1099 my @time = @_== 0 ? localtime() : @_==1 ? localtime(shift) : @_;
    50          
290 16         117 my $format = "$weekday[$time[6]], %d $month[$time[4]] %Y %H:%M:%S %z";
291 16         115 my $time = strftime $format, @time;
292              
293             # for C libs which do not (GNU compliantly) support %z
294 16         4144 $time =~ s/ (\%z|[A-Za-z ]+)$/_tz_offset($1)/e;
  0         0  
295              
296 16         87 $time;
297             }
298              
299             sub _tz_offset($)
300 0     0   0 { my $zone = shift;
301 0         0 require Time::Zone;
302              
303 0 0       0 my $diff = $zone eq '%z' ? Time::Zone::tz_local_offset()
304             : Time::Zone::tz_offset($zone);
305 0         0 my $minutes = int((abs($diff)+0.01) / 60); # float rounding errors
306 0         0 my $hours = int(($minutes+0.01) / 60);
307 0         0 $minutes -= $hours * 60;
308 0 0       0 sprintf( ($diff < 0 ? " -%02d%02d" : " +%02d%02d"), $hours, $minutes);
309             }
310              
311             #------------------------------------------
312              
313              
314 18     18 1 60 sub addresses() { Mail::Address->parse(shift->unfoldedBody) }
315              
316             #------------------------------------------
317              
318              
319             sub study()
320 68     68 1 162 { my $self = shift;
321 68         448 require Mail::Message::Field::Full;
322 68         262 Mail::Message::Field::Full->new(scalar $self->folded);
323             }
324              
325             #------------------------------------------
326              
327              
328             sub dateToTimestamp($)
329 1     1 1 7 { my $string = $_[0]->stripCFWS($_[1]);
330              
331             # in RFC822, FWSes can appear within the time.
332 1         16 $string =~ s/(\d\d)\s*\:\s*(\d\d)\s*\:\s*(\d\d)/$1:$2:$3/;
333              
334 1         735 require Date::Parse;
335 1         2819 Date::Parse::str2time($string, 'GMT');
336             }
337              
338              
339             #------------------------------------------
340              
341              
342             #=notice Empty field: $name
343             #Empty fields are not allowed, however sometimes found in messages constructed
344             #by broken applications. You probably want to ignore this message unless you
345             #wrote this broken application yourself.
346              
347             sub consume($;$)
348 777     777 1 1033 { my $self = shift;
349 777 100       1807 my ($name, $body) = defined $_[1] ? @_ : split(/\s*\:\s*/, (shift), 2);
350              
351 777 50       2236 Mail::Reporter->log(WARNING => "Illegal character in field name $name")
352             if $name =~ m/[^\041-\071\073-\176]/;
353              
354             #
355             # Compose the body.
356             #
357              
358 777 100       2121 if(ref $body) # Objects or array
    100          
359 28 50       101 { my $flat = $self->stringifyData($body) or return ();
360 28         87 $body = $self->fold($name, $flat);
361             }
362             elsif($body !~ s/\n+$/\n/g) # Added by user...
363 702         1556 { $body = $self->fold($name, $body);
364             }
365             else # Created by parser
366             { # correct erroneous wrap-seperators (dos files under UNIX)
367 47         159 $body =~ s/[\012\015]+/\n/g;
368 47         120 $body =~ s/^[ \t]*/ /; # start with one blank, folding kept unchanged
369              
370 47 50       108 $self->log(NOTICE => "Empty field: $name")
371             if $body eq " \n";
372             }
373              
374 777         2194 ($name, $body);
375             }
376              
377             #------------------------------------------
378              
379              
380             sub stringifyData($)
381 28     28 1 61 { my ($self, $arg) = (shift, shift);
382 28         46 my @addr;
383 28 100       106 foreach my $obj (ref $arg eq 'ARRAY' ? @$arg : ($arg))
384 33 50       85 { next unless defined $obj;
385              
386 33 100       85 if(!ref $obj) { push @addr, $obj; next }
  1         2  
  1         3  
387 32 100       168 if($obj->isa('Mail::Address')) { push @addr, $obj->format; next }
  19         85  
  19         718  
388              
389 13 100 100     128 if($obj->isa('Mail::Identity') || $obj->isa('User::Identity'))
    100          
    50          
390 7         431 { require Mail::Message::Field::Address;
391 7         24 push @addr, Mail::Message::Field::Address->coerce($obj)->string;
392             }
393             elsif($obj->isa('User::Identity::Collection::Emails'))
394 1 50       4 { my @roles = $obj->roles or next;
395 1         14 require Mail::Message::Field::AddrGroup;
396 1         8 my $group = Mail::Message::Field::AddrGroup->coerce($obj);
397 1 50       44 push @addr, $group->string if $group;
398             }
399             elsif($obj->isa('Mail::Message::Field'))
400             {
401 5         25 my $folded = join ' ', $obj->foldedBody;
402 5         35 $folded =~ s/^ //;
403 5         20 $folded =~ s/\n\z//;
404 5         15 push @addr, $folded;
405             }
406             else
407 0         0 { push @addr, "$obj"; # any other object is stringified
408             }
409             }
410              
411 28 50       121 @addr ? join(', ',@addr) : undef;
412             }
413              
414             #------------------------------------------
415              
416              
417             sub setWrapLength(;$)
418 350     350 1 471 { my $self = shift;
419              
420 350 100       705 $self->foldedBody(scalar $self->fold($self->Name, $self->unfoldedBody, @_))
421             if @_;
422              
423 350         526 $self;
424             }
425              
426             #------------------------------------------
427              
428              
429             sub defaultWrapLength(;$)
430 0     0 1 0 { my $self = shift;
431 0 0       0 @_ ? ($default_wrap_length = shift) : $default_wrap_length;
432             }
433              
434             #------------------------------------------
435              
436              
437             sub fold($$;$)
438 927     927 1 1329 { my $thing = shift;
439 927         1143 my $name = shift;
440 927         1187 my $line = shift;
441 927   66     2386 my $wrap = shift || $default_wrap_length;
442 927 100       1652 defined $line or $line = '';
443              
444 927         1467 $line =~ s/\n\s/ /gms; # Remove accidental folding
445 927 100       1644 return " \n" unless CORE::length($line); # empty field
446              
447 924         1260 my @folded;
448 924         1099 while(1)
449 979 100       1982 { my $max = $wrap - (@folded ? 1 : CORE::length($name) + 2);
450 979         1520 my $min = $max >> 2;
451 979 100       2018 last if CORE::length($line) < $max;
452              
453 55 100 100     1293 $line =~ s/^ ( .{$min,$max} # $max to 30 chars
      100        
454             [;,] # followed at a ; or ,
455             )[ \t] # and then a WSP
456             //x
457             || $line =~ s/^ ( .{$min,$max} ) # $max to 30 chars
458             [ \t] # followed by a WSP
459             //x
460             || $line =~ s/^ ( .{$max,}? ) # longer, but minimal chars
461             [ \t] # followed by a WSP
462             //x
463             || $line =~ s/^ (.*) //x; # everything
464              
465 55         215 push @folded, " $1\n";
466             }
467              
468 924 100       2597 push @folded, " $line\n" if CORE::length($line);
469 924 100       3231 wantarray ? @folded : join('', @folded);
470             }
471              
472              
473             sub unfold($)
474 1630     1630 1 2200 { my $string = $_[1];
475 1630         2655 for($string)
476 1630         7272 { s/\r?\n\s?/ /gs; # remove FWS
477 1630         4287 s/^ +//;
478 1630         4716 s/ +$//;
479             }
480 1630         5061 $string;
481             }
482              
483             #------------------------------------------
484              
485              
486             1;