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-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::Head::Complete;
10 35     35   2884 use vars '$VERSION';
  35         65  
  35         1687  
11             $VERSION = '3.012';
12              
13 35     35   177 use base 'Mail::Message::Head';
  35         70  
  35         13919  
14              
15 35     35   242 use strict;
  35         66  
  35         611  
16 35     35   149 use warnings;
  35         63  
  35         816  
17              
18 35     35   8520 use Mail::Box::Parser;
  35         72  
  35         760  
19 35     35   14382 use Mail::Message::Head::Partial;
  35         84  
  35         1061  
20              
21 35     35   191 use Scalar::Util qw/weaken/;
  35         69  
  35         1464  
22 35     35   181 use List::Util qw/sum/;
  35         69  
  35         2423  
23 35     35   6218 use Sys::Hostname qw/hostname/;
  35         15004  
  35         98604  
24              
25              
26             sub clone(;@)
27 49     49 1 576 { my $self = shift;
28 49         259 my $copy = ref($self)->new($self->logSettings);
29              
30 49         204 $copy->addNoRealize($_->clone) for $self->grepNames(@_);
31 49         238 $copy->modified(1);
32 49         240 $copy;
33             }
34              
35              
36             sub build(@)
37 4     4 1 105 { my $class = shift;
38 4         41 my $self = $class->new;
39 4         19 while(@_)
40 25         37 { my $name = shift;
41 25 50       46 defined $name or next;
42              
43 25 50       152 if($name->isa('Mail::Message::Field'))
44 0         0 { $self->add($name);
45 0         0 next;
46             }
47              
48 25         46 my $content = shift;
49 25 50       46 defined $content or next;
50              
51 25 50 33     58 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         53 $self->add($name, $content);
58             }
59              
60 4         15 $self;
61             }
62              
63             #------------------------------------------
64              
65              
66             sub isDelayed() {0}
67              
68              
69 42     42 1 1633 sub nrLines() { sum 1, map $_->nrLines, shift->orderedFields }
70 55     55 1 142 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 2342 { 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     1047 : ($self->{MMH_field_type} || 'Mail::Message::Field::Fast')->new(@_);
      50        
89              
90 176 50       350 return if !defined $field;
91              
92 176         479 $field->setWrapLength;
93              
94             # Put it in place.
95              
96 176         255 my $known = $self->{MMH_fields};
97 176         399 my $name = $field->name; # is already lower-cased
98              
99 176         542 $self->addOrderedFields($field);
100              
101 176 100       357 if(defined $known->{$name})
102 3 100       12 { if(ref $known->{$name} eq 'ARRAY') { push @{$known->{$name}}, $field }
  1         3  
  1         3  
103 2         6 else { $known->{$name} = [ $known->{$name}, $field ] }
104             }
105             else
106 173         378 { $known->{$name} = $field;
107             }
108              
109 176         241 $self->{MMH_modified}++;
110 176         391 $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 1122 sub names() {shift->knownNames}
125              
126              
127             sub grepNames(@)
128 55     55 1 91 { my $self = shift;
129 55         90 my @take;
130 55 50       145 push @take, (ref $_ eq 'ARRAY' ? @$_ : $_) foreach @_;
131              
132 55 100       231 return $self->orderedFields unless @take;
133              
134 6         8 my $take;
135 6 50 33     21 if(@take==1 && ref $take[0] eq 'Regexp')
136 6         12 { $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         15 grep {$_->name =~ $take} $self->orderedFields;
  24         49  
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 1246 { my $self = shift;
155 454   50     1185 my $type = $self->{MMH_field_type} || 'Mail::Message::Field::Fast';
156 454         621 $self->{MMH_modified}++;
157              
158             # Create object for this field.
159 454 100 66     1827 my $field = @_==1 && ref $_[0] ? shift->clone : $type->new(@_);
160              
161 454         1045 my $name = $field->name; # is already lower-cased
162 454         698 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     1320 if($skip_none{$name} && $field->body eq 'none')
168 289         507 { delete $known->{$name};
169 289         730 return $field;
170             }
171              
172 165         517 $field->setWrapLength;
173 165         410 $known->{$name} = $field;
174              
175 165         547 $self->addOrderedFields($field);
176 165         296 $field;
177             }
178              
179              
180             sub reset($@)
181 8     8 1 1238 { my ($self, $name) = (shift, lc shift);
182              
183 8         15 my $known = $self->{MMH_fields};
184              
185 8 100       21 if(@_==0)
186 7 100       19 { $self->{MMH_modified}++ if delete $known->{$name};
187 7         17 return ();
188             }
189              
190 1         2 $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         3 $self;
202             }
203              
204              
205 6     6 1 16 sub delete($) { $_[0]->reset($_[1]) }
206              
207              
208             sub removeField($)
209 8     8 1 13 { my ($self, $field) = @_;
210 8         15 my $name = $field->name;
211 8         12 my $known = $self->{MMH_fields};
212              
213 8 50       31 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         8 return splice @{$known->{$name}}, $i, 1
219 2 50       5 if $known->{$name}[$i] eq $field;
220             }
221             }
222             elsif($known->{$name} eq $field)
223 6         15 { 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 91 { my $self = shift;
234 46         293 (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 568 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 1539 { my $self = shift;
274 35   33     87 my $fh = shift || select;
275              
276             $_->print($fh)
277 35         199 foreach $self->orderedFields;
278              
279 35 50       84 if(ref $fh eq 'GLOB') { print $fh "\n" }
  0         0  
280 35         81 else { $fh->print("\n") }
281              
282 35         317 $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 454 sub toString() {shift->string}
322             sub string()
323 6     6 1 12 { my $self = shift;
324              
325 6         27 my @lines = map {$_->string} $self->orderedFields;
  44         121  
326 6         19 push @lines, "\n";
327              
328 6 50       54 wantarray ? @lines : join('', @lines);
329             }
330              
331              
332             sub resentGroups()
333 3     3 1 554 { my $self = shift;
334 3         385 require Mail::Message::Head::ResentGroup;
335 3         16 Mail::Message::Head::ResentGroup->from($self);
336             }
337              
338              
339             sub addResentGroup(@)
340 3     3 1 490 { my $self = shift;
341              
342 3         17 require Mail::Message::Head::ResentGroup;
343 3 100       15 my $rg = @_==1 ? (shift) : Mail::Message::Head::ResentGroup->new(@_);
344              
345 3         8 my @fields = $rg->orderedFields;
346 3         7 my $order = $self->{MMH_order};
347              
348             # Look for the first line which relates to resent groups
349 3         5 my $i;
350 3         11 for($i=0; $i < @$order; $i++)
351 13 50       29 { next unless defined $order->[$i];
352 13 100       22 last if $rg->isResentGroupFieldName($order->[$i]->name);
353             }
354              
355 3         5 my $known = $self->{MMH_fields};
356 3         9 while(@fields)
357 15         18 { my $f = pop @fields;
358              
359             # Add to the order of fields
360 15         28 splice @$order, $i, 0, $f;
361 15         35 weaken( $order->[$i] );
362 15         29 my $name = $f->name;
363              
364             # Adds *before* in the list for get().
365 15 100       31 if(!defined $known->{$name}) {$known->{$name} = $f}
  13 50       27  
366 0         0 elsif(ref $known->{$name} eq 'ARRAY'){unshift @{$known->{$name}},$f}
  0         0  
367 2         5 else {$known->{$name} = [$f, $known->{$name}]}
368             }
369              
370 3         12 $rg->messageHead($self);
371              
372             # Oh, the header has changed!
373 3         13 $self->modified(1);
374              
375 3         18 $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 10 sub timestamp() {shift->guessTimestamp || time}
410              
411              
412             sub recvstamp()
413 2     2 1 7 { my $self = shift;
414              
415 2 100       15 return $self->{MMH_recvstamp} if exists $self->{MMH_recvstamp};
416              
417             my $recvd = $self->get('received', 0) or
418 1 50       3 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 6 { my $self = shift;
428 2 100       25 return $self->{MMH_timestamp} if exists $self->{MMH_timestamp};
429              
430 1         3 my $stamp;
431 1 50       5 if(my $date = $self->get('date'))
432 1         5 { $stamp = Mail::Message::Field->dateToTimestamp($date);
433             }
434              
435 1 50       640 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     41 $self->{MMH_timestamp} = defined $stamp && $stamp > 0 ? $stamp : undef;
443             }
444              
445             sub guessBodySize()
446 12     12 1 26 { my $self = shift;
447              
448 12         29 my $cl = $self->get('Content-Length');
449 12 50 33     48 return $1 if defined $cl && $cl =~ m/(\d+)/;
450              
451 12         28 my $lines = $self->get('Lines'); # 40 chars per lines
452 12 50 33     28 return $1 * 40 if defined $lines && $lines =~ m/(\d+)/;
453              
454 12         21 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 311 { $msgid_creator ||= $_[0]->messageIdPrefix;
473 91         261 $msgid_creator->(@_);
474             }
475              
476              
477             sub messageIdPrefix(;$$)
478 10     10 1 24 { 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     71 my $prefix = shift || "mailbox-$$";
486              
487 10         25 my $hostname = shift;
488 10 50       56 if(!defined $hostname)
489 10         770 { eval "require Net::Domain";
490 10 50       81297 $@ or $hostname = Net::Domain::hostfqdn();
491             }
492 10   0     61557 $hostname ||= hostname || 'localhost';
      33        
493              
494 10         626 eval "require Time::HiRes";
495 10 50       13146 if(Time::HiRes->can('gettimeofday'))
496             {
497             return $msgid_creator
498 91     91   376 = sub { my ($sec, $micro) = Time::HiRes::gettimeofday();
499 91         492 "$prefix-$sec-$micro\@$hostname";
500 10         118 };
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;