File Coverage

blib/lib/Mail/Message.pm
Criterion Covered Total %
statement 195 291 67.0
branch 74 192 38.5
condition 34 94 36.1
subroutine 44 64 68.7
pod 49 53 92.4
total 396 694 57.0


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Message version 4.04.
2             # The POD got stripped from this file by OODoc version 3.06.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2026 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Message;{
13             our $VERSION = '4.04';
14             }
15              
16 37     37   159082 use parent 'Mail::Reporter';
  37         5948  
  37         269  
17              
18 37     37   2828 use strict;
  37         163  
  37         1030  
19 37     37   188 use warnings;
  37         79  
  37         2381  
20              
21 37     37   241 use Log::Report 'mail-message', import => [ qw/__x error info panic/ ];
  37         64  
  37         404  
22              
23 37     37   29227 use Mail::Message::Part ();
  37         114  
  37         1187  
24 37     37   23339 use Mail::Message::Head::Complete ();
  37         213  
  37         1676  
25 37     37   20768 use Mail::Message::Construct ();
  37         138  
  37         1209  
26              
27 37     37   11469 use Mail::Message::Body::Lines ();
  37         113  
  37         1208  
28 37     37   23203 use Mail::Message::Body::Multipart ();
  37         134  
  37         1558  
29 37     37   22781 use Mail::Message::Body::Nested ();
  37         164  
  37         1564  
30              
31 37     37   272 use Scalar::Util qw/weaken blessed/;
  37         75  
  37         193690  
