File Coverage

blib/lib/Mail/Message.pm
Criterion Covered Total %
statement 181 302 59.9
branch 62 194 31.9
condition 24 79 30.3
subroutine 43 65 66.1
pod 48 52 92.3
total 358 692 51.7


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;
10 31     31   8781 use vars '$VERSION';
  31         150  
  31         2096  
11             $VERSION = '3.011';
12              
13 31     31   195 use base 'Mail::Reporter';
  31         106  
  31         8304  
14              
15 31     31   219 use strict;
  31         62  
  31         685  
16 31     31   149 use warnings;
  31         66  
  31         950  
17              
18 31     31   14894 use Mail::Message::Part ();
  31         77  
  31         696  
19 31     31   15510 use Mail::Message::Head::Complete ();
  31         105  
  31         1277  
20 31     31   16163 use Mail::Message::Construct ();
  31         86  
  31         740  
21              
22 31     31   6838 use Mail::Message::Body::Lines ();
  31         88  
  31         719  
23 31     31   15504 use Mail::Message::Body::Multipart ();
  31         170  
  31         909  
24 31     31   16460 use Mail::Message::Body::Nested ();
  31         97  
  31         760  
25              
26 31     31   254 use Carp;
  31         97  
  31         1805  
27 31     31   194 use Scalar::Util qw(weaken blessed);
  31         68  
  31         2874  
