File Coverage

blib/lib/Mail/Message/Body.pm
Criterion Covered Total %
statement 165 202 81.6
branch 85 122 69.6
condition 58 101 57.4
subroutine 36 53 67.9
pod 32 34 94.1
total 376 512 73.4


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::Body;{
13             our $VERSION = '4.04';
14             }
15              
16 37     37   173887 use parent 'Mail::Reporter';
  37         88  
  37         305  
17              
18 37     37   2628 use strict;
  37         73  
  37         1121  
19 37     37   174 use warnings;
  37         161  
  37         2468  
20              
21 37     37   277 use Log::Report 'mail-message', import => [ qw/__x error fault info panic/ ];
  37         122  
  37         412  
22              
23 37     37   7790 use Scalar::Util qw/weaken refaddr blessed/;
  37         147  
  37         2956  
24 37     37   282 use File::Basename qw/basename/;
  37         117  
  37         3461  
25              
26 37     37   10519 use Mail::Message::Field ();
  37         117  
  37         1201  
27 37     37   5326 use Mail::Message::Body::Lines ();
  37         90  
  37         1011  
28 37     37   22489 use Mail::Message::Body::File ();
  37         165  
  37         1519  
29              
30 37     37   21701 use MIME::Types ();
  37         234588  
  37         6592  
31             my $mime_types = MIME::Types->new;
32             my $mime_plain = $mime_types->type('text/plain');
33              
34             #--------------------
35              
36             use overload
37 830     830   4748 bool => sub {1}, # $body->print if $body
38             '""' => 'string_unless_carp',
39             '@{}' => 'lines',
40 6 50   6   77 '==' => sub {ref $_[1] && refaddr $_[0] == refaddr $_[1]},
41 37 50   37   362 '!=' => sub {ref $_[1] && refaddr $_[0] != refaddr $_[1]};
  37     53   83  
  37         573  
  53         636  
42              
43             #--------------------
44              
45             my $body_count = 0; # to be able to compare bodies for equivalence.
46              
47             sub new(@)
48 327     327 1 2758543 { my $class = shift;
49 327 100       2124 $class eq __PACKAGE__ or return $class->SUPER::new(@_);
50              
51 19         93 my %args = @_;
52 19 50       214 exists $args{file} ? Mail::Message::Body::File->new(@_) : Mail::Message::Body::Lines->new(@_);
53             }
54              
55             # All body implementations shall implement all of the following!!
56 0     0   0 sub _data_from_filename(@) { $_[0]->notImplemented }
57 0     0   0 sub _data_from_filehandle(@) { $_[0]->notImplemented }
58 0     0   0 sub _data_from_lines(@) { $_[0]->notImplemented }
59              
60             sub init($)
61 308     308 0 818 { my ($self, $args) = @_;
62 308         1300 $self->SUPER::init($args);
63              
64 308   50     4753 $self->{MMB_modified} = $args->{modified} || 0;
65              
66 308         689 my $filename = $args->{filename};
67 308         695 my $mime = $args->{mime_type};
68              
69 308 100 100     2340 if(defined(my $file = $args->{file}))
    100          
    100          
70             {
71 3 50 33     60 if(!ref $file)
    50 33        
72 0 0       0 { $self->_data_from_filename($file) or return;
73 0   0     0 $filename ||= $file;
74 0   0     0 $mime ||= $mime_types->mimeTypeOf($filename) || (-T $file ? 'text/plain' : 'application/octet-stream');
      0        
75             }
76             elsif(ref $file eq 'GLOB' || (blessed $file && $file->isa('IO::Handle')))
77 3 50       25 { $self->_data_from_filehandle($file) or return;
78             }
79             else
80 0   0     0 { error __x"message body: illegal datatype '{type}' for file option.", type => ref $file // $file;
81             }
82             }
83             elsif(defined(my $data = $args->{data}))
84             {
85 155 100       617 if(!ref $data)
    50          
86 88         404 { my @lines = split /^/, $data;
87 88         414 $self->_data_from_lines(\@lines)
88             }
89             elsif(ref $data eq 'ARRAY')
90 67 50       293 { $self->_data_from_lines($data) or return;
91             }
92             else
93 0   0     0 { error __x"message body: illegal datatype '{type}' for data option.", type => ref $data // $data;
94             }
95             }
96             elsif(! $self->isMultipart && ! $self->isNested)
97             { # Neither 'file' nor 'data', so empty body.
98 89 50       386 $self->_data_from_lines( [] ) or return;
99             }
100              
101             # Set the content info
102              
103 308         1396 my ($transfer, $disp, $descr, $cid, $lang) = @$args{ qw/transfer_encoding disposition description content_id language/ };
104              
105 308 50       825 if(defined $filename)
106 0 0 0     0 { $disp //= Mail::Message::Field->new(
107             'Content-Disposition' => (-T $filename ? 'inline' : 'attachment'),
108             filename => basename($filename)
109             );
110 0   0     0 $mime //= $mime_types->mimeTypeOf($filename);
111             }
112              
113 308 50 66     1574 if(ref $mime && $mime->isa('MIME::Type'))
114 0         0 { $mime = $mime->type;
115             }
116              
117 308 100       867 if(defined(my $based = $args->{based_on}))
118 131   66     510 { $mime //= $based->type;
119 131   66     677 $transfer //= $based->transferEncoding;
120 131   33     930 $disp //= $based->disposition;
121 131   33     645 $descr //= $based->description;
122 131   33     858 $lang //= $based->language;
123 131   33     738 $cid //= $based->contentId;
124              
125 131 100       676 $self->{MMB_checked} = exists $args->{checked} ? $args->{checked} : $based->checked;
126             }
127             else
128 177         411 { $transfer = $args->{transfer_encoding};
129 177   100     889 $self->{MMB_checked} = $args->{checked} || 0;
130             }
131              
132 308   100     1086 $mime ||= 'text/plain';
133 308         1065 $mime = $self->type($mime);
134              
135 308 100       1169 my $default_charset = exists $args->{charset} ? $args->{charset} : 'PERL';
136 308 100 100     2455 $mime->attribute(charset => $default_charset)
      100        