32              
33             #--------------------
34              
35             our $crlf_platform = $^O =~ m/win32/i;
36              
37              
38             sub init($)
39 177     177 0 758 { my ($self, $args) = @_;
40 177         2827 $self->SUPER::init($args);
41              
42             # Field initializations also in coerce()
43 177   50     1147 $self->{MM_modified} = $args->{modified} || 0;
44 177   50     868 $self->{MM_trusted} = $args->{trusted} || 0;
45              
46             # Set the header
47              
48 177         289 my $head;
49 177 100 33     845 if(defined($head = $args->{head})) { $self->head($head) }
  130 50       432  
50             elsif(my $msgid = $args->{messageId} || $args->{messageID})
51 0         0 { $self->takeMessageId($msgid);
52             }
53              
54             # Set the body
55 177 100       621 if(my $body = $args->{body})
56 14         71 { $self->{MM_body} = $body;
57 14         52 $body->message($self);
58             }
59              
60 177 50       536 $self->{MM_body_type} = $args->{body_type} if defined $args->{body_type};
61 177 100       539 $self->{MM_head_type} = $args->{head_type} if defined $args->{head_type};
62 177 50       502 $self->{MM_field_type} = $args->{field_type} if defined $args->{field_type};
63              
64 177   50     818 my $labels = $args->{labels} || [];
65 177 50       726 my @labels = ref $labels eq 'ARRAY' ? @$labels : %$labels;
66 177 50       463 push @labels, deleted => $args->{deleted} if exists $args->{deleted};
67 177         648 $self->{MM_labels} = { @labels };
68              
69 177         648 $self;
70             }
71              
72              
73             sub clone(@)
74 13     13 1 913 { my ($self, %args) = @_;
75              
76             # First clone body, which may trigger head load as well. If head is
77             # triggered first, then it may be decided to be lazy on the body at
78             # moment. And then the body would be triggered.
79              
80 13         34 my ($head, $body) = ($self->head, $self->body);
81 13 50 33     112 $head = $head->clone unless $args{shallow} || $args{shallow_head};
82 13 50 33     190 $body = $body->clone unless $args{shallow} || $args{shallow_body};
83 13         96 my $clone = Mail::Message->new(head => $head, body => $body);
84              
85 13         35 my %labels = %{$self->labels};
  13         58  
86 13         21 delete $labels{deleted};
87 13         37 $clone->{MM_labels} = \%labels;
88              
89 13         24 $clone->{MM_cloned} = $self;
90 13         26 weaken($clone->{MM_cloned});
91              
92 13         89 $clone;
93             }
94              
95             #--------------------
96              
97 77 50   77 1 1385 sub messageId() { $_[0]->{MM_message_id} || $_[0]->takeMessageId}
98 0     0 0 0 sub messageID() { $_[0]->messageId } # compatibility
99              
100              
101             sub container() { undef } # overridden by Mail::Message::Part
102              
103              
104             sub isPart() { 0 } # overridden by Mail::Message::Part
105              
106              
107             sub partNumber()
108 5     5 1 1105 { my $self = shift;
109 5         18 my $cont = $self->container;
110 5 50       33 $cont ? $cont->partNumber : undef;
111             }
112              
113              
114 10     10 1 36 sub toplevel() { $_[0] } # overridden by Mail::Message::Part
115              
116              
117             sub isDummy() { 0 }
118              
119              
120 0     0 1 0 sub endsOnNewline() { $_[0]->body->endsOnNewline }
121              
122              
123             sub print(;$)
124 35     35 1 575 { my $self = shift;
125 35   33     103 my $out = shift || select;
126              
127 35         177 $self->head->print($out);
128 35         108 my $body = $self->body;
129 35 50       151 $body->print($out) if $body;
130              
131             # We need a new-line at the end of a full message.
132 35 50 66     142 ref $self ne __PACKAGE__ or $body->endsOnNewline or $out->print("\n");
133 35         93 $self;
134             }
135              
136              
137             sub write(;$)
138 0     0 1 0 { my $self = shift;
139 0   0     0 my $out = shift || select;
140              
141 0         0 $self->head->print($out);
142 0         0 $self->body->print($out);
143 0         0 $self;
144             }
145              
146              
147             my $default_mailer;
148              
149             sub send(@)
150 0     0 1 0 { my $self = shift;
151              
152             # Loosely coupled module
153 0         0 require Mail::Transport::Send;
154              
155 0         0 my $mailer;
156 0 0 0     0 $default_mailer = $mailer = shift
157             if ref $_[0] && $_[0]->isa('Mail::Transport::Send');
158              
159 0         0 my %args = @_;
160 0 0 0     0 if( ! $args{via} && defined $default_mailer )
161 0         0 { $mailer = $default_mailer;
162             }
163             else
164 0   0     0 { my $via = delete $args{via} || 'sendmail';
165 0         0 $default_mailer = $mailer = Mail::Transport->new(via => $via, %args);
166             }
167              
168 0         0 $mailer->send($self, %args);
169             }
170              
171              
172             sub size()
173 60     60 1 103 { my $self = shift;
174 60         183 $self->head->size + $self->body->size;
175             }
176              
177             #--------------------
178              
179             sub head(;$)
180 1408     1408 1 18076 { my $self = shift;
181 1408 100       6316 @_ or return $self->{MM_head};
182              
183 204         336 my $head = shift;
184 204 50       578 unless(defined $head)
185 0         0 { delete $self->{MM_head};
186 0         0 return undef;
187             }
188 204 50 33     2433 blessed $head && $head->isa('Mail::Message::Head') or panic;
189              
190 204         816 $head->message($self);
191              
192 204 50       619 if(my $old = $self->{MM_head})
193 0 0       0 { $self->{MM_modified}++ unless $old->isDelayed;
194             }
195              
196 204         435 $self->{MM_head} = $head;
197 204 50       1239 $self->takeMessageId unless $head->isDelayed;
198 204         694 $head;
199             }
200              
201              
202             sub get($)
203 254     254 1 461 { my $self = shift;
204 254 100       688 my $field = $self->head->get(shift) or return undef;
205 99         346 $field->body;
206             }
207              
208              
209             sub study($)
210 0 0   0 1 0 { my $head = shift->head or return;
211 0         0 scalar $head->study(@_); # return only last
212             }
213              
214              
215             sub from()
216 0 0   0 1 0 { my @from = shift->head->get('From') or return ();
217 0         0 map $_->addresses, @from;
218             }
219              
220              
221             sub sender()
222 0     0 1 0 { my $self = shift;
223 0   0     0 my $sender = $self->head->get('Sender') || $self->head->get('From');
224 0 0       0 $sender ? ($sender->addresses)[0] : (); # first specified address
225             }
226              
227              
228 10     10 1 36 sub to() { map $_->addresses, $_[0]->head->get('To') }
229              
230              
231 2     2 1 7 sub cc() { map $_->addresses, $_[0]->head->get('Cc') }
232              
233              
234 0     0 1 0 sub bcc() { map $_->addresses, $_[0]->head->get('Bcc') }
235              
236              
237             sub destinations()
238 0     0 1 0 { my $self = shift;
239 0         0 my %to = map +(lc($_->address) => $_), $self->to, $self->cc, $self->bcc;
240 0         0 values %to;
241             }
242              
243              
244 16   100 16 1 67 sub subject() { $_[0]->get('subject') // '' }
245              
246              
247 0     0 1 0 sub guessTimestamp() { $_[0]->head->guessTimestamp }
248              
249              
250             sub timestamp()
251 2     2 1 7 { my $head = $_[0]->head;
252 2 50       16 $head->recvstamp || $head->timestamp;
253             }
254              
255              
256             sub nrLines()
257 44     44 1 76 { my $self = shift;
258 44         117 $self->head->nrLines + $self->body->nrLines;
259             }
260              
261             #--------------------
262              
263             sub body(;$@)
264 588     588 1 18087 { my $self = shift;
265 588 100       2935 @_ or return $self->{MM_body};
266              
267 87         228 my $head = $self->head;
268 87 50       489 $head->removeContentInfo if defined $head;
269              
270 87         329 my ($rawbody, %args) = @_;
271 87 50       257 unless(defined $rawbody)
272             { # Disconnect body from message.
273 0         0 my $body = delete $self->{MM_body};
274 0 0       0 $body->message(undef) if defined $body;
275 0         0 return $body;
276             }
277 87 50 33     737 blessed $rawbody && $rawbody->isa('Mail::Message::Body') or panic;
278              
279             # Bodies of real messages must be encoded for safe transmission.
280             # Message parts will get encoded on the moment the whole multipart
281             # is transformed into a real message.
282              
283 87 100       509 my $body = $self->isPart ? $rawbody : $rawbody->encoded;
284 87         425 $body->contentInfoTo($self->head);
285              
286 87         248 my $oldbody = $self->{MM_body};
287 87 50 33     269 return $body if defined $oldbody && $body==$oldbody;
288              
289 87         518 $body->message($self);
290 87 50       205 $body->modified(1) if defined $oldbody;
291              
292 87         402 $self->{MM_body} = $body;
293             }
294              
295              
296             sub decoded(@)
297 2     2 1 10 { my $body = shift->body->load;
298 2 50       12 $body ? $body->decoded(@_) : undef;
299             }
300              
301              
302             sub encode(@)
303 0     0 1 0 { my $body = shift->body->load;
304 0 0       0 $body ? $body->encode(@_) : undef;
305             }
306              
307              
308 132     132 1 7839 sub isMultipart() { $_[0]->head->isMultipart }
309              
310              
311 61     61 1 1401 sub isNested() { $_[0]->body->isNested }
312              
313              
314             sub contentType()
315 1     1 1 3 { my $head = shift->head;
316 1 50 50     5 my $ct = (defined $head ? $head->get('Content-Type', 0) : undef) // '';
317 1         3 $ct =~ s/\s*\;.*//;
318 1 50       11 length $ct ? $ct : 'text/plain';
319             }
320              
321              
322             sub parts(;$)
323 28     28 1 2418 { my $self = shift;
324 28   100     133 my $what = shift || 'ACTIVE';
325              
326 28         77 my $body = $self->body;
327 28   33     134 my $recurse = $what eq 'RECURSE' || ref $what;
328              
329             my @parts
330 28 50       242 = $body->isNested ? $body->nested->parts($what)
    50          
    100          
331             : $body->isMultipart ? $body->parts($recurse ? 'RECURSE' : ())
332             : $self;
333              
334 28 0       177 ref $what eq 'CODE' ? (grep $what->($_), @parts)
    0          
    0          
    50          
    50          
335             : $what eq 'ACTIVE' ? (grep !$_->isDeleted, @parts)
336             : $what eq 'DELETED' ? (grep $_->isDeleted, @parts)
337             : $what eq 'ALL' ? @parts
338             : $recurse ? @parts
339             : error __x"select parts via '{what}'?", what => $what;
340             }
341              
342             #--------------------
343              
344             sub modified(;$)
345 0     0 1 0 { my $self = shift;
346 0 0       0 @_ or return $self->isModified; # compatibility 2.036
347              
348 0         0 my $flag = $self->{MM_modified} = shift;
349              
350 0         0 my $head = $self->head;
351 0 0       0 $head->modified($flag) if $head;
352              
353 0         0 my $body = $self->body;
354 0 0       0 $body->modified($flag) if $body;
355              
356 0         0 $flag;
357             }
358              
359              
360             sub isModified()
361 22     22 1 98 { my $self = shift;
362 22 50       75 return 1 if $self->{MM_modified};
363              
364 22         78 my $head = $self->head;
365 22 50 33     54 if($head && $head->isModified)
366 0         0 { $self->{MM_modified}++;
367 0         0 return 1;
368             }
369              
370 22         51 my $body = $self->body;
371 22 50 33     53 if($body && $body->isModified)
372 0         0 { $self->{MM_modified}++;
373 0         0 return 1;
374             }
375              
376 22         47 0;
377             }
378              
379              
380             sub label($;$@)
381 320     320 1 656 { my $self = shift;
382 320 100       1733 @_ > 1 or return $self->{MM_labels}{$_[0]};
383              
384 35         151 my %labels = @_;
385 35         117 @{$self->{MM_labels}}{keys %labels} = values %labels;
  35         140  
386 35         136 $_[1];
387             }
388              
389              
390             sub labels()
391 13     13 1 22 { my $self = shift;
392 13 50       59 wantarray ? keys %{$self->{MM_labels}} : $self->{MM_labels};
  0         0  
393             }
394              
395              
396 275     275 1 891 sub isDeleted() { $_[0]->label('deleted') }
397              
398              
399             sub delete()
400 4     4 1 22 { my $self = shift;
401 4         15 my $old = $self->label('deleted');
402 4 50       22 $old || $self->label(deleted => time);
403             }
404              
405              
406             sub deleted(;$)
407 2     2 1 4 { my $self = shift;
408 2 50       11 @_ ? $self->label(deleted => shift)
409             : $self->label('deleted') # compat 2.036
410             }
411              
412              
413             sub labelsToStatus()
414 0     0 1 0 { my $self = shift;
415 0         0 my $head = $self->head;
416 0         0 my $labels = $self->labels;
417              
418 0   0     0 my $status = $head->get('status') || '';
419 0 0       0 my $newstatus = $labels->{seen} ? 'RO' : $labels->{old} ? 'O' : '';
    0          
420              
421 0   0     0 my $xstatus = $head->get('x-status') || '';
422 0 0       0 my $newxstatus = ($labels->{replied} ? 'A' : '') . ($labels->{flagged} ? 'F' : '');
    0          
423              
424 0 0       0 $head->set(Status => $newstatus) if $newstatus ne $status;
425 0 0       0 $head->set('X-Status' => $newxstatus) if $newxstatus ne $xstatus;
426              
427 0         0 $self;
428             }
429              
430              
431             sub statusToLabels()
432 29     29 1 118 { my $self = shift;
433 29         93 my $head = $self->head;
434              
435 29 100       93 if(my $status = $head->get('status'))
436 15         114 { $status = $status->foldedBody;
437 15         112 $self->label(seen => (index($status, 'R') >= 0), old => (index($status, 'O') >= 0));
438             }
439              
440 29 100       94 if(my $xstatus = $head->get('x-status'))
441 1         4 { $xstatus = $xstatus->foldedBody;
442 1         6 $self->label(replied => (index($xstatus, 'A') >= 0), flagged => (index($xstatus, 'F') >= 0));
443             }
444              
445 29         64 $self;
446             }
447              
448             #--------------------
449              
450             my $mail_internet_converter;
451             my $mime_entity_converter;
452             my $email_simple_converter;
453              
454             sub coerce($@)
455 13     13 1 32 { my ($class, $message) = @_;
456              
457 13 50 0     34 blessed $message
458             or error __x"coercion starts with some object, not '{type}'.", type => ref $message // $message ;
459              
460 13 50       38 return $message
461             if ref $message eq $class;
462              
463 13 50       43 if($message->isa(__PACKAGE__)) {
464 13         36 $message->head->modified(1);
465 13         37 $message->body->modified(1);
466 13         41 return bless $message, $class;
467             }
468              
469 0 0       0 if($message->isa('MIME::Entity'))
    0          
    0          
    0          
470 0 0       0 { unless($mime_entity_converter)
471 0         0 { eval {require Mail::Message::Convert::MimeEntity};
  0         0  
472 0 0       0 $@ and error __x"please install MIME::Entity.";
473 0         0 $mime_entity_converter = Mail::Message::Convert::MimeEntity->new;
474             }
475              
476 0 0       0 $message = $mime_entity_converter->from($message)
477             or return;
478             }
479              
480             elsif($message->isa('Mail::Internet'))
481 0 0       0 { unless($mail_internet_converter)
482 0         0 { eval { require Mail::Message::Convert::MailInternet };
  0         0  
483 0 0       0 $@ and error __x"please install Mail::Internet.";
484 0         0 $mail_internet_converter = Mail::Message::Convert::MailInternet->new;
485             }
486              
487 0 0       0 $message = $mail_internet_converter->from($message)
488             or return;
489             }
490              
491             elsif($message->isa('Email::Simple'))
492 0 0       0 { unless($email_simple_converter)
493 0         0 { eval {require Mail::Message::Convert::EmailSimple};
  0         0  
494 0 0       0 $@ and error __x"please install Email::Simple.";
495 0         0 $email_simple_converter = Mail::Message::Convert::EmailSimple->new;
496             }
497              
498 0 0       0 $message = $email_simple_converter->from($message)
499             or return;
500             }
501              
502             elsif($message->isa('Email::Abstract'))
503 0         0 { return $class->coerce($message->object);
504             }
505              
506             else
507 0         0 { error __x"cannot coerce a {type} object into a {me} object.", type => ref $message, me => __PACKAGE__;
508             }
509              
510 0   0     0 $message->{MM_modified} ||= 0;
511 0         0 bless $message, $class;
512             }
513              
514              
515 0     0 1 0 sub clonedFrom() { $_[0]->{MM_cloned} }
516              
517             # All next routines try to create compatibility with release < 2.0
518 0     0 0 0 sub isParsed() { not $_[0]->isDelayed }
519 0     0 0 0 sub headIsRead() { not $_[0]->head->isDelayed }
520              
521              
522             sub readFromParser($;$)
523 45     45 1 1572 { my ($self, $parser, $bodytype) = @_;
524              
525             my $head = $self->readHead($parser) //
526 45   33     163 Mail::Message::Head::Complete->new(message => $self, field_type => $self->{MM_field_type});
527              
528 45 50       350 my $body = $self->readBody($parser, $head, $bodytype) or return;
529 45         255 $self->head($head);
530 45         275 $self->storeBody($body);
531 45         193 $self;
532             }
533              
534              
535             sub readHead($;$)
536 74     74 1 205 { my ($self, $parser, $headtype) = @_;
537 74   100     560 $headtype //= $self->{MM_head_type} // 'Mail::Message::Head::Complete';
      33        
538              
539             $headtype->new(message => $self, field_type => $self->{MM_field_type})
540 74         630 ->read($parser);
541             }
542              
543              
544             sub readBody($$;$$)
545 74     74 1 980 { my ($self, $parser, $head, $getbodytype) = @_;
546              
547             my $bodytype
548 74 100 0     336 = ! $getbodytype ? ($self->{MM_body_type} // 'Mail::Message::Body::Lines')
    50          
549             : ref $getbodytype ? $getbodytype->($self, $head)
550             : $getbodytype;
551              
552 74         415 my $body;
553 74 50       501 if($bodytype->isDelayed)
554             { # autodetect charset after transfer decoding.
555 0         0 $body = $bodytype->new(message => $self, charset => undef);
556             }
557             else
558 74         268 { my $ct = $head->get('Content-Type', 0);
559 74 100       319 my $type = defined $ct ? lc($ct->body) : 'text/plain';
560              
561             # Be sure you have acceptable bodies for multiparts and nested.
562 74 100 66     468 if(substr($type, 0, 10) eq 'multipart/' && !$bodytype->isMultipart)
    100          
563 16         36 { $bodytype = 'Mail::Message::Body::Multipart';
564             }
565             elsif($type eq 'message/rfc822')
566             { # RFC2046 forbids the extras of RFC6532, but Outlook implemented it anyway:
567             # transfer encoding of this part. In that case, do not use a ::Nested
568 2   100     10 my $enc = $head->get('Content-Transfer-Encoding') || 'none';
569              
570 2 100 66     16 $bodytype = 'Mail::Message::Body::Nested'
571             if $enc =~ m/^(?:none|7bit|8bit|binary)$/i && ! $bodytype->isNested;
572             }
573              
574 74         559 $body = $bodytype->new(message => $self, checked => $self->{MM_trusted}, charset => undef);
575 74         370 $body->contentInfoFrom($head);
576             }
577              
578 74         377 my $lines = $head->get('Lines'); # usually off-by-one
579 74         290 my $size = $head->guessBodySize;
580 74         367 $body->read($parser, $head, $getbodytype, $size, $lines);
581             }
582              
583              
584             sub storeBody($)
585 74     74 1 183 { my ($self, $body) = @_;
586 74         211 $self->{MM_body} = $body;
587 74         474 $body->message($self);
588 74         154 $body;
589             }
590              
591              
592             sub isDelayed()
593 0     0 1 0 { my $body = shift->body;
594 0 0       0 !$body || $body->isDelayed;
595             }
596              
597              
598             sub takeMessageId(;$)
599 218     218 1 402 { my $self = shift;
600 218   100     853 my $msgid = (@_ ? shift : $self->get('Message-ID')) || '';
601              
602 218 100       894 if($msgid =~ m/\<([^>]*)\>/s)
603 73         411 { $msgid = $1 =~ s/\s//grs;
604             }
605              
606 218   66     945 $self->{MM_message_id} = $msgid || $self->head->createMessageId;
607             }
608              
609             #--------------------
610              
611             sub shortSize(;$)
612 0     0 1   { my $self = shift;
613 0   0       my $size = shift // $self->head->guessBodySize;
614              
615 0 0         !defined $size ? '?'
    0          
    0          
    0          
    0          
616             : $size < 1_000 ? sprintf "%3d " , $size
617             : $size < 10_000 ? sprintf "%3.1fK", $size/1024
618             : $size < 1_000_000 ? sprintf "%3.0fK", $size/1024
619             : $size < 10_000_000 ? sprintf "%3.1fM", $size/(1024*1024)
620             : sprintf "%3.0fM", $size/(1024*1024);
621             }
622              
623              
624             sub shortString()
625 0     0 1   { my $self = shift;
626 0           sprintf "%4s %-30.30s", $self->shortSize, $self->subject;
627             }
628              
629             #--------------------
630              
631 0     0 1   sub destruct() { $_[0] = undef }
632              
633             #--------------------
634              
635             1;