28              
29             BEGIN {
30 31 50   31   129078 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 106     106 0 254 { my ($self, $args) = @_;
46 106         360 $self->SUPER::init($args);
47              
48             # Field initializations also in coerce()
49 106   50     421 $self->{MM_modified} = $args->{modified} || 0;
50 106   50     405 $self->{MM_trusted} = $args->{trusted} || 0;
51              
52             # Set the header
53              
54 106         164 my $head;
55 106 100 33     287 if(defined($head = $args->{head})) { $self->head($head) }
  99 50       282  
56             elsif(my $msgid = $args->{messageId} || $args->{messageID})
57 0         0 { $self->takeMessageId($msgid);
58             }
59              
60             # Set the body
61 106 100       344 if(my $body = $args->{body})
62 11         27 { $self->{MM_body} = $body;
63 11         47 $body->message($self);
64             }
65              
66             $self->{MM_body_type} = $args->{body_type}
67 106 50       277 if defined $args->{body_type};
68              
69             $self->{MM_head_type} = $args->{head_type}
70 106 50       344 if defined $args->{head_type};
71              
72             $self->{MM_field_type} = $args->{field_type}
73 106 50       243 if defined $args->{field_type};
74              
75 106   50     424 my $labels = $args->{labels} || [];
76 106 50       398 my @labels = ref $labels eq 'ARRAY' ? @$labels : %$labels;
77 106 50       271 push @labels, deleted => $args->{deleted} if exists $args->{deleted};
78 106         306 $self->{MM_labels} = { @labels };
79              
80 106         324 $self;
81             }
82              
83              
84             sub clone(@)
85 10     10 1 369 { 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 10         31 my ($head, $body) = ($self->head, $self->body);
92             $head = $head->clone
93 10 50 33     84 unless $args{shallow} || $args{shallow_head};
94              
95             $body = $body->clone
96 10 50 33     83 unless $args{shallow} || $args{shallow_body};
97              
98 10         66 my $clone = Mail::Message->new
99             ( head => $head
100             , body => $body
101             , $self->logSettings
102             );
103              
104 10         57 my $labels = $self->labels;
105 10         36 my %labels = %$labels;
106 10         19 delete $labels{deleted};
107              
108 10         25 $clone->{MM_labels} = \%labels;
109              
110 10         22 $clone->{MM_cloned} = $self;
111 10         35 weaken($clone->{MM_cloned});
112              
113 10         58 $clone;
114             }
115              
116             #------------------------------------------
117              
118              
119 39 50   39 1 296 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 2     2 1 373 { my $self = shift;
131 2         9 my $cont = $self->container;
132 2 50       14 $cont ? $cont->partNumber : undef;
133             }
134              
135              
136 10     10 1 46 sub toplevel() { shift } # overridden by Mail::Message::Part
137              
138              
139             sub isDummy() { 0 }
140              
141              
142             sub print(;$)
143 31     31 1 522 { my $self = shift;
144 31   33     88 my $out = shift || select;
145              
146 31         163 $self->head->print($out);
147 31         77 my $body = $self->body;
148 31 50       116 $body->print($out) if $body;
149 31         63 $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 55     55 1 96 { my $self = shift;
190 55         112 $self->head->size + $self->body->size;
191             }
192              
193             #------------------------------------------
194              
195              
196             sub head(;$)
197 930     930 1 6894 { my $self = shift;
198 930 100       3846 return $self->{MM_head} unless @_;
199              
200 111         174 my $head = shift;
201 111 50       284 unless(defined $head)
202 0         0 { delete $self->{MM_head};
203 0         0 return undef;
204             }
205              
206 111 50 33     757 $self->log(INTERNAL => "wrong type of head ($head) for message $self")
207             unless ref $head && $head->isa('Mail::Message::Head');
208              
209 111         427 $head->message($self);
210              
211 111 50       370 if(my $old = $self->{MM_head})
212 0 0       0 { $self->{MM_modified}++ unless $old->isDelayed;
213             }
214              
215 111         229 $self->{MM_head} = $head;
216              
217 111 50       615 $self->takeMessageId unless $head->isDelayed;
218              
219 111         233 $head;
220             }
221              
222              
223             sub get($)
224 159     159 1 251 { my $self = shift;
225 159   100     387 my $field = $self->head->get(shift) || return undef;
226 66         193 $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 32 sub to() { map $_->addresses, shift->head->get('To') }
252              
253              
254 2     2 1 6 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 58 { my $subject = shift->get('subject');
269 15 100       93 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 8 { my $head = shift->head;
278 2 50       16 $head->recvstamp || $head->timestamp;
279             }
280              
281              
282             sub nrLines()
283 39     39 1 64 { my $self = shift;
284 39         88 $self->head->nrLines + $self->body->nrLines;
285             }
286              
287             #-------------------------------------------
288              
289            
290             sub body(;$@)
291 511     511 1 5813 { my $self = shift;
292 511 100       2004 return $self->{MM_body} unless @_;
293              
294 83         186 my $head = $self->head;
295 83 50       429 $head->removeContentInfo if defined $head;
296              
297 83         303 my ($rawbody, %args) = @_;
298 83 50       298 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 83 50 33     681 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 83 100       448 my $body = $self->isPart ? $rawbody : $rawbody->encoded;
313 83         315 $body->contentInfoTo($self->head);
314              
315 83         192 my $oldbody = $self->{MM_body};
316 83 50 33     257 return $body if defined $oldbody && $body==$oldbody;
317              
318 83         371 $body->message($self);
319 83 50       218 $body->modified(1) if defined $oldbody;
320              
321 83         377 $self->{MM_body} = $body;
322             }
323              
324              
325             sub decoded(@)
326 2     2 1 10 { my $body = shift->body->load;
327 2 50       7 $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 3308 sub isMultipart() {shift->head->isMultipart}
338              
339              
340 60     60 1 464 sub isNested() {shift->body->isNested}
341              
342              
343             sub contentType()
344 0     0 1 0 { my $head = shift->head;
345 0   0     0 my $ct = (defined $head ? $head->get('Content-Type', 0) : undef) || '';
346 0         0 $ct =~ s/\s*\;.*//;
347 0 0       0 length $ct ? $ct : 'text/plain';
348             }
349              
350              
351             sub parts(;$)
352 22     22 1 944 { my $self = shift;
353 22   100     79 my $what = shift || 'ACTIVE';
354              
355 22         52 my $body = $self->body;
356 22   33     94 my $recurse = $what eq 'RECURSE' || ref $what;
357              
358             my @parts
359 22 50       142 = $body->isNested ? $body->nested->parts($what)
    50          
    50          
360             : $body->isMultipart ? $body->parts($recurse ? 'RECURSE' : ())
361             : $self;
362              
363 22 0       102 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 259     259 1 359 { my $self = shift;
412 259 100       1258 return $self->{MM_labels}{$_[0]} unless @_ > 1;
413 17         35 my $return = $_[1];
414              
415 17         56 my %labels = @_;
416 17         63 @{$self->{MM_labels}}{keys %labels} = values %labels;
  17         57  
417 17         51 $return;
418             }
419              
420              
421             sub labels()
422 10     10 1 19 { my $self = shift;
423 10 50       33 wantarray ? keys %{$self->{MM_labels}} : $self->{MM_labels};
  0         0  
424             }
425              
426              
427 232     232 1 508 sub isDeleted() { shift->label('deleted') }
428              
429              
430             sub delete()
431 4     4 1 17 { my $self = shift;
432 4         11 my $old = $self->label('deleted');
433 4 50       16 $old || $self->label(deleted => time);
434             }
435              
436              
437             sub deleted(;$)
438 2     2 1 4 { my $self = shift;
439              
440 2 50       10 @_ ? $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 10     10 1 29 { my ($class, $message) = @_;
503              
504 10 50       50 blessed $message
505             or die "coercion starts with some object";
506              
507 10 50       40 return $message
508             if ref $message eq $class;
509              
510 10 50       36 if($message->isa(__PACKAGE__)) {
511 10         28 $message->head->modified(1);
512 10         31 $message->body->modified(1);
513 10         31 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 7     7 1 33 { 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 7   33     33 , $self->logSettings
579             );
580              
581 7 50       66 my $body = $self->readBody($parser, $head, $bodytype)
582             or return;
583              
584 7         27 $self->head($head);
585 7         26 $self->storeBody($body);
586 7         20 $self;
587             }
588              
589              
590             sub readHead($;$)
591 12     12 1 29 { my ($self, $parser) = (shift, shift);
592              
593             my $headtype = shift
594 12   50     80 || $self->{MM_head_type} || 'Mail::Message::Head::Complete';
595              
596             $headtype->new
597             ( message => $self
598             , field_type => $self->{MM_field_type}
599 12         66 , $self->logSettings
600             )->read($parser);
601             }
602              
603              
604             my $mpbody = 'Mail::Message::Body::Multipart';
605             my $nbody = 'Mail::Message::Body::Nested';
606             my $lbody = 'Mail::Message::Body::Lines';
607              
608             sub readBody($$;$$)
609 12     12 1 33 { my ($self, $parser, $head, $getbodytype) = @_;
610              
611             my $bodytype
612 12 0 33     66 = ! $getbodytype ? ($self->{MM_body_type} || $lbody)
    50          
613             : ref $getbodytype ? $getbodytype->($self, $head)
614             : $getbodytype;
615              
616 12         21 my $body;
617 12 50       81 if($bodytype->isDelayed)
618 0         0 { $body = $bodytype->new
619             ( message => $self
620             , charset => 'us-ascii'
621             , $self->logSettings
622             );
623             }
624             else
625 12         41 { my $ct = $head->get('Content-Type', 0);
626 12 100       47 my $type = defined $ct ? lc($ct->body) : 'text/plain';
627              
628             # Be sure you have acceptable bodies for multiparts and nested.
629 12 100 66     98 if(substr($type, 0, 10) eq 'multipart/' && !$bodytype->isMultipart)
    100 66        
630 2         4 { $bodytype = $mpbody }
631             elsif($type eq 'message/rfc822' && !$bodytype->isNested)
632 1         2 { $bodytype = $nbody }
633              
634             $body = $bodytype->new
635             ( message => $self
636             , checked => $self->{MM_trusted}
637 12         44 , charset => 'us-ascii'
638             , $self->logSettings
639             );
640              
641 12         55 $body->contentInfoFrom($head);
642             }
643              
644 12         78 my $lines = $head->get('Lines'); # usually off-by-one
645 12         47 my $size = $head->guessBodySize;
646              
647 12 50       168 $body->read
648             ( $parser, $head, $getbodytype,
649             , $size, (defined $lines ? $lines : undef)
650             );
651             }
652              
653              
654             sub storeBody($)
655 12     12 1 24 { my ($self, $body) = @_;
656 12         32 $self->{MM_body} = $body;
657 12         43 $body->message($self);
658 12         16 $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 125     125 1 251 { my $self = shift;
670 125   100     402 my $msgid = (@_ ? shift : $self->get('Message-ID')) || '';
671              
672 125 100       470 if($msgid =~ m/\<([^>]*)\>/s)
673 41         123 { $msgid = $1;
674 41         95 $msgid =~ s/\s//gs;
675             }
676            
677 125 100       434 $msgid = $self->head->createMessageId
678             unless length $msgid;
679              
680 125         362 $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;