File Coverage

blib/lib/Mail/Message.pm
Criterion Covered Total %
statement 186 303 61.3
branch 66 196 33.6
condition 27 80 33.7
subroutine 44 65 67.6
pod 48 52 92.3
total 371 696 53.3


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;
10 34     34   8527 use vars '$VERSION';
  34         143  
  34         2149  
11             $VERSION = '3.013';
12              
13 34     34   222 use base 'Mail::Reporter';
  34         92  
  34         8800  
14              
15 34     34   242 use strict;
  34         71  
  34         690  
16 34     34   178 use warnings;
  34         72  
  34         849  
17              
18 34     34   14855 use Mail::Message::Part ();
  34         90  
  34         825  
19 34     34   16194 use Mail::Message::Head::Complete ();
  34         119  
  34         974  
20 34     34   14722 use Mail::Message::Construct ();
  34         79  
  34         803  
21              
22 34     34   7076 use Mail::Message::Body::Lines ();
  34         101  
  34         757  
23 34     34   15369 use Mail::Message::Body::Multipart ();
  34         83  
  34         837  
24 34     34   15352 use Mail::Message::Body::Nested ();
  34         99  
  34         773  
25              
26 34     34   381 use Carp;
  34         150  
  34         2000  
27 34     34   227 use Scalar::Util qw(weaken blessed);
  34         70  
  34         3191  
