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-2023 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 38     38   2936 use vars '$VERSION';
  38         100  
  38         2020  
11             $VERSION = '3.013';
12              
13 38     38   259 use base 'Mail::Message::Head';
  38         80  
  38         17693  
14              
15 38     38   281 use strict;
  38         73  
  38         736  
16 38     38   180 use warnings;
  38         71  
  38         855  
17              
18 38     38   9228 use Mail::Box::Parser;
  38         93  
  38         927  
19 38     38   17134 use Mail::Message::Head::Partial;
  38         100  
  38         1314  
20              
21 38     38   261 use Scalar::Util qw/weaken/;
  38         77  
  38         1914  
22 38     38   223 use List::Util qw/sum/;
  38         90  
  38         3250  
23 38     38   7629 use Sys::Hostname qw/hostname/;
  38         17042  
  38         128860  
24              
25              
26             sub clone(;@)
27 55     55 1 828 { my $self = shift;
28 55         183 my $copy = ref($self)->new($self->logSettings);
29              
30 55         196 $copy->addNoRealize($_->clone) for $self->grepNames(@_);
31 55         193 $copy->modified(1);
32 55         191 $copy;
33             }
34              
35              
36             sub build(@)
37 4     4 1 100 { my $class = shift;
38 4         32 my $self = $class->new;
39 4         19 while(@_)
40 25         49 { my $name = shift;
41 25 50       55 defined $name or next;
42              
43 25 50       160 if($name->isa('Mail::Message::Field'))
44 0         0 { $self->add($name);
45 0         0 next;
46             }
47              
48 25         50 my $content = shift;
49 25 50       53 defined $content or next;
50              
51 25 50 33     62 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         57 $self->add($name, $content);
58             }
59              
60 4         16 $self;
61             }
62              
63             #------------------------------------------
64              
65              
66             sub isDelayed() {0}
67              
68              
69 47     47 1 2396 sub nrLines() { sum 1, map $_->nrLines, shift->orderedFields }
70 60     60 1 176 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 181     181 1 3515 { 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 181 50 33     1183 : ($self->{MMH_field_type} || 'Mail::Message::Field::Fast')->new(@_);
      50        
89              
90 181 50       407 return if !defined $field;
91              
92 181         597 $field->setWrapLength;
93              
94             # Put it in place.
95              
96 181         311 my $known = $self->{MMH_fields};
97 181         420 my $name = $field->name; # is already lower-cased
98              
99 181         607 $self->addOrderedFields($field);
100              
101 181 100       426 if(defined $known->{$name})
102 3 100       13 { if(ref $known->{$name} eq 'ARRAY') { push @{$known->{$name}}, $field }
  1         2  
  1         7  
103 2         9 else { $known->{$name} = [ $known->{$name}, $field ] }
104             }
105             else
106 178         385 { $known->{$name} = $field;
107             }
108              
109 181         295 $self->{MMH_modified}++;
110 181         448 $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 19     19 1 1827 sub names() {shift->knownNames}
125              
126              
127             sub grepNames(@)
128 61     61 1 112 { my $self = shift;
129 61         99 my @take;
130 61 50       136 push @take, (ref $_ eq 'ARRAY' ? @$_ : $_) foreach @_;
131              
132 61 100       210 return $self->orderedFields unless @take;
133              
134 6         12 my $take;
135 6 50 33     39 if(@take==1 && ref $take[0] eq 'Regexp')
136 6         14 { $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         20 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 479     479 1 1739 { my $self = shift;
155 479   50     1421 my $type = $self->{MMH_field_type} || 'Mail::Message::Field::Fast';
156 479         756 $self->{MMH_modified}++;
157              
158             # Create object for this field.
159 479 100 66     2132 my $field = @_==1 && ref $_[0] ? shift->clone : $type->new(@_);
160              
161 479         1196 my $name = $field->name; # is already lower-cased
162 479         847 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 479 100 100     1519 if($skip_none{$name} && $field->body eq 'none')
168 302         579 { delete $known->{$name};
169 302         907 return $field;
170             }
171              
172 177         600 $field->setWrapLength;
173 177         437 $known->{$name} = $field;
174              
175 177         604 $self->addOrderedFields($field);
176 177         365 $field;
177             }
178              
179              
180             sub reset($@)
181 12     12 1 2298 { my ($self, $name) = (shift, lc shift);
182              
183 12         31 my $known = $self->{MMH_fields};
184              
185 12 100       33 if(@_==0)
186 11 100       34 { $self->{MMH_modified}++ if delete $known->{$name};
187 11         29 return ();
188             }
189              
190 1         4 $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       3 if(@_==1) { $known->{$name} = $fields[0] }
  0         0  
198 1         3 else { $known->{$name} = [@fields] }
199              
200 1         5 $self->addOrderedFields(@fields);
201 1         5 $self;
202             }
203              
204              
205 10     10 1 32 sub delete($) { $_[0]->reset($_[1]) }
206              
207              
208             sub removeField($)
209 8     8 1 17 { my ($self, $field) = @_;
210 8         17 my $name = $field->name;
211 8         13 my $known = $self->{MMH_fields};
212              
213 8 50       66 if(!defined $known->{$name})
    100          
    50          
214             { ; } # complain
215             elsif(ref $known->{$name} eq 'ARRAY')
216 2         4 { for(my $i=0; $i < @{$known->{$name}}; $i++)
  2         7  
217             {
218 2         11 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         21 { 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 47     47 1 95 { my $self = shift;
234 47         226 (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 87     87 1 552 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 2229 { my $self = shift;
274 35   33     105 my $fh = shift || select;
275              
276             $_->print($fh)
277 35         240 foreach $self->orderedFields;
278              
279 35 50       116 if(ref $fh eq 'GLOB') { print $fh "\n" }
  0         0  
280 35         85 else { $fh->print("\n") }
281              
282 35         382 $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 693 sub toString() {shift->string}
322             sub string()
323 6     6 1 16 { my $self = shift;
324              
325 6         25 my @lines = map {$_->string} $self->orderedFields;
  44         126  
326 6         20 push @lines, "\n";
327              
328 6 50       50 wantarray ? @lines : join('', @lines);
329             }
330              
331              
332             sub resentGroups()
333 3     3 1 886 { my $self = shift;
334 3         438 require Mail::Message::Head::ResentGroup;
335 3         17 Mail::Message::Head::ResentGroup->from($self);
336             }
337              
338              
339             sub addResentGroup(@)
340 3     3 1 773 { my $self = shift;
341              
342 3         19 require Mail::Message::Head::ResentGroup;
343 3 100       22 my $rg = @_==1 ? (shift) : Mail::Message::Head::ResentGroup->new(@_);
344              
345 3         10 my @fields = $rg->orderedFields;
346 3         13 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       29 { next unless defined $order->[$i];
352 13 100       30 last if $rg->isResentGroupFieldName($order->[$i]->name);
353             }
354              
355 3         6 my $known = $self->{MMH_fields};
356 3         10 while(@fields)
357 15         22 { my $f = pop @fields;
358              
359             # Add to the order of fields
360 15         30 splice @$order, $i, 0, $f;
361 15         41 weaken( $order->[$i] );
362 15         30 my $name = $f->name;
363              
364             # Adds *before* in the list for get().
365 15 100       41 if(!defined $known->{$name}) {$known->{$name} = $f}
  13 50       33  
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         14 $rg->messageHead($self);
371              
372             # Oh, the header has changed!
373 3         12 $self->modified(1);
374              
375 3         6 $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 9 sub timestamp() {shift->guessTimestamp || time}
410              
411              
412             sub recvstamp()
413 2     2 1 7 { my $self = shift;
414              
415 2 100       11 return $self->{MMH_recvstamp} if exists $self->{MMH_recvstamp};
416              
417             my $recvd = $self->get('received', 0) or
418 1 50       4 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       14 return $self->{MMH_timestamp} if exists $self->{MMH_timestamp};
429              
430 1         3 my $stamp;
431 1 50       4 if(my $date = $self->get('date'))
432 1         10 { $stamp = Mail::Message::Field->dateToTimestamp($date);
433             }
434              
435 1 50       556 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 26     26 1 50 { my $self = shift;
447              
448 26         63 my $cl = $self->get('Content-Length');
449 26 50 33     109 return $1 if defined $cl && $cl =~ m/(\d+)/;
450              
451 26         77 my $lines = $self->get('Lines'); # 40 chars per lines
452 26 50 33     83 return $1 * 40 if defined $lines && $lines =~ m/(\d+)/;
453              
454 26         61 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 118   66 118 1 401 { $msgid_creator ||= $_[0]->messageIdPrefix;
473 118         309 $msgid_creator->(@_);
474             }
475              
476              
477             sub messageIdPrefix(;$$)
478 12     12 1 33 { my $thing = shift;
479 12 50 33     96 return $msgid_creator
480             unless @_ || !defined $msgid_creator;
481              
482 12 50 33     76 return $msgid_creator = shift
483             if @_==1 && ref $_[0] eq 'CODE';
484              
485 12   33     108 my $prefix = shift || "mailbox-$$";
486              
487 12         34 my $hostname = shift;
488 12 50       49 if(!defined $hostname)
489 12         937 { eval "require Net::Domain";
490 12 50       108782 $@ or $hostname = Net::Domain::hostfqdn();
491             }
492 12   0     82855 $hostname ||= hostname || 'localhost';
      33        
493              
494 12         705 eval "require Time::HiRes";
495 12 50       17376 if(Time::HiRes->can('gettimeofday'))
496             {
497             return $msgid_creator
498 118     118   425 = sub { my ($sec, $micro) = Time::HiRes::gettimeofday();
499 118         643 "$prefix-$sec-$micro\@$hostname";
500 12         147 };
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;