File Coverage

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


line stmt bran cond sub pod time code
1             # Copyrights 2001-2021 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.02.
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   1243 use vars '$VERSION';
  50         1804  
  50         4610  
11             $VERSION = '3.011';
12              
13 50     50   292 use base 'Mail::Reporter';
  50         2005  
  50         13107  
14              
15 50     50   353 use strict;
  50         107  
  50         3374  
16 50     50   288 use warnings;
  50         3996  
  50         3569  
17              
18 50     50   261 use Carp;
  50         126  
  50         7308  
19 50     50   31435 use Mail::Address;
  50         126785  
  50         4210  
20 50     50   27618 use Date::Format 'strftime';
  50         426916  
  50         4220  
21 50     50   10507 use IO::Handle;
  50         99574  
  50         10181  
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   1101 qq("") => sub { $_[0]->unfoldedBody }
29 2 50   2   27 , '0+' => sub { $_[0]->toInt || 0 }
30 2297     2297   5779 , bool => sub {1}
31 142     142   9805 , cmp => sub { $_[0]->unfoldedBody cmp "$_[1]" }
32 10 100   10   188 , '<=>' => sub { $_[2] ? $_[1] <=> $_[0]->toInt : $_[0]->toInt <=> $_[1] }
33 50     50   18966 , fallback => 1;
  50         14802  
  50         1056  