28              
29             BEGIN {
30 34 50   34   140599 unless($ENV{HARNESS_ACTIVE}) { # no tests during upgrade
31             # v3 splits Mail::Box in a few distributions
32 0         0 eval { require Mail::Box };
  0         0  
33 0   0     0 my $v = $Mail::Box::VERSION || 3;
34 0 0       0 $v >= 3 or die "You need to upgrade the Mail::Box module";
35             }
36             }
37              
38              
39             our $crlf_platform = $^O =~ m/win32/i;
40              
41             #------------------------------------------
42              
43              
44             sub init($)
45 127     127 0 301 { my ($self, $args) = @_;
46 127         424 $self->SUPER::init($args);
47              
48             # Field initializations also in coerce()
49 127   50     514 $self->{MM_modified} = $args->{modified} || 0;
50 127   50     455 $self->{MM_trusted} = $args->{trusted} || 0;
51              
52             # Set the header
53              
54 127         197 my $head;
55 127 100 33     351 if(defined($head = $args->{head})) { $self->head($head) }
  115 50       330  
56             elsif(my $msgid = $args->{messageId} || $args->{messageID})
57 0         0 { $self->takeMessageId($msgid);
58             }
59              
60             # Set the body
61 127 100       341 if(my $body = $args->{body})
62 14         35 { $self->{MM_body} = $body;
63 14         50 $body->message($self);
64             }
65              
66             $self->{MM_body_type} = $args->{body_type}
67 127 50       298 if defined $args->{body_type};
68              
69             $self->{MM_head_type} = $args->{head_type}
70 127 50       276 if defined $args->{head_type};
71              
72             $self->{MM_field_type} = $args->{field_type}
73 127 50       260 if defined $args->{field_type};
74              
75 127   50     467 my $labels = $args->{labels} || [];
76 127 50       473 my @labels = ref $labels eq 'ARRAY' ? @$labels : %$labels;
77 127 50       295 push @labels, deleted => $args->{deleted} if exists $args->{deleted};
78 127         313 $self->{MM_labels} = { @labels };
79              
80 127         352 $self;
81             }
82              
83              
84             sub clone(@)
85 13     13 1 495 { my ($self, %args) = @_;
86              
87             # First clone body, which may trigger head load as well. If head is
88             # triggered first, then it may be decided to be lazy on the body at
89             # moment. And then the body would be triggered.
90              
91 13         37 my ($head, $body) = ($self->head, $self->body);
92             $head = $head->clone
93 13 50 33     95 unless $args{shallow} || $args{shallow_head};
94              
95             $body = $body->clone
96 13 50 33     469 unless $args{shallow} || $args{shallow_body};
97              
98 13         98 my $clone = Mail::Message->new
99             ( head => $head
100             , body => $body
101             , $self->logSettings
102             );
103              
104 13         70 my $labels = $self->labels;
105 13         37 my %labels = %$labels;
106 13         22 delete $labels{deleted};
107              
108 13         31 $clone->{MM_labels} = \%labels;
109              
110 13         27 $clone->{MM_cloned} = $self;
111 13         42 weaken($clone->{MM_cloned});
112              
113 13         85 $clone;
114             }
115              
116             #------------------------------------------
117              
118              
119 44 50   44 1 307 sub messageId() { $_[0]->{MM_message_id} || $_[0]->takeMessageId}
120 0     0 0 0 sub messageID() {shift->messageId} # compatibility
121              
122              
123             sub container() { undef } # overridden by Mail::Message::Part
124              
125              
126             sub isPart() { 0 } # overridden by Mail::Message::Part
127              
128              
129             sub partNumber()
130 5     5 1 479 { my $self = shift;
131 5         15 my $cont = $self->container;
132 5 50       33 $cont ? $cont->partNumber : undef;
133             }
134              
135              
136 10     10 1 25 sub toplevel() { shift } # overridden by Mail::Message::Part
137              
138              
139             sub isDummy() { 0 }
140              
141              
142             sub print(;$)
143 31     31 1 485 { my $self = shift;
144 31   33     124 my $out = shift || select;
145              
146 31         153 $self->head->print($out);
147 31         72 my $body = $self->body;
148 31 50       139 $body->print($out) if $body;
149 31         70 $self;
150             }
151              
152              
153             sub write(;$)
154 0     0 1 0 { my $self = shift;
155 0   0     0 my $out = shift || select;
156              
157 0         0 $self->head->print($out);
158 0         0 $self->body->print($out);
159 0         0 $self;
160             }
161              
162              
163             my $default_mailer;
164              
165             sub send(@)
166 0     0 1 0 { my $self = shift;
167              
168             # Loosely coupled module
169 0         0 require Mail::Transport::Send;
170              
171 0         0 my $mailer;
172 0 0 0     0 $default_mailer = $mailer = shift
173             if ref $_[0] && $_[0]->isa('Mail::Transport::Send');
174              
175 0         0 my %args = @_;
176 0 0 0     0 if( ! $args{via} && defined $default_mailer )
177 0         0 { $mailer = $default_mailer;
178             }
179             else
180 0   0     0 { my $via = delete $args{via} || 'sendmail';
181 0         0 $default_mailer = $mailer = Mail::Transport->new(via => $via, %args);
182             }
183              
184 0         0 $mailer->send($self, %args);
185             }
186              
187              
188             sub size()
189 60     60 1 114 { my $self = shift;
190 60         118 $self->head->size + $self->body->size;
191             }
192              
193             #------------------------------------------
194              
195              
196             sub head(;$)
197 1059     1059 1 9184 { my $self = shift;
198 1059 100       4040 return $self->{MM_head} unless @_;
199              
200 141         208 my $head = shift;
201 141 50       296 unless(defined $head)
202 0         0 { delete $self->{MM_head};
203 0         0 return undef;
204             }
205              
206 141 50 33     869 $self->log(INTERNAL => "wrong type of head ($head) for message $self")
207             unless ref $head && $head->isa('Mail::Message::Head');
208              
209 141         754 $head->message($self);
210              
211 141 50       371 if(my $old = $self->{MM_head})
212 0 0       0 { $self->{MM_modified}++ unless $old->isDelayed;
213             }
214              
215 141         307 $self->{MM_head} = $head;
216              
217 141 50       913 $self->takeMessageId unless $head->isDelayed;
218              
219 141         276 $head;
220             }
221              
222              
223             sub get($)
224 189     189 1 302 { my $self = shift;
225 189   100     420 my $field = $self->head->get(shift) || return undef;
226 69         202 $field->body;
227             }
228              
229              
230             sub study($)
231 0 0   0 1 0 { my $head = shift->head or return;
232 0         0 scalar $head->study(@_); # return only last
233             }
234              
235              
236             sub from()
237 0 0   0 1 0 { my @from = shift->head->get('From') or return ();
238 0         0 map $_->addresses, @from;
239             }
240              
241              
242             sub sender()
243 0     0 1 0 { my $self = shift;
244 0   0     0 my $sender = $self->head->get('Sender') || $self->head->get('From')
245             || return ();
246              
247 0         0 ($sender->addresses)[0]; # first specified address
248             }
249              
250              
251 10     10 1 19 sub to() { map $_->addresses, shift->head->get('To') }
252              
253              
254 2     2 1 7 sub cc() { map $_->addresses, shift->head->get('Cc') }
255              
256              
257 0     0 1 0 sub bcc() { map $_->addresses, shift->head->get('Bcc') }
258              
259              
260             sub destinations()
261 0     0 1 0 { my $self = shift;
262 0         0 my %to = map +(lc($_->address) => $_), $self->to, $self->cc, $self->bcc;
263 0         0 values %to;
264             }
265              
266              
267             sub subject()
268 15     15 1 49 { my $subject = shift->get('subject');
269 15 100       79 defined $subject ? $subject : '';
270             }
271              
272              
273 0     0 1 0 sub guessTimestamp() {shift->head->guessTimestamp}
274              
275              
276             sub timestamp()
277 2     2 1 5 { my $head = shift->head;
278 2 50       14 $head->recvstamp || $head->timestamp;
279             }
280              
281              
282             sub nrLines()
283 44     44 1 83 { my $self = shift;
284 44         117 $self->head->nrLines + $self->body->nrLines;
285             }
286              
287             #-------------------------------------------
288              
289            
290             sub body(;$@)
291 548     548 1 6787 { my $self = shift;
292 548 100       2434 return $self->{MM_body} unless @_;
293              
294 87         180 my $head = $self->head;
295 87 50       385 $head->removeContentInfo if defined $head;
296              
297 87         255 my ($rawbody, %args) = @_;
298 87 50       234 unless(defined $rawbody)
299             { # Disconnect body from message.
300 0         0 my $body = delete $self->{MM_body};
301 0 0       0 $body->message(undef) if defined $body;
302 0         0 return $body;
303             }
304              
305 87 50 33     600 ref $rawbody && $rawbody->isa('Mail::Message::Body')
306             or $self->log(INTERNAL => "wrong type of body for message $rawbody");
307              
308             # Bodies of real messages must be encoded for safe transmission.
309             # Message parts will get encoded on the moment the whole multipart
310             # is transformed into a real message.
311              
312 87 100       411 my $body = $self->isPart ? $rawbody : $rawbody->encoded;
313 87         263 $body->contentInfoTo($self->head);
314              
315 87         212 my $oldbody = $self->{MM_body};
316 87 50 33     247 return $body if defined $oldbody && $body==$oldbody;
317              
318 87         322 $body->message($self);
319 87 50       179 $body->modified(1) if defined $oldbody;
320              
321 87         357 $self->{MM_body} = $body;
322             }
323              
324              
325             sub decoded(@)
326 2     2 1 9 { my $body = shift->body->load;
327 2 50       9 $body ? $body->decoded(@_) : undef;
328             }
329              
330              
331             sub encode(@)
332 0     0 1 0 { my $body = shift->body->load;
333 0 0       0 $body ? $body->encode(@_) : undef;
334             }
335              
336              
337 130     130 1 5211 sub isMultipart() {shift->head->isMultipart}
338              
339              
340 61     61 1 570 sub isNested() {shift->body->isNested}
341              
342              
343             sub contentType()
344 1     1 1 5 { my $head = shift->head;
345 1   50     8 my $ct = (defined $head ? $head->get('Content-Type', 0) : undef) || '';
346 1         3 $ct =~ s/\s*\;.*//;
347 1 50       39 length $ct ? $ct : 'text/plain';
348             }
349              
350              
351             sub parts(;$)
352 26     26 1 1970 { my $self = shift;
353 26   100     79 my $what = shift || 'ACTIVE';
354              
355 26         55 my $body = $self->body;
356 26   33     102 my $recurse = $what eq 'RECURSE' || ref $what;
357              
358             my @parts
359 26 50       166 = $body->isNested ? $body->nested->parts($what)
    50          
    100          
360             : $body->isMultipart ? $body->parts($recurse ? 'RECURSE' : ())
361             : $self;
362              
363 26 0       119 ref $what eq 'CODE' ? (grep $what->($_), @parts)
    0          
    0          
    50          
    50          
364             : $what eq 'ACTIVE' ? (grep !$_->isDeleted, @parts)
365             : $what eq 'DELETED' ? (grep $_->isDeleted, @parts)
366             : $what eq 'ALL' ? @parts
367             : $recurse ? @parts
368             : confess "Select parts via $what?";
369             }
370              
371             #------------------------------------------
372              
373              
374             sub modified(;$)
375 0     0 1 0 { my $self = shift;
376              
377 0 0       0 return $self->isModified unless @_; # compatibility 2.036
378              
379 0         0 my $flag = shift;
380 0         0 $self->{MM_modified} = $flag;
381 0         0 my $head = $self->head;
382 0 0       0 $head->modified($flag) if $head;
383 0         0 my $body = $self->body;
384 0 0       0 $body->modified($flag) if $body;
385              
386 0         0 $flag;
387             }
388              
389              
390             sub isModified()
391 0     0 1 0 { my $self = shift;
392 0 0       0 return 1 if $self->{MM_modified};
393              
394 0         0 my $head = $self->head;
395 0 0 0     0 if($head && $head->isModified)
396 0         0 { $self->{MM_modified}++;
397 0         0 return 1;
398             }
399              
400 0         0 my $body = $self->body;
401 0 0 0     0 if($body && $body->isModified)
402 0         0 { $self->{MM_modified}++;
403 0         0 return 1;
404             }
405              
406 0         0 0;
407             }
408              
409              
410             sub label($;$@)
411 283     283 1 430 { my $self = shift;
412 283 100       1400 return $self->{MM_labels}{$_[0]} unless @_ > 1;
413 17         29 my $return = $_[1];
414              
415 17         46 my %labels = @_;
416 17         55 @{$self->{MM_labels}}{keys %labels} = values %labels;
  17         52  
417 17         45 $return;
418             }
419              
420              
421             sub labels()
422 13     13 1 25 { my $self = shift;
423 13 50       39 wantarray ? keys %{$self->{MM_labels}} : $self->{MM_labels};
  0         0  
424             }
425              
426              
427 256     256 1 874 sub isDeleted() { shift->label('deleted') }
428              
429              
430             sub delete()
431 4     4 1 17 { my $self = shift;
432 4         10 my $old = $self->label('deleted');
433 4 50       18 $old || $self->label(deleted => time);
434             }
435              
436              
437             sub deleted(;$)
438 2     2 1 6 { my $self = shift;
439              
440 2 50       9 @_ ? $self->label(deleted => shift)
441             : $self->label('deleted') # compat 2.036
442             }
443              
444              
445             sub labelsToStatus()
446 0     0 1 0 { my $self = shift;
447 0         0 my $head = $self->head;
448 0         0 my $labels = $self->labels;
449              
450 0   0     0 my $status = $head->get('status') || '';
451             my $newstatus
452             = $labels->{seen} ? 'RO'
453 0 0       0 : $labels->{old} ? 'O'
    0          
454             : '';
455              
456 0 0       0 $head->set(Status => $newstatus)
457             if $newstatus ne $status;
458              
459 0   0     0 my $xstatus = $head->get('x-status') || '';
460             my $newxstatus
461             = ($labels->{replied} ? 'A' : '')
462 0 0       0 . ($labels->{flagged} ? 'F' : '');
    0          
463              
464 0 0       0 $head->set('X-Status' => $newxstatus)
465             if $newxstatus ne $xstatus;
466              
467 0         0 $self;
468             }
469              
470              
471             sub statusToLabels()
472 0     0 1 0 { my $self = shift;
473 0         0 my $head = $self->head;
474              
475 0 0       0 if(my $status = $head->get('status'))
476 0         0 { $status = $status->foldedBody;
477 0         0 $self->label
478             ( seen => (index($status, 'R') >= 0)
479             , old => (index($status, 'O') >= 0)
480             );
481             }
482              
483 0 0       0 if(my $xstatus = $head->get('x-status'))
484 0         0 { $xstatus = $xstatus->foldedBody;
485 0         0 $self->label
486             ( replied => (index($xstatus, 'A') >= 0)
487             , flagged => (index($xstatus, 'F') >= 0)
488             );
489             }
490              
491 0         0 $self;
492             }
493              
494             #------------------------------------------
495              
496              
497             my $mail_internet_converter;
498             my $mime_entity_converter;
499             my $email_simple_converter;
500              
501             sub coerce($@)
502 13     13 1 32 { my ($class, $message) = @_;
503              
504 13 50       61 blessed $message
505             or die "coercion starts with some object";
506              
507 13 50       183 return $message
508             if ref $message eq $class;
509              
510 13 50       56 if($message->isa(__PACKAGE__)) {
511 13         31 $message->head->modified(1);
512 13         40 $message->body->modified(1);
513 13         39 return bless $message, $class;
514             }
515              
516 0 0       0 if($message->isa('MIME::Entity'))
    0          
    0          
    0          
517 0 0       0 { unless($mime_entity_converter)
518 0         0 { eval {require Mail::Message::Convert::MimeEntity};
  0         0  
519 0 0       0 confess "Install MIME::Entity" if $@;
520 0         0 $mime_entity_converter = Mail::Message::Convert::MimeEntity->new;
521             }
522              
523 0 0       0 $message = $mime_entity_converter->from($message)
524             or return;
525             }
526              
527             elsif($message->isa('Mail::Internet'))
528 0 0       0 { unless($mail_internet_converter)
529 0         0 { eval {require Mail::Message::Convert::MailInternet};
  0         0  
530 0 0       0 confess "Install Mail::Internet" if $@;
531 0         0 $mail_internet_converter = Mail::Message::Convert::MailInternet->new;
532             }
533              
534 0 0       0 $message = $mail_internet_converter->from($message)
535             or return;
536             }
537              
538             elsif($message->isa('Email::Simple'))
539 0 0       0 { unless($email_simple_converter)
540 0         0 { eval {require Mail::Message::Convert::EmailSimple};
  0         0  
541 0 0       0 confess "Install Email::Simple" if $@;
542 0         0 $email_simple_converter = Mail::Message::Convert::EmailSimple->new;
543             }
544              
545 0 0       0 $message = $email_simple_converter->from($message)
546             or return;
547             }
548              
549             elsif($message->isa('Email::Abstract'))
550 0         0 { return $class->coerce($message->object);
551             }
552              
553             else
554 0         0 { $class->log(INTERNAL => "Cannot coerce a ". ref($message)
555             . " object into a ". __PACKAGE__." object");
556             }
557              
558 0   0     0 $message->{MM_modified} ||= 0;
559 0         0 bless $message, $class;
560             }
561              
562              
563 0     0 1 0 sub clonedFrom() { shift->{MM_cloned} }
564              
565             #------------------------------------------
566             # All next routines try to create compatibility with release < 2.0
567 0     0 0 0 sub isParsed() { not shift->isDelayed }
568 0     0 0 0 sub headIsRead() { not shift->head->isDelayed }
569              
570              
571             sub readFromParser($;$)
572 12     12 1 48 { my ($self, $parser, $bodytype) = @_;
573              
574             my $head = $self->readHead($parser)
575             || Mail::Message::Head::Complete->new
576             ( message => $self
577             , field_type => $self->{MM_field_type}
578 12   33     39 , $self->logSettings
579             );
580              
581 12 50       57 my $body = $self->readBody($parser, $head, $bodytype)
582             or return;
583              
584 12         47 $self->head($head);
585 12         67 $self->storeBody($body);
586 12         33 $self;
587             }
588              
589              
590             sub readHead($;$)
591 26     26 1 54 { my ($self, $parser) = (shift, shift);
592              
593             my $headtype = shift
594 26   50     134 || $self->{MM_head_type} || 'Mail::Message::Head::Complete';
595              
596             $headtype->new
597             ( message => $self
598             , field_type => $self->{MM_field_type}
599 26         131 , $self->logSettings
600             )->read($parser);
601             }
602              
603              
604             sub readBody($$;$$)
605 26     26 1 103 { my ($self, $parser, $head, $getbodytype) = @_;
606              
607             my $bodytype
608 26 0 50     127 = ! $getbodytype ? ($self->{MM_body_type} || 'Mail::Message::Body::Lines')
    50          
609             : ref $getbodytype ? $getbodytype->($self, $head)
610             : $getbodytype;
611              
612 26         53 my $body;
613 26 50       153 if($bodytype->isDelayed)
614 0         0 { $body = $bodytype->new
615             ( message => $self
616             , charset => undef # we do not know, autodetect after transfer decode
617             , $self->logSettings
618             );
619             }
620             else
621 26         142 { my $ct = $head->get('Content-Type', 0);
622 26 100       123 my $type = defined $ct ? lc($ct->body) : 'text/plain';
623              
624             # Be sure you have acceptable bodies for multiparts and nested.
625 26 100 66     171 if(substr($type, 0, 10) eq 'multipart/' && !$bodytype->isMultipart)
    100          
626 7         17 { $bodytype = 'Mail::Message::Body::Multipart';
627             }
628             elsif($type eq 'message/rfc822')
629 2   100     10 { my $enc = $head->get('Content-Transfer-Encoding') || 'none';
630 2 100 66     13 $bodytype = 'Mail::Message::Body::Nested'
631             if lc($enc) eq 'none' && ! $bodytype->isNested;
632             }
633              
634             $body = $bodytype->new
635             ( message => $self
636             , checked => $self->{MM_trusted}
637 26         101 , charset => undef
638             , $self->logSettings
639             );
640              
641 26         140 $body->contentInfoFrom($head);
642             }
643              
644 26         86 my $lines = $head->get('Lines'); # usually off-by-one
645 26         96 my $size = $head->guessBodySize;
646              
647 26 50       152 $body->read
648             ( $parser, $head, $getbodytype,
649             , $size, (defined $lines ? $lines : undef)
650             );
651             }
652              
653              
654             sub storeBody($)
655 26     26 1 54 { my ($self, $body) = @_;
656 26         65 $self->{MM_body} = $body;
657 26         88 $body->message($self);
658 26         43 $body;
659             }
660              
661              
662             sub isDelayed()
663 0     0 1 0 { my $body = shift->body;
664 0 0       0 !$body || $body->isDelayed;
665             }
666              
667              
668             sub takeMessageId(;$)
669 155     155 1 274 { my $self = shift;
670 155   100     491 my $msgid = (@_ ? shift : $self->get('Message-ID')) || '';
671              
672 155 100       592 if($msgid =~ m/\<([^>]*)\>/s)
673 44         127 { $msgid = $1;
674 44         106 $msgid =~ s/\s//gs;
675             }
676            
677 155 100       387 $msgid = $self->head->createMessageId
678             unless length $msgid;
679              
680 155         431 $self->{MM_message_id} = $msgid;
681             }
682              
683             #------------------------------------------
684              
685              
686             sub shortSize(;$)
687 0     0 1   { my $self = shift;
688 0           my $size = shift;
689 0 0         $size = $self->head->guessBodySize unless defined $size;
690              
691 0 0         !defined $size ? '?'
    0          
    0          
    0          
    0          
692             : $size < 1_000 ? sprintf "%3d " , $size
693             : $size < 10_000 ? sprintf "%3.1fK", $size/1024
694             : $size < 1_000_000 ? sprintf "%3.0fK", $size/1024
695             : $size < 10_000_000 ? sprintf "%3.1fM", $size/(1024*1024)
696             : sprintf "%3.0fM", $size/(1024*1024);
697             }
698              
699              
700             sub shortString()
701 0     0 1   { my $self = shift;
702 0           sprintf "%4s %-30.30s", $self->shortSize, $self->subject;
703             }
704              
705             #------------------------------------------
706              
707              
708 0     0 1   sub destruct() { $_[0] = undef }
709              
710             #------------------------------------------
711              
712              
713             1;