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-2022 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.03.
5             # This code is part of distribution Mail-Message. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Message;
10 31     31   7187 use vars '$VERSION';
  31         129  
  31         1748  
11             $VERSION = '3.012';
12              
13 31     31   178 use base 'Mail::Reporter';
  31         76  
  31         7058  
14              
15 31     31   188 use strict;
  31         58  
  31         617  
16 31     31   128 use warnings;
  31         56  
  31         725  
17              
18 31     31   12108 use Mail::Message::Part ();
  31         65  
  31         600  
19 31     31   12763 use Mail::Message::Head::Complete ();
  31         99  
  31         833  
20 31     31   13349 use Mail::Message::Construct ();
  31         74  
  31         697  
21              
22 31     31   5471 use Mail::Message::Body::Lines ();
  31         79  
  31         610  
23 31     31   12656 use Mail::Message::Body::Multipart ();
  31         90  
  31         779  
24 31     31   13644 use Mail::Message::Body::Nested ();
  31         90  
  31         657  
25              
26 31     31   228 use Carp;
  31         93  
  31         1619  
27 31     31   185 use Scalar::Util qw(weaken blessed);
  31         54  
  31         2466  
28              
29             BEGIN {
30 31 50   31   108145 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 253 { my ($self, $args) = @_;
46 106         353 $self->SUPER::init($args);
47              
48             # Field initializations also in coerce()
49 106   50     442 $self->{MM_modified} = $args->{modified} || 0;
50 106   50     390 $self->{MM_trusted} = $args->{trusted} || 0;
51              
52             # Set the header
53              
54 106         162 my $head;
55 106 100 33     298 if(defined($head = $args->{head})) { $self->head($head) }
  99 50       238  
56             elsif(my $msgid = $args->{messageId} || $args->{messageID})
57 0         0 { $self->takeMessageId($msgid);
58             }
59              
60             # Set the body
61 106 100       327 if(my $body = $args->{body})
62 11         19 { $self->{MM_body} = $body;
63 11         50 $body->message($self);
64             }
65              
66             $self->{MM_body_type} = $args->{body_type}
67 106 50       263 if defined $args->{body_type};
68              
69             $self->{MM_head_type} = $args->{head_type}
70 106 50       237 if defined $args->{head_type};
71              
72             $self->{MM_field_type} = $args->{field_type}
73 106 50       266 if defined $args->{field_type};
74              
75 106   50     422 my $labels = $args->{labels} || [];
76 106 50       372 my @labels = ref $labels eq 'ARRAY' ? @$labels : %$labels;
77 106 50       258 push @labels, deleted => $args->{deleted} if exists $args->{deleted};
78 106         242 $self->{MM_labels} = { @labels };
79              
80 106         342 $self;
81             }
82              
83              
84             sub clone(@)
85 10     10 1 497 { 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         26 my ($head, $body) = ($self->head, $self->body);
92             $head = $head->clone
93 10 50 33     65 unless $args{shallow} || $args{shallow_head};
94              
95             $body = $body->clone
96 10 50 33     69 unless $args{shallow} || $args{shallow_body};
97              
98 10         52 my $clone = Mail::Message->new
99             ( head => $head
100             , body => $body
101             , $self->logSettings
102             );
103              
104 10         44 my $labels = $self->labels;
105 10         28 my %labels = %$labels;
106 10         16 delete $labels{deleted};
107              
108 10         18 $clone->{MM_labels} = \%labels;
109              
110 10         16 $clone->{MM_cloned} = $self;
111 10         26 weaken($clone->{MM_cloned});
112              
113 10         49 $clone;
114             }
115              
116             #------------------------------------------
117              
118              
119 39 50   39 1 280 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 659 { my $self = shift;
131 2         7 my $cont = $self->container;
132 2 50       12 $cont ? $cont->partNumber : undef;
133             }
134              
135              
136 10     10 1 41 sub toplevel() { shift } # overridden by Mail::Message::Part
137              
138              
139             sub isDummy() { 0 }
140              
141              
142             sub print(;$)
143 31     31 1 399 { my $self = shift;
144 31   33     72 my $out = shift || select;
145              
146 31         148 $self->head->print($out);
147 31         68 my $body = $self->body;
148 31 50       85 $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 92 { my $self = shift;
190 55         112 $self->head->size + $self->body->size;
191             }
192              
193             #------------------------------------------
194              
195              
196             sub head(;$)
197 930     930 1 7026 { my $self = shift;
198 930 100       3430 return $self->{MM_head} unless @_;
199              
200 111         193 my $head = shift;
201 111 50       271 unless(defined $head)
202 0         0 { delete $self->{MM_head};
203 0         0 return undef;
204             }
205              
206 111 50 33     715 $self->log(INTERNAL => "wrong type of head ($head) for message $self")
207             unless ref $head && $head->isa('Mail::Message::Head');
208              
209 111         436 $head->message($self);
210              
211 111 50       367 if(my $old = $self->{MM_head})
212 0 0       0 { $self->{MM_modified}++ unless $old->isDelayed;
213             }
214              
215 111         262 $self->{MM_head} = $head;
216              
217 111 50       594 $self->takeMessageId unless $head->isDelayed;
218              
219 111         213 $head;
220             }
221              
222              
223             sub get($)
224 159     159 1 233 { my $self = shift;
225 159   100     337 my $field = $self->head->get(shift) || return undef;
226 66         224 $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 29 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 45 { my $subject = shift->get('subject');
269 15 100       114 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 7 { my $head = shift->head;
278 2 50       25 $head->recvstamp || $head->timestamp;
279             }
280              
281              
282             sub nrLines()
283 39     39 1 74 { my $self = shift;
284 39         96 $self->head->nrLines + $self->body->nrLines;
285             }
286              
287             #-------------------------------------------
288              
289            
290             sub body(;$@)
291 511     511 1 5493 { my $self = shift;
292 511 100       2042 return $self->{MM_body} unless @_;
293              
294 83         182 my $head = $self->head;
295 83 50       456 $head->removeContentInfo if defined $head;
296              
297 83         240 my ($rawbody, %args) = @_;
298 83 50       199 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     602 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       542 my $body = $self->isPart ? $rawbody : $rawbody->encoded;
313 83         318 $body->contentInfoTo($self->head);
314              
315 83         185 my $oldbody = $self->{MM_body};
316 83 50 33     372 return $body if defined $oldbody && $body==$oldbody;
317              
318 83         399 $body->message($self);
319 83 50       249 $body->modified(1) if defined $oldbody;
320              
321 83         402 $self->{MM_body} = $body;
322             }
323              
324              
325             sub decoded(@)
326 2     2 1 9 { my $body = shift->body->load;
327 2 50       10 $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 4410 sub isMultipart() {shift->head->isMultipart}
338              
339              
340 60     60 1 642 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 1261 { my $self = shift;
353 22   100     91 my $what = shift || 'ACTIVE';
354              
355 22         65 my $body = $self->body;
356 22   33     118 my $recurse = $what eq 'RECURSE' || ref $what;
357              
358             my @parts
359 22 50       183 = $body->isNested ? $body->nested->parts($what)
    50          
    50          
360             : $body->isMultipart ? $body->parts($recurse ? 'RECURSE' : ())
361             : $self;
362              
363 22 0       132 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 353 { my $self = shift;
412 259 100       1229 return $self->{MM_labels}{$_[0]} unless @_ > 1;
413 17         34 my $return = $_[1];
414              
415 17         57 my %labels = @_;
416 17         60 @{$self->{MM_labels}}{keys %labels} = values %labels;
  17         58  
417 17         44 $return;
418             }
419              
420              
421             sub labels()
422 10     10 1 18 { my $self = shift;
423 10 50       23 wantarray ? keys %{$self->{MM_labels}} : $self->{MM_labels};
  0         0  
424             }
425              
426              
427 232     232 1 500 sub isDeleted() { shift->label('deleted') }
428              
429              
430             sub delete()
431 4     4 1 23 { my $self = shift;
432 4         17 my $old = $self->label('deleted');
433 4 50       26 $old || $self->label(deleted => time);
434             }
435              
436              
437             sub deleted(;$)
438 2     2 1 5 { 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 26 { my ($class, $message) = @_;
503              
504 10 50       42 blessed $message
505             or die "coercion starts with some object";
506              
507 10 50       32 return $message
508             if ref $message eq $class;
509              
510 10 50       35 if($message->isa(__PACKAGE__)) {
511 10         25 $message->head->modified(1);
512 10         24 $message->body->modified(1);
513 10         26 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 27 { 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     20 , $self->logSettings
579             );
580              
581 7 50       34 my $body = $self->readBody($parser, $head, $bodytype)
582             or return;
583              
584 7         23 $self->head($head);
585 7         46 $self->storeBody($body);
586 7         20 $self;
587             }
588              
589              
590             sub readHead($;$)
591 12     12 1 24 { my ($self, $parser) = (shift, shift);
592              
593             my $headtype = shift
594 12   50     65 || $self->{MM_head_type} || 'Mail::Message::Head::Complete';
595              
596             $headtype->new
597             ( message => $self
598             , field_type => $self->{MM_field_type}
599 12         56 , $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 27 { my ($self, $parser, $head, $getbodytype) = @_;
610              
611             my $bodytype
612 12 0 33     64 = ! $getbodytype ? ($self->{MM_body_type} || $lbody)
    50          
613             : ref $getbodytype ? $getbodytype->($self, $head)
614             : $getbodytype;
615              
616 12         18 my $body;
617 12 50       95 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         46 { my $ct = $head->get('Content-Type', 0);
626 12 100       46 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     100 if(substr($type, 0, 10) eq 'multipart/' && !$bodytype->isMultipart)
    100 66        
630 2         5 { $bodytype = $mpbody }
631             elsif($type eq 'message/rfc822' && !$bodytype->isNested)
632 1         3 { $bodytype = $nbody }
633              
634             $body = $bodytype->new
635             ( message => $self
636             , checked => $self->{MM_trusted}
637 12         42 , charset => 'us-ascii'
638             , $self->logSettings
639             );
640              
641 12         43 $body->contentInfoFrom($head);
642             }
643              
644 12         32 my $lines = $head->get('Lines'); # usually off-by-one
645 12         38 my $size = $head->guessBodySize;
646              
647 12 50       55 $body->read
648             ( $parser, $head, $getbodytype,
649             , $size, (defined $lines ? $lines : undef)
650             );
651             }
652              
653              
654             sub storeBody($)
655 12     12 1 28 { my ($self, $body) = @_;
656 12         35 $self->{MM_body} = $body;
657 12         33 $body->message($self);
658 12         17 $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 248 { my $self = shift;
670 125   100     433 my $msgid = (@_ ? shift : $self->get('Message-ID')) || '';
671              
672 125 100       471 if($msgid =~ m/\<([^>]*)\>/s)
673 41         161 { $msgid = $1;
674 41         106 $msgid =~ s/\s//gs;
675             }
676            
677 125 100       365 $msgid = $self->head->createMessageId
678             unless length $msgid;
679              
680 125         444 $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;