34              
35             #------------------------------------------
36              
37              
38             sub new(@)
39 598     598 1 3391 { my $class = shift;
40 598 100       1451 if($class eq __PACKAGE__) # bootstrap
41 468         2960 { require Mail::Message::Field::Fast;
42 468         1567 return Mail::Message::Field::Fast->new(@_);
43             }
44 130         708 $class->SUPER::new(@_);
45             }
46              
47              
48              
49             #------------------------------------------
50              
51              
52 161     161 1 362 sub length { length shift->folded }
53              
54              
55             BEGIN {
56 50     50   14495 %_structured = map { (lc($_) => 1) }
  1350         161793  
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 2195 { my $name = ref $_[0] ? shift->name : $_[1];
70 697         2876 exists $_structured{lc $name};
71             }
72              
73              
74             sub print(;$)
75 21     21 1 43 { my $self = shift;
76 21   33     58 my $fh = shift || select;
77 21         133 $fh->print(scalar $self->folded);
78             }
79              
80              
81 27     27 0 1800 sub toString(;$) {shift->string(@_)}
82             sub string(;$)
83 93     93 1 10407 { my $self = shift;
84 93 100       355 return $self->folded unless @_;
85              
86 5   33     15 my $wrap = shift || $default_wrap_length;
87 5         15 my $name = $self->Name;
88 5         17 my @lines = $self->fold($name, $self->unfoldedBody, $wrap);
89 5         16 $lines[0] = $name . ':' . $lines[0];
90 5 50       21 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 315 sub nrLines() { my @l = shift->foldedBody; scalar @l }
  122         462  
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 4219 { my $thing = shift;
124 27 100       75 my $name = @_ ? shift : $thing->name;
125              
126             join '-',
127 27 100       84 map { $wf_lookup{lc $_} || ( /[aeiouyAEIOUY]/ ? ucfirst lc : uc ) }
  42 100       337  
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 5461 { my $self = shift;
139 665         1873 my $body = $self->unfoldedBody;
140 665 100       1654 return $body unless $self->isStructured;
141              
142 467         3416 my ($first) = $body =~ m/^((?:"[^"]*"|'[^']*'|[^;])*)/;
143 467         1187 $first =~ s/\s+$//;
144 467         1998 $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 11722 { my $thing = shift;
156              
157             # get (folded) data
158 55 100       126 my $string = @_ ? shift : $thing->foldedBody;
159              
160             # remove comments
161 55         76 my $r = '';
162 55         62 my $in_dquotes = 0;
163 55         105 my $open_paren = 0;
164              
165 55         292 my @s = split m/([()"])/, $string;
166 55         110 while(@s)
167 472         545 { my $s = shift @s;
168              
169 472 100 100     1816 if(CORE::length($r)&& substr($r, -1) eq "\\") { $r .= $s }
  4 100 100     8  
    100 100        
    100          
    100          
170 40         101 elsif($s eq '"') { $in_dquotes = not $in_dquotes; $r .= $s }
  40         65  
171 80         118 elsif($s eq '(' && !$in_dquotes) { $open_paren++ }
172 80         109 elsif($s eq ')' && !$in_dquotes) { $open_paren-- }
173             elsif($open_paren) {} # in comment
174 156         286 else { $r .= $s }
175             }
176              
177             # beautify and unfold at the same time
178 55         95 for($r)
179 55         231 { s/\s+/ /gs;
180 55         168 s/\s+$//;
181 55         130 s/^\s+//;
182             }
183              
184 55         163 $r;
185             }
186              
187             #------------------------------------------
188              
189              
190             sub comment(;$)
191 31     31 1 79 { my $self = shift;
192 31 100       62 return undef unless $self->isStructured;
193              
194 27         76 my $body = $self->unfoldedBody;
195              
196 27 100       70 if(@_)
197 2         4 { my $comment = shift;
198 2         6 $body =~ s/\s*\;.*//;
199 2 50 33     14 $body .= "; $comment" if defined $comment && CORE::length($comment);
200 2         7 $self->unfoldedBody($body);
201 2         5 return $comment;
202             }
203            
204 25 100       174 $body =~ s/.*?\;\s*// ? $body : '';
205             }
206              
207 10     10 1 29 sub content() { shift->unfoldedBody } # Compatibility
208              
209              
210             sub attribute($;$)
211 359     359 1 874 { my ($self, $attr) = (shift, shift);
212 359         847 my $body = $self->unfoldedBody;
213              
214 359 100       947 unless(@_)
215             { # only get a value
216 250 100       6327 if($body =~ m/\b$attr\s*\=\s*
217             ( "( (?> [^\\"]+|\\. )* )"
218             | ([^";\s]*)
219             )/xi)
220 165         691 { (my $val = $+) =~ s/\\(.)/$1/g;
221 165         802 return $val;
222             }
223 85         848 return undef;
224             }
225              
226             # set the value
227 109         216 my $value = shift;
228 109 50       296 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         281 (my $quoted = $value) =~ s/(["\\])/\\$1/g;
238              
239 109         237 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     3018 or do { $_ .= qq(; $attr="$quoted") }
  84         387  
243             }
244              
245 109         401 $self->unfoldedBody($body);
246 109         280 $value;
247             }
248              
249             #------------------------------------------
250              
251              
252             sub attributes()
253 1     1 1 4 { 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         20 { push @attrs, $1 => $+;
264             }
265              
266 1         7 @attrs;
267             }
268              
269             #------------------------------------------
270              
271              
272             sub toInt()
273 12     12 1 21 { my $self = shift;
274 12 50       25 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 44 { my $class = shift;
289 16 0       1004 my @time = @_== 0 ? localtime() : @_==1 ? localtime(shift) : @_;
    50          
290 16         125 my $format = "$weekday[$time[6]], %d $month[$time[4]] %Y %H:%M:%S %z";
291 16         120 my $time = strftime $format, @time;
292              
293             # for C libs which do not (GNU compliantly) support %z
294 16         4443 $time =~ s/ (\%z|[A-Za-z ]+)$/_tz_offset($1)/e;
  0         0  
295              
296 16         101 $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 66 sub addresses() { Mail::Address->parse(shift->unfoldedBody) }
315              
316             #------------------------------------------
317              
318              
319             sub study()
320 68     68 1 153 { my $self = shift;
321 68         444 require Mail::Message::Field::Full;
322 68         290 Mail::Message::Field::Full->new(scalar $self->folded);
323             }
324              
325             #------------------------------------------
326              
327              
328             sub dateToTimestamp($)
329 1     1 1 5 { my $string = $_[0]->stripCFWS($_[1]);
330              
331             # in RFC822, FWSes can appear within the time.
332 1         13 $string =~ s/(\d\d)\s*\:\s*(\d\d)\s*\:\s*(\d\d)/$1:$2:$3/;
333              
334 1         578 require Date::Parse;
335 1         2952 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 1223 { my $self = shift;
349 777 100       2058 my ($name, $body) = defined $_[1] ? @_ : split(/\s*\:\s*/, (shift), 2);
350              
351 777 50       2570 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       2450 if(ref $body) # Objects or array
    100          
359 28 50       106 { my $flat = $self->stringifyData($body) or return ();
360 28         93 $body = $self->fold($name, $flat);
361             }
362             elsif($body !~ s/\n+$/\n/g) # Added by user...
363 702         1808 { $body = $self->fold($name, $body);
364             }
365             else # Created by parser
366             { # correct erroneous wrap-seperators (dos files under UNIX)
367 47         167 $body =~ s/[\012\015]+/\n/g;
368 47         135 $body =~ s/^[ \t]*/ /; # start with one blank, folding kept unchanged
369              
370 47 50       120 $self->log(NOTICE => "Empty field: $name")
371             if $body eq " \n";
372             }
373              
374 777         2463 ($name, $body);
375             }
376              
377             #------------------------------------------
378              
379              
380             sub stringifyData($)
381 28     28 1 76 { my ($self, $arg) = (shift, shift);
382 28         44 my @addr;
383 28 100       110 foreach my $obj (ref $arg eq 'ARRAY' ? @$arg : ($arg))
384 33 50       141 { next unless defined $obj;
385              
386 33 100       92 if(!ref $obj) { push @addr, $obj; next }
  1         3  
  1         2  
387 32 100       183 if($obj->isa('Mail::Address')) { push @addr, $obj->format; next }
  19         107  
  19         805  
388              
389 13 100 100     130 if($obj->isa('Mail::Identity') || $obj->isa('User::Identity'))
    100          
    50          
390 7         548 { require Mail::Message::Field::Address;
391 7         32 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         6 my $group = Mail::Message::Field::AddrGroup->coerce($obj);
397 1 50       49 push @addr, $group->string if $group;
398             }
399             elsif($obj->isa('Mail::Message::Field'))
400             {
401 5         19 my $folded = join ' ', $obj->foldedBody;
402 5         26 $folded =~ s/^ //;
403 5         20 $folded =~ s/\n\z//;
404 5         17 push @addr, $folded;
405             }
406             else
407 0         0 { push @addr, "$obj"; # any other object is stringified
408             }
409             }
410              
411 28 50       150 @addr ? join(', ',@addr) : undef;
412             }
413              
414             #------------------------------------------
415              
416              
417             sub setWrapLength(;$)
418 350     350 1 566 { my $self = shift;
419              
420 350 100       770 $self->foldedBody(scalar $self->fold($self->Name, $self->unfoldedBody, @_))
421             if @_;
422              
423 350         681 $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 926     926 1 1581 { my $thing = shift;
439 926         1348 my $name = shift;
440 926         1427 my $line = shift;
441 926   66     2876 my $wrap = shift || $default_wrap_length;
442 926 100       1864 defined $line or $line = '';
443              
444 926         1602 $line =~ s/\n\s/ /gms; # Remove accidental folding
445 926 100       1993 return " \n" unless CORE::length($line); # empty field
446              
447 923         1329 my @folded;
448 923         1347 while(1)
449 977 100       2282 { my $max = $wrap - (@folded ? 1 : CORE::length($name) + 2);
450 977         1727 my $min = $max >> 2;
451 977 100       2416 last if CORE::length($line) < $max;
452              
453 54 100 100     1528 $line =~ s/^ ( .{$min,$max} # $max to 30 chars
      66        
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 54         247 push @folded, " $1\n";
466             }
467              
468 923 100       2977 push @folded, " $line\n" if CORE::length($line);
469 923 100       3752 wantarray ? @folded : join('', @folded);
470             }
471              
472              
473             sub unfold($)
474 1630     1630 1 2558 { my $string = $_[1];
475 1630         2846 for($string)
476 1630         8320 { s/\r?\n\s?/ /gs; # remove FWS
477 1630         5057 s/^ +//;
478 1630         5825 s/ +$//;
479             }
480 1630         5878 $string;
481             }
482              
483             #------------------------------------------
484              
485              
486             1;