File Coverage

blib/lib/Mail/Message/Head/Complete.pm
Criterion Covered Total %
statement 176 251 70.1
branch 54 108 50.0
condition 20 57 35.0
subroutine 34 49 69.3
pod 36 38 94.7
total 320 503 63.6


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::Head::Complete;
10 35     35   2806 use vars '$VERSION';
  35         78  
  35         1978  
11             $VERSION = '3.011';
12              
13 35     35   204 use base 'Mail::Message::Head';
  35         68  
  35         17034  
14              
15 35     35   268 use strict;
  35         84  
  35         719  
16 35     35   188 use warnings;
  35         94  
  35         906  
17              
18 35     35   10090 use Mail::Box::Parser;
  35         89  
  35         863  
19 35     35   17243 use Mail::Message::Head::Partial;
  35         93  
  35         1216  
20              
21 35     35   239 use Scalar::Util qw/weaken/;
  35         71  
  35         1809  
22 35     35   228 use List::Util qw/sum/;
  35         74  
  35         2892  
23 35     35   8246 use Sys::Hostname qw/hostname/;
  35         16513  
  35         120371  
24              
25              
26             sub clone(;@)
27 49     49 1 674 { my $self = shift;
28 49         171 my $copy = ref($self)->new($self->logSettings);
29              
30 49         208 $copy->addNoRealize($_->clone) for $self->grepNames(@_);
31 49         176 $copy->modified(1);
32 49         176 $copy;
33             }
34              
35              
36             sub build(@)
37 4     4 1 102 { my $class = shift;
38 4         40 my $self = $class->new;
39 4         21 while(@_)
40 25         51 { my $name = shift;
41 25 50       53 defined $name or next;
42              
43 25 50       162 if($name->isa('Mail::Message::Field'))
44 0         0 { $self->add($name);
45 0         0 next;
46             }
47              
48 25         53 my $content = shift;
49 25 50       51 defined $content or next;
50              
51 25 50 33     60 if(ref $content && $content->isa('Mail::Message::Field'))
52 0         0 { $self->log(WARNING => "Field objects have an implied name ($name)");
53 0         0 $self->add($content);
54 0         0 next;
55             }
56              
57 25         56 $self->add($name, $content);
58             }
59              
60 4         26 $self;
61             }
62              
63             #------------------------------------------
64              
65              
66             sub isDelayed() {0}
67              
68              
69 42     42 1 1621 sub nrLines() { sum 1, map $_->nrLines, shift->orderedFields }
70 55     55 1 201 sub size() { sum 1, map $_->size, shift->orderedFields }
71              
72              
73             sub wrap($)
74 0     0 1 0 { my ($self, $length) = @_;
75 0         0 $_->setWrapLength($length) for $self->orderedFields;
76             }
77              
78             #------------------------------------------
79              
80              
81             sub add(@)
82 176     176 1 2798 { my $self = shift;
83              
84             # Create object for this field.
85              
86             my $field
87             = @_==1 && ref $_[0] ? shift # A fully qualified field is added.
88 176 50 33     1218 : ($self->{MMH_field_type} || 'Mail::Message::Field::Fast')->new(@_);
      50        
89              
90 176 50       518 return if !defined $field;
91              
92 176         561 $field->setWrapLength;
93              
94             # Put it in place.
95              
96 176         331 my $known = $self->{MMH_fields};
97 176         484 my $name = $field->name; # is already lower-cased
98              
99 176         649 $self->addOrderedFields($field);
100              
101 176 100       432 if(defined $known->{$name})
102 3 100       12 { if(ref $known->{$name} eq 'ARRAY') { push @{$known->{$name}}, $field }
  1         2  
  1         3  
103 2         7 else { $known->{$name} = [ $known->{$name}, $field ] }
104             }
105             else
106 173         499 { $known->{$name} = $field;
107             }
108              
109 176         301 $self->{MMH_modified}++;
110 176         456 $field;
111             }
112              
113              
114             sub count($)
115 0     0 1 0 { my $known = shift->{MMH_fields};
116 0         0 my $value = $known->{lc shift};
117              
118 0 0       0 ! defined $value ? 0
    0          
119             : ref $value ? @$value
120             : 1;
121             }
122              
123              
124 10     10 1 1411 sub names() {shift->knownNames}
125              
126              
127             sub grepNames(@)
128 55     55 1 105 { my $self = shift;
129 55         84 my @take;
130 55 50       140 push @take, (ref $_ eq 'ARRAY' ? @$_ : $_) foreach @_;
131              
132 55 100       216 return $self->orderedFields unless @take;
133              
134 6         12 my $take;
135 6 50 33     30 if(@take==1 && ref $take[0] eq 'Regexp')
136 6         13 { $take = $take[0]; # one regexp prepared already
137             }
138             else
139             { # I love this trick:
140 0         0 local $" = ')|(?:';
141 0         0 $take = qr/^(?:(?:@take))/i;
142             }
143              
144 6         22 grep {$_->name =~ $take} $self->orderedFields;
  24         61  
145             }
146              
147              
148             my @skip_none = qw/content-transfer-encoding content-disposition
149             content-description content-id/;
150              
151             my %skip_none = map { ($_ => 1) } @skip_none;
152              
153             sub set(@)
154 454     454 1 1398 { my $self = shift;
155 454   50     1391 my $type = $self->{MMH_field_type} || 'Mail::Message::Field::Fast';
156 454         726 $self->{MMH_modified}++;
157              
158             # Create object for this field.
159 454 100 66     2055 my $field = @_==1 && ref $_[0] ? shift->clone : $type->new(@_);
160              
161 454         1373 my $name = $field->name; # is already lower-cased
162 454         795 my $known = $self->{MMH_fields};
163              
164             # Internally, non-existing content-info are in the body stored as 'none'
165             # The header will not contain these lines.
166              
167 454 100 100     1427 if($skip_none{$name} && $field->body eq 'none')
168 289         589 { delete $known->{$name};
169 289         880 return $field;
170             }
171              
172 165         579 $field->setWrapLength;
173 165         449 $known->{$name} = $field;
174              
175 165         610 $self->addOrderedFields($field);
176 165         347 $field;
177             }
178              
179              
180             sub reset($@)
181 8     8 1 1546 { my ($self, $name) = (shift, lc shift);
182              
183 8         21 my $known = $self->{MMH_fields};
184              
185 8 100       24 if(@_==0)
186 7 100       24 { $self->{MMH_modified}++ if delete $known->{$name};
187 7         19 return ();
188             }
189              
190 1         3 $self->{MMH_modified}++;
191              
192             # Cloning required, otherwise double registrations will not be
193             # removed from the ordered list: that's controled by 'weaken'
194              
195 1         6 my @fields = map $_->clone, @_;
196              
197 1 50       4 if(@_==1) { $known->{$name} = $fields[0] }
  0         0  
198 1         3 else { $known->{$name} = [@fields] }
199              
200 1         5 $self->addOrderedFields(@fields);
201 1         4 $self;
202             }
203              
204              
205 6     6 1 20 sub delete($) { $_[0]->reset($_[1]) }
206              
207              
208             sub removeField($)
209 8     8 1 17 { my ($self, $field) = @_;
210 8         16 my $name = $field->name;
211 8         14 my $known = $self->{MMH_fields};
212              
213 8 50       32 if(!defined $known->{$name})
    100          
    50          
214             { ; } # complain
215             elsif(ref $known->{$name} eq 'ARRAY')
216 2         3 { for(my $i=0; $i < @{$known->{$name}}; $i++)
  2         6  
217             {
218 2         9 return splice @{$known->{$name}}, $i, 1
219 2 50       6 if $known->{$name}[$i] eq $field;
220             }
221             }
222             elsif($known->{$name} eq $field)
223 6         19 { return delete $known->{$name};
224             }
225              
226 0         0 $self->log(WARNING => "Cannot remove field $name from header: not found.");
227              
228 0         0 return;
229             }
230              
231              
232             sub removeFields(@)
233 46     46 1 93 { my $self = shift;
234 46         310 (bless $self, 'Mail::Message::Head::Partial')->removeFields(@_);
235             }
236              
237              
238             sub removeFieldsExcept(@)
239 0     0 1 0 { my $self = shift;
240 0         0 (bless $self, 'Mail::Message::Head::Partial')->removeFieldsExcept(@_);
241             }
242              
243              
244 83     83 1 532 sub removeContentInfo() { shift->removeFields(qr/^Content-/, 'Lines') }
245              
246              
247             sub removeResentGroups(@)
248 0     0 1 0 { my $self = shift;
249 0         0 (bless $self, 'Mail::Message::Head::Partial')->removeResentGroups(@_);
250             }
251              
252              
253             sub removeListGroup(@)
254 0     0 1 0 { my $self = shift;
255 0         0 (bless $self, 'Mail::Message::Head::Partial')->removeListGroup(@_);
256             }
257              
258              
259             sub removeSpamGroups(@)
260 0     0 1 0 { my $self = shift;
261 0         0 (bless $self, 'Mail::Message::Head::Partial')->removeSpamGroups(@_);
262             }
263              
264              
265             sub spamDetected()
266 0     0 1 0 { my $self = shift;
267 0 0       0 my @sgs = $self->spamGroups or return undef;
268 0         0 grep { $_->spamDetected } @sgs;
  0         0  
269             }
270              
271              
272             sub print(;$)
273 35     35 1 1959 { my $self = shift;
274 35   33     94 my $fh = shift || select;
275              
276             $_->print($fh)
277 35         244 foreach $self->orderedFields;
278              
279 35 50       107 if(ref $fh eq 'GLOB') { print $fh "\n" }
  0         0  
280 35         87 else { $fh->print("\n") }
281              
282 35         378 $self;
283             }
284              
285              
286             sub printUndisclosed($)
287 0     0 1 0 { my ($self, $fh) = @_;
288              
289             $_->print($fh)
290 0         0 foreach grep {$_->toDisclose} $self->orderedFields;
  0         0  
291              
292 0 0       0 if(ref $fh eq 'GLOB') { print $fh "\n" }
  0         0  
293 0         0 else { $fh->print("\n") }
294              
295 0         0 $self;
296             }
297              
298              
299             sub printSelected($@)
300 0     0 1 0 { my ($self, $fh) = (shift, shift);
301              
302 0         0 foreach my $field ($self->orderedFields)
303 0         0 { my $Name = $field->Name;
304 0         0 my $name = $field->name;
305              
306 0         0 my $found;
307 0         0 foreach my $pattern (@_)
308 0 0       0 { $found = ref $pattern?($Name =~ $pattern):($name eq lc $pattern);
309 0 0       0 last if $found;
310             }
311              
312 0 0       0 if(!$found) { ; }
    0          
313 0         0 elsif(ref $fh eq 'GLOB') { print $fh "\n" }
314 0         0 else { $fh->print("\n") }
315             }
316              
317 0         0 $self;
318             }
319              
320              
321 1     1 0 602 sub toString() {shift->string}
322             sub string()
323 6     6 1 16 { my $self = shift;
324              
325 6         31 my @lines = map {$_->string} $self->orderedFields;
  44         138  
326 6         22 push @lines, "\n";
327              
328 6 50       66 wantarray ? @lines : join('', @lines);
329             }
330              
331              
332             sub resentGroups()
333 3     3 1 710 { my $self = shift;
334 3         710 require Mail::Message::Head::ResentGroup;
335 3         21 Mail::Message::Head::ResentGroup->from($self);
336             }
337              
338              
339             sub addResentGroup(@)
340 3     3 1 589 { my $self = shift;
341              
342 3         24 require Mail::Message::Head::ResentGroup;
343 3 100       17 my $rg = @_==1 ? (shift) : Mail::Message::Head::ResentGroup->new(@_);
344              
345 3         12 my @fields = $rg->orderedFields;
346 3         8 my $order = $self->{MMH_order};
347              
348             # Look for the first line which relates to resent groups
349 3         5 my $i;
350 3         15 for($i=0; $i < @$order; $i++)
351 13 50       27 { next unless defined $order->[$i];
352 13 100       32 last if $rg->isResentGroupFieldName($order->[$i]->name);
353             }
354              
355 3         8 my $known = $self->{MMH_fields};
356 3         9 while(@fields)
357 15         23 { my $f = pop @fields;
358              
359             # Add to the order of fields
360 15         43 splice @$order, $i, 0, $f;
361 15         44 weaken( $order->[$i] );
362 15         33 my $name = $f->name;
363              
364             # Adds *before* in the list for get().
365 15 100       38 if(!defined $known->{$name}) {$known->{$name} = $f}
  13 50       36  
366 0         0 elsif(ref $known->{$name} eq 'ARRAY'){unshift @{$known->{$name}},$f}
  0         0  
367 2         6 else {$known->{$name} = [$f, $known->{$name}]}
368             }
369              
370 3         12 $rg->messageHead($self);
371              
372             # Oh, the header has changed!
373 3         15 $self->modified(1);
374              
375 3         7 $rg;
376             }
377              
378              
379             sub listGroup()
380 0     0 1 0 { my $self = shift;
381 0         0 eval "require 'Mail::Message::Head::ListGroup'";
382 0         0 Mail::Message::Head::ListGroup->from($self);
383             }
384              
385              
386             sub addListGroup($)
387 0     0 1 0 { my ($self, $lg) = @_;
388 0         0 $lg->attach($self);
389             }
390              
391              
392             sub spamGroups(@)
393 0     0 1 0 { my $self = shift;
394 0         0 require Mail::Message::Head::SpamGroup;
395 0 0       0 my @types = @_ ? (types => \@_) : ();
396 0         0 my @sgs = Mail::Message::Head::SpamGroup->from($self, @types);
397 0 0 0     0 wantarray || @_ != 1 ? @sgs : $sgs[0];
398             }
399              
400              
401             sub addSpamGroup($)
402 0     0 1 0 { my ($self, $sg) = @_;
403 0         0 $sg->attach($self);
404             }
405              
406             #------------------------------------------
407              
408              
409 2 50   2 1 12 sub timestamp() {shift->guessTimestamp || time}
410              
411              
412             sub recvstamp()
413 2     2 1 6 { my $self = shift;
414              
415 2 100       14 return $self->{MMH_recvstamp} if exists $self->{MMH_recvstamp};
416              
417             my $recvd = $self->get('received', 0) or
418 1 50       5 return $self->{MMH_recvstamp} = undef;
419              
420 0         0 my $stamp = Mail::Message::Field->dateToTimestamp($recvd->comment);
421              
422 0 0 0     0 $self->{MMH_recvstamp} = defined $stamp && $stamp > 0 ? $stamp : undef;
423             }
424              
425              
426             sub guessTimestamp()
427 2     2 0 4 { my $self = shift;
428 2 100       23 return $self->{MMH_timestamp} if exists $self->{MMH_timestamp};
429              
430 1         2 my $stamp;
431 1 50       4 if(my $date = $self->get('date'))
432 1         6 { $stamp = Mail::Message::Field->dateToTimestamp($date);
433             }
434              
435 1 50       568 unless($stamp)
436 0         0 { foreach (reverse $self->get('received'))
437 0         0 { $stamp = Mail::Message::Field->dateToTimestamp($_->comment);
438 0 0       0 last if $stamp;
439             }
440             }
441              
442 1 50 33     25 $self->{MMH_timestamp} = defined $stamp && $stamp > 0 ? $stamp : undef;
443             }
444              
445             sub guessBodySize()
446 12     12 1 17 { my $self = shift;
447              
448 12         34 my $cl = $self->get('Content-Length');
449 12 50 33     34 return $1 if defined $cl && $cl =~ m/(\d+)/;
450              
451 12         27 my $lines = $self->get('Lines'); # 40 chars per lines
452 12 50 33     35 return $1 * 40 if defined $lines && $lines =~ m/(\d+)/;
453              
454 12         26 undef;
455             }
456              
457             #------------------------------------------
458              
459              
460             sub createFromLine()
461 0     0 1 0 { my $self = shift;
462 0         0 my $sender = $self->message->sender;
463 0   0     0 my $stamp = $self->recvstamp || $self->timestamp || time;
464 0 0       0 my $addr = defined $sender ? $sender->address : 'unknown';
465 0         0 "From $addr ".(gmtime $stamp)."\n"
466             }
467              
468              
469             my $msgid_creator;
470              
471             sub createMessageId()
472 91   66 91 1 316 { $msgid_creator ||= $_[0]->messageIdPrefix;
473 91         270 $msgid_creator->(@_);
474             }
475              
476              
477             sub messageIdPrefix(;$$)
478 10     10 1 33 { my $thing = shift;
479 10 50 33     65 return $msgid_creator
480             unless @_ || !defined $msgid_creator;
481              
482 10 50 33     54 return $msgid_creator = shift
483             if @_==1 && ref $_[0] eq 'CODE';
484              
485 10   33     103 my $prefix = shift || "mailbox-$$";
486              
487 10         25 my $hostname = shift;
488 10 50       40 if(!defined $hostname)
489 10         904 { eval "require Net::Domain";
490 10 50       89884 $@ or $hostname = Net::Domain::hostfqdn();
491             }
492 10   0     68083 $hostname ||= hostname || 'localhost';
      33        
493              
494 10         641 eval "require Time::HiRes";
495 10 50       14497 if(Time::HiRes->can('gettimeofday'))
496             {
497             return $msgid_creator
498 91     91   405 = sub { my ($sec, $micro) = Time::HiRes::gettimeofday();
499 91         576 "$prefix-$sec-$micro\@$hostname";
500 10         139 };
501             }
502              
503 0           my $unique_id = time;
504             $msgid_creator
505 0     0     = sub { $unique_id++;
506 0           "$prefix-$unique_id\@$hostname";
507 0           };
508             }
509              
510             1;