137             if $default_charset
138             && $mime =~ m!^text/!i
139             && !$mime->attribute('charset');
140              
141 308 100       1567 $self->transferEncoding($transfer) if defined $transfer;
142 308 100       1004 $self->disposition($disp) if defined $disp;
143 308 100       989 $self->description($descr) if defined $descr;
144 308 50       782 $self->language($lang) if defined $lang;
145 308 100       920 $self->contentId($cid) if defined $cid;
146 308         1257 $self->type($mime);
147              
148             # Set message where the body belongs to.
149              
150             $self->message($args->{message})
151 308 100       1393 if defined $args->{message};
152              
153 308         919 $self->{MMB_seqnr} = $body_count++;
154 308         2071 $self;
155             }
156              
157              
158              
159 0     0 1 0 sub clone() { $_[0]->notImplemented }
160              
161             #--------------------
162              
163             sub decoded(@)
164 25     25 1 72 { my $self = shift;
165 25         120 $self->encode(charset => 'PERL', transfer_encoding => 'none', @_);
166             }
167              
168             #--------------------
169              
170             sub message(;$)
171 260     260 1 482 { my $self = shift;
172 260 100       667 if(@_)
173 249 50       1045 { if($self->{MMB_message} = shift)
174 249         671 { weaken $self->{MMB_message};
175             }
176             }
177 260         11835 $self->{MMB_message};
178             }
179              
180              
181             sub isDelayed() {0}
182              
183              
184             sub isMultipart() {0}
185              
186              
187             sub isNested() {0}
188              
189              
190             sub partNumberOf($)
191 0     0 1 0 { error __x"part number needs multi-part or nested.";
192             }
193              
194             #--------------------
195              
196             sub type(;$)
197 1231     1231 1 3669 { my $self = shift;
198 1231 100 66     7576 return $self->{MMB_type} if !@_ && defined $self->{MMB_type};
199              
200 719         1694 delete $self->{MMB_mime};
201 719   100     1859 my $type = shift // 'text/plain';
202              
203 719 100       4005 $self->{MMB_type} = blessed $type ? $type->clone : Mail::Message::Field->new('Content-Type' => $type);
204             }
205              
206              
207             sub mimeType()
208 108     108 1 3053 { my $self = shift;
209 108 100       932 return $self->{MMB_mime} if exists $self->{MMB_mime};
210              
211 69         168 my $field = $self->{MMB_type};
212 69 50       562 my $body = defined $field ? $field->body : '';
213 69 50 33     600 $self->{MMB_mime} = length $body ? ($mime_types->type($body) || MIME::Type->new(type => $body)) : $mime_plain;
214             }
215              
216              
217 62     62 1 8165 sub charset() { $_[0]->type->attribute('charset') }
218              
219              
220             sub transferEncoding(;$)
221 633     633 1 10519 { my $self = shift;
222 633 100 100     3174 return $self->{MMB_transfer} if !@_ && defined $self->{MMB_transfer};
223              
224 337   100     3162 my $set = shift // 'none';
225 337 100       1663 $self->{MMB_transfer} = blessed $set ? $set->clone : Mail::Message::Field->new('Content-Transfer-Encoding' => $set);
226             }
227              
228              
229             sub description(;$)
230 452     452 1 1197 { my $self = shift;
231 452 100 100     1565 return $self->{MMB_description} if !@_ && $self->{MMB_description};
232              
233 291   100     987 my $disp = shift // 'none';
234 291 100       1110 $self->{MMB_description} = blessed $disp ? $disp->clone : Mail::Message::Field->new('Content-Description' => $disp);
235             }
236              
237              
238             sub disposition(;$)
239 454     454 1 730 { my $self = shift;
240 454 100 100     1853 return $self->{MMB_disposition} if !@_ && $self->{MMB_disposition};
241              
242 291   100     912 my $disp = shift // 'none';
243 291 100       1171 $self->{MMB_disposition} = blessed $disp ? $disp->clone : Mail::Message::Field->new('Content-Disposition' => $disp);
244             }
245              
246              
247             sub language(@)
248 321     321 1 554 { my $self = shift;
249 321 50 66     1359 return $self->{MMB_lang} if !@_ && $self->{MMB_lang};
250              
251             my $langs
252             = @_ > 1 ? (join ', ', @_)
253             : blessed $_[0] ? $_[0]
254 321 50       2004 : ref $_[0] eq 'ARRAY' ? (join ', ', @{$_[0]})
  0 50       0  
    50          
255             : $_[0];
256              
257             $self->{MMB_lang}
258 321 0 33     2031 = ! defined $langs || ! length $langs ? undef
    50          
259             : blessed $langs ? $langs->clone
260             : Mail::Message::Field->new('Content-Language' => $langs);
261             }
262              
263              
264             sub contentId(;$)
265 452     452 1 752 { my $self = shift;
266 452 100 100     1530 return $self->{MMB_id} if !@_ && $self->{MMB_id};
267              
268 291   100     947 my $cid = shift // 'none';
269 291 100       1079 $self->{MMB_id} = blessed $cid ? $cid->clone : Mail::Message::Field->new('Content-ID' => $cid);
270             }
271              
272              
273             sub checked(;$)
274 113     113 1 3076 { my $self = shift;
275 113 100       597 @_ ? ($self->{MMB_checked} = shift) : $self->{MMB_checked};
276             }
277              
278              
279 0     0 1 0 sub nrLines(@) { $_[0]->notImplemented }
280              
281              
282 0     0 1 0 sub size(@) { $_[0]->notImplemented }
283              
284             #--------------------
285              
286 0     0 1 0 sub string() { $_[0]->notImplemented }
287              
288             sub string_unless_carp()
289 27     27 0 24032 { my $self = shift;
290 27 50       197 (caller)[0] eq 'Carp' or return $self->string;
291              
292 0         0 my $class = ref $self =~ s/^Mail::Message/MM/r;
293 0         0 "$class object";
294             }
295              
296              
297 0     0 1 0 sub lines() { $_[0]->notImplemented }
298              
299              
300 0     0 1 0 sub file(;$) { $_[0]->notImplemented }
301              
302              
303 0     0 1 0 sub print(;$) { $_[0]->notImplemented }
304              
305              
306 0     0 1 0 sub printEscapedFrom($) { $_[0]->notImplemented }
307              
308              
309             sub write(@)
310 0     0 1 0 { my ($self, %args) = @_;
311             my $filename = $args{filename}
312 0 0       0 or error __x"no filename parameter for write() body.";
313              
314             #XXX encoding?
315 0 0       0 open my $out, '>', $filename
316             or fault __x"cannot open {file} to write body", file => $filename;
317              
318 0         0 $self->print($out);
319 0 0       0 $out->close
320             or fault __x"error closing {file} after write body", file => $filename;
321              
322 0         0 $self;
323             }
324              
325              
326 0     0 1 0 sub endsOnNewline() { $_[0]->notImplemented }
327              
328              
329 0     0 1 0 sub stripTrailingNewline() { $_[0]->notImplemented }
330              
331             #--------------------
332              
333 0     0 1 0 sub read(@) { $_[0]->notImplemented }
334              
335              
336             sub contentInfoTo($)
337 87     87 1 357 { my ($self, $head) = @_;
338 87 50       221 return unless defined $head;
339              
340 87         381 my $lines = $self->nrLines;
341 87         432 my $size = $self->size;
342 87 50       279 $size += $lines if $Mail::Message::crlf_platform;
343              
344 87         262 $head->set($self->type);
345 87         283 $head->set($self->transferEncoding);
346 87         317 $head->set($self->disposition);
347 87         348 $head->set($self->description);
348 87         292 $head->set($self->language);
349 87         277 $head->set($self->contentId);
350 87         334 $self;
351             }
352              
353              
354             sub contentInfoFrom($)
355 103     103 1 229 { my ($self, $head) = @_;
356              
357 103         360 $self->type($head->get('Content-Type', 0));
358              
359             my ($te, $disp, $desc, $cid, $lang) = map {
360 103   100     292 my $x = $head->get("Content-$_") || '';
  515         1412  
361 515         1434 s/^\s+//,s/\s+$// for $x;
362 515 100       1368 length $x ? $x : undef;
363             } qw/Transfer-Encoding Disposition Description ID Language/;
364              
365 103         471 $self->transferEncoding($te);
366 103         490 $self->disposition($disp);
367 103         387 $self->description($desc);
368 103         416 $self->language($lang);
369 103         409 $self->contentId($cid);
370              
371 103         224 delete $self->{MMB_mime};
372 103         442 $self;
373              
374             }
375              
376              
377             sub modified(;$)
378 13     13 1 21 { my $self = shift;
379 13 50       35 @_ or return $self->isModified; # compat 2.036
380 13         34 $self->{MMB_modified} = shift;
381             }
382              
383              
384 22     22 1 73 sub isModified() { $_[0]->{MMB_modified} }
385              
386              
387             sub fileLocation(;@)
388 121     121 1 217 { my $self = shift;
389 121 100       303 @_ or return @$self{ qw/MMB_begin MMB_end/ };
390 105         585 @$self{ qw/MMB_begin MMB_end/ } = @_;
391             }
392              
393              
394             sub moveLocation($)
395 0     0 1 0 { my ($self, $dist) = @_;
396 0         0 $self->{MMB_begin} -= $dist;
397 0         0 $self->{MMB_end} -= $dist;
398 0         0 $self;
399             }
400              
401              
402 2     2 1 7 sub load() { $_[0] }
403              
404             #--------------------
405              
406             my @in_encode = qw/check encode encoded eol isBinary isText unify dispositionFilename/;
407             my %in_module = map +($_ => 'encode'), @in_encode;
408              
409             sub AUTOLOAD(@)
410 16     16   3258 { my $self = shift;
411 16         37 our $AUTOLOAD;
412 16         241 my $call = $AUTOLOAD =~ s/.*\:\://gr;
413              
414 16   100     265 my $mod = $in_module{$call} || 'construct';
415 16 100       146 if($mod eq 'encode') { require Mail::Message::Body::Encode }
  13         11554  
416 3         2178 else { require Mail::Message::Body::Construct }
417              
418 37     37   142086 no strict 'refs';
  37         94  
  37         6151  
419 16 50       435 return $self->$call(@_) if $self->can($call); # now loaded
420              
421             # AUTOLOAD inheritance is a pain
422 0           panic "method $call() is not defined for a " . ref $self;
423             }
424              
425             #--------------------
426              
427